123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290(* 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_ast(* -- Definition of the lazy interpreter -- *)letlogfmt=Format.ifprintfFormat.err_formatter(fmt^^"@\n")leterrore=Message.error~pos:(Expr.pose)letnoassert=truetypelaziness_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_default:bool;(* if false, stop evaluating as soon as you can discriminate with
`EEmptyError` *)}letvalue_level={eval_struct=false;eval_op=true;eval_default=true}moduleEnv=structtype'mt=|Envof((dcalc,'m)gexpr,((dcalc,'m)gexpr*'mt)ref)Var.Map.tletfindv(Envt)=Var.Map.findvtletaddvee_env(Envt)=Env(Var.Map.addv(ref(e,e_env))t)letempty=EnvVar.Map.emptyletjoin(Envt1)(Envt2)=Env(Var.Map.union(fun_x1x2->assert(x1==x2);Somex1)t1t2)letprintppf(Envt)=Format.pp_print_list~pp_sep:Format.pp_print_space(funppfv->Print.var_debugppfv)ppf(Var.Map.keyst)endletreclazy_eval:decl_ctx->'mEnv.t->laziness_level->(dcalc,'m)gexpr->(dcalc,'m)gexpr*'mEnv.t=functxenvllevele0->leteval_to_value?(eval_default=true)enve=lazy_evalctxenv{value_levelwitheval_default}einmatche0with|EVarv,_->ifnotllevel.eval_defaultthene0,envelse(* Variables reducing to EEmpty should not propagate to parent EDefault
(?) *)letv_env=tryEnv.findvenvwithVar.Map.Not_found_->errore0"Variable %a undefined [@[<hv>%a@]]"Print.var_debugvEnv.printenvinlete,env1=!v_envinletr,env1=lazy_evalctxenv1lleveleinifnot(Expr.equaler)then(log"@[<hv 2>{{%a =@ [%a]@ ==> [%a]}}@]"Print.var_debugv(Print.expr~debug:true())e(Print.expr~debug:true())r;v_env:=r,env1);r,Env.joinenvenv1|EAppOp{op;args;tys},m->if(notllevel.eval_default)&¬(List.equalExpr.equalargs[ELitLUnit,m])(* Applications to () encode thunked default terms *)thene0,envelseletenv,args=List.fold_left_map(funenve->lete,env=lazy_evalctxenvlleveleinenv,e)envargsinifnotllevel.eval_opthen(EAppOp{op;args;tys},m),envelseletrenv=refenvin(* Dirty workaround returning env from evaluate_operator *)letevale=lete,env=lazy_evalctx!renvlleveleinrenv:=env;ein(Interpreter.evaluate_operatorevalopmGlobal.En(* Default language to English but this should not raise any error
messages so we don't care. *)args,!renv)(* fixme: this forwards eempty *)|EApp{f;args;tys},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_debugvar(Print.expr~debug:true())e;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->lazy_evalctxenvllevel(StructField.Map.findfieldfields)|e,_->errore"Invalid field access on %a"Expr.formate)|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_defaultthene0,envelsematcheval_to_valueenvewith|(EInj{name=n;cons;e},m),envwhenEnumName.equalnamen->lazy_evalctxenvllevel(EApp{f=EnumConstructor.Map.findconscases;args=[e];tys=[]},m)|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),_),_->lazy_evalctxenvllevelcons|(ELit(LBoolfalse),_),_->(EEmpty,m),env|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},_->(matcheval_to_valueenvcondwith|(ELit(LBooltrue),_),_->lazy_evalctxenvlleveletrue|(ELit(LBoolfalse),_),_->lazy_evalctxenvllevelefalse|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.formate|_->errore"Invalid assertion condition %a"Expr.formate)|EFatalErrorerr,m->errore0"%a"Format.pp_print_text(Runtime.error_messageerr)|EExternal_,_->assertfalse(* todo *)|_->.letinterpret_program(prg:('dcalc,'m)gexprprogram)(scope:ScopeName.t):('t,'m)gexpr*'mEnv.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_exprctxbodyin(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.findscopescopesinlet{contents=e,env}=Env.findscope_vall_envinlete=Expr.unbox(Expr.remove_logging_callse)inlog"=====================";log"%a"(Print.expr~debug:true())e;log"=====================";letm=Mark.geteinletapplication_arg=Expr.estruct~name:scope_arg_struct~fields:(StructField.Map.map(function|TArrow(ty_in,ty_out),_->Expr.make_abs[|Var.make"_"|](Bindlib.boxEEmpty,Expr.with_tymty_out)ty_in(Expr.mark_posm)|ty->Expr.evar(Var.make"undefined_input")(Expr.with_tymty))(StructName.Map.findscope_arg_structctx.ctx_structs))minlete_app=Expr.eapp~f:(Expr.boxe)~args:[application_arg]~tys:[]minlazy_evalctxenv{value_levelwitheval_struct=true;eval_op=false}(Expr.unboxe_app)(* -- Plugin registration -- *)letrunincludesoptimizecheck_invariantsex_scopeoptions=letprg,_=Driver.Passes.dcalcoptions~includes~optimize~check_invariants~typed:Expr.typedinInterpreter.load_runtime_modulesprg;letscope=Driver.Commands.get_scope_uidprg.decl_ctxex_scopeinletresult_expr,_env=interpret_programprgscopeinletfmt=Format.std_formatterinExpr.formatfmtresult_exprletterm=letopenCmdliner.Terminconstrun$Cli.Flags.include_dirs$Cli.Flags.optimize$Cli.Flags.check_invariants$Cli.Flags.ex_scopelet()=Driver.Plugin.register"lazy"term~doc:"Experimental lazy evaluation (plugin)"