catala/compiler/lcalc/compile_without_exceptions.ml
2021-12-17 15:27:34 +01:00

507 lines
16 KiB
OCaml

(* 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. *)
open Utils
module D = Dcalc.Ast
module A = Ast
type info = {expr: A.expr Pos.marked Bindlib.box; var: A.expr Bindlib.var; is_pure: bool}
type ctx = info D.VarMap.t
let translate_lit (l : D.lit) : A.expr =
let build lit =
fst @@ Bindlib.unbox @@ A.make_some (Bindlib.box (Pos.mark Pos.no_pos (A.ELit lit)))
in
match l with
| D.LBool l -> build (A.LBool l)
| D.LInt i -> build (A.LInt i)
| D.LRat r -> build (A.LRat r)
| D.LMoney m -> build (A.LMoney m)
| D.LUnit -> build A.LUnit
| D.LDate d -> build (A.LDate d)
| D.LDuration d -> build (A.LDuration d)
| D.LEmptyError -> fst @@ Bindlib.unbox @@ A.make_none Pos.no_pos
let ( let+ ) x f = Bindlib.box_apply f x
let ( and+ ) x y = Bindlib.box_pair x y
let thunk_expr (e : A.expr Pos.marked Bindlib.box) (pos : Pos.t) : A.expr Pos.marked Bindlib.box =
let dummy_var = A.Var.make ("_", pos) in
A.make_abs [| dummy_var |] e pos [ (D.TAny, pos) ] pos
let add_var pos var is_pure ctx =
let new_var = A.Var.make (Bindlib.name_of var, pos) in
let expr = A.make_var (new_var, pos) in
D.VarMap.add var {expr; var=new_var; is_pure} ctx
let rec translate_default (ctx : ctx) (exceptions : D.expr Pos.marked list)
(just : D.expr Pos.marked) (cons : D.expr Pos.marked) (pos_default : Pos.t) :
A.expr Pos.marked Bindlib.box =
let exceptions = List.map (fun except -> translate_expr ctx except) exceptions in
let exceptions =
A.make_app
(A.make_var (A.handle_default, pos_default))
[
Bindlib.box_apply
(fun exceptions -> (A.EArray exceptions, pos_default))
(Bindlib.box_list exceptions);
thunk_expr (translate_expr ctx just) pos_default;
thunk_expr (translate_expr ctx cons) pos_default;
]
pos_default
in
exceptions
and translate_typ (t : D.typ Pos.marked) : D.typ Pos.marked =
(* Hack: If the type is D.TAny, it means for the compiler to not put any type annotation.*)
Pos.same_pos_as D.TAny t
and translate_binder (ctx: ctx) ((binder, pos_binder): (D.expr, D.expr Pos.marked) Bindlib.mbinder Pos.marked): (A.expr, A.expr Pos.marked) Bindlib.mbinder Pos.marked Bindlib.box =
let vars, body = Bindlib.unmbind binder in
let ctx, lc_vars =
Array.fold_right
begin fun var (ctx, lc_vars) ->
let lc_var = A.Var.make (Bindlib.name_of var, pos_binder) in
let lc_var_expr = A.make_var (lc_var, pos_binder) in
let new_ctx = D.VarMap.add var {expr=lc_var_expr; is_pure= false; var= lc_var} ctx in
(new_ctx, lc_var :: lc_vars) end
vars (ctx, [])
in
let lc_vars = Array.of_list lc_vars in
let new_body = translate_expr ctx body in
let+ binder = Bindlib.bind_mvar lc_vars new_body in
(binder, pos_binder)
and translate_expr (ctx : ctx) (e : D.expr Pos.marked) : A.expr Pos.marked Bindlib.box =
let same_pos e' = Pos.same_pos_as e' e in
match Pos.unmark e with
| D.EVar v ->
let info = D.VarMap.find (Pos.unmark v) ctx in
if info.is_pure then
A.make_some info.expr
else
info.expr
| D.ETuple (args, s) ->
let+ args = Bindlib.box_list (List.map (translate_expr ctx) args) in
Pos.same_pos_as (A.ETuple (args, s)) e
| D.ETupleAccess (e1, i, s, ts) ->
let e1 = translate_expr ctx e1 in
let pos = Pos.get_position (Bindlib.unbox e1) in
let tau = (D.TAny, pos) in
let new_e new_e1 =
let+ new_e1 = new_e1 in
same_pos @@ A.ETupleAccess (new_e1, i, s, ts)
in
A.make_bindopt pos tau e1 new_e
| D.EInj (e1, i, en, ts) ->
let e1 = translate_expr ctx e1 in
let pos = Pos.get_position (Bindlib.unbox e1) in
let tau = (D.TAny, pos) in
let new_e new_e1 =
let+ new_e1 = new_e1 in
same_pos @@ A.EInj (new_e1, i, en, ts)
in
A.make_bindopt pos tau e1 new_e
| D.EMatch (e1, cases, en) ->
let e1 = translate_expr ctx e1 in
let pos = Pos.get_position (Bindlib.unbox e1) in
let tau = (D.TAny, pos) in
let new_e new_e1 =
let+ new_e1 = new_e1
and+ cases =
cases
|> List.map (fun (e', _pos) ->
match e' with
| D.EAbs (binder, ts) ->
let+ new_binder = translate_binder ctx binder in
same_pos @@ A.EAbs (new_binder, List.map translate_typ ts)
| _ -> Errors.raise_spanned_error "Internal error: an error occured during the translation of a amtch." (Pos.get_position e))
|> Bindlib.box_list
in
if (List.for_all (fun (x, _) -> match x with A.EAbs _ -> true | _ -> false) cases) then
Errors.raise_spanned_error "Internal error: an error occured during the translation of a match." (Pos.get_position e);
same_pos @@ A.EMatch (new_e1, cases, en)
in
A.make_bindopt pos tau e1 new_e
| D.EArray es ->
let+ es = es |> List.map (translate_expr ctx) |> Bindlib.box_list in
same_pos @@ A.make_some' (same_pos @@ A.EArray es)
| D.ELit l -> Bindlib.box @@ same_pos @@ translate_lit l
| D.EOp _op ->
Errors.raise_spanned_error "Internal error: partial application of generic operator are not yet supported when using --avoid_exception." (Pos.get_position e)
| D.EApp((D.EOp op, pos), args) ->
begin
let xs = List.mapi (fun i arg -> A.Var.make (Printf.sprintf "x_%d" i, Pos.get_position arg)) args in
let dummy = A.Var.make ("unit", pos) in
let e' final = args
|> List.map (translate_expr ctx)
|> List.combine xs
|> List.fold_left (fun acc (x, arg) ->
A.make_matchopt
arg
(A.make_abs (Array.of_list [dummy]) (A.make_none pos) (pos) [D.TLit D.TUnit, pos] pos)
(A.make_abs (Array.of_list [x]) acc pos [D.TAny, pos] pos)
) final
in
let new_e =
let+ args_var = xs
|> List.map (fun x -> Bindlib.box_var x)
|> Bindlib.box_list
in
let args_var = args_var
|> List.combine args
|> List.map (fun (arg, x) -> Pos.same_pos_as x arg)
in
same_pos @@ A.make_some' @@ same_pos @@ A.EApp ((A.EOp op, pos), args_var)
in
e' new_e
end
| D.EIfThenElse (e1, e2, e3) ->
let e1 = translate_expr ctx e1 in
let pos = Pos.get_position (Bindlib.unbox e1) in
let tau = (D.TAny, pos) in
let new_e new_e1 =
let+ e1_new = new_e1
and+ e2 = translate_expr ctx e2
and+ e3 = translate_expr ctx e3 in
same_pos @@ A.EIfThenElse (e1_new, e2, e3)
in
A.make_bindopt pos tau e1 new_e
| D.EAssert e1 ->
(* don't know the semantic of EAssert. *)
(* Bindlib.box_apply (fun e1 -> Pos.same_pos_as (A.EAssert e1) e) (translate_expr ctx e1) *)
let e1 = translate_expr ctx e1 in
let pos = Pos.get_position (Bindlib.unbox e1) in
let tau = (D.TAny, pos) in
let new_e new_e1 =
let+ e1_new = new_e1 in
same_pos @@ A.EAssert e1_new
in
A.make_bindopt pos tau e1 new_e
| D.EApp (e1, args) ->
let e1 = translate_expr ctx e1 in
let pos = Pos.get_position (Bindlib.unbox e1) in
let tau = (D.TAny, pos) in
let new_e new_e1 =
let+ new_e1 = new_e1
and+ args = args
|> List.map (translate_expr ctx)
|> Bindlib.box_list
in
same_pos @@ A.EApp (new_e1, args)
in
A.make_bindopt pos tau e1 new_e
| D.EAbs (binder, ts) ->
let+ new_binder = translate_binder ctx binder in
same_pos
@@ A.make_some' (same_pos @@ A.EAbs (new_binder, List.map translate_typ ts))
| D.EDefault (exceptions, just, cons) ->
translate_default ctx exceptions just cons (Pos.get_position e)
| D.ErrorOnEmpty arg ->
(* we need to be carefull on this one *)
begin
(* ~> match [| arg |] with None -> raise NoValueProvided | Some x -> x *)
let pos_arg = Pos.get_position arg in
let x = A.Var.make ("result", pos_arg) in
let arg = translate_expr ctx arg in
let tau = (D.TAny, pos_arg) in
let e3 =
A.make_abs
(Array.of_list [ x ])
(let+ v = Bindlib.box_var x in (v, pos_arg))
pos_arg [ tau ] pos_arg
and e1 = arg
and e2 =
A.make_abs
(Array.of_list [ x ])
(Bindlib.box @@ (A.ERaise A.NoValueProvided, Pos.get_position e))
pos_arg [ tau ] pos_arg
in
A.make_some @@ A.make_matchopt e1 e2 e3
end
(* Errors.raise_spanned_error "Internal error: Error on empty found in incorrect place when compiling using the --avoid_exception option." (Pos.get_position e) *)
let rec translate_scope_vardefinition ctx expr: A.expr Pos.marked Bindlib.box =
match expr with
| D.ErrorOnEmpty arg, pos_expr ->
begin
(* ~> match [| arg |] with None -> raise NoValueProvided | Some x -> x *)
let pos_arg = Pos.get_position arg in
let x = A.Var.make ("result", pos_arg) in
let arg = translate_expr ctx arg in
let tau = (D.TAny, pos_arg) in
let e3 =
A.make_abs
(Array.of_list [ x ])
(let+ v = Bindlib.box_var x in (v, pos_arg))
pos_arg [ tau ] pos_arg
and e1 = arg
and e2 =
A.make_abs
(Array.of_list [ x ])
(Bindlib.box @@ (A.ERaise A.NoValueProvided, pos_expr))
pos_arg [ tau ] pos_arg
in
A.make_matchopt e1 e2 e3
end
| D.EApp((D.EOp (D.Unop (D.Log (le, l))), pos_log), [e']), pos ->
let+ e' = translate_scope_vardefinition ctx e' in
A.EApp((A.EOp (D.Unop (D.Log (le, l))), pos_log), [e']), pos
| (expr, pos) ->
Errors.raise_spanned_error (Printf.sprintf "Internal error: Found unexpected expression when compiling an expression using the --avoid_exception option. ''Full'' term: %s" (D.show_expr expr)) pos
let translate_scope_let (ctx: ctx) (s: D.scope_let) : ctx * A.expr Pos.marked Bindlib.box =
match s with {
D.scope_let_var = var;
D.scope_let_kind = kind;
D.scope_let_typ = typ;
D.scope_let_expr = expr;
} -> begin
(* I need to match on the expression. *)
let expr' : A.expr Pos.marked Bindlib.box =
let expr = Bindlib.unbox expr in
let same_pos e' = Pos.same_pos_as e' expr in
match kind, typ, expr with
| ScopeVarDefinition, _typ, expr ->
translate_scope_vardefinition ctx expr
| Assertion, _typ, expr -> begin
let pos = Pos.get_position expr in
let x = A.Var.make ("result", pos) in
let arg = translate_expr ctx expr in
let tau = (D.TAny, pos) in
let e3 =
A.make_abs
(Array.of_list [ x ])
(let+ v = Bindlib.box_var x in (v, pos))
pos [ tau ] pos
and e1 = arg
and e2 =
A.make_abs
(Array.of_list [ x ])
(Bindlib.box @@ same_pos @@ A.ERaise A.NoValueProvided)
pos [ tau ] pos
in
A.make_matchopt e1 e2 e3
end
| SubScopeVarDefinition, _typ, (D.EAbs ((binder, pos_binder), _tau), pos) ->
begin
let vs, body = Bindlib.unmbind binder in
(* we need to add them to the context momentally *)
let ctx = ArrayLabels.fold_left vs
~init:ctx
~f:(fun ctx (v: D.expr Bindlib.var) ->
add_var pos_binder v false ctx
)
in
let vs' = Array.map (fun v -> (D.VarMap.find v ctx).var) vs in
let body' = translate_expr ctx body in
(* there is no need to add the binded var to the context since we know it is thunked *)
A.make_abs vs' body' pos_binder [D.TAny, pos_binder] pos
end
| DestructuringInputStruct, _typ, expr ->
translate_expr ctx expr
| DestructuringSubScopeResults, _typ, expr ->
translate_expr ctx expr
| CallingSubScope, _typ, expr ->
translate_expr ctx expr
| kind, _typ, (expr, pos) ->
let kind_s = match kind with
| ScopeVarDefinition -> "ScopeVarDefinition"
| Assertion -> "Assertion"
| SubScopeVarDefinition -> "SubScopeVarDefinition"
| DestructuringInputStruct -> "DestructuringInputStruct"
| DestructuringSubScopeResults -> "DestructuringSubScopeResults"
| CallingSubScope -> "CallingSubScope" in
let expr_s = match expr with
| EVar _ -> "EVar"
| ETuple _ -> "ETuple"
| ETupleAccess _ -> "ETupleAccess"
| EInj _ -> "EInj"
| EMatch _ -> "EMatch"
| EArray _ -> "EArray"
| ELit _ -> "ELit"
| EAbs _ -> "EAbs"
| EApp _ -> "EApp"
| EAssert _ -> "EAssert"
| EOp _ -> "EOp"
| EDefault _ -> "EDefault"
| EIfThenElse _ -> "EIfThenElse"
| ErrorOnEmpty _ -> "ErrorOnEmpty"
in
Errors.raise_spanned_error (Printf.sprintf "Internal error: Found unexpected %s when compiling an expression containing %s using the --avoid_exception option. ''Full'' term: %s" kind_s expr_s (D.show_expr expr)) pos
in
let is_pure = match kind with
| ScopeVarDefinition -> true
| Assertion -> true
| SubScopeVarDefinition -> true
| DestructuringInputStruct -> true
| DestructuringSubScopeResults -> true
| CallingSubScope -> false
in
let ctx' = add_var (snd var) (fst var) is_pure ctx in
(ctx', expr')
end
let translate_scope_body (ctx: ctx) (s: D.scope_body): A.expr Pos.marked Bindlib.box =
match s with {
D.scope_body_lets=lets;
D.scope_body_result=result;
D.scope_body_arg=arg;
_
} -> begin
(* first we add to the input the ctx *)
let ctx1 = add_var Pos.no_pos arg true ctx in
(* then, we compute the lets bindings and modification to the ctx *)
(* todo: once we update to ocaml 4.11, use fold_left_map instead of fold_left + List.rev *)
let ctx2, acc = ListLabels.fold_left lets
~init:(ctx1, [])
~f:begin fun (ctx, acc) (s: D.scope_let) ->
let ctx, e = translate_scope_let ctx s in
(ctx, (s.scope_let_var, D.TAny, e)::acc)
end
in
let acc = List.rev acc in
(* we now have the context for the final transformation: the result *)
(* todo: alaid, result is boxed and hence incompatible with translate_expr... *)
let result = translate_expr ctx2 (Bindlib.unbox result) in
(* finally, we can recombine everything using nested let ... = ... in *)
let body =
ListLabels.fold_left acc
~init:result
~f:(fun (body: (A.expr * Pos.t) Bindlib.box) ((v, pos), tau, e) ->
A.make_let_in (D.VarMap.find v ctx2).var (tau, pos) e body
)
in
(* we finnally rebuild the binder *)
A.make_abs (Array.of_list [(D.VarMap.find arg ctx1).var]) body Pos.no_pos [D.TAny, Pos.no_pos] Pos.no_pos
end
let translate_program (prgm : D.program) : A.program =
let new_scopes = (prgm.scopes : (D.ScopeName.t * D.expr Bindlib.var * D.scope_body) list)
|> ListLabels.fold_left
~init:([], D.VarMap.empty)
~f:begin fun (acc, ctx) (_, n, e) ->
let env: ctx = D.VarMap.map (fun v ->
let new_var = A.Var.make (Bindlib.name_of v, Pos.no_pos) in
let expr = A.make_var (new_var, Pos.no_pos) in
{expr; var=new_var; is_pure=true}
) ctx in
let new_n = A.Var.make (Bindlib.name_of n, Pos.no_pos) in
let new_e = translate_scope_body env e in
let new_acc = (new_n, Bindlib.unbox new_e) :: acc in
let new_ctx = D.VarMap.add n new_n ctx in
(new_acc, new_ctx)
end
|> fst
|> List.rev
in
{
scopes = new_scopes;
decl_ctx =
{
ctx_enums = prgm.decl_ctx.ctx_enums |> D.EnumMap.add A.option_enum A.option_enum_config;
ctx_structs = prgm.decl_ctx.ctx_structs;
};
}