123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213typecolor=Black|Red|Green|Yellow|Blue|Magenta|Cyan|WhitemoduleConcrete=structtypestyle=Bold|Faint|Italic|Underline|Blink|Inverse|Hidden|Strike|Foreofcolor|Backofcolor|Unknownofinttypet=Escofstylelist|Reset|Textofstringletfmt_of_int=function|1->Bold|2->Faint|3->Italic|4->Underline|5->Blink|7->Inverse|8->Hidden|9->Strike|x->Unknownxletcolor_of_int=function|0->Black|1->Red|2->Green|3->Yellow|4->Blue|5->Magenta|6->Cyan|7->White|_->assertfalseletstyle_of_int=function|xwhen30<=x&&x<=37->Fore(color_of_int(x-30))|xwhen40<=x&&x<=47->Back(color_of_int(x-40))|x->fmt_of_intx(* Warning: possibly re-inventing the square parser monad here *)(* val extract_esc : int list -> style list * int list *)letrecextract_esc=function|0::ints->([],ints)|x::ints->letstyles,rest=extract_escintsin(style_of_intx::styles,rest)|[]->([],[])(* val extract_item : int list -> t list * int list *)letextract_item=function|0::ints->(Reset,ints)|ints->letstyles,rest=extract_escintsin(Escstyles,rest)(* val items_of_ints : int list -> t list *)letrecitems_of_intsints=letitem,ints'=extract_itemintsinmatchints'with|_::_->item::items_of_intsints'|[]->item::[](* Grammar:
Item --> Escape | Text
Escape --> csi Styles? cst
Styles --> Style ( ';' Style )*
Style --> dig+
Text --> [not start of csi]*
*)openAngstromletstyle=take_while1(function'0'..'9'->true|_->false)>>|int_of_stringletstyles=sep_by(char';')styleletcsi_str="\x1b["letcsi=stringcsi_strletcst=string"m"modulePrivate=structlettext=peek_char>>=function|Some_->take_till(func->c=csi_str.[0])>>|funstr->[Textstr]|None->fail"End of input"letescape=csi*>styles<*cst>>|items_of_intsletitem=(escape<|>text)(* : t list parser ; needs flattening *)letitems=manyitem>>|List.concat(* Done *)end(* val parse : in_channel -> Concrete.t list *)moduleB=Bufferedletparsein_ch=letrecwith_state=function|B.Partialk->with_state@@k(try`String(input_linein_ch^"\n")withEnd_of_file->`Eof)|B.Done(_,result)->result|B.Fail(_,ss,s)->Esc[ForeRed]::Texts::List.map(funx->Textx)ss(* Cheap ... but it shouldn't fail? XD *)inwith_state@@B.parsePrivate.itemsletparse_strstr=matchparse_string~consume:Consume.AllPrivate.itemsstrwith|Okresult->result|Errorerr->[Esc[ForeRed];Texterr]endmoduleC=ConcretemoduleDebug=structopenAngstromletstr="\x1b[0m\x1b[1;39m[ INFO ]\x1b[0m Something interesting happened."lettext=peek_char>>=function|Some_->take_till(func->c=C.csi_str.[0])>>|funstr->`Shmextstr|None->fail"End of input"letescape=C.csi*>C.styles<*C.cst>>|funxs->`Shmintsxsletitem=escape<|>textletitems=manyitemendmoduleAbstract=structtypeweight=Normal|Bold|Fainttypestyle={weight:weight;italic:bool;underline:bool;blink:bool;reverse:bool;strike:bool;foreground:coloroption;background:coloroption}type'at=Baseof'a|Styledofstyle*'atlistletdefault={weight=Normal;italic=false;underline=false;blink=false;reverse=false;strike=false;foreground=None;background=None}(* Apply the concrete style to the abstract style *)(* apply_single : C.style -> A.style -> A.style *)letapply_singlecstyleastyle=matchcstylewith|C.Bold->{astylewithweight=Bold}|C.Faint->{astylewithweight=Faint}|C.Italic->{astylewithitalic=true}|C.Underline->{astylewithunderline=true}|C.Blink->{astylewithblink=true}|C.Inverse->{astylewithreverse=true}|C.Hidden->astyle(* Ignore for now... *)|C.Strike->{astylewithstrike=true}|C.Forecol->{astylewithforeground=Somecol}|C.Backcol->{astylewithbackground=Somecol}|C.Unknown_->astyle(* Ignore *)(* val apply_multi : C.style list -> A.style -> A.style *)letapply_multicstylesastyle=List.fold_left(funxy->apply_singleyx)astylecstyles(* Further possibility of reinventing the square parser monad *)(* val branch : C.t list -> A.t list * C.t list *)letrecbranch=function|[]->([],[])|(C.Reset::_)asitems->([],items)|x::items->letnodes,items'=branchitemsinmatchxwith|C.Textstr->(Basestr::nodes,items')|C.Escstyles->letnodes',_items''=branchitems'in(Styled(apply_multistylesdefault,nodes)::nodes',items')|C.Reset->(nodes,items')(* val branch_root : C.t list -> A.t list *)letrecbranch_root=function|[]->[]|C.Reset::items->branch_rootitems|C.Textstr::items->Basestr::branch_rootitems|C.Escstyles::items->letnodes,items'=branchitemsinStyled(apply_multistylesdefault,nodes)::branch_rootitems'(* val parse : Concrete.t list -> string Abstract.t *)letparseitems=Styled(default,branch_rootitems)endmoduleA=Abstractlet(^^^)xy=(x&¬y)||(y&¬x)moduleHtml=structletstring_of_col=function|Black->"black"|Red->"red"|Green->"green"|Yellow->"yellow"|Blue->"blue"|Magenta->"magenta"|Cyan->"cyan"|White->"white"openTyxml.Htmlletcss_of_stylectx_rvs{A.weight;italic;underline;blink;reverse;strike;foreground;background}=letreverse'=reverse^^^ctx_rvsinletcss_weight=matchweightwith|A.Normal->""|A.Bold->"font-weight: bold"|A.Faint->"font-weight: lighter"inletcss_style=ifitalicthen"font-style: italic"else""in(* arbitrarily prioritising strike > blink > underline for single text-decoration property *)letcss_decor=ifstrikethen"text-decoration: line-through"elseifblinkthen"text-decoration: blink"elseifunderlinethen"text-decoration: underline"else""inletcss_color=matchifreverse'thenbackgroundelseforegroundwith|Somec->"color: "^string_of_colc|None->""inletcss_bgcol=matchifreverse'thenforegroundelsebackgroundwith|Somec->"background-color: "^string_of_colc|None->""in(reverse',String.concat"; "[css_weight;css_style;css_decor;css_color;css_bgcol])letof_treetree=letrecper_nodereverse=function|A.Base(str:string)->txtstr|A.Styled(style,nodes)->letreverse',css=css_of_stylereversestyleinspan~a:[a_stylecss](List.map(per_nodereverse')nodes)inpre[per_nodefalsetree]end