123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935(* This file is part of the Catala compiler, a specification language for tax
and social benefits computation rules. Copyright (C) 2023 Inria, contributor:
Louis Gesbert <louis.gesbert@inria.fr>.
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. *)openCatala_utilsopenShared_astmoduleRuntime=Catala_runtimemoduleStyle=structtypecolor=Graph.Graphviz.colortypeelt={fill:color;border:color;stroke:int;(* in px *)text:color;}typetheme={page_background:Graph.Graphviz.color;arrows:Graph.Graphviz.color;input:elt;middle:elt;constant:elt;condition:elt;output:elt;}letdark={page_background=0x0;arrows=0x606060;input={fill=0x252526;border=0xBC3FBC;stroke=2;text=0xFFFFFF};middle={fill=0x252526;border=0x0097FB;stroke=2;text=0xFFFFFF};constant={fill=0x252526;border=0x40C8AE;stroke=2;text=0xFFFFFF};condition={fill=0x252526;border=0xff7700;stroke=2;text=0xFFFFFF};output={fill=0x252526;border=0xFFFFFF;stroke=2;text=0xFFFFFF};}letlight={page_background=0xffffff;arrows=0x0;input={fill=0xffaa55;border=0x0;stroke=1;text=0x0};middle={fill=0xffee99;border=0x0;stroke=1;text=0x0};constant={fill=0x99bbff;border=0x0;stroke=1;text=0x0};condition={fill=0xffffff;border=0xff7700;stroke=2;text=0x0};output={fill=0xffffff;border=0x1;stroke=2;text=0x0};}letwidthpixels=letdpi=96.inletpt_per_inch=72.28infloat_of_intpixels/.dpi*.pt_per_inchendtypeflags={with_conditions:bool;with_cleanup:bool;merge_level:int;format:[`Dot|`Convertofstring];theme:Style.theme;show:stringoption;output:Global.raw_fileoption;base_src_url:string;line_format:string;inline_module_usages:bool;}(* -- Definition of the lazy interpreter -- *)letlogfmt=Format.ifprintfFormat.err_formatter(fmt^^"@\n")leterrore=Message.error~pos:(Expr.pose)letnoassert=truemoduleEnv=structtypet=Envof(expr,elt)Var.Map.tandelt={base:expr*t;mutablereduced:expr*t}andexpr=(dcalc,annotcustom)gexprandannot={conditions:(expr*t)list}letfindv(Envt)=Var.Map.findvt(* let get_bas v t = let v, env = find v t in v, !env *)letaddvee_env(Envt)=Env(Var.Map.addv{base=e,e_env;reduced=e,e_env}t)letempty=EnvVar.Map.emptyletjoin(Envt1)(Envt2)=Env(Var.Map.union(fun_x1x2->(* assert (x1 == x2); *)Somex2)t1t2)letprintppf(Envt)=Format.pp_print_list~pp_sep:Format.pp_print_space(funppfv->Print.var_debugppfv)ppf(Var.Map.keyst)endtypeexpr=Env.exprtypeannot=Env.annot={conditions:(expr*Env.t)list}typelaziness_level={eval_struct:bool;(* if true, evaluate members of structures, tuples, etc. *)eval_op:bool;(* if false, evaluate the operands but keep e.g. `3 + 4` as is *)eval_match:bool;eval_default:bool;(* if false, stop evaluating as soon as you can discriminate with
`EEmptyError` *)eval_vars:exprVar.t->bool;(* if false, variables are only resolved when they point to another
unchanged variable *)}letvalue_level={eval_struct=false;eval_op=true;eval_match=true;eval_default=true;eval_vars=(fun_->true);}letadd_condition~conditione=Mark.map_mark(fun(Custom{pos;custom={conditions}})->Custom{pos;custom={conditions=condition::conditions}})eletadd_conditions~conditionse=Mark.map_mark(fun(Custom{pos;custom={conditions=c}})->Custom{pos;custom={conditions=conditions@c}})eletneg_op=function|Op.Xor->SomeOp.Eq(* Alright, we are cheating here since the type is wider, but the
transformation preserves the semantics *)|Op.Lt_int_int->SomeOp.Gte_int_int|Op.Lt_rat_rat->SomeOp.Gte_rat_rat|Op.Lt_mon_mon->SomeOp.Gte_mon_mon|Op.Lt_dat_dat->SomeOp.Gte_dat_dat|Op.Lt_dur_dur->SomeOp.Gte_dur_dur|Op.Lte_int_int->SomeOp.Gt_int_int|Op.Lte_rat_rat->SomeOp.Gt_rat_rat|Op.Lte_mon_mon->SomeOp.Gt_mon_mon|Op.Lte_dat_dat->SomeOp.Gt_dat_dat|Op.Lte_dur_dur->SomeOp.Gt_dur_dur|Op.Gt_int_int->SomeOp.Lte_int_int|Op.Gt_rat_rat->SomeOp.Lte_rat_rat|Op.Gt_mon_mon->SomeOp.Lte_mon_mon|Op.Gt_dat_dat->SomeOp.Lte_dat_dat|Op.Gt_dur_dur->SomeOp.Lte_dur_dur|Op.Gte_int_int->SomeOp.Lt_int_int|Op.Gte_rat_rat->SomeOp.Lt_rat_rat|Op.Gte_mon_mon->SomeOp.Lt_mon_mon|Op.Gte_dat_dat->SomeOp.Lt_dat_dat|Op.Gte_dur_dur->SomeOp.Lt_dur_dur|_->Noneletrecbool_negationpose=matchExpr.skip_wrappersewith|ELit(LBooltrue),m->ELit(LBoolfalse),m|ELit(LBoolfalse),m->ELit(LBooltrue),m|EAppOp{op=Op.Not,_;args=[(e,_)]},m->e,m|(EAppOp{op=op,opos;tys;args=[e1;e2]},m)ase->(matchopwith|Op.And->(EAppOp{op=Op.Or,opos;tys;args=[bool_negationpose1;bool_negationpose2];},m)|Op.Or->(EAppOp{op=Op.And,opos;tys;args=[bool_negationpose1;bool_negationpose2];},m)|op->(matchneg_opopwith|Someop->EAppOp{op=op,opos;tys;args=[e1;e2]},m|None->(EAppOp{op=Op.Not,opos;tys=[TLitTBool,Expr.mark_posm];args=[e];},m)))|(_,m)ase->(EAppOp{op=Op.Not,pos;tys=[TLitTBool,Expr.mark_posm];args=[e]},m)letreclazy_eval:decl_ctx->Env.t->laziness_level->expr->expr*Env.t=functxenvllevele0->leteval_to_value?(eval_default=true)enve=lazy_evalctxenv{value_levelwitheval_default}einletis_zeroenve=letzero=Runtime.integer_of_int0inlete,_env=eval_to_valueenveinletcondition=matchMark.removeewith|ELit(LInti)->Runtime.o_eq_int_intzeroi|ELit(LRatr)->Runtime.o_eq_rat_rat(Runtime.decimal_of_integerzero)r|ELit(LMoneym)->Runtime.o_eq_mon_mon(Runtime.money_of_cents_integerzero)m|ELit(LDurationdt)->Runtime.duration_to_years_months_daysdt=(0,0,0)|_->falseinifconditionthenSome(e,env)elseNoneinletis_oneenve=letone=Runtime.integer_of_int1inlete,env=eval_to_valueenveinletcondition=matchMark.removeewith|ELit(LInti)->Runtime.o_eq_int_intonei|ELit(LRatr)->Runtime.o_eq_rat_rat(Runtime.decimal_of_integerone)r|ELit(LMoneym)->Runtime.o_eq_mon_mon(Runtime.money_of_units_int1)m|ELit(LDurationdt)->Runtime.duration_to_years_months_daysdt=(0,0,1)|_->falseinifconditionthenSome(e,env)elseNoneinmatche0with|EVarv,_->if(notllevel.eval_default)||not(llevel.eval_varsv)thene0,envelse(* Variables reducing to EEmpty should not propagate to parent EDefault
(?) *)letenv_elt=tryEnv.findvenvwithVar.Map.Not_found_->errore0"Variable %a undefined [@[<hv>%a@]]"Print.var_debugvEnv.printenvinlete,env1=env_elt.reducedinletr,env1=lazy_evalctxenv1lleveleinenv_elt.reduced<-r,env1;r,Env.joinenvenv1|EAppOp{op=op,opos;args;tys},m->(ifnotllevel.eval_defaultthene0,envelsematchopwith|(Op.Map|Op.Filter|Op.Reduce|Op.Fold|Op.Length)asop->((* when not llevel.eval_op *)(* Distribute collection operations to the terms rather than use their
runtime implementations *)letarr=List.hd(List.revargs)in(* All these ops have the array as last arg *)letaty=List.hd(List.revtys)inmatcheval_to_valueenvarrwith|(EArrayelts,_),env->leteappfe=EApp{f;args=[e];tys=[]},minletempty_condition()=(* Is the expression [length(arr) = 0] *)letpos=Expr.mark_posmin(EAppOp{op=Op.Eq_int_int,opos;tys=[TLitTInt,pos;TLitTInt,pos];args=[(EAppOp{op=Op.Length,opos;tys=[aty];args=[arr]},m);ELit(LInt(Runtime.integer_of_int0)),m;];},m)inlete,env=matchop,args,eltswith|(Op.Map|Op.Filter),_,[]->lete=EArray[],minadd_condition~condition:(empty_condition(),env)e,env|(Op.Reduce|Op.Fold),[_;dft;_],[]->add_condition~condition:(empty_condition(),env)dft,env|Op.Map,[f;_],elts->(EArray(List.map(eappf)elts),m),env|Op.Filter,[f;_],elts->letrev_elts,env=List.fold_left(fun(elts,env)e->letcond=eappfeinmatchlazy_evalctxenvvalue_levelcondwith|(ELit(LBooltrue),_),_->add_condition~condition:(cond,env)e::elts,env|(ELit(LBoolfalse),_),_->elts,env|_->assertfalse)([],env)eltsin(EArray(List.revrev_elts),m),env(* Note: no annots for removed terms, even if the result is empty *)|Op.Reduce,[f;_;_],elt0::elts->lete=List.fold_left(funaccelt->EApp{f;args=[acc;elt];tys=[]},m)elt0eltsine,env|Op.Fold,[f;base;_],elts->lete=List.fold_left(funaccelt->EApp{f;args=[acc;elt];tys=[]},m)baseeltsine,env|Op.Length,[_],elts->(ELit(LInt(Runtime.integer_of_int(List.lengthelts))),m),env|_->assertfalsein(* We did a transformation (removing the outer operator), but further
evaluation may be needed to guarantee that [llevel] is reached *)lazy_evalctxenv{llevelwitheval_match=true}e|_->(EAppOp{op=op,opos;args;tys},m),env)|_->(letenv,args=List.fold_left_map(funenve->lete,env=lazy_evalctxenvlleveleinenv,e)envargsinletare_zeroes=lazy(List.map(funx->x,is_zeroenvx)args)inletare_ones=lazy(List.map(funx->x,is_oneenvx)args)inmatchop,are_zeroes,are_oneswith(* First handle neutral elements: they are removed from the formula, but
added as conditions *)|((Op.Mult_int_int|Op.Mult_rat_rat),_,(lazy([(x_neutral,Some(neutral,env));(not_neutral,None)]|[(not_neutral,None);(x_neutral,Some(neutral,env))])))(* Note: we could add [Op.Mult_mon_rat | Op.Mult_dur_int] here, but that
would require inserting a conversion operator instead *)|((Op.Add_dat_dur_|Op.Add_dur_dur|Op.Add_int_int|Op.Add_mon_mon|Op.Add_rat_rat),(lazy([(x_neutral,Some(neutral,env));(not_neutral,None)]|[(not_neutral,None);(x_neutral,Some(neutral,env))])),_)|((Op.Sub_dat_dur_|Op.Sub_dur_dur|Op.Sub_int_int|Op.Sub_mon_mon|Op.Sub_rat_rat),(lazy[(not_neutral,None);(x_neutral,Some(neutral,env))]),_)->letannot=Custom{pos=opos;custom={conditions=[]}}inletcondition=((EAppOp{op=Op.Eq,opos;args=[x_neutral;neutral];tys},annot),env)inadd_condition~conditionnot_neutral,env|_->ifnotllevel.eval_opthen(EAppOp{op=op,opos;args;tys},m),envelseletrenv=refenvin(* Dirty workaround returning env and conds from
evaluate_operator *)letevale=lete,env=lazy_evalctx!renvlleveleinrenv:=env;einlete=Interpreter.evaluate_operatoreval(op,opos)mGlobal.En(* Default language to English but this should not raise any
error messages so we don't care. *)argsine,!renv))|EApp{f;args},m->(ifnotllevel.eval_defaultthene0,envelsematcheval_to_valueenvfwith|(EAbs{binder;_},_),env->letvars,body=Bindlib.unmbindbinderinletenv=Seq.fold_left2(funenv1vare->Env.addvareenvenv1)env(Array.to_seqvars)(List.to_seqargs)inlete,env=lazy_evalctxenvllevelbodyine,env|e,_->errore"Invalid apply on %a"Expr.formate)|(EAbs_|ELit_|EEmpty|EPos_),_->e0,env(* these are values *)|(EStruct_|ETuple_|EInj_|EArray_),_->ifnotllevel.eval_structthene0,envelseletenv,e=Expr.map_gather~acc:env~join:Env.join~f:(fune->lete,env=lazy_evalctxenvlleveleinenv,Expr.boxe)e0inExpr.unboxe,env|EStructAccess{e;name;field},_->(ifnotllevel.eval_defaultthene0,envelsematcheval_to_valueenvewith|(EStruct{name=n;fields},_),envwhenStructName.equalnamen->lete,env=lazy_evalctxenvllevel(StructField.Map.findfieldfields)ine,env|_->e0,env)|ETupleAccess{e;index;size},_->(ifnotllevel.eval_defaultthene0,envelsematcheval_to_valueenvewith|(ETuplees,_),envwhenList.lengthes=size->lazy_evalctxenvllevel(List.nthesindex)|e,_->errore"Invalid tuple access on %a"Expr.formate)|EMatch{e;name;cases},_->(ifnotllevel.eval_matchthene0,envelsematcheval_to_valueenvewith|(EInj{name=n;cons;e=e1},m),envwhenEnumName.equalnamen->letcondition=e,envin(* FIXME: condition should be "e TEST_MATCH n" but we don't have a
concise expression to express that *)lete1,env=lazy_evalctxenvllevel(EApp{f=EnumConstructor.Map.findconscases;args=[e1];tys=[];},m)inadd_condition~conditione1,env|e,_->errore"Invalid match argument %a"Expr.formate)|EDefault{excepts;just;cons},m->(letexcs=List.filter_map(fune->matcheval_to_valueenve~eval_default:falsewith|(EEmpty,_),_->None|e->Somee)exceptsinmatchexcswith|[]->(matcheval_to_valueenvjustwith|(ELit(LBooltrue),_),_->letcondition=just,envinlete,env=lazy_evalctxenvllevelconsinadd_condition~conditione,env|(ELit(LBoolfalse),_),_->(EEmpty,m),env(* Note: conditions for empty are skipped *)|e,_->errore"Invalid exception justification %a"Expr.formate)|[(e,env)]->log"@[<hov 5>EVAL %a@]"Expr.formate;lazy_evalctxenvllevele|_::_::_->Message.error~pos:(Expr.mark_posm)~extra_pos:(List.map(fun(e,_)->"",Expr.pose)excs)"Conflicting exceptions")|EPureDefaulte,_->lazy_evalctxenvllevele|EIfThenElse{cond;etrue;efalse},m->(matcheval_to_valueenvcondwith|(ELit(LBooltrue),_),_->letcondition=cond,envinlete,env=lazy_evalctxenvlleveletrueinadd_condition~conditione,env|(ELit(LBoolfalse),m),_->(letcondition=bool_negation(Expr.mark_posm)cond,envinlete,env=lazy_evalctxenvllevelefalseinmatchefalsewith(* The negated condition is not added for nested [else if] to reduce
verbosity *)|EIfThenElse_,_->e,env|_->add_condition~conditione,env)|e,_->errore"Invalid condition %a"Expr.formate)|EErrorOnEmptye,_->(matcheval_to_valueenve~eval_default:falsewith|((EEmpty,_)ase'),_->(* This does _not_ match the eager semantics ! *)errore'"This value is undefined %a"Expr.formate|e,env->lazy_evalctxenvllevele)|EAsserte,m->(ifnoassertthen(ELitLUnit,m),envelsematcheval_to_valueenvewith|(ELit(LBooltrue),m),env->(ELitLUnit,m),env|(ELit(LBoolfalse),_),_->errore"Assert failure (%a)"Expr.formateerrore"Assert failure (%a)"Expr.formate|_->errore"Invalid assertion condition %a"Expr.formate)|EFatalErrorerr,_->errore0"%a"Format.pp_print_text(Runtime.error_messageerr)|EExternal_,_->assertfalse(* todo *)|_->.letresult_levelbase_vars={value_levelwitheval_struct=true;eval_op=false;eval_vars=(funv->not(Var.Set.memvbase_vars));}letinterpret_program(prg:('dcalc,'m)gexprprogram)(scope:ScopeName.t):('t,'m)gexpr*Env.t=letctx=prg.decl_ctxinlet(all_env,scopes),_=BoundList.fold_leftprg.code_items~init:(Env.empty,ScopeName.Map.empty)~f:(fun(env,scopes)itemv->matchitemwith|ScopeDef(name,body)->lete=Scope.to_exprctxbodyinlete=Expr.remove_logging_calls(Expr.unboxe)in(Env.addv(Expr.unboxe)envenv,ScopeName.Map.addname(v,body.scope_body_input_struct)scopes)|Topdef(_,_,_,e)->Env.addveenvenv,scopes)inletscope_v,_scope_arg_struct=ScopeName.Map.findscopescopesinlete,env=(Env.findscope_vall_env).baseinlog"=====================";log"%a"(Print.expr~debug:true())e;log"=====================";(* let m = Mark.get e in *)(* let application_arg =
* Expr.estruct scope_arg_struct
* (StructField.Map.map
* (function
* | TArrow (ty_in, ty_out), _ ->
* Expr.make_abs
* [| Var.make "_" |]
* (Bindlib.box EEmptyError, Expr.with_ty m ty_out)
* ty_in (Expr.mark_pos m)
* | ty -> Expr.evar (Var.make "undefined_input") (Expr.with_ty m ty))
* (StructName.Map.find scope_arg_struct ctx.ctx_structs))
* m
* in *)matchewith|EAbs{binder;_},_->let_vars,e=Bindlib.unmbindbinderinletrecget_varsbase_varsenv=function|EApp{f=EAbs{binder;_},_;args=[arg]},_->letvars,e=Bindlib.unmbindbinderinletvar=vars.(0)inletbase_vars=matchExpr.skip_wrappersargwith|ELit_,_->Var.Set.addvarbase_vars|_->base_varsinletenv=Env.addvarargenvenvinget_varsbase_varsenve|e->base_vars,env,einletbase_vars,env,e=get_varsVar.Set.emptyenveinlazy_evalctxenv(result_levelbase_vars)e|_->assertfalseletprint_value_with_envctxppfenvexpr=letalready_printed=refVar.Set.emptyinletrecauxenvppfexpr=Print.expr~debug:true()ppfexpr;Format.pp_print_cutppf();letvars=Var.Set.diff(Expr.free_varsexpr)!already_printedinVar.Set.iter(funv->lete,env=(Env.findvenv).reducedinlete,env=lazy_evalctxenv(result_levelVar.Set.empty)einFormat.fprintfppf"@[<hov 2>%a %a =@ %a =@ %a@]@,@,"Print.punctuation"»"Print.var_debugvExpr.format(fst(lazy_evalctxenvvalue_levele))(auxenv)e)vars;already_printed:=Var.Set.union!already_printedvars;Format.pp_print_cutppf()inFormat.pp_open_vboxppf2;auxenvppfexpr;Format.pp_close_boxppf()moduleV=structtypet=exprletcompareab=Expr.compareablethash=function|EVarv,_->Var.hashv|EAbs{tys;_},_->Hashtbl.hashtys|e,_->Hashtbl.hasheletequalab=Expr.equalabletformat=Expr.formatendmoduleE=structtypehand_side=Lhsofstring|Rhsofstringtypet={side:hand_sideoption;condition:bool;invisible:bool}letcomparexy=matchBool.comparex.conditiony.conditionwith|0->Option.compare(funxy->matchx,ywith|Lhss,Lhst|Rhss,Rhst->String.comparest|Lhs_,Rhs_->-1|Rhs_,Lhs_->1)x.sidey.side|n->nletdefault={side=None;condition=false;invisible=false}endmoduleG=Graph.Persistent.Digraph.AbstractLabeled(V)(E)letop_kind=function|Op.Add_int_int|Add_rat_rat|Add_mon_mon|Add_dat_dur_|Add_dur_dur|Sub_int_int|Sub_rat_rat|Sub_mon_mon|Sub_dat_dat|Sub_dat_dur_|Sub_dur_dur->`Sum|Mult_int_int|Mult_rat_rat|Mult_mon_rat|Mult_dur_int|Div_int_int|Div_rat_rat|Div_mon_rat|Div_mon_mon|Div_dur_dur->`Product|Round_mon|Round_rat->`Round|Map|Filter|Reduce|Fold->`Fct|_->`OthermoduleGTopo=Graph.Topological.Make(G)letto_graphctxenvexpr=letrecauxenvge=(* lazy_eval ctx env (result_level base_vars) e *)matchExpr.skip_wrappersewith|(EAppOp{op=(ToRat_int|ToRat_mon|ToMoney_rat),_;args=[arg];_},_)->auxenvgarg(* we skip conversions *)|ELitl,_->letv=G.V.createeinG.add_vertexgv,v|(EVarvar,_)ase->letv=G.V.createeinletg=G.add_vertexgvinletchild,env=(Env.findvarenv).baseinletg,child_v=auxenvgchildinG.add_edgegvchild_v,v|EAppOp{op=_;args;_},_->letv=G.V.createeinletg=G.add_vertexgvinletg,children=List.fold_left_map(auxenv)gargsinList.fold_left(fung->G.add_edgegv)gchildren,v|EInj{e;_},_->auxenvge|EStruct{fields;_},_->letv=G.V.createeinletg=G.add_vertexgvinletargs=StructField.Map.valuesfieldsinletg,children=List.fold_left_map(auxenv)gargsinList.fold_left(fung->G.add_edgegv)gchildren,v|_->Format.eprintf"%a"Expr.formate;assertfalseinletbase_g,_=auxenvG.emptyexprinbase_gletrecis_conste=matchExpr.skip_wrappersewith|ELit_,_->true|EInj{e;_},_->is_conste|EStruct{fields;_},_->StructField.Map.for_all(fun_e->is_conste)fields|EArrayel,_->List.for_allis_constel|_->falseletprogram_to_graphoptions(prg:(dcalc,'m)gexprprogram)(scope:ScopeName.t):G.t*exprVar.Set.t*Env.t=letctx=prg.decl_ctxinletcustomize=Expr.map_marks~f:(funm->Custom{pos=Expr.mark_posm;custom={conditions=[]}})inlet(all_env,scopes),_=BoundList.fold_leftprg.code_items~init:(Env.empty,ScopeName.Map.empty)~f:(fun(env,scopes)itemv->matchitemwith|ScopeDef(name,body)->lete=Scope.to_exprctxbodyinlete=customize(Expr.unboxe)inlete=Expr.remove_logging_calls(Expr.unboxe)inlete=Renaming.expr(Renaming.get_ctx{Renaming.reserved=[];sanitize_varname=Fun.id;skip_constant_binders=false;constant_binder_name=None;})(Expr.unboxe)in(Env.add(Var.translatev)(Expr.unboxe)envenv,ScopeName.Map.addname(v,body.scope_body_input_struct)scopes)|Topdef(_,_,_,e)->Env.add(Var.translatev)(Expr.unbox(customizee))envenv,scopes)inletscope_v,_scope_arg_struct=ScopeName.Map.findscopescopesinlete,env=(Env.find(Var.translatescope_v)all_env).baseinletrecfind_tested_scopeeacc=ifacc<>Nonethenaccelsematchewith|(EApp{f=EVarvscope,_;args=[(EStruct{name;fields},_)];tys=[_in_ty];},_)->Some(vscope,name,fields)|e->Expr.shallow_foldfind_tested_scopeeaccinlettested_scope_v,in_struct,in_fields=Option.get(find_tested_scopeeNone)inlog"The specified scope is detected to be testing scope %s"(Bindlib.name_oftested_scope_v);lete,env=(Env.findtested_scope_vall_env).baseinletin_var,e=matchewith|EAbs{binder;_},_->letvars,e=Bindlib.unmbindbinderinvars.(0),e|_->assertfalseinletrecget_varsbase_varsenv=function(* This assumes the scope body starts with the deconstruction and binding of
its input struct *)|(EApp{f=EAbs{binder;_},_;args=[(EStructAccess{name;e=EVarvstruc,_;field;_},_)];_;},_)whenStructName.equalnamein_struct->letvars,body=Bindlib.unmbindbinderinletvar=vars.(0)inletbase_vars=Var.Set.addvarbase_varsinletenv=Env.addvar(StructField.Map.findfieldin_fields)envenvinget_varsbase_varsenvbody|e->base_vars,env,einletbase_vars,env,e=get_varsVar.Set.emptyenveinlete1,env=lazy_evalctxenv(result_levelbase_vars)einletlevel={value_levelwitheval_struct=false;eval_op=false;eval_match=true;eval_vars=(funv->false);}inletrecauxparent(g,var_vertices,env0)e=lete,env0=lazy_evalctxenv0leveleinletm=Mark.geteinlet(Custom{custom={conditions;_};_})=minletg,var_vertices,env0=(* add conditions *)ifnotoptions.with_conditionstheng,var_vertices,env0elsematchparentwith|None->g,var_vertices,env0|Someparent->List.fold_left(fun(g,var_vertices,env0)(econd,env)->let(g,var_vertices,env),vcond=aux(Someparent)(g,var_vertices,env)econdin(G.add_edge_eg(G.E.createparent{side=None;condition=true;invisible=false}vcond),var_vertices,Env.joinenv0env))(g,var_vertices,env0)conditionsinlete=Mark.setm(Expr.skip_wrapperse)inmatchewith|(EAppOp{op=(ToRat_int|ToRat_mon|ToMoney_rat),_;args=[arg];tys},_)->auxparent(g,var_vertices,env0)(Mark.setmarg)(* we skip conversions *)|ELitl,_->letv=G.V.createein(G.add_vertexgv,var_vertices,env0),v|EVarvar,_->(try(g,var_vertices,env0),Var.Map.findvarvar_verticeswithVar.Map.Not_found_->(tryletchild,env=(Env.findvarenv0).baseinletm=Mark.getchildinletv=G.V.create(Mark.setme)inletg=G.add_vertexgvinlet(g,var_vertices,env),child_v=aux(Somev)(g,var_vertices,Env.joinenv0env)childinletvar_vertices=(* Duplicates non-base constant var nodes *)ifVar.Set.memvarbase_varsthenvar_verticeselseletrecis_litv=matchG.V.labelvwith|ELit_,_->true|EVarvar,_whennot(Var.Set.memvarbase_vars)->(matchG.succgvwith[v]->is_litv|_->false)|_->falseinifis_litchild_vthenvar_vertices(* This duplicates constant var nodes *)elseVar.Map.addvarvvar_verticesin(G.add_edgegvchild_v,var_vertices,env),vwithVar.Map.Not_found_->Message.warning"VAR NOT FOUND: %a"Print.varvar;letv=G.V.createeinletg=G.add_vertexgvin(g,var_vertices,env),v))|EAppOp{op=(Map|Filter|Reduce|Fold),_;args=_::args;_},_->(* First argument (which is a function) is ignored *)letv=G.V.createeinletg=G.add_vertexgvinlet(g,var_vertices,env),children=List.fold_left_map(aux(Somev))(g,var_vertices,env0)argsin((List.fold_left(fung->G.add_edgegv)gchildren,var_vertices,env),v)|EAppOp{op=op,_;args=[lhs;rhs];_},_->letv=G.V.createeinletg=G.add_vertexgvinlet(g,var_vertices,env),lhs=aux(Somev)(g,var_vertices,env0)lhsinlet(g,var_vertices,env),rhs=aux(Somev)(g,var_vertices,env)rhsinletlhs_label,rhs_label=matchopwith|Add_int_int|Add_rat_rat|Add_mon_mon|Add_dat_dur_|Add_dur_dur->Some(E.Lhs"⊕"),Some(E.Rhs"⊕")|Sub_int_int|Sub_rat_rat|Sub_mon_mon|Sub_dat_dat|Sub_dat_dur_|Sub_dur_dur->Some(E.Lhs"⊕"),Some(E.Rhs"⊖")|Mult_int_int|Mult_rat_rat|Mult_mon_rat|Mult_dur_int->Some(E.Lhs"⊗"),Some(E.Rhs"⊗")|Div_int_int|Div_rat_rat|Div_mon_rat|Div_mon_mon|Div_dur_dur->Some(E.Lhs"⊗"),Some(E.Rhs"⊘")|_->None,Noneinletg=G.add_edge_eg(G.E.createv{side=lhs_label;condition=false;invisible=false}lhs)inletg=G.add_edge_eg(G.E.createv{side=rhs_label;condition=false;invisible=false}rhs)in(g,var_vertices,env),v|EAppOp{op=_;args;_},_->letv=G.V.createeinletg=G.add_vertexgvinlet(g,var_vertices,env),children=List.fold_left_map(aux(Somev))(g,var_vertices,env0)argsin((List.fold_left(fung->G.add_edgegv)gchildren,var_vertices,env),v)|EInj{e;_},_->auxparent(g,var_vertices,env0)e|EStruct{fields;_},_->letv=G.V.createeinletg=G.add_vertexgvinletargs=StructField.Map.valuesfieldsinlet(g,var_vertices,env),children=List.fold_left_map(aux(Somev))(g,var_vertices,env0)argsin((List.fold_left(fung->G.add_edgegv)gchildren,var_vertices,env),v)|EArrayelts,_->letv=G.V.createeinletg=G.add_vertexgvinlet(g,var_vertices,env),children=List.fold_left_map(aux(Somev))(g,var_vertices,env0)eltsin((List.fold_left(fung->G.add_edgegv)gchildren,var_vertices,env),v)|EAbs_,_->(g,var_vertices,env),G.V.createe(* (testing -> ignored) *)|EMatch{name;e;cases},_->auxparent(g,var_vertices,env0)e|EStructAccess{e;field;_},_->letv=G.V.createeinletg=G.add_vertexgvinlet(g,var_vertices,env),child=aux(Somev)(g,var_vertices,env0)ein(G.add_edgegvchild,var_vertices,env),v|_->Format.eprintf"%a"Expr.formate;assertfalseinlet(g,vmap,env),_=auxNone(G.empty,Var.Map.empty,env)einlog"BASE: @[<v>%a@]"(Format.pp_print_listPrint.var)(Var.Set.elementsbase_vars);g,base_vars,envletreverse_graphg=G.fold_edges_e(funeg->G.add_edge_e(G.remove_edge_ege)(G.E.create(G.E.dste)(G.E.labele)(G.E.srce)))ggletsubst_byv1e2e=letrecf=function|EVarv,mwhenVar.equalvv1->Expr.boxe2|e->Expr.map~f~op:Fun.ideinExpr.unbox(fe)letmap_verticesfg=G.fold_vertex(funvg->letv'=G.V.create(fv)inletg=G.fold_pred_e(funeg->G.add_edge_eg(G.E.create(G.E.srce)(G.E.labele)v'))gvginletg=G.fold_succ_e(funeg->G.add_edge_eg(G.E.createv'(G.E.labele)(G.E.dste)))gvginG.remove_vertexgv)ggletrecgraph_cleanupoptionsgbase_vars=(* let _g =
* let module GCtr = Graph.Contraction.Make (G) in
* GCtr.contract
* (fun e ->
* G.E.label e = None
* &&
* match G.V.label (G.E.src e), G.V.label (G.E.dst e) with
* | (EVar _, _), (EVar _, _) -> true
* | ( (EApp { f = EOp { op = op1; _ }, _; args = [_; _] }, _),
* (EApp { f = EOp { op = op2; _ }, _; args = [_; _] }, _) ) -> (
* match op_kind op1, op_kind op2 with
* | `Sum, `Sum -> true
* | `Prod, `Prod -> true
* | _ -> false)
* | _ -> false)
* g
* in *)letmoduleGTop=Graph.Topological.Make(G)inletmoduleVMap=Map.Make(structincludeG.Vletformatppfv=V.formatppf(G.V.labelv)end)inletg,vmap=(* Remove separate nodes for variable literal values *)G.fold_vertex(funv(g,vmap)->matchG.V.labelvwith(* | (ELit _, _), [EVar _, _] -> G.remove_vertex g v *)|ELit_,m->(G.remove_vertexgv,(* Forward position of the deleted literal to its parent *)List.fold_left(funvmapv->letout=G.succ_egv|>List.filter(fune->not(G.E.labele).condition)inmatchoutwith[_]->VMap.addvmvmap|_->vmap)vmap(G.predgv))|_,_->g,vmap)g(g,VMap.empty)inletg=map_vertices(funv->matchVMap.find_optvvmapwith|Somem->Mark.setm(G.V.labelv)|None->G.V.labelv)ginletg=(* Merge intermediate operations *)letg=reverse_graphginGTop.fold(* Variables -> result order *)(funvg->letsucc=G.succgvinmatchG.V.labelv,succ,List.mapG.V.labelsuccwith|(EAppOp_,_),[v2],[(EAppOp_,_)]->letg=List.fold_left(funge->G.add_edge_eg(G.E.create(G.E.srce)(G.E.labele)v2))g(G.pred_egv)inG.remove_vertexgv|_->g)gg|>reverse_graphinletg,substs=(* Remove intermediate variables *)GTop.fold(* Result -> variables order *)(funv(g,substs)->letsucc_e=G.succ_egvinifList.exists(funed->(G.E.labeled).condition)succ_etheng,substselseletsucc=List.mapG.E.dstsucc_einmatchG.V.labelv,succ,List.mapG.V.labelsuccwith|(EVarvar1,m1),[v2],[(EVarvar2,m2)]whennot(Var.Set.memvar1base_vars)->letg=List.fold_left(funge->G.add_edge_eg(G.E.create(G.E.srce)(G.E.labele)v2))g(G.pred_egv)in(G.remove_vertexgv,fune->subst_byvar1(EVarvar2,m2)(substse))|(EVarvar1,m1),[v2],[((EApp_,_)ase2)]whennot(Var.Set.memvar1base_vars)->(letpred_e=G.pred_egvinmatchpred_e,List.map(fune->G.V.label(G.E.srce))pred_ewith|[pred_e],[(EApp_,_)]whenG.E.srcpred_e|>G.out_degreeg<=options.merge_level->(* Arbitrary heuristics: don't merge if the child node already has
> level parents *)letg=G.add_edge_eg(G.E.create(G.E.srcpred_e)(G.E.labelpred_e)v2)inG.remove_vertexgv,fune->subst_byvar1e2(substse)|_->g,substs)|_->g,substs)g(g,G.V.label)inletg=map_verticessubstsginletg=(* Merge intermediate operations (again) *)letg=reverse_graphginGTop.fold(* Variables -> result order *)(funvg->letsucc=G.succgvinmatchG.V.labelv,succ,List.mapG.V.labelsuccwith|(EAppOp_,_),[v2],[(EAppOp_,_)]->letg=List.fold_left(funge->G.add_edge_eg(G.E.create(G.E.srce)(G.E.labele)v2))g(G.pred_egv)inG.remove_vertexgv|_->g)gg|>reverse_graphinletg=letmoduleEMap=Map.Make(structtypet=exprletcompare=Expr.compareletformat=Expr.formatend)in(* Merge duplicate nodes *)letemap=G.fold_vertex(funvexpr_map->lete=G.V.labelvinEMap.updatee(functionNone->Some[v]|Somel->Some(v::l))expr_map)gEMap.emptyinEMap.fold(funexprvsg->matchvswith|[]|[_]->g|v0::vn->lete_in=List.map(G.pred_eg)vs|>List.flatten|>List.map(fune->G.E.create(G.E.srce)(G.E.labele)v0)|>List.sort_uniqG.E.compareinlete_out=List.map(G.succ_eg)vs|>List.flatten|>List.map(fune->G.E.createv0(G.E.labele)(G.E.dste))|>List.sort_uniqG.E.compareinletg=List.fold_leftG.remove_vertexgvninletg=List.fold_leftG.remove_edge_eg(G.succ_egv0)inletg=List.fold_leftG.remove_edge_eg(G.pred_egv0)inletg=List.fold_leftG.add_edge_ege_ininletg=List.fold_leftG.add_edge_ege_outing)emapginletg=(* Merge formulas and subsequent variable affectation nodes *)G.fold_edges_e(funeg->if(not(G.mem_edge_ege))||(G.E.labele).conditionthengelsematchG.V.label(G.E.srce),G.V.label(G.E.dste)with|((EVar_,_)asvar),((EAppOp_,m)asexpr)->letpos=Expr.posexprinletv'=G.V.create(EAppOp{op=Op.Eq,pos;args=[var;expr];tys=[Type.anypos;Type.anypos];},m)(* This form is matched and displayed specifically below *)inletg=G.fold_pred_e(fune1g->G.add_edge_eg(G.E.create(G.E.srce1)(G.E.labele1)v'))g(G.E.srce)ginletg=G.fold_succ_e(fune1g->G.add_edge_eg(G.E.createv'(G.E.labele1)(G.E.dste1)))g(G.E.srce)ginletg=G.fold_succ_e(fune1g->G.add_edge_eg(G.E.createv'(G.E.labele1)(G.E.dste1)))g(G.E.dste)ginG.remove_vertex(G.remove_vertexg(G.E.dste))(G.E.srce)|_->g)ggingletexpr_to_dot_label0:typea.Global.backend_lang->decl_ctx->Env.t->Format.formatter->(a,'t)gexpr->unit=funlangctxenv->letxlang~en?(pl=en)~fr()=matchlangwithGlobal.Fr->fr|Global.En->en|Global.Pl->plinletrecaux_value:typeat.Format.formatter->(a,t)gexpr->unit=funppfe->Print.UserFacing.value~fallbacklangppfeandfallback:typeat.Format.formatter->(a,t)gexpr->unit=funppfe->letmoduleE=Print.ExprGen(structletvarppfv=String.formatppf(Bindlib.name_ofv)letlit=Print.UserFacing.litlangletoperator:typex.Format.formatter->xoperator->unit=funppfo->letopenOpinletstr=matchowith|Eq_boo_boo|Eq_int_int|Eq_rat_rat|Eq_mon_mon|Eq_dur_dur|Eq_dat_dat|Eq->"="|Minus_int|Minus_rat|Minus_mon|Minus_dur|Minus->"-"|ToRat_int|ToRat_mon|ToRat->""|ToMoney_rat|ToMoney|ToInt|ToInt_rat->""|Add_int_int|Add_rat_rat|Add_mon_mon|Add_dat_dur_|Add_dur_dur|Add->"+"|Sub_int_int|Sub_rat_rat|Sub_mon_mon|Sub_dat_dat|Sub_dat_dur_|Sub_dur_dur|Sub->"-"|Mult_int_int|Mult_rat_rat|Mult_mon_int|Mult_mon_rat|Mult_dur_int|Mult->"×"|Div_int_int|Div_rat_rat|Div_mon_mon|Div_mon_int|Div_mon_rat|Div_dur_dur|Div->"÷"|Lt_int_int|Lt_rat_rat|Lt_mon_mon|Lt_dur_dur|Lt_dat_dat|Lt->"<"|Lte_int_int|Lte_rat_rat|Lte_mon_mon|Lte_dur_dur|Lte_dat_dat|Lte->"≤"|Gt_int_int|Gt_rat_rat|Gt_mon_mon|Gt_dur_dur|Gt_dat_dat|Gt->">"|Gte_int_int|Gte_rat_rat|Gte_mon_mon|Gte_dur_dur|Gte_dat_dat|Gte->"≥"|Concat->"++"|Not->xlang()~en:"not"~fr:"non"|Length->xlang()~en:"length"~fr:"nombre"|Round_rat|Round_mon|Round->xlang()~en:"round"~fr:"arrondi"|Log_->xlang()~en:"Log"~fr:"Journal"|And->xlang()~en:"and"~fr:"et"|Or->xlang()~en:"or"~fr:"ou"|Xor->xlang()~en:"xor"~fr:"ou bien"|Map->xlang()~en:"on_every"~fr:"pour_chaque"|Map2->xlang()~en:"on_every_2"~fr:"pour_chaque_2"|Reduce->xlang()~en:"reduce"~fr:"réunion"|Filter->xlang()~en:"filter"~fr:"filtre"|Fold->xlang()~en:"fold"~fr:"pliage"|HandleExceptions->""|ToClosureEnv->""|FromClosureEnv->""inFormat.pp_print_stringppfstrletpre_map=Expr.skip_wrappersletbypass:typeat.Format.formatter->(a,t)gexpr->bool=funppfe->letpercent_printerppf=function|ELit(LRatr),mwhenRuntime.(o_lt_rat_ratr(Runtime.decimal_of_float1.))->Format.fprintfppf"%a%%"aux_value(ELit(LRat(Runtime.o_mult_rat_ratr(Runtime.decimal_of_float100.))),m)|e->aux_valueppfeinmatchMark.removeewith|ELit_|EArray_|ETuple_|EStruct_|EInj_|EEmpty|EAbs_|EExternal_->aux_valueppfe;true|EAppOp{op=(Op.Mult_rat_rat|Op.Mult_mon_rat),_;args=[x1;x2];_}->Format.fprintfppf"%a × %a"percent_printerx1percent_printerx2;true|EMatch{e;cases;_}->letcases=List.map(function|cons,(EAbs{binder;_},_)->cons,snd(Bindlib.unmbindbinder)|cons,e->cons,e)(EnumConstructor.Map.bindingscases)inifList.for_all(function_,(ELit(LBool_),_)->true|_->false)casesthen(letcases=List.filter_map(functionc,(ELit(LBooltrue),_)->Somec|_->None)casesinFormat.fprintfppf"%a @<1>%s @[<hov>%a@]"aux_valuee"≅"(Format.pp_print_list~pp_sep:(funppf()->Format.fprintfppf" %t@ "(funppf->operatorppfOr))EnumConstructor.format)cases;true)elsefalse|_->falseend)inE.exprppfeinaux_valuelethtmlencode=letre=Re.(compile(set"&<>'\"@"))inRe.replacere~f:(fung->matchRe.Group.getg0with|"&"->"&"|"<"->"<"|">"->">"|"'"->"'"|"\""->"""|"@"->"@"|_->assertfalse)letexpr_to_dot_label0langctxenvppfe=Format.fprintfppf"%s"(htmlencode(Format.asprintf"%a"(expr_to_dot_label0langctxenv)e))letrecexpr_to_dot_label(style:Style.theme)langctxenvppfe=letprint_exprppf=function|(EVar_,_)ase->lete,_=lazy_evalctxenvvalue_leveleinexpr_to_dot_label0langctxenvppfe|e->expr_to_dot_label0langctxenvppfeinlete=Expr.skip_wrapperseinmatchewith|EVarv,_->lete,_=lazy_evalctxenvvalue_leveleinFormat.fprintfppf"<table border=\"0\" cellborder=\"0\" cellspacing=\"1\"><tr><td \
align=\"left\"><b>%a</b></td></tr><tr><td align=\"right\"><b>= <font \
color=\"#007799\">@[<hv>%a@]</font></b></td></tr></table>"String.format(Bindlib.name_ofv)(expr_to_dot_label0langctxenv)e|(EAppOp{op=Op.Eq,_;args=[(EVarv,_);((EAppOp_,_)asexpr)];_},_)->letvalue,_=lazy_evalctxenvvalue_levelexprinFormat.fprintfppf"<table border=\"0\" cellborder=\"0\" cellspacing=\"1\"><tr><td \
align=\"left\"><b>%a</b></td></tr><hr/><tr><td \
align=\"left\">@[<hv>%a@]</td></tr><tr><td align=\"right\"><b>= <font \
color=\"#0088aa\">@[<hv>%a@]</font></b></td></tr></table>"String.format(Bindlib.name_ofv)(expr_to_dot_label0langctxenv)expr(expr_to_dot_label0langctxenv)value|EStruct{name;fields},_->letprppf=Format.fprintfppf"<table border=\"%f\" cellborder=\"1\" cellspacing=\"0\" \
bgcolor=\"#%06x\" color=\"#%06x\"><tr><td \
colspan=\"2\">%a</td></tr><tr><td>%a</td><td>%a</td></tr></table>"(float_of_intstyle.output.stroke)style.output.fillstyle.output.borderStructName.formatname(Format.pp_print_list~pp_sep:(funppf()->Format.pp_print_stringppf" | ")(funppffld->StructField.formatppffld(* ; * Format.pp_print_string ppf "<vr/>" *)))(StructField.Map.keysfields)(Format.pp_print_list~pp_sep:(funppf()->Format.pp_print_stringppf" | ")(funppf->function|((EVar_|ELit_|EInj{e=(EVar_|ELit_),_;_}),_)ase->print_exprppfe(* ; * Format.pp_print_string ppf "\\l" *)|_->Format.pp_print_stringppf"…"))(StructField.Map.valuesfields)inFormat.pp_print_stringppf(Message.unformatpr)|EArrayelts,_->letprppf=Format.fprintfppf"<table border=\"0\" cellborder=\"1\" \
cellspacing=\"0\"><tr>%a</tr></table>"(Format.pp_print_list(funppf->function|((EVar_|ELit_),_)ase->Format.fprintfppf"<td>%a</td>"print_expre|_->Format.pp_print_stringppf"<td>…</td>"))eltsinFormat.pp_print_stringppf(Message.unformatpr)|e->Format.fprintfppf"%a@,"(expr_to_dot_label0langctxenv)eletto_dotlangppfctxenvbase_varsg~base_src_url~line_format~theme=letmoduleGPr=Graph.Graphviz.Dot(structincludeGletprint_exprenvctxlangppfe=(* let out_funs = Format.pp_get_formatter_out_functions ppf () in
* Format.pp_set_formatter_out_functions ppf
* {
* out_funs with
* Format.out_newline = (fun () -> out_funs.out_string "<br/>" 0 2);
* }; *)expr_to_dot_labelthemeenvctxlangppfe(* ; * Format.pp_print_flush ppf (); * Format.pp_set_formatter_out_functions
ppf out_funs *)letgraph_attributes_=[`BgcolorWithTransparency(Int32.of_int0x00);(* `Ratio (`Float 0.8); *)(* `Concentrate true; *)`Ratio`Compress;(* `Size (8.3, 11.7); (* A4 in inches..... *) *)(* `Rankdir `LeftToRight *)]letdefault_vertex_attributes_=[]letvertex_labelv=letprint_expr=print_exprlangctxenvin(* match G.V.label v with
* | (EVar v, _) as e ->
* Format.asprintf "%a = %a" String.format (Bindlib.name_of v) print_expr
* (fst (lazy_eval ctx env value_level e))
* | e -> *)Format.asprintf"%a"print_expr(G.V.labelv)letvertex_namev=Printf.sprintf"x%03d"(G.V.hashv)letvertex_attributesv=lete=V.labelvinletpos=matchewith|EVarv,_->Expr.pos(fst(Env.findvenv).reduced)|e->Expr.poseinletloc_text=Re.replace_stringRe.(compile(char'\n'))~by:" "(String.concat"\n» "(List.rev(Pos.get_law_infopos))^"\n")inleturl=base_src_url^"/"^Pos.get_fileposinletline_suffix=Re.(replace_string~all:true(compile(str"NN"))~by:(string_of_int(Pos.get_start_linepos))line_format)in`HtmlLabel(vertex_labelv(* ^ "\n" ^ loc_text *))::`Commentloc_text(* :: `Url
* ("http://localhost:8080/fr/examples/housing-benefits#"
* ^ Re.(
* replace_string
* (compile
* (seq [char '/'; rep1 (diff any (char '/')); str "/../"]))
* ~by:"/" (Pos.get_file pos))
* ^ "-"
* ^ string_of_int (Pos.get_start_line pos)) *)::`Url(url^line_suffix)::`Fontname"sans"::(matchG.V.labelvwith|EVarvar,_->ifVar.Set.memvarbase_varsthen[`Style`Filled;`Fillcolortheme.input.fill;`Shape`Box;`Penwidth(Style.widththeme.input.stroke);`Colortheme.input.border;`Fontcolortheme.input.text;]elseifList.exists(fune->not(G.E.labele).condition)(G.succ_egv)then(* non-constants *)[`Style`Filled;`Fillcolortheme.middle.fill;`Shape`Box;`Penwidth(Style.widththeme.middle.stroke);`Colortheme.middle.border;`Fontcolortheme.middle.text;]else(* Constants *)[`Style`Filled;`Fillcolortheme.constant.fill;`Shape`Box;`Penwidth(Style.widththeme.middle.stroke);`Colortheme.constant.border;`Fontcolortheme.constant.text;]|EAppOp{op=Op.Eq,_;args=[(EVar_,_);(EAppOp_,_)];_},_->[`Style`Filled;`Fillcolortheme.middle.fill;`Shape`Box;`Penwidth(Style.widththeme.middle.stroke);`Colortheme.middle.border;`Fontcolortheme.middle.text;]|EStruct_,_|EArray_,_->[`Style`Solid;(* `Fillcolor theme.output.fill; *)`Shape`Plaintext;`Penwidth(Style.widththeme.output.stroke);`Colortheme.output.border;`Fontcolortheme.output.text;](* | EAppOp { op = op, _; _ }, _ -> (
* match op_kind op with
* | `Sum | `Product | _ -> [`Shape `Box; `Fillcolor 0xff0000] (* | _ -> [] *)) *)|_->[`Style`Dashed;`Style`Filled;`Fillcolortheme.condition.fill;`Shape`Box;`Penwidth(Style.widththeme.condition.stroke);`Colortheme.condition.border;`Fontcolortheme.condition.text;])letget_subgraphv=letis_input=matchG.V.labelvwith|EVarvar,_->Var.Set.memvarbase_vars|_->falseinifis_inputthenSome{Graph.Graphviz.DotAttributes.sg_name="inputs";sg_attributes=[`Style`Filled;`FillcolorWithTransparency(Int32.of_int0x0);`ColorWithTransparency(Int32.of_int0x0);];sg_parent=None;}elseNoneletdefault_edge_attributes_=[]letedge_attributese=matchE.labelewith|{invisible=true;_}->[`Style`Invis;`Weight6]|{condition=true;_}->[`Style`Dashed;`Penwidth2.;`Color0xff7700;`Arrowhead`Odot;`Weight8;]|{side=Some(Lhss|Rhss);_}->[`Colortheme.arrows(* `Label s; `Color 0xbb7700 *);`Weight10]|{side=None;_}->[`Colortheme.arrows(* `Minlen 0; `Weight 10 *);`Weight10]end)inletg=(* Add fake edges from everything towards the inputs to force ordering *)G.fold_vertex(funvg->matchG.V.labelvwith|EVarvar,_whenVar.Set.memvarbase_vars->G.fold_vertex(funv0g->ifG.out_degreegv0>0thengelsematchG.V.labelv0with|EVarvar,_whenVar.Set.memvarbase_vars->g|_->G.add_edge_eg(G.E.createv0{invisible=true;condition=false;side=None}v))gg|_->g)gginGPr.fprint_graphppf(reverse_graphg)(* -- Plugin registration -- *)letoptions=letopenCmdlinerinletconditions=Arg.(value&flag&info["conditions"]~doc:"Include boolean conditions used to choose the specific formula \
nodes (with dashed lines) in the resulting graph. Without this, \
only the nodes contributing to the actual calculation are shown.")inletno_cleanup=Arg.(value&flag&info["no-cleanup"]~doc:"Disable automatic cleanup of intermediate computation nodes. Very \
verbose but sometimes useful for debugging.")inletmerge_level=Arg.(value&optint2&info["merge-level"]~doc:"Determines an internal threshold to the heuristics for merging \
intermediate nodes with as many parents. Higher means more \
aggressive merges.")inletformat=letmkinfos=(`Converts,Arg.info[s]~doc:(Printf.sprintf"Outputs a compiled $(b,.%s) file instead of a $(b,.dot) file \
(requires $(i,graphviz) to be installed)."s))inArg.(value&vflag`Dot[(`Dot,info["dot"]~doc:"Output the graph in dot format (this is the default)");mkinfo"svg";mkinfo"png";mkinfo"pdf";mkinfo"html";])inlettheme=Arg.(value&opt(enum["light",Style.light;"dark",Style.dark])Style.light&info["theme"]~doc:"Select the color theme for graphical outputs")inletshow=Arg.(value&opt~vopt:(Some"xdot")(somestring)None&info["show"]~doc:"Opens the resulting graph in the given command immediately.")inletbase_src_url=Arg.(value&optstring"https://github.com/CatalaLang/catala-examples/blob/exemple_explication"&info["url-base"]~docv:"URL"~doc:"Base URL that can be used to browse the Catala code. Nodes will \
link to $(i,URL)/relative/filename.catala_xx")inletline_format=Arg.(value&optstring"#LNN"&info["line-format"]~docv:"FORMAT"~doc:"Format used to encode line position in URL's suffix. The sequence \
of characters 'NN' will be expanded using the actual positions. \
The default value '#LNN' matches github-like positions")inletinline_module_usages=Arg.(value&flag&info["inline-mod-uses"]~doc:"Attempts to inline existing module usages using a heuristic.")inletfwith_conditionsno_cleanupmerge_levelformatthemeshowoutputbase_src_urlline_formatinline_module_usages={with_conditions;with_cleanup=notno_cleanup;merge_level;format;theme;show;output;base_src_url;line_format;inline_module_usages;}inTerm.(constf$conditions$no_cleanup$merge_level$format$theme$show$Cli.Flags.output$base_src_url$line_format$inline_module_usages)letinline_used_modulesglobal_options=letprg=Surface.Parser_driver.parse_top_level_fileglobal_options.Global.input_srcinletused_modules=prg.Surface.Ast.program_used_modules|>List.map(fun{Surface.Ast.mod_use_name;mod_use_alias;_}->Mark.removemod_use_name,Mark.removemod_use_alias)inifused_modules=[]then()elseletfind_module_file_in_input_directorymod_name=letdir=matchglobal_options.Global.input_srcwith|FileNamef->Filename.dirnamef|_->Sys.getcwd()inleten_candidate=String.uncapitalize_asciimod_name^".catala_en"inletfr_candidate=String.uncapitalize_asciimod_name^".catala_fr"inSys.readdirdir|>Array.map(Filename.concatdir)|>Array.find_map(funpath->letfile=Filename.basenamepathiniffile=en_candidatethenSomepathelseiffile=fr_candidatethenSomepathelseNone)inletraw_prg,file=matchglobal_options.input_srcwith|FileNames->(Catala_utils.File.(contents(check_files|>Option.value~default:"")),s)|Contents(s,fname)->s,fname|Stdin_->Message.error"Cannot inline module usage from stdin"inletraw_prg=(* let's assume it's in english *)String.split_on_char'\n'raw_prginletcontents=List.fold_left(funraw_prg(used_module,used_module_alias)->letmod_file_opt=find_module_file_in_input_directoryused_moduleinmatchmod_file_optwith|None->Message.error"Cannot find corresponding file for module '%s' required for \
module inlining"used_module|Somemod_file->letnew_content=lets=Re.(replace_string(compile(str"> Module"))~by:"< Module"(File.contentsmod_file))inGlobal.Contents(s,mod_file)inSurface.Parser_driver.register_included_file_resolver~filename:mod_file~new_content;List.map(funs->letopenReinletusing_mod_re=compile(str(Format.sprintf"> Using %s"used_module))inifmatchesusing_mod_res<>[]thenFormat.sprintf"> Include: %s"(Filename.basenamemod_file)elsereplace_string(compile(str(used_module_alias^".")))~by:""~all:trues)raw_prg)raw_prgused_modulesinletcontents=String.concat"\n"contentsinGlobal.enforce_options~input_src:(Global.Contents(contents,file))()|>ignoreletrun(includes:Global.raw_filelist)stdliboptimizeex_scopeexplain_optionsglobal_options=let()=ifexplain_options.inline_module_usagestheninline_used_modulesglobal_optionsinletprg,_=Driver.Passes.dcalcglobal_options~includes~stdlib~optimize~check_invariants:false~autotest:false~typed:Expr.typedinInterpreter.load_runtime_modulesprg~hashf:(Hash.finalise~monomorphize_types:false);letscope=Driver.Commands.get_scope_uidprg.decl_ctxex_scopein(* let result_expr, env = interpret_program prg scope in *)letg,base_vars,env=program_to_graphexplain_optionsprgscopeinlog"Base variables detected: @[<hov>%a@]"(Format.pp_print_listPrint.var)(Var.Set.elementsbase_vars);letg=ifexplain_options.with_cleanupthengraph_cleanupexplain_optionsgbase_varselseginletlang=Cli.file_lang(Global.input_src_fileglobal_options.Global.input_src)inletdot_content=to_dotlangFormat.str_formatterprg.decl_ctxenvbase_varsg~base_src_url:explain_options.base_src_url~line_format:explain_options.line_format~theme:explain_options.theme;Format.flush_str_formatter()|>Re.(replace_string(compile(seq[bow;str"comment="]))~by:"tooltip=")inletwith_dot_file=matchexplain_optionswith|{format=`Convert_;_}|{show=Some_;output=None;_}->File.with_temp_file"catala-explain""dot"~contents:dot_content|{output;_}->let_,with_out=Driver.Commands.get_outputglobal_optionsoutputinwith_out(funoc->output_stringocdot_content);funf->f(Option.value~default:"-"(Option.mapGlobal.options.path_rewriteoutput))inwith_dot_file@@fundotfile->(matchexplain_options.formatwith|`Convertfmt->let_,with_out=Driver.Commands.get_outputglobal_optionsexplain_options.outputinletwrap_html,fmt=iffmt="html"thentrue,"svg"elsefalse,fmtinwith_out(funoc->ifwrap_htmlthen(output_stringoc"<!DOCTYPE html>\n<html>\n<head>\n <title>";output_stringoc(htmlencodeex_scope);Printf.fprintfoc" </title>\n\
\ <style>\n\
\ body { background-color: #%06x }\n\
\ svg { max-width: 80rem; height: fit-content; }\n\
\ </style>\n\
</head>\n\
<body>\n"explain_options.theme.page_background);letcontents=File.process_out"dot"["-T"^fmt;dotfile]inoutput_stringoccontents;ifwrap_htmlthenoutput_stringoc"</body>\n</html>\n")|`Dot->());matchexplain_options.showwith|None->()|Somecmd->raise(Cli.Exit_with(Sys.command(cmd^" "^Filename.quotedotfile)))letterm=letopenCmdliner.Terminconstrun$Cli.Flags.include_dirs$Cli.Flags.stdlib_dir$Cli.Flags.optimize$Cli.Flags.ex_scope$optionslet()=Driver.Plugin.register"explain"term~doc:"Generates a graph of the formulas that are used for a given execution \
of a scope"~man:[`P"This command requires a given scope with no inputs (i.e. a test \
scope). A partial/lazy evaluation will recursively take place to \
explain intermediate formulas that take place in the computation, \
from the inputs (specified in the test scope) to the final outputs. \
The output is a graph, in .dot format (graphviz) by default (see \
$(b,--svg) and $(b,--show) for other options)";]