12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937(* 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_int|ToMoney|ToInt|ToInt_rat|ToInt_mon->""|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)";]