blob: a2cc93fad0c6d753d4ac4a0ced3b9ecc7a484feb [file] [log] [blame] [edit]
open Util
open Source
open Ast
open Xl
open Env
(* Environment *)
type env = Env.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 ($>) it e = {e with it}
let fst3 (x, _, _) = x
let snd3 (_, x, _) = x
let unordered s1 s2 = not Set.(subset s1 s2 || subset s2 s1)
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 =
match xo1, xo2 with
| None, None -> Some s
| Some x1, Some x2 -> match_x env s x1 x2
| _, _ -> None
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 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 =
Debug.(log_if "il.reduce_typ" (t.it <> NumT `NatT)
(fun _ -> fmt "%s" (il_typ t))
(fun r -> fmt "%s" (il_typ r))
) @@ fun _ ->
match t.it with
| VarT (id, args) ->
let args' = List.map (reduce_arg env) args in
(match reduce_typ_app' env id args' t.at (Env.find_opt_typ env id) with
| Some {it = AliasT t'; _} -> reduce_typ env t'
| _ -> VarT (id, args') $ t.at
)
| _ -> t
and reduce_typdef env t : deftyp =
let t' = reduce_typ env t in
match t'.it with
| VarT (id, as_) ->
(match reduce_typ_app env id as_ t'.at with
| Some dt -> dt
| None -> AliasT t $ t'.at
)
| _ -> AliasT t $ t'.at
and reduce_typ_app env id args at : deftyp option =
Debug.(log "il.reduce_typ_app"
(fun _ -> fmt "%s(%s)" id.it (il_args args))
(fun r -> fmt "%s" (opt il_deftyp r))
) @@ fun _ ->
reduce_typ_app' env id (List.map (reduce_arg env) args) at (Env.find_opt_typ env id)
and reduce_typ_app' env id args at = function
| None -> None (* id is a type parameter *)
| Some (_ps, []) ->
if !assume_coherent_matches then None else
Error.error at "validation"
("undefined instance of partial type `" ^ id.it ^ "`")
| Some (ps, {it = InstD (_binds, args', dt); _}::insts') ->
Debug.(log "il.reduce_typ_app'"
(fun _ -> fmt "%s(%s) =: %s(%s)" id.it (il_args args) id.it (il_args args'))
(fun r -> fmt "%s" (opt (Fun.const "!") r))
) @@ fun _ ->
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 (Some (ps, insts'))
| None -> reduce_typ_app' env id args at (Some (ps, insts'))
| Some s -> Some (Subst.subst_deftyp s dt)
(* Expression Reduction *)
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_normal_exp e =
match e.it with
| BoolE _ | NumE _ | TextE _ -> true
| ListE es | TupE es -> List.for_all is_normal_exp es
| OptE None -> true
| OptE (Some e) | CaseE (_, e) | SubE (e, _, _) -> is_normal_exp e
| StrE efs -> List.for_all (fun (_, e) -> is_normal_exp e) efs
| _ -> false
and reduce_exp env e : exp =
Debug.(log "il.reduce_exp"
(fun _ -> fmt "%s" (il_exp e))
(fun e' -> fmt "%s" (il_exp e'))
) @@ fun _ ->
match e.it with
| VarE _ | BoolE _ | NumE _ | TextE _ -> e
| UnE (op, ot, 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
| #Num.unop as op', NumE n1 ->
(match Num.un op' n1 with
| Some n -> NumE n
| None -> UnE (op, ot, e1')
) $> e
| `NotOp, UnE (`NotOp, _, e11') -> e11'
| `MinusOp, UnE (`MinusOp, _, e11') -> e11'
| _ -> UnE (op, ot, e1') $> e
)
| BinE (op, ot, e1, 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 (op, ot, e1', e2')
| Some e' -> e'
)
| #Num.binop as op' ->
(match Num.bin_partial op' e1'.it e2'.it of_num_exp to_num_exp with
| None -> BinE (op, ot, e1', e2')
| Some e' -> e'
)
) $> e
| CmpE (op, ot, e1, 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 (op, ot, e1', e2')
)
| _ -> CmpE (op, ot, e1', e2')
) $> e
| IdxE (e1, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
(match e1'.it, e2'.it with
| ListE es, NumE (`Nat i) when i < Z.of_int (List.length es) -> List.nth es (Z.to_int i)
| _ -> IdxE (e1', e2') $> e
)
| 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
| ListE es, NumE (`Nat i), NumE (`Nat n) when Z.(i + n) < Z.of_int (List.length es) ->
ListE (Lib.List.take (Z.to_int n) (Lib.List.drop (Z.to_int i) es))
| _ -> SliceE (e1', e2', e3')
) $> e
| 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')
| 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 (CatE (e', e2') $> e')
else ExtE (e', p', e2') $> e'
)
| StrE efs -> StrE (List.map (reduce_expfield env) efs) $> e
| DotE (e1, atom) ->
let e1' = reduce_exp env e1 in
(match e1'.it with
| StrE efs -> snd (List.find (fun (atomN, _) -> Atom.eq atomN atom) efs)
| _ -> DotE (e1', atom) $> e
)
| CompE (e1, e2) ->
(* TODO(4, rossberg): avoid overlap with CatE? *)
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
(match e1'.it, e2'.it with
| ListE es1, ListE es2 -> ListE (es1 @ es2)
| OptE None, OptE _ -> e2'.it
| OptE _, OptE None -> e1'.it
| StrE efs1, StrE efs2 ->
let merge (atom1, e1) (atom2, e2) =
assert (Atom.eq atom1 atom2);
(atom1, reduce_exp env (CompE (e1, e2) $> e1))
in StrE (List.map2 merge efs1 efs2)
| _ -> CompE (e1', e2')
) $> e
| MemE (e1, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
(match e2'.it with
| OptE None -> BoolE false
| OptE (Some e2') when Eq.eq_exp e1' e2' -> BoolE true
| OptE (Some e2') when is_normal_exp e1' && is_normal_exp e2' -> BoolE false
| ListE [] -> BoolE false
| ListE es2' when List.exists (Eq.eq_exp e1') es2' -> BoolE true
| ListE es2' when is_normal_exp e1' && List.for_all is_normal_exp es2' -> BoolE false
| _ -> MemE (e1', e2')
) $> e
| LenE e1 ->
let e1' = reduce_exp env e1 in
(match e1'.it with
| ListE es -> NumE (`Nat (Z.of_int (List.length es)))
| _ -> LenE e1'
) $> e
| TupE es -> TupE (List.map (reduce_exp env) es) $> e
| CallE (id, args) ->
let args' = List.map (reduce_arg env) args in
let _ps, _t, clauses = Env.find_def env id in
(* Allow for uninterpreted functions *)
if not !assume_coherent_matches && clauses = [] then CallE (id, args') $> e else
(match reduce_exp_call env id args' e.at clauses with
| None -> CallE (id, args') $> e
| Some e -> e
)
| IterE (e1, iterexp) ->
let e1' = reduce_exp env e1 in
let (iter', xes') as iterexp' = reduce_iterexp env iterexp in
let ids, es' = List.split xes' in
if not (List.for_all is_head_normal_exp es') || iter' <= List1 && es' = [] then
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
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 env (Subst.subst_exp s e1')
else
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 env (IterE (e1', (ListN (en, None), xes')) $> e)
else
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 env
else
IterE (e1', iterexp') $> e
| ListN _ ->
IterE (e1', iterexp') $> e
)
| ProjE (e1, i) ->
let e1' = reduce_exp env e1 in
(match e1'.it with
| TupE es -> List.nth es i
| _ -> ProjE (e1', i) $> e
)
| UncaseE (e1, mixop) ->
let e1' = reduce_exp env e1 in
(match e1'.it with
| CaseE (_, e11') -> e11'
| _ -> UncaseE (e1', mixop) $> e
)
| OptE eo -> OptE (Option.map (reduce_exp env) eo) $> e
| TheE e1 ->
let e1' = reduce_exp env e1 in
(match e1'.it with
| OptE (Some e11) -> e11
| _ -> TheE e1' $> e
)
| ListE es -> ListE (List.map (reduce_exp env) es) $> e
| LiftE e1 ->
let e1' = reduce_exp env e1 in
(match e1'.it with
| OptE None -> ListE []
| OptE (Some e11') -> ListE [e11']
| _ -> LiftE e1'
) $> e
| CatE (e1, e2) ->
let e1' = reduce_exp env e1 in
let e2' = reduce_exp env e2 in
(match e1'.it, e2'.it with
| ListE es1, ListE es2 -> ListE (es1 @ es2)
| OptE None, OptE _ -> e2'.it
| OptE _, OptE None -> e1'.it
| _ -> CatE (e1', e2')
) $> e
| CaseE (op, e1) -> CaseE (op, reduce_exp env e1) $> e
| CvtE (e1, _nt1, nt2) ->
let e1' = reduce_exp env e1 in
(match e1'.it with
| NumE n ->
(match Num.cvt nt2 n with
| Some n' -> NumE n' $> e
| None -> e1'
)
| _ -> e1'
)
| SubE (e1, t1, t2) ->
let e1' = reduce_exp env e1 in
let t1' = reduce_typ env t1 in
let t2' = reduce_typ env t2 in
if equiv_typ env t1' t2' then e1' else
(match e1'.it with
| SubE (e11', t11', _t12') ->
reduce_exp env (SubE (e11', t11', t2') $> e)
| TupE es' ->
(match t1.it, t2.it with
| TupT ets1, TupT ets2 ->
(match
List.fold_left2 (fun opt eI ((e1I, t1I), (e2I, t2I)) ->
let* (s1, s2, res') = opt in
let t1I' = Subst.subst_typ s1 t1I in
let t2I' = Subst.subst_typ s2 t2I in
let e1I' = reduce_exp env (Subst.subst_exp s1 e1I) in
let e2I' = reduce_exp env (Subst.subst_exp s2 e2I) in
let* s1' = try match_exp env s1 eI e1I' with Irred -> None in
let* s2' = try match_exp env s2 eI e2I' with Irred -> None in
let eI' = reduce_exp env (SubE (eI, t1I', t2I') $$ eI.at % t2I') in
Some (s1', s2', eI'::res')
) (Some (Subst.empty, Subst.empty, [])) es' (List.combine ets1 ets2)
with
| Some (_, _, res') -> TupE (List.rev res') $> e
| None -> SubE (e1', t1', t2') $> e
)
| _ -> SubE (e1', t1', t2') $> e
)
| _ when is_head_normal_exp e1' ->
{e1' with note = e.note}
| _ -> SubE (e1', t1', t2') $> e
)
and reduce_iter env = function
| ListN (e, ido) -> ListN (reduce_exp env e, ido)
| iter -> iter
and reduce_iterexp env (iter, xes) =
(reduce_iter env iter, List.map (fun (id, e) -> id, reduce_exp env e) xes)
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
| ListE es, NumE (`Nat i) when i < Z.of_int (List.length es) ->
ListE (List.mapi (fun j eJ -> if Z.of_int j = i then f eJ p1' else eJ) es) $> e'
| _ ->
f e' (IdxP (p1', e1') $> p)
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
| 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
reduce_exp env (CatE (e1', CatE (f e2' p1', e3') $> e') $> e')
| _ ->
f e' (SliceP (p1', e1', e2') $> p)
in
reduce_path env e p1 f'
| DotP (p1, atom) ->
let f' e' p1' =
match e'.it with
| StrE efs ->
StrE (List.map (fun (atomI, eI) ->
if Eq.eq_atom atomI atom then (atomI, f eI p1') else (atomI, eI)) efs) $> e'
| _ ->
f e' (DotP (p1', atom) $> p)
in
reduce_path env e p1 f'
and reduce_arg env a : arg =
Debug.(log "il.reduce_arg"
(fun _ -> fmt "%s" (il_arg a))
(fun a' -> fmt "%s" (il_arg a'))
) @@ fun _ ->
match a.it with
| ExpA e -> ExpA (reduce_exp env e) $ a.at
| TypA _t -> a (* types are reduced on demand *)
| DefA _id -> a
| GramA _g -> a
and reduce_exp_call env id args at = function
| [] ->
if !assume_coherent_matches then None else
Error.error at "validation"
("undefined call to partial function `$" ^ id.it ^ "`")
| {it = DefD (_binds, 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'))
(function None -> "-" | Some e' -> fmt "%s" (il_exp e'))
) @@ fun _ ->
assert (List.for_all (fun a -> Eq.eq_arg a (reduce_arg env a)) args);
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 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 s = function
| [] -> Some true
| prem::prems ->
match reduce_prem env (Subst.subst_prem s prem) with
| `True s' -> reduce_prems env (Subst.union s s') prems
| `False -> Some false
| `None -> None
and reduce_prem env prem : [`True of Subst.t | `False | `None] =
match prem.it with
| RulePr _ -> `None
| IfPr e ->
(match (reduce_exp env e).it with
| BoolE b -> if b then `True Subst.empty else `False
| _ -> `None
)
| ElsePr -> `True Subst.empty
| LetPr (e1, e2, _ids) ->
(match match_exp env Subst.empty e2 e1 with
| Some s -> `True s
| None -> `None
| exception Irred -> `None
)
| IterPr (prem1, iterexp) ->
let iter', xes' = reduce_iterexp 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 (_, _, xs) -> List.mem x.it xs
| IterPr (premI, iterexpI) ->
let _iter1', xes1' = reduce_iterexp env iterexpI in
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. *)
`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. *)
`True 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
match reduce_prem env (Subst.subst_prem s prem1) with
| (`None | `False) as r -> r
| `True s' ->
(* Body is true: now reverse-match out-bound iteration values
* against iteration sources. *)
match
List.fold_left (fun s_opt (xI, eI) ->
let* s = s_opt in
let tI = match eI.note.it with IterT (tI, _) -> tI | _ -> assert false in
match_exp' env s (OptE (Some (Subst.subst_exp s' (VarE xI $$ xI.at % tI))) $> eI) eI
) (Some Subst.empty) xes_out
with
| Some s'' -> `True s''
| None -> `None
else
(* Inconsistent arity of iteration values: can't perform mapping.
* (This is a stuck computation, i.e., undefined.) *)
`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.
* (This is a stuck computation, i.e., undefined.) *)
`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)
)
in
if List.mem `None rs then `None else
if List.mem `False rs then `False else
(* Body was true in every iteration: now reverse-match out-bound
* iteration variables against iteration sources. *)
let ss = List.map (function `True s -> s | _ -> assert false) 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 match_list match_exp env Subst.empty es_out' es_out with
| Some s' -> `True s'
| None -> `None
else
(* Inconsistent list lengths: can't perform mapping.
* (This is a stuck computation, i.e., undefined.) *)
`None
| ListN _ -> `None
)
(* 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
| (Opt | List1 | ListN _), List -> Some s
| _, _ -> None
(* Types *)
and match_typ env s t1 t2 : subst option =
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 *)
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 ets1, TupT ets2 -> match_list match_typbind env s ets1 ets2
| IterT (t11, iter1), IterT (t21, iter2) ->
let* s' = match_typ env s t11 t21 in match_iter env s' iter1 iter2
| _, _ -> None
and match_typbind env s (e1, t1) (e2, t2) =
let* s' = match_exp env s e1 (Subst.subst_exp s e2) in
let* s'' = match_typ env s' t1 (Subst.subst_typ s t2) in
Some s''
(* Expressions *)
and match_exp env s e1 e2 : subst option =
match_exp' env s (reduce_exp env e1) e2
and match_exp' env s e1 e2 : subst option =
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" (opt il_subst r))
) @@ fun _ ->
assert (Eq.eq_exp e1 (reduce_exp env e1));
if Eq.eq_exp e1 e2 then Some s else (* HACK around subtype elim pass introducing calls on LHS's *)
match e1.it, (reduce_exp env (Subst.subst_exp s e2)).it with
| _, VarE id when Subst.mem_varid s id ->
(* A pattern variable already in the substitution is non-linear *)
if equiv_exp env e1 (Subst.subst_exp s e2) then
Some s
else
None
| _, VarE id ->
(* Treat as a fresh pattern variable. *)
let e1' = reduce_exp env (SubE (e1, e1.note, e2.note) $$ e1.at % e2.note) in
Some (Subst.add_varid s id e1')
| 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 n1, UnE (`MinusOp, _, e21) when Num.is_neg n1 ->
match_exp env s (reduce_exp env {e1 with it = NumE (Num.abs n1)}) e21
(*
| 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' env s es1 es2
| _, TupE es2 ->
let* es1 = eta_tup_exp env e1 in
match_list match_exp' 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' env s (ListE es11 $> e1) e21 in
match_exp' 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' env s (ListE es11 $> e1) e21 in
match_exp' 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, StrE efs2 -> match_list match_expfield 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, e11), CaseE (op2, e21) when Eq.eq_mixop op1 op2 ->
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
*)
| _, UncaseE (e21, mixop) ->
match_exp' env s (CaseE (mixop, e1) $$ e1.at % e21.note) e21
| _, ProjE (e21, 0) -> (* only valid on unary tuples! *)
match_exp' 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 s_opt (_xI, eI) ->
let* s = s_opt in
match_exp' env s e1 eI
) (Some s) xes
| OptE (Some e11), IterE (e21, (Opt, xes)) ->
let* s' = match_exp' env s e11 e21 in
let* s'' =
List.fold_left (fun s_opt (xI, exI) ->
let* s = s_opt in
let tI = match exI.note.it with IterT (tI, _) -> tI | _ -> assert false in
match_exp' env s (OptE (Some (Subst.subst_exp s' (VarE xI $$ exI.at % tI))) $> e2) exI
) (Some (List.fold_left Subst.remove_varid s (List.map fst xes))) xes
in 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' env s e1 (IterE (e21, (ListN (en, None), xes)) $> e2)
| ListE es1, IterE (e21, (List1, xes)) ->
if es1 = [] then None else
let en = VarE ("_" $ e2.at) $$ e2.at % (NumT `NatT $ e2.at) in
match_exp' 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' 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* ss =
List.mapi (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' env s''' e1J (Subst.subst_exp s''' e21)
) es1 |> Lib.List.flatten_opt
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' env s eI exI
) env s' xs exs
in Some (Subst.union s''' s) (* re-add possibly locally shadowed bindings *)
| _, 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
| SubE (e11, t11, _t12), SubE (e21, t21, _t22) when sub_typ env t11 t21 ->
match_exp' env s (reduce_exp env (SubE (e11, t11, t21) $> e21)) e21
| SubE (_e11, t11, _t12), SubE (_e21, t21, _t22) when disj_typ env t11 t21 ->
None
| _, SubE (e21, t21, _t22) ->
if sub_typ env e1.note t21 then
match_exp' env s (reduce_exp env (SubE (e1, e1.note, t21) $> e21)) e21
else if is_head_normal_exp e1 then
let t21' = reduce_typ env t21 in
if
match e1.it, t21'.it with
| BoolE _, BoolT
| NumE _, NumT _
| TextE _, TextT -> true
| CaseE (op, _), VarT _ ->
(match (reduce_typdef env t21).it with
| VariantT tcs ->
(* Assumes that we only have shallow subtyping. *)
List.exists (fun (opN, _, _) -> Eq.eq_mixop opN op) tcs
| _ -> false
)
| VarE id1, _ ->
let t1 = reduce_typ env (Env.find_var env id1) in
sub_typ env t1 t21 || raise Irred
| _, _ -> false
then match_exp' env s {e1 with note = t21} e21
else None
else raise Irred
| _, _ when is_head_normal_exp e1 -> None
| _, _ ->
raise Irred
and match_expfield env s (atom1, e1) (atom2, e2) =
if not (Eq.eq_atom atom1 atom2) then None else
match_exp' env s e1 e2
and match_iterexp env s (iter1, _ids1) (iter2, _ids2) =
match_iter env s iter1 iter2
and eta_tup_exp env e : exp list option =
let ets =
match (reduce_typ env e.note).it with
| TupT ets -> ets
| _ -> assert false
in
let* es' =
List.fold_left (fun opt (eI, tI) ->
let* res', i, s = opt in
let eI' = ProjE (e, i) $$ e.at % Subst.subst_typ s tI in
let* s' = try match_exp env s eI' eI with Irred -> None in
Some (eI'::res', i + 1, s')
) (Some ([], 0, Subst.empty)) ets |> Option.map fst3 |> Option.map List.rev
in Some es'
and eta_iter_exp env e : exp * iterexp =
match (reduce_typ env e.note).it with
| IterT (t, Opt) -> reduce_exp env (TheE e $$ e.at % t), (Opt, [])
| IterT (t, List) ->
let id = "_i_" $ e.at in
let len = reduce_exp env (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 =
Debug.(log_in "il.match_sym" (fun _ -> fmt "%s =: %s" (il_sym g1) (il_sym g2)));
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 *)
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
| IterG (g11, iter1), IterG (g21, iter2) ->
let* s' = match_sym env s g11 g21 in match_iterexp env s' iter1 iter2
| _, _ -> None
(* Parameters *)
and match_arg env s a1 a2 : subst option =
Debug.(log_in "il.match_arg" (fun _ -> fmt "%s =: %s" (il_arg a1) (il_arg a2)));
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
| DefA id1, DefA id2 -> 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 env t1 in
let t2' = reduce_typ env t2 in
(t1 <> t1' || t2 <> t2') && equiv_typ env t1' t2' ||
Eq.eq_deftyp (reduce_typdef env t1') (reduce_typdef env t2') (* TODO(3, rossberg): be more expressive *)
| 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'
| 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 ets1 ets2 =
match ets1, ets2 with
| (e1, t1)::ets1', (e2, t2)::ets2' ->
equiv_typ env t1 (Subst.subst_typ s t2) &&
(match match_exp env s e1 e2 with
| None -> false
| Some s' -> equiv_tup env s' ets1' ets2'
| exception Irred -> false
)
| _, _ -> ets1 = ets2
and equiv_iter env iter1 iter2 =
match iter1, iter2 with
| ListN (e1, ido1), ListN (e2, ido2) ->
equiv_exp 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 (e11, e12, _ids1), LetPr (e21, e22, _id2) ->
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 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 *)
Eq.eq_exp (reduce_exp env e1) (reduce_exp env e2)
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 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, t1), GramP (id2, t2) ->
if not (equiv_typ env t1 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 =
List.for_all (fun prem2 -> List.exists (Eq.eq_prem prem2) prems1) prems2
and sub_typ env 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 ||
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*)
| TupT ets1, TupT ets2 -> sub_tup env Subst.empty ets1 ets2
| VarT _, VarT _ ->
(match (reduce_typdef env t1').it, (reduce_typdef env t2').it with
| StructT tfs1, StructT tfs2 ->
List.for_all (fun (atom, (_binds2, t2, prems2), _) ->
match find_field tfs1 atom with
| Some (_binds1, t1, prems1) ->
sub_typ env t1 t2 && sub_prems env prems1 prems2
| None -> false
) tfs2
| VariantT tcs1, VariantT tcs2 ->
List.for_all (fun (mixop, (_binds1, t1, prems1), _) ->
match find_case tcs2 mixop with
| Some (_binds2, t2, prems2) ->
sub_typ env t1 t2 && sub_prems env prems1 prems2
| None -> false
) tcs1
| _, _ -> false
)
| _, _ ->
false
and sub_tup env s ets1 ets2 =
match ets1, ets2 with
| (e1, t1)::ets1', (e2, t2)::ets2' ->
sub_typ env t1 (Subst.subst_typ s t2) &&
(match match_exp env s e1 e2 with
| None -> false
| Some s' -> sub_tup env s' ets1' ets2'
| exception Irred -> false
)
| _, _ -> ets1 = ets2
and find_field tfs atom =
List.find_opt (fun (atom', _, _) -> Eq.eq_atom atom' atom) tfs |> Option.map snd3
and find_case tcs op =
List.find_opt (fun (op', _, _) -> Eq.eq_mixop op' op) tcs |> Option.map 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 env t1).it, (reduce_typdef env t2).it with
| StructT tfs1, StructT tfs2 ->
unordered (atoms tfs1) (atoms tfs2) ||
List.exists (fun (atom, (_binds2, t2, _prems2), _) ->
match find_field tfs1 atom with
| Some (_binds1, t1, _prems1) -> disj_typ env t1 t2
| None -> true
) tfs2
| VariantT tcs1, VariantT tcs2 ->
Set.disjoint (mixops tcs1) (mixops tcs2) ||
List.exists (fun (atom, (_binds1, t1, _prems1), _) ->
match find_case tcs2 atom with
| Some (_binds2, t2, _prems2) -> disj_typ env t1 t2
| None -> false
) tcs1
| _, _ -> true
)
| 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'
| 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)
| _, _ ->
t1.it <> t2.it
and atoms xs = Set.of_list (List.map Print.string_of_atom (List.map fst3 xs))
and mixops xs = Set.of_list (List.map Print.string_of_mixop (List.map fst3 xs))
and disj_tup env s ets1 ets2 =
match ets1, ets2 with
| (e1, t1)::ets1', (e2, t2)::ets2' ->
disj_typ env t1 (Subst.subst_typ s t2) ||
(match match_exp env s e1 e2 with
| None -> false
| Some s' -> disj_tup env s' ets1' ets2'
| exception Irred -> false
)
| _, _ -> ets1 = ets2