12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454(* 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_asttypeflags={with_conditions:bool;with_cleanup:bool;merge_level:int;format:[`Dot|`Convertofstring];show:stringoption;output:Global.raw_fileoption;base_src_url:string;}(* -- 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}einmatche0with|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->(if(notllevel.eval_default)&¬(List.equalExpr.equalargs[ELitLUnit,m])(* Applications to () encode thunked default terms *)thene0,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)envargsinifnotllevel.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)(* fixme: this forwards eempty *)|EApp{f;args},m->(if(notllevel.eval_default)&¬(List.equalExpr.equalargs[ELitLUnit,m])(* Applications to () encode thunked default terms *)thene0,envelsematcheval_to_valueenvfwith|(EAbs{binder;_},_),env->letvars,body=Bindlib.unmbindbinderinlog"@[<v 2>@[<hov 4>{";letenv=Seq.fold_left2(funenv1vare->log"@[<hov 2>LET %a = %a@]@ "Print.var_debugvarExpr.formate;Env.addvareenvenv1)env(Array.to_seqvars)(List.to_seqargs)inlog"@]@[<hov 4>IN [%a]@]"(Print.expr~debug:true())body;lete,env=lazy_evalctxenvllevelbodyinlog"@]}";e,env|e,_->errore"Invalid apply on %a"Expr.formate)|(EAbs_|ELit_|EEmpty),_->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}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}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=Expr.rename_vars(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).baseinlete=matchewith|EAbs{binder;_},_->let_vars,e=Bindlib.unmbindbinderine|_->assertfalseinletrecget_varsbase_varsenv=function|EApp{f=EAbs{binder;_},_;args=[arg]},_->letvars,e=Bindlib.unmbindbinderinletvar=vars.(0)inletbase_vars=ifis_constargthenVar.Set.addvarbase_varselsebase_varsinletenv=Env.addvarargenvenvinget_varsbase_varsenve|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=false;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}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}lhs)inletg=G.add_edge_eg(G.E.createv{side=rhs_label;condition=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)emapgingletexpr_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_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->""|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_rat|Mult_dur_int|Mult->"×"|Div_int_int|Div_rat_rat|Div_mon_mon|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"|GetDay->xlang()~en:"day_of_month"~fr:"jour_du_mois"|GetMonth->xlang()~en:"month"~fr:"mois"|GetYear->xlang()~en:"year"~fr:"année"|FirstDayOfMonth->xlang()~en:"first_day_of_month"~fr:"premier_jour_du_mois"|LastDayOfMonth->xlang()~en:"last_day_of_month"~fr:"dernier_jour_du_mois"|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"|HandleDefault->""|HandleDefaultOpt->""|ToClosureEnv->""|FromClosureEnv->""inFormat.pp_print_stringppfstrletpre_map=Expr.skip_wrappersletbypass:typeat.Format.formatter->(a,t)gexpr->bool=funppfe->matchMark.removeewith|ELit_|EArray_|ETuple_|EStruct_|EInj_|EEmpty|EAbs_|EExternal_->aux_valueppfe;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_valueletrecexpr_to_dot_labellangctxenvppfe=letprint_expr=expr_to_dot_labellangctxenvinlete=Expr.skip_wrapperseinmatchewith|EVarv,_->lete,_=lazy_evalctxenvvalue_leveleinFormat.fprintfppf"%a = %a"String.format(Bindlib.name_ofv)(expr_to_dot_label0langctxenv)e|EStruct{name;fields},_->letprppf=Format.fprintfppf"{ %a | { { %a } | { %a }}}"StructName.formatname(Format.pp_print_list~pp_sep:(funppf()->Format.pp_print_stringppf" | ")(funppffld->StructField.formatppffld;Format.pp_print_stringppf"\\l"))(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_stringppf"\\l"|_->Format.pp_print_stringppf"…\\l"))(StructField.Map.valuesfields)inFormat.pp_print_stringppf(Message.unformatpr)|EArrayelts,_->letprppf=Format.fprintfppf"{ %a }"(Format.pp_print_list~pp_sep:(funppf()->Format.pp_print_stringppf" | ")(funppf->function|((EVar_|ELit_),_)ase->print_exprppfe|_->Format.pp_print_stringppf"…"))eltsinFormat.pp_print_stringppf(Message.unformatpr)|e->Format.fprintfppf"%a@,"(expr_to_dot_label0langctxenv)eletto_dotlangppfctxenvbase_varsg~base_src_url=letmoduleGPr=Graph.Graphviz.Dot(structincludeGletprint_exprenvctxlangppfe=letout_funs=Format.pp_get_formatter_out_functionsppf()inFormat.pp_set_formatter_out_functionsppf{out_funswithFormat.out_newline=(fun()->out_funs.out_string"\\l"02);};expr_to_dot_labelenvctxlangppfe;Format.pp_print_flushppf();Format.pp_set_formatter_out_functionsppfout_funsletgraph_attributes_=[(* `Rankdir `LeftToRight *)]letdefault_vertex_attributes_=[]letvertex_labelv=letprint_expr=print_exprlangctxenvinmatchG.V.labelvwith|(EVarv,_)ase->Format.asprintf"%a = %a"String.format(Bindlib.name_ofv)print_expr(fst(lazy_evalctxenvvalue_levele))|e->Format.asprintf"%a"print_expreletvertex_namev=Printf.sprintf"x%03d"(G.V.hashv)letvertex_attributesv=lete=V.labelvinletpos=Expr.poseinletloc_text=Re.replace_stringRe.(compile(char'\n'))~by:" "(String.concat"\n» "(List.rev(Pos.get_law_infopos))^"\n")in`Label(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(base_src_url^"/"^Pos.get_filepos^"#L"^string_of_int(Pos.get_start_linepos))::`Fontname"DejaVu Sans Mono"::(matchG.V.labelvwith|EVarvar,_->ifVar.Set.memvarbase_varsthen[`Style`Filled;`Fillcolor0xffaa55;`Shape`Box]elseifList.exists(fune->not(G.E.labele).condition)(G.succ_egv)then(* non-constants *)[`Style`Filled;`Fillcolor0xffee99;`Shape`Box]else(* Constants *)[`Style`Filled;`Fillcolor0x77aaff;`Shape`Note]|EStruct_,_|EArray_,_->[`Shape`Record]|EAppOp{op=op,_;_},_->(matchop_kindopwith|`Sum|`Product|_->[`Shape`Box](* | _ -> [] *))|_->[])letget_subgraphv=matchG.V.labelvwith|EVarvar,_->(ifVar.Set.memvarbase_varsthenSome{Graph.Graphviz.DotAttributes.sg_name="inputs";sg_attributes=[];sg_parent=None;}elsematchList.mapG.V.label(G.succgv)with(* | [] | [ELit _, _] ->
* Some
* {
* Graph.Graphviz.DotAttributes.sg_name = "constants";
* sg_attributes = [`Shape `Box];
* sg_parent = None;
* } *)|_->None)|_->Noneletdefault_edge_attributes_=[]letedge_attributese=matchE.labelewith|{condition=true;_}->[`Style`Dashed;`Penwidth5.;`Color0xff7700;`Arrowhead`Odot]|{side=Some(Lhss|Rhss);_}->[(* `Label s; `Color 0xbb7700 *)]|_->[]end)inGPr.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";])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/blob/master"&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#LNN where NN is the \
line number in the file")inletfwith_conditionsno_cleanupmerge_levelformatshowoutputbase_src_url={with_conditions;with_cleanup=notno_cleanup;merge_level;format;show;output;base_src_url;}inTerm.(constf$conditions$no_cleanup$merge_level$format$show$Cli.Flags.output$base_src_url)letrunincludesoptimizeex_scopeexplain_optionsglobal_options=letprg,_=Driver.Passes.dcalcglobal_options~includes~optimize~check_invariants:false~typed:Expr.typedinInterpreter.load_runtime_modulesprg;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;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.outputinwith_out(funoc->output_stringoc(File.process_out"dot"["-T"^fmt;dotfile]))|`Dot->());matchexplain_options.showwith|None->()|Somecmd->raise(Cli.Exit_with(Sys.command(cmd^" "^Filename.quotedotfile)))letterm=letopenCmdliner.Terminconstrun$Cli.Flags.include_dirs$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)";]