123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854openPrintf(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One Layout: Module Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)moduletypeS0=sigtypetvalnil:tvaltext:string->tvalline:tval(<>):t->t->tvalnest:int->t->tvallayout:t->stringend(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One Layout
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Inefficient recursion for nesting and concatenation.
Concatenation
(x <> y) <> z
First x is traversed completely to append y to all line and text
elemenst. And then x <> y is traversed completely to append z.
Nesting
nest i (nest j x)
nest j x adds the indentation j to all lines in x and then nest i adds i
again. This is quadratic complexity on the length of the document.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)moduleDoc0:S0=structtypet=|Nil|Textofstring*t|Lineofint*tletnil=Nillettexts=Text(s,Nil)letline=Line(0,Nil)letrec(<>)xy=matchxwith|Nil->y|Text(s,Nil)->Text(s,y)|Text(s,x)->Text(s,x<>y)|Line(i,Nil)->Line(i,y)|Line(i,x)->Line(i,x<>y)letrecnesti=function|Nil->Nil|Text(s,x)->Text(s,nestix)|Line(j,x)->Line(i+j,nestix)letreclayout=function|Nil->""|Text(s,x)->s^layoutx|Line(i,x)->sprintf"\n%s%s"(String.makei' ')(layoutx)end(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One Layout Lazy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
No gain in efficiency if layout is called. All lazy terms have to be forced.
The inefficiency for nesting and concatenation remains.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)moduleDoc0_lazy:sigtypetvalnil:tvaltext:string->tvalline:tval(<>):t->t->tvalnest:int->t->tvallayout:t->stringend=structtypet=|Nil|Textofstring*laz|Lineofint*lazandlaz=tLazy.tletforce=Lazy.forceletnil=Nillettexts=Text(s,lazyNil)letline=Line(0,lazyNil)letrec(<>)xy=matchxwith|Nil->y|Text(s,x)->beginmatchforcexwith|Nil->Text(s,lazyy)|x->Text(s,lazy(x<>y))end|Line(i,x)->beginmatchforcexwith|Nil->Line(i,lazyy)|x->Line(i,lazy(x<>y))endletrecnesti=function|Nil->Nil|Text(s,x)->Text(s,lazy(nesti(forcex)))|Line(j,x)->Line(i+j,lazy(nesti(forcex)))letreclayout=function(* document must not contain unions *)|Nil->""|Text(s,x)->s^(forcex|>layout)|Line(i,x)->sprintf"\n%s%s"(String.makei' ')(forcex|>layout)end(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One Layout Optimized
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Key ideas to avoid the quadratic complexity for left associative
concatenation and nested nesting:
- Represent concatenation x <> y by an own constructor Cat (x, y) and layout
x completely before laying out y.
- Represent nest i x by an own constructor Nest (i, x) and cumulate
the nesting levels.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)moduleDoc0_opt:S0=structtypet=|Nil|Textofstring|Line|Catoft*t|Nestofint*tletnil=Nillettexts=Textsletline=Linelet(<>)xy=Cat(x,y)letnestix=Nest(i,x)letrecla:(int*t)list->string=function|[]->""|(i,x)::z->matchxwith|Nil->""|Texts->s|Line->sprintf"\n%s"(String.makei' ')|Nest(j,x)->la(((i+j),x)::z)(* cumulate *)|Cat(x,y)->la((i,x)::(i,y)::z)(* x first *)letlayout(x:t):string=la[0,x]end(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Module Type for Multiple Layouts
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One additional function group.
Function pretty with a desired line width is exported instead of layout.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)moduletypeS=sigtypetvalnil:tvaltext:string->tvalline:tval(<>):t->t->tvalnest:int->t->tvalgroup:t->tvalpretty:int->t->stringend(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Multiple Layouts (Inefficient)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Each group doubles the documents i.e. for 10 groups we have 2^10 documents.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)moduleDoc1:S=structtypet=|Nil|Textofstring*t|Lineofint*t|Unionoft*tletrecflatten:t->t=function|Nil->Nil|Text(s,x)->Text(s,flattenx)|Line(_,x)->Text(" ",flattenx)|Union(x,_)->flattenxletrecfits(w:int):t->bool=function(* Document must not contain unions *)|Nil->true|Line(_,_)->true|Union_->assertfalse(* illegal call *)|Text(s,x)->assert(0<=w);letw=w-String.lengthsinw<0||fitswxletrecbest(w:int)(k:int):t->t=function|Nil->Nil|Text(s,x)->Text(s,bestw(k+String.lengths)x)|Line(i,x)->Line(i,bestwix)|Union(x,y)->iffits(w-k)(bestwkx)thenxelsebestwkyletreclayout=function(* document must not contain unions *)|Nil->""|Text(s,x)->s^layoutx|Line(i,x)->sprintf"\n%s%s"(String.makei' ')(layoutx)|Union_->assertfalse(* illegal call *)letnil=Nillettexts=Text(s,Nil)letline=Line(0,Nil)letrec(<>)xy=matchxwith|Nil->y|Text(s,Nil)->Text(s,y)|Text(s,x)->Text(s,x<>y)|Line(i,Nil)->Line(i,y)|Line(i,x)->Line(i,x<>y)|Union(x1,x2)->Union(x1<>y,x2<>y)letrecnesti=function|Nil->Nil|Text(s,x)->Text(s,nestix)|Line(j,x)->Line(i+j,nestix)|Union(x,y)->Union(nestix,nestiy)letgroup(x:t):t=Union(flattenx,x)letpretty(w:int)(x:t):string=layout(bestw0x)end(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Multiple Layouts Lazy Implementation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Basic idea:
The constructors Text (s, x), Line (i, x), and Union (x, y) do not
contain a document x, but a lazy document.
The function fits forces the document x in Text (s, x) only as long as
the document fits on the line.
The function best is lazy as well and forces documents only through
fits. If fits returns true, then the document is forced completely. This
is not a problem, since it is selected and used in the layout.
If fits fails the document is only partially forced until the failure is
evident and then thrown away, because the unflatted version is the
selected one.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)moduleDoc1_lazy:S=structtypet=|Nil|Textofstring*laz|Lineofint*laz|Unionoflaz*tandlaz=tLazy.tletforce=Lazy.forceletrecflatten:t->laz=function|Nil->lazyNil|Text(s,x)->lazy(Text(s,forcex|>flatten))|Line(_,x)->lazy(Text(" ",forcex|>flatten))|Union(x,_)->forcex|>flattenletrecfits(w:int):t->bool=function(* Document must not contain unions *)|_whenw<0->false|Nil|Line_->true|Union_->assertfalse(* illegal call *)|Text(s,x)->fits(w-String.lengths)(forcex)letrecbest(w:int)(k:int):t->t=function|Nil->Nil|Text(s,x)->Text(s,lazy(bestw(k+String.lengths)(forcex)))|Line(i,x)->Line(i,lazy(bestwi(forcex)))|Union(x,y)->letx=forcex|>bestwkiniffits(w-k)xthenxelsebestwkyletreclayout=function(* document must not contain unions *)|Nil->""|Text(s,x)->s^layout(forcex)|Line(i,x)->sprintf"\n%s%s"(String.makei' ')(layout(forcex))|Union_->assertfalse(* illegal call *)letnil=Nillettexts=Text(s,lazyNil)letline=Line(0,lazyNil)letrec(<>)xy=matchxwith|Nil->y|Text(s,x)->beginmatchforcexwith|Nil->Text(s,lazyy)|x->Text(s,lazy(x<>y))end|Line(i,x)->beginmatchforcexwith|Nil->Line(i,lazyy)|x->Line(i,lazy(x<>y))end|Union(x1,x2)->Union(lazy(forcex1<>y),x2<>y)letrecnesti=function|Nil->Nil|Text(s,x)->Text(s,lazy(nesti(forcex)))|Line(j,x)->Line(i+j,lazy(nesti(forcex)))|Union(x,y)->Union(lazy(nesti(forcex)),nestiy)letgroup(x:t):t=Union(flattenx,x)letpretty(w:int)(x:t):string=layout(bestw0x)end(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Multiple Layouts Optimized Lazy Implementation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)moduleDoc2_lazy:S=structletforce=Lazy.forcemoduleBase=structtypet=|Nil|Textofstring*laz|Lineofint*lazandlaz=tLazy.tletrecfits(w:int):t->bool=function|_whenw<0->false|Nil|Line(_,_)->true|Text(s,x)->fits(w-String.lengths)(forcex)letreclayout:t->string=function|Nil->""|Text(s,x)->sprintf"%s%s"s(forcex|>layout)|Line(i,x)->sprintf"\n%s%s"(String.makei' ')(forcex|>layout)endtypet=|Nil|Textofstring|Line|Catoflaz*laz|Nestofint*laz|Unionoflaz*tandlaz=tLazy.tletrecflatten:t->laz=function|Nil->lazyNil|Line->lazy(Text" ")|Text_asx->lazyx|Cat(x,y)->lazy(Cat(flatlazx,flatlazy))|Nest(i,x)->lazy(Nest(i,flatlazx))|Union(x,_)->flatlazxandflatlazx=forcex|>flattenletrecbe(w:int)(k:int):(int*t)list->Base.t=function|[]->Nil|(i,x)::z->matchxwith|Nil->bewkz|Texts->Text(s,lazy(bew(k+String.lengths)z))|Line->Line(i,lazy(bewiz))|Cat(x,y)->(* Make concatenation right associative *)bewk((i,forcex)::(i,forcey)::z)|Nest(j,x)->(* Accumulate nesting levels *)bewk(((i+j),forcex)::z)|Union(x,y)->letx=bewk((i,forcex)::z)inifBase.fits(w-k)xthenxelsebewk((i,y)::z)letnil=Nillettexts=Textsletline=Linelet(<>)xy=Cat(lazyx,lazyy)letnestix=Nest(i,lazyx)letgroup=function|Union_asx->x(* group is idempotent *)|x->Union(flattenx,x)letpretty(w:int)(x:t):string=bew0[0,x]|>Base.layoutendmodulePretty_plus(Pretty:S):sigincludeSvalparent_child:int->t->t->tend=structincludePrettyletparent_child(i:int)(parent:t)(child:t):t=parent<>nesti(line<>groupchild)|>groupendmoduleTree(Pretty:S)=structmoduleP=Pretty_plus(Pretty)typet={name:string;children:tlist;}letleaf(name:string):t={name;children=[]}lettree(name:string)(children:tlist):t={name;children}letdoc_tree(t:t):P.t=letopenPinletrecdocis_toptree=matchtree.childrenwith|[]->texttree.name|_->letd=parent_child2(texttree.name)(childrentree.children)inifis_topthendelsetext"("<>d<>text")"andchildrenlst=matchlstwith|[last]->docfalselast|head::tail->docfalsehead<>line<>childrentail|[]->assertfalse(* [lst] is never empty *)indoctruetletargs2=[leaf"a";leaf"b"]letargs3=[leaf"a";leaf"b";leaf"c"]letargs4=[leaf"a";leaf"b";leaf"c";leaf"d"]lettree0=tree"ff"args3lettree1=tree"ff"[leaf"a";tree"gf"args2;leaf"d"]lettree2=tree"ff"[tree"gf"args2;tree"gf"args2;tree"gf"args2]lettree3=tree"f"[tree"f"args2;tree"f"[tree"f"args4];tree"f"args4;tree"f"args3;]letstring0w=doc_treetree0|>P.prettywletstring1w=doc_treetree1|>P.prettywletstring2w=doc_treetree2|>P.prettywletstring3w=doc_treetree3|>P.prettywendlettest0(print:bool)(str:string)(expected:string):bool=letok=str=expectedinifnotok||printthenprintf"---\nstr\n%s\nexpected\n%s\n\n"strexpected;okmoduleTest(P:S)=structopenUnit_test_supportmoduleTree=Tree(P)let%test_=letstr=Tree.string03andexpected={|
ff
a
b
c
|}|>quotedintest0falsestrexpectedlet%test_=letstr=Tree.string08andexpected="ff a b c"intest0falsestrexpectedlet%test_=letstr=Tree.string07andexpected={|
ff
a b c
|}|>quotedintest0falsestrexpectedlet%test_=letstr=Tree.string227andexpected={|
ff
(gf a b)
(gf a b)
(gf a b)
|}|>quotedintest0falsestrexpectedlet%test_=letstr=Tree.string347(* 01234567890123456789012345678901234567890123456 *)andexpected="f (f a b) (f (f a b c d)) (f a b c d) (f a b c)"intest0falsestrexpectedlet%test_=letstr=Tree.string346andexpected={|
f
(f a b)
(f (f a b c d))
(f a b c d)
(f a b c)
|}|>quotedintest0falsestrexpectedlet%test_=letstr=Tree.string316andexpected={|
f
(f a b)
(f
(f a b c d))
(f a b c d)
(f a b c)
|}|>quotedintest0falsestrexpectedlet%test_=letstr=Tree.string313andexpected={|
f
(f a b)
(f
(f
a
b
c
d))
(f a b c d)
(f a b c)
|}|>quotedintest0falsestrexpectedlet%test_=letstr=Tree.string35andexpected={|
f
(f
a
b)
(f
(f
a
b
c
d))
(f
a
b
c
d)
(f
a
b
c)
|}|>quotedintest0falsestrexpectedendmoduleTest_Doc1_lazy=Test(Doc1_lazy)moduleTest_Doc2_lazy=Test(Doc2_lazy)