mirror of
https://github.com/CatalaLang/catala.git
synced 2024-09-19 16:28:12 +03:00
d9fbe4b499
better error messages fixed make_matchopt_with_abs_arms fixed license few documentation
456 lines
18 KiB
OCaml
456 lines
18 KiB
OCaml
open Utils
|
|
module D = Dcalc.Ast
|
|
module A = Ast
|
|
|
|
(** hoisting *)
|
|
|
|
(** The main idea around this pass is to compile Dcalc to Lcalc without using [raise EmptyError] nor
|
|
[try _ with EmptyError -> _]. To do so, we use the same technique as in rust or erlang to handle
|
|
this kind of exceptions. Each [raise EmptyError] will be translated as [None] and each
|
|
[try e1 with EmtpyError -> e2] as [match e1 with | None -> e2 | Some x -> x].
|
|
|
|
When doing this naively, this requires to add matches and Some constructor everywhere. We apply
|
|
here an other technique where we generate what we call `cuts`. Cuts are expression whom could
|
|
minimally [raise EmptyError]. For instance
|
|
[let x = <e1, e2, ..., en| e_just :- e_cons> * 3 in x + 1], the sub-expression
|
|
[<e1, e2, ..., en| e_just :- e_cons>] can produce an empty error. So we make a cut with a new
|
|
variable [y] linked to the Dcalc expression [<e1, e2, ..., en| e_just :- e_cons>], and we return
|
|
as the translated expression [let x = y * 3 in x + 1].
|
|
|
|
The compilation of expressions is found in the functions [translate_and_cut ctx e] and
|
|
[translate_expr ctx e]. Every option-generating expresion when calling [translate_and_cut] will
|
|
be cutted and later handled by the [translate_expr] function. Every other cases is found in the
|
|
translate_and_cut function. *)
|
|
|
|
type cuts = D.expr Pos.marked A.VarMap.t
|
|
(** cuts *)
|
|
|
|
type info = { expr : A.expr Pos.marked Bindlib.box; var : A.expr Bindlib.var; is_pure : bool }
|
|
(** information about the Dcalc variable : what is the corresponding LCalc variable; an expression
|
|
build correctly using Bindlib, and a boolean `is_pure` indicating whenever the variable can be an EmptyError and hence should be matched (false) or if it never can be EmptyError (true). *)
|
|
|
|
type ctx = info D.VarMap.t
|
|
(** information context about variables in the current scope *)
|
|
|
|
let pp_info (fmt : Format.formatter) (info : info) =
|
|
Format.fprintf fmt "{var: %a; is_pure: %b}" Print.format_var info.var info.is_pure
|
|
|
|
let pp_binding (fmt : Format.formatter) ((v, info) : D.Var.t * info) =
|
|
Format.fprintf fmt "%a:%a" Dcalc.Print.format_var v pp_info info
|
|
|
|
let pp_ctx (fmt : Format.formatter) (ctx : ctx) =
|
|
let pp_bindings =
|
|
Format.pp_print_list ~pp_sep:(fun fmt () -> Format.pp_print_string fmt "; ") pp_binding
|
|
in
|
|
Format.fprintf fmt "@[<2>[%a]@]" pp_bindings (D.VarMap.bindings ctx)
|
|
|
|
(** [find ~info n ctx] is a warpper to ocaml's Map.find that handle errors in a slightly better way. *)
|
|
let find ?(info: string = "none") (n: D.Var.t) (ctx: ctx) : info =
|
|
let _ =
|
|
Format.asprintf "Searching for variable %a inside context %a"
|
|
Dcalc.Print.format_var n pp_ctx ctx
|
|
|> Cli.debug_print
|
|
in
|
|
try D.VarMap.find n ctx
|
|
with Not_found ->
|
|
Errors.raise_spanned_error
|
|
(Format.asprintf
|
|
"Internal Error: Variable %a was not found in the current environment. Additional \
|
|
informations : %s."
|
|
Dcalc.Print.format_var n info)
|
|
Pos.no_pos
|
|
|
|
let add_var (pos: Pos.t) (var: D.Var.t) (is_pure: bool) (ctx: ctx) : ctx =
|
|
let new_var = A.Var.make (Bindlib.name_of var, pos) in
|
|
let expr = A.make_var (new_var, pos) in
|
|
|
|
Cli.debug_print @@ Format.asprintf "D.%a |-> A.%a"
|
|
Dcalc.Print.format_var var
|
|
Print.format_var new_var;
|
|
|
|
D.VarMap.update var (fun _ -> Some { expr; var = new_var; is_pure }) ctx
|
|
|
|
(* D.VarMap.add var { expr; var = new_var; is_pure } ctx *)
|
|
|
|
let translate_lit (l : D.lit) (pos : Pos.t) : A.lit =
|
|
match l with
|
|
| D.LBool l -> A.LBool l
|
|
| D.LInt i -> A.LInt i
|
|
| D.LRat r -> A.LRat r
|
|
| D.LMoney m -> A.LMoney m
|
|
| D.LUnit -> A.LUnit
|
|
| D.LDate d -> A.LDate d
|
|
| D.LDuration d -> A.LDuration d
|
|
| D.LEmptyError ->
|
|
Errors.raise_spanned_error
|
|
"Internal Error: An empty error was found in a place that shouldn't be possible." pos
|
|
|
|
(** [c = disjoint_union_maps cs] Compute the disjoint union of multiple maps. Raises an internal
|
|
error if there is two identicals keys in differnts parts. *)
|
|
let disjoint_union_maps (pos : Pos.t) (cs : 'a A.VarMap.t list) : 'a A.VarMap.t =
|
|
let disjoint_union =
|
|
A.VarMap.union (fun _ _ _ ->
|
|
Errors.raise_spanned_error
|
|
"Internal Error: Two supposed to be disjoints maps have one shared key." pos)
|
|
in
|
|
|
|
List.fold_left disjoint_union A.VarMap.empty cs
|
|
|
|
(** [e' = translate_and_cut ctx e ] Translate the Dcalc expression e into an expression in Lcalc,
|
|
given we translate each cuts correctly. It ensures the equivalence between the execution of e
|
|
and the execution of e' are equivalent in an environement where each variable v, where (v, e_v)
|
|
is in cuts, has the non-empty value in e_v. *)
|
|
let rec translate_and_cut (ctx : ctx) (e : D.expr Pos.marked) : A.expr Pos.marked Bindlib.box * cuts
|
|
=
|
|
let pos = Pos.get_position e in
|
|
match Pos.unmark e with
|
|
(* empty-producing/using terms. We cut those. (D.EVar in some cases, EApp(D.EVar _, [ELit LUnit]), EDefault _, ELit LEmptyDefault) I'm unsure about assert. *)
|
|
| D.EVar v ->
|
|
(* todo: for now, every unpure (such that [is_pure] is [false] in the current context) is thunked, hence matched in the next case. This assumption can change in the future, and this case is here for this reason. *)
|
|
let v, pos_v = v in
|
|
if not (find ~info:"search for a variable" v ctx).is_pure then begin
|
|
let v' = A.Var.make (Bindlib.name_of v, pos_v) in
|
|
Cli.debug_print
|
|
@@ Format.asprintf "Found an unpure variable %a, created a variable %a to replace it" Dcalc.Print.format_var
|
|
v Print.format_var v';
|
|
(A.make_var (v', pos), A.VarMap.singleton v' e)
|
|
end
|
|
else ((find ~info:"should never happend" v ctx).expr, A.VarMap.empty)
|
|
| D.EApp ((D.EVar (v, pos_v), p), [ (D.ELit D.LUnit, _) ]) ->
|
|
if not (find ~info:"search for a variable" v ctx).is_pure then begin
|
|
let v' = A.Var.make (Bindlib.name_of v, pos_v) in
|
|
Cli.debug_print
|
|
@@ Format.asprintf "Found an unpure variable %a, created a variable %a to replace it" Dcalc.Print.format_var
|
|
v Print.format_var v';
|
|
(A.make_var (v', pos), A.VarMap.singleton v' (D.EVar (v, pos_v), p))
|
|
end
|
|
else
|
|
Errors.raise_spanned_error
|
|
"Internal error: an pure variable was found in an unpure environment." pos
|
|
| D.EDefault (_exceptions, _just, _cons) ->
|
|
let v' = A.Var.make ("default_term", pos) in
|
|
(A.make_var (v', pos), A.VarMap.singleton v' e)
|
|
| D.ELit D.LEmptyError ->
|
|
let v' = A.Var.make ("empty_litteral", pos) in
|
|
(A.make_var (v', pos), A.VarMap.singleton v' e)
|
|
| D.EAssert _ ->
|
|
(* as discuted, if the value in an assertion is empty, an error should the raised. This beavior is different from the ICFP paper. *)
|
|
let v' = A.Var.make ("assertion_value", pos) in
|
|
(A.make_var (v', pos), A.VarMap.singleton v' e)
|
|
(* This one is a very special case. It transform an unpure expression environement to a pure expression. *)
|
|
| ErrorOnEmpty arg ->
|
|
(* [
|
|
match arg with
|
|
| None -> raise NoValueProvided
|
|
| Some v -> {{ v }}
|
|
] *)
|
|
let silent_var = A.Var.make ("_", pos) in
|
|
let x = A.Var.make ("non_empty_argument", pos) in
|
|
|
|
let arg' = translate_expr ctx arg in
|
|
|
|
( A.make_matchopt_with_abs_arms arg'
|
|
(A.make_abs [| silent_var |]
|
|
(Bindlib.box (A.ERaise A.NoValueProvided, pos))
|
|
pos [ (D.TAny, pos) ] pos)
|
|
(A.make_abs [| x |] (A.make_var (x, pos)) pos [ (D.TAny, pos) ] pos),
|
|
A.VarMap.empty )
|
|
(* pure terms *)
|
|
| D.ELit l -> (Bindlib.box (A.ELit (translate_lit l pos), pos), A.VarMap.empty)
|
|
| D.EIfThenElse (e1, e2, e3) ->
|
|
let e1', c1 = translate_and_cut ctx e1 in
|
|
let e2', c2 = translate_and_cut ctx e2 in
|
|
let e3', c3 = translate_and_cut ctx e3 in
|
|
|
|
let e' =
|
|
Bindlib.box_apply3 (fun e1' e2' e3' -> (A.EIfThenElse (e1', e2', e3'), pos)) e1' e2' e3'
|
|
in
|
|
|
|
(*(* equivalent code : *)
|
|
let e' =
|
|
let+ e1' = e1' and+ e2' = e2' and+ e3' = e3' in
|
|
(A.EIfThenElse (e1', e2', e3'), pos)
|
|
in
|
|
*)
|
|
(e', disjoint_union_maps pos [ c1; c2; c3 ])
|
|
| D.EAbs ((binder, pos_binder), ts) ->
|
|
let vars, body = Bindlib.unmbind binder in
|
|
let ctx, lc_vars =
|
|
ArrayLabels.fold_right vars ~init:(ctx, []) ~f:(fun var (ctx, lc_vars) ->
|
|
(* we suppose the invariant that when applying a function, its arguments cannot be of the type "option".
|
|
|
|
The code should behave correctly in the without this assumption if we put here an is_pure=false, but the types are more compilcated. (unimplemented for now) *)
|
|
let ctx = add_var pos var true ctx in
|
|
let lc_var = (find var ctx).var in
|
|
(ctx, lc_var :: lc_vars))
|
|
in
|
|
let lc_vars = Array.of_list lc_vars in
|
|
|
|
(* here we take the guess that if we cannot build the closure because one of the variable is empty, then we cannot build the function. *)
|
|
let new_body, cuts = translate_and_cut ctx body in
|
|
let new_binder = Bindlib.bind_mvar lc_vars new_body in
|
|
|
|
( Bindlib.box_apply (fun new_binder -> (A.EAbs ((new_binder, pos_binder), ts), pos)) new_binder,
|
|
cuts )
|
|
| EApp (e1, args) ->
|
|
(* general case is simple *)
|
|
let e1', c1 = translate_and_cut ctx e1 in
|
|
let args', c_args = args |> List.map (translate_and_cut ctx) |> List.split in
|
|
|
|
let cuts = disjoint_union_maps pos (c1 :: c_args) in
|
|
let e' =
|
|
Bindlib.box_apply2
|
|
(fun e1' args' -> (A.EApp (e1', args'), pos))
|
|
e1' (Bindlib.box_list args')
|
|
in
|
|
(e', cuts)
|
|
| ETuple (args, s) ->
|
|
let args', c_args = args |> List.map (translate_and_cut ctx) |> List.split in
|
|
|
|
let cuts = disjoint_union_maps pos c_args in
|
|
(Bindlib.box_apply (fun args' -> (A.ETuple (args', s), pos)) (Bindlib.box_list args'), cuts)
|
|
| ETupleAccess (e1, i, s, ts) ->
|
|
let e1', cuts = translate_and_cut ctx e1 in
|
|
let e1' = Bindlib.box_apply (fun e1' -> (A.ETupleAccess (e1', i, s, ts), pos)) e1' in
|
|
(e1', cuts)
|
|
| EInj (e1, i, en, ts) ->
|
|
let e1', cuts = translate_and_cut ctx e1 in
|
|
let e1' = Bindlib.box_apply (fun e1' -> (A.EInj (e1', i, en, ts), pos)) e1' in
|
|
(e1', cuts)
|
|
| EMatch (e1, cases, en) ->
|
|
let e1', c1 = translate_and_cut ctx e1 in
|
|
let cases', c_cases = cases |> List.map (translate_and_cut ctx) |> List.split in
|
|
|
|
let cuts = disjoint_union_maps pos (c1 :: c_cases) in
|
|
let e' =
|
|
Bindlib.box_apply2
|
|
(fun e1' cases' -> (A.EMatch (e1', cases', en), pos))
|
|
e1' (Bindlib.box_list cases')
|
|
in
|
|
(e', cuts)
|
|
| EArray es ->
|
|
let es', cuts = es |> List.map (translate_and_cut ctx) |> List.split in
|
|
|
|
( Bindlib.box_apply (fun es' -> (A.EArray es', pos)) (Bindlib.box_list es'),
|
|
disjoint_union_maps pos cuts )
|
|
| EOp op -> (Bindlib.box (A.EOp op, pos), A.VarMap.empty)
|
|
|
|
and translate_expr ?(append_esome = true) (ctx : ctx) (e : D.expr Pos.marked) :
|
|
A.expr Pos.marked Bindlib.box =
|
|
let e', cs = translate_and_cut ctx e in
|
|
let cs = A.VarMap.bindings cs in
|
|
|
|
let _pos = Pos.get_position e in
|
|
|
|
(* build the cuts *)
|
|
Cli.debug_print
|
|
@@ Format.asprintf "cut for the expression: [%a]" (Format.pp_print_list Print.format_var) (List.map fst cs);
|
|
|
|
ListLabels.fold_left cs
|
|
~init:(if append_esome then A.make_some e' else e')
|
|
~f:(fun acc (v, (c, pos_c)) ->
|
|
Cli.debug_print @@ Format.asprintf "cut using A.%a" Print.format_var v;
|
|
|
|
let c' : A.expr Pos.marked Bindlib.box =
|
|
match c with
|
|
(* Here we have to handle only the cases appearing in cuts, as defined the [translate_and_cut] function. *)
|
|
| D.EVar v -> (find ~info:"should never happend" (Pos.unmark v) ctx).expr
|
|
| D.EDefault (excep, just, cons) ->
|
|
let excep' = List.map (translate_expr ctx) excep in
|
|
let just' = translate_expr ctx just in
|
|
let cons' = translate_expr ctx cons in
|
|
(* calls handle_option. *)
|
|
A.make_app
|
|
(A.make_var (A.handle_default_opt, pos_c))
|
|
[
|
|
Bindlib.box_apply (fun excep' -> (A.EArray excep', pos_c)) (Bindlib.box_list excep');
|
|
just';
|
|
cons';
|
|
]
|
|
pos_c
|
|
| D.ELit D.LEmptyError -> A.make_none pos_c
|
|
| D.EAssert arg ->
|
|
let arg' = translate_expr ctx arg in
|
|
|
|
(* [
|
|
match arg with
|
|
| None -> raise NoValueProvided
|
|
| Some v -> assert {{ v }}
|
|
] *)
|
|
let silent_var = A.Var.make ("_", pos_c) in
|
|
let x = A.Var.make ("assertion_argument", pos_c) in
|
|
|
|
A.make_matchopt_with_abs_arms arg'
|
|
(A.make_abs [| silent_var |]
|
|
(Bindlib.box (A.ERaise A.NoValueProvided, pos_c))
|
|
pos_c [ (D.TAny, pos_c) ] pos_c)
|
|
(A.make_abs [| x |]
|
|
(Bindlib.box_apply (fun arg -> (A.EAssert arg, pos_c)) (A.make_var (x, pos_c)))
|
|
pos_c [ (D.TAny, pos_c) ] pos_c)
|
|
| _ ->
|
|
Errors.raise_spanned_error
|
|
"Internal Error: An term was found in a position where it should not be" pos_c
|
|
in
|
|
|
|
(* [
|
|
match {{ c' }} with
|
|
| None -> None
|
|
| Some {{ v }} -> {{ acc }}
|
|
end
|
|
] *)
|
|
Cli.debug_print @@ Format.asprintf "build matchopt using %a" Print.format_var v;
|
|
A.make_matchopt pos_c v (D.TAny, pos_c) c' (A.make_none pos_c) acc)
|
|
|
|
type scope_lets =
|
|
| Result of D.expr Pos.marked
|
|
| ScopeLet of {
|
|
scope_let_kind : D.scope_let_kind;
|
|
scope_let_typ : D.typ Pos.marked;
|
|
scope_let_expr : D.expr Pos.marked;
|
|
scope_let_next : (D.expr, scope_lets) Bindlib.binder;
|
|
scope_let_pos : Pos.t;
|
|
}
|
|
|
|
let union = D.VarMap.union (fun _ _ _ -> Some ())
|
|
|
|
let rec fv_scope_lets scope_lets =
|
|
match scope_lets with
|
|
| Result e -> D.fv e
|
|
| ScopeLet { scope_let_expr = e; scope_let_next = next; _ } ->
|
|
let v, body = Bindlib.unbind next in
|
|
union (D.fv e) (D.VarMap.remove v (fv_scope_lets body))
|
|
|
|
type scope_body = {
|
|
scope_body_input_struct : D.StructName.t;
|
|
scope_body_output_struct : D.StructName.t;
|
|
scope_body_result : (D.expr, scope_lets) Bindlib.binder;
|
|
}
|
|
|
|
let fv_scope_body { scope_body_result = binder; _ } =
|
|
let v, body = Bindlib.unbind binder in
|
|
D.VarMap.remove v (fv_scope_lets body)
|
|
|
|
let free_vars_scope_body scope_body = fv_scope_body scope_body |> D.VarMap.bindings |> List.map fst
|
|
|
|
let translate_and_bind_lets (acc : scope_lets Bindlib.box) (scope_let : D.scope_let) :
|
|
scope_lets Bindlib.box =
|
|
let pos = snd scope_let.D.scope_let_var in
|
|
|
|
Cli.debug_print
|
|
@@ Format.asprintf "binding let %a. Variable occurs = %b" Dcalc.Print.format_var (fst scope_let.D.scope_let_var)
|
|
(Bindlib.occur (fst scope_let.D.scope_let_var) acc);
|
|
|
|
let binder = Bindlib.bind_var (fst scope_let.D.scope_let_var) acc in
|
|
Bindlib.box_apply2
|
|
(fun expr binder ->
|
|
Cli.debug_print
|
|
@@ Format.asprintf "free variables in expression: %a" (Format.pp_print_list Dcalc.Print.format_var)
|
|
(D.free_vars expr);
|
|
ScopeLet
|
|
{
|
|
scope_let_kind = scope_let.D.scope_let_kind;
|
|
scope_let_typ = scope_let.D.scope_let_typ;
|
|
scope_let_expr = expr;
|
|
scope_let_next = binder;
|
|
scope_let_pos = pos;
|
|
})
|
|
scope_let.D.scope_let_expr binder
|
|
|
|
let translate_and_bind (body : D.scope_body) : scope_body Bindlib.box =
|
|
let body_result =
|
|
ListLabels.fold_right body.D.scope_body_lets
|
|
~init:(Bindlib.box_apply (fun e -> Result e) body.D.scope_body_result)
|
|
~f:(Fun.flip translate_and_bind_lets)
|
|
in
|
|
|
|
Cli.debug_print @@ Format.asprintf "binding arg %a" Dcalc.Print.format_var body.D.scope_body_arg;
|
|
let scope_body_result = Bindlib.bind_var body.D.scope_body_arg body_result in
|
|
|
|
Cli.debug_print
|
|
@@ Format.asprintf "isfinal term is closed: %b" (Bindlib.is_closed scope_body_result);
|
|
Bindlib.box_apply
|
|
(fun scope_body_result ->
|
|
Cli.debug_print
|
|
@@ Format.asprintf "rank of the final term: %i" (Bindlib.binder_rank scope_body_result);
|
|
{
|
|
scope_body_output_struct = body.D.scope_body_output_struct;
|
|
scope_body_input_struct = body.D.scope_body_input_struct;
|
|
scope_body_result;
|
|
})
|
|
scope_body_result
|
|
|
|
let rec translate_scope_let (ctx : ctx) (lets : scope_lets) =
|
|
match lets with
|
|
| Result e -> translate_expr ~append_esome:false ctx e
|
|
| ScopeLet
|
|
{
|
|
scope_let_kind = kind;
|
|
scope_let_typ = typ;
|
|
scope_let_expr = expr;
|
|
scope_let_next = next;
|
|
scope_let_pos = pos;
|
|
} ->
|
|
let var_is_pure =
|
|
match kind with
|
|
| DestructuringInputStruct -> false
|
|
| ScopeVarDefinition | SubScopeVarDefinition | CallingSubScope
|
|
| DestructuringSubScopeResults | Assertion ->
|
|
true
|
|
in
|
|
let var, next = Bindlib.unbind next in
|
|
Cli.debug_print @@ Format.asprintf "unbinding %a" Dcalc.Print.format_var var;
|
|
let ctx' = add_var pos var var_is_pure ctx in
|
|
let new_var = (find ~info:"variable that was just created" var ctx').var in
|
|
A.make_let_in new_var typ
|
|
(translate_expr ctx ~append_esome:false expr)
|
|
(translate_scope_let ctx' next)
|
|
|
|
let translate_scope_body (scope_pos : Pos.t) (_decl_ctx : D.decl_ctx) (ctx : ctx)
|
|
(body : scope_body) : A.expr Pos.marked Bindlib.box =
|
|
match body with
|
|
| {
|
|
scope_body_result = result;
|
|
scope_body_input_struct = _input_struct;
|
|
scope_body_output_struct = _output_struct;
|
|
} ->
|
|
let v, lets = Bindlib.unbind result in
|
|
|
|
let ctx' = add_var scope_pos v true ctx in
|
|
|
|
translate_scope_let ctx' lets
|
|
|
|
let translate_program (prgm : D.program) : A.program =
|
|
(* modify *)
|
|
let decl_ctx =
|
|
{
|
|
D.ctx_enums = prgm.decl_ctx.ctx_enums |> D.EnumMap.add A.option_enum A.option_enum_config;
|
|
D.ctx_structs = prgm.decl_ctx.ctx_structs;
|
|
}
|
|
in
|
|
|
|
let scopes =
|
|
prgm.scopes
|
|
|> ListLabels.fold_left ~init:([], D.VarMap.empty)
|
|
~f:(fun ((acc, ctx) : _ * info D.VarMap.t) (scope_name, n, scope_body) ->
|
|
let scope_body = Bindlib.unbox (translate_and_bind scope_body) in
|
|
|
|
Cli.debug_print
|
|
@@ Format.asprintf "global free variable : %a" (Format.pp_print_list Dcalc.Print.format_var)
|
|
(free_vars_scope_body scope_body);
|
|
let new_ctx = add_var Pos.no_pos n true ctx in
|
|
|
|
let new_n = find ~info:"variable that was just created" n new_ctx in
|
|
|
|
let scope_pos = Pos.get_position (D.ScopeName.get_info scope_name) in
|
|
|
|
let new_acc =
|
|
(new_n, Bindlib.unbox (translate_scope_body scope_pos decl_ctx ctx scope_body)) :: acc
|
|
in
|
|
|
|
(new_acc, new_ctx))
|
|
|> fst |> List.rev
|
|
|> List.map (fun (info, e) -> (info.var, e))
|
|
in
|
|
|
|
{ scopes; decl_ctx }
|