Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file ppx_custom_printf.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295openBaseopenPpxlibopenAst_builder.Default(* returns the index of the conversion spec (unless the end of string is reached) *)letrecskip_over_format_flagsfmti=ifi>=String.lengthfmtthen`Eoielse(matchfmt.[i]with|'*'|'#'|'-'|' '|'+'|'_'|'0'..'9'|'.'->skip_over_format_flagsfmt(i+1)|_->`Oki);;(* doesn't check to make sure the format string is well-formed *)(* Formats with subformats are skipped for the following reasons:
One is that they are hard to understand and not often used.
Another is that subformats like "%(%{Module}%)" won't work, since it
is impossible to produce a format of type [(Module.t -> 'a,...) format].
*)lethas_subformats(fmt:string)=letlim=String.lengthfmt-1inletrecloopi=ifi>limthenfalseelseifChar.equalfmt.[i]'%'then(matchskip_over_format_flagsfmt(i+1)with|`Eoi->false|`Oki->(matchfmt.[i]with|'('|')'|'}'->true|_->loop(i+1)))elseloop(i+1)inloop0;;(* returns a list of strings where even indexed elements are parts of the format string
that the preprocessor won't touch and odd indexed elements are the contents of %{...}
specifications. *)letexplode~loc(s:string)=letlen=String.lengthsin(* for cases where we can't parse the string with custom format specifiers, consider
the string as a regular format string *)letas_normal_format_string=[s]inifhas_subformatssthenas_normal_format_stringelse(letsubfromto_=String.subs~pos:from~len:(to_-from)inletrecloopaccfromto_=assert(List.lengthacc%2=0);ifto_>=lenthenList.rev(iffrom>=lenthenaccelsesubfromlen::acc)elseifChar.(<>)s.[to_]'%'thenloopaccfrom(to_+1)else(matchskip_over_format_flagss(to_+1)with|`Eoi->as_normal_format_string|`Oki->(matchs.[i]with|'['->(* Scan char sets are not allowed by printf-like functions. So we might as
well disallow them at compile-time so that we can reuse them as magic
format strings in this implementation. *)Location.raise_errorf~loc"ppx_custom_printf: scan char sets are not allowed in custom format \
strings"|'{'->ifto_+1<>ithenLocation.raise_errorf~loc"ppx_custom_printf: unexpected format flags before %%{} specification \
in %S"s;(matchString.index_froms(to_+2)'}'with|None->as_normal_format_string|Somei->letl=sub(to_+2)i::subfromto_::accinloopl(i+1)(i+1))|_->loopaccfrom(i+1)))(* skip the conversion spec *)inloop[]00);;letprocessed_format_string~exploded_format_string=letl=letrecloopil=matchlwith|s1::_s2::l->s1::Printf.sprintf"%%%d[.]"i::loop(i+1)l|[s1]->[s1]|[]->[]inloop0exploded_format_stringinString.concatl~sep:"";;letrecevens=function|([]|[_])asl->l|x::_::l->x::evensl;;letodds=function|[]->[]|_::l->evensl;;(* Returns a pair of:
- a format string, which is [s] where all custom format specifications have been
replaced by ["%" ^ string_of_int index ^ "[.]"] where [index] is the number of
the custom format specification, starting from 0. This string can be passed directly
to [CamlinternalFormat.fmt_ebb_of_string]
- an array of custom format specifications, in the order they appear in the original
string
*)letextract_custom_format_specifications~locs=letexploded_format_string=explode~locsinletprocessed=processed_format_string~exploded_format_stringinletcustom_specs=Array.of_list(oddsexploded_format_string)inprocessed,custom_specs;;letgen_symbol=gen_symbol~prefix:"_custom_printf"letis_space=function|' '|'\t'|'\n'|'\r'->true|_->false;;letstrips=leta=ref0inletb=ref(String.lengths-1)inwhile!a<=!b&&is_spaces.[!a]doInt.incradone;while!a<=!b&&is_spaces.[!b]doInt.decrbdone;if!a>!bthen""elseString.subs~pos:!a~len:(!b-!a+1);;letstring_to_expr~locs=letsexp_converter_opt=matchString.lsplit2s~on:':'with|None->None|Some("sexp",colon_suffix)->Some([%exprPpx_sexp_conv_lib.Sexp.to_string_hum],colon_suffix)|Some(colon_prefix,colon_suffix)->(matchString.chop_prefixcolon_prefix~prefix:"sexp#"with|None->None|Somehash_suffix->Some(pexp_ident~loc(Located.mk~loc(Longident.parse("Ppx_sexp_conv_lib.Sexp.to_string_"^hash_suffix))),colon_suffix))inmatchsexp_converter_optwith|Some(sexp_converter,unparsed_type)->letlexbuf=Lexing.from_stringunparsed_typein(* ~loc is the position of the string, not the position of the %{bla} group we're
looking at. The format strings don't contain location information, so we can't
actually find the proper positions. *)lexbuf.lex_abs_pos<-loc.loc_start.pos_cnum;lexbuf.lex_curr_p<-loc.loc_start;letty=Parse.core_typelexbufinlete=Ppx_sexp_conv_expander.Sexp_of.core_typetyinletarg=gen_symbol()inpexp_fun~locNolabelNone(pvar~locarg)(eapply~locsexp_converter[eapply~loce[evar~locarg]])|None->letfailloc=Location.raise_errorf~loc"ppx_custom_printf: string %S should be of the form <Module>, \
<Module>.<identifier>, <Module>#identifier, sexp:<type>, or sexp#mach:<type>"sinlets,has_hash_suffix,to_string=matchString.lsplit2s~on:'#'with|None->s,false,"to_string"|Some(s,hash_suffix)->s,true,"to_string_"^hash_suffixinletto_string_id:Longident.t=lets=stripsinmatchswith|""->Lidentto_string|_->(matchLongident.parseswith|(Lidentn|Ldot(_,n))asid->ifString.(<>)n""&&Char.equal(Char.uppercasen.[0])n.[0]thenLongident.Ldot(id,to_string)elseifnothas_hash_suffixthenidelsefailloc|_->failloc)inletfunc=pexp_ident~loc(Located.mk~locto_string_id)in(* Eta-expand as the to_string function might take optional arguments *)letarg=gen_symbol()inpexp_fun~locNolabelNone(pvar~locarg)(eapply~locfunc[evar~locarg]);;classlifter~loc~custom_specs=object(self)inherit[expression]Format_lifter.liftassuperinheritPpxlib_metaquot_lifters.expression_lifterslocmethod!fmt:typef0f1f2f3f4f5.(f0->expression)->(f1->expression)->(f2->expression)->(f3->expression)->(f4->expression)->(f5->expression)->(f0,f1,f2,f3,f4,f5)CamlinternalFormatBasics.fmt->expression=funf0f1f2f3f4f5fmt->letopenCamlinternalFormatBasicsinmatchfmtwith(* Recognize the special form "%index[...whatever...]" *)|Scan_char_set(Someidx,_,fmt)(* [custom_specs] is empty if [explode] couldn't parse the string. In this case we
can have some scar char sets left. *)whenidx>=0&&idx<Array.lengthcustom_specs->letrest=self#fmt(fun_->assertfalse)f1f2f3f4f5fmtinletfunc=string_to_expr~loccustom_specs.(idx)in[%exprCustom(Custom_succCustom_zero,(fun()->[%efunc]),[%erest])]|_->super#fmtf0f1f2f3f4f5fmtendletexpand_format_string~locfmt_string=letprocessed_fmt_string,custom_specs=extract_custom_format_specifications~locfmt_stringinlet(CamlinternalFormat.Fmt_EBBfmt)=tryCamlinternalFormat.fmt_ebb_of_stringprocessed_fmt_stringwith|e->Location.raise_errorf~loc"%s"(matchewith(* [fmt_ebb_of_string] normally raises [Failure] on invalid input *)|Failuremsg->msg|e->Exn.to_stringe)inletlifter=newlifter~loc~custom_specsinletformat6=CamlinternalFormatBasics.Format(fmt,fmt_string)inletphantom_=assertfalseinlete=lifter#format6phantomphantomphantomphantomphantomphantomformat6in[%expr([%ee]:(_,_,_,_,_,_)CamlinternalFormatBasics.format6)];;letexpande=matche.pexp_descwith|Pexp_apply({pexp_attributes=ident_attrs;_},[(Nolabel,{pexp_desc=Pexp_constant(Pconst_string(str,_,_));pexp_loc=loc;pexp_loc_stack=_;pexp_attributes=str_attrs})])->assert_no_attributesident_attrs;assert_no_attributesstr_attrs;lete'=expand_format_string~locstrinSome{e'withpexp_attributes=Merlin_helpers.hide_attribute::e.pexp_attributes}|_->None;;let()=Driver.register_transformation"custom_printf"~rules:[Context_free.Rule.special_function"!"expand];;