blob: b6b06512db805c0ef090cfb55174a66c3cc56795 [file] [edit]
open Util
open Source
open Ast
open Xl
open Env
(* Environment *)
type env = Env.t
type subst = Subst.t
(* Helpers *)
let assume_coherent_matches = ref true
type 'a result = ('a, 'a) Result.t
let static f env x =
let (Ok y | Error y) = f true env x in Ok y
let static3 f env x1 x2 x3 =
let (Ok y | Error y) = f true env x1 x2 x3 in Ok y
let (let*) = Option.bind
let (let**) xr f =
match xr with
| Ok x -> f x
| Error x -> let Ok y | Error y = f x in Error y
let (let***) xor f =
let** xo = xor in
match xo with
| None -> Ok None
| Some x -> f x
let rec map_results f = function
| [] -> Ok []
| x::xs -> let** y = f x in let** ys = map_results f xs in Ok (y::ys)
let error_typ at t expect =
Error.error at "validation"
("type `" ^ Print.string_of_typ t ^ "` is not " ^ expect)
let ($>) it e = {e with it}
let unordered s1 s2 = not Set.(subset s1 s2 || subset s2 s1)
let il_result f = function
| Ok x -> f x
| Error x -> "FAIL(" ^ f x ^ ")"
let il_opt_result f = function
| Ok (Some x) -> f x
| Ok None -> "?"
| Error _ -> "FAIL"
let of_bool_exp = function
| BoolE b -> Some b
| _ -> None
let of_num_exp = function
| NumE n -> Some n
| _ -> None
let to_bool_exp b = BoolE b
let to_num_exp n = NumE n
let as_opt_exp e =
match e.it with
| OptE eo -> eo
| _ -> failwith "as_opt_exp"
let as_list_exp e =
match e.it with
| ListE es -> es
| _ -> failwith "as_list_exp"
(* Matching Lists *)
let _match_opt match_x env s xo1 xo2 : subst option result =
match xo1, xo2 with
| None, None -> Ok (Some s)
| Some x1, Some x2 -> match_x env s x1 x2
| _, _ -> Error None
let rec match_list match_x env s xs1 xs2 : subst option result =
match xs1, xs2 with
| [], [] -> Ok (Some s)
| x1::xs1', x2::xs2' ->
let*** s' = match_x env s x1 x2 in
match_list match_x env (Subst.union s s') xs1' xs2'
| _, _ -> Error None
let equiv_list equiv_x env xs1 xs2 =
List.length xs1 = List.length xs2 && List.for_all2 (equiv_x env) xs1 xs2
(* Type Reduction (weak-head) *)
let rec reduce_typ env t : typ result =
Debug.(log_if "il.reduce_typ" (t.it <> NumT `NatT)
(fun _ -> fmt "%s" (il_typ t))
(fun r -> fmt "%s" (il_result il_typ r))
) @@ fun _ ->
match t.it with
| VarT (id, as_) ->
let** as' = map_results (static reduce_arg env) as_ in
let** dto = reduce_typ_app env id as' (Env.find_opt_typ env id) t.at in
(match dto with
| Some {it = AliasT t'; _} -> reduce_typ env t'
| _ -> Ok (VarT (id, as') $ t.at)
)
| _ -> Ok t
and reduce_typdef env t : deftyp result =
let** t' = reduce_typ env t in
match t'.it with
| VarT (id, as_) ->
let** dto = reduce_typ_app env id as_ (Env.find_opt_typ env id) t'.at in
(match dto with
| Some dt -> Ok dt
| None -> Ok (AliasT t $ t'.at)
)
| _ -> Ok (AliasT t $ t'.at)
and reduce_typ_app env id as_ tdo at : deftyp option result =
Debug.(log "il.reduce_typ_app"
(fun _ -> fmt "%s(%s)" id.it (il_args as_))
(fun r -> fmt "%s" (il_opt_result il_deftyp r))
) @@ fun _ ->
match tdo with
| None -> Ok None (* id is a type parameter *)
| Some (_ps, []) -> if !assume_coherent_matches then Ok None else Error None
| Some (ps, {it = InstD (_ps, as', dt); _}::insts') ->
match match_list (static3 match_arg) env Subst.empty as_ as' with
| Ok (Some s) -> Ok (Some (Subst.subst_deftyp s dt))
| Error _ -> reduce_typ_app env id as_ (Some (ps, insts')) at
| Ok None ->
if not !assume_coherent_matches then Ok None else
match reduce_typ_app env id as_ (Some (ps, insts')) at with
| Error _ -> Ok None
| ok -> ok
and reduce_typ_ok env t =
match reduce_typ env t with
| Ok t -> t
| Error _ -> error_typ t.at t "defined"
and reduce_typdef_ok env t =
match reduce_typdef env t with
| Ok dt -> dt
| Error _ -> error_typ t.at t "defined"
and as_tup_typ env t at : (id * typ) list =
match (reduce_typ_ok env t).it with
| TupT xts -> xts
| _ -> error_typ at t "a tuple"
and as_iter_typ env t at : typ * iter =
match (reduce_typ_ok env t).it with
| IterT (t1, it) -> t1, it
| _ -> error_typ at t "an iteration"
and as_struct_typ env t at : typfield list =
match (reduce_typdef_ok env t).it with
| StructT tfs -> tfs
| _ -> error_typ at t "a record"
and as_variant_typ env t at : typcase list =
match (reduce_typdef_ok env t).it with
| VariantT tcs -> tcs
| _ -> error_typ at t "a variant"
and find_typfield t tfs atom at =
match List.find_opt (fun (atom', _, _) -> Eq.eq_atom atom' atom) tfs with
| Some (_, x, _) -> x
| None ->
error_typ at t ("a record with field `" ^ Print.string_of_atom atom ^ "`")
and find_typcase t tcs op at =
match List.find_opt (fun (op', _, _) -> Eq.eq_mixop op' op) tcs with
| Some (_, x, _) -> x
| None ->
error_typ at t ("a variant with case `" ^ Print.string_of_mixop op ^ "`")
(* Expression Reduction *)
and is_normal_arg static env a = (* only for assertions *)
match reduce_arg static env a with
| Ok a' | Error a' -> Eq.eq_arg a a'
and is_normal_exp static env e = (* only for assertions *)
match reduce_exp static env e with
| Ok e' | Error e' -> Eq.eq_exp e e'
and is_head_normal_exp e =
match e.it with
| BoolE _ | NumE _ | TextE _
| OptE _ | ListE _ | TupE _ | CaseE _ | StrE _ -> true
| SubE (e, _, _) -> is_head_normal_exp e
| _ -> false
and is_value_exp e =
match e.it with
| BoolE _ | NumE _ | TextE _ -> true
| ListE es | TupE es -> List.for_all is_value_exp es
| OptE None -> true
| OptE (Some e) | CaseE (_, e, Checked) | SubE (e, _, _) -> is_value_exp e
| StrE (efs, Checked) -> List.for_all (fun (_, e) -> is_value_exp e) efs
| _ -> false
and reduce_exp static env e : exp result =
Debug.(log ("il.reduce_exp" ^ if static then " static" else "")
(fun _ -> fmt "%s" (il_exp e))
(fun e' -> fmt "%s" (il_result il_exp e'))
) @@ fun _ ->
match e.it with
| VarE _ | BoolE _ | NumE _ | TextE _ -> Ok e
| UnE (op, ot, e1) ->
let** e1' = reduce_exp static env e1 in
(match op, e1'.it with
| #Bool.unop as op', BoolE b1 -> Ok (BoolE (Bool.un op' b1) $> e)
| #Num.unop as op', NumE n1 ->
(match Num.un op' n1 with
| Some n -> Ok (NumE n $> e)
| None -> Error (UnE (op, ot, e1') $> e)
)
| `NotOp, UnE (`NotOp, _, e11') -> Ok e11'
| `MinusOp, UnE (`MinusOp, _, e11') -> Ok e11'
| _ -> Ok (UnE (op, ot, e1') $> e)
)
| BinE (op, ot, e1, e2) ->
let** e1' = reduce_exp static env e1 in
let** e2' = reduce_exp static env e2 in
(match op with
| #Bool.binop as op' ->
(match Bool.bin_partial op' e1'.it e2'.it of_bool_exp to_bool_exp with
| None -> Ok (BinE (op, ot, e1', e2') $> e)
| Some e' -> Ok (e' $> e)
)
| #Num.binop as op' ->
(match Num.bin_partial op' e1'.it e2'.it of_num_exp to_num_exp with
| None -> Ok (BinE (op, ot, e1', e2') $> e)
| Some None -> Error (BinE (op, ot, e1', e2') $> e)
| Some (Some e') -> Ok (e' $> e)
)
)
| CmpE (op, ot, e1, e2) ->
let** e1' = reduce_exp static env e1 in
let** e2' = reduce_exp static env e2 in
(match op, e1'.it, e2'.it with
| `EqOp, _, _ when static || is_value_exp e1' && is_value_exp e2' ->
Ok (BoolE (Eq.eq_exp e1' e2') $> e)
| `NeOp, _, _ when is_value_exp e1' && is_value_exp e2' ->
Ok (BoolE (not (Eq.eq_exp e1' e2')) $> e)
| #Num.cmpop as op', NumE n1, NumE n2 ->
(match Num.cmp op' n1 n2 with
| Some b -> Ok (BoolE b $> e)
| None -> Ok (CmpE (op, ot, e1', e2') $> e)
)
| _ -> Ok (CmpE (op, ot, e1', e2') $> e)
)
| IdxE (e1, e2) ->
let** e1' = reduce_exp static env e1 in
let** e2' = reduce_exp static env e2 in
(match e1'.it, e2'.it with
| ListE es, NumE (`Nat i) when i < Z.of_int (List.length es) ->
Ok (List.nth es (Z.to_int i))
| _ -> Error (IdxE (e1', e2') $> e)
)
| SliceE (e1, e2, e3) ->
let** e1' = reduce_exp static env e1 in
let** e2' = reduce_exp static env e2 in
let** e3' = reduce_exp static env e3 in
(match e1'.it, e2'.it, e3'.it with
| ListE es, NumE (`Nat i), NumE (`Nat n) when Z.(i + n) < Z.of_int (List.length es) ->
Ok (ListE (Lib.List.take (Z.to_int n) (Lib.List.drop (Z.to_int i) es)) $> e)
| _ -> Error (SliceE (e1', e2', e3') $> e)
)
| UpdE (e1, p, e2) ->
let** e1' = reduce_exp static env e1 in
let** e2' = reduce_exp static env e2 in
reduce_path static env e1' p
(fun e' p' ->
if p'.it = RootP
then Ok e2'
else Ok (UpdE (e', p', e2') $> e')
)
| ExtE (e1, p, e2) ->
let** e1' = reduce_exp static env e1 in
let** e2' = reduce_exp static env e2 in
reduce_path static env e1' p
(fun e' p' ->
if p'.it = RootP
then reduce_exp static env (CatE (e', e2') $> e')
else Ok (ExtE (e', p', e2') $> e')
)
| StrE (efs, ch) ->
let tfs = as_struct_typ env e.note e.at in
let** ef_chs' = map_results (reduce_expfield static env e.note tfs ch) efs in
let efs', chs' = List.split ef_chs' in
let ch' = if List.for_all ((=) Checked) chs' then Checked else Unchecked in
Ok (StrE (efs', ch') $> e)
| DotE (e1, atom) ->
let** e1' = reduce_exp static env e1 in
(match e1'.it with
| StrE (efs, Checked) when static || is_value_exp e1' ->
Ok (snd (List.find (fun (atomN, _) -> Atom.eq atomN atom) efs))
| _ -> Ok (DotE (e1', atom) $> e)
)
| CompE (e1, e2) ->
(* TODO(4, rossberg): avoid overlap with CatE? *)
let** e1' = reduce_exp static env e1 in
let** e2' = reduce_exp static env e2 in
(match e1'.it, e2'.it with
| ListE es1, ListE es2 -> Ok (ListE (es1 @ es2) $> e)
| OptE None, OptE _ -> Ok e2'
| OptE _, OptE None -> Ok e1'
| StrE (efs1, Checked), StrE (efs2, Checked) ->
let tfs = as_struct_typ env e.note e.at in
let merge ((atom1, e1), (atom2, e2)) =
assert (Atom.eq atom1 atom2);
reduce_expfield static env e.note tfs Checked (atom1, CompE (e1, e2) $> e1)
in
assert (List.length efs1 = List.length efs2);
let** ef_chs' = map_results merge (List.combine efs1 efs2) in
let efs', _chs = List.split ef_chs' in
Ok (StrE (efs', Checked) $> e)
| _ -> Ok (CompE (e1', e2') $> e)
)
| MemE (e1, e2) ->
let** e1' = reduce_exp static env e1 in
let** e2' = reduce_exp static env e2 in
(match e2'.it with
| OptE None -> Ok (BoolE false $> e)
| OptE (Some e2') when static || is_value_exp e1' && is_value_exp e2' ->
Ok (BoolE (Eq.eq_exp e1' e2') $> e)
| ListE [] -> Ok (BoolE false $> e)
| ListE es2' when static || is_value_exp e1' && List.for_all is_value_exp es2' ->
Ok (BoolE (List.exists (Eq.eq_exp e1') es2') $> e)
| _ -> Ok (MemE (e1', e2') $> e)
)
| LenE e1 ->
let** e1' = reduce_exp static env e1 in
(match e1'.it with
| ListE es when static || is_value_exp e1' ->
Ok (NumE (`Nat (Z.of_int (List.length es))) $> e)
| _ -> Ok (LenE e1' $> e)
)
| TupE es ->
let** es' = map_results (reduce_exp static env) es in
Ok (TupE es' $> e)
| CallE (id, as_) ->
let _ps, _t, clauses = Env.find_def env id in
let** as' = map_results (reduce_arg static env) as_ in
(* Allow for parameters or uninterpreted functions *)
if clauses = [] then
Ok (CallE (id, as') $> e)
else
let** eo = reduce_exp_call static env id as' clauses e.at in
(match eo with
| None -> Ok (CallE (id, as') $> e)
| Some e -> reduce_exp static env e
)
| IterE (e1, iterexp) ->
let** e1' = reduce_exp static env e1 in
let** (iter', xes') as iterexp' = reduce_iterexp static env iterexp in
let ids, es' = List.split xes' in
if iter' <= List1 && es' = [] then
(* Lists with no iteration vars are invalid except as patterns *)
Ok (IterE (e1', iterexp') $> e)
else if not (List.for_all is_head_normal_exp es') then
Ok (IterE (e1', iterexp') $> e)
else
(match iter' with
| Opt ->
let eos' = List.map as_opt_exp es' in
if List.for_all Option.is_none eos' then
Ok (OptE None $> e)
else if List.for_all Option.is_some eos' then
let es1' = List.map Option.get eos' in
let s = List.fold_left2 Subst.add_varid Subst.empty ids es1' in
reduce_exp static env (Subst.subst_exp s e1')
else
Ok (IterE (e1', iterexp') $> e)
| List | List1 ->
let n = List.length (as_list_exp (List.hd es')) in
if iter' = List || n >= 1 then
let en = NumE (`Nat (Z.of_int n)) $$ e.at % (NumT `NatT $ e.at) in
reduce_exp static env (IterE (e1', (ListN (en, None), xes')) $> e)
else
Error (IterE (e1', iterexp') $> e)
| ListN ({it = NumE (`Nat n'); _}, ido) ->
let ess' = List.map as_list_exp es' in
let ns = List.map List.length ess' in
let n = Z.to_int n' in
if List.for_all ((=) n) ns then
(ListE (List.init n (fun i ->
let esI' = List.map (fun es -> List.nth es i) ess' in
let s = List.fold_left2 Subst.add_varid Subst.empty ids esI' in
let s' =
Option.fold ido ~none:s ~some:(fun id ->
let en = NumE (`Nat (Z.of_int i)) $$ id.at % (NumT `NatT $ id.at) in
Subst.add_varid s id en
)
in Subst.subst_exp s' e1'
)) $> e) |> reduce_exp static env
else
Error (IterE (e1', iterexp') $> e)
| ListN _ ->
Ok (IterE (e1', iterexp') $> e)
)
| ProjE (e1, i) ->
let** e1' = reduce_exp static env e1 in
(match e1'.it with
| TupE es when static || is_value_exp e1' -> Ok (List.nth es i)
| _ -> Ok (ProjE (e1', i) $> e)
)
| UncaseE (e1, mixop) ->
let** e1' = reduce_exp static env e1 in
(match e1'.it with
| CaseE (mixop', e11', Checked) when Mixop.eq mixop mixop' -> Ok e11'
| CaseE (mixop', _, _) when not (Mixop.eq mixop mixop') ->
Error (UncaseE (e1', mixop) $> e)
| _ -> Ok (UncaseE (e1', mixop) $> e)
)
| OptE eo ->
(match eo with
| None -> Ok e
| Some e1 ->
let** e1' = reduce_exp static env e1 in
Ok (OptE (Some e1') $> e)
)
| TheE e1 ->
let** e1' = reduce_exp static env e1 in
(match e1'.it with
| OptE (Some e11) -> Ok e11
| OptE None -> Error (TheE e1' $> e)
| _ -> Ok (TheE e1' $> e)
)
| ListE es ->
let** es' = map_results (reduce_exp static env) es in
Ok (ListE es' $> e)
| LiftE e1 ->
let** e1' = reduce_exp static env e1 in
(match e1'.it with
| OptE None -> Ok (ListE [] $> e)
| OptE (Some e11') -> Ok (ListE [e11'] $> e)
| _ -> Ok (LiftE e1' $> e)
)
| CatE (e1, e2) ->
let** e1' = reduce_exp static env e1 in
let** e2' = reduce_exp static env e2 in
(match e1'.it, e2'.it with
| ListE es1, ListE es2 -> Ok (ListE (es1 @ es2) $> e)
| OptE None, OptE _ -> Ok e2'
| OptE _, OptE None -> Ok e1'
| OptE _, OptE _ -> Error (CatE (e1', e2') $> e)
| _ -> Ok (CatE (e1', e2') $> e)
)
| CaseE (op, e1, Unchecked) when not static ->
let tcs = as_variant_typ env e.note e.at in
let t, _qs, prems = find_typcase e.note tcs op e.at in
let** e1' = reduce_exp static env e1 in
let** so = match_exp_typ static env Subst.empty e1' t in
(match so with
| Some s ->
let** so' = reduce_prems env s prems in
Ok (CaseE (op, e1', if so' <> None then Checked else Unchecked) $> e)
| None ->
Ok (CaseE (op, e1', Unchecked) $> e)
)
| CaseE (op, e1, _ch) ->
let** e1' = reduce_exp static env e1 in
Ok (CaseE (op, e1', Checked) $> e)
| CvtE (e1, nt1, nt2) ->
let** e1' = reduce_exp static env e1 in
(match e1'.it with
| NumE n ->
(match Num.cvt nt2 n with
| Some n' -> Ok (NumE n' $> e)
| None -> Error (CvtE (e1', nt1, nt2) $> e)
)
| _ -> Ok (CvtE (e1', nt1, nt2) $> e)
)
| SubE (e1, t1, t2) ->
let** e1' = reduce_exp static env e1 in
let** t1' = reduce_typ env t1 in
let** t2' = reduce_typ env t2 in
if equiv_typ env t1' t2' then Ok e1' else
(match e1'.it with
| SubE (e11', t11', _t12') ->
reduce_exp static env (SubE (e11', t11', t2') $> e)
| TupE es' ->
let xts1 = as_tup_typ env t1' t1.at in
let xts2 = as_tup_typ env t2' t2.at in
let** _, _, res' =
List.fold_left2 (fun opt eI ((x1I, t1I), (x2I, t2I)) ->
let** s1, s2, res' = opt in
let t1I' = Subst.subst_typ s1 t1I in
let t2I' = Subst.subst_typ s2 t2I in
let s1' = Subst.add_varid s1 x1I eI in
let s2' = Subst.add_varid s2 x2I eI in
let** eI' =
reduce_exp static env (SubE (eI, t1I', t2I') $$ eI.at % t2I') in
Ok (s1', s2', eI'::res')
) (Ok (Subst.empty, Subst.empty, [])) es' (List.combine xts1 xts2)
in Ok (TupE (List.rev res') $> e)
| StrE (efs', _ch) ->
let tfs1 = as_struct_typ env t1' t1.at in
let tfs2 = as_struct_typ env t2' t2.at in
let efs'' =
List.map2 (fun (atomI, eI) ((atom1I, (t1I, _, _), _), (atom2I, (t2I, _, _), _)) ->
assert (Atom.eq atomI atom1I);
assert (Atom.eq atomI atom2I);
(atomI, SubE (eI, t1I, t2I) $$ eI.at % t2I)
) efs' (List.combine tfs1 tfs2)
in reduce_exp static env (StrE (efs'', Unchecked) $> e)
| CaseE (op, e11', _ch) ->
let tcs1 = as_variant_typ env t1' t1.at in
let tcs2 = as_variant_typ env t2' t2.at in
let t1', _qs, _prems = find_typcase t1' tcs1 op t1.at in
let t2', _qs, _prems = find_typcase t2' tcs2 op t2.at in
reduce_exp static env
(CaseE (op, SubE (e11', t1', t2') $$ e11'.at % t2', Unchecked) $> e)
| _ when is_head_normal_exp e1' ->
Ok {e1' with note = e.note}
| _ -> Ok (SubE (e1', t1', t2') $> e)
)
and reduce_iter static env iter : iter result =
match iter with
| ListN (e, ido) ->
let** e' = reduce_exp static env e in
Ok (ListN (e', ido))
| iter -> Ok iter
and reduce_iterexp static env (iter, xes) : iterexp result =
let** iter' = reduce_iter static env iter in
let** es' = map_results (reduce_exp static env) (List.map snd xes) in
Ok (iter', List.map2 (fun (xI, _) eI' -> xI, eI') xes es')
and reduce_expfield static env t tfs ch (atom, e) : (expfield * check) result =
match ch with
| Unchecked when not static ->
let _t, _qs, prems = find_typfield t tfs atom atom.at in
let** e' = reduce_exp static env e in
let** so = match_exp_typ static env Subst.empty e' t in
(match so with
| Some s ->
let** so' = reduce_prems env s prems in
Ok ((atom, e'), if so' <> None then Checked else Unchecked)
| None ->
Ok ((atom, e'), Unchecked)
)
| _ch ->
let** e' = reduce_exp static env e in
Ok ((atom, e'), Checked)
and reduce_path static env e p k : exp result =
match p.it with
| RootP -> k e p
| IdxP (p1, e1) ->
let** e1' = reduce_exp static env e1 in
let k' e' p1' =
match e'.it, e1'.it with
| ListE es, NumE (`Nat i)
when (static || is_value_exp e') && i < Z.of_int (List.length es) ->
let** es' =
map_results (fun (j, eJ) ->
if Z.of_int j = i then k eJ p1' else Ok eJ
) (List.mapi Pair.make es)
in
Ok (ListE es' $> e')
| ListE _es, NumE (`Nat _) ->
let** e'' = k e' (IdxP (p1', e1') $> p) in
Error e''
| _ ->
k e' (IdxP (p1', e1') $> p)
in
reduce_path static env e p1 k'
| SliceP (p1, e1, e2) ->
let** e1' = reduce_exp static env e1 in
let** e2' = reduce_exp static env e2 in
let k' e' p1' =
match e'.it, e1'.it, e2'.it with
| ListE es, NumE (`Nat i), NumE (`Nat n) when Z.(i + n) < Z.of_int (List.length es) ->
let e1' = ListE Lib.List.(take (Z.to_int i) es) $> e' in
let e2' = ListE Lib.List.(take (Z.to_int n) (drop (Z.to_int i) es)) $> e' in
let e3' = ListE Lib.List.(drop Z.(to_int (i + n)) es) $> e' in
let** e2'' = k e2' p1' in
reduce_exp static env (CatE (e1', CatE (e2'', e3') $> e') $> e')
| ListE _es, NumE (`Nat _), NumE (`Nat _) ->
let** e'' = k e' (SliceP (p1', e1', e2') $> p) in
Error e''
| _ ->
k e' (SliceP (p1', e1', e2') $> p)
in
reduce_path static env e p1 k'
| DotP (p1, atom) ->
let k' e' p1' =
match e'.it with
| StrE (efs, Checked) ->
let tfs = as_struct_typ env e'.note e'.at in
let** ef_chs' =
map_results (fun (atomI, eI) ->
if Eq.eq_atom atomI atom then
let** eI' = k eI p1' in
reduce_expfield static env e'.note tfs Unchecked (atomI, eI')
else Ok ((atomI, eI), Checked)
) efs
in
let efs', chs' = List.split ef_chs' in
let ch' = if List.for_all ((=) Checked) chs' then Checked else Unchecked in
Ok (StrE (efs', ch') $> e')
| _ ->
k e' (DotP (p1', atom) $> p)
in
reduce_path static env e p1 k'
and reduce_arg static env a : arg result =
Debug.(log "il.reduce_arg"
(fun _ -> fmt "%s" (il_arg a))
(fun a' -> fmt "%s" (il_result il_arg a'))
) @@ fun _ ->
match a.it with
| ExpA e -> let** e' = reduce_exp static env e in Ok (ExpA e' $ a.at)
| TypA _t -> Ok a (* types are reduced on demand *)
| DefA _id -> Ok a
| GramA _g -> Ok a
and reduce_exp_call static env id args clauses at : exp option result =
match clauses with
| [] ->
Debug.(log "il.reduce_exp_call"
(fun _ -> fmt "$%s%s / -" id.it (il_args args))
(fun r -> fmt "%s" (il_opt_result il_exp r))
) @@ fun _ ->
Error None
| {it = DefD (_ps, args', e, prems); _}::clauses' ->
Debug.(log "il.reduce_exp_call"
(fun _ -> fmt "$%s%s / $%s%s" id.it (il_args args) id.it (il_args args'))
(fun r -> fmt "%s" (il_opt_result il_exp r))
) @@ fun _ ->
assert (List.for_all (is_normal_arg static env) args);
match match_list (match_arg static) env Subst.empty args args' with
| Error _ -> reduce_exp_call static env id args clauses' at
| Ok None ->
if not !assume_coherent_matches then Ok None else
(match reduce_exp_call static env id args clauses' at with
| Error _ -> Ok None
| ok -> ok
)
| Ok (Some s) ->
match reduce_prems env s prems with
| Ok (Some _s') -> Ok (Some (Subst.subst_exp s e))
| Error _ -> reduce_exp_call static env id args clauses' at
| Ok None ->
if not !assume_coherent_matches then Ok None else
match reduce_exp_call static env id args clauses' at with
| Error _ -> Ok None
| ok -> ok
and reduce_prems env s prems : subst option result =
Debug.(log "il.reduce_prems"
(fun _ -> fmt "%s [%s]" (list il_prem prems) (il_subst s))
(fun r -> fmt "%s" (il_opt_result il_subst r))
) @@ fun _ ->
match prems with
| [] -> Ok (Some s)
| prem::prems ->
let** so = reduce_prem env (Subst.subst_prem s prem) in
match so with
| Some s' -> reduce_prems env (Subst.union s s') prems
| None -> Ok None
and reduce_prem env prem : subst option result =
match prem.it with
| RulePr _ -> Ok None
| IfPr e ->
let** e' = reduce_exp false env e in
(match e'.it with
| BoolE b -> if b then Ok (Some Subst.empty) else Error None
| _ -> Ok None
)
| ElsePr -> Ok (Some Subst.empty)
| LetPr (_qs, e1, e2) ->
let** e1' = reduce_exp false env e1 in
let** e2' = reduce_exp false env e2 in
match_exp false env Subst.empty e2' e1'
| IterPr (prem1, iterexp) ->
let** iter', xes' = reduce_iterexp false env iterexp in
(* Distinguish between let-defined variables, which flow outwards,
* and others, which are assumed to flow inwards. *)
let rec is_let_bound prem (x, _) =
match prem.it with
| LetPr (qs, _, _) -> Free.(Set.mem x.it (bound_quants qs).varid)
| IterPr (premI, (_iter1', xes1')) ->
let xes1_out, _ = List.partition (is_let_bound premI) xes1' in
List.exists (fun (_, e1) -> Free.(Set.mem x.it (free_exp e1).varid)) xes1_out
| _ -> false
in
let xes_out, xes_in = List.partition (is_let_bound prem) xes' in
let xs_out, es_out = List.split xes_out in
let xs_in, es_in = List.split xes_in in
if not (List.for_all is_head_normal_exp es_in) || iter' <= List1 && es_in = [] then
(* We don't know the number of iterations (yet): can't do anything. *)
Ok None
else
(match iter' with
| Opt ->
(* Iterationen values es_in are in hnf, so got to be options. *)
let eos_in = List.map as_opt_exp es_in in
if List.for_all Option.is_none eos_in then
(* Iterating over empty options: nothing to do. *)
Ok (Some Subst.empty)
else if List.for_all Option.is_some eos_in then
(* All iteration variables are non-empty: reduce body. *)
let es1_in = List.map Option.get eos_in in
(* s substitutes in-bound iteration variables with corresponding
* values. *)
let s = List.fold_left2 Subst.add_varid Subst.empty xs_in es1_in in
let*** s' = reduce_prem env (Subst.subst_prem s prem1) in
(* Body is true: now reverse-match out-bound iteration values
* against iteration sources. *)
List.fold_left (fun r (xI, eI) ->
let*** s = r in
let tI = match eI.note.it with IterT (tI, _) -> tI | _ -> assert false in
match_exp' false env s (OptE (Some (Subst.subst_exp s' (VarE xI $$ xI.at % tI))) $> eI) eI
) (Ok (Some Subst.empty)) xes_out
else
(* Inconsistent arity of iteration values. *)
Error None
| List | List1 ->
(* Unspecified iteration count: get length from (first) iteration value
* and start over; es_in are in hnf, so got to be lists. *)
let n = List.length (as_list_exp (List.hd es_in)) in
if iter' = List || n >= 1 then
let en = NumE (`Nat (Z.of_int n)) $$ prem.at % (NumT `NatT $ prem.at) in
reduce_prem env (IterPr (prem1, (ListN (en, None), xes')) $> prem)
else
(* List is empty although it is List1: inconsistency. *)
Error None
| ListN ({it = NumE (`Nat n'); _}, xo) ->
(* Iterationen values es_in are in hnf, so got to be lists. *)
let ess_in = List.map as_list_exp es_in in
let ns = List.map List.length ess_in in
let n = Z.to_int n' in
if List.for_all ((=) n) ns then
(* All in-bound lists have the expected length: reduce body,
* once for each tuple of values from the iterated lists. *)
let** rs = List.init n (fun i ->
let esI_in = List.map (fun es -> List.nth es i) ess_in in
(* s substitutes in-bound iteration variables with corresponding
* values for this respective iteration. *)
let s = List.fold_left2 Subst.add_varid Subst.empty xs_in esI_in in
(* Add iteration counter variable if used. *)
let s' =
Option.fold xo ~none:s ~some:(fun x ->
let en = NumE (`Nat (Z.of_int i)) $$ x.at % (NumT `NatT $ x.at) in
Subst.add_varid s x en
)
in
reduce_prem env (Subst.subst_prem s' prem1)
) |> map_results Fun.id
in
if List.mem None rs then Ok None else
(* Body was true in every iteration: now reverse-match out-bound
* iteration variables against iteration sources. *)
let ss = List.map Option.get rs in
(* Aggregate the out-lists for each out-bound variable. *)
let es_out' =
List.map2 (fun xI eI ->
let tI = match eI.note.it with IterT (tI, _) -> tI | _ -> assert false in
let esI = List.map (fun sJ ->
Subst.subst_exp sJ (VarE xI $$ xI.at % tI)
) ss
in ListE esI $> eI
) xs_out es_out
in
(* Reverse-match out-bound list values against iteration sources. *)
match_list (match_exp false) env Subst.empty es_out' es_out
else
(* Inconsistent list lengths: can't perform mapping.
* (This is a stuck computation, i.e., undefined.) *)
Error None
| ListN _ -> Ok None
)
(* Matching *)
(* Iteration *)
and match_iter static env s iter1 iter2 : subst option result =
match iter1, iter2 with
| Opt, Opt -> Ok (Some s)
| List, List -> Ok (Some s)
| List1, List1 -> Ok (Some s)
| ListN (e1, _ido1), ListN (e2, _ido2) -> match_exp static env s e1 e2
| (Opt | List1 | ListN _), List -> Ok (Some s)
| _, _ -> Error None
(* Types *)
and match_typ env s t1 t2 : subst option result =
Debug.(log "il.match_typ"
(fun _ -> fmt "%s / %s" (il_typ t1) (il_typ (Subst.subst_typ s t2)))
(fun r -> fmt "%s" (il_result (opt il_subst) r))
) @@ fun _ ->
match t1.it, t2.it with
| _, VarT (id, []) when Subst.mem_typid s id ->
match_typ env s t1 (Subst.subst_typ s t2)
| _, VarT (id, []) when not (Map.mem id.it env.typs) ->
(* An unbound type is treated as a pattern variable *)
Ok (Some (Subst.add_typid s id t1))
| VarT (id1, args1), VarT (id2, args2) when id1.it = id2.it ->
(* Optimization for the common case where args are absent or equivalent. *)
(match match_list (static3 match_arg) env s args1 args2 with
| Ok so -> Ok so
| Error _ ->
(* If that fails, fall back to reduction. *)
let** t1' = reduce_typ env t1 in
let** t2' = reduce_typ env t2 in
if Eq.(eq_typ t1 t1' && eq_typ t2 t2') then Ok None else
match_typ env s t1' t2'
)
| VarT _, _ ->
let** t1' = reduce_typ env t1 in
if Eq.eq_typ t1 t1' then Ok None else
match_typ env s t1' t2
| _, VarT _ ->
let** t2' = reduce_typ env t2 in
if Eq.eq_typ t2 t2' then Ok None else
match_typ env s t1 t2'
| TupT xts1, TupT xts2 ->
match_list match_typbind env s xts1 xts2
| IterT (t11, iter1), IterT (t21, iter2) ->
let*** s' = match_typ env s t11 t21 in
static3 match_iter env s' iter1 iter2
| _, _ -> Ok None
and match_typbind env s (x1, t1) (x2, t2) =
let s' = Subst.add_varid s x2 (VarE x1 $$ x1.at % t1) in
match_typ env s' t1 (Subst.subst_typ s t2)
(* Expressions *)
and match_exp_typ static env s e t : subst option result =
Debug.(log "il.match_exp_typ"
(fun _ -> fmt "%s / %s [%s]" (il_exp e) (il_typ t) (il_subst s))
(fun r -> fmt "%s" (il_result (opt il_subst) r))
) @@ fun _ ->
let** e' = reduce_exp static env e in
let** t' = reduce_typ env t in
match e'.it, t'.it with
| TupE es, TupT xts when List.length es = List.length xts ->
Ok (Some (List.fold_left (fun s (e, (x, _)) ->
Subst.add_varid s x (Subst.subst_exp s e)
) s (List.combine es xts)))
| _, _ -> Error None
and match_exp static env s e1 e2 : subst option result =
let** e1' = reduce_exp static env e1 in
match_exp' static env s e1' e2
and match_exp' static env s e1 e2 : subst option result =
Debug.(log "il.match_exp"
(fun _ -> fmt "%s : %s / %s" (il_exp e1) (il_typ e1.note) (il_exp (Subst.subst_exp s e2)))
(fun r -> fmt "%s" (il_result (opt il_subst) r))
) @@ fun _ ->
assert (is_normal_exp static env e1);
let** e2' = reduce_exp static env (Subst.subst_exp s e2) in
(* HACK around subtype elim pass introducing calls on LHS's *)
if Eq.eq_exp e1 e2 && (static || is_value_exp e1 && is_value_exp e2) then Ok (Some s) else
match e1.it, e2'.it with
| _, VarE id when Subst.mem_varid s id ->
(* A pattern variable already in the substitution is non-linear *)
if equiv_exp static env e1 (Subst.subst_exp s e2) then
Ok (Some s)
else
Error None
| _, VarE id ->
(* Treat as a fresh pattern variable. *)
let** e1' = reduce_exp static env (SubE (e1, e1.note, e2.note) $$ e1.at % e2.note) in
Ok (Some (Subst.add_varid s id e1'))
| BoolE b1, BoolE b2 when b1 = b2 -> Ok (Some s)
| NumE n1, NumE n2 when n1 = n2 -> Ok (Some s)
| TextE s1, TextE s2 when s1 = s2 -> Ok (Some s)
| NumE n1, UnE (`PlusOp, _, e21) when not (Num.is_neg n1) ->
match_exp static env s e1 e21
| NumE n1, UnE (`MinusOp, _, e21) when Num.is_neg n1 ->
let** e1' = reduce_exp static env (NumE (Num.abs n1) $> e1) in
match_exp static env s e1' e21
| NumE n1, CvtE (e21, nt1, _nt2) ->
(match Num.cvt nt1 n1 with
| Some n1' -> match_exp static env s (NumE n1' $> e1) e21
| None -> Error None
)
(*
| UnE (op1, _, e11), UnE (op2, _, e21) when op1 = op2 -> match_exp' env s e11 e21
| BinE (e11, op1, e12), BinE (e21, op2, e22) when op1 = op2 ->
let** s' = match_exp' env s e11 e21 in match_exp' env s' e12 e22
| CmpE (e11, op1, e12), CmpE (e21, op2, e22) when op1 = op2 ->
let** s' = match_exp' env s e11 e21 in match_exp' env s' e12 e22
| (EpsE | SeqE []), (EpsE | SeqE []) -> Some s
*)
| ListE es1, ListE es2
| TupE es1, TupE es2 -> match_list (match_exp' static) env s es1 es2
| _, TupE es2 ->
let es1 = eta_tup_exp env e1 in
match_list (match_exp' static) env s es1 es2
| ListE es1, CatE ({it = ListE es21; _} as e21, e22)
when List.length es21 <= List.length es1 ->
let es11, es12 = Lib.List.split (List.length es21) es1 in
let*** s' = match_exp' static env s (ListE es11 $> e1) e21 in
match_exp' static env s' (ListE es12 $> e1) e22
| ListE es1, CatE (e21, ({it = ListE es22; _} as e22))
when List.length es22 <= List.length es1 ->
let es11, es12 = Lib.List.split (List.length es22) es1 in
let*** s' = match_exp' static env s (ListE es11 $> e1) e21 in
match_exp' static env s' (ListE es12 $> e1) e22
(*
| IdxE (e11, e12), IdxE (e21, e22)
| CommaE (e11, e12), CommaE (e21, e22)
| CompE (e11, e12), CompE (e21, e22) ->
let** s' = match_exp' env s e11 e21 in match_exp' env s' e12 e22
| SliceE (e11, e12, e13), SliceE (e21, e22, e23) ->
let** s' = match_exp' env s e11 e21 in
let** s'' = match_exp' env s' e12 e22 in
match_exp' env s'' e13 e23
| UpdE (e11, p1, e12), UpdE (e21, p2, e22)
| ExtE (e11, p1, e12), ExtE (e21, p2, e22) ->
let** s' = match_exp' env s e11 e21 in
let** s'' = match_path env s' p1 p2 in
match_exp' env s'' e12 e22
*)
| StrE (efs1, Checked), StrE (efs2, _) ->
match_list (match_expfield static) env s efs1 efs2
(*
| DotE (e11, atom1), DotE (e21, atom2) when Eq.eq_atom atom1 atom2 ->
match_exp' env s e11 e21
| LenE e11, LenE e21 -> match_exp' env s e11 e21
*)
| CaseE (op1, _, _), CaseE (op2, e21, _) when Eq.eq_mixop op1 op2 ->
(* Beta-expand to allow unchecked e1 without losing checks *)
let** e11' = reduce_exp static env (UncaseE (e1, op2) $> e21) in
match_exp' static env s e11' e21
| _, CaseE (op2, e21, _)
when List.length (as_variant_typ env e2.note e2.at) = 1 ->
(* Beta-expand irrefutable case pattern *)
let** e11' = reduce_exp static env (UncaseE (e1, op2) $> e21) in
match_exp' static env s e11' e21
(*
| CallE (id1, args1), CallE (id2, args2) when id1.it = id2.it ->
match_list (match_arg static) env s args1 args2
*)
| _, UncaseE (e21, mixop) ->
let** e1' =
reduce_exp static env (CaseE (mixop, e1, Unchecked) $$ e1.at % e21.note) in
match_exp' static env s e1' e21
| _, ProjE (e21, 0) -> (* only valid on unary tuples! *)
match_exp' static env s (TupE [e1] $$ e1.at % e21.note) e21
(*
| IterE (e11, iter1), IterE (e21, iter2) ->
let** s' = match_exp' env s e11 e21 in
match_iterexp env s' iter1 iter2
| _, IterE (e21, iter2) ->
let e11, iter1 = eta_iter_exp env e1 in
let** s' = match_exp' env s e11 e21 in
match_iterexp env s' iter1 iter2
*)
| OptE None, IterE (_e21, (Opt, xes)) ->
List.fold_left (fun r (_xI, eI) ->
let*** s = r in match_exp' static env s e1 eI
) (Ok (Some s)) xes
| OptE (Some e11), IterE (e21, (Opt, xes)) ->
let*** s' = match_exp' static env s e11 e21 in
let*** s'' =
List.fold_left (fun r (xI, exI) ->
let*** s = r in
let tI = match exI.note.it with IterT (tI, _) -> tI | _ -> assert false in
match_exp' static env s (OptE (Some (Subst.subst_exp s' (VarE xI $$ exI.at % tI))) $> e2) exI
) (Ok (Some (List.fold_left Subst.remove_varid s (List.map fst xes)))) xes
in Ok (Some (Subst.union s'' s)) (* re-add possibly locally shadowed bindings *)
| ListE _es1, IterE (e21, (List, xes)) ->
let en = VarE ("_" $ e2.at) $$ e2.at % (NumT `NatT $ e2.at) in
match_exp' static env s e1 (IterE (e21, (ListN (en, None), xes)) $> e2)
| ListE es1, IterE (e21, (List1, xes)) ->
if es1 = [] then Error None else
let en = VarE ("_" $ e2.at) $$ e2.at % (NumT `NatT $ e2.at) in
match_exp' static env s e1 (IterE (e21, (ListN (en, None), xes)) $> e2)
| ListE es1, IterE (e21, (ListN (en, id_opt), xes)) ->
let en' = NumE (`Nat (Z.of_int (List.length es1))) $$ e1.at % (NumT `NatT $ e1.at) in
let*** s' = match_exp' static env s en' en in
let s'' = List.fold_left Subst.remove_varid s' (List.map fst xes) in (* local subst *)
(* match each list element against iteration body for corresponding subst *)
let** sos =
map_results (fun (j, e1J) ->
let s''' =
match id_opt with
| None -> s''
| Some xJ ->
Subst.add_varid s'' xJ
(NumE (`Nat (Z.of_int j)) $$ e1.at % (NumT `NatT $ e1.at))
in match_exp' static env s''' e1J (Subst.subst_exp s''' e21)
) (List.mapi Pair.make es1)
in
let*** ss = Ok (Lib.List.flatten_opt sos) in
(* now project list for each iteration variable and match against rhs's *)
let xs, exs = List.split xes in
let*** s''' =
match_list (fun env s xI exI ->
let tI = match exI.note.it with IterT (tI, _) -> tI | _ -> assert false in
let eI = ListE (List.map (fun sJ -> Subst.subst_exp sJ (VarE xI $$ exI.at % tI)) ss) $> e2 in
match_exp' static env s eI exI
) env s' xs exs
in Ok (Some (Subst.union s''' s)) (* re-add possibly locally shadowed bindings *)
| _, IterE (e21, iter2) ->
let e11, iter1 = eta_iter_exp env e1 in
let** e11' = reduce_exp static env e11 in
let** iter1' = reduce_iterexp static env iter1 in
let*** s' = match_exp' static env s e11' e21 in
match_iterexp static env s' iter1' iter2
| SubE (e11, t11, _t12), SubE (e21, t21, _t22) when sub_typ env t11 t21 ->
let** e11' = reduce_exp static env (SubE (e11, t11, t21) $> e21) in
match_exp' static env s e11' e21
| SubE (_e11, t11, _t12), SubE (_e21, t21, _t22) when disj_typ env t11 t21 ->
Error None
| _, SubE (e21, t21, _t22) ->
if sub_typ env e1.note t21 then
let** e1' = reduce_exp static env (SubE (e1, e1.note, t21) $> e21) in
match_exp' static env s e1' e21
else if is_head_normal_exp e1 then
let** t21' = reduce_typ env t21 in
let** b =
match e1.it, t21'.it with
| BoolE _, BoolT
| NumE _, NumT _
| TextE _, TextT -> Ok true
| CaseE (op, _, _), VarT _ ->
let** dt = reduce_typdef env t21 in
(match dt.it with
| VariantT tcs ->
(* Assumes that we only have shallow subtyping. *)
Ok (List.exists (fun (opN, _, _) -> Eq.eq_mixop opN op) tcs)
| _ -> Error false
)
| VarE id1, _ ->
let t1 = Env.find_var env id1 in
let** t1' = reduce_typ env t1 in
Ok (sub_typ env t1' t21)
| _, _ -> Error false
in
if b
then match_exp' static env s {e1 with note = t21} e21
else Ok None
else Ok None
| _, _ when is_head_normal_exp e1 -> Error None
| _, _ -> Ok None
and match_expfield static env s (atom1, e1) (atom2, e2) =
assert (Eq.eq_atom atom1 atom2);
match_exp' static env s e1 (Subst.subst_exp s e2)
and match_iterexp static env s (iter1, _ids1) (iter2, _ids2) =
match_iter static env s iter1 iter2
and eta_tup_exp env e : exp list =
let xts = as_tup_typ env e.note e.at in
List.fold_left (fun (res', i, s) (xI, tI) ->
let eI' = ProjE (e, i) $$ e.at % Subst.subst_typ s tI in
let s' = Subst.add_varid s xI eI' in
(eI'::res', i + 1, s')
) ([], 0, Subst.empty) xts |> Lib.fst3 |> List.rev
and eta_iter_exp env e : exp * iterexp =
let t, it = as_iter_typ env e.note e.at in
match it with
| Opt -> TheE e $$ e.at % t, (Opt, [])
| List ->
let id = "_i_" $ e.at in
let len = LenE e $$ e.at % (NumT `NatT $ e.at) in
IdxE (e, VarE id $$ e.at % (NumT `NatT $ e.at)) $$ e.at % t,
(ListN (len, Some id), [])
| _ -> assert false
(* Grammars *)
and match_sym env s g1 g2 : subst option result =
Debug.(log "il.match_sym"
(fun _ -> fmt "%s / %s" (il_sym g1) (il_sym g2))
(fun r -> fmt "%s" (il_result (opt il_subst) r))
) @@ fun _ ->
match g1.it, g2.it with
| _, VarG (id, []) when Subst.mem_gramid s id ->
match_sym env s g1 (Subst.subst_sym s g2)
| _, VarG (id, []) when not (Map.mem id.it env.grams) ->
(* An unbound grammar is treated as a pattern variable *)
Ok (Some (Subst.add_gramid s id g1))
| VarG (id1, args1), VarG (id2, args2) when id1.it = id2.it ->
match_list (static3 match_arg) env s args1 args2
| IterG (g11, iter1), IterG (g21, iter2) ->
let*** s' = match_sym env s g11 g21 in
static3 match_iterexp env s' iter1 iter2
| _, _ -> Ok None
(* Parameters *)
and match_arg static env s a1 a2 : subst option result =
Debug.(log "il.match_arg"
(fun _ -> fmt "%s / %s" (il_arg a1) (il_arg a2))
(fun r -> fmt "%s" (il_result (opt il_subst) r))
) @@ fun _ ->
match a1.it, a2.it with
| ExpA e1, ExpA e2 -> match_exp static env s e1 e2
| TypA t1, TypA t2 -> match_typ env s t1 t2
| DefA id1, DefA id2 -> Ok (Some (Subst.add_defid s id1 id2))
| GramA g1, GramA g2 -> match_sym env s g1 g2
| _, _ -> assert false
(* Type Equivalence *)
and equiv_typ env t1 t2 =
Debug.(log "il.equiv_typ"
(fun _ -> fmt "%s == %s" (il_typ t1) (il_typ t2)) Bool.to_string
) @@ fun _ ->
match t1.it, t2.it with
| VarT (id1, as1), VarT (id2, as2) ->
id1.it = id2.it && equiv_list equiv_arg env as1 as2 ||
(* optimization *)
let t1' = reduce_typ_ok env t1 in
let t2' = reduce_typ_ok env t2 in
(* TODO(3, rossberg): be more expressive *)
(t1 <> t1' || t2 <> t2') && equiv_typ env t1' t2' ||
Eq.eq_deftyp (reduce_typdef_ok env t1') (reduce_typdef_ok env t2')
| VarT _, _ ->
let t1' = reduce_typ_ok env t1 in
t1 <> t1' && equiv_typ env t1' t2
| _, VarT _ ->
let t2' = reduce_typ_ok env t2 in
t2 <> t2' && equiv_typ env t1 t2'
| TupT ets1, TupT ets2 -> equiv_tup env Subst.empty ets1 ets2
| IterT (t11, iter1), IterT (t21, iter2) ->
equiv_typ env t11 t21 && equiv_iter env iter1 iter2
| _, _ ->
t1.it = t2.it
and equiv_tup env s xts1 xts2 =
match xts1, xts2 with
| (x1, t1)::xts1', (x2, t2)::xts2' ->
equiv_typ env t1 (Subst.subst_typ s t2) &&
equiv_tup env (Subst.add_varid s x2 (VarE x1 $$ x1.at % t1)) xts1' xts2'
| _, _ -> xts1 = xts2
and equiv_iter env iter1 iter2 =
match iter1, iter2 with
| ListN (e1, ido1), ListN (e2, ido2) ->
equiv_exp true env e1 e2 && Option.equal (fun id1 id2 -> id1.it = id2.it) ido1 ido2
| _, _ -> iter1 = iter2
(*
and equiv_prem env pr1 pr2 =
match pr1.it, pr2.it with
| RulePr (id1, mixop1, e1), RulePr (id2, mixop2, e2) ->
id1.it = id2.it && Eq.eq_mixop mixop1 mixop2 && equiv_exp env e1 e2
| IfPr e1, IfPr e2 -> equiv_exp env e1 e2
| LetPr (_qs1, e11, e12), LetPr (_qs2, e21, e22) ->
equiv_exp env e11 e21 && equiv_exp env e12 e22
| IterPr (pr11, iter1), IterPr (pr21, iter2) ->
equiv_prem env pr11 pr21 && equiv_iter env (fst iter1) (fst iter2)
| pr1', pr2' -> pr1' = pr2'
*)
and equiv_exp static env e1 e2 =
Debug.(log "il.equiv_exp"
(fun _ -> fmt "%s == %s" (il_exp e1) (il_exp e2)) Bool.to_string
) @@ fun _ ->
(* TODO(3, rossberg): this does not reduce inner type arguments *)
match reduce_exp static env e1, reduce_exp static env e2 with
| Ok e1', Ok e2' -> Eq.eq_exp e1' e2'
| (Ok e1' | Error e1'), (Ok e2' | Error e2') when static -> Eq.eq_exp e1' e2'
| Error _, _ ->
Error.error e1.at "validation"
"expression failed to evaluate during pattern-matching"
| _, Error _ ->
Error.error e2.at "validation"
"expression failed to evaluate during pattern-matching"
and equiv_sym _env g1 g2 =
Debug.(log "il.equiv_sym"
(fun _ -> fmt "%s == %s" (il_sym g1) (il_sym g2)) Bool.to_string
) @@ fun _ ->
Eq.eq_sym g1 g2
and equiv_arg env a1 a2 =
Debug.(log "il.equiv_arg"
(fun _ -> fmt "%s == %s" (il_arg a1) (il_arg a2)) Bool.to_string
) @@ fun _ ->
match a1.it, a2.it with
| ExpA e1, ExpA e2 -> equiv_exp true env e1 e2
| TypA t1, TypA t2 -> equiv_typ env t1 t2
| DefA id1, DefA id2 -> id1.it = id2.it
| GramA g1, GramA g2 -> equiv_sym env g1 g2
| _, _ -> false
and equiv_functyp env (ps1, t1) (ps2, t2) =
List.length ps1 = List.length ps2 &&
match equiv_params env ps1 ps2 with
| None -> false
| Some s -> equiv_typ env t1 (Subst.subst_typ s t2)
and equiv_params env ps1 ps2 =
List.fold_left2 (fun s_opt p1 p2 ->
let* s = s_opt in
match p1.it, (Subst.subst_param s p2).it with
| ExpP (id1, t1), ExpP (id2, t2) ->
if not (equiv_typ env t1 t2) then None else
Some (Subst.add_varid s id2 (VarE id1 $$ p1.at % t1))
| TypP _, TypP _ -> Some s
| DefP (id1, ps1, t1), DefP (id2, ps2, t2) ->
if not (equiv_functyp env (ps1, t1) (ps2, t2)) then None else
Some (Subst.add_defid s id2 id1)
| GramP (id1, ps1, t1), GramP (id2, ps2, t2) ->
if not (equiv_functyp env (ps1, t1) (ps2, t2)) then None else
Some (Subst.add_gramid s id2 (VarG (id1, []) $ p1.at))
| _, _ -> assert false
) (Some Subst.empty) ps1 ps2
(* Subtyping *)
and sub_prems _env prems1 prems2 =
prems2 = [] ||
List.length prems1 = List.length prems2 &&
List.for_all2 Eq.eq_prem prems1 prems2
and sub_typ env t1 t2 = sub_typ' env [] t1 t2
and sub_typ' env assum t1 t2 =
Debug.(log "il.sub_typ"
(fun _ -> fmt "%s <: %s" (il_typ t1) (il_typ t2)) Bool.to_string
) @@ fun _ ->
equiv_typ env t1 t2 ||
List.exists (fun (t1', t2') -> Eq.eq_typ t1 t1' && Eq.eq_typ t2 t2') assum ||
let t1' = reduce_typ_ok env t1 in
let t2' = reduce_typ_ok env t2 in
let assum' = (t1, t2)::assum in
match t1'.it, t2'.it with
(*| NumT nt1, NumT nt2 -> Num.sub nt1 nt2*)
| TupT ets1, TupT ets2 -> sub_tup env assum' Subst.empty ets1 ets2
| VarT _, VarT _ ->
(match (reduce_typdef_ok env t1').it, (reduce_typdef_ok env t2').it with
| StructT tfs1, StructT tfs2 ->
List.for_all (fun (atom, (t21, _qs2, prems2), _) ->
match find_field tfs1 atom with
| Some (t11, _qs1, prems1) ->
sub_typ' env assum' t11 t21 && sub_prems env prems1 prems2
| None -> false
) tfs2
| VariantT tcs1, VariantT tcs2 ->
List.for_all (fun (mixop, (t11, _qs1, prems1), _) ->
match find_case tcs2 mixop with
| Some (t21, _qs2, prems2) ->
sub_typ' env assum' t11 t21 && sub_prems env prems1 prems2
| None -> false
) tcs1
| _, _ -> false
)
| IterT (t11, it1), IterT (t21, it2) ->
sub_typ' env assum' t11 t21 && Eq.eq_iter it1 it2
| _, _ ->
false
and sub_tup env assum s xts1 xts2 =
match xts1, xts2 with
| (x1, t1)::xts1', (x2, t2)::xts2' ->
sub_typ' env assum t1 (Subst.subst_typ s t2) &&
sub_tup env assum (Subst.add_varid s x2 (VarE x1 $$ x1.at % t1)) xts1' xts2'
| _, _ -> xts1 = xts2
and find_field tfs atom =
List.find_opt (fun (atom', _, _) -> Eq.eq_atom atom' atom) tfs |> Option.map Lib.snd3
and find_case tcs op =
List.find_opt (fun (op', _, _) -> Eq.eq_mixop op' op) tcs |> Option.map Lib.snd3
(* Type Disjointness *)
and disj_typ env t1 t2 =
Debug.(log "il.disj_typ"
(fun _ -> fmt "%s >< %s" (il_typ t1) (il_typ t2)) Bool.to_string
) @@ fun _ ->
match t1.it, t2.it with
| VarT _, VarT _ ->
(match (reduce_typdef_ok env t1).it, (reduce_typdef_ok env t2).it with
| StructT tfs1, StructT tfs2 ->
unordered (atoms tfs1) (atoms tfs2) ||
List.exists (fun (atom, (t2, _qs2, _prems2), _) ->
match find_field tfs1 atom with
| Some (t1, _qs1, _prems1) -> disj_typ env t1 t2
| None -> true
) tfs2
| VariantT tcs1, VariantT tcs2 ->
List.for_all (fun (mixop, (t1, _qs1, _prems1), _) ->
match find_case tcs2 mixop with
| Some (t2, _qs2, _prems2) -> disj_typ env t1 t2
| None -> true
) tcs1
| _, _ -> true
)
| VarT _, _ ->
let t1' = reduce_typ_ok env t1 in
t1 <> t1' && disj_typ env t1' t2
| _, VarT _ ->
let t2' = reduce_typ_ok env t2 in
t2 <> t2' && disj_typ env t1 t2'
| TupT ets1, TupT ets2 -> disj_tup env Subst.empty ets1 ets2
| IterT (t11, iter1), IterT (t21, iter2) ->
disj_typ env t11 t21 || not (Eq.eq_iter iter1 iter2)
| _, _ ->
not (equiv_typ env t1 t2)
and atoms xs = Set.of_list (List.map Print.string_of_atom (List.map Lib.fst3 xs))
and disj_tup env s xts1 xts2 =
match xts1, xts2 with
| (x1, t1)::xts1', (x2, t2)::xts2' ->
disj_typ env t1 (Subst.subst_typ s t2) ||
disj_tup env (Subst.add_varid s x2 (VarE x1 $$ x1.at % t1)) xts1' xts2'
| _, _ -> xts1 <> xts2
(* Export (since OCaml's signature match can't instantiate opt args) *)
let reduce_exp = reduce_exp false
let reduce_arg = reduce_arg false
let match_iter = match_iter false
let match_exp = match_exp false
let match_arg = match_arg false