123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323(* This file is part of the Catala compiler, a specification language for tax and social benefits
computation rules. Copyright (C) 2020 Inria, contributor: 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. *)(** Reference interpreter for the default calculus *)modulePos=Utils.PosmoduleErrors=Utils.ErrorsmoduleCli=Utils.ClimoduleA=Ast(** {1 Helpers} *)letis_empty_error(e:A.exprPos.marked):bool=matchPos.unmarkewithELitLEmptyError->true|_->falseletempty_thunked_term:Ast.exprPos.marked=letsilent=Ast.Var.make("_",Pos.no_pos)inBindlib.unbox(Ast.make_abs(Array.of_list[silent])(Bindlib.box(Ast.ELitAst.LEmptyError,Pos.no_pos))Pos.no_pos[(Ast.TLitAst.TUnit,Pos.no_pos)]Pos.no_pos)(** {1 Evaluation} *)letevaluate_operator(op:A.operatorPos.marked)(args:A.exprPos.markedlist):A.exprPos.marked=Pos.same_pos_as(match(Pos.unmarkop,List.mapPos.unmarkargs)with|A.BinopA.And,[ELit(LBoolb1);ELit(LBoolb2)]->A.ELit(LBool(b1&&b2))|A.BinopA.Or,[ELit(LBoolb1);ELit(LBoolb2)]->A.ELit(LBool(b1||b2))|A.Binop(A.AddKInt),[ELit(LInti1);ELit(LInti2)]->A.ELit(LInt(Z.addi1i2))|A.Binop(A.SubKInt),[ELit(LInti1);ELit(LInti2)]->A.ELit(LInt(Z.subi1i2))|A.Binop(A.MultKInt),[ELit(LInti1);ELit(LInti2)]->A.ELit(LInt(Z.muli1i2))|A.Binop(A.DivKInt),[ELit(LInti1);ELit(LInti2)]->ifi2<>Z.zerothenA.ELit(LInt(Z.divi1i2))elseErrors.raise_multispanned_error"division by zero at runtime"[(Some"The division operator:",Pos.get_positionop);(Some"The null denominator:",Pos.get_position(List.nthargs2));]|A.Binop(A.AddKRat),[ELit(LRati1);ELit(LRati2)]->A.ELit(LRat(Q.addi1i2))|A.Binop(A.SubKRat),[ELit(LRati1);ELit(LRati2)]->A.ELit(LRat(Q.subi1i2))|A.Binop(A.MultKRat),[ELit(LRati1);ELit(LRati2)]->A.ELit(LRat(Q.muli1i2))|A.Binop(A.DivKRat),[ELit(LRati1);ELit(LRati2)]->ifi2<>Q.zerothenA.ELit(LRat(Q.divi1i2))elseErrors.raise_multispanned_error"division by zero at runtime"[(Some"The division operator:",Pos.get_positionop);(Some"The null denominator:",Pos.get_position(List.nthargs2));]|A.Binop(A.AddKMoney),[ELit(LMoneyi1);ELit(LMoneyi2)]->A.ELit(LMoney(Z.addi1i2))|A.Binop(A.SubKMoney),[ELit(LMoneyi1);ELit(LMoneyi2)]->A.ELit(LMoney(Z.subi1i2))|A.Binop(A.MultKMoney),[ELit(LMoneyi1);ELit(LRati2)]->letrat_result=Q.mul(Q.of_biginti1)i2inletres,remainder=Z.div_rem(Q.numrat_result)(Q.denrat_result)in(* we perform nearest rounding when multiplying an amount of money by a decimal !*)letout=ifZ.(of_int2*remainder>=Q.denrat_result)thenZ.addres(Z.of_int1)elseresinA.ELit(LMoneyout)|A.Binop(A.DivKMoney),[ELit(LMoneyi1);ELit(LMoneyi2)]->ifi2<>Z.zerothenA.ELit(LRat(Q.div(Q.of_biginti1)(Q.of_biginti2)))elseErrors.raise_multispanned_error"division by zero at runtime"[(Some"The division operator:",Pos.get_positionop);(Some"The null denominator:",Pos.get_position(List.nthargs2));]|A.Binop(A.AddKDuration),[ELit(LDurationi1);ELit(LDurationi2)]->A.ELit(LDuration(Z.(+)i1i2))|A.Binop(A.SubKDuration),[ELit(LDurationi1);ELit(LDurationi2)]->A.ELit(LDuration(Z.(-)i1i2))|A.Binop(A.SubKDate),[ELit(LDatei1);ELit(LDatei2)]->A.ELit(LDuration(Z.of_int(ODuration.To.day(ODate.Unix.betweeni2i1))))|A.Binop(A.AddKDate),[ELit(LDatei1);ELit(LDurationi2)]->A.ELit(LDate(ODate.Unix.advance_by_daysi1(Z.to_inti2)))|A.Binop(A.LtKInt),[ELit(LInti1);ELit(LInti2)]->A.ELit(LBool(i1<i2))|A.Binop(A.LteKInt),[ELit(LInti1);ELit(LInti2)]->A.ELit(LBool(i1<=i2))|A.Binop(A.GtKInt),[ELit(LInti1);ELit(LInti2)]->A.ELit(LBool(i1>i2))|A.Binop(A.GteKInt),[ELit(LInti1);ELit(LInti2)]->A.ELit(LBool(i1>=i2))|A.Binop(A.LtKRat),[ELit(LRati1);ELit(LRati2)]->A.ELit(LBoolQ.(i1<i2))|A.Binop(A.LteKRat),[ELit(LRati1);ELit(LRati2)]->A.ELit(LBoolQ.(i1<=i2))|A.Binop(A.GtKRat),[ELit(LRati1);ELit(LRati2)]->A.ELit(LBoolQ.(i1>i2))|A.Binop(A.GteKRat),[ELit(LRati1);ELit(LRati2)]->A.ELit(LBoolQ.(i1>=i2))|A.Binop(A.LtKMoney),[ELit(LMoneyi1);ELit(LMoneyi2)]->A.ELit(LBool(i1<i2))|A.Binop(A.LteKMoney),[ELit(LMoneyi1);ELit(LMoneyi2)]->A.ELit(LBool(i1<=i2))|A.Binop(A.GtKMoney),[ELit(LMoneyi1);ELit(LMoneyi2)]->A.ELit(LBool(i1>i2))|A.Binop(A.GteKMoney),[ELit(LMoneyi1);ELit(LMoneyi2)]->A.ELit(LBool(i1>=i2))|A.Binop(A.LtKDuration),[ELit(LDurationi1);ELit(LDurationi2)]->A.ELit(LBool(i1<i2))|A.Binop(A.LteKDuration),[ELit(LDurationi1);ELit(LDurationi2)]->A.ELit(LBool(i1<=i2))|A.Binop(A.GtKDuration),[ELit(LDurationi1);ELit(LDurationi2)]->A.ELit(LBool(i1>i2))|A.Binop(A.GteKDuration),[ELit(LDurationi1);ELit(LDurationi2)]->A.ELit(LBool(i1>=i2))|A.Binop(A.LtKDate),[ELit(LDatei1);ELit(LDatei2)]->A.ELit(LBool(ODate.Unix.comparei1i2<0))|A.Binop(A.LteKDate),[ELit(LDatei1);ELit(LDatei2)]->A.ELit(LBool(ODate.Unix.comparei1i2<=0))|A.Binop(A.GtKDate),[ELit(LDatei1);ELit(LDatei2)]->A.ELit(LBool(ODate.Unix.comparei1i2>0))|A.Binop(A.GteKDate),[ELit(LDatei1);ELit(LDatei2)]->A.ELit(LBool(ODate.Unix.comparei1i2>=0))|A.BinopA.Eq,[ELit(LDurationi1);ELit(LDurationi2)]->A.ELit(LBool(i1=i2))|A.BinopA.Eq,[ELit(LDatei1);ELit(LDatei2)]->A.ELit(LBool(ODate.Unix.comparei1i2=0))|A.BinopA.Eq,[ELit(LMoneyi1);ELit(LMoneyi2)]->A.ELit(LBool(i1=i2))|A.BinopA.Eq,[ELit(LRati1);ELit(LRati2)]->A.ELit(LBoolQ.(i1=i2))|A.BinopA.Eq,[ELit(LInti1);ELit(LInti2)]->A.ELit(LBool(i1=i2))|A.BinopA.Eq,[ELit(LBoolb1);ELit(LBoolb2)]->A.ELit(LBool(b1=b2))|A.BinopA.Eq,[_;_]->A.ELit(LBoolfalse)(* comparing functions return false *)|A.BinopA.Neq,[ELit(LDurationi1);ELit(LDurationi2)]->A.ELit(LBool(i1<>i2))|A.BinopA.Neq,[ELit(LDatei1);ELit(LDatei2)]->A.ELit(LBool(ODate.Unix.comparei1i2<>0))|A.BinopA.Neq,[ELit(LMoneyi1);ELit(LMoneyi2)]->A.ELit(LBool(i1<>i2))|A.BinopA.Neq,[ELit(LRati1);ELit(LRati2)]->A.ELit(LBoolQ.(i1<>i2))|A.BinopA.Neq,[ELit(LInti1);ELit(LInti2)]->A.ELit(LBool(i1<>i2))|A.BinopA.Neq,[ELit(LBoolb1);ELit(LBoolb2)]->A.ELit(LBool(b1<>b2))|A.BinopA.Neq,[_;_]->A.ELit(LBooltrue)|A.Binop_,([ELitLEmptyError;_]|[_;ELitLEmptyError])->A.ELitLEmptyError|A.Unop(A.MinusKInt),[ELit(LInti)]->A.ELit(LInt(Z.subZ.zeroi))|A.Unop(A.MinusKRat),[ELit(LRati)]->A.ELit(LRat(Q.subQ.zeroi))|A.UnopA.Not,[ELit(LBoolb)]->A.ELit(LBool(notb))|A.UnopA.ErrorOnEmpty,[e']->ife'=A.ELitLEmptyErrorthenErrors.raise_spanned_error"This variable evaluated to an empty term (no rule that defined it applied in this \
situation)"(Pos.get_positionop)elsee'|A.Unop(A.Log(entry,infos)),[e']->if!Cli.trace_flagthenmatchentrywith|VarDef->Cli.log_print(Format.asprintf"%a %a = %a"Print.format_log_entryentry(Format.pp_print_list~pp_sep:(funfmt()->Format.fprintffmt".")(funfmtinfo->Utils.Uid.MarkedString.format_infofmtinfo))infosPrint.format_expr(e',Pos.no_pos))|_->Cli.log_print(Format.asprintf"%a %a"Print.format_log_entryentry(Format.pp_print_list~pp_sep:(funfmt()->Format.fprintffmt".")(funfmtinfo->Utils.Uid.MarkedString.format_infofmtinfo))infos)else();e'|A.Unop_,[ELitLEmptyError]->A.ELitLEmptyError|_->Errors.raise_multispanned_error"Operator applied to the wrong arguments\n(should nothappen if the term was well-typed)"([(Some"Operator:",Pos.get_positionop)]@List.mapi(funiarg->(Some(Format.asprintf"Argument n°%d, value %a"(i+1)Print.format_exprarg),Pos.get_positionarg))args))opletrecevaluate_expr(e:A.exprPos.marked):A.exprPos.marked=matchPos.unmarkewith|EVar_->Errors.raise_spanned_error"free variable found at evaluation (should not happen if term was well-typed"(Pos.get_positione)|EApp(e1,args)->(lete1=evaluate_expre1inletargs=List.mapevaluate_exprargsinmatchPos.unmarke1with|EAbs(_,binder,_)->ifBindlib.mbinder_aritybinder=List.lengthargsthenevaluate_expr(Bindlib.msubstbinder(Array.of_list(List.mapPos.unmarkargs)))elseErrors.raise_spanned_error(Format.asprintf"wrong function call, expected %d arguments, got %d"(Bindlib.mbinder_aritybinder)(List.lengthargs))(Pos.get_positione)|EOpop->Pos.same_pos_as(Pos.unmark(evaluate_operator(Pos.same_pos_asope1)args))e|ELitLEmptyError->Pos.same_pos_as(A.ELitLEmptyError)e|_->Errors.raise_spanned_error"function has not been reduced to a lambda at evaluation (should not happen if the \
term was well-typed"(Pos.get_positione))|EAbs_|ELit_|EOp_->e(* thse are values *)|ETuplees->Pos.same_pos_as(A.ETuple(List.map(fun(e',i)->(evaluate_expre',i))es))e|ETupleAccess(e1,n,_)->(lete1=evaluate_expre1inmatchPos.unmarke1with|ETuplees->(matchList.nth_optesnwith|Some(e',_)->e'|None->Errors.raise_spanned_error(Format.asprintf"the tuple has %d components but the %i-th element was requested (should not \
happen if the term was well-type)"(List.lengthes)n)(Pos.get_positione1))|_->Errors.raise_spanned_error(Format.asprintf"the expression should be a tuple with %d components but is not (should not happen \
if the term was well-typed)"n)(Pos.get_positione1))|EInj(e1,n,i,ts)->lete1'=evaluate_expre1inPos.same_pos_as(A.EInj(e1',n,i,ts))e|EMatch(e1,es)->(lete1=evaluate_expre1inmatchPos.unmarke1with|A.EInj(e1,n,_,_)->letes_n,_=matchList.nth_optesnwith|Somees_n->es_n|None->Errors.raise_spanned_error"sum type index error (should not happend if the term was well-typed)"(Pos.get_positione)inletnew_e=Pos.same_pos_as(A.EApp(es_n,[e1]))einevaluate_exprnew_e|A.ELitA.LEmptyError->Pos.same_pos_as(A.ELitA.LEmptyError)e|_->Errors.raise_spanned_error"Expected a term having a sum type as an argument to a match (should not happend if \
the term was well-typed"(Pos.get_positione1))|EDefault(exceptions,just,cons)->(letexceptions_orig=exceptionsinletexceptions=List.mapevaluate_exprexceptionsinletempty_count=List.length(List.filteris_empty_errorexceptions)inmatchList.lengthexceptions-empty_countwith|0->(letjust=evaluate_exprjustinmatchPos.unmarkjustwith|ELitLEmptyError->Pos.same_pos_as(A.ELitLEmptyError)e|ELit(LBooltrue)->evaluate_exprcons|ELit(LBoolfalse)->Pos.same_pos_as(A.ELitLEmptyError)e|_->Errors.raise_spanned_error"Default justification has not been reduced to a boolean at evaluation (should not \
happen if the term was well-typed"(Pos.get_positione))|1->List.find(funsub->not(is_empty_errorsub))exceptions|_->Errors.raise_multispanned_error"There is a conflict between multiple exceptions for assigning the same variable."(List.map(fun(_,except)->(Some"This justification is true:",Pos.get_positionexcept))(List.filter(fun(sub,_)->not(is_empty_errorsub))(List.map2(funxy->(x,y))exceptionsexceptions_orig))))|EIfThenElse(cond,et,ef)->(matchPos.unmark(evaluate_exprcond)with|ELit(LBooltrue)->evaluate_expret|ELit(LBoolfalse)->evaluate_expref|_->Errors.raise_spanned_error"Expected a boolean literal for the result of this condition (should not happen if the \
term was well-typed)"(Pos.get_positioncond))|EAsserte'->(matchPos.unmark(evaluate_expre')with|ELit(LBooltrue)->Pos.same_pos_as(Ast.ELitLUnit)e'|ELit(LBoolfalse)->(matchPos.unmarke'with|EApp((Ast.EOp(Binopop),pos_op),[e1;e2])->Errors.raise_spanned_error(Format.asprintf"Assertion failed: %a %a %a"Print.format_expre1Print.format_binop(op,pos_op)Print.format_expre2)(Pos.get_positione')|_->Errors.raise_spanned_error(Format.asprintf"Assertion failed")(Pos.get_positione'))|_->Errors.raise_spanned_error"Expected a boolean literal for the result of this assertion (should not happen if the \
term was well-typed)"(Pos.get_positione'))(** {1 API} *)(** Interpret a program. This function expects an expression typed as a function whose argument are
all thunked. The function is executed by providing for each argument a thunked empty default. *)letinterpret_program(e:Ast.exprPos.marked):(Ast.Var.t*Ast.exprPos.marked)list=matchPos.unmark(evaluate_expre)with|Ast.EAbs(_,binder,taus)->(letapplication_term=List.map(fun_->empty_thunked_term)tausinletto_interpret=(Ast.EApp(e,application_term),Pos.no_pos)inmatchPos.unmark(evaluate_exprto_interpret)with|Ast.ETupleargs->letvars,_=Bindlib.unmbindbinderinList.map2(fun(arg,_)var->(var,arg))args(Array.to_listvars)|_->Errors.raise_spanned_error"The interpretation of a program should always yield a tuple"(Pos.get_positione))|_->Errors.raise_spanned_error"The interpreter can only interpret terms starting with functions having thunked arguments"(Pos.get_positione)