123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335openCore_kernelopenInt.Replace_polymorphic_comparetypedims={width:int;height:int}letsexp_of_dims{width;height}=sexp_of_string(sprintf"w%dh%d"widthheight)letdims_invariant{width;height}=assert(width>=0);assert(height>=0);;typevalign=[`Top|`Bottom|`Center][@@derivingsexp_of]typehalign=[`Left|`Right|`Center][@@derivingsexp_of]typet=|Textofstring|Fillofchar*dims|Hcatoft*t*dims|Vcatoft*t*dims|Ansiofstringoption*t*stringoption*dims[@@derivingsexp_of]letheight=function|Text_->1|Fill(_,d)|Hcat(_,_,d)|Vcat(_,_,d)|Ansi(_,_,_,d)->d.height;;letwidth=function|Texts->String.lengths|Fill(_,d)|Hcat(_,_,d)|Vcat(_,_,d)|Ansi(_,_,_,d)->d.width;;letrecinvariantt=matchtwith|Texts->assert(not(String.mems'\n'))|Fill(_,dims)->dims_invariantdims|Hcat(t1,t2,dims)->dims_invariantdims;invariantt1;invariantt2;[%test_result:int](heightt1)~expect:dims.height;[%test_result:int](heightt2)~expect:dims.height;[%test_result:int](widtht1+widtht2)~expect:dims.width|Vcat(t1,t2,dims)->dims_invariantdims;invariantt1;invariantt2;[%test_result:int](widtht1)~expect:dims.width;[%test_result:int](widtht2)~expect:dims.width;[%test_result:int](heightt1+heightt2)~expect:dims.height|Ansi(_,t,_,dims)->dims_invariantdims;invariantt;[%test_result:int](widtht)~expect:dims.width;[%test_result:int](heightt)~expect:dims.height;;letfill_genericch~width~height=assert(width>=0);assert(height>=0);Fill(ch,{width;height});;letfillch~width~height=fill_genericch~width~heightletspace~width~height=fill_generic' '~width~heightletnil=space~width:0~height:0lethstrutwidth=space~width~height:0letvstrutheight=space~height~width:0letdimst={width=widtht;height=heightt}lethalven=letfst=n/2inletsnd=fst+(nmod2)infst,snd;;letansi_escape?prefix?suffixt=Ansi(prefix,t,suffix,dimst)letrechpadt~aligndelta=assert(delta>=0);ifdelta=0thentelse(letheight=heighttinletpad=space~height~width:deltainmatchalignwith|`Left->Hcat(t,pad,{height;width=widtht+delta})|`Right->Hcat(pad,t,{height;width=widtht+delta})|`Center->letdelta1,delta2=halvedeltainlett=hpadt~align:`Leftdelta1inlett=hpadt~align:`Rightdelta2int);;letrecvpadt~aligndelta=assert(delta>=0);ifdelta=0thentelse(letwidth=widthtinletpad=space~width~height:deltainmatchalignwith|`Top->Vcat(t,pad,{width;height=heightt+delta})|`Bottom->Vcat(pad,t,{width;height=heightt+delta})|`Center->letdelta1,delta2=halvedeltainlett=vpadt~align:`Topdelta1inlett=vpadt~align:`Bottomdelta2int);;letmax_heightts=List.foldts~init:0~f:(funacct->Int.maxacc(heightt))letmax_widthts=List.foldts~init:0~f:(funacct->Int.maxacc(widtht))letvalignalignts=leth=max_heighttsinList.mapts~f:(funt->vpad~alignt(h-heightt));;lethalignalignts=letw=max_widthtsinList.mapts~f:(funt->hpad~alignt(w-widtht));;lethcat?(align=`Top)?septs=letts=Option.foldsep~init:ts~f:(funtssep->List.interspersets~sep)inletts=valignaligntsinmatchtswith|[]->nil|t::ts->List.fold~init:tts~f:(funacct->assert(heightacc=heightt);Hcat(acc,t,{height=heightacc;width=widthacc+widtht}));;letvcat?(align=`Left)?septs=letts=Option.foldsep~init:ts~f:(funtssep->List.interspersets~sep)inletts=halignaligntsinmatchtswith|[]->nil|t::ts->List.fold~init:tts~f:(funacct->assert(widthacc=widtht);Vcat(acc,t,{width=widthacc;height=heightacc+heightt}));;lettext_of_lineslines~align=lines|>List.map~f:(funline->Textline)|>vcat~align;;lettext_no_wrap~alignstr=ifString.memstr'\n'thenString.split~on:'\n'str|>text_of_lines~alignelseTextstr;;letword_wrapstr~max_width=String.splitstr~on:' '|>List.concat_map~f:(String.split~on:'\n')|>List.fold~init:(Fqueue.empty,Fqueue.empty,0)~f:(fun(lines,line,len)word->letn=String.lengthwordinletn'=len+1+ninifn'>max_widththenFqueue.enqueuelinesline,Fqueue.singletonword,nelselines,Fqueue.enqueuelineword,n')|>(fun(lines,line,_)->Fqueue.enqueuelinesline)|>Fqueue.map~f:(funline->Fqueue.to_listline|>String.concat~sep:" ")|>Fqueue.to_list;;lettext?(align=`Left)?max_widthstr=matchmax_widthwith|None->text_no_wrap~alignstr|Somemax_width->word_wrapstr~max_width|>text_of_lines~align;;(* an abstract renderer, instantiated once to compute line lengths and then again to
actually produce a string. *)letrender_abstractt~write_direct~line_length=forj=0toheightt-1dowrite_direct'\n'(line_lengthj)jdone;letnext_i=Array.init(heightt)~f:(fun_->0)inletadd_charcj=leti=next_i.(j)innext_i.(j)<-i+1;write_directcijinletwrite_stringsj=fori=0toString.lengths-1doadd_chars.[i]jdoneinletrecauxtj_offset=matchtwith|Texts->write_stringsj_offset|Fill(ch,d)->for_i=0tod.width-1doforj=0tod.height-1doadd_charch(j+j_offset)donedone|Vcat(t1,t2,_)->auxt1j_offset;auxt2(j_offset+heightt1)|Hcat(t1,t2,_)->auxt1j_offset;auxt2j_offset|Ansi(prefix,t,suffix,_)->letvcopys=Option.iters~f:(funs->forj=0toheightt-1dowrite_strings(j+j_offset)done)invcopyprefix;auxtj_offset;vcopysuffixinauxt0;;letline_lengthst=letr=Array.create~len:(heightt)0inletwrite_directcij=ifnot(Char.is_whitespacec)thenr.(j)<-Int.maxr.(j)(i+1)inletline_length_=-1(* doesn't matter *)inrender_abstractt~write_direct~line_length;r;;letrendert=letheight=heighttinifheight=0then""else(letline_lengths=line_lengthstinletline_offsets,buflen=letr=Array.create~len:height0inletline_offsetj=r.(j-1)+line_lengths.(j-1)+1inforj=1toheight-1dor.(j)<-line_offsetjdone;r,line_offsetheightinletbuf=Bytes.makebuflen' 'inletwrite_directcij=ifChar.equalc'\n'||i<line_lengths.(j)thenBytes.setbuf(i+line_offsets.(j))cinletline_lengthj=line_lengths.(j)inrender_abstractt~write_direct~line_length;Bytes.unsafe_to_string~no_mutation_while_string_reachable:buf);;(* header compression *)letrecconsx=function|[]->[x]|y::zs->ifheightx<heightythenx::y::zselsecons(hcat~align:`Bottom[x;y])zs;;letcompress_table_header?(sep_width=2)(`Colscols)=letcols=List.mapcols~f:(fun(header,data,align)->header,Int.max1(max_widthdata),halignaligndata)inletheader=hcat~align:`Bottom(List.fold_rightcols~init:[]~f:(fun(header,max_width,_)stairs->letrecloopstairsacc=letstop()=cons(vcat~align:`Left[header;acc])stairsinmatchstairswith|[]->stop()|x::rest->ifwidthheader+sep_width<=widthaccthenstop()elselooprest(hcat[vcat~align:`Left[fill'|'~width:1~height:(heightx-heightacc);acc];x])inloopstairs(vcat~align:`Left[text"|";hstrut(max_width+sep_width)])))inletrows=List.mapcols~f:(fun(_,_,data)->data)|>List.transpose_exn|>List.map~f:(funrow->hcatrow~sep:(hstrutsep_width))in`Headerheader,`Rowsrows;;lettable?(sep_width=2)(`Colscols)=letcols=List.mapcols~f:(fun(data,align)->Int.max1(max_widthdata),halignaligndata)inletrows=List.mapcols~f:(fun(_,data)->data)|>List.transpose_exn|>List.map~f:(funrow->hcatrow~sep:(hstrutsep_width))in`Rowsrows;;(* convenience definitions *)letvsep=vstrut1lethsep=hstrut1letindent?(n=2)t=hcat[hstrutn;t]letsexpsexp_of_aa=sexp_of_aa|>Sexp.to_string_hum|>textlettextf?align?max_widthfmt=ksprintf(text?align?max_width)fmt