1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051openCommonmoduletypePRINTER=sigtypetvalempty:tval(<+>):t->t->tvalchar:char->tvalsubstring:string->int->int->tvalfill:int->char->tendmoduletypeSIG=sigtypetvalempty:tvalsubstring:string->int->int->tvalstring:string->tvalchar:char->tvalfill:int->char->tvalline:string->tvalcut:tvalspace:tvalnest:int->t->tvalnest_list:int->tlist->tvalnest_relative:int->t->tvalgroup:t->tvalgroup_list:tlist->tvalwrap_words:string->tvalfill_paragraph:string->tval(<+>):t->t->tvalchain:tlist->tvalchain_separated:tlist->t->tvallist_separated:t->tlist->tendmoduleText=structtypet=|Stringofstring*int*int|Fillofint*char|Charofcharletstringsil=assert(0<=i);assert(0<=l);assert(i+l<=String.lengths);String(s,i,l)letcharc=Charcletfillnc=Fill(n,c)letlength=function|String(_,_,l)->l|Fill(n,_)->n|Char_->1letapply(f1:string->int->int->'a)(f2:int->char->'a)(f3:char->'a):t->'a=function|String(s,i,l)->f1sil|Fill(n,c)->f2nc|Charc->f3cendmoduleLine=structtypet={s:string;i:int}letmakesi={s;i}lettext(l:t):string=l.sletlengthl=String.lengthl.sletindentl=l.iend(* Gammar
d ::= t* g* c* -- document
g ::= [| g* c* |] -- group, at least one LB, either direct or
-- indirect
c ::= l t* g* -- chunk
*)typechunk={line:Line.t;texts:Text.tlist;cgroups:grouplist}andgroup={len:int;groups:grouplist;chunks:chunklist}moduleChunk=structletline(c:chunk):Line.t=c.lineletgroups(c:chunk):grouplist=c.cgroupslettexts(c:chunk):Text.tlist=c.textsletmake(line:Line.t):chunk={line;texts=[];cgroups=[]}letadd_text(t:Text.t)(c:chunk):chunk=assert(c.cgroups=[]);{cwithtexts=t::c.texts}letadd_group(g:group)(c:chunk):chunk={cwithcgroups=g::c.cgroups}endmoduleGroup=structletlength(g:group):int=g.lenletempty={len=0;groups=[];chunks=[]}letgroups(g:group):grouplist=g.groupsletchunks(g:group):chunklist=g.chunksletadd_text(t:Text.t)(g:group):group=matchg.chunkswith|[]->assertfalse(* Illegal call *)|c::tl->{gwithlen=g.len+Text.lengtht;chunks=Chunk.add_texttc::tl}letadd_line(l:Line.t)(g:group):group={gwithlen=g.len+Line.lengthl;chunks=Chunk.makel::g.chunks}letadd_group(gi:group)(go:group):group=letlen=go.len+gi.leninmatchgo.chunkswith|[]->{gowithlen;groups=gi::go.groups}|c::cs->{gowithlen;chunks=Chunk.add_groupgic::cs}endmoduleBuffer=structtypet={gs:grouplist;l:int;(* length *)o:int(* open groups*)}letis_empty(b:t):bool=(b.o=0)letlength(b:t):int=b.lletcount(b:t):int=b.oletgroups(b:t):grouplist=b.gsletempty:t={gs=[];l=0;o=0;}letpush(g:group)(b:t):t={gs=g::b.gs;l=Group.lengthg+b.l;o=b.o+1}letadd_text(t:Text.t)(b:t):t=matchb.gswith|[]->assertfalse(* Illegal call *)|g::tl->{bwithgs=Group.add_texttg::tl;l=b.l+Text.lengtht}letadd_line(l:Line.t)(b:t):t=matchb.gswith|[]->assertfalse(* Illegal call *)|g::tl->{bwithgs=Group.add_linelg::tl;l=b.l+Line.lengthl}letopen_groups(n:int)(b:t):t=assert(0<=n);letrecogsn=ifn=0thenb.gselseGroup.empty::ogs(n-1)in{bwitho=b.o+n;gs=ogsn}letclose_groups(n:int)(b:t):t=assert(0<=n);assert(n<b.o);letrecclosengs=ifn=0thengselsematchgswith|gi::go::tl->close(n-1)(Group.add_groupgigo::tl)|_->assertfalse(* Illegal call: cannot close group unless there is
one group to which it can be added. *)in{bwitho=b.o-n;gs=closenb.gs}endmoduleState=structtypeindent={line_indent:int;(* Indent of the current line *)current_indent:int;(* Current indentation level *)pos:int(* Position on the current line *)}typegroups={oe:int;(* open effective groups *)oa:int;(* open active groups *)o_r:int;(* open groups to the right of the last open
group in buffer *)}typeparams={width:int;(* desired maximal line width *)ribbon:int;(* desired maximal ribbon width *)}typet={params:params;indent:indent;groups:groups;buffer:Buffer.t}letinit(i:int)(width:int)(ribbon:int):t={params={width;ribbon};indent={line_indent=i;current_indent=i;pos=i};groups={oe=0;oa=0;o_r=0};buffer=Buffer.empty}letnormal(st:t):bool=Buffer.is_emptyst.bufferletbuffering(st:t):bool=not(Buffer.is_emptyst.buffer)letposition(st:t):int=st.indent.posletcurrent_indent(st:t):int=st.indent.current_indentletrelative_position(st:t):int=st.indent.pos-st.indent.current_indentletfits_pos(p:int)(st:t):bool=(* Is position [p] allowed, i.e. is it within the line width and the
ribbon width? *)p<=st.params.width&&p-st.indent.line_indent<=st.params.ribbonletfits(len:int)(st:t):bool=(* Do [len] more characters after the buffer still fit on the line? *)fits_pos(st.indent.pos+(Buffer.lengthst.buffer)+len)stletbuffer_fits(st:t):bool=(* Does the buffer fit on the line? *)fits0stletincrement_position(i:int)(st:t):t={stwithindent={st.indentwithpos=st.indent.pos+i}}letadd_text(t:Text.t)(st:t):t={stwithbuffer=Buffer.add_texttst.buffer}letadd_line(alternative_text:string)(st:t):t=assert(bufferingst);assert(0<st.groups.oa);leto=Buffer.countst.bufferinletbuffer=ifst.groups.oa<=othenBuffer.close_groups(o-st.groups.oa)st.bufferelsest.bufferin{stwithbuffer=Buffer.open_groups(ifo<st.groups.oathenst.groups.oa-oelsest.groups.o_r)buffer|>(Buffer.add_line@@Line.makealternative_textst.indent.current_indent)}letnewline(indent:int)(st:t):t=assert(normalst);{stwithindent={st.indentwithpos=indent;line_indent=indent}}letactive_to_effective(st:t):t=(* Make all active groups effective *)assert(normalst);{stwithgroups={st.groupswithoa=0;oe=st.groups.oe+st.groups.oa}}letone_active_to_effective(st:t):t=(* Make one active group effective *)assert(0<st.groups.oa);{stwithgroups={st.groupswithoa=st.groups.oa-1;oe=st.groups.oe+1}}letright_to_active(st:t):t={stwithgroups={st.groupswithoa=st.groups.oa+st.groups.o_r;o_r=0}}letstart_buffering(s:string)(st:t):t=assert(normalst);{stwithbuffer=Buffer.(open_groupsst.groups.oast.buffer|>add_line(Line.makesst.indent.current_indent));groups={st.groupswitho_r=0}}letclear_buffer(st:t):t={stwithbuffer=Buffer.empty}letpush(g:group)(st:t):t={stwithbuffer=Buffer.pushgst.buffer}letopen_group(st:t):t={stwithgroups=ifst.groups.oa<Buffer.countst.bufferthen{st.groupswitho_r=st.groups.o_r+1}else{st.groupswithoa=st.groups.oa+1}}letclose_group(st:t):t={stwithgroups=if0<st.groups.o_rthen(assert(st.groups.oa<Buffer.countst.buffer);{st.groupswitho_r=st.groups.o_r-1})elseif0<st.groups.oathen{st.groupswithoa=st.groups.oa-1}else(assert(0<st.groups.oe);{st.groupswithoe=st.groups.oe-1})}letincrement_indent(i:int)(st:t)=letcurrent_indent=st.indent.current_indent+iinassert(0<=current_indent);{stwithindent={st.indentwithcurrent_indent}}endmodulePretty(P:PRINTER)=structtypestate=State.ttypeloop_state=|Moreof(unit->loop_state)|DoneofP.tletloop:loop_state->P.t=letrecdo_loopi=function|Donep->p|Moref->do_loop(i+1)(f())indo_loop0type'acont=(* A continuation function takes a value, a state and the cumulated
printing command and returns the remainder of the loop. *)'a->state->P.t->loop_statemoduleM=structtype'at=state->P.t(* printed up to now *)->'acont(* continuation *)->loop_stateletreturn(a:'a):'at=funstpk->kastplet(>>=)(m:'at)(f:'a->'bt):'bt=funstpk->mstp(funastp->More(fun_->fastpk))endtypet=unitM.tletempty:t=M.return()let(<+>)(m1:t)(m2:t):t=M.(m1>>=fun_->m2)letstate:stateM.t=funstpk->kststpletput(st:state):unitM.t=fun_pk->k()stpletupdate(f:state->state):t=funstpk->k()(fst)pletget_and_update(f:state->state):stateM.t=funstpk->kst(fst)pletrelative_position:intM.t=M.(state>>=funst->return@@State.relative_positionst)letfill_indent(state:state)(p:P.t):state*P.t=letpos=State.positionstateandind=State.current_indentstateinifpos=0thenState.increment_position(ind-pos)state,P.(p<+>fill(ind-pos)' ')elsestate,pletout_string(s:string)(start:int)(len:int):t=funstpk->More(fun_->letst,p=fill_indentstpink()(State.increment_positionlenst)P.(p<+>substringsstartlen))letout_char(c:char):t=funstpk->More(fun_->letst,p=fill_indentstpink()(State.increment_position1st)P.(p<+>charc))letout_fill(n:int)(c:char):t=assert(0<=n);funstpk->More(fun_->letst,p=fill_indentstpink()(State.increment_positionnst)P.(p<+>fillnc))letout_text(t:Text.t):t=Text.applyout_stringout_fillout_chartletout_line(indent:int):t=funstpk->More(fun_->k()(State.newlineindentst)P.(p<+>char'\n'<+>fillindent' '))letprint_list(l:'alist)(f:'a->t):t=M.(List.fold_right(funapr->pr>>=fun_->fa)l(return()))letout_texts(l:Text.tlist):t=print_listlout_textletout_alternative_text(l:Line.t):t=lets=Line.textlinout_strings0(String.lengths)letline_normal(s:string):t=M.(state>>=funst->if0<st.groups.oa&&State.fits(String.lengths)stthenput(State.start_bufferingsst)elseput(State.active_to_effectivest)>>=fun_->out_linest.indent.current_indent)letrecflush_flatten_group(g:group):t=(* Print the group [g] flattened. *)letopenGroupinM.(flush_flatten_groups(groupsg)>>=fun_->flush_flatten_chunks(chunksg))andflush_flatten_groups(gs:grouplist):t=(* Print all groups in the list [gs] flattened. *)print_listgsflush_flatten_groupandflush_flatten_chunks(cs:chunklist):t=(* Print all chunks in the list [cs] flattened. *)print_listcsflush_flatten_chunkandflush_flatten_chunk(c:chunk):t=(* Print the chunk [c] flattened. *)M.(out_alternative_text(Chunk.linec)>>=fun_->out_texts(Chunk.textsc)>>=fun_->flush_flatten_groups(Chunk.groupsc))letflush_flatten:t=(* flush the complete buffer flattening it i.e. print all line breaks
with their corresponding alternative text. *)letopenMinstate>>=funst->print_list(Buffer.groupsst.buffer)flush_flatten_group>>=fun_->updateState.clear_bufferletrecflush_group(g:group):t=(* Flush the complete group [g]. If it fits on the line, then flush it
flattened. Otherwise print its break hints as line breaks. *)letopenMinstate>>=funst->ifState.fits(Group.lengthg)stthenflush_flatten_groupgelseflush_groupsg.groups>>=fun_->flush_chunksg.chunksandflush_chunk(c:chunk):t=letopenMinout_line(Line.indent(Chunk.linec))>>=fun_->out_textsc.texts>>=fun_->flush_groupsc.cgroupsandflush_groups(gs:grouplist):t=print_listgsflush_groupandflush_chunks(cs:chunklist):t=print_listcsflush_chunkletflush_incomplete(is_last:bool)(g:group):t=(* Flush an incomplete group from the buffer *)letopenMinupdate(funst->if0<st.groups.oathenState.one_active_to_effectivestelseifis_lastthenState.right_to_activestelsest)>>=fun_->flush_groupsg.groups>>=fun_->flush_chunksg.chunksletflush_effective:t=(* Flush open groups until buffer fits or is empty. *)letopenMinletrecflush(remaining_len:int)(is_last:bool)=function|[]->assertfalse(* Illegal call! *)|[g]->flush_incompleteis_lastg|g::gs->letlen=Group.lengthg+remaining_leninflushlenfalsegs>>=fun_->state>>=funst->ifState.fitslenstthenput(State.pushgst)elseflush_incompleteis_lastginstate>>=funst->put(State.clear_bufferst)>>=fun_->flush0true(Buffer.groupsst.buffer)lettext(t:Text.t):t=letopenMinstate>>=funst->ifState.normalstthenout_texttelseletst=State.add_texttstinifState.buffer_fitsstthenputstelseputst>>=fun_->flush_effectiveletsubstring(s:string)(start:int)(len:int):t=assert(0<=start);assert(start+len<=String.lengths);text(Text.stringsstartlen)letstring(s:string):t=substrings0(String.lengths)letchar(c:char):t=text(Text.charc)letfill(n:int)(c:char):t=assert(0<=n);text(Text.fillnc)letrecline(alternative_text:string):t=letopenMinstate>>=funst->ifState.normalstthenline_normalalternative_textelseif0<st.groups.oathen(* Still inside the active group *)letst=State.(right_to_active@@add_linealternative_textst)inifState.buffer_fitsstthenputstelseputst>>=fun_->flush_effectiveelse(* Outside the active group. *)put(State.right_to_activest)>>=fun_->flush_flatten>>=fun_->linealternative_textletcut:t=line""letspace:t=line" "letrecchain(l:tlist):t=letopenMinmatchlwith|[]->return()|hd::tl->hd>>=fun_->chaintlletlist_separated(sep:t)(lst:tlist):t=letrecchn=function|[]->empty|[p]->p|p::tl->p<+>sep<+>chntlinchnlstletchain_separated(lst:tlist)(sep:t):t=list_separatedseplstletgroup(m:t):t=letopenMinupdateState.open_group>>=fun_->m>>=funa->updateState.close_group>>=fun_->returnaletnest(i:int)(m:t):'t=letopenMinget_and_update(State.increment_indenti)>>=fun_->(*let pos,ind = State.position st, State.current_indent st in
let m =
if pos = 0 (*pos < ind + i*) then
fill (ind + i - pos) ' ' >>= fun _ -> m
else
m
in*)m>>=funa->update(State.increment_indent(-i))>>=fun_->returnaletnest_list(i:int)(lst:tlist):t=nesti(chainlst)letnest_relative(i:int)(m:t):t=letopenMinrelative_position>>=funp->nest(i+p)mletgroup_list(lst:tlist):t=group@@chainlstletwrap_words(s:string):t=letopenMinletis_blankc=c=' 'andis_not_blankc=c<>' 'inletword_starti=String.findis_not_blankisandword_endi=String.findis_blankisandlen=String.lengthsinletrecfillp=leti=word_startpin(ifp<ithengroupspaceelsereturn())>>=fun_->ifi<lenthenletj=word_endiinsubstringsi(j-i)>>=fun_->filljelsereturn()infill0letfill_paragraph=wrap_wordsletrun(indent:int)(width:int)(ribbon:int)(p:unitM.t):P.t=loop@@M.(p>>=fun_->flush_flatten)(State.initindentwidthribbon)P.empty(fun()_p->Donep)end(*
------------
Module Tests
------------
*)modulePP=Pretty(Readable_printer)includePPletrun(indent:int)(width:int)(ribbon:int)(m:t):Readable_printer.R.t=PP.runindentwidthribbonm|>Readable_printer.readablemoduleR=Readable_printer.Rletcompare(r:R.t)(s:string):bool=letlen=String.lengthsinletreccompri=ifi=lenthentrueelseletmore=R.has_morerinifnotmorethenfalseelses.[i]=R.peekr&&comp(R.advancer)(i+1)incompr0letprint_readable(r:R.t):unit=letopenPrintfinletrecprintr=ifR.has_morerthen(printf"%c"(R.peekr);print(R.advancer))else()inprintr;printf"\n"lettest(w:int)(pflag:bool)(pp:t)(expected:string):bool=letres=compare(run0wwpp)expectedinifpflagthenprint_readable(run0wwpp);reslet%test_=letstr="01234 6789 0 2 4 6 8 01 34 6789 0"andres="01234 6789\n\
0 2 4 6 8\n\
01 34 6789\n\
0"intest10false(fill_paragraphstr)reslet%test_=test10false(nest_list4[string"0123";cut;string"456"]<+>cut<+>string"0123")" 0123\n\
\ 456\n\
0123"(*
let%test _ =
test
70 true
(
string "line1"
<+> cut
<+> nest 4 (string "indented" <+> cut)
<+> string "line2"
)
"line1\n\
\ indented\n\
\ line2" (* WRONG: shall not be indented! *)
*)let%test_=compare(run02020(group_list[(group_list[string"class";nest_list4[space;string"Natural"];space;string"create"]);nest_list4[space;(group_list[string"0";line"; ";string"succ(Natural)"])];chain[space;string"end"]]))"class Natural create\n\
\ 0; succ(Natural)\n\
end"letmaybe=group(group(string"class"<+>nest4(space<+>string"Maybe(A)")<+>space<+>string"create")<+>nest4(space<+>group(string"nothing"<+>line"; "<+>string"just(A)"))<+>space<+>string"end")let%test_=compare(run07070maybe)"class Maybe(A) create nothing; just(A) end"let%test_=compare(run02020maybe)"class\
\n Maybe(A)\n\
create\
\n nothing; just(A)\n\
end"let%test_=compare(run01515maybe)"class\
\n Maybe(A)\n\
create\
\n nothing\
\n just(A)\n\
end"letplus=letgnsp=group(nest2(space<+>p))inletinsp=group(string"inspect"<+>nest2(space<+>group(string"a"<+>line"; "<+>string"(_:Natural) := Natural"))<+>space<+>string"case")andcases=group(string"0 :="<+>gns(string"b")<+>line"; "<+>string"n.successor :="<+>gns(string"n + b.successor"))instring"(+)(a:Natural,b:Natural): Natural :="<+>gns(group(insp<+>nest2(space<+>cases)<+>space<+>string"end"))let%test_=compare(run04040plus)"(+)(a:Natural,b:Natural): Natural :=\
\n inspect a; (_:Natural) := Natural case\
\n 0 := b\n n.successor := n + b.successor\
\n end"let%test_=compare(run03939plus)"(+)(a:Natural,b:Natural): Natural :=\
\n inspect\
\n a; (_:Natural) := Natural\
\n case\
\n 0 := b\
\n n.successor := n + b.successor\
\n end"let%test_=compare(run03333plus)"(+)(a:Natural,b:Natural): Natural :=\
\n inspect\n a; (_:Natural) := Natural\
\n case\
\n 0 := b\
\n n.successor :=\
\n n + b.successor\
\n end"