123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468(******************************************************************************)(* *)(* PPrint *)(* *)(* François Pottier, Inria Paris *)(* Nicolas Pouillard *)(* *)(* Copyright 2007-2022 Inria. All rights reserved. This file is *)(* distributed under the terms of the GNU Library General Public *)(* License, with an exception, as described in the file LICENSE. *)(* *)(******************************************************************************)includePPrintEngine(* -------------------------------------------------------------------------- *)(* Predefined single-character documents. *)letlparen=char'('letrparen=char')'letlangle=char'<'letrangle=char'>'letlbrace=char'{'letrbrace=char'}'letlbracket=char'['letrbracket=char']'letsquote=char'\''letdquote=char'"'letbquote=char'`'letsemi=char';'letcolon=char':'letcomma=char','letdot=char'.'letsharp=char'#'letslash=char'/'letbackslash=char'\\'letequals=char'='letqmark=char'?'lettilde=char'~'letat=char'@'letpercent=char'%'letdollar=char'$'letcaret=char'^'letampersand=char'&'letstar=char'*'letplus=char'+'letminus=char'-'letunderscore=char'_'letbang=char'!'letbar=char'|'(* -------------------------------------------------------------------------- *)(* Repetition. *)let[@inline]twicedoc=doc^^docletrepeatndoc=letrecloopndocaccu=ifn=0thenaccuelseloop(n-1)doc(doc^^accu)inloopndocempty(* -------------------------------------------------------------------------- *)(* Delimiters. *)let[@inline]precedelx=l^^xlet[@inline]terminaterx=x^^rlet[@inline]encloselrx=l^^x^^rlet[@inline]squotesx=enclosesquotesquotexlet[@inline]dquotesx=enclosedquotedquotexlet[@inline]bquotesx=enclosebquotebquotexlet[@inline]bracesx=encloselbracerbracexlet[@inline]parensx=encloselparenrparenxlet[@inline]anglesx=encloselangleranglexlet[@inline]bracketsx=encloselbracketrbracketx(* -------------------------------------------------------------------------- *)(* Some functions on lists. *)(* A variant of [fold_left] that keeps track of the element index. *)letfoldli(f:int->'b->'a->'b)(accu:'b)(xs:'alist):'b=letr=ref0inList.fold_left(funaccux->leti=!rinr:=i+1;fiaccux)accuxs(* -------------------------------------------------------------------------- *)(* Working with lists of documents. *)letconcatdocs=(* We take advantage of the fact that [^^] operates in constant
time, regardless of the size of its arguments. The document
that is constructed is essentially a reversed list (i.e., a
tree that is biased towards the left). This is not a problem;
when pretty-printing this document, the engine will descend
along the left branch, pushing the nodes onto its stack as
it goes down, effectively reversing the list again. *)List.fold_left(^^)emptydocsletseparatesepdocs=foldli(funiaccudoc->ifi=0thendocelseaccu^^sep^^doc)emptydocsletconcat_mapfxs=List.fold_left(funaccux->accu^^fx)emptyxsletseparate_mapsepfxs=foldli(funiaccux->ifi=0thenfxelseaccu^^sep^^fx)emptyxsletseparate2seplast_sepdocs=letn=List.lengthdocsinfoldli(funiaccudoc->ifi=0thendocelseaccu^^(ifi<n-1thensepelselast_sep)^^doc)emptydocsletoptionalf=function|None->empty|Somex->fx(* -------------------------------------------------------------------------- *)(* Text. *)(* This variant of [String.index_from] returns an option. *)letindex_fromsic=trySome(String.index_fromsic)withNot_found->None(* [lines s] chops the string [s] into a list of lines, which are turned
into documents. *)letliness=letrecchopaccui=matchindex_fromsi'\n'with|Somej->letaccu=substringsi(j-i)::accuinchopaccu(j+1)|None->substringsi(String.lengths-i)::accuinList.rev(chop[]0)letarbitrary_strings=separate(break1)(liness)(* [split ok s] splits the string [s] at every occurrence of a character
that satisfies the predicate [ok]. The substrings thus obtained are
turned into documents, and a list of documents is returned. No information
is lost: the concatenation of the documents yields the original string.
This code is not UTF-8 aware. *)letsplitoks=letn=String.lengthsinletrecindex_fromi=ifi=nthenNoneelseifoks.[i]thenSomeielseindex_from(i+1)inletrecchopaccui=matchindex_fromiwith|Somej->letaccu=substringsi(j-i)::accuinletaccu=chars.[j]::accuinchopaccu(j+1)|None->substringsi(String.lengths-i)::accuinList.rev(chop[]0)(* [words s] chops the string [s] into a list of words, which are turned
into documents. *)letwordss=letn=String.lengthsin(* A two-state finite automaton. *)(* In this state, we have skipped at least one blank character. *)letrecskippingaccui=ifi=nthen(* There was whitespace at the end. Drop it. *)accuelsematchs.[i]with|' '|'\t'|'\n'|'\r'->(* Skip more whitespace. *)skippingaccu(i+1)|_->(* Begin a new word. *)wordaccui(i+1)(* In this state, we have skipped at least one non-blank character. *)andwordaccuij=ifj=nthen(* Final word. *)substringsi(j-i)::accuelsematchs.[j]with|' '|'\t'|'\n'|'\r'->(* A new word has been identified. *)letaccu=substringsi(j-i)::accuinskippingaccu(j+1)|_->(* Continue inside the current word. *)wordaccui(j+1)inList.rev(skipping[]0)letflow_mapsepfdocs=foldli(funiaccudoc->ifi=0thenfdocelseaccu^^(* This idiom allows beginning a new line if [doc] does not
fit on the current line. *)group(sep^^fdoc))emptydocsletflowsepdocs=flow_mapsep(funx->x)docsleturls=flow(break0)(split(function'/'|'.'->true|_->false)s)(* -------------------------------------------------------------------------- *)(* Alignment and indentation. *)lethangid=align(nestid)let(!^)=stringlet[@inline](^/^)xy=x^^break1^^yletprefixnbxy=group(x^^nestn(breakb^^y))let[@inline](^//^)xy=prefix21xyletjumpnby=group(nestn(breakb^^y))letinfixnbopxy=prefixnb(x^^blankb^^op)yletsurroundnbopeningcontentsclosing=group(opening^^nestn(breakb^^contents)^^breakb^^closing)letsoft_surroundnbopeningcontentsclosing=group(opening^^nestn(group(breakb)^^contents)^^group(breakb^^closing))letsurround_separatenbvoidopeningsepclosingdocs=matchdocswith|[]->void|_::_->surroundnbopening(separatesepdocs)closingletsurround_separate_mapnbvoidopeningsepclosingfxs=matchxswith|[]->void|_::_->surroundnbopening(separate_mapsepfxs)closing(* -------------------------------------------------------------------------- *)(* Printing OCaml values. *)moduleOCaml=structopenPrintftypeconstructor=stringtypetype_name=stringtyperecord_field=stringtypetag=int(* -------------------------------------------------------------------------- *)(* This internal [sprintf]-like function produces a document. We use [string],
as opposed to [arbitrary_string], because the strings that we produce will
never contain a newline character. *)let[@inline]dsprintfformat=ksprintfstringformat(* -------------------------------------------------------------------------- *)(* Nicolas prefers using this code as opposed to just [sprintf "%g"] or
[sprintf "%f"]. The latter print [inf] and [-inf], whereas OCaml
understands [infinity] and [neg_infinity]. [sprintf "%g"] does not add a
trailing dot when the number happens to be an integral number. [sprintf
"%F"] seems to lose precision and ignores the precision modifier. *)letvalid_float_lexeme(s:string):string=letl=String.lengthsinletrecloopi=ifi>=lthen(* If we reach the end of the string and have found only characters in
the set '0' .. '9' and '-', then this string will be considered as an
integer literal by OCaml. Adding a trailing dot makes it a float
literal. *)s^"."elsematchs.[i]with|'0'..'9'|'-'->loop(i+1)|_->sinloop0(* This function constructs a string representation of a floating point
number. This representation is supposed to be accepted by OCaml as a
valid floating point literal. *)letfloat_representation(f:float):string=matchclassify_floatfwith|FP_nan->"nan"|FP_infinite->iff<0.0then"neg_infinity"else"infinity"|_->(* Try increasing precisions and validate. *)lets=sprintf"%.12g"finiff=float_of_stringsthenvalid_float_lexemeselselets=sprintf"%.15g"finiff=float_of_stringsthenvalid_float_lexemeselsesprintf"%.18g"f(* -------------------------------------------------------------------------- *)(* A few constants and combinators, used below. *)letsome=string"Some"letnone=string"None"letlbracketbar=string"[|"letrbracketbar=string"|]"letseq1openingseparatorclosing=surround_separate20(opening^^closing)opening(separator^^break1)closingletseq2openingseparatorclosing=surround_separate_map21(opening^^closing)opening(separator^^break1)closing(* -------------------------------------------------------------------------- *)(* The following functions are printers for many types of OCaml values. *)(* There is no protection against cyclic values. *)lettuple=seq1lparencommarparenletvariant_cons_args=matchargswith|[]->!^cons|_::_->!^cons^^tupleargsletrecord_fields=seq2lbracesemirbrace(fun(k,v)->infix21equals!^kv)fieldsletoptionf=function|None->none|Somex->some^^tuple[fx]letlistfxs=seq2lbracketsemirbracketfxsletflowing_listfxs=group(lbracket^^space^^nest2(flow_map(semi^^break1)fxs)^^space^^rbracket)letarrayfxs=seq2lbracketbarsemirbracketbarf(Array.to_listxs)letflowing_arrayfxs=group(lbracketbar^^space^^nest2(flow_map(semi^^break1)f(Array.to_listxs))^^space^^rbracketbar)letreffx=record"ref"["contents",f!x]letfloatf=string(float_representationf)letint=dsprintf"%d"letint32=dsprintf"%ld"letint64=dsprintf"%Ld"letnativeint=dsprintf"%nd"letchar=dsprintf"%C"letbool=dsprintf"%B"letunit=dsprintf"()"letstring=dsprintf"%S"letunknowntyname_=dsprintf"<abstr:%s>"tynametyperepresentation=documentend(* OCaml *)