123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672# 1 "vlt_ppx.ml.cppo"(*
* This file is part of Vlt.
* Copyright (C) 2023-2025 Codinuum.
*
* Vlt is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Vlt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*)openPpxlibmoduleArgs=structletlevel=ref5letlogger=ref""letfor_pack=ref""endletget_logger_namecode_path=if!Args.logger<>""then!Args.loggerelseif!Args.for_pack<>""then!Args.for_pack^"."^code_pathelsecode_pathmoduleName=structletexpand~ctxts=letloc=Expansion_context.Extension.extension_point_locctxtinAst_builder.Default.estring~locsletextract()=Ast_pattern.(single_expr_payload@@(estring__))letextension=Extension.(V3.declare"NAME"Context.expression(extract())expand)endmoduleProperties=structletexpand~ctxtexpr=letloc=Expansion_context.Extension.extension_point_locctxtin[%exprletopenVltin[%eexpr]]letextract()=Ast_pattern.(single_expr_payload__)letextension=Extension.(V3.declare"PROPERTIES"Context.expression(extract())expand)letextension_alt=Extension.(V3.declare"WITH"Context.expression(extract())expand)endmoduleException=structletexpand~ctxte=let_=ctxtineletextract()=Ast_pattern.(single_expr_payload__)letextension=Extension.(V3.declare"EXCEPTION"Context.expression(extract())expand)letextension_alt=Extension.(V3.declare"EXN"Context.expression(extract())expand)endletsplit_args=List.partition(fune->matche.pexp_descwith|Pexp_extension(loc,_)->beginmatchloc.txtwith|"NAME"|"PROPERTIES"|"WITH"|"EXCEPTION"|"EXN"->true|_->falseend|_->false)letmake_attr_tbl=List.map(fune->matche.pexp_descwith|Pexp_extension(loc,_)->beginmatchloc.txtwith|"NAME"->("cpath",e)|"PROPERTIES"|"WITH"->("props",e)|"EXCEPTION"|"EXN"->("exc",e)|_->assertfalseend|_->assertfalse)letarg_to_string=function{txt=lid;_}->String.concat"."(Astlib.Longident.flattenlid)letconv_pathlocp=letprefix=loc.loc_start.pos_fnameinifString.starts_with~prefixpthenletlen=String.lengthprefixinletsuffix=String.subplen((String.lengthp)-len)inletprefix_=tryFilename.(String.capitalize_ascii(chop_suffix(basenameprefix)".ml"))with_->prefixinprefix_^suffixelsepletget_attrslocpatharg_optal=letlogger_name=get_logger_name(matcharg_optwith|Somea->arg_to_stringa|None->conv_pathlocpath)inletlogger=Ast_builder.Default.estring~loclogger_nameinletpos=loc.loc_startinletfile_name=Ast_builder.Default.estring~locpos.pos_fnameinletline_num=Ast_builder.Default.eint~locpos.pos_lnuminletcol_num=Ast_builder.Default.eint~loc(pos.pos_cnum-pos.pos_bol)inletattr_tbl=make_attr_tblalinletlogger=matchList.assoc_opt"cpath"attr_tblwith|Somex->x|None->loggerinletprops=matchList.assoc_opt"props"attr_tblwith|Somex->x|None->[%expr[]]inletexc_opt=matchList.assoc_opt"exc"attr_tblwith|Somex->[%exprSome[%ex]]|None->[%exprNone]inlogger,file_name,line_num,col_num,props,exc_optmoduleLevel=structexceptionInvalid_levelofLocation.t*stringletfatalloc=[%exprVlt.Level.FATAL]leterrorloc=[%exprVlt.Level.ERROR]letwarnloc=[%exprVlt.Level.WARN]letinfoloc=[%exprVlt.Level.INFO]letdebugloc=[%exprVlt.Level.DEBUG]lettraceloc=[%exprVlt.Level.TRACE]letto_stringlv=matchlv.pexp_descwith|Pexp_construct({txt=Ldot(_,name);_},_)->name|Pexp_extension({txt=name;_},_)->name|_->letloc=lv.pexp_locinlets=Pprintast.string_of_expressionlvinletmes=Printf.sprintf"invalid log level expression: %s"sinraise(Invalid_level(loc,mes))let_to_int?(loc=Location.none)=function|"NONE"->-1|"FATAL"->0|"ERROR"->1|"WARN"->2|"INFO"->3|"DEBUG"->4|"TRACE"->5|s->(*Int.max_int*)letmes=Printf.sprintf"invalid log level: %s"sinraise(Invalid_level(loc,mes))letto_intlv=_to_int~loc:lv.pexp_loc(to_stringlv)letexpandlevel~ctxt=letloc=Expansion_context.Extension.extension_point_locctxtinlevellocletextract()=Ast_pattern.(drop)moduleExtension=structletmakenamebody=Extension.(V3.declarenameContext.expression(extract())(expandbody))letfatal=make"FATAL"fatalleterror=make"ERROR"errorletwarn=make"WARN"warnletinfo=make"INFO"infoletdebug=make"DEBUG"debuglettrace=make"TRACE"traceendmoduleLog=structletnot_fmtel=matchelwith|[e]->beginmatche.pexp_descwith|Pexp_constant(Pconst_string_)->false|_->trueend|_->falseletexpand?(guard=true)level~loc~path~argexpr_el=tryletlv=levellocinletlvi=to_intlviniflvi>!Args.levelthen[%expr()]elseletal,el=split_args_elinletel_=expr::elinletlogger,file_name,line_num,col_num,props,exc_opt=get_attrslocpathargalinletbody=ifnot_fmtel_thenAst_builder.Default.eapply~loc[%exprVlt.Logger.log[%elogger][%elv]~file:[%efile_name]~line:[%eline_num]~column:[%ecol_num]~properties:[%eprops]~error:[%eexc_opt]][[%exprletopenVltin[%eexpr]]]elseAst_builder.Default.eapply~loc[%exprVlt.Logger.logf[%elogger][%elv]~file:[%efile_name]~line:[%eline_num]~column:[%ecol_num]~properties:[%eprops]~error:[%eexc_opt]]el_inifguardthenmatchlv.pexp_descwith|Pexp_construct({txt=Ldot(_,"TRACE");_},_)->body|_->[%exprifVlt.Logger.check_level[%elogger][%elv]then[%ebody]else()]elsebodywithe->Ast_builder.Default.pexp_extension~loc@@Location.error_extensionf~loc"%s"(Printexc.to_stringe)letextract()=Ast_pattern.(alt(single_expr_payload@@pexp_apply__(many(no_label__)))(map_result~f:(funx->x[])(single_expr_payload__)))moduleExtension=structletmakeguardnamelevel=Ppxlib.Extension.(declare_with_path_argnameContext.expression(extract())(expand~guardlevel))lettbl=[(*name, guard, level*)"_fatal_log",false,fatal;"_error_log",false,error;"_warn_log",false,warn;"_info_log",false,info;"_debug_log",false,debug;"_trace_log",false,trace;"fatal_log",true,fatal;"error_log",true,error;"warn_log",true,warn;"info_log",true,info;"debug_log",true,debug;"trace_log",true,trace;]letis_ext_name=letnl=List.map(fun(n,_,_)->n)tblinlett=Hashtbl.create(List.lengthnl)inList.iter(funn->Hashtbl.addtntrue)nl;funx->tryHashtbl.findtxwithNot_found->falseletextensions=List.map(fun(name,guard,level)->makeguardnamelevel)tblendend(* module Level.Log *)moduleBlock=structexceptionTo_be_modifiedletmodify_exprlviexpr=letchecker=objectinheritAst_traverse.iterassupermethod!extensionext=matchextwith|{txt=lvn;loc},_when(_to_int~loclvn)<=lvi->raiseTo_be_modified|_->super#extensionextendinletname_to_int=function|"fatal_log"|"fatal_block"->0|"error_log"|"error_block"->1|"warn_log"|"warn_block"->2|"info_log"|"info_block"->3|"debug_log"|"debug_block"->4|"trace_log"|"trace_block"->5|_->Int.max_intinletmodifier=objectinheritAst_traverse.mapmethod!extensionext=matchextwith|{txt="LOG";loc},p->begintrylet_=checker#payloadpinextwithTo_be_modified->{txt="_LOG";loc},pend|{txt=name;loc},pwhen(name_to_intname)<=lvi->{txt="_"^name;loc},p|_->extendinmodifier#expressionexprletexpand?(guard=true)level~loc~path~argexpr_optexpr=tryletlv=levellocinletlvi=to_intlviniflvi>!Args.levelthen[%expr()]elseletexpr=modify_exprlviexprinletlogger_name=get_logger_name(matchargwith|Somea->arg_to_stringa|None->conv_pathlocpath)inletlogger=Ast_builder.Default.estring~loclogger_nameinletis_nulle=matche.pexp_descwith|Pexp_construct({txt=Lident"()";_},None)->true|_->falseinletbody=matchexpr_optwith|Somee->[%expr[%ee];[%eexpr]]|None->exprinifis_nullbody||notguardthenbodyelsematchlv.pexp_descwith|Pexp_construct({txt=Ldot(_,"TRACE");_},_)->body|_->[%exprifVlt.Logger.check_level[%elogger][%elv]then[%ebody]else()]withe->Ast_builder.Default.pexp_extension~loc@@Location.error_extensionf~loc"%s"(Printexc.to_stringe)letextract()=Ast_pattern.(alt_option(single_expr_payload@@pexp_sequence____)(single_expr_payload__))moduleExtension=structletmakeguardnamelevel=Ppxlib.Extension.(declare_with_path_argnameContext.expression(extract())(expand~guardlevel))lettbl=[(*name, guard, level*)"_fatal_block",false,fatal;"_error_block",false,error;"_warn_block",false,warn;"_info_block",false,info;"_debug_block",false,debug;"_trace_block",false,trace;"fatal_block",true,fatal;"error_block",true,error;"warn_block",true,warn;"info_block",true,info;"debug_block",true,debug;"trace_block",true,trace;]letis_ext_name=letnl=List.map(fun(n,_,_)->n)tblinlett=Hashtbl.create(List.lengthnl)inList.iter(funn->Hashtbl.addtntrue)nl;funx->tryHashtbl.findtxwithNot_found->falseletextensions=List.map(fun(name,guard,level)->makeguardnamelevel)tblendend(* module Level.Block *)end(* module Level *)moduleLog=structletsprintf_expr_listlocel=matchelwith|[e]->beginmatche.pexp_descwith|Pexp_constant(Pconst_string_)->e|_->[%exprletopenVltin[%ee]]end|_->Ast_builder.Default.eapply~loc[%exprPrintf.sprintf]elletexpand?(guard=true)~loc~path~arglv_el=tryletlvi=Level.to_intlviniflvi>!Args.levelthen[%expr()]elseletal,el=split_args_elinletmes=sprintf_expr_listlocelinletlogger,file_name,line_num,col_num,props,exc_opt=get_attrslocpathargalinletbody=[%exprVlt.Logger.log[%elogger][%elv]~file:[%efile_name]~line:[%eline_num]~column:[%ecol_num]~properties:[%eprops]~error:[%eexc_opt][%emes]]inifguardthenmatchlv.pexp_descwith|Pexp_extension(a,_)whenString.equala.txt"TRACE"->body|_->[%exprifVlt.Logger.check_level[%elogger][%elv]then[%ebody]else()]elsebodywith|Level.Invalid_level(loc,mes)->Ast_builder.Default.pexp_extension~loc@@Location.error_extensionf~loc"%s"mes|e->Ast_builder.Default.pexp_extension~loc@@Location.error_extensionf~loc"%s"(Printexc.to_stringe)letextract()=Ast_pattern.(single_expr_payload@@pexp_apply__(many(no_label__)))letextension,_extension=letfguardname=Extension.(declare_with_path_argnameContext.expression(extract())(expand~guard))inftrue"LOG",ffalse"_LOG"end(* module Log *)modulePrepare=structletexpand~ctxt=letloc=Expansion_context.Extension.extension_point_locctxtinif!Args.level>=0thenletcp=Expansion_context.Extension.code_pathctxtinletlogger_name=get_logger_name(Code_path.fully_qualified_pathcp)inletlogger=Ast_builder.Default.estring~loclogger_namein[%strilet()=Vlt.Logger.prepare[%elogger]]else[%strilet()=()]letextract()=Ast_pattern.(drop)letextension=Extension.(V3.declare"prepare_logger"Context.structure_item(extract())expand)endmoduleStructureItem=structletexpand~ctxtsi=letcp=Expansion_context.Extension.code_pathctxtinletlogger_name=get_logger_name(Code_path.fully_qualified_pathcp)inletmapper=object(self)inherit[stringlist]Ast_traverse.map_with_contextmethod!value_bindingctxtvb=matchvb.pvb_pat.ppat_descwith|Ppat_var{txt=vname;_}->{vbwithpvb_expr=self#expression(("."^vname)::ctxt)vb.pvb_expr}|_->{vbwithpvb_expr=self#expressionctxtvb.pvb_expr}(*method! structure_item_desc ctxt idesc =
match idesc with
| Pstr_eval(e, a) -> Pstr_eval (self#expression ctxt e, a)
| Pstr_value(r, vbl) -> Pstr_value(r, List.map (self#map_value_bind ctxt) vbl)
| Pstr_module mb -> Pstr_module (self#module_binding ctxt mb)
| Pstr_recmodule mbl -> Pstr_recmodule (List.map (self#module_binding ctxt) mbl)
| Pstr_class cdl -> Pstr_class (List.map (self#class_declaration ctxt) cdl)
| Pstr_include inc ->
Pstr_include { inc with pincl_mod = self#module_expr ctxt inc.pincl_mod }
| _ -> idesc*)method!module_bindingctxtmbind=matchmbindwith|{pmb_name={txt=Somemname;_};_}->beginletctxt=("."^mname)::ctxtin{mbindwithpmb_expr=self#module_exprctxtmbind.pmb_expr}end|_->{mbindwithpmb_expr=self#module_exprctxtmbind.pmb_expr}method!class_declarationctxtcdecl=matchcdeclwith|{pci_name={txt=cname;_};_}->beginletctxt=("."^cname)::ctxtin{cdeclwithpci_expr=self#class_exprctxtcdecl.pci_expr}endmethod!class_field_descctxtcfield_desc=matchcfield_descwith|Pcf_method({txt=mname;loc},priv,Cfk_concrete(ovrd,expr))->beginletctxt=("#"^mname)::ctxtinPcf_method({txt=mname;loc},priv,Cfk_concrete(ovrd,self#expressionctxtexpr))end|Pcf_initializerexpr->beginletctxt="#<init>"::ctxtinPcf_initializer(self#expressionctxtexpr)end|_->cfield_descmethod!expression_descctxtexpr_desc=letmap_expr=self#expressionctxtinletmap_expr_opt=function|Somee->Some(map_expre)|None->Noneinmatchexpr_descwith|Pexp_letmodule({txt=mname_opt;_}asm,me,e)->beginletctxt=matchmname_optwith|Somemname->("."^mname)::ctxt|None->ctxtinPexp_letmodule(m,self#module_exprctxtme,map_expre)end|Pexp_let(r,vbl,e)->Pexp_let(r,List.map(self#value_bindingctxt)vbl,map_expre)# 573 "vlt_ppx.ml.cppo"|Pexp_functionc->Pexp_function(self#casesctxtc)|Pexp_fun(a,e_opt,p,e)->Pexp_fun(a,map_expr_opte_opt,p,map_expre)# 577 "vlt_ppx.ml.cppo"|Pexp_apply(e,al)->Pexp_apply(map_expre,List.map(fun(l,e)->l,map_expre)al)|Pexp_match(e,c)->Pexp_match(map_expre,self#casesctxtc)|Pexp_try(e,c)->Pexp_try(map_expre,self#casesctxtc)|Pexp_tupleel->Pexp_tuple(List.mapmap_exprel)|Pexp_construct(c,Somee)->Pexp_construct(c,Some(map_expre))|Pexp_variant(l,Somee)->Pexp_variant(l,Some(map_expre))|Pexp_record(rl,e_opt)->Pexp_record(List.map(fun(l,e)->l,map_expre)rl,map_expr_opte_opt)|Pexp_field(e,l)->Pexp_field(map_expre,l)|Pexp_setfield(e0,l,e1)->Pexp_setfield(map_expre0,l,map_expre1)|Pexp_arrayel->Pexp_array(List.mapmap_exprel)|Pexp_ifthenelse(e0,e1,e2_opt)->Pexp_ifthenelse(map_expre0,map_expre1,map_expr_opte2_opt)|Pexp_sequence(e0,e1)->Pexp_sequence(map_expre0,map_expre1)|Pexp_while(e0,e1)->Pexp_while(map_expre0,map_expre1)|Pexp_for(p,e0,e1,d,e2)->Pexp_for(p,map_expre0,map_expre1,d,map_expre2)|Pexp_constraint(e,t)->Pexp_constraint(map_expre,t)|Pexp_coerce(e,t_opt,t)->Pexp_coerce(map_expre,t_opt,t)|Pexp_send(e,l)->Pexp_send(map_expre,l)|Pexp_setinstvar(l,e)->Pexp_setinstvar(l,map_expre)|Pexp_overridelel->Pexp_override(List.map(fun(l,e)->l,map_expre)lel)|Pexp_letexception(c,e)->Pexp_letexception(c,map_expre)|Pexp_asserte->Pexp_assert(map_expre)|Pexp_lazye->Pexp_lazy(map_expre)|Pexp_poly(e,t)->Pexp_poly(map_expre,t)|Pexp_objectcs->Pexp_object(self#class_structurectxtcs)|Pexp_newtype(l,e)->Pexp_newtype(l,map_expre)|Pexp_open(o,e)->Pexp_open(o,map_expre)|Pexp_letopl->beginletmap_bopbop=matchbop.pbop_pat.ppat_descwith|Ppat_var{txt=vname;_}->{bopwithpbop_exp=self#expression(("."^vname)::ctxt)bop.pbop_exp}|_->{bopwithpbop_exp=map_exprbop.pbop_exp}inPexp_letop{let_=map_bopl.let_;ands=List.mapmap_bopl.ands;body=map_exprl.body}end|Pexp_extensione->Pexp_extension(self#extensionctxte)|_->expr_descmethod!extensionctxtext=letchecken=en="LOG"||en="_LOG"||Level.Log.Extension.is_ext_nameen||Level.Block.Extension.is_ext_nameeninmatchextwith|{txt=ename;loc},pwhenctxt<>[]&&checkename->beginletpath="."^logger_name^(String.concat""(List.revctxt))in{txt=ename^path;loc},self#payloadctxtpend|_->extendinmapper#structure_item[]siletextract()=Ast_pattern.(pstr(__^::nil))letextension=Extension.(V3.declare"capture_path"Context.structure_item(extract())expand)endlet()=Driver.add_arg"-level"(Arg.String(funs->Args.level:=Level._to_ints))~doc:"<level> Set logging level";Driver.add_arg"-logger"(Arg.Set_stringArgs.logger)~doc:"<name> Set logger name";Driver.add_arg"-for-pack"(Arg.Set_stringArgs.for_pack)~doc:"<prefix> Set prefix for logger names";letlevel_rules=List.mapContext_free.Rule.extensionLevel.Extension.([fatal;error;warn;info;debug;trace])inletlog_rule=Context_free.Rule.extensionLog.extensioninlet_log_rule=Context_free.Rule.extensionLog._extensioninletname_rule=Context_free.Rule.extensionName.extensioninletprops_rule=Context_free.Rule.extensionProperties.extensioninletprops_alt_rule=Context_free.Rule.extensionProperties.extension_altinletexc_rule=Context_free.Rule.extensionException.extensioninletexc_alt_rule=Context_free.Rule.extensionException.extension_altinletprep_rule=Context_free.Rule.extensionPrepare.extensioninletvlt_rule=Context_free.Rule.extensionStructureItem.extensioninletrules0=log_rule::_log_rule::name_rule::props_rule::props_alt_rule::exc_rule::exc_alt_rule::level_rulesinletrules1=List.fold_left(funlx->(Context_free.Rule.extensionx)::l)rules0Level.Log.Extension.extensionsinletrules=vlt_rule::prep_rule::(List.fold_left(funlx->(Context_free.Rule.extensionx)::l)rules1Level.Block.Extension.extensions)inDriver.register_transformation~rules"_vlt_ppx"