123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223(* This file is part of the Catala compiler, a specification language for tax
and social benefits computation rules. Copyright (C) 2022 Inria,
contributors: Alain Delaët <alain.delaet--tixeuil@inria.fr>, Denis Merigoux
<denis.merigoux@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_astopenAsttypepartial_evaluation_ctx={var_values:(typedexpr,typedexpr)Var.Map.t;decl_ctx:decl_ctx;}letrecpartial_evaluation(ctx:partial_evaluation_ctx)(e:'mexpr):(dcalc,'mmark)boxed_gexpr=(* We proceed bottom-up, first apply on the subterms *)lete=Expr.map~f:(partial_evaluationctx)einletmark=Marked.get_markein(* Then reduce the parent node *)letreducee=(* Todo: improve the handling of eapp(log,elit) cases here, it obfuscates
the matches and the log calls are not preserved, which would be a good
property *)matchMarked.unmarkewith|EApp{f=(EOp{op=Not;_},_|(EApp{f=EOp{op=Log_;_},_;args=[(EOp{op=Not;_},_)];},_))asop;args=[e1];}->((* reduction of logical not *)matche1with|ELit(LBoolfalse),_->ELit(LBooltrue)|ELit(LBooltrue),_->ELit(LBoolfalse)|e1->EApp{f=op;args=[e1]})|EApp{f=(EOp{op=Or;_},_|(EApp{f=EOp{op=Log_;_},_;args=[(EOp{op=Or;_},_)];},_))asop;args=[e1;e2];}->((* reduction of logical or *)matche1,e2with|(ELit(LBoolfalse),_),new_e|new_e,(ELit(LBoolfalse),_)->Marked.unmarknew_e|(ELit(LBooltrue),_),_|_,(ELit(LBooltrue),_)->ELit(LBooltrue)|_->EApp{f=op;args=[e1;e2]})|EApp{f=(EOp{op=And;_},_|(EApp{f=EOp{op=Log_;_},_;args=[(EOp{op=And;_},_)];},_))asop;args=[e1;e2];}->((* reduction of logical and *)matche1,e2with|(ELit(LBooltrue),_),new_e|new_e,(ELit(LBooltrue),_)->Marked.unmarknew_e|(ELit(LBoolfalse),_),_|_,(ELit(LBoolfalse),_)->ELit(LBoolfalse)|_->EApp{f=op;args=[e1;e2]})|EMatch{e=EInj{e;name=name1;cons},_;cases;name}whenEnumName.equalnamename1->(* iota reduction *)EApp{f=EnumConstructor.Map.findconscases;args=[e]}|EApp{f=EAbs{binder;_},_;args}->(* beta reduction *)Marked.unmark(Bindlib.msubstbinder(List.mapfstargs|>Array.of_list))|EDefault{excepts;just;cons}->((* TODO: mechanically prove each of these optimizations correct :) *)letexcepts=List.filter(funexcept->Marked.unmarkexcept<>ELitLEmptyError)excepts(* we can discard the exceptions that are always empty error *)inletvalue_except_count=List.fold_left(funnbexcept->ifExpr.is_valueexceptthennb+1elsenb)0exceptsinifvalue_except_count>1then(* at this point we know a conflict error will be triggered so we just
feed the expression to the interpreter that will print the beautiful
right error message *)Marked.unmark(Interpreter.evaluate_exprctx.decl_ctxe)elsematchexcepts,justwith|[except],_whenExpr.is_valueexcept->(* if there is only one exception and it is a non-empty value it is
always chosen *)Marked.unmarkexcept|([],((ELit(LBooltrue)|EApp{f=EOp{op=Log_;_},_;args=[(ELit(LBooltrue),_)];}),_))->Marked.unmarkcons|([],((ELit(LBoolfalse)|EApp{f=EOp{op=Log_;_},_;args=[(ELit(LBoolfalse),_)];}),_))->ELitLEmptyError|[],justwhennot!Cli.avoid_exceptions_flag->(* without exceptions, a default is just an [if then else] raising an
error in the else case. This exception is only valid in the context
of compilation_with_exceptions, so we desactivate with a global
flag to know if we will be compiling using exceptions or the option
monad. FIXME: move this optimisation somewhere else to avoid this
check *)EIfThenElse{cond=just;etrue=cons;efalse=ELitLEmptyError,mark}|excepts,just->EDefault{excepts;just;cons})|EIfThenElse{cond=(ELit(LBooltrue),_|(EApp{f=EOp{op=Log_;_},_;args=[(ELit(LBooltrue),_)];},_));etrue;_;}->Marked.unmarketrue|EIfThenElse{cond=((ELit(LBoolfalse)|EApp{f=EOp{op=Log_;_},_;args=[(ELit(LBoolfalse),_)];}),_);efalse;_;}->Marked.unmarkefalse|EIfThenElse{cond;etrue=((ELit(LBoolbtrue)|EApp{f=EOp{op=Log_;_},_;args=[(ELit(LBoolbtrue),_)];}),_);efalse=((ELit(LBoolbfalse)|EApp{f=EOp{op=Log_;_},_;args=[(ELit(LBoolbfalse),_)];}),_);}->ifbtrue&¬bfalsethenMarked.unmarkcondelseif(notbtrue)&&bfalsethenEApp{f=EOp{op=Not;tys=[TLitTBool,Expr.mark_posmark]},mark;args=[cond];}(* note: this last call eliminates the condition & might skip log calls
as well *)else(* btrue = bfalse *)ELit(LBoolbtrue)|e->einExpr.Box.app1ereducemarkletoptimize_expr(decl_ctx:decl_ctx)(e:'mexpr)=partial_evaluation{var_values=Var.Map.empty;decl_ctx}eletoptimize_program(p:'mprogram):'mprogram=Bindlib.unbox(Program.map_exprs~f:(partial_evaluation{var_values=Var.Map.empty;decl_ctx=p.decl_ctx})~varf:(funv->v)p)