123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113moduleStream=Char_streamtypecolour=[`Black|`Blue|`Cyan|`Green|`Magenta|`Red|`White|`Yellow]typesgr=[`BgColof[`Default|colour]|`Bold|`FgColof[`Default|colour]|`Italic|`NoBold|`NoItalic|`NoReverse|`NoUnderline|`Reset|`Reverse|`Underline]typeescape=[`Reset|`Ctrlof[`SelectGraphicRenditionofsgrlist]]letis_param_bytec=letc=Char.codecincland0xf0=0x30letis_im_bytec=letc=Char.codecincland0xf0=0x40letis_final_bytec=letc=Char.codecinc>=0x40&&c<=0x7eexceptionUnknown_escapeletcolour=function|0->`Black|1->`Red|2->`Green|3->`Yellow|4->`Blue|5->`Magenta|6->`Cyan|7->`White|_->raiseUnknown_escapeletsgr=function|""->`Reset|x->(matchint_of_stringxwith|exception_->raiseUnknown_escape|0->`Reset|1->`Bold|3->`Italic|4->`Underline|7->`Reverse|22->`NoBold|23->`NoItalic|24->`NoUnderline|27->`NoReverse|xwhenx>=30&&x<=37->`FgCol(colour(x-30))|xwhenx>=90&&x<=97->`FgCol(colour(x-90))(* Non-standard "bright" fg colour *)|39->`FgCol`Default|xwhenx>=40&&x<=47->`BgCol(colour(x-40))|49->`BgCol`Default|_->raiseUnknown_escape)letparse_ctrl~params=function|"m"->`SelectGraphicRendition(List.mapsgrparams)|_->raiseUnknown_escapeletread_intermediates~paramsstart=letrecauxs=matchStream.nextswith|None->`Incomplete(* No final byte *)|Some(x,s)whenis_im_bytex->auxs|Some(x,s2)whenis_final_bytex->(letfunc=Stream.(start--s2|>string_of_span)inletparams=Astring.String.cuts~sep:";"paramsintry`Escape(`Ctrl(parse_ctrl~paramsfunc),s2)withUnknown_escape->`Invalids2)|Some_->`Invalidsinauxstartletread_paramsstart=letrecauxs=matchStream.nextswith|None->`Incomplete(* No final byte *)|Some(x,s)whenis_param_bytex->auxs|Some_->letparams=Stream.(start--s|>string_of_span)inread_intermediates~paramssinauxstart(* Parse [esc], an escape sequence. *)letparse_escapeesc=matchStream.(next(Stream.skipesc))with|Some('[',s)->read_paramss(* [esc] is a control sequence *)|Some(']',s)->`Invalids(* [esc] is a operating system command sequence (todo) *)|Some('c',s)->`Escape(`Reset,s)|Some(_,s)->`Invalids(* TODO: other types of escape *)|None->`Incompleteletparseinput=(* In theory, we could also get the 8-bit escape character encoded as two
UTF-8 bytes, but for now we just process the "<ESC>[" sequence, which
seems to be what everyone is using. *)matchStream.findinput'\x1b'with|None->`Literal(Stream.skip_allinput)|SomeiwhenStream.equalinputi->parse_escapeinput|Somei->`Literali