blob: d8be4a396b1e688cdf496b282a5d9c9e5f27999b [file] [edit]
open Util
open Source
open El
open Xl
open Ast
(* Environment *)
module Set = Set.Make(String)
module Map = Map.Make(String)
type typ_def = (arg list * typ) list
type def_def = (arg list * exp * prem list) list
type gram_def = unit
type env = {vars : typ Map.t; typs : typ_def Map.t; defs : def_def Map.t; grams : gram_def Map.t}
type subst = Subst.t
(* Helpers *)
(* This exception indicates that a an application cannot be reduced because a pattern
* match cannot be decided.
* When assume_coherent_matches is set, that case is treated as a non-match.
*)
exception Irred
let assume_coherent_matches = ref true
let (let*) = Option.bind
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 (`DecOp, n)
(* Matching Lists *)
let rec match_list match_x env s xs1 xs2 : subst option =
match xs1, xs2 with
| [], [] -> 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'
| _, _ -> None
let match_nl_list match_x env s xs1 xs2 =
match_list match_x env s (Convert.filter_nl xs1) (Convert.filter_nl xs2)
let equiv_list equiv_x env xs1 xs2 =
List.length xs1 = List.length xs2 && List.for_all2 (equiv_x env) xs1 xs2
let equiv_nl_list equiv_x env xs1 xs2 =
equiv_list equiv_x env (El.Convert.filter_nl xs1) (El.Convert.filter_nl xs2)
let equiv_opt equiv_x env xo1 xo2 =
match xo1, xo2 with
| None, None -> true
| Some x1, Some x2 -> equiv_x env x1 x2
| _, _ -> false
let disj_list disj_x env xs1 xs2 =
List.length xs1 <> List.length xs2 || List.exists2 (disj_x env) xs1 xs2
(* Type Reduction (weak-head) *)
let rec reduce_typ env t : typ =
Debug.(log_if "el.reduce_typ" (t.it <> NumT `NatT)
(fun _ -> fmt "%s" (el_typ t))
(fun r -> fmt "%s" (el_typ r))
) @@ fun _ ->
match t.it with
| VarT (id, args) ->
let args' = List.map (reduce_arg env) args in
let id' = El.Convert.strip_var_suffix id in
if id'.it <> id.it && args = [] then reduce_typ env (El.Convert.typ_of_varid id') else
(match reduce_typ_app env id args' t.at (Map.find id'.it env.typs) with
| Some t' ->
(* TODO(2, rossberg): reenable?
if id'.it <> id.it then
Error.error id.at "syntax" "identifer suffix encountered during reduction";
*)
t'
| None -> VarT (id, args') $ t.at
)
| ParenT t1 -> reduce_typ env t1
| CaseT (dots1, ts, tcs, _dots2) ->
assert (dots1 = NoDots);
(* TODO(3, rossberg): unclosed case types are not checked early enough for this
assert (dots2 = NoDots);
*)
let tcs' = Convert.concat_map_nl_list (reduce_casetyp env) ts in
CaseT (NoDots, [], tcs' @ tcs, NoDots) $ t.at
| _ -> t
and reduce_casetyp env t : typcase nl_list =
match (reduce_typ env t).it with
| CaseT (NoDots, [], tcs, NoDots) -> tcs
| _ -> assert false
and reduce_typ_app env id args at = function
| [] ->
if !assume_coherent_matches then None else
let args = if args = [] then "" else
"(" ^ String.concat ", " (List.map Print.string_of_arg args) ^ ")" in
Error.error at "type"
("undefined instance of partial syntax type definition: `" ^ id.it ^ args ^ "`")
| (args', t)::insts' ->
Debug.(log "el.reduce_typ_app"
(fun _ -> fmt "%s(%s) =: %s(%s)" id.it (el_args args) id.it (el_args args'))
(fun r -> fmt "%s" (opt (Fun.const "!") r))
) @@ fun _ ->
(* HACK: check for forward reference to yet undefined type (should throw?) *)
if Eq.eq_typ t (VarT (id, args') $ id.at) then None else
match match_list match_arg env Subst.empty args args' with
| exception Irred ->
if not !assume_coherent_matches then None else
reduce_typ_app env id args at insts'
| None -> reduce_typ_app env id args at insts'
| Some s -> Some (reduce_typ env (Subst.subst_typ s t))
(* Expression Reduction *)
and is_head_normal_exp e =
match e.it with
| AtomE _ | BoolE _ | NumE _ | TextE _
| SeqE _ | TupE _ | InfixE _ | BrackE _ | StrE _ -> true
| _ -> false
and is_normal_exp e =
match e.it with
| AtomE _ | BoolE _ | NumE _ | TextE _ -> true
| SeqE es | TupE es -> List.for_all is_normal_exp es
| BrackE (_, e, _) -> is_normal_exp e
| InfixE (e1, _, e2) -> is_normal_exp e1 && is_normal_exp e2
| StrE efs -> Convert.forall_nl_list (fun (_, e) -> is_normal_exp e) efs
| _ -> false
and reduce_exp env e : exp =
Debug.(log "el.reduce_exp"
(fun _ -> fmt "%s" (el_exp e))
(fun r -> fmt "%s" (el_exp r))
) @@ fun _ ->
match e.it with
| VarE _ | AtomE _ | BoolE _ | TextE _ | SizeE _ -> e
| NumE (numop, n) -> NumE (numop, Num.narrow n) $ e.at
| CvtE (e1, nt) ->
let e1' = reduce_exp env e1 in
(match e1'.it with
| NumE (numop, n) ->
(match Num.cvt nt n with
| Some n' -> NumE (numop, n') $ e.at
| None -> e1'
)
| _ -> e1'
)
| UnE (op, e1) ->
let e1' = reduce_exp env e1 in
(match op, e1'.it with
| #Bool.unop as op', BoolE b1 -> BoolE (Bool.un op' b1) $ e.at
| #Num.unop as op', NumE (numop, n1) ->
(match Num.un op' n1 with
| Some n -> NumE (numop, n)
| None -> UnE (op, e1')
) $ e.at
| `NotOp, UnE (`NotOp, e11') -> e11'
| `MinusOp, UnE (`MinusOp, e11') -> e11'
| `PlusOp, _ -> e1'
| _ -> UnE (op, e1') $ e.at
)
| BinE (e1, op, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp 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 -> BinE (e1', op, e2')
| Some e' -> e'
)
| #Num.binop as op' ->
let e1'', e2'' =
match e1'.it, e2'.it with
| NumE (numop1, n1), NumE (numop2, n2) ->
let n1', n2' = Num.widen n1 n2 in
NumE (numop1, n1'), NumE (numop2, n2')
| _, _ -> e1'.it, e2'.it
in
(match Num.bin_partial op' e1'' e2'' of_num_exp to_num_exp with
| None -> BinE (e1', op, e2')
| Some e' -> e'
)
) $ e.at
| CmpE (e1, op, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
(match op, e1'.it, e2'.it with
| `EqOp, _, _ when Eq.eq_exp e1' e2' -> BoolE true
| `NeOp, _, _ when Eq.eq_exp e1' e2' -> BoolE false
| `EqOp, _, _ when is_normal_exp e1' && is_normal_exp e2' -> BoolE false
| `NeOp, _, _ when is_normal_exp e1' && is_normal_exp e2' -> BoolE true
| #Num.cmpop as op', NumE (_, n1), NumE (_, n2) ->
(match Num.cmp op' n1 n2 with
| Some b -> BoolE b
| None -> CmpE (e1', op, e2')
)
| _ -> CmpE (e1', op, e2')
) $ e.at
| EpsE -> SeqE [] $ e.at
| SeqE es -> SeqE (List.map (reduce_exp env) es) $ e.at
| ListE es -> SeqE (List.map (reduce_exp env) es) $ e.at
| IdxE (e1, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
(match e1'.it, e2'.it with
| SeqE es, NumE (_, `Nat i) when i < Z.of_int (List.length es) -> List.nth es (Z.to_int i)
| _ -> IdxE (e1', e2') $ e.at
)
| SliceE (e1, e2, e3) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
let e3' = reduce_exp env e3 in
(match e1'.it, e2'.it, e3'.it with
| SeqE es, NumE (_, `Nat i), NumE (_, `Nat n) when Z.(i + n) < Z.of_int (List.length es) ->
SeqE (Lib.List.take (Z.to_int n) (Lib.List.drop (Z.to_int i) es))
| _ -> SliceE (e1', e2', e3')
) $ e.at
| UpdE (e1, p, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
reduce_path env e1' p
(fun e' p' -> if p'.it = RootP then e2' else UpdE (e', p', e2') $ e.at)
| ExtE (e1, p, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
reduce_path env e1' p
(fun e' p' ->
if p'.it = RootP
then reduce_exp env (SeqE [e'; e2'] $ e.at)
else ExtE (e', p', e2') $ e.at
)
| StrE efs -> StrE (Convert.map_nl_list (reduce_expfield env) efs) $ e.at
| DotE (e1, atom) ->
let e1' = reduce_exp env e1 in
(match e1'.it with
| StrE efs ->
snd (Option.get (El.Convert.find_nl_list (fun (atomN, _) -> Atom.eq atomN atom) efs))
| _ -> DotE (e1', atom) $ e.at
)
| CommaE (e1, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
(match e2'.it with
| SeqE ({it = AtomE atom; _} :: es2') ->
let e21' = match es2' with [e21'] -> e21' | _ -> SeqE es2' $ e2.at in
reduce_exp env (CatE (e1', StrE [Elem (atom, e21')] $ e2.at) $ e.at)
| _ -> CommaE (e1', e2') $ e.at
)
| CatE (e1, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
(match e1'.it, e2'.it with
| SeqE es1, SeqE es2 -> SeqE (es1 @ es2)
| SeqE [], _ -> e2'.it
| _, SeqE [] -> e1'.it
| StrE efs1, StrE efs2 ->
let rec merge efs1 efs2 =
match efs1, efs2 with
| [], _ -> efs2
| _, [] -> efs1
| Nl::efs1', _ -> merge efs1' efs2
| _, Nl::efs2' -> merge efs1 efs2'
| Elem (atom1, e1) :: efs1', Elem (atom2, e2) :: efs2' ->
(* Assume that both lists are sorted in same order *)
if Atom.eq atom1 atom2 then
let e' = reduce_exp env (CatE (e1, e2) $ e.at) in
Elem (atom1, e') :: merge efs1' efs2'
else if El.Convert.exists_nl_list (fun (atom, _) -> Atom.eq atom atom2) efs1 then
Elem (atom1, e1) :: merge efs1' efs2
else
Elem (atom2, e2) :: merge efs1 efs2'
in StrE (merge efs1 efs2)
| _ -> CatE (e1', e2')
) $ e.at
| MemE (e1, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
(match e2'.it with
| SeqE [] -> BoolE false
| SeqE es2' when List.exists (Eq.eq_exp e1') es2' -> BoolE true
| SeqE es2' when is_normal_exp e1' && List.for_all is_normal_exp es2' -> BoolE false
| _ -> MemE (e1', e2')
) $ e.at
| LenE e1 ->
let e1' = reduce_exp env e1 in
(match e1'.it with
| SeqE es -> NumE (`DecOp, `Nat (Z.of_int (List.length es)))
| _ -> LenE e1'
) $ e.at
| ParenE e1 | ArithE e1 | TypE (e1, _) -> reduce_exp env e1
| TupE es -> TupE (List.map (reduce_exp env) es) $ e.at
| InfixE (e1, atom, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
InfixE (e1', atom, e2') $ e.at
| BrackE (atom1, e1, atom2) ->
let e1' = reduce_exp env e1 in
BrackE (atom1, e1', atom2) $ e.at
| CallE (id, args) ->
let args' = List.map (reduce_arg env) args in
let clauses = Map.find id.it env.defs in
(* Allow for uninterpreted functions *)
if not !assume_coherent_matches && clauses = [] then CallE (id, args') $ e.at else
(match reduce_exp_call env id args' e.at clauses with
| None -> CallE (id, args') $ e.at
| Some e -> e
)
| IterE (e1, iter) ->
let e1' = reduce_exp env e1 in
IterE (e1', iter) $ e.at (* TODO(2, rossberg): simplify? *)
| HoleE _ | FuseE _ | UnparenE _ | LatexE _ -> assert false
and reduce_expfield env (atom, e) : expfield = (atom, reduce_exp env e)
and reduce_path env e p f =
match p.it with
| RootP -> f e p
| IdxP (p1, e1) ->
let e1' = reduce_exp env e1 in
let f' e' p1' =
match e'.it, e1'.it with
| SeqE es, NumE (_, `Nat i) when i < Z.of_int (List.length es) ->
SeqE (List.mapi (fun j eJ -> if Z.of_int j = i then f eJ p1' else eJ) es) $ e'.at
| _ ->
f e' (IdxP (p1', e1') $ p.at)
in
reduce_path env e p1 f'
| SliceP (p1, e1, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
let f' e' p1' =
match e'.it, e1'.it, e2'.it with
| SeqE es, NumE (_, `Nat i), NumE (_, `Nat n) when Z.(i + n) < Z.of_int (List.length es) ->
let e1' = SeqE Lib.List.(take (Z.to_int i) es) $ e'.at in
let e2' = SeqE Lib.List.(take (Z.to_int n) (drop (Z.to_int i) es)) $ e'.at in
let e3' = SeqE Lib.List.(drop Z.(to_int (i + n)) es) $ e'.at in
reduce_exp env (SeqE [e1'; f e2' p1'; e3'] $ e'.at)
| _ ->
f e' (SliceP (p1', e1', e2') $ p.at)
in
reduce_path env e p1 f'
| DotP (p1, atom) ->
let f' e' p1' =
match e'.it with
| StrE efs ->
StrE (Convert.map_nl_list (fun (atomI, eI) ->
if atomI = atom then (atomI, f eI p1') else (atomI, eI)) efs) $ e'.at
| _ ->
f e' (DotP (p1', atom) $ p.at)
in
reduce_path env e p1 f'
and reduce_arg env a : arg =
match !(a.it) with
| ExpA e -> ref (ExpA (reduce_exp env e)) $ a.at
| TypA _t -> a (* types are reduced on demand *)
| GramA _g -> a
| DefA _id -> a
and reduce_exp_call env id args at = function
| [] ->
if !assume_coherent_matches then None else
let args = if args = [] then "" else
"(" ^ String.concat ", " (List.map Print.string_of_arg args) ^ ")" in
Error.error at "type"
("undefined call to partial function: `$" ^ id.it ^ args ^ "`")
| (args', e, prems)::clauses' ->
match match_list match_arg env Subst.empty args args' with
| exception Irred ->
if not !assume_coherent_matches then None else
reduce_exp_call env id args at clauses'
| None -> reduce_exp_call env id args at clauses'
| Some s ->
match reduce_prems env Subst.(subst_list subst_prem s prems) with
| None -> None
| Some false -> reduce_exp_call env id args at clauses'
| Some true -> Some (reduce_exp env (Subst.subst_exp s e))
and reduce_prems env = function
| [] -> Some true
| prem::prems ->
match reduce_prem env prem with
| Some true -> reduce_prems env prems
| other -> other
and reduce_prem env prem : bool option =
match prem.it with
| VarPr _
| ElsePr -> Some true
| RulePr _ -> None
| IfPr e ->
(match (reduce_exp env e).it with
| BoolE b -> Some b
| _ -> None
)
| IterPr (_prem, _iter) -> None (* TODO(2, rossberg): implement *)
(* Matching *)
(* Iteration *)
and match_iter env s iter1 iter2 : subst option =
match iter1, iter2 with
| Opt, Opt -> Some s
| List, List -> Some s
| List1, List1 -> Some s
| ListN (e1, _ido1), ListN (e2, _ido2) -> match_exp env s e1 e2
| _, _ -> None
(* Types *)
and match_typ env s t1 t2 : subst option =
match t1.it, t2.it with
| ParenT t11, _ -> match_typ env s t11 t2
| _, ParenT t21 -> match_typ env s t1 t21
| _, 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 *)
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 match_arg env s args1 args2 with
| Some s -> Some s
| None ->
(* 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 None else
match_typ env s t1' t2'
)
| VarT _, _ ->
let t1' = reduce_typ env t1 in
if Eq.eq_typ t1 t1' then None else
match_typ env s t1' t2
| _, VarT _ ->
let t2' = reduce_typ env t2 in
if Eq.eq_typ t2 t2' then None else
match_typ env s t1 t2'
| TupT ts1, TupT ts2 -> match_list match_typ env s ts1 ts2
| IterT (t11, iter1), IterT (t21, iter2) ->
let* s' = match_typ env s t11 t21 in match_iter env s' iter1 iter2
| _, _ -> None
(* Expressions *)
(* Matching can produce one of several results:
- Some s: matches producing substitutions
- None: does not match
- exception Irred: lacking normal form, can't decide between Some or None
- exception Error: some inner application was undefined, i.e., non-exhaustive
*)
and match_exp env s e1 e2 : subst option =
Debug.(log "el.match_exp"
(fun _ -> fmt "%s =: %s" (el_exp e1) (el_exp e2))
(fun r -> fmt "%s" (opt el_subst r))
) @@ fun _ ->
match e1.it, (reduce_exp env (Subst.subst_exp s e2)).it with
(*
| (ParenE e11 | TypE (e11, _)), _ -> match_exp env s e11 e2
| _, (ParenE e21 | TypE (e21, _)) -> match_exp env s e1 e21
| _, VarE (id, []) when Subst.mem_varid s id ->
match_exp env s e1 (Subst.subst_exp s e2)
| VarE (id1, args1), VarE (id2, args2) when id1.it = id2.it ->
match_list match_arg env s args1 args2
*)
| _, VarE (id2, []) when Subst.mem_varid s id2 ->
(* A pattern variable already in the substitution is non-linear *)
let e2' = Subst.subst_exp s e2 in
if equiv_exp env e1 e2' then
Some s
else if is_head_normal_exp e1 && is_head_normal_exp e2' then
None
else
raise Irred
| _, VarE (id2, []) ->
(* Treat as a fresh pattern variable. If declared, need to check domain. *)
let find_var id =
match Map.find_opt id.it env.vars with
| None ->
(* Implicitly bound *)
Map.find_opt (El.Convert.strip_var_suffix id).it env.vars (* TODO(2, rossberg): should be gvars *)
| some -> some
in
if
match Map.find_opt (El.Convert.strip_var_suffix id2).it env.vars (* gvars *) with
| None -> true (* undeclared pattern variable always matches *)
| Some t2 ->
let t2' = reduce_typ env t2 in
match e1.it, t2'.it with
| BoolE _, BoolT
| TextE _, TextT -> true
| NumE (_, n), NumT t -> t >= Num.to_typ (Num.narrow n)
| UnE ((`MinusOp | `PlusOp), _), NumT t1 -> t1 >= `IntT
| NumE (_, `Nat n), RangeT tes ->
List.exists (function
| ({it = NumE (_, `Nat n1); _}, None) -> n1 = n
| ({it = NumE (_, `Nat n1); _}, Some {it = NumE (_, `Nat n2); _}) -> n1 <= n && n <= n2
| _ -> false
) (Convert.filter_nl tes)
| (AtomE atom | SeqE ({it = AtomE atom; _}::_)), CaseT (_, _, tcs, _) ->
(match El.Convert.find_nl_list (fun (atomN, _, _) -> atomN.it = atom.it) tcs with
| Some (_, (tN, _), _) ->
match_exp env s e1 (Convert.exp_of_typ tN) <> None
| None -> false
)
| VarE (id1, []), _ ->
(match find_var id1 with
| None -> raise Irred
| Some t1 -> sub_typ env t1 t2' ||
if disj_typ env t1 t2' then false else raise Irred
)
| _, (StrT _ | CaseT _ | ConT _ | RangeT _) -> raise Irred
| _, _ -> true
then
if id2.it = "_" then Some s else
Some (Subst.add_varid s id2 e1)
else None
| AtomE atom1, AtomE atom2 when atom1.it = atom2.it -> Some s
| BoolE b1, BoolE b2 when b1 = b2 -> Some s
| NumE (_, n1), NumE (_, n2) when n1 = n2 -> Some s
| TextE s1, TextE s2 when s1 = s2 -> Some s
| NumE (_, n1), UnE (`PlusOp, e21) when not (Num.is_neg n1) ->
match_exp env s e1 e21
| NumE (numop, n1), UnE (`MinusOp, e21) when Num.is_neg n1 ->
match_exp env s (reduce_exp env {e1 with it = NumE (numop, Num.abs n1)}) e21
| NumE (_, n1), UnE (#signop as op, _) ->
let pm, mp =
if Num.is_neg n1 = (op = `MinusPlusOp)
then `PlusOp, `MinusOp else `MinusOp, `PlusOp
in
match_exp env
(Subst.add_unop (Subst.add_unop s `PlusMinusOp pm) `MinusPlusOp mp) e1 e2
(*
| 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
*)
| SeqE es1, SeqE es2
| TupE es1, TupE es2 -> match_list match_exp env s es1 es2
(*
| IdxE (e11, e12), IdxE (e21, e22)
| CommaE (e11, e12), CommaE (e21, e22)
| CatE (e11, e12), CatE (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, StrE efs2 -> match_nl_list match_expfield env s efs1 efs2
(*
| DotE (e11, atom1), DotE (e21, atom2) when atom1 = atom2 ->
match_exp env s e11 e21
| LenE e11, LenE e21 -> match_exp env s e11 e21
| SizeE id1, SizeE id2 when id1.it = id2.it -> Some s
*)
| InfixE (e11, atom1, e12), InfixE (e21, atom2, e22) when atom1 = atom2 ->
let* s' = match_exp env s e11 e21 in match_exp env s' e12 e22
| BrackE (atom11, e11, atom12), BrackE (atom21, e21, atom22)
when atom11 = atom21 && atom12 = atom22 ->
match_exp env s e11 e21
(*
| CallE (id1, args1), CallE (id2, args2) when id1.it = id2.it ->
match_list match_arg env s args1 args2
*)
| IterE (e11, iter1), IterE (e21, iter2) ->
let* s' = match_exp env s e11 e21 in match_iter env s' iter1 iter2
| (HoleE _ | FuseE _ | UnparenE _), _
| _, (HoleE _ | FuseE _ | UnparenE _) -> assert false
| _, _ when is_head_normal_exp e1 -> None
| _, _ -> raise Irred
and match_expfield env s (atom1, e1) (atom2, e2) =
if atom1 <> atom2 then None else
match_exp env s e1 e2
(*
and match_path env s p1 p2 =
match p1.it, p2.it with
| RootP, RootP -> Some s
| IdxP (p11, e1), IdxP (p21, e2) ->
let* s' = match_path env s p11 p21 in
match_exp env s' e1 e2
| SliceP (p11, e11, e12), SliceP (p21, e21, e22) ->
let* s' = match_path env s p11 p21 in
let* s'' = match_exp env s' e11 e21 in
match_exp env s'' e12 e22
| DotP (p11, atom1), DotP (p21, atom2) when atom1 = atom2 ->
match_path env s p11 p21
| _, _ -> None
*)
(* Grammars *)
and match_sym env s g1 g2 : subst option =
Debug.(log "el.match_sym"
(fun _ -> fmt "%s =: %s" (el_sym g1) (el_sym g2))
(fun r -> fmt "%s" (opt el_subst r))
) @@ fun _ ->
match g1.it, g2.it with
| ParenG g11, _ -> match_sym env s g11 g2
| _, ParenG g21 -> match_sym env s g1 g21
| _, 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 id is treated as a pattern variable *)
Some (Subst.add_gramid s id g1)
| VarG (id1, args1), VarG (id2, args2) when id1.it = id2.it ->
match_list match_arg env s args1 args2
| TupG gs1, TupG gs2 -> match_list match_sym env s gs1 gs2
| IterG (g11, iter1), IterG (g21, iter2) ->
let* s' = match_sym env s g11 g21 in match_iter env s' iter1 iter2
| _, _ -> None
(* Parameters *)
and match_arg env s a1 a2 : subst option =
Debug.(log "el.match_arg"
(fun _ -> fmt "%s =: %s" (el_arg a1) (el_arg a2))
(fun r -> fmt "%s" (opt el_subst r))
) @@ fun _ ->
match !(a1.it), !(a2.it) with
| ExpA e1, ExpA e2 -> match_exp env s e1 e2
| TypA t1, TypA t2 -> match_typ env s t1 t2
| GramA g1, GramA g2 -> match_sym env s g1 g2
| DefA id1, DefA id2 ->
if id2.it = "_" then Some s else
Some (Subst.add_defid s id2 id1)
| _, _ -> assert false
(* Type Equivalence *)
and equiv_typ env t1 t2 =
Debug.(log "el.equiv_typ"
(fun _ -> fmt "%s == %s" (el_typ t1) (el_typ t2)) Bool.to_string
) @@ fun _ ->
match t1.it, t2.it with
| VarT (id1, args1), VarT (id2, args2) ->
(El.Convert.strip_var_suffix id1).it = (El.Convert.strip_var_suffix id2).it &&
equiv_list equiv_arg env args1 args2 || (* optimization *)
let t1' = reduce_typ env t1 in
let t2' = reduce_typ env t2 in
(t1' <> t1 || t2' <> t2) && equiv_typ env t1' t2'
| VarT _, _ ->
let t1' = reduce_typ env t1 in
t1' <> t1 && equiv_typ env t1' t2
| _, VarT _ ->
let t2' = reduce_typ env t2 in
t2' <> t2 && equiv_typ env t1 t2'
| ParenT t11, _ -> equiv_typ env t11 t2
| _, ParenT t21 -> equiv_typ env t1 t21
| TupT ts1, TupT ts2 | SeqT ts1, SeqT ts2 -> equiv_list equiv_typ env ts1 ts2
| IterT (t11, iter1), IterT (t21, iter2) ->
equiv_typ env t11 t21 && Eq.eq_iter iter1 iter2
| AtomT atom1, AtomT atom2 -> atom1.it = atom2.it
| InfixT (t11, atom1, t12), InfixT (t21, atom2, t22) ->
equiv_typ env t11 t21 && atom1.it = atom2.it && equiv_typ env t12 t22
| BrackT (atom11, t11, atom12), BrackT (atom21, t21, atom22) ->
atom11.it = atom21.it && equiv_typ env t11 t21 && atom12 = atom22
| StrT (NoDots, [], tfs1, NoDots), StrT (NoDots, [], tfs2, NoDots) ->
equiv_nl_list equiv_typfield env tfs1 tfs2
| CaseT (NoDots, [], tcs1, NoDots), CaseT (NoDots, [], tcs2, NoDots) ->
equiv_nl_list equiv_typcase env tcs1 tcs2
| ConT tc1, ConT tc2 -> equiv_typcon env tc1 tc2
| RangeT tes1, RangeT tes2 -> equiv_nl_list equiv_typenum env tes1 tes2
| _, _ -> t1.it = t2.it
and equiv_typfield env (atom1, (t1, prems1), _) (atom2, (t2, prems2), _) =
atom1.it = atom2.it && equiv_typ env t1 t2 && Eq.(eq_nl_list eq_prem prems1 prems2)
and equiv_typcase env (atom1, (t1, prems1), _) (atom2, (t2, prems2), _) =
atom1.it = atom2.it && equiv_typ env t1 t2 && Eq.(eq_nl_list eq_prem prems1 prems2)
and equiv_typcon env ((t1, prems1), _) ((t2, prems2), _) =
equiv_typ env t1 t2 && Eq.(eq_nl_list eq_prem prems1 prems2)
and equiv_typenum env (e11, e12o) (e21, e22o) =
equiv_exp env e11 e21 && equiv_opt equiv_exp env e12o e22o
and equiv_exp env e1 e2 =
Debug.(log "el.equiv_exp"
(fun _ -> fmt "%s == %s" (el_exp e1) (el_exp e2)) Bool.to_string
) @@ fun _ ->
(* TODO(3, rossberg): this does not reduce inner type arguments *)
Eq.eq_exp (reduce_exp env e1) (reduce_exp env e2)
and equiv_arg env a1 a2 =
Debug.(log "el.equiv_arg"
(fun _ -> fmt "%s == %s" (el_arg a1) (el_arg a2)) Bool.to_string
) @@ fun _ ->
(*
Printf.eprintf "[el.equiv_arg] %s == %s\n%!"
(Print.string_of_arg a1)
(Print.string_of_arg a2);
*)
match !(a1.it), !(a2.it) with
| ExpA e1, ExpA e2 -> equiv_exp env e1 e2
| TypA t1, TypA t2 -> equiv_typ env t1 t2
| GramA g1, GramA g2 -> Eq.eq_sym g1 g2
| DefA id1, DefA id2 -> id1.it = id2.it
| _, _ -> 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))
| TypP _, TypP _ -> Some s
| GramP (id1, t1), GramP (id2, t2) ->
if not (equiv_typ env t1 t2) then None else
Some (Subst.add_gramid s id2 (VarG (id1, []) $ p1.at))
| 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)
| _, _ -> None
) (Some Subst.empty) ps1 ps2
(* Subtyping *)
and sub_prems _env prems1 prems2 =
Debug.(log "el.sub_prems"
(fun _ -> fmt "%s <: %s" (nl_list el_prem prems1) (nl_list el_prem prems2))
Bool.to_string
) @@ fun _ ->
let open Convert in
forall_nl_list (fun prem2 -> exists_nl_list (Eq.eq_prem prem2) prems1) prems2
and sub_typ env t1 t2 =
Debug.(log "el.sub_typ"
(fun _ -> fmt "%s <: %s" (el_typ t1) (el_typ t2)) Bool.to_string
) @@ fun _ ->
let t1 = reduce_typ env t1 in
let t2 = reduce_typ env t2 in
match t1.it, t2.it with
(*| NumT nt1, NumT nt2 -> Num.sub nt1 nt2*)
| StrT (NoDots, [], tfs1, NoDots), StrT (NoDots, [], tfs2, NoDots) ->
El.Convert.forall_nl_list (fun (atom, (t2, prems2), _) ->
match find_field tfs1 atom with
| Some (t1, prems1) ->
equiv_typ env t1 t2 && sub_prems env prems1 prems2
| None -> false
) tfs2
| CaseT (NoDots, [], tcs1, NoDots), CaseT (NoDots, [], tcs2, NoDots) ->
El.Convert.forall_nl_list (fun (atom, (t1, prems1), _) ->
match find_case tcs2 atom with
| Some (t2, prems2) ->
equiv_typ env t1 t2 && sub_prems env prems1 prems2
| None -> false
) tcs1
| ConT ((t11, prems1), _), ConT ((t21, prems2), _) ->
sub_typ env t11 t21 && sub_prems env prems1 prems2 ||
equiv_typ env t1 t2
(*
| ConT ((t11, _), _), _ -> sub_typ env t11 t2
| _, ConT ((t21, _), _) -> sub_typ env t1 t21
| RangeT [], NumT _ -> true
| RangeT (Elem (e1, _)::tes1), NumT t2' ->
(match (reduce_exp env e1).it with
| NumE _ -> true
| UnE (`MinusOp, _) -> t2' <= `IntT
| _ -> assert false
) && sub_typ env (RangeT tes1 $ t1.at) t2
| NumT _, RangeT [] -> true
| NumT t1', RangeT (Elem (e2, _)::tes2) ->
(match (reduce_exp env e2).it with
| NumE (_, `Nat _) -> t1' = `NatT
| UnE (`MinusOp, _) -> true
| _ -> assert false
) && sub_typ env t1 (RangeT tes2 $ t2.at)
*)
| TupT ts1, TupT ts2
| SeqT ts1, SeqT ts2 ->
List.length ts1 = List.length ts2 && List.for_all2 (sub_typ env) ts1 ts2
| _, _ -> equiv_typ env t1 t2
and find_field tfs atom =
El.Convert.find_nl_list (fun (atom', _, _) -> atom'.it = atom.it) tfs
|> Option.map snd3
and find_case tcs atom =
El.Convert.find_nl_list (fun (atom', _, _) -> atom'.it = atom.it) tcs
|> Option.map snd3
and fst3 (x, _, _) = x
and snd3 (_, x, _) = x
(* Type Disjointness *)
and disj_typ env t1 t2 =
Debug.(log "el.disj_typ"
(fun _ -> fmt "%s ## %s" (el_typ t1) (el_typ t2)) Bool.to_string
) @@ fun _ ->
match t1.it, t2.it with
| VarT (id1, args1), VarT (id2, args2) ->
let t1' = reduce_typ env t1 in
let t2' = reduce_typ env t2 in
if t1' <> t1 || t2' <> t2 then
disj_typ env t1' t2'
else
(El.Convert.strip_var_suffix id1).it <> (El.Convert.strip_var_suffix id2).it ||
disj_list disj_arg env args1 args2
| VarT _, _ ->
let t1' = reduce_typ env t1 in
t1' <> t1 && disj_typ env t1' t2
| _, VarT _ ->
let t2' = reduce_typ env t2 in
t2' <> t2 && disj_typ env t1 t2'
| ParenT t11, _ -> disj_typ env t11 t2
| _, ParenT t21 -> disj_typ env t1 t21
| TupT ts1, TupT ts2 | SeqT ts1, SeqT ts2 -> disj_list disj_typ env ts1 ts2
| IterT (t11, iter1), IterT (t21, iter2) ->
disj_typ env t11 t21 || not (Eq.eq_iter iter1 iter2)
| AtomT atom1, AtomT atom2 -> atom1.it <> atom2.it
| InfixT (t11, atom1, t12), InfixT (t21, atom2, t22) ->
disj_typ env t11 t21 || atom1.it <> atom2.it || disj_typ env t12 t22
| BrackT (atom11, t11, atom12), BrackT (atom21, t21, atom22) ->
atom11.it <> atom21.it || disj_typ env t11 t21 || atom12 = atom22
| StrT (NoDots, [], tfs1, NoDots), StrT (NoDots, [], tfs2, NoDots) ->
unordered (atoms tfs1) (atoms tfs2) ||
El.Convert.exists_nl_list (fun (atom, (t2, _prems2), _) ->
match find_field tfs1 atom with
| Some (t1, _prems1) -> disj_typ env t1 t2
| None -> true
) tfs2
| CaseT (NoDots, [], tcs1, NoDots), CaseT (NoDots, [], tcs2, NoDots) ->
Set.disjoint (atoms tcs1) (atoms tcs2) ||
El.Convert.exists_nl_list (fun (atom, (t1, _prems1), _) ->
match find_case tcs2 atom with
| Some (t2, _prems2) -> disj_typ env t1 t2
| None -> false
) tcs1
| ConT ((t11, _), _), ConT ((t21, _), _) -> disj_typ env t11 t21
| RangeT _, RangeT _ -> false (* approximation *)
| _, _ -> t1.it <> t2.it
and narrow_typ env t1 t2 =
Debug.(log "el.narrow_typ"
(fun _ -> fmt "%s <: %s" (el_typ t1) (el_typ t2)) Bool.to_string
) @@ fun _ ->
let t1 = reduce_typ env t1 in
let t2 = reduce_typ env t2 in
match t1.it, t2.it with
| NumT nt1, NumT nt2 -> Num.sub nt1 nt2
| _, _ -> equiv_typ env t1 t2
and atoms xs =
Set.of_list (List.map Print.string_of_atom
(El.Convert.map_filter_nl_list fst3 xs))
and unordered s1 s2 = not Set.(subset s1 s2 || subset s2 s1)
and disj_exp env e1 e2 =
(* TODO(3, rossberg): this does not reduce inner type arguments *)
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
is_normal_exp e1' && is_normal_exp e2' && not (Eq.eq_exp e1' e2')
and disj_arg env a1 a2 =
match !(a1.it), !(a2.it) with
| ExpA e1, ExpA e2 -> disj_exp env e1 e2
| TypA t1, TypA t2 -> disj_typ env t1 t2
| GramA _, GramA _ -> false
| DefA _, DefA _ -> false
| _, _ -> false