123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472(*
Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2017 Anton Lavrik
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*)moduleC=Piqi_commonopenCopenIolist(* split (utf8) string into individual lines treating '\n' as a separator *)letsplit_texts=letrecauxleniaccu=ifi<0thenletres=String.subs0leninres::accuelseifs.[i]='\n'thenletres=String.subs(i+1)leninaux0(i-1)(res::accu)elseaux(len+1)(i-1)accuinaux0(String.lengths-1)[]letmake_text_lines=ifs=""thenios"#"elseios"# "^^ioss(* NOTE: list is not empty *)letprint_textl=letl=List.fold_left(funaccux->eol::(make_text_linex)::accu)[]liniol(List.revl)letrecis_multiline=function|Ioss->String.containss'\n'|Ioll->List.fold_left(funaccux->accu||is_multilinex)falsel|Iob'\n'->true|Indent|Unindent|Eol->true|_->falseletuint64_to_stringx=(* XXX: printing large unsigned uint values in hex *)ifInt64.comparex0L>=0thenPrintf.sprintf"%Lu"xelsePrintf.sprintf"0x%Lx"x(* This method for printing floats is borrowed from Martin Jambon's Yojson
* library, see piqi_json_gen.ml for details. *)(*
Ensure that the float is not printed as an int.
This is not required by JSON, but useful in order to guarantee
reversibility.
*)letfloat_needs_periods=tryfori=0toString.lengths-1domatchs.[i]with'0'..'9'|'-'->()|_->raiseExitdone;truewithExit->false(*
Both write_float_fast and write_float guarantee
that a sufficient number of digits are printed in order to
allow reversibility.
The _fast version is faster but often produces unnecessarily long numbers.
*)letwrite_floatobx=lets1=Printf.sprintf"%.16g"xinlets=iffloat_of_strings1=xthens1elsePrintf.sprintf"%.17g"xinBuffer.add_stringobs;iffloat_needs_periodsthenBuffer.add_stringob".0"(*
let write_float_fast ob x =
let s = Printf.sprintf "%.17g" x in
Buffer.add_string ob s;
if float_needs_period s then
Buffer.add_string ob ".0"
let write_float = write_float_fast
*)letstring_of_floatx=letob=Buffer.create20inwrite_floatobx;Buffer.contentsob(* XXX: providing custom version since Pervasives.string_of_float will add
* trailing "." to the literal *)letformat_floatx=matchPervasives.classify_floatxwith|FP_nan->"0.nan"|FP_infinite->(** Number is positive or negative infinity *)ifx=Pervasives.infinitythen"0.inf"else"-0.inf"|FP_normal(** Normal number, none of the below *)|FP_zero(** Number is 0.0 or -0.0 *)|FP_subnormal->(** Number very close to 0.0, has reduced precision *)string_of_floatx(*
* Pretty-printing
*)moduleFmt=Easy_formatletcommon_list=Fmt.({listwithindent_body=4;})letatom_list=Fmt.({common_listwithwrap_body=`Always_wrap;})letsingle_elem_list=Fmt.({common_listwithwrap_body=`Always_wrap;})letmultiple_elem_list=Fmt.({common_listwithwrap_body=`Force_breaks;})letform_list=Fmt.({common_listwithspace_after_opening=false;space_before_closing=false;})letmulti_form_list=Fmt.({form_listwithwrap_body=`Force_breaks;})letsingle_form_list=Fmt.({form_listwithwrap_body=`Always_wrap;})letatom_form_list=Fmt.({form_listwithwrap_body=`Always_wrap;})letmake_atomx=Fmt.Atom(x,Fmt.atom)letis_atom=function|Fmt.Atom_->true|_->falseletrechas_list=function|Fmt.List_->true|Fmt.Custom_->true|Fmt.Label((label,_),node)->ifhas_listlabelthentrueelsehas_listnode|_->falseletmake_listl=letfmt=matchlwith|[]->single_elem_list|[x]->ifhas_listxthenmultiple_elem_listelsesingle_elem_list|_->ifList.for_allis_atomlthenatom_listelsemultiple_elem_listinFmt.List(("[","","]",fmt),l)letmake_form_fmtargs=(* TODO: unify this with similar code in make_list *)matchargswith|[]->single_form_list|[x]->ifhas_listxthenmulti_form_listelsesingle_form_list|l->ifList.for_allis_atomlthenatom_form_listelsemulti_form_listletmake_formnameargs=letfmt=make_form_fmtargsinletextra_space=(* add space after name it is followed by args *)ifargs<>[]then" "else""inFmt.List(("("^name^extra_space,"",")",fmt),args)(* parenthesis around an ast element *)letmake_parensast=letfmt=make_form_fmt[ast]inFmt.List(("(","",")",fmt),[ast])letmake_labellabelnode=letfmt=Fmt.({labelwithindent_after_label=4;})inFmt.Label((label,fmt),node)letquotes="\""^s^"\""letformat_text_line?(indent=false)s=letspace=ifindentthen" "(* standard 4 space indentation after label *)else""(* no indentation *)inifs=""thenspace^"#"elsespace^"# "^s(* ~is_labeled = true if text appears after a label;
* ~is_first = true if text is the first element in the list *)letformat_textl~is_labeled~is_first=matchlwith|[]->assertfalse|[x]whenis_labeled->(* single text line after label *)(* try to put a single text line on the same line with its label *)letfmt=Fmt.({common_listwithwrap_body=`Force_breaks;align_closing=false;space_after_opening=false;space_before_closing=false;})in(* no opening, closing; break after each item; standard 4-space
* indentation *)letline=format_text_linexinFmt.List(("","","",fmt),[make_atomline])|h::t->(* more than one lines of text *)(* print several lines them as one block; indented if it appears after
* a label *)Fmt.Custom(funfmt->(* force new line before text block if it appears after a label or if
* it is not the first element of the list *)ifis_labeled||notis_firstthenFormat.pp_force_newlinefmt();letprint_lines=letline=format_text_lines~indent:is_labeledinFormat.pp_print_stringfmtlineinprint_lineh;List.iter(funx->Format.pp_force_newlinefmt();print_linex;)t;)(* we need to take `name in parenthesis unless followed by `named or another
* `name *)letpreprocess_namesl=letrecauxl=matchlwith|[]|[_]->l|(`name_)asname::t->lett=auxtin(* we need to process the list from right to left *)(matchList.hdtwith|`name_|`named_|`typename_|`typed_->(* leave unchanged *)name::t|_->(* if followed by anything else, we need to take the name in
* parenthesis *)`form(name,[])::t)|h::t->h::(auxt)inauxlletformat_ast(x:piq_ast)=letrecaux?(is_labeled=false)?(is_first=false)=function|`int(x,"")->make_atom(Int64.to_stringx)|`uint(x,"")->make_atom(uint64_to_stringx)|`float(x,"")->make_atom(format_floatx)|`string(s,"")->make_atom(quote(Piq_lexer.escape_strings))|`binary(s,"")->make_atom(quote(Piq_lexer.escape_binarys))(* use original literals when they are available *)|`int(_,s)|`uint(_,s)|`float(_,s)->make_atoms|`string(_,s)|`binary(_,s)->make_atom(quotes)|`raw_strings->(* This literal can't be read back reliably after printing, and it
* doesn't come from Piq, but we still need to print it somehow -- in
* case if it is present. *)(* XXX: printing it is as binary for now, but may try to print it as
* utf8 string if it does represet a valid string. *)make_atom(quote(Piq_lexer.escape_binarys))|`booltrue->make_atom"true"|`boolfalse->make_atom"false"|`words->make_atoms|`texts->format_text(split_texts)~is_labeled~is_first|`names->make_atom("."^s)|`typenames->letname=":"^sin(* parentheses are not needed if `typename is followed by `typed,
* `named, `name or another `typename, but using them anyway for now *)make_formname[]|`named{Piq_ast.Named.name=n;Piq_ast.Named.value=v}->letjoined_labels,node=format_labeled_astvinletname="."^n^joined_labelsin(matchnodewith|None->make_atomname|Somenode->make_label(make_atomname)node)|`typed{Piq_ast.Typed.typename=n;Piq_ast.Typed.value=v}->letjoined_labels,node=format_labeled_astvinletname=":"^n^joined_labelsinifnotis_labeledthenmatchnodewith|None->make_atomname|Somenode->make_label(make_atomname)nodeelseletnodes=matchnodewith|None->[]|Somenode->[node]in(* wrap typed in parenthesis by creating a (:<typename> ...) form *)(make_formnamenodes)|`list[]->make_atom"[]"|`listl->make_list(map_auxl)|`form(name,args)->(matchnamewith|(#Piq_ast.form_nameasform_name)->(* this is a form *)letname=matchform_namewith|`words->s|`names->"."^s|`typenames->":"^sinifPiq_ast.is_infix_formform_nameargsthenletname=name^"*"inmake_label(make_atomname)(make_list(map_auxargs))elsemake_formname(map_auxargs)|ast->(* this is an ast element in parenthesis *)make_parens(auxast))|`any_->(* shouldn't happen except when C.debug_level > 0 *)make_atom"!PIQI-ANY!"andmap_auxl=matchlwith|[]->[]|l->(* we need to take `name in parenthesis unless followed by `named or
* another `name *)letl=preprocess_nameslin(aux(List.hdl)~is_first:true)::(List.mapaux(List.tll))andformat_labeled_ast=function|`namen->"."^n,None|`named{Piq_ast.Named.name=n;Piq_ast.Named.value=v}->letjoined_labels,node=format_labeled_astvin"."^n^joined_labels,node|x->"",Some(auxx~is_labeled:true)inauxx~is_first:true(* TODO: remove trailing line whitespace left by the pretty-printing library *)letto_buffer?(nl=true)bufx=Fmt.Pretty.to_bufferbuf(format_astx);ifnlthenBuffer.add_charbuf'\n'letto_string?(nl=true)x=letbuf=Buffer.create256into_buffer~nlbufx;Buffer.contentsbufletto_channelchx=Fmt.Pretty.to_channelch(format_astx);output_charch'\n'(* make sure that text file ends with a newline *)