checkpoint failing test after fixing tests checkpoint checkpoint checkpoint re-work asd checkpoint checkpoint checkpoint mix proj checkpoint mix first parser impl checkpoint fix tests re-org parser checkpoint strings fix multiline strings tuples checkpoint maps checkpoint checkpoint checkpoint checkpoint fix weird eof expression parse error checkpoint before typing checkpoint checpoint checkpoint checkpoint checkpoint ids in primitive types checkpoint checkpoint fix tests initial annotation checkpoint checkpoint checkpoint union subtyping conventions refactor - split typer typing tuples checkpoint test refactor checkpoint test refactor parsing atoms checkpoint atoms wip lists checkpoint typing lists checkopint checkpoint wip fixing correct list typing map discussion checkpoint map basic typing fix tests checkpoint checkpoint checkpoint checkpoint fix condition typing fix literal keys in map types checkpoint union types checkpoint union type checkpoint row types discussion & bidirectional typecheck checkpoint basic lambdas checkpoint lambdas typing application wip function application checkpoint checkpoint checkpoint cduce checkpoint checkpoint checkpoint checkpoint checkpoint checkpoint checkpoint
192 KiB
192 KiB
CDuce Source Code Reference
Below is the full content of relevant CDuce source files for context.
types/bdd.ml
type 'a line = 'a list * 'a list
type 'a dnf = 'a line list
type ('elem, 'leaf) bdd =
| False
| True
| Leaf of 'leaf
| Split of
int * 'elem * ('elem, 'leaf) bdd * ('elem, 'leaf) bdd * ('elem, 'leaf) bdd
let empty = False
type 'atom var_bdd = (Var.t, ('atom, bool) bdd) bdd
(* Ternary decision diagrams:
False → 𝟘
True → 𝟙
Leaf (v) → ⟦ v ⟧
Split (h, x, p, i, n) →
(⟦ x ⟧ ∩ ⟦ p ⟧) ∪ ⟦ i ⟧ ∪ (⟦ n ⟧ \ ⟦ x ⟧)
Invariants maintained (see simplify split and leaf smart constructors and simplify functions:)
- p ≠ n
- ∀ i st. t ≡ Split (h, x, p, Split (…, Split (_, _, i, _) …), n), p ≠ i
Indeed, given the equation above, if some bdd is in ⟦ i ⟧, it can be removed,
from ⟦ p ⟧ and ⟦ n ⟧ safely. A corollary is that if ⟦ i ⟧ contains True, both p and
n are useless, and therefore the whole bdd is replaced by true.
- if t ≡ Leaf (v), ⟦ v ⟧ ≠ 𝟘 and ⟦ v ⟧ ≠ 𝟙
(if v is recursively a bdd of atoms (while t is a bdd of variables) then
it means that v is not trivially False or True, but it could be a complex
bdd that is in fine equal to True, but we would need subtyping to decide this).
*)
module MK
(E : Custom.T) (Leaf : sig
include Tset.S
val iter : (elem -> unit) -> t -> unit
end) =
struct
type elem = E.t
type t = (E.t, Leaf.t) bdd
let test x =
match x with
| Leaf l -> Leaf.test l
| _ -> Tset.Unknown
let rec equal a b =
a == b
||
match (a, b) with
| Split (h1, x1, p1, i1, n1), Split (h2, x2, p2, i2, n2) ->
h1 == h2 && E.equal x1 x2 && equal p1 p2 && equal i1 i2 && equal n1 n2
| Leaf l1, Leaf l2 -> Leaf.equal l1 l2
| _ -> false
let rec compare a b =
if a == b then 0
else
match (a, b) with
| Split (h1, x1, p1, i1, n1), Split (h2, x2, p2, i2, n2) ->
if h1 < h2 then -1
else if h1 > h2 then 1
else
let c = E.compare x1 x2 in
if c <> 0 then c
else
let c = compare p1 p2 in
if c <> 0 then c
else
let c = compare i1 i2 in
if c <> 0 then c else compare n1 n2
| Leaf l1, Leaf l2 -> Leaf.compare l1 l2
| True, _ -> -1
| _, True -> 1
| False, _ -> -1
| _, False -> 1
| Leaf _, Split _ -> 1
| Split _, Leaf _ -> -1
let hash = function
| False -> 0
| True -> 1
| Leaf l -> 17 * Leaf.hash l
| Split (h, _, _, _, _) -> h
let compute_hash x p i n =
let h = E.hash x in
let h = h lxor (257 * hash p) in
let h = h lxor (8191 * hash i) in
let h = h lxor (16637 * hash n) in
h
let rec check = function
| True
| False ->
()
| Leaf l -> Leaf.check l
| Split (h, x, p, i, n) ->
assert (h = compute_hash x p i n);
(match p with
| Split (_, y, _, _, _) -> assert (E.compare x y < 0)
| _ -> ());
(match i with
| Split (_, y, _, _, _) -> assert (E.compare x y < 0)
| _ -> ());
(match n with
| Split (_, y, _, _, _) -> assert (E.compare x y < 0)
| _ -> ());
E.check x;
check p;
check i;
check n
let any = True
let empty = empty
let leaf l =
match Leaf.test l with
| Tset.Empty -> empty
| Tset.Full -> any
| _ -> Leaf l
let rec iter_partial fe fleaf = function
| Split (_, x, p, i, n) ->
fe x;
iter_partial fe fleaf p;
iter_partial fe fleaf i;
iter_partial fe fleaf n
| Leaf leaf -> fleaf leaf
| _ -> ()
let iter_full fe fl = iter_partial fe (Leaf.iter fl)
let rec dump s ppf = function
| False -> Format.fprintf ppf "F"
| True -> Format.fprintf ppf "T"
| Leaf l -> Format.fprintf ppf "%s%a" s Leaf.dump l
| Split (_, x, p, i, n) ->
Format.fprintf ppf "@[%s%a@[(@[%a@],@,@[%a@],@,@[%a@])@]@]" s E.dump x
(dump "+") p (dump "=") i (dump "-") n
let dump ppf a = (dump "") ppf a
let rec get accu pos neg = function
| False -> accu
| True -> ((pos, neg), Leaf.any) :: accu
| Leaf leaf -> ((pos, neg), leaf) :: accu
| Split (_, x, p, i, n) ->
(*OPT: can avoid creating this list cell when pos or neg =False *)
let accu = get accu (x :: pos) neg p in
let accu = get accu pos (x :: neg) n in
let accu = get accu pos neg i in
accu
let get (l : t) = get [] [] [] l
let compute_full ~empty ~any ~cup ~cap ~diff ~atom ~leaf b =
let rec aux t =
match t with
| False -> empty
| True -> any
| Leaf l -> leaf l
| Split (_, x, p, i, n) ->
let atx = atom x in
let p = cap atx (aux p)
and i = aux i
and n = diff (aux n) atx in
cup (cup p i) n
in
aux b
let split0 x pos ign neg = Split (compute_hash x pos ign neg, x, pos, ign, neg)
let atom x = split0 x any empty empty
(* Smart constructor for split, ensures that the envariants are preserved
with the use of simplify *)
let rec split x p i n =
if i == True then any
else if equal p n then if equal p i then p else p ++ i
else
let p = simplify p [ i ]
and n = simplify n [ i ] in
if equal p n then if equal p i then p else p ++ i else split0 x p i n
(* ensures that a does not contain any of the bdds in l,
if it does, replace it does, remove it. *)
and simplify a l =
match a with
| False -> empty
| Split (_, x, p, i, n) -> restrict_from_list a x p i n [] [] [] False l
| _ -> restrict_true_or_same a l
and restrict_true_or_same a = function
| [] -> a
| True :: _ -> empty
| b :: l -> if equal a b then empty else restrict_true_or_same a l
(* traverses a list of bdds b and
accumulate in ap ai an those that might be present in each
component p, i, n of a (a ≡ Split (h, x, p, i, n)) the arguments
are passed to avoid reconstructing it.
The _dummy argument keeps the arguments of both functions
in the same order which prevent ocaml from spilling too much on the stack.
*)
and restrict_from_list a x p i n ap ai an _dummy = function
| [] ->
let p = simplify p ap
and n = simplify n an
and i = simplify i ai in
if equal p n then p ++ i else split0 x p i n
| b :: l -> restrict_elem a x p i n ap ai an b l
and restrict_elem a x p i n ap ai an b l =
match b with
| False -> restrict_from_list a x p i n ap ai an b l
| True -> empty
| Leaf _ as b ->
(* inline then next case, knowing that a = Split ...
and b is Leaf b is greater*)
restrict_from_list a x p i n (b :: ap) (b :: ai) (b :: an) b l
| Split (_, x2, p2, i2, n2) as b ->
let c = E.compare x2 x in
if c < 0 then restrict_elem a x p i n ap ai an i2 l
else if c > 0 then
restrict_from_list a x p i n (b :: ap) (b :: ai) (b :: an) b l
else if equal a b then empty
else
restrict_from_list a x p i n (p2 :: i2 :: ap) (i2 :: ai)
(n2 :: i2 :: an) b l
and ( ++ ) a b =
if a == b then a
else
match (a, b) with
| True, _
| _, True ->
any
| False, _ -> b
| _, False -> a
| Leaf l1, Leaf l2 -> leaf (Leaf.cup l1 l2)
| Split (_, x1, p1, i1, n1), Split (_, x2, p2, i2, n2) ->
let c = E.compare x1 x2 in
if c = 0 then split x1 (p1 ++ p2) (i1 ++ i2) (n1 ++ n2)
else if c < 0 then split x1 p1 (i1 ++ b) n1
else split x2 p2 (i2 ++ a) n2
| Split (_, x1, p1, i1, n1), Leaf _ -> split x1 p1 (i1 ++ b) n1
| Leaf _, Split (_, x2, p2, i2, n2) -> split x2 p2 (i2 ++ a) n2
let rec ( ** ) a b =
if a == b then a
else
match (a, b) with
| False, _
| _, False ->
empty
| True, _ -> b
| _, True -> a
| Leaf l1, Leaf l2 -> leaf (Leaf.cap l1 l2)
| Split (_, x1, p1, i1, n1), Split (_, x2, p2, i2, n2) ->
let c = E.compare x1 x2 in
if c = 0 then
split x1
((p1 ** (p2 ++ i2)) ++ (p2 ** i1))
(i1 ** i2)
((n1 ** (n2 ++ i2)) ++ (n2 ** i1))
else if c < 0 then split x1 (p1 ** b) (i1 ** b) (n1 ** b)
else split x2 (p2 ** a) (i2 ** a) (n2 ** a)
| Split (_, x1, p1, i1, n1), Leaf _ ->
split x1 (p1 ** b) (i1 ** b) (n1 ** b)
| Leaf _, Split (_, x2, p2, i2, n2) ->
split x2 (p2 ** a) (i2 ** a) (n2 ** a)
let rec neg = function
| False -> any
| True -> empty
| Leaf l -> leaf (Leaf.neg l)
| Split (_, x, p, i, False) -> split x empty (neg (i ++ p)) (neg i)
| Split (_, x, False, i, n) -> split x (neg i) (neg (i ++ n)) empty
| Split (_, x, p, False, n) -> split x (neg p) (neg (p ++ n)) (neg n)
| Split (_, x, p, i, n) -> neg i ** split x (neg p) (neg (p ++ n)) (neg n)
let rec ( // ) a b =
if a == b then empty
else
match (a, b) with
| False, _
| _, True ->
empty
| _, False -> a
| True, _ -> neg b
| Leaf l1, Leaf l2 -> leaf (Leaf.diff l1 l2)
| Split (_, x1, p1, i1, n1), Split (_, x2, p2, i2, n2) ->
let c = E.compare x1 x2 in
if c = 0 then
if i2 == empty && n2 == empty then
split x1 (p1 // p2) (i1 // p2) (n1 ++ i1)
else
split x1
((p1 ++ i1) // (p2 ++ i2))
empty
((n1 ++ i1) // (n2 ++ i2))
else if c < 0 then split x1 (p1 // b) (i1 // b) (n1 // b)
else split x2 (a // (i2 ++ p2)) empty (a // (i2 ++ n2))
| _ -> a ** neg b
let ( ~~ ) = neg
let cup = ( ++ )
let cap = ( ** )
let diff = ( // )
let extract_var = function
| Split
( _,
v,
((False | True | Leaf _) as p),
((False | True | Leaf _) as i),
((False | True | Leaf _) as n) ) ->
Some (v, p, i, n)
| _ -> None
end
module Bool : sig
include Tset.S with type t = bool and type elem = bool
val iter : (elem -> unit) -> t -> unit
end = struct
include Custom.Bool
type elem = bool
let empty = false
let any = true
let test (x : bool) : Tset.cardinal = Obj.magic x
let atom b = b
let cup a b = a || b
let cap a b = a && b
let neg a = not a
let diff a b = a && not b
let iter _ _ = ()
end
module type S = sig
type atom
(** The type of atoms in the Boolean combinations *)
type mono
(** The type of Boolean combinations of atoms. *)
include Custom.T with type t = (Var.t, mono) bdd
type line
(** An explicit representation of conjunctions of atoms. *)
type dnf
(** An explicit representation fo the DNF. *)
val atom : atom -> t
val mono : mono -> t
val mono_dnf : mono -> dnf
val any : t
val empty : t
val cup : t -> t -> t
val cap : t -> t -> t
val diff : t -> t -> t
val neg : t -> t
val get : t -> dnf
val get_mono : t -> mono
val iter : (atom -> unit) -> t -> unit
val compute :
empty:'b ->
any:'b ->
cup:('b -> 'b -> 'b) ->
cap:('b -> 'b -> 'b) ->
diff:('b -> 'b -> 'b) ->
atom:(atom -> 'b) ->
t ->
'b
val var : Var.t -> t
val extract_var : t -> (Var.t * t * t * t) option
(** {2 Polymorphic interface. }*)
val get_partial : t -> ((Var.t list * Var.t list) * mono) list
val get_full : t -> ((Var.t list * Var.t list) * line) list
val iter_partial : (Var.t -> unit) -> (mono -> unit) -> t -> unit
val iter_full : (Var.t -> unit) -> (atom -> unit) -> t -> unit
val compute_partial :
empty:'b ->
any:'b ->
cup:('b -> 'b -> 'b) ->
cap:('b -> 'b -> 'b) ->
diff:('b -> 'b -> 'b) ->
mono:(mono -> 'b) ->
var:(Var.t -> 'b) ->
t ->
'b
val compute_full :
empty:'b ->
any:'b ->
cup:('b -> 'b -> 'b) ->
cap:('b -> 'b -> 'b) ->
diff:('b -> 'b -> 'b) ->
atom:(atom -> 'b) ->
var:(Var.t -> 'b) ->
t ->
'b
val ( ++ ) : t -> t -> t
val ( ** ) : t -> t -> t
val ( // ) : t -> t -> t
val ( ~~ ) : t -> t
end
module Make (E : Custom.T) :
S
with type atom = E.t
and type line = E.t list * E.t list
and type dnf = (E.t list * E.t list) list
and type mono = (E.t, Bool.t) bdd = struct
module Atom = MK (E) (Bool)
type atom = E.t
type mono = (E.t, Bool.t) bdd
include
MK
(Var)
(struct
include Atom
let iter f b = iter_full f ignore b
end)
type line = E.t list * E.t list
type dnf = (E.t list * E.t list) list
let var x = atom x
let atom x = leaf (Atom.atom x)
let mono x = leaf x
let get_aux combine acc b =
List.fold_left
(fun acc ((p, n), dnf) ->
let p = List.rev p in
let n = List.rev n in
let dnf = Atom.get dnf in
List.fold_left
(fun acc ((p2, n2), b) -> if b then combine p n p2 n2 acc else acc)
acc dnf)
acc (get b)
let get_full = get_aux (fun p n p2 n2 acc -> ((p, n), (p2, n2)) :: acc) []
let mono_dnf mono =
let l = Atom.get mono in
List.map fst l
let get_partial b : ((Var.t list * Var.t list) * mono) list =
List.rev_map (fun ((p, n), m) -> ((List.rev p, List.rev n), m)) (get b)
let get b = get_aux (fun _ _ p2 n2 acc -> (p2, n2) :: acc) [] b
let get_mono b : mono =
List.fold_left
(fun acc (_, dnf) -> Atom.cup acc dnf)
Atom.empty (get_partial b)
(* Temporary List.rev to order elements as before introduction of
polymorphic variables.
*)
let iter = iter_full (fun _ -> ())
let compute_partial ~empty ~any ~cup ~cap ~diff ~mono ~(var : Var.t -> 'a) b =
compute_full ~empty ~any ~cup ~cap ~diff ~atom:var ~leaf:mono b
let compute_full ~empty ~any ~cup ~cap ~diff ~atom ~(var : Var.t -> 'a) b =
compute_full ~empty ~any ~cup ~cap ~diff ~atom:var
~leaf:
(Atom.compute_full ~empty ~any ~cup ~cap ~diff ~atom ~leaf:(function
| false -> empty
| true -> any))
b
let compute ~empty ~any ~cup ~cap ~diff ~atom b =
compute_full ~empty ~any ~cup ~cap ~diff ~atom ~var:(fun _ -> any) b
end
module MKBasic (T : Tset.S) :
S with type atom = T.elem and type mono = T.t and type dnf = T.t = struct
type atom = T.elem
type mono = T.t
include
MK
(Var)
(struct
include T
let iter _ _ = assert false
end)
type line
type dnf = T.t
let var v = atom v
let mono x = leaf x
let atom x = leaf (T.atom x)
let get_partial t =
List.map (fun ((p, n), m) -> ((List.rev p, List.rev n), m)) (get t)
let mono_dnf x = x
let get_mono t =
List.fold_left (fun acc ((_, _), dnf) -> T.cup acc dnf) T.empty (get t)
let get _ = assert false
let get_full _ = assert false
let compute_partial ~empty ~any ~cup ~cap ~diff ~mono ~var b =
compute_full ~empty ~any ~cup ~cap ~diff ~atom:var ~leaf:mono b
let[@ocaml.warning "-27"] compute_full
~empty
~any
~cup
~cap
~diff
~atom
~var
b =
assert false
let[@ocaml.warning "-27"] compute ~empty ~any ~cup ~cap ~diff ~atom b =
assert false
let iter _ _ = assert false
let iter_full _ _ _ = assert false
end
module VarIntervals = MKBasic (Intervals)
module VarCharSet = MKBasic (CharSet)
module VarAtomSet = MKBasic (AtomSet)
module VarAbstractSet = MKBasic (AbstractSet)
types/types.ml
let std_compare = compare
open Ident
module U = Encodings.Utf8
let count = ref 0
let () =
Stats.register Stats.Summary (fun ppf ->
Format.fprintf ppf "Allocated type nodes:%i@\n" !count)
(*
To be sure not to use generic comparison ...
*)
let ( = ) : int -> int -> bool = ( == )
let ( < ) : int -> int -> bool = ( < )
let[@ocaml.warning "-32"] ( <= ) : int -> int -> bool = ( <= )
let[@ocaml.warning "-32"] ( <> ) : int -> int -> bool = ( <> )
let[@ocaml.warning "-32"] compare = 1
type const =
| Integer of Intervals.V.t
| Atom of AtomSet.V.t
| Char of CharSet.V.t
| Pair of const * const
| Xml of const * const
| Record of const label_map
| String of U.uindex * U.uindex * U.t * const
module Const = struct
type t = const
let check _ = ()
let dump ppf _ = Format.fprintf ppf "<Types.Const.t>"
let rec compare c1 c2 =
match (c1, c2) with
| Integer x, Integer y -> Intervals.V.compare x y
| Integer _, _ -> -1
| _, Integer _ -> 1
| Atom x, Atom y -> AtomSet.V.compare x y
| Atom _, _ -> -1
| _, Atom _ -> 1
| Char x, Char y -> CharSet.V.compare x y
| Char _, _ -> -1
| _, Char _ -> 1
| Pair (x1, x2), Pair (y1, y2) ->
let c = compare x1 y1 in
if c <> 0 then c else compare x2 y2
| Pair (_, _), _ -> -1
| _, Pair (_, _) -> 1
| Xml (x1, x2), Xml (y1, y2) ->
let c = compare x1 y1 in
if c <> 0 then c else compare x2 y2
| Xml (_, _), _ -> -1
| _, Xml (_, _) -> 1
| Record x, Record y -> LabelMap.compare compare x y
| Record _, _ -> -1
| _, Record _ -> 1
| String (i1, j1, s1, r1), String (i2, j2, s2, r2) ->
let c = std_compare i1 i2 in
if c <> 0 then c
else
let c = std_compare j1 j2 in
if c <> 0 then c
else
let c = U.compare s1 s2 in
if c <> 0 then c
(* Should compare
only the substring *)
else compare r1 r2
let rec hash = function
| Integer x -> 1 + (17 * Intervals.V.hash x)
| Atom x -> 2 + (17 * AtomSet.V.hash x)
| Char x -> 3 + (17 * CharSet.V.hash x)
| Pair (x, y) -> 4 + (17 * hash x) + (257 * hash y)
| Xml (x, y) -> 5 + (17 * hash x) + (257 * hash y)
| Record x -> 6 + (17 * LabelMap.hash hash x)
| String (_, _, s, r) -> 7 + (17 * U.hash s) + (257 * hash r)
(* Note: improve hash for String *)
let equal c1 c2 = compare c1 c2 = 0
end
type pair_kind =
[ `Normal
| `XML
]
type descr = {
atoms : Bdd.VarAtomSet.t;
ints : Bdd.VarIntervals.t;
chars : Bdd.VarCharSet.t;
times : (node * node) Bdd.var_bdd;
xml : (node * node) Bdd.var_bdd;
arrow : (node * node) Bdd.var_bdd;
record : (bool * node label_map) Bdd.var_bdd;
abstract : Bdd.VarAbstractSet.t;
absent : bool;
mutable hash : int;
}
and node = {
id : int;
cu : Compunit.t;
mutable descr : descr;
}
let empty =
{
atoms = Bdd.empty;
ints = Bdd.empty;
chars = Bdd.empty;
times = Bdd.empty;
xml = Bdd.empty;
arrow = Bdd.empty;
record = Bdd.empty;
abstract = Bdd.empty;
absent = false;
hash = -1;
}
let todo_dump = Hashtbl.create 16
let forward_print = ref (fun _ _ -> assert false)
module Node = struct
type t = node
let check _ = ()
let dump ppf n =
if not (Hashtbl.mem todo_dump n.id) then
Hashtbl.add todo_dump n.id (n, false);
Format.fprintf ppf "X%i" n.id
let hash x = x.id + Compunit.hash x.cu
let compare x y =
let c = x.id - y.id in
if c = 0 then Compunit.compare x.cu y.cu else c
let equal x y = x == y || (x.id == y.id && Compunit.equal x.cu y.cu)
let mk id d = { id; cu = Compunit.current (); descr = d }
end
module BoolPair = Bdd.Make (Custom.Pair (Node) (Node))
module BoolRec = Bdd.Make (Custom.Pair (Custom.Bool) (LabelSet.MakeMap (Node)))
module Descr = struct
type t = descr
let _print_lst ppf =
List.iter (fun f ->
f ppf;
Format.fprintf ppf " |")
let hash a =
if a.hash >= 0 then a.hash
else
let accu = Bdd.VarCharSet.hash a.chars in
let accu = accu + (accu lsl 4) + Bdd.VarIntervals.hash a.ints in
let accu = accu + (accu lsl 4) + Bdd.VarAtomSet.hash a.atoms in
let accu = accu + (accu lsl 4) + BoolPair.hash a.times in
let accu = accu + (accu lsl 4) + BoolPair.hash a.xml in
let accu = accu + (accu lsl 4) + BoolPair.hash a.arrow in
let accu = accu + (accu lsl 4) + BoolRec.hash a.record in
let accu = accu + (accu lsl 4) + Bdd.VarAbstractSet.hash a.abstract in
let accu = if a.absent then accu + 5 else accu in
let accu = max_int land accu in
let () = a.hash <- accu in
accu
let equal a b =
a == b
|| hash a == hash b
&& Bdd.VarAtomSet.equal a.atoms b.atoms
&& Bdd.VarCharSet.equal a.chars b.chars
&& Bdd.VarIntervals.equal a.ints b.ints
&& BoolPair.equal a.times b.times
&& BoolPair.equal a.xml b.xml
&& BoolPair.equal a.arrow b.arrow
&& BoolRec.equal a.record b.record
&& Bdd.VarAbstractSet.equal a.abstract b.abstract
&& a.absent == b.absent
let compare a b =
if a == b then 0
else
let c = Bdd.VarAtomSet.compare a.atoms b.atoms in
if c <> 0 then c
else
let c = Bdd.VarCharSet.compare a.chars b.chars in
if c <> 0 then c
else
let c = Bdd.VarIntervals.compare a.ints b.ints in
if c <> 0 then c
else
let c = BoolPair.compare a.times b.times in
if c <> 0 then c
else
let c = BoolPair.compare a.xml b.xml in
if c <> 0 then c
else
let c = BoolPair.compare a.arrow b.arrow in
if c <> 0 then c
else
let c = BoolRec.compare a.record b.record in
if c <> 0 then c
else
let c = Bdd.VarAbstractSet.compare a.abstract b.abstract in
if c <> 0 then c else Bdd.Bool.compare a.absent b.absent
let check a =
Bdd.VarCharSet.check a.chars;
Bdd.VarIntervals.check a.ints;
Bdd.VarAtomSet.check a.atoms;
BoolPair.check a.times;
BoolPair.check a.xml;
BoolPair.check a.arrow;
BoolRec.check a.record;
Bdd.VarAbstractSet.check a.abstract;
()
let forward_any = ref empty
let dump_descr ppf d =
if equal d empty then Format.fprintf ppf "EMPTY"
else if equal d !forward_any then Format.fprintf ppf "ANY"
else
Format.fprintf ppf
"<@[types = @[%a@]@\n\
\ ints(@[%a@])@\n\
\ atoms(@[%a@])@\n\
\ chars(@[%a@])@\n\
\ abstract(@[%a@])@\n\
\ times(@[%a@])@\n\
\ xml(@[%a@])@\n\
\ record(@[%a@])@\n\
\ arrow(@[%a@])@\n\
\ absent=%b@]>" !forward_print d Bdd.VarIntervals.dump d.ints
Bdd.VarAtomSet.dump d.atoms Bdd.VarCharSet.dump d.chars
Bdd.VarAbstractSet.dump d.abstract BoolPair.dump d.times BoolPair.dump
d.xml BoolRec.dump d.record BoolPair.dump d.arrow d.absent
let dump ppf d =
Hashtbl.clear todo_dump;
dump_descr ppf d;
let continue = ref true in
let seen_list = ref [] in
while !continue do
continue := false;
Hashtbl.iter
(fun _ (n, seen) ->
if not seen then begin
seen_list := n :: !seen_list;
continue := true
end)
todo_dump;
List.iter
(fun n ->
Hashtbl.replace todo_dump n.id (n, true);
Format.fprintf ppf "@\n%a=@[" Node.dump n;
dump_descr ppf n.descr;
Format.fprintf ppf "@]")
!seen_list;
seen_list := []
done;
Hashtbl.clear todo_dump
end
module DescrHash = Hashtbl.Make (Descr)
module DescrMap = Map.Make (Descr)
module DescrSet = Set.Make (Descr)
module DescrSList = SortedList.Make (Descr)
let dummy_descr =
let () = incr count in
let loop = Node.mk !count empty in
let dummy =
{ empty with times = BoolPair.atom (loop, loop); absent = true; hash = -1 }
in
let () = loop.descr <- dummy in
dummy
let make () =
incr count;
let res = Node.mk !count dummy_descr in
res
let is_opened n = n.descr == dummy_descr
let define n d =
assert (n.descr == dummy_descr);
n.descr <- d
let cons d =
incr count;
Node.mk !count d
let any =
{
times = BoolPair.any;
xml = BoolPair.any;
arrow = BoolPair.any;
record = BoolRec.any;
ints = Bdd.VarIntervals.any;
atoms = Bdd.VarAtomSet.any;
chars = Bdd.VarCharSet.any;
abstract = Bdd.VarAbstractSet.any;
absent = false;
hash = -1;
}
let () = Descr.forward_any := any
module type Kind = sig
module Dnf : Bdd.S
val any : Descr.t
val get : Descr.t -> Dnf.mono
val get_vars : Descr.t -> Dnf.t
val mk : Dnf.t -> Descr.t
val update : Descr.t -> Dnf.t -> Descr.t
end
module Int = struct
module Dnf = Bdd.VarIntervals
let get_vars d = d.ints
let get d = Dnf.get_mono d.ints
let update t c = { t with ints = c; hash = -1 }
let mk c = update empty c
let any = mk (get_vars any)
end
module Abstract = struct
module Dnf = Bdd.VarAbstractSet
let get_vars d = d.abstract
let get d = Dnf.get_mono d.abstract
let update t c = { t with abstract = c; hash = -1 }
let mk c = update empty c
let any = mk (get_vars any)
end
module Atom = struct
module Dnf = Bdd.VarAtomSet
let get_vars d = d.atoms
let get d = Dnf.get_mono d.atoms
let update t c = { t with atoms = c; hash = -1 }
let mk c = update empty c
let any = mk (get_vars any)
end
module Char = struct
module Dnf = Bdd.VarCharSet
let get_vars d = d.chars
let get d = Dnf.get_mono d.chars
let update t c = { t with chars = c; hash = -1 }
let mk c = update empty c
let any = mk (get_vars any)
end
module Times = struct
module Dnf = BoolPair
let get d = Dnf.get_mono d.times
let get_vars d = d.times
let update t c = { t with times = c; hash = -1 }
let mk c = update empty c
let any = mk (get_vars any)
end
module Xml = struct
module Dnf = BoolPair
let get_vars d = d.xml
let get d = Dnf.get_mono (get_vars d)
let update t c = { t with xml = c; hash = -1 }
let mk c = update empty c
let any = mk (get_vars any)
end
module Function = struct
module Dnf = BoolPair
let get_vars d = d.arrow
let get d = Dnf.get_mono (get_vars d)
let update t c = { t with arrow = c; hash = -1 }
let mk c = update empty c
let any = mk (get_vars any)
end
module Rec = struct
module Dnf = BoolRec
let get_vars d = d.record
let get d = Dnf.get_mono (get_vars d)
let update t c = { t with record = c; hash = -1 }
let mk c = update empty c
let any = mk (get_vars any)
end
module Absent = struct
module Dnf = Bdd.Bool
let get d = d.absent
let update t c = { t with absent = c; hash = -1 }
let mk c = update empty c
let any = mk true
end
include Descr
let non_constructed =
{
any with
times = empty.times;
xml = empty.xml;
record = empty.record;
hash = -1;
}
let interval i = Int.mk (Int.Dnf.mono i)
let times x y = Times.mk (Times.Dnf.atom (x, y))
let xml x y = Xml.mk (Xml.Dnf.atom (x, y))
let arrow x y = Function.mk (Function.Dnf.atom (x, y))
let record label t = Rec.mk (Rec.Dnf.atom (true, LabelMap.singleton label t))
let record_fields (x : bool * node Ident.label_map) = Rec.mk (Rec.Dnf.atom x)
let atom a = Atom.mk (Atom.Dnf.mono a)
let char c = Char.mk (Char.Dnf.mono c)
let abstract a = Abstract.mk (Abstract.Dnf.mono a)
let get_abstract = Abstract.get
let var x =
{
times = BoolPair.var x;
xml = BoolPair.var x;
arrow = BoolPair.var x;
record = BoolRec.var x;
ints = Bdd.VarIntervals.var x;
atoms = Bdd.VarAtomSet.var x;
chars = Bdd.VarCharSet.var x;
abstract = Bdd.VarAbstractSet.var x;
absent = false;
hash = -1;
}
let cup x y =
if x == y then x
else
{
times = Times.Dnf.cup x.times y.times;
xml = Xml.Dnf.cup x.xml y.xml;
arrow = Function.Dnf.cup x.arrow y.arrow;
record = Rec.Dnf.cup x.record y.record;
ints = Int.Dnf.cup x.ints y.ints;
atoms = Atom.Dnf.cup x.atoms y.atoms;
chars = Char.Dnf.cup x.chars y.chars;
abstract = Abstract.Dnf.cup x.abstract y.abstract;
absent = Absent.Dnf.cup x.absent y.absent;
hash = -1;
}
let cap x y =
if x == y then x
else
{
times = Times.Dnf.cap x.times y.times;
xml = Xml.Dnf.cap x.xml y.xml;
arrow = Function.Dnf.cap x.arrow y.arrow;
record = Rec.Dnf.cap x.record y.record;
ints = Int.Dnf.cap x.ints y.ints;
atoms = Atom.Dnf.cap x.atoms y.atoms;
chars = Char.Dnf.cap x.chars y.chars;
abstract = Abstract.Dnf.cap x.abstract y.abstract;
absent = Absent.Dnf.cap x.absent y.absent;
hash = -1;
}
let diff x y =
if x == y then empty
else
{
times = Times.Dnf.diff x.times y.times;
xml = Xml.Dnf.diff x.xml y.xml;
arrow = Function.Dnf.diff x.arrow y.arrow;
record = Rec.Dnf.diff x.record y.record;
ints = Int.Dnf.diff x.ints y.ints;
atoms = Atom.Dnf.diff x.atoms y.atoms;
chars = Char.Dnf.diff x.chars y.chars;
abstract = Abstract.Dnf.diff x.abstract y.abstract;
absent = Absent.Dnf.diff x.absent y.absent;
hash = -1;
}
let descr n =
assert (n.descr != dummy_descr);
n.descr
let internalize n = n
let id n = n.id
let rec constant = function
| Integer i -> interval (Intervals.atom i)
| Atom a -> atom (AtomSet.atom a)
| Char c -> char (CharSet.atom c)
| Pair (x, y) -> times (const_node x) (const_node y)
| Xml (x, y) -> xml (const_node x) (const_node y)
| Record x -> record_fields (false, LabelMap.map const_node x)
| String (i, j, s, c) ->
if U.equal_index i j then constant c
else
let ch, i' = U.next s i in
constant (Pair (Char (CharSet.V.mk_int ch), String (i', j, s, c)))
and const_node c = cons (constant c)
let neg x = diff any x
module LabelS = Set.Make (Label)
let any_or_absent = { any with absent = true; hash = -1 }
let only_absent = { empty with absent = true; hash = -1 }
let get_single_record r =
let labs accu (_, r) =
List.fold_left (fun accu (l, _) -> LabelS.add l accu) accu (LabelMap.get r)
in
let extend descrs labs (o, r) =
let rec aux i labs r =
match labs with
| [] -> ()
| l1 :: labs -> (
match r with
| (l2, x) :: r when l1 == l2 ->
descrs.(i) <- cap descrs.(i) (descr x);
aux (i + 1) labs r
| r ->
if not o then descrs.(i) <- cap descrs.(i) only_absent;
(* TODO:OPT *)
aux (i + 1) labs r)
in
aux 0 labs (LabelMap.get r);
o
in
let line (p, n) =
let labels = List.fold_left labs (List.fold_left labs LabelS.empty p) n in
let labels = LabelS.elements labels in
let nlab = List.length labels in
let mk () = Array.make nlab any_or_absent in
let pos = mk () in
let opos =
List.fold_left (fun accu x -> extend pos labels x && accu) true p
in
let p = (opos, pos) in
let n =
List.map
(fun x ->
let neg = mk () in
let o = extend neg labels x in
(o, neg))
n
in
(labels, p, n)
in
line r
let[@ocaml.warning "-32"] get_record r = List.map get_single_record (BoolRec.get r)
let get_record_full r =
List.map (fun (vars, r) ->
vars, get_single_record r)
Rec.(r |> get_vars |> Dnf.get_full)
(* Subtyping algorithm *)
let diff_t d t = diff d (descr t)
let cap_t d t = cap d (descr t)
let cap_product any_left any_right l =
List.fold_left
(fun (d1, d2) (t1, t2) -> (cap_t d1 t1, cap_t d2 t2))
(any_left, any_right) l
let any_pair = { empty with times = any.times; hash = -1 }
let rec exists max f = max > 0 && (f (max - 1) || exists (max - 1) f)
exception NotEmpty
module Witness = struct
module NodeSet = Set.Make (Node)
type witness =
| WInt of Intervals.V.t
| WAtom of AtomSet.sample
| WChar of CharSet.V.t
| WAbsent
| WAbstract of AbstractSet.elem option
| WPair of witness * witness * witness_slot
| WXml of witness * witness * witness_slot
| WRecord of witness label_map * bool * witness_slot
(* Invariant: WAbsent cannot actually appear *)
| WFun of (witness * witness option) list * witness_slot
(* Poly *)
| WPoly of Var.Set.t * Var.Set.t * witness
and witness_slot = {
mutable wnodes_in : NodeSet.t;
mutable wnodes_out : NodeSet.t;
mutable wuid : int;
}
module WHash = Hashtbl.Make (struct
type t = witness
let rec hash_small = function
| WInt i -> 17 * Intervals.V.hash i
| WChar c -> 1 + (17 * CharSet.V.hash c)
| WAtom None -> 2
| WAtom (Some (ns, None)) -> 3 + (17 * Ns.Uri.hash ns)
| WAtom (Some (_, Some t)) -> 4 + (17 * Ns.Label.hash t)
| WAbsent -> 5
| WAbstract None -> 6
| WAbstract (Some t) -> 7 + (17 * AbstractSet.T.hash t)
| WPair (_, _, s)
| WXml (_, _, s)
| WRecord (_, _, s)
| WFun (_, s) ->
8 + (17 * s.wuid)
| WPoly (pos, neg, w) ->
9 + Var.Set.hash pos + 17 * Var.Set.hash neg + 257 * hash_small w
let hash = function
| WPair (p1, p2, _) -> (257 * hash_small p1) + (65537 * hash_small p2)
| WXml (p1, p2, _) -> 1 + (257 * hash_small p1) + (65537 * hash_small p2)
| WRecord (r, o, _) ->
(if o then 2 else 3) + (257 * LabelMap.hash hash_small r)
| WFun (f, _) ->
4
+ 257
* Hashtbl.hash
(List.map
(function
| x, None -> 17 * hash_small x
| x, Some y ->
1 + (17 * hash_small x) + (257 * hash_small y))
f)
| _ -> assert false
let rec equal_small w1 w2 =
match (w1, w2) with
| WInt i1, WInt i2 -> Intervals.V.equal i1 i2
| WChar c1, WChar c2 -> CharSet.V.equal c1 c2
| WAtom None, WAtom None -> true
| WAtom (Some (ns1, None)), WAtom (Some (ns2, None)) ->
Ns.Uri.equal ns1 ns2
| WAtom (Some (_, Some t1)), WAtom (Some (_, Some t2)) ->
Ns.Label.equal t1 t2
| WAbsent, WAbsent -> true
| WAbstract None, WAbstract None -> false
| WAbstract (Some t1), WAbstract (Some t2) -> AbstractSet.T.equal t1 t2
| WPoly (p1, n1, w1), WPoly (p2, n2, w2) ->
Var.Set.equal p1 p2 &&
Var.Set.equal n1 n2 &&
equal_small w1 w2
| _ -> w1 == w2
let equal w1 w2 =
match (w1, w2) with
| WPair (p1, q1, _), WPair (p2, q2, _)
| WXml (p1, q1, _), WXml (p2, q2, _) ->
equal_small p1 p2 && equal_small q1 q2
| WRecord (r1, o1, _), WRecord (r2, o2, _) ->
o1 == o2 && LabelMap.equal equal_small r1 r2
| WFun (f1, _), WFun (f2, _) ->
List.length f1 = List.length f2
&& List.for_all2
(fun (x1, y1) (x2, y2) ->
equal_small x1 x2
&&
match (y1, y2) with
| Some y1, Some y2 -> equal_small y1 y2
| None, None -> true
| _ -> false)
f1 f2
| _ -> false
end)
let wmemo = WHash.create 1024
let wuid = ref 0
let wslot () =
{ wuid = !wuid; wnodes_in = NodeSet.empty; wnodes_out = NodeSet.empty }
let () =
Stats.register Stats.Summary (fun ppf ->
Format.fprintf ppf "Allocated witnesses:%i@\n" !wuid)
let rec print_witness ppf = function
| WInt i -> Format.fprintf ppf "%a" Intervals.V.print i
| WChar c -> Format.fprintf ppf "%a" CharSet.V.print c
| WAtom None -> Format.fprintf ppf "`#:#"
| WAtom (Some (ns, None)) ->
Format.fprintf ppf "`%a" Ns.InternalPrinter.print_any_ns ns
| WAtom (Some (_, Some t)) -> Format.fprintf ppf "`%a" Ns.Label.print_attr t
| WPair (w1, w2, _) ->
Format.fprintf ppf "(%a,%a)" print_witness w1 print_witness w2
| WXml (w1, w2, _) ->
Format.fprintf ppf "XML(%a,%a)" print_witness w1 print_witness w2
| WRecord (ws, o, _) ->
Format.fprintf ppf "{";
LabelMap.iteri
(fun l w ->
Format.fprintf ppf " %a=%a" Label.print_attr l print_witness w)
ws;
if o then Format.fprintf ppf " ..";
Format.fprintf ppf " }"
| WFun (f, _) ->
Format.fprintf ppf "FUN{";
List.iter
(fun (x, y) ->
Format.fprintf ppf " %a->" print_witness x;
match y with
| None -> Format.fprintf ppf "#"
| Some y -> print_witness ppf y)
f;
Format.fprintf ppf " }"
| WAbstract None -> Format.fprintf ppf "Abstract(..)"
| WAbstract (Some s) -> Format.fprintf ppf "Abstract(%s)" s
| WAbsent -> Format.fprintf ppf "Absent"
| WPoly (pos, neg, w) ->
Format.fprintf ppf "Poly(%a, %a, %a)"
Var.Set.print pos
Var.Set.print neg
print_witness w
let wmk w =
(* incr wuid; w *)
(* hash-consing disabled *)
try WHash.find wmemo w with
| Not_found ->
incr wuid;
WHash.add wmemo w w;
(* Format.fprintf Format.std_formatter "W:%a@."
print_witness w; *)
w
let wpair p1 p2 = wmk (WPair (p1, p2, wslot ()))
let wxml p1 p2 = wmk (WXml (p1, p2, wslot ()))
let wrecord r o = wmk (WRecord (r, o, wslot ()))
let wfun f = wmk (WFun (f, wslot ()))
(* A witness with variables wpos wneg
belongs to type with variables tpos tneg
if :
- wpos and tneg are disjoint (a witness 'a&0 cannot belong
to a type T\'a)
- likewise for wneg and tpos
- wpos is a superset of tpos, that is 'a&'b&'c&42 is
belongs to the type 'a&Int
- wneg is a super set of tneg
*)
let subset_vars wpos wneg tpos tneg =
let sneg = Var.Set.from_list tneg in
Var.Set.disjoint wpos sneg &&
Var.Set.subset sneg wneg &&
let spos = Var.Set.from_list tpos in
Var.Set.disjoint wneg spos &&
Var.Set.subset spos wpos
let basic_dnf (type mono) (module M : Kind with type Dnf.mono = mono)
pos neg t f =
M.get_vars t
|> M.Dnf.get_partial
|> List.exists (fun ((vp, vn), mono) ->
subset_vars pos neg vp vn &&
f mono)
let full_dnf (type atom) (module M : Kind
with type Dnf.atom = atom
and type Dnf.line = atom list * atom list)
pos neg t f =
M.get_vars t
|> M.Dnf.get_full
|> List.exists (fun ((vp, vn), (ap, an)) ->
subset_vars pos neg vp vn &&
List.for_all f ap &&
not (List.exists f an)
)
let rec node_has n = function
| (WXml (_, _, s) | WPair (_, _, s) | WFun (_, s) | WRecord (_, _, s)) as w
->
if NodeSet.mem n s.wnodes_in then true
else if NodeSet.mem n s.wnodes_out then false
else
let r = type_has (descr n) w in
if r then s.wnodes_in <- NodeSet.add n s.wnodes_in
else s.wnodes_out <- NodeSet.add n s.wnodes_out;
r
| w -> type_has (descr n) w
and type_has t w =
let pos, neg, w =
match w with
WPoly (pos, neg, w) -> pos, neg, w
| _ -> Var.Set.empty, Var.Set.empty, w
in
match w with
WPoly _ -> assert false
| WInt i ->
basic_dnf (module Int) pos neg t (Intervals.contains i)
| WChar c ->
basic_dnf (module Char) pos neg t (CharSet.contains c)
| WAtom a ->
basic_dnf (module Atom) pos neg t (AtomSet.contains_sample a)
| WAbsent -> t.absent
| WAbstract a ->
basic_dnf (module Abstract) pos neg t (AbstractSet.contains_sample a)
| WPair (w1, w2, _) ->
full_dnf (module Times) pos neg t
(fun (n1, n2) -> node_has n1 w1 && node_has n2 w2)
| WXml (w1, w2, _) ->
full_dnf (module Xml) pos neg t
(fun (n1, n2) -> node_has n1 w1 && node_has n2 w2)
| WFun (f, _) ->
full_dnf (module Function) pos neg t
(fun (n1, n2) ->
List.for_all
(fun (x, y) ->
(not (node_has n1 x))
||
match y with
| None -> false
| Some y -> node_has n2 y)
f)
| WRecord (f, o, _) ->
full_dnf (module Rec) pos neg t
(fun (o', f') ->
((not o) || o')
&&
let checked = ref 0 in
try
LabelMap.iteri
(fun l n ->
let w =
try
let w = LabelMap.assoc l f in
incr checked;
w
with
| Not_found -> WAbsent
in
if not (node_has n w) then raise Exit)
f';
o' || LabelMap.length f == !checked
(* All the remaining fields cannot be WAbsent
because of an invariant. Otherwise, we must
check that all are WAbsent here. *)
with
| Exit -> false)
end
type slot = {
mutable status : status;
mutable notify : notify;
mutable active : bool;
}
and status =
| Empty
| NEmpty of Witness.witness
| Maybe
and notify =
| Nothing
| Do of slot * (Witness.witness -> unit) * notify
let slot_nempty w = { status = NEmpty w; active = false; notify = Nothing }
let rec notify w = function
| Nothing -> ()
| Do (n, f, rem) ->
(if n.status == Maybe then
try f w with
| NotEmpty -> ());
notify w rem
let mk_poly vars w =
match vars with
[], [] -> w
| p, n -> Witness.WPoly (Var.Set.from_list p,
Var.Set.from_list n, w)
let rec iter_s s f = function
| [] -> ()
| (x,y) :: rem ->
f x y s;
iter_s s f rem
let set s w =
s.status <- NEmpty w;
notify w s.notify;
s.notify <- Nothing;
raise NotEmpty
let rec big_conj f l n w =
match l with
| [] -> set n w
| [ arg ] -> f w arg n
| arg :: rem -> (
let s =
{
status = Maybe;
active = false;
notify = Do (n, big_conj f rem n, Nothing);
}
in
try
f w arg s;
if s.active then n.active <- true
with
| NotEmpty when n.status == Empty || n.status == Maybe -> ())
let memo = DescrHash.create 8191
let marks = ref []
let count_subtype = Stats.Counter.create "Subtyping internal loop"
let rec find_map f = function
[] -> None
| e :: l ->
match f e with
None -> find_map f l
| r -> r
let check_basic (type mono) (module M : Kind with type Dnf.mono = mono) d is_empty mk =
M.get_vars d
|> M.Dnf.get_partial
|> find_map (fun ((pos, neg), m) ->
if is_empty m then None
else
let w = mk m in
let w = match pos, neg with
[], [] -> w
| _ -> Witness.WPoly (Var.Set.from_list pos,
Var.Set.from_list neg, w)
in
Some (slot_nempty w))
let (let*) o f =
match o with
Some e -> e
| None -> f ()
let rec slot d =
Stats.Counter.incr count_subtype;
if d == empty then { status = Empty; active = false; notify = Nothing }
else if d.absent then slot_nempty Witness.WAbsent
else
let* () = check_basic (module Int) d Intervals.is_empty
(fun i -> Witness.WInt (Intervals.sample i)) in
let* () = check_basic (module Atom) d AtomSet.is_empty
(fun a -> Witness.WAtom (AtomSet.sample a)) in
let* () = check_basic (module Char) d CharSet.is_empty
(fun c -> Witness.WChar (CharSet.sample c)) in
let* () = check_basic (module Abstract) d AbstractSet.is_empty
(fun a -> Witness.WAbstract (AbstractSet.sample a)) in
try DescrHash.find memo d with
| Not_found ->
let s = { status = Maybe; active = false; notify = Nothing } in
DescrHash.add memo d s;
(try
iter_s s check_times Times.(d |> get_vars |> Dnf.get_full);
iter_s s check_xml Xml.(d |> get_vars |> Dnf.get_full);
iter_s s check_arrow Function.(d |> get_vars |> Dnf.get_full);
iter_s s check_record (get_record_full d);
if s.active then marks := s :: !marks else s.status <- Empty
with
| NotEmpty -> ());
s
and guard n t f =
match slot t with
| { status = Empty; _ } -> ()
| { status = Maybe; _ } as s ->
n.active <- true;
s.notify <- Do (n, f, s.notify)
| { status = NEmpty v; _ } -> f v
and check_product vars any_right mk_witness (left, right) s =
let rec aux w1 w2 accu1 accu2 seen = function
(* Find a product in right which contains (w1,w2) *)
| [] ->
(* no such product: the current witness is in the difference. *)
set s (mk_poly vars (mk_witness w1 w2))
| (n1, n2) :: rest when Witness.node_has n1 w1 && Witness.node_has n2 w2 ->
let right = List.rev_append seen rest in
let accu2' = diff accu2 (descr n2) in
guard s accu2' (fun w2 -> aux w1 w2 accu1 accu2' [] right);
let accu1' = diff accu1 (descr n1) in
guard s accu1' (fun w1 -> aux w1 w2 accu1' accu2 [] right)
| k :: rest -> aux w1 w2 accu1 accu2 (k :: seen) rest
in
let t1, t2 = cap_product any any_right left in
guard s t1 (fun w1 -> guard s t2 (fun w2 -> aux w1 w2 t1 t2 [] right))
and check_times vars prod s = check_product vars any Witness.wpair prod s
and check_xml vars prod s = check_product vars any_pair Witness.wxml prod s
and check_arrow (vpos, vneg) (left, right) s =
let single_right f (s1, s2) s =
let rec aux w1 w2 accu1 accu2 left =
match left with
| (t1, t2) :: left ->
let accu1' = diff_t accu1 t1 in
guard s accu1' (fun w1 -> aux w1 w2 accu1' accu2 left);
let accu2' = cap_t accu2 t2 in
guard s accu2' (fun w2 -> aux w1 (Some w2) accu1 accu2' left)
| [] ->
let op, on, f =
match f with
| Witness.WFun (f, _) -> [], [], f
| Witness.WPoly (op, on, Witness.WFun (f, _)) ->
Var.Set.(get op, get on, f)
| _ -> assert false
in
set s (mk_poly (op @ vpos, on@vneg) (Witness.wfun ((w1, w2) :: f)))
in
let accu1 = descr s1 in
guard s accu1 (fun w1 -> aux w1 None accu1 (neg (descr s2)) left)
in
big_conj single_right right s (Witness.wfun [])
and check_record vars (labels, (oleft, left), rights) s =
let rec aux ws accus seen = function
| [] ->
let rec aux w i = function
| [] ->
assert (i == Array.length ws);
w
| l :: labs ->
let w =
match ws.(i) with
| Witness.WAbsent -> w
| wl -> LabelMap.add l wl w
in
aux w (succ i) labs
in
set s (mk_poly vars (Witness.wrecord (aux LabelMap.empty 0 labels) oleft))
| (false, _) :: rest when oleft -> aux ws accus seen rest
| (_, f) :: rest
when not
(exists (Array.length left) (fun i ->
not (Witness.type_has f.(i) ws.(i)))) ->
(* TODO: a version f get_record which keeps nodes in neg records. *)
let right = seen @ rest in
for i = 0 to Array.length left - 1 do
let di = diff accus.(i) f.(i) in
guard s di (fun wi ->
let accus' = Array.copy accus in
accus'.(i) <- di;
let ws' = Array.copy ws in
ws'.(i) <- wi;
aux ws' accus' [] right)
done
| k :: rest -> aux ws accus (k :: seen) rest
in
let rec start wl i =
if i < 0 then aux (Array.of_list wl) left [] rights
else guard s left.(i) (fun w -> start (w :: wl) (i - 1))
in
start [] (Array.length left - 1)
let timer_subtype = Stats.Timer.create "Types.is_empty"
let is_empty d =
Stats.Timer.start timer_subtype;
let s = slot d in
List.iter
(fun s' ->
if s'.status == Maybe then s'.status <- Empty;
s'.notify <- Nothing)
!marks;
marks := [];
Stats.Timer.stop timer_subtype (s.status == Empty)
let getwit t =
match (slot t).status with
| NEmpty w -> w
| _ -> assert false
(* Assumes that is_empty has been called on t before. *)
let witness t = if is_empty t then raise Not_found else getwit t
let print_witness ppf t = Witness.print_witness ppf (witness t)
let non_empty d = not (is_empty d)
let disjoint d1 d2 = is_empty (cap d1 d2)
let subtype d1 d2 = is_empty (diff d1 d2)
let equiv d1 d2 = subtype d1 d2 && subtype d2 d1
(* redefine operations to take subtyping into account and perform hash consing *)
let forward_pointers = DescrHash.create 16
module NL = SortedList.Make (Node)
let get_all n =
if is_opened n then NL.singleton n
else
try DescrHash.find forward_pointers (descr n) with
| Not_found -> NL.empty
let add t n =
let lold =
try DescrHash.find forward_pointers t with
| Not_found -> NL.empty
in
DescrHash.replace forward_pointers t (NL.cup n lold)
let times n1 n2 =
let f1 = get_all n1 in
let f2 = get_all n2 in
let t = times n1 n2 in
add t f1;
add t f2;
t
module Cache = struct
(*
let type_has_witness t w =
Format.fprintf Format.std_formatter
"check wit:%a@." print_witness w;
let r = type_has_witness t w in
Format.fprintf Format.std_formatter "Done@.";
r
*)
type 'a cache =
| Empty
| Type of t * 'a
| Split of Witness.witness * 'a cache * 'a cache
let rec find f t = function
| Empty ->
let r = f t in
(Type (t, r), r)
| Split (w, yes, no) ->
if Witness.type_has t w then
let yes, r = find f t yes in
(Split (w, yes, no), r)
else
let no, r = find f t no in
(Split (w, yes, no), r)
| Type (s, rs) as c -> (
let f1 () =
let w = witness (diff t s) in
let rt = f t in
(Split (w, Type (t, rt), c), rt)
and f2 () =
let w = witness (diff s t) in
let rt = f t in
(Split (w, c, Type (t, rt)), rt)
in
if Random.int 2 = 0 then
try f1 () with
| Not_found -> (
try f2 () with
| Not_found -> (c, rs))
else
try f2 () with
| Not_found -> (
try f1 () with
| Not_found -> (c, rs)))
let rec lookup t = function
| Empty -> None
| Split (w, yes, no) -> lookup t (if Witness.type_has t w then yes else no)
| Type (s, rs) -> if equiv s t then Some rs else None
let emp = Empty
let[@ocaml.warning "-32"] rec dump_cache f ppf = function
| Empty -> Format.fprintf ppf "Empty"
| Type (_, s) -> Format.fprintf ppf "*%a" f s
| Split (_, c1, c2) ->
Format.fprintf ppf "?(%a,%a)"
(*Witness.print_witness w *) (dump_cache f)
c1 (dump_cache f) c2
let memo f =
let c = ref emp in
fun t ->
let c', r = find f t !c in
c := c';
r
end
module Product = struct
type t = (descr * descr) list
let _other ?(kind = `Normal) d =
match kind with
| `Normal -> { d with times = empty.times; hash = -1 }
| `XML -> { d with xml = empty.xml; hash = -1 }
let _is_product ?kind d = is_empty (_other ?kind d)
let need_second = function
| _ :: _ :: _ -> true
| _ -> false
let normal_aux = function
| ([] | [ _ ]) as d -> d
| d ->
let res = ref [] in
let add (t1, t2) =
let rec loop t1 t2 = function
| [] -> res := ref (t1, t2) :: !res
| ({ contents = d1, d2 } as r) :: l ->
(*OPT*)
(* if equal_descr d1 t1 then r := (d1,cup d2 t2) else*)
let i = cap t1 d1 in
if is_empty i then loop t1 t2 l
else (
r := (i, cup t2 d2);
let k = diff d1 t1 in
if non_empty k then res := ref (k, d2) :: !res;
let j = diff t1 d1 in
if non_empty j then loop j t2 l)
in
loop t1 t2 !res
in
List.iter add d;
List.map ( ! ) !res
(* Partitioning:
(t,s) - ((t1,s1) | (t2,s2) | ... | (tn,sn))
=
(t & t1, s - s1) | ... | (t & tn, s - sn) | (t - (t1|...|tn), s)
*)
let get_aux any_right d =
let accu = ref [] in
let line (left, right) =
let d1, d2 = cap_product any any_right left in
if non_empty d1 && non_empty d2 then
let right = List.map (fun (t1, t2) -> (descr t1, descr t2)) right in
let right = normal_aux right in
let resid1 = ref d1 in
let () =
List.iter
(fun (t1, t2) ->
let t1 = cap d1 t1 in
if non_empty t1 then
let () = resid1 := diff !resid1 t1 in
let t2 = diff d2 t2 in
if non_empty t2 then accu := (t1, t2) :: !accu)
right
in
if non_empty !resid1 then accu := (!resid1, d2) :: !accu
in
List.iter line (BoolPair.get d);
!accu
let partition = get_aux
(* Maybe, can improve this function with:
(t,s) \ (t1,s1) = (t&t',s\s') | (t\t',s),
don't call normal_aux *)
let get ?(kind = `Normal) d =
match kind with
| `Normal -> get_aux any d.times
| `XML -> get_aux any_pair d.xml
let pi1 = List.fold_left (fun acc (t1, _) -> cup acc t1) empty
let pi2 = List.fold_left (fun acc (_, t2) -> cup acc t2) empty
let pi2_restricted restr =
List.fold_left
(fun acc (t1, t2) -> if disjoint t1 restr then acc else cup acc t2)
empty
let restrict_1 rects pi1 =
let aux acc (t1, t2) =
let t1 = cap t1 pi1 in
if is_empty t1 then acc else (t1, t2) :: acc
in
List.fold_left aux [] rects
type normal = t
module Memo = Map.Make (BoolPair)
(* TODO: try with an hashtable *)
(* Also, avoid lookup for simple products (t1,t2) *)
let memo = ref Memo.empty
let normal_times d =
try Memo.find d !memo with
| Not_found ->
let gd = get_aux any d in
let n = normal_aux gd in
(* Could optimize this call to normal_aux because one already
know that each line is normalized ... *)
memo := Memo.add d n !memo;
n
let memo_xml = ref Memo.empty
let normal_xml d =
try Memo.find d !memo_xml with
| Not_found ->
let gd = get_aux any_pair d in
let n = normal_aux gd in
memo_xml := Memo.add d n !memo_xml;
n
let normal ?(kind = `Normal) d =
match kind with
| `Normal -> normal_times d.times
| `XML -> normal_xml d.xml
(*
let merge_same_2 r =
let r =
List.fold_left
(fun accu (t1,t2) ->
let t = try DescrMap.find t2 accu with Not_found -> empty in
DescrMap.add t2 (cup t t1) accu
) DescrMap.empty r in
DescrMap.fold (fun t2 t1 accu -> (t1,t2)::accu) r []
*)
let constraint_on_2 n t1 =
List.fold_left
(fun accu (d1, d2) -> if disjoint d1 t1 then accu else cap accu d2)
any n
let merge_same_first tr =
let trs = ref [] in
let _ =
List.fold_left
(fun memo (t1, t2) ->
let memo', l =
Cache.find
(fun t1 ->
let l = ref empty in
trs := (t1, l) :: !trs;
l)
t1 memo
in
l := cup t2 !l;
memo')
Cache.emp tr
in
List.map (fun (t1, l) -> (t1, !l)) !trs
(* same on second component: use the same implem? *)
let clean_normal l =
let rec aux accu (t1, t2) =
match accu with
| [] -> [ (t1, t2) ]
| (s1, s2) :: rem when equiv t2 s2 -> (cup s1 t1, s2) :: rem
| (s1, s2) :: rem -> (s1, s2) :: aux rem (t1, t2)
in
List.fold_left aux [] l
let is_empty d = d == []
end
module Record = struct
let has_record d = not (is_empty { empty with record = d.record; hash = -1 })
let or_absent d = { d with absent = true; hash = -1 }
let absent = or_absent empty
let any_or_absent = any_or_absent
let has_absent d = d.absent
let absent_node = cons absent
module T = struct
type t = descr
let any = any_or_absent
let cap = cap
let cup = cup
let diff = diff
let is_empty = is_empty
let empty = empty
end
module R = struct
type t = descr
let any = { empty with record = any.record; hash = -1 }
let cap = cap
let cup = cup
let diff = diff
let is_empty = is_empty
let empty = empty
end
module TR = Normal.Make (T) (R)
let any_record = { empty with record = BoolRec.any; hash = -1 }
let atom o l =
if o && LabelMap.is_empty l then any_record
else { empty with record = BoolRec.atom (o, l); hash = -1 }
type zor =
| Pair of descr * descr
| Any
(* given a type t and a label l, this function computes the projection on
l on each component of the DNF *)
let aux_split d l =
let f (o, r) =
try
(* separate a record type between the type of its label l, if it appears
explicitely and the type of the reminder of the record *)
let lt, rem = LabelMap.assoc_remove l r in
Pair (descr lt, atom o rem)
with
| Not_found ->
(* if the label l is not present explicitely *)
if o then
(* if the record is open *)
if LabelMap.is_empty r then Any
(* if there are no explicity fields return Any,
the record type is not splited *)
else
(* otherwise returns the fact that the field may or may not be present
*)
Pair
( any_or_absent,
{ empty with record = BoolRec.atom (o, r); hash = -1 } )
else
(* for closed records, return the fact that the label was absent *)
Pair (absent, { empty with record = BoolRec.atom (o, r); hash = -1 })
in
List.fold_left
(fun b (p, n) ->
(* for each positive/negative intersections*)
let rec aux_p accu = function
| x :: p -> (
(*get the ucrrent positive record, and split according to l*)
match f x with
(* if something, add (typof l, rest) to the positive accumulator*)
| Pair (t1, t2) -> aux_p ((t1, t2) :: accu) p
| Any ->
aux_p accu p
(* if we have { ..} in this positive
intersection, we can ignore it. *))
| [] -> aux_n accu [] n
(* now follow up with negative*)
and aux_n p accu = function
| x :: n -> (
match f x with
(* if we have a pair add it to the current negative accmulator*)
| Pair (t1, t2) -> aux_n p ((t1, t2) :: accu) n
(* if { .. } is in a negative intersection, the whole branch (p,n)
can be discarded *)
| Any -> b)
| [] -> (p, accu) :: b
(* add the current pair of line *)
in
aux_p [] p)
[] (BoolRec.get d.record)
(* We now have a DNF of pairs where the left component is the type of
a label l and the right component the rest of the record types.
split returns a simplified DNF where the intersection are pushed
below the products.
Given a type d and a label l, this function returns
a list of pairs : the first component represents the disjoint union
of types associated to l
the second projection the remaining types (records \ l)
*)
let split (d : descr) l = TR.boolean (aux_split d l)
(* same as above, but the types for .l are disjoint *)
let split_normal d l = TR.boolean_normal (aux_split d l)
(* returns the union of the first projections. If one of the record
had an absent l, then absent will end up in the result. *)
let pi l d = TR.pi1 (split d l)
(* Same but check that the resulting type does not contain absent *)
let project d l =
let t = pi l d in
if t.absent then raise Not_found;
t
(* Same but erase the status of absent : meaning return the type of l
if it is present.*)
let project_opt d l =
let t = pi l d in
{ t with absent = false; hash = -1 }
let _condition d l t = TR.pi2_restricted t (split d l)
(* TODO: eliminate this cap ... (record l absent_node) when
not necessary. eg. { ..... } \ l *)
(* get the pi2 part of split, that is the union of all the record types where
l has been removed explicitely. And cap it with an open record where l
is absent, to eliminate l from open record types where it was implicitely present.
*)
let remove_field d l = cap (TR.pi2 (split d l)) (record l absent_node)
let _all_labels d =
let res = ref LabelSet.empty in
let aux (_, r) =
let ls = LabelMap.domain r in
res := LabelSet.cup ls !res
in
BoolRec.iter aux d.record;
!res
let first_label d =
let min = ref Label.dummy in
let aux (_, r) =
match LabelMap.get r with
| (l, _) :: _ -> min := Label.min l !min
| _ -> ()
in
Rec.Dnf.iter aux d.record;
!min
let empty_cases d =
let x =
BoolRec.compute ~empty:0 ~any:3
~cup:(fun x y -> x lor y)
~cap:(fun x y -> x land y)
~diff:(fun a b -> a land lnot b)
~atom:(function
| o, r ->
assert (LabelMap.get r == []);
if o then 3 else 1)
d.record
in
(x land 2 <> 0, x land 1 <> 0)
let has_empty_record d =
BoolRec.compute ~empty:false ~any:true ~cup:( || ) ~cap:( && )
~diff:(fun a b -> a && not b)
~atom:(function
| _, r -> List.for_all (fun (_, t) -> (descr t).absent) (LabelMap.get r))
d.record
(*TODO: optimize merge
- pre-compute the sequence of labels
- remove empty or full { l = t }
*)
let merge d1 d2 =
let res = ref empty in
let rec aux accu d1 d2 =
let l = Label.min (first_label d1) (first_label d2) in
if l == Label.dummy then
let some1, none1 = empty_cases d1
and some2, none2 = empty_cases d2 in
let _none = none1 && none2
and some = some1 || some2 in
let accu = LabelMap.from_list (fun _ _ -> assert false) accu in
(* approx for the case (some && not none) ... *)
res := cup !res (record_fields (some, accu))
else
let l1 = split d1 l
and l2 = split d2 l in
let loop (t1, d1) (t2, d2) =
let t =
if t2.absent then cup t1 { t2 with absent = false; hash = -1 }
else t2
in
aux ((l, cons t) :: accu) d1 d2
in
List.iter (fun x -> List.iter (loop x) l2) l1
in
aux [] d1 d2;
!res
let get d =
let rec aux r accu d =
let l = first_label d in
if l == Label.dummy then
let o1, o2 = empty_cases d in
if o1 || o2 then (LabelMap.from_list_disj r, o1, o2) :: accu else accu
else
List.fold_left
(fun accu (t1, t2) ->
let x = (t1.absent, { t1 with absent = false }) in
aux ((l, x) :: r) accu t2)
accu (split d l)
in
aux [] [] d
type t = TR.t
let focus = split_normal
let get_this r = { (TR.pi1 r) with absent = false; hash = -1 }
let need_others = function
| _ :: _ :: _ -> true
| _ -> false
let constraint_on_others r t1 =
List.fold_left
(fun accu (d1, d2) -> if disjoint d1 t1 then accu else cap accu d2)
any_record r
end
let memo_normalize = ref DescrMap.empty
let rec rec_normalize d =
try DescrMap.find d !memo_normalize with
| Not_found ->
let n = make () in
memo_normalize := DescrMap.add d n !memo_normalize;
let times =
List.fold_left
(fun accu (d1, d2) ->
BoolPair.cup accu
(BoolPair.atom (rec_normalize d1, rec_normalize d2)))
BoolPair.empty (Product.normal d)
in
let xml =
List.fold_left
(fun accu (d1, d2) ->
BoolPair.cup accu
(BoolPair.atom (rec_normalize d1, rec_normalize d2)))
BoolPair.empty
(Product.normal ~kind:`XML d)
in
let record = d.record in
define n { d with times; xml; record; hash = -1 };
n
let normalize n = descr (internalize (rec_normalize n))
module Arrow = struct
let trivially_arrow t =
if subtype Function.any t then `Arrow
else if is_empty { empty with arrow = t.arrow; hash = -1 } then `NotArrow
else `Other
let check_simple left (s1, s2) =
let rec aux accu1 accu2 = function
| (t1, t2) :: left ->
let accu1' = diff_t accu1 t1 in
if non_empty accu1' then aux accu1 accu2 left;
let accu2' = cap_t accu2 t2 in
if non_empty accu2' then aux accu1 accu2 left
| [] -> raise NotEmpty
in
let accu1 = descr s1 in
is_empty accu1
||
try
aux accu1 (diff any (descr s2)) left;
true
with
| NotEmpty -> false
let check_line_non_empty (left, right) =
not (List.exists (check_simple left) right)
let sample t =
let left, _right =
List.find check_line_non_empty (Function.Dnf.get t.arrow)
in
List.fold_left
(fun accu (t, s) -> cap accu (arrow t s))
{ empty with arrow = any.arrow; hash = -1 }
left
(* [check_strenghten t s]
Assume that [t] is an intersection of arrow types
representing the interface of an abstraction;
check that this abstraction has type [s] (otherwise raise Not_found)
and returns a refined type for this abstraction.
*)
let _check_strenghten t s =
(*
let left = match (BoolPair.get t.arrow) with [ (p,[]) ] -> p | _ -> assert false in
let rec aux = function
| [] -> raise Not_found
| (p,n) :: rem ->
if (List.for_all (fun (a,b) -> check_simple left a b) p) &&
(List.for_all (fun (a,b) -> not (check_simple left a b)) n) then
{ empty with arrow = Obj.magic [ (SortedList.cup left p, n) ] } (* rework this ! *)
else aux rem
in
aux (BoolPair.get s.arrow)
*)
if subtype t s then t else raise Not_found
let check_simple_iface left s1 s2 =
let rec aux accu1 accu2 = function
| (t1, t2) :: left ->
let accu1' = diff accu1 t1 in
if non_empty accu1' then aux accu1 accu2 left;
let accu2' = cap accu2 t2 in
if non_empty accu2' then aux accu1 accu2 left
| [] -> raise NotEmpty
in
let accu1 = descr s1 in
is_empty accu1
||
try
aux accu1 (diff any (descr s2)) left;
true
with
| NotEmpty -> false
let check_iface iface s =
let rec aux = function
| [] -> false
| (p, n) :: rem ->
List.for_all (fun (a, b) -> check_simple_iface iface a b) p
&& List.for_all (fun (a, b) -> not (check_simple_iface iface a b)) n
|| aux rem
in
aux (Function.Dnf.get s.arrow)
type t = descr * (descr * descr) list list
let get t =
List.fold_left
(fun ((dom, arr) as accu) (left, right) ->
if check_line_non_empty (left, right) then
let left = List.map (fun (t, s) -> (descr t, descr s)) left in
let d = List.fold_left (fun d (t, _) -> cup d t) empty left in
(cap dom d, left :: arr)
else accu)
(any, []) (BoolPair.get t.arrow)
let domain (dom, _) = dom
let apply_simple t result left =
let rec aux result accu1 accu2 = function
| (t1, s1) :: left ->
let result =
let accu1 = diff accu1 t1 in
if non_empty accu1 then aux result accu1 accu2 left else result
in
let result =
let accu2 = cap accu2 s1 in
aux result accu1 accu2 left
in
result
| [] -> if subtype accu2 result then result else cup result accu2
in
aux result t any left
let apply (_, arr) t =
if is_empty t then empty else List.fold_left (apply_simple t) empty arr
let need_arg (_dom, arr) =
List.exists
(function
| [ _ ] -> false
| _ -> true)
arr
let apply_noarg (_, arr) =
List.fold_left
(fun accu -> function
| [ (_t, s) ] -> cup accu s
| _ -> assert false)
empty arr
let is_empty (_, arr) = arr == []
end
let rec tuple = function
| [ t1; t2 ] -> times t1 t2
| t :: tl -> times t (cons (tuple tl))
| _ -> failwith "tuple: invalid length"
let rec_of_list o l =
let map =
LabelMap.from_list
(fun _ _ -> failwith "rec_of_list: duplicate fields")
(List.map
(fun (opt, qname, typ) ->
(qname, cons (if opt then Record.or_absent typ else typ)))
l)
in
record_fields (o, map)
let empty_closed_record = rec_of_list false []
let empty_open_record = Rec.any
let cond_partition univ qs =
let add accu (t, s) =
let t = if subtype t s then t else cap t s in
if subtype s t || is_empty t then accu
else
let aux accu u =
let c = cap u t in
if is_empty c || subtype (cap u s) t then u :: accu
else c :: diff u t :: accu
in
List.fold_left aux [] accu
in
List.fold_left add [ univ ] qs
lang/typing/typepat.ml
open Ident
open Cduce_error
let any_node = Types.(cons any)
let any_or_absent_node = Types.(cons Record.any_or_absent)
type err = string -> exn
let deferr s = raise_err Typer_Pattern s
type node = {
mutable desc : desc;
mutable smallhash : int;
(* Local hash *)
mutable rechash : int;
(* Global (recursive) hash *)
mutable sid : int;
(* Sequential id used to compute rechash *)
mutable t : Types.t option;
mutable tnode : Types.Node.t option;
mutable p : Patterns.descr option;
mutable pnode : Patterns.node option;
mutable fv : fv option;
}
and desc =
| ILink of node
| IType of Types.descr * int
| IOr of node * node * err
| IAnd of node * node * err
| IDiff of node * node * err
| ITimes of node * node
| IXml of node * node
| IArrow of node * node
| IOptional of node * err
| IRecord of bool * (node * node option) label_map * err
| ICapture of id
| IConstant of id * Types.const
| IConcat of node * node * err
| IMerge of node * node * err
let concats = ref []
let rec node_temp =
{
desc = ILink node_temp;
smallhash = 0;
rechash = 0;
sid = 0;
t = None;
tnode = None;
p = None;
pnode = None;
fv = None;
}
let mk d = { node_temp with desc = d }
let mk_delayed () = { node_temp with desc = ILink node_temp }
let mk_type t = mk (IType (t, Types.hash t))
let mk_or ?(err = deferr) n1 n2 = mk (IOr (n1, n2, err))
let mk_and ?(err = deferr) n1 n2 = mk (IAnd (n1, n2, err))
let mk_diff ?(err = deferr) n1 n2 = mk (IDiff (n1, n2, err))
let mk_prod n1 n2 = mk (ITimes (n1, n2))
let mk_xml n1 n2 = mk (IXml (n1, n2))
let mk_arrow n1 n2 = mk (IArrow (n1, n2))
let mk_optional ?(err = deferr) n = mk (IOptional (n, err))
let mk_record ?(err = deferr) n1 n2 = mk (IRecord (n1, n2, err))
let mk_capture n = mk (ICapture n)
let mk_constant n1 n2 = mk (IConstant (n1, n2))
let mk_concat ?(err = deferr) n1 n2 =
let n = mk (IConcat (n1, n2, err)) in
concats := n :: !concats;
n
let mk_merge ?(err = deferr) n1 n2 =
let n = mk (IMerge (n1, n2, err)) in
concats := n :: !concats;
n
let iempty = mk_type Types.empty
let mk_or ?err p1 p2 =
if p1.desc == iempty.desc then p2
else if p2.desc == iempty.desc then p1
else mk_or ?err p1 p2
let mk_and ?err p1 p2 =
if p1.desc == iempty.desc || p2.desc == iempty.desc then iempty
else mk_and ?err p1 p2
(* Recursive hash-consing *)
let hash_field f = function
| p, Some e -> 1 + (17 * f p) + (257 * f e)
| p, None -> 2 + (17 * f p)
let rec hash f n =
match n.desc with
| ILink n -> hash f n
| IType (t, h) -> 1 + (17 * h)
| IOr (p1, p2, _) -> 2 + (17 * f p1) + (257 * f p2)
| IAnd (p1, p2, _) -> 3 + (17 * f p1) + (257 * f p2)
| IDiff (p1, p2, _) -> 4 + (17 * f p1) + (257 * f p2)
| ITimes (p1, p2) -> 5 + (17 * f p1) + (257 * f p2)
| IXml (p1, p2) -> 6 + (17 * f p1) + (257 * f p2)
| IArrow (p1, p2) -> 7 + (17 * f p1) + (257 * f p2)
| IOptional (p, _) -> 8 + (17 * f p)
| IRecord (o, r, _) ->
9 + (if o then 17 else 0) + (257 * LabelMap.hash (hash_field f) r)
| ICapture x -> 10 + (17 * Id.hash x)
| IConstant (x, c) -> 11 + (17 * Id.hash x) + (257 * Types.Const.hash c)
| IConcat _
| IMerge _ ->
assert false
let hash0 = hash (fun n -> 1)
let hash1 = hash hash0
let hash2 = hash hash1
let hash3 = hash hash2
let smallhash n =
if n.smallhash != 0 then n.smallhash
else
let h = hash2 n in
n.smallhash <- h;
h
let rec repr = function
| { desc = ILink n } as m ->
let z = repr n in
m.desc <- ILink z;
z
| n -> n
let back = ref []
let rec prot_repr = function
| { desc = ILink n } -> repr n
| n -> n
let link x y =
match (x, y) with
| ({ t = None } as x), y
| y, ({ t = None } as x) ->
back := (x, x.desc) :: !back;
x.desc <- ILink y
| _ -> assert false
let rec unify x y =
if x == y then ()
else
let x = prot_repr x
and y = prot_repr y in
if x == y then ()
else if smallhash x != smallhash y then raise_err Typepat_Unify ()
else if x.t != None && y.t != None then raise_err Typepat_Unify ()
(* x and y have been internalized; if they were equivalent,
they would be equal *)
else
match (x.desc, y.desc) with
| IType (tx, _), IType (ty, _) when Types.equal tx ty -> link x y
| IOr (x1, x2, _), IOr (y1, y2, _)
| IAnd (x1, x2, _), IAnd (y1, y2, _)
| IDiff (x1, x2, _), IDiff (y1, y2, _)
| ITimes (x1, x2), ITimes (y1, y2)
| IXml (x1, x2), IXml (y1, y2)
| IArrow (x1, x2), IArrow (y1, y2) ->
link x y;
unify x1 y1;
unify x2 y2
| IOptional (x1, _), IOptional (y1, _) ->
link x y;
unify x1 y1
| IRecord (xo, xr, _), IRecord (yo, yr, _) when xo == yo ->
link x y;
LabelMap.may_collide unify_field (Error (Unlocated, (Typepat_Unify, ()))) xr yr
| ICapture xv, ICapture yv when Id.equal xv yv -> ()
| IConstant (xv, xc), IConstant (yv, yc)
when Id.equal xv yv && Types.Const.equal xc yc ->
()
| _ -> raise_err Typepat_Unify ()
and unify_field f1 f2 =
match (f1, f2) with
| (p1, Some e1), (p2, Some e2) ->
unify p1 p2;
unify e1 e2
| (p1, None), (p2, None) -> unify p1 p2
| _ -> raise_err Typepat_Unify ()
let may_unify x y =
try
unify x y;
back := [];
true
with
| Error (_, (Typepat_Unify, ())) ->
List.iter (fun (x, xd) -> x.desc <- xd) !back;
back := [];
false
module SmallHash = Hashtbl.Make (struct
type t = node
let equal = may_unify
let hash = smallhash
end)
let iter_field f = function
| x, Some y ->
f x;
f y
| x, None -> f x
let iter f = function
| IOr (x, y, _)
| IAnd (x, y, _)
| IDiff (x, y, _)
| ITimes (x, y)
| IXml (x, y)
| IArrow (x, y) ->
f x;
f y
| IOptional (x, _) -> f x
| IRecord (_, r, _) -> LabelMap.iter (iter_field f) r
| _ -> ()
let minimize (mem, add) =
let rec aux n =
let n = repr n in
if mem n then ()
else
let n = repr n in
add n ();
if n.t == None then iter aux n.desc
in
aux
let to_clear = ref []
let sid = ref 0
let rec rechash n =
let n = repr n in
if n.sid != 0 then 17 * n.sid
else (
incr sid;
n.sid <- !sid;
to_clear := n :: !to_clear;
hash rechash n)
let clear () =
sid := 0;
List.iter (fun x -> x.sid <- 0) !to_clear;
to_clear := []
let rechash n =
let n = repr n in
if n.rechash != 0 then n.rechash
else
let h = rechash n in
clear ();
n.rechash <- h;
h
module RecHash = Hashtbl.Make (struct
type t = node
let equal = may_unify
let hash = smallhash
end)
(** Two-phases recursive hash-consing **)
(*
let gtable = RecHash.create 17577
let internalize n =
let local = SmallHash.create 17 in
minimize (SmallHash.mem local, SmallHash.add local) n;
minimize (RecHash.mem gtable, RecHash.add gtable) n;
()
*)
(** Single-phase hash-consing **)
let gtable = SmallHash.create 17
let internalize n = minimize (SmallHash.mem gtable, SmallHash.add gtable) n
(* let internalize n = () *)
(* Compute free variables *)
let fv n =
let fv = ref IdSet.empty in
let rec aux n =
let n = repr n in
if n.sid = 0 then (
n.sid <- 1;
to_clear := n :: !to_clear;
match (n.fv, n.desc) with
| Some x, _ -> fv := IdSet.cup !fv x
| None, (ICapture x | IConstant (x, _)) -> fv := IdSet.add x !fv
| None, d -> iter aux d)
in
assert (!to_clear == []);
match n.fv with
| Some x -> x
| None ->
aux n;
clear ();
n.fv <- Some !fv;
!fv
(* optimized version to check closedness *)
let no_fv = Some IdSet.empty
let peek_fv n =
let err x = raise_err Typepat_FoundFv x in
let rec aux n =
let n = repr n in
if n.sid = 0 then (
n.sid <- 1;
to_clear := n :: !to_clear;
match (n.fv, n.desc) with
| Some x, _ when IdSet.is_empty x -> ()
| Some x, _ -> err (IdSet.choose x)
| None, (ICapture x | IConstant (x, _)) -> err x
| None, d -> iter aux d)
in
assert (!to_clear == []);
try
match n.fv with
| Some x when IdSet.is_empty x -> ()
| Some x -> err (IdSet.choose x)
| None ->
aux n;
List.iter
(fun n ->
n.sid <- 0;
n.fv <- no_fv)
!to_clear;
to_clear := []
with
| exn ->
clear ();
raise exn
let has_no_fv n =
try
peek_fv n;
true
with
| Error (_, (Typepat_FoundFv, _)) -> false
let peek_fv n =
try
peek_fv n;
None
with
| Cduce_error.Error (_, (Typepat_FoundFv, x)) -> Some (x : Ident.id)
(* From the intermediate representation to the internal one *)
let rec typ n =
let n = repr n in
match n.t with
| Some t -> t
| None ->
let t = compute_typ n.desc in
n.t <- Some t;
t
and compute_typ = function
| IType (t, _) -> t
| IOr (s1, s2, _) -> Types.cup (typ s1) (typ s2)
| IAnd (s1, s2, _) -> Types.cap (typ s1) (typ s2)
| IDiff (s1, s2, _) -> Types.diff (typ s1) (typ s2)
| ITimes (s1, s2) -> Types.times (typ_node s1) (typ_node s2)
| IXml (s1, s2) -> Types.xml (typ_node s1) (typ_node s2)
| IArrow (s1, s2) -> Types.arrow (typ_node s1) (typ_node s2)
| IOptional (s, _) -> Types.Record.or_absent (typ s)
| IRecord (o, r, err) ->
Types.record_fields (o, LabelMap.map (compute_typ_field err) r)
| ILink _ -> assert false
| ICapture _
| IConstant (_, _) ->
assert false
| IConcat _
| IMerge _ ->
assert false
and compute_typ_field err = function
| s, None -> typ_node s
| s, Some _ -> raise (err "Or-else clauses are not allowed in types")
and typ_node n =
let n = repr n in
match n.tnode with
| Some t -> t
| None ->
let x = Types.make () in
n.tnode <- Some x;
Types.define x (typ n);
x
let rec pat n =
let n = repr n in
if has_no_fv n then
let t = typ n in
if Var.Set.is_empty (Types.Subst.vars t) then Patterns.constr t
else raise_err Typer_Pattern "Polymorphic types are not allowed in patterns"
else
match n.p with
| Some p -> p
| None ->
let p = compute_pat n.desc in
n.p <- Some p;
p
and compute_pat = function
| IOr (s1, s2, err) -> (
try Patterns.cup (pat s1) (pat s2) with
| Cduce_error.Error (_, (Typer_Pattern, s)) -> raise (err s))
| IAnd (s1, s2, err) -> (
try Patterns.cap (pat s1) (pat s2) with
| Cduce_error.Error (_, (Typer_Pattern, s)) -> raise (err s))
| IDiff (s1, s2, _) when IdSet.is_empty (fv s2) ->
let s2 = Types.neg (typ s2) in
Patterns.cap (pat s1) (Patterns.constr s2)
| IDiff (_, _, err) -> raise (err "Differences are not allowed in patterns")
| ITimes (s1, s2) -> Patterns.times (pat_node s1) (pat_node s2)
| IXml (s1, s2) -> Patterns.xml (pat_node s1) (pat_node s2)
| IOptional (_, err) ->
raise (err "Optional fields are not allowed in record patterns")
| IRecord (o, r, err) ->
let pats = ref [] in
let aux l = function
| s, None ->
if IdSet.is_empty (fv s) then typ_node s
else (
pats := Patterns.record l (pat_node s) :: !pats;
any_node)
| s, Some e ->
if IdSet.is_empty (fv s) then
raise (err "Or-else clauses are not allowed in types")
else (
pats :=
Patterns.cup (Patterns.record l (pat_node s)) (pat e) :: !pats;
any_or_absent_node)
in
let constr = Types.record_fields (o, LabelMap.mapi aux r) in
List.fold_left Patterns.cap (Patterns.constr constr) !pats
(* TODO: can avoid constr when o=true, and all fields have fv *)
| ICapture x -> Patterns.capture x
| IConstant (x, c) -> Patterns.constant x c
| IArrow _ -> raise_err Typer_Pattern "Arrows are not allowed in patterns"
| IType _
| ILink _
| IConcat _
| IMerge _ ->
assert false
and pat_node n =
let n = repr n in
match n.pnode with
| Some p -> p
| None -> (
let x = Patterns.make (fv n) in
try
n.pnode <- Some x;
Patterns.define x (pat n);
x
with
| exn ->
n.pnode <- None;
raise exn)
type regexp =
| PElem of node
| PGuard of node
| PSeq of regexp list
| PAlt of regexp list
| PStar of regexp
| PWeakStar of regexp
let rec nullable = function
| PElem _ -> false
| PSeq rl -> List.for_all nullable rl
| PAlt rl -> List.exists nullable rl
| PStar _
| PWeakStar _
| PGuard _ ->
true
let eps = PSeq []
let emp = PAlt []
let star x = PStar x
let elem x = PElem x
let seq r1 r2 =
let r1 =
match r1 with
| PSeq l -> l
| x -> [ x ]
in
let r2 =
match r2 with
| PSeq l -> l
| x -> [ x ]
in
match r1 @ r2 with
| [ x ] -> x
| l -> PSeq l
let alt r1 r2 =
let r1 =
match r1 with
| PAlt l -> l
| x -> [ x ]
in
let r2 =
match r2 with
| PAlt l -> l
| x -> [ x ]
in
match r1 @ r2 with
| [ x ] -> x
| l -> PAlt l
let rec merge_alt = function
| PElem p :: PElem q :: l -> merge_alt (PElem (mk_or p q) :: l)
| r :: l -> r :: merge_alt l
| [] -> []
(* Works only for types, not patterns, because
[ (x&Int|_) R' ] is possible *)
let rec simplify_regexp = function
| PSeq l -> PSeq (List.map simplify_regexp l)
| PAlt l -> PAlt (merge_alt (List.map simplify_regexp l))
| PStar r
| PWeakStar r ->
PStar (simplify_regexp r)
| x -> x
let rec print_regexp ppf = function
| PElem _ -> Format.fprintf ppf "Elem"
| PGuard _ -> Format.fprintf ppf "Guard"
| PSeq l -> Format.fprintf ppf "Seq(%a)" print_regexp_list l
| PAlt l -> Format.fprintf ppf "Alt(%a)" print_regexp_list l
| PStar r -> Format.fprintf ppf "Star(%a)" print_regexp r
| PWeakStar r -> Format.fprintf ppf "WStar(%a)" print_regexp r
and print_regexp_list ppf l =
List.iter (fun x -> Format.fprintf ppf "%a;" print_regexp x) l
let rec remove_regexp r q =
match r with
| PElem p -> mk_prod p q
| PGuard p -> mk_and p q
| PSeq l -> List.fold_right (fun r a -> remove_regexp r a) l q
| PAlt rl -> List.fold_left (fun a r -> mk_or a (remove_regexp r q)) iempty rl
| PStar r ->
let x = mk_delayed () in
let res = mk_or x q in
x.desc <- ILink (remove_regexp_nullable r res iempty);
res
| PWeakStar r ->
let x = mk_delayed () in
let res = mk_or q x in
x.desc <- ILink (remove_regexp_nullable r res iempty);
res
and remove_regexp_nullable r q_nonempty q_empty =
if nullable r then remove_regexp2 r q_nonempty q_empty
else remove_regexp r q_nonempty
and remove_regexp2 r q_nonempty q_empty =
(* Assume r is nullable *)
if q_nonempty == q_empty then remove_regexp r q_nonempty
else
match r with
| PSeq [] -> q_empty
| PElem p -> assert false
| PGuard p -> mk_and p q_empty
| PSeq (r :: rl) ->
remove_regexp2 r
(remove_regexp (PSeq rl) q_nonempty)
(remove_regexp2 (PSeq rl) q_nonempty q_empty)
| PAlt rl ->
List.fold_left
(fun a r -> mk_or a (remove_regexp_nullable r q_nonempty q_empty))
iempty rl
| PStar r ->
let x = mk_delayed () in
x.desc <- ILink (remove_regexp_nullable r (mk_or x q_nonempty) iempty);
mk_or x q_empty
| PWeakStar r ->
let x = mk_delayed () in
x.desc <- ILink (remove_regexp_nullable r (mk_or q_nonempty x) iempty);
mk_or q_empty x
let pcdata = star (PElem (mk_type Types.(char CharSet.any)))
let mix_regexp regexp =
let rec aux = function
| PSeq [] -> eps
| PElem re -> PElem re
| PGuard re -> assert false
| PSeq (r :: rl) -> seq (aux r) (seq pcdata (aux (PSeq rl)))
| PAlt rl -> PAlt (List.map aux rl)
| PStar re -> star (seq pcdata (aux re))
| PWeakStar re -> assert false
in
seq pcdata (seq (aux regexp) pcdata)
let cst_nil = Types.(Atom Sequence.nil_atom)
let capture_all vars p = IdSet.fold (fun p x -> mk_and p (mk_capture x)) p vars
let termin b vars p =
if b then p
else IdSet.fold (fun p x -> seq p (PGuard (mk_constant x cst_nil))) p vars
type re =
| Epsilon
| Empty
| Elem of node
| Guard of node
| Seq of re * re
| Alt of re * re
| Star of re
| WeakStar of re
| SeqCapture of id * re
let mk_empty = Empty
let mk_epsilon = Epsilon
let mk_elem n = Elem n
let mk_guard n = Guard n
let mk_seq n1 n2 = Seq (n1, n2)
let mk_alt n1 n2 = Alt (n1, n2)
let mk_star n = Star n
let mk_weakstar n = WeakStar n
let mk_seqcapt x n = SeqCapture (x, n)
let mk_str s =
let j = Encodings.Utf8.end_index s in
let rec aux i =
if Encodings.Utf8.equal_index i j then Epsilon
else
let c, i = Encodings.Utf8.next s i in
let t = Types.(char CharSet.(atom (V.mk_int c))) in
Seq (Elem (mk_type t), aux i)
in
aux (Encodings.Utf8.start_index s)
let rec prepare_regexp vars b rvars f = function
(* - vars: seq variables to be propagated top-down and added
to each captured element
- b: below a star ?
- rvars: seq variables that appear on the right of the regexp
- f: tail position
returns the set of seq variable of the regexp minus rvars
(they have already been terminated if not below a star)
*)
| Epsilon -> (PSeq [], IdSet.empty)
| Empty -> (PAlt [], IdSet.empty)
| Elem p -> (PElem (capture_all vars p), IdSet.empty)
| Guard p -> (PGuard p, IdSet.empty)
| Seq (p1, p2) ->
let p2, v2 = prepare_regexp vars b rvars f p2 in
let p1, v1 = prepare_regexp vars b (IdSet.cup rvars v2) false p1 in
(seq p1 p2, IdSet.cup v1 v2)
| Alt (p1, p2) ->
let p1, v1 = prepare_regexp vars b rvars f p1
and p2, v2 = prepare_regexp vars b rvars f p2 in
( alt (termin b (IdSet.diff v2 v1) p1) (termin b (IdSet.diff v1 v2) p2),
IdSet.cup v1 v2 )
| Star p ->
let p, v = prepare_regexp vars true rvars false p in
(termin b v (PStar p), v)
| WeakStar p ->
let p, v = prepare_regexp vars true rvars false p in
(termin b v (PWeakStar p), v)
| SeqCapture (x, p) ->
let vars = if f then vars else IdSet.add x vars in
let after = IdSet.mem rvars x in
let rvars = IdSet.add x rvars in
let p, v = prepare_regexp vars b rvars false p in
( (if f then seq (PGuard (mk (ICapture x))) p
else termin (after || b) (IdSet.singleton x) p),
if after then v else IdSet.add x v )
let rexp r =
let r, _ = prepare_regexp IdSet.empty false IdSet.empty true r in
remove_regexp r (mk_type Types.Sequence.nil_type)
let rexp_simplify ~mix r =
let r, _ = prepare_regexp IdSet.empty false IdSet.empty true r in
let r = if mix then mix_regexp r else r in
let r = simplify_regexp r in
remove_regexp r (mk_type Types.Sequence.nil_type)
let check_wf p =
let rec aux q =
if p == q then raise Exit;
aux2 q.desc
and aux2 = function
| IOr (q1, q2, _)
| IAnd (q1, q2, _)
| IDiff (q1, q2, _) ->
aux q1;
aux q2
| ILink q -> aux q
| _ -> ()
in
try
aux2 p.desc;
true
with
| Exit -> false
module H = Hashtbl.Make (Types)
let rec elim_concat n =
match n.desc with
| IConcat (a, b, err) ->
if n.sid > 0 then raise (err "Ill-formed concatenation loop");
n.sid <- 1;
n.desc <- ILink (elim_conc a b err)
| IMerge (a, b, err) ->
if n.sid > 0 then raise (err "Ill-formed merge loop");
n.sid <- 1;
n.desc <- ILink (elim_merge a b err)
| _ -> ()
and elim_merge a b err =
let get_rec t =
let t = Types.Record.get t in
List.map
(fun (l, o, _) ->
( o,
LabelMap.map
(fun (opt, x) ->
let x = mk_type x in
((if opt then mk_optional x else x), None))
l ))
t
in
let merge (o1, l1) (o2, l2) =
mk_record (o1 || o2) (LabelMap.merge (fun _ x -> x) l1 l2)
in
(* Problem: repr can loop with ill-formed recursion.
type t = s + t where s = s | s;; *)
let a = repr a
and b = repr b in
elim_concat a;
elim_concat b;
let a = repr a
and b = repr b in
match (a.desc, b.desc) with
| IType (t1, _), IType (t2, _) ->
if not (Types.subtype t1 Types.Rec.any) then
raise (err "Left argument of record concatenation is not a record type");
if not (Types.subtype t2 Types.Rec.any) then
raise
(err "Right argument of record concatenation is not a record type");
mk_type (Types.Record.merge t1 t2)
| IOr (a1, a2, _), _ -> mk_or (elim_merge a1 b err) (elim_merge a2 b err)
| _, IOr (b1, b2, _) -> mk_or (elim_merge a b1 err) (elim_merge a b2 err)
| IRecord (o1, l1, _), IRecord (o2, l2, _) -> merge (o1, l1) (o2, l2)
| IType (t1, _), IRecord (o2, l2, _) ->
if not (Types.subtype t1 Types.Rec.any) then
raise (err "Left argument of record concatenation is not a record type");
List.fold_left
(fun accu (o1, l1) -> mk_or accu (merge (o1, l1) (o2, l2)))
iempty (get_rec t1)
| IRecord (o1, l1, _), IType (t2, _) ->
if not (Types.subtype t2 Types.Rec.any) then
raise
(err "Right argument of record concatenation is not a record type");
List.fold_left
(fun accu (o2, l2) -> mk_or accu (merge (o1, l1) (o2, l2)))
iempty (get_rec t2)
| _ -> raise (err "Cannot compute record concatenation")
and elim_conc n q err =
let mem = ref [] in
let rec aux n =
try List.assq n !mem with
| Not_found ->
let r = mk_delayed () in
mem := (n, r) :: !mem;
let rec aux2 n =
match n.desc with
| ILink n' -> aux2 n'
| IOr (a, b, _) -> mk_or (aux a) (aux b)
| ITimes (a, b) -> mk_prod a (aux b)
| IConcat (a, b, _) ->
elim_concat n;
aux2 n
| IType (t, _) -> elim_concat_type t q err
| _ -> raise (err "Cannot compute concatenation")
in
r.desc <- ILink (aux2 n);
r
in
aux n
and elim_concat_type t q err =
if not Types.(subtype t Sequence.any) then
raise (err "Left argument of concatenation is not a sequence type");
let mem = H.create 17 in
let rec aux t =
try H.find mem t with
| Not_found ->
let n = mk_delayed () in
H.add mem t n;
let d =
List.fold_left
(fun accu (t1, t2) -> mk_or accu (mk_prod (mk_type t1) (aux t2)))
(if Types.(AtomSet.contains Sequence.nil_atom (Atom.get t)) then q
else iempty)
(Types.Product.get t)
in
n.desc <- ILink d;
n
in
aux t
let elim_concats () =
try
List.iter elim_concat !concats;
List.iter (fun n -> n.sid <- 0) !concats;
concats := []
with
| exn ->
List.iter (fun n -> n.sid <- 0) !concats;
concats := [];
raise exn
let link a b = a.desc <- ILink b
let get_ct c =
let c = repr c in
match c.desc with
| ITimes (k, content) -> (
match (repr k).desc with
| IType (t, _) -> (t, content)
| _ -> assert false)
| _ -> assert false
lang/typing/patterns.ml
open Ident
let get_opt = function
| Some x -> x
| None -> assert false
(*
To be sure not to use generic comparison ...
*)
let ( = ) : int -> int -> bool = ( == )
let ( < ) : int -> int -> bool = ( < )
let ( <= ) : int -> int -> bool = ( <= )
let ( <> ) : int -> int -> bool = ( <> )
let compare = 1
(* Syntactic algebra *)
(* Constraint: any node except Constr has fv<>[] ... *)
type d =
| Constr of Types.t
| Cup of descr * descr
| Cap of descr * descr
| Times of node * node
| Xml of node * node
| Record of label * node
| Capture of id
| Constant of id * Types.const
| Dummy
and node = {
id : int;
mutable descr : descr;
accept : Types.Node.t;
fv : fv;
}
and descr = Types.t * fv * d
let id x = x.id
let descr x = x.descr
let fv x = x.fv
let accept x = Types.internalize x.accept
let printed = ref []
let to_print = ref []
let rec print ppf (a, _, d) =
match d with
| Constr t -> Types.Print.print ppf t
| Cup (p1, p2) -> Format.fprintf ppf "(%a | %a)" print p1 print p2
| Cap (p1, p2) -> Format.fprintf ppf "(%a & %a)" print p1 print p2
| Times (n1, n2) ->
Format.fprintf ppf "(P%i,P%i)" n1.id n2.id;
to_print := n1 :: n2 :: !to_print
| Xml (n1, n2) ->
Format.fprintf ppf "XML(P%i,P%i)" n1.id n2.id;
to_print := n1 :: n2 :: !to_print
| Record (l, n) ->
Format.fprintf ppf "{ %a = P%i }" Label.print_attr l n.id;
to_print := n :: !to_print
| Capture x -> Format.fprintf ppf "%a" Ident.print x
| Constant (x, c) ->
Format.fprintf ppf "(%a := %a)" Ident.print x Types.Print.print_const c
| Dummy -> Format.fprintf ppf "*DUMMY*"
let dump_print ppf =
while !to_print != [] do
let p = List.hd !to_print in
to_print := List.tl !to_print;
if not (List.mem p.id !printed) then (
printed := p.id :: !printed;
Format.fprintf ppf "P%i:=%a\n" p.id print (descr p))
done
let print ppf d =
Format.fprintf ppf "%a@\n" print d;
dump_print ppf
let print_node ppf n =
Format.fprintf ppf "P%i" n.id;
to_print := n :: !to_print;
dump_print ppf
let counter = ref 0
let dummy = (Types.empty, IdSet.empty, Dummy)
let make fv =
incr counter;
{ id = !counter; descr = dummy; accept = Types.make (); fv }
let define x ((accept, fv, _) as d) =
(* assert (x.fv = fv); *)
Types.define x.accept accept;
x.descr <- d
let cons fv d =
let q = make fv in
define q d;
q
let constr x = (x, IdSet.empty, Constr x)
let cup ((acc1, fv1, _) as x1) ((acc2, fv2, _) as x2) =
(if not (IdSet.equal fv1 fv2) then
let x =
match IdSet.pick (IdSet.diff fv1 fv2) with
| Some x -> x
| None -> get_opt (IdSet.pick (IdSet.diff fv2 fv1))
in
Cduce_error.(raise_err Typer_Pattern
("The capture variable " ^ Ident.to_string x
^ "should appear on both sides of this | pattern")));
Types.cup acc1 acc2, IdSet.cup fv1 fv2, Cup (x1, x2)
let cap ((acc1, fv1, _) as x1) ((acc2, fv2, _) as x2) =
(if not (IdSet.disjoint fv1 fv2) then
let x = get_opt (IdSet.pick (IdSet.cap fv1 fv2)) in
Cduce_error.(raise_err Typer_Pattern
("The capture variable " ^ Ident.to_string x
^ "cannot appear on both sides of this & pattern")));
(Types.cap acc1 acc2, IdSet.cup fv1 fv2, Cap (x1, x2))
let times x y =
(Types.times x.accept y.accept, IdSet.cup x.fv y.fv, Times (x, y))
let xml x y = (Types.xml x.accept y.accept, IdSet.cup x.fv y.fv, Xml (x, y))
let record l x = (Types.record l x.accept, x.fv, Record (l, x))
let capture x = (Types.any, IdSet.singleton x, Capture x)
let constant x c = (Types.any, IdSet.singleton x, Constant (x, c))
let print_node = ref (fun _ _ -> assert false)
module Node = struct
type t = node
let compare n1 n2 = n1.id - n2.id
let equal n1 n2 = n1.id == n2.id
let hash n = n.id
let check n = ()
let dump ppf x = !print_node ppf x
end
(* Pretty-print *)
module Pat = struct
type t = descr
let rec compare (_, _, d1) (_, _, d2) =
if d1 == d2 then 0
else
match (d1, d2) with
| Constr t1, Constr t2 -> Types.compare t1 t2
| Constr _, _ -> -1
| _, Constr _ -> 1
| Cup (x1, y1), Cup (x2, y2)
| Cap (x1, y1), Cap (x2, y2) ->
let c = compare x1 x2 in
if c <> 0 then c else compare y1 y2
| Cup _, _ -> -1
| _, Cup _ -> 1
| Cap _, _ -> -1
| _, Cap _ -> 1
| Times (x1, y1), Times (x2, y2)
| Xml (x1, y1), Xml (x2, y2) ->
let c = Node.compare x1 x2 in
if c <> 0 then c else Node.compare y1 y2
| Times _, _ -> -1
| _, Times _ -> 1
| Xml _, _ -> -1
| _, Xml _ -> 1
| Record (x1, y1), Record (x2, y2) ->
let c = Label.compare x1 x2 in
if c <> 0 then c else Node.compare y1 y2
| Record _, _ -> -1
| _, Record _ -> 1
| Capture x1, Capture x2 -> Id.compare x1 x2
| Capture _, _ -> -1
| _, Capture _ -> 1
| Constant (x1, y1), Constant (x2, y2) ->
let c = Id.compare x1 x2 in
if c <> 0 then c else Types.Const.compare y1 y2
| Constant _, _ -> -1
| _, Constant _ -> 1
| Dummy, Dummy -> assert false
let equal p1 p2 = compare p1 p2 == 0
let rec hash (_, _, d) =
match d with
| Constr t -> 1 + (17 * Types.hash t)
| Cup (p1, p2) -> 2 + (17 * hash p1) + (257 * hash p2)
| Cap (p1, p2) -> 3 + (17 * hash p1) + (257 * hash p2)
| Times (q1, q2) -> 4 + (17 * q1.id) + (257 * q2.id)
| Xml (q1, q2) -> 5 + (17 * q1.id) + (257 * q2.id)
| Record (l, q) -> 6 + (17 * Label.hash l) + (257 * q.id)
| Capture x -> 7 + Id.hash x
| Constant (x, c) -> 8 + (17 * Id.hash x) + (257 * Types.Const.hash c)
| Dummy -> assert false
let check _ = assert false
let dump _ = assert false
end
module PrintN = struct
module M = Map.Make (Pat)
module S = Set.Make (Pat)
let names = ref M.empty
let printed = ref S.empty
let toprint = Queue.create ()
let id = ref 0
let rec mark seen ((_, _, d) as p) =
if M.mem p !names then ()
else if S.mem p seen then (
incr id;
names := M.add p !id !names;
Queue.add p toprint)
else
let seen = S.add p seen in
match d with
| Cup (p1, p2)
| Cap (p1, p2) ->
mark seen p1;
mark seen p2
| Times (q1, q2)
| Xml (q1, q2) ->
mark seen q1.descr;
mark seen q2.descr
| Record (_, q) -> mark seen q.descr
| _ -> ()
let rec print ppf p =
try
let i = M.find p !names in
Format.fprintf ppf "P%i" i
with
| Not_found -> real_print ppf p
and real_print ppf (_, _, d) =
match d with
| Constr t -> Types.Print.print ppf t
| Cup (p1, p2) -> Format.fprintf ppf "(%a | %a)" print p1 print p2
| Cap (p1, p2) -> Format.fprintf ppf "(%a & %a)" print p1 print p2
| Times (q1, q2) ->
Format.fprintf ppf "(%a,%a)" print q1.descr print q2.descr
| Xml (q1, { descr = _, _, Times (q2, q3) }) ->
Format.fprintf ppf "<(%a) (%a)>(%a)" print q1.descr print q2.descr print
q3.descr
| Xml _ -> assert false
| Record (l, q) ->
Format.fprintf ppf "{%a=%a}" Label.print_attr l print q.descr
| Capture x -> Format.fprintf ppf "%a" Ident.print x
| Constant (x, c) ->
Format.fprintf ppf "(%a:=%a)" Ident.print x Types.Print.print_const c
| Dummy -> assert false
let print ppf p =
mark S.empty p;
print ppf p;
let first = ref true in
(try
while true do
let p = Queue.pop toprint in
if not (S.mem p !printed) then (
printed := S.add p !printed;
Format.fprintf ppf " %s@ @[%a=%a@]"
(if !first then (
first := false;
"where")
else "and")
print p real_print p)
done
with
| Queue.Empty -> ());
id := 0;
names := M.empty;
printed := S.empty
let print_xs ppf xs =
Format.fprintf ppf "{";
let rec aux = function
| [] -> ()
| [ x ] -> Ident.print ppf x
| x :: q ->
Ident.print ppf x;
Format.fprintf ppf ",";
aux q
in
aux xs;
Format.fprintf ppf "}"
end
let () = print_node := fun ppf n -> PrintN.print ppf (descr n)
(* Static semantics *)
let cup_res v1 v2 = Types.Positive.cup [ v1; v2 ]
let empty_res fv = IdMap.constant Types.(Positive.ty empty) fv
let times_res v1 v2 = Types.Positive.times v1 v2
(* Try with a hash-table *)
module MemoFilter = Map.Make (struct
type t = Types.t * node
let compare (t1, n1) (t2, n2) =
if n1.id < n2.id then -1
else if n1.id > n2.id then 1
else Types.compare t1 t2
end)
let memo_filter = ref MemoFilter.empty
let rec filter_descr t (_, fv, d) : Types.Positive.v id_map =
(* TODO: avoid is_empty t when t is not changing (Cap) *)
if Types.is_empty t then empty_res fv
else
match d with
| Constr _ -> IdMap.empty
| Cup (((a, _, _) as d1), d2) ->
IdMap.merge cup_res
(filter_descr (Types.cap t a) d1)
(filter_descr (Types.diff t a) d2)
| Cap (d1, d2) ->
IdMap.merge cup_res (filter_descr t d1) (filter_descr t d2)
| Times (p1, p2) -> filter_prod fv p1 p2 t
| Xml (p1, p2) -> filter_prod ~kind:`XML fv p1 p2 t
| Record (l, p) -> filter_node (Types.Record.project t l) p
| Capture c -> IdMap.singleton c (Types.Positive.ty t)
| Constant (c, cst) ->
IdMap.singleton c (Types.Positive.ty (Types.constant cst))
| Dummy -> assert false
and filter_prod ?kind fv p1 p2 t =
List.fold_left
(fun accu (d1, d2) ->
let term =
IdMap.merge times_res (filter_node d1 p1) (filter_node d2 p2)
in
IdMap.merge cup_res accu term)
(empty_res fv)
(Types.(Product.normal ?kind t) :> Types.Product.t)
and filter_node t p : Types.Positive.v id_map =
try MemoFilter.find (t, p) !memo_filter with
| Not_found ->
let _, fv, _ = descr p in
let res = IdMap.map_from_slist (fun _ -> Types.Positive.forward ()) fv in
memo_filter := MemoFilter.add (t, p) res !memo_filter;
let r = filter_descr t (descr p) in
IdMap.collide Types.Positive.define res r;
r
let filter t p =
let r = filter_node t p in
memo_filter := MemoFilter.empty;
IdMap.map Types.Positive.solve r
let filter_descr t p =
let r = filter_descr t p in
memo_filter := MemoFilter.empty;
IdMap.get (IdMap.map Types.Positive.solve r)
(* Factorization of capture variables and constant patterns *)
module Factorize = struct
module NodeTypeSet = Set.Make (Custom.Pair (Node) (Types))
let pi1 ~kind t = Types.Product.pi1 (Types.Product.get ~kind t)
let pi2 ~kind t = Types.Product.pi2 (Types.Product.get ~kind t)
(* Note: this is incomplete because of non-atomic constant patterns:
# debug approx (_,(x:=(1,2))) (1,2);;
[DEBUG:approx]
x=(1,2)
*)
let rec approx_var seen (a, fv, d) t xs =
(* assert (Types.subtype t a);
assert (IdSet.subset xs fv); *)
if IdSet.is_empty xs || Types.is_empty t then xs
else
match d with
| Cup (((a1, _, _) as p1), p2) ->
approx_var seen p2 (Types.diff t a1)
(approx_var seen p1 (Types.cap t a1) xs)
| Cap (((_, fv1, _) as p1), ((_, fv2, _) as p2)) ->
IdSet.cup
(approx_var seen p1 t (IdSet.cap fv1 xs))
(approx_var seen p2 t (IdSet.cap fv2 xs))
| Capture _ -> xs
| Constant (_, c) ->
if Types.subtype t (Types.constant c) then xs else IdSet.empty
| Times (q1, q2) ->
let xs = IdSet.cap xs (IdSet.cap q1.fv q2.fv) in
IdSet.cap
(approx_var_node seen q1 (pi1 ~kind:`Normal t) xs)
(approx_var_node seen q2 (pi2 ~kind:`Normal t) xs)
| Xml (q1, q2) ->
let xs = IdSet.cap xs (IdSet.cap q1.fv q2.fv) in
IdSet.cap
(approx_var_node seen q1 (pi1 ~kind:`XML t) xs)
(approx_var_node seen q2 (pi2 ~kind:`XML t) xs)
| Record _ -> IdSet.empty
| _ -> assert false
and approx_var_node seen q t xs =
if NodeTypeSet.mem (q, t) seen then xs
else approx_var (NodeTypeSet.add (q, t) seen) q.descr t xs
(* Obviously not complete ! *)
let rec approx_nil seen (a, fv, d) t xs =
assert (Types.subtype t a);
assert (IdSet.subset xs fv);
if IdSet.is_empty xs || Types.is_empty t then xs
else
match d with
| Cup (((a1, _, _) as p1), p2) ->
approx_nil seen p2 (Types.diff t a1)
(approx_nil seen p1 (Types.cap t a1) xs)
| Cap (((_, fv1, _) as p1), ((_, fv2, _) as p2)) ->
IdSet.cup
(approx_nil seen p1 t (IdSet.cap fv1 xs))
(approx_nil seen p2 t (IdSet.cap fv2 xs))
| Constant (_, c) when Types.(Const.equal c Sequence.nil_cst) -> xs
| Times (q1, q2) ->
let xs = IdSet.cap q2.fv (IdSet.diff xs q1.fv) in
approx_nil_node seen q2 (pi2 ~kind:`Normal t) xs
| _ -> IdSet.empty
and approx_nil_node seen q t xs =
if NodeTypeSet.mem (q, t) seen then xs
else approx_nil (NodeTypeSet.add (q, t) seen) q.descr t xs
(*
let cst ((a,_,_) as p) t xs =
if IdSet.is_empty xs then IdMap.empty
else
let rec aux accu (x,t) =
if (IdSet.mem xs x) then
match Sample.single_opt (Types.descr t) with
| Some c -> (x,c)::accu
| None -> accu
else accu in
let t = Types.cap t a in
IdMap.from_list_disj (List.fold_left aux [] (filter_descr t p))
*)
let var ((a, _, _) as p) t = approx_var NodeTypeSet.empty p (Types.cap t a)
let nil ((a, _, _) as p) t = approx_nil NodeTypeSet.empty p (Types.cap t a)
end
(* Normal forms for patterns and compilation *)
let min (a : int) (b : int) = if a < b then a else b
let any_basic = Types.Record.or_absent Types.non_constructed
let rec first_label (acc, fv, d) =
if Types.is_empty acc then Label.dummy
else
match d with
| Constr t -> Types.Record.first_label t
| Cap (p, q) -> Label.min (first_label p) (first_label q)
| Cup (((acc1, _, _) as p), q) -> Label.min (first_label p) (first_label q)
| Record (l, p) -> l
| _ -> Label.dummy
module Normal = struct
type source =
| SCatch
| SConst of Types.const
type result = source id_map
let compare_source s1 s2 =
if s1 == s2 then 0
else
match (s1, s2) with
| SCatch, _ -> -1
| _, SCatch -> 1
| SConst c1, SConst c2 -> Types.Const.compare c1 c2
(*
let hash_source = function
| SCatch -> 1
| SConst c -> Types.Const.hash c
*)
let compare_result r1 r2 = IdMap.compare compare_source r1 r2
module ResultMap = Map.Make (struct
type t = result
let compare = compare_result
end)
module NodeSet = SortedList.Make (Node)
module Nnf = struct
include Custom.Dummy
type t = NodeSet.t * Types.t * IdSet.t (* pl,t; t <= \accept{pl} *)
let check (pl, t, xs) =
List.iter
(fun p -> assert (Types.subtype t (Types.descr p.accept)))
(NodeSet.get pl)
let print ppf (pl, t, xs) =
Format.fprintf ppf "@[(pl=%a;t=%a;xs=%a)@]" NodeSet.dump pl
Types.Print.print t IdSet.dump xs
let compare (l1, t1, xs1) (l2, t2, xs2) =
let c = NodeSet.compare l1 l2 in
if c <> 0 then c
else
let c = Types.compare t1 t2 in
if c <> 0 then c else IdSet.compare xs1 xs2
let hash (l, t, xs) =
NodeSet.hash l + (17 * Types.hash t) + (257 * IdSet.hash xs)
let equal x y = compare x y == 0
let first_label (pl, t, xs) =
NodeSet.fold
(fun l p -> Label.min l (first_label (descr p)))
(Types.Record.first_label t)
pl
end
module NProd = struct
type t = result * Nnf.t * Nnf.t
let compare (r1, x1, y1) (r2, x2, y2) =
let c = compare_result r1 r2 in
if c <> 0 then c
else
let c = Nnf.compare x1 x2 in
if c <> 0 then c else Nnf.compare y1 y2
end
module NLineProd = Set.Make (NProd)
type record =
| RecNolabel of result option * result option
| RecLabel of label * NLineProd.t
type t = {
nprod : NLineProd.t;
nxml : NLineProd.t;
nrecord : record;
}
let fus = IdMap.union_disj
let nempty lab =
{
nprod = NLineProd.empty;
nxml = NLineProd.empty;
nrecord =
(match lab with
| Some l -> RecLabel (l, NLineProd.empty)
| None -> RecNolabel (None, None));
}
let dummy = nempty None
let ncup nf1 nf2 =
{
nprod = NLineProd.union nf1.nprod nf2.nprod;
nxml = NLineProd.union nf1.nxml nf2.nxml;
nrecord =
(match (nf1.nrecord, nf2.nrecord) with
| RecLabel (l1, r1), RecLabel (l2, r2) ->
(* assert (l1 = l2); *)
RecLabel (l1, NLineProd.union r1 r2)
| RecNolabel (x1, y1), RecNolabel (x2, y2) ->
RecNolabel
((if x1 == None then x2 else x1), if y1 == None then y2 else y1)
| _ -> assert false);
}
let double_fold_prod f l1 l2 =
NLineProd.fold
(fun x1 accu -> NLineProd.fold (fun x2 accu -> f accu x1 x2) l2 accu)
l1 NLineProd.empty
let ncap nf1 nf2 =
let prod
accu
(res1, (pl1, t1, xs1), (ql1, s1, ys1))
(res2, (pl2, t2, xs2), (ql2, s2, ys2)) =
let t = Types.cap t1 t2 in
if Types.is_empty t then accu
else
let s = Types.cap s1 s2 in
if Types.is_empty s then accu
else
NLineProd.add
( fus res1 res2,
(NodeSet.cup pl1 pl2, t, IdSet.cup xs1 xs2),
(NodeSet.cup ql1 ql2, s, IdSet.cup ys1 ys2) )
accu
in
let record r1 r2 =
match (r1, r2) with
| RecLabel (l1, r1), RecLabel (l2, r2) ->
(* assert (l1 = l2); *)
RecLabel (l1, double_fold_prod prod r1 r2)
| RecNolabel (x1, y1), RecNolabel (x2, y2) ->
let x =
match (x1, x2) with
| Some res1, Some res2 -> Some (fus res1 res2)
| _ -> None
and y =
match (y1, y2) with
| Some res1, Some res2 -> Some (fus res1 res2)
| _ -> None
in
RecNolabel (x, y)
| _ -> assert false
in
{
nprod = double_fold_prod prod nf1.nprod nf2.nprod;
nxml = double_fold_prod prod nf1.nxml nf2.nxml;
nrecord = record nf1.nrecord nf2.nrecord;
}
let nnode p xs = (NodeSet.singleton p, Types.descr p.accept, xs)
let nc t = (NodeSet.empty, t, IdSet.empty)
let ncany = nc Types.any
let ncany_abs = nc Types.Record.any_or_absent
let empty_res = IdMap.empty
let single_prod src p q = NLineProd.singleton (src, p, q)
let ntimes lab acc p q xs =
let xsp = IdSet.cap xs p.fv
and xsq = IdSet.cap xs q.fv in
{
(nempty lab) with
nprod = single_prod empty_res (nnode p xsp) (nnode q xsq);
}
let nxml lab acc p q xs =
let xsp = IdSet.cap xs p.fv
and xsq = IdSet.cap xs q.fv in
{
(nempty lab) with
nxml = single_prod empty_res (nnode p xsp) (nnode q xsq);
}
let nrecord (lab : Label.t option) acc (l : Label.t) p xs =
let label = get_opt lab in
(* Format.fprintf
Format.std_formatter "label=%a l=%a@."
Label.print label Label.print l; *)
assert (Label.compare label l <= 0);
let lft, rgt =
if Label.equal l label then (nnode p xs, ncany)
else (ncany_abs, nnode (cons p.fv (record l p)) xs)
in
{
(nempty lab) with
nrecord = RecLabel (label, single_prod empty_res lft rgt);
}
let nconstr lab t =
let aux l =
List.fold_left
(fun accu (t1, t2) -> NLineProd.add (empty_res, nc t1, nc t2) accu)
NLineProd.empty l
in
let record =
match lab with
| None ->
let x, y = Types.Record.empty_cases t in
RecNolabel
( (if x then Some empty_res else None),
if y then Some empty_res else None )
| Some l -> RecLabel (l, aux (Types.Record.split_normal t l))
in
{
nprod = aux (Types.Product.clean_normal (Types.Product.normal t));
nxml =
aux (Types.Product.clean_normal (Types.Product.normal ~kind:`XML t));
nrecord = record;
}
let nany lab res =
{
nprod = single_prod res ncany ncany;
nxml = single_prod res ncany ncany;
nrecord =
(match lab with
| None -> RecNolabel (Some res, Some res)
| Some lab -> RecLabel (lab, single_prod res ncany_abs ncany));
}
let nconstant lab x c = nany lab (IdMap.singleton x (SConst c))
let ncapture lab x = nany lab (IdMap.singleton x SCatch)
let rec nnormal lab (acc, fv, d) xs =
let xs = IdSet.cap xs fv in
if Types.is_empty acc then nempty lab
else if IdSet.is_empty xs then nconstr lab acc
else
match d with
| Constr t -> assert false
| Cap (p, q) -> ncap (nnormal lab p xs) (nnormal lab q xs)
| Cup (((acc1, _, _) as p), q) ->
ncup (nnormal lab p xs)
(ncap (nnormal lab q xs) (nconstr lab (Types.neg acc1)))
| Times (p, q) -> ntimes lab acc p q xs
| Xml (p, q) -> nxml lab acc p q xs
| Capture x -> ncapture lab x
| Constant (x, c) -> nconstant lab x c
| Record (l, p) -> nrecord lab acc l p xs
| Dummy -> assert false
(*TODO: when an operand of Cap has its first_label > lab,
directly shift it*)
let facto f t xs pl =
NodeSet.fold
(fun vs p -> IdSet.cup vs (f (descr p) t (IdSet.cap (fv p) xs)))
IdSet.empty pl
let factorize t0 (pl, t, xs) =
let t0 = if Types.subtype t t0 then t else Types.cap t t0 in
let vs_var = facto Factorize.var t0 xs pl in
let xs = IdSet.diff xs vs_var in
let vs_nil = facto Factorize.nil t0 xs pl in
let xs = IdSet.diff xs vs_nil in
(vs_var, vs_nil, (pl, t, xs))
let normal l t pl xs =
NodeSet.fold (fun a p -> ncap a (nnormal l (descr p) xs)) (nconstr l t) pl
let nnf lab t0 (pl, t, xs) =
(* assert (not (Types.disjoint t t0)); *)
let t = if Types.subtype t t0 then t else Types.cap t t0 in
normal lab t pl xs
let basic_tests f ((pl, t, xs) : NodeSet.t * _ * _) =
let rec aux more s accu t res = function
(* Invariant: t and s disjoint, t not empty *)
| [] ->
let accu =
try
let t' = ResultMap.find res accu in
ResultMap.add res (Types.cup t t') accu
with
| Not_found -> ResultMap.add res t accu
in
cont (Types.cup t s) accu more
| (tp, xp, d) :: r -> (
if IdSet.disjoint xp xs then
aux_check more s accu (Types.cap t tp) res r
else
match d with
| Cup (p1, p2) ->
aux ((t, res, p2 :: r) :: more) s accu t res (p1 :: r)
| Cap (p1, p2) -> aux more s accu t res (p1 :: p2 :: r)
| Capture x -> aux more s accu t (IdMap.add x SCatch res) r
| Constant (x, c) ->
aux more s accu t (IdMap.add x (SConst c) res) r
| _ -> cont s accu more)
and aux_check more s accu t res pl =
if Types.is_empty t then cont s accu more else aux more s accu t res pl
and cont s accu = function
| [] -> ResultMap.iter f accu
| (t, res, pl) :: tl -> aux_check tl s accu (Types.diff t s) res pl
in
aux_check [] Types.empty ResultMap.empty (Types.cap t any_basic) IdMap.empty
(List.map descr (pl :> Node.t list))
(*
let prod_tests (pl,t,xs) =
let rec aux accu q1 q2 res = function
| [] -> (res,q1,q2) :: accu
| (tp,xp,d) :: r ->
if (IdSet.disjoint xp xs)
then aux_check accu q1 q2 res tp r
else match d with
| Cup (p1,p2) -> aux (aux accu q1 q2 res (p2::r)) q1 q2 res (p1::r)
| Cap (p1,p2) -> aux accu q1 q2 res (p1 :: p2 :: r)
| Capture x -> aux accu q1 q2 (IdMap.add x SCatch res) r
| Constant (x,c) -> aux accu q1 q2 (IdMap.add x (SConst c) res) r
| Times (p1,p2) ->
let (pl1,t1,xs1) = q1 and (pl2,t2,xs2) = q2 in
let t1 = Types.cap t1 (Types.descr (accept p1)) in
if Types.is_empty t1 then accu
else let t2 = Types.cap t2 (Types.descr (accept p2)) in
if Types.is_empty t2 then accu
else
let q1 =
let xs1' = IdSet.cap (fv p1) xs in
if IdSet.is_empty xs1' then (pl1,t1,xs1)
else (NodeSet.add p1 pl1, t1, IdSet.cup xs1 xs1')
and q2 =
let xs2' = IdSet.cap (fv p2) xs in
if IdSet.is_empty xs2' then (pl2,t2,xs2)
else (NodeSet.add p2 pl2, t2, IdSet.cup xs2 xs2')
in
aux accu q1 q2 res r
| _ -> accu
and aux_check accu q1 q2 res t r =
List.fold_left
(fun accu (t1',t2') ->
let (pl1,t1,xs1) = q1 and (pl2,t2,xs2) = q2 in
let t1 = Types.cap t1 t1' in
if Types.is_empty t1 then accu
else let t2 = Types.cap t2 t2' in
if Types.is_empty t2 then accu
else aux accu (pl1,t1,xs1) (pl2,t2,xs2) res r)
accu (Types.Product.clean_normal (Types.Product.normal t))
in
aux_check [] ncany ncany IdMap.empty t (List.map descr pl)
*)
end
module Compile = struct
open Auto_pat
type return_code =
Types.t * int * (* accepted type, arity *)
int id_map option array
type interface =
[ `Result of int
| `Switch of interface * interface
| `None
]
type dispatcher = {
id : int;
t : Types.t;
pl : Normal.Nnf.t array;
label : label option;
interface : interface;
codes : return_code array;
state : state;
}
let dispatcher_of_state = Hashtbl.create 1024
let equal_array f a1 a2 =
let rec aux i = i < 0 || (f a1.(i) a2.(i) && aux (i - 1)) in
let l1 = Array.length a1
and l2 = Array.length a2 in
l1 == l2 && aux (l1 - 1)
let array_for_all f a =
let rec aux f a i = i < 0 || (f a.(i) && aux f a (pred i)) in
aux f a (Array.length a - 1)
let array_for_all_i f a =
let rec aux f a i = i < 0 || (f i a.(i) && aux f a (pred i)) in
aux f a (Array.length a - 1)
let equal_source s1 s2 =
s1 == s2
||
match (s1, s2) with
| Const x, Const y -> Types.Const.equal x y
| Stack x, Stack y -> x == y
| Recompose (x1, x2), Recompose (y1, y2) -> x1 == y1 && x2 == y2
| _ -> false
let equal_result (r1, s1, l1) (r2, s2, l2) =
r1 == r2 && equal_array equal_source s1 s2 && l1 == l2
let equal_result_dispatch d1 d2 =
d1 == d2
||
match (d1, d2) with
| Dispatch (d1, a1), Dispatch (d2, a2) ->
d1 == d2 && equal_array equal_result a1 a2
| TailCall d1, TailCall d2 -> d1 == d2
| Ignore a1, Ignore a2 -> equal_result a1 a2
| _ -> false
let immediate_res basic prod xml record =
let res : result option ref = ref None in
let chk = function
| Catch
| Const _ ->
true
| _ -> false
in
let f ((_, ret, _) as r) =
match !res with
| Some r0 when equal_result r r0 -> ()
| None when array_for_all chk ret -> res := Some r
| _ -> raise Exit
in
(match basic with
| [ (_, r) ] -> f r
| [] -> ()
| _ -> raise Exit);
(match prod with
| Ignore (Ignore r) -> f r
| Impossible -> ()
| _ -> raise Exit);
(match xml with
| Ignore (Ignore r) -> f r
| Impossible -> ()
| _ -> raise Exit);
(match record with
| None -> ()
| Some (RecLabel (_, Ignore (Ignore r))) -> f r
| Some (RecNolabel (Some r1, Some r2)) ->
f r1;
f r2
| _ -> raise Exit);
match !res with
| Some r -> r
| None -> raise Exit
let split_kind basic prod xml record =
{
basic;
atoms =
AtomSet.mk_map (List.map (fun (t, r) -> (Types.Atom.get t, r)) basic);
chars =
CharSet.mk_map (List.map (fun (t, r) -> (Types.Char.get t, r)) basic);
prod;
xml;
record;
}
let combine_kind basic prod xml record =
try AIgnore (immediate_res basic prod xml record) with
| Exit -> AKind (split_kind basic prod xml record)
let combine f (disp, act) =
if Array.length act == 0 then Impossible
else if
array_for_all (fun (_, ar, _) -> ar == 0) disp.codes
&& array_for_all (f act.(0)) act
then Ignore act.(0)
else Dispatch (disp.state, act)
let detect_tail_call f = function
| Dispatch (disp, branches) when array_for_all_i f branches -> TailCall disp
| x -> x
let detect_right_tail_call =
detect_tail_call (fun i (code, ret, _) ->
i == code
&&
let ar = Array.length ret in
array_for_all_i
(fun pos -> function
| Stack j when pos + j == ar -> true
| _ -> false)
ret)
let detect_left_tail_call =
detect_tail_call (fun i -> function
| Ignore (code, ret, _) when i == code ->
let ar = Array.length ret in
array_for_all_i
(fun pos -> function
| Stack j when pos + j == ar -> true
| _ -> false)
ret
| _ -> false)
let cur_id = ref 0
module NfMap = Map.Make (Normal.Nnf)
module NfSet = Set.Make (Normal.Nnf)
module DispMap = Map.Make (Custom.Pair (Types) (Custom.Array (Normal.Nnf)))
(* Try with a hash-table ! *)
let dispatchers = ref DispMap.empty
let rec print_iface ppf = function
| `Result i -> Format.fprintf ppf "Result(%i)" i
| `Switch (yes, no) ->
Format.fprintf ppf "Switch(%a,%a)" print_iface yes print_iface no
| `None -> Format.fprintf ppf "None"
let dump_disp disp =
let ppf = Format.std_formatter in
Format.fprintf ppf "Dispatcher t=%a@." Types.Print.print disp.t;
Array.iter
(fun p -> Format.fprintf ppf " pat %a@." Normal.Nnf.print p)
disp.pl
let first_lab t reqs =
let aux l req = Label.min l (Normal.Nnf.first_label req) in
let lab = Array.fold_left aux (Types.Record.first_label t) reqs in
if lab == Label.dummy then None else Some lab
let dummy_actions = AIgnore (-1, [||], -1)
let compute_actions = ref (fun _ -> assert false)
let dispatcher t pl : dispatcher =
try DispMap.find (t, pl) !dispatchers with
| Not_found ->
let lab = first_lab t pl in
let nb = ref 0 in
let codes = ref [] in
let rec aux t arity i accu =
if i == Array.length pl then (
incr nb;
let r = Array.of_list (List.rev accu) in
codes := (t, arity, r) :: !codes;
`Result (!nb - 1))
else
let _, tp, v = pl.(i) in
let a1 = Types.cap t tp in
if Types.is_empty a1 then
`Switch (`None, aux t arity (i + 1) (None :: accu))
else
let a2 = Types.diff t tp in
let accu' = Some (IdMap.num arity v) :: accu in
if Types.is_empty a2 then
`Switch (aux t (arity + IdSet.length v) (i + 1) accu', `None)
else
`Switch
( aux a1 (arity + IdSet.length v) (i + 1) accu',
aux a2 arity (i + 1) (None :: accu) )
(* Unopt version:
`Switch
(
aux (Types.cap t tp) (arity + (IdSet.length v)) (i+1) accu',
aux (Types.diff t tp) arity (i+1) accu
)
*)
in
let iface = if Types.is_empty t then `None else aux t 0 0 [] in
let codes = Array.of_list (List.rev !codes) in
let state =
{
uid = !cur_id;
arity = Array.map (fun (_, ar, _) -> ar) codes;
actions = dummy_actions;
fail_code = -1;
expected_type = "";
}
in
let disp =
{ id = !cur_id; t; label = lab; pl; interface = iface; codes; state }
in
incr cur_id;
Hashtbl.add dispatcher_of_state state.uid disp;
dispatchers := DispMap.add (t, pl) disp !dispatchers;
!compute_actions disp;
disp
let find_code d a =
let rec aux i = function
| `Result code -> code
| `None ->
Format.fprintf Format.std_formatter "IFACE=%a@." print_iface
d.interface;
for i = 0 to Array.length a - 1 do
Format.fprintf Format.std_formatter "a.(%i)=%b@." i (a.(i) != None)
done;
assert false
| `Switch (yes, _) when a.(i) != None -> aux (i + 1) yes
| `Switch (_, no) -> aux (i + 1) no
in
aux 0 d.interface
let create_result pl =
let aux x accu =
match x with
| Some b -> b @ accu
| None -> accu
in
Array.of_list (Array.fold_right aux pl [])
let return disp pl f ar =
let aux = function
| x :: _ -> Some (f x)
| [] -> None
in
let final = Array.map aux pl in
(find_code disp final, create_result final, ar)
let conv_source = function
| Normal.SCatch -> Catch
| Normal.SConst c -> Const c
let return_basic disp selected =
let aux_final res = IdMap.map_to_list conv_source res in
return disp selected aux_final 0
(* let print_idset ppf s =
let s = String.concat "," (List.map (fun x -> Ident.to_string x) s) in
Format.fprintf ppf "{%s}" s
let print_idmap ppf s =
print_idset ppf (IdMap.domain s) *)
let merge_res_prod ofs1 ofs2 (lvars, lnils, lres) (rvars, rnils, rres) extra =
let lres =
IdMap.union_disj
(IdMap.map (fun i -> Stack (ofs1 + ofs2 - i)) lres)
(IdMap.union_disj
(IdMap.constant Left lvars)
(IdMap.constant Nil lnils))
in
let rres =
IdMap.union_disj
(IdMap.map (fun i -> Stack (ofs2 - i)) rres)
(IdMap.union_disj
(IdMap.constant Right rvars)
(IdMap.constant Nil rnils))
in
let sub =
IdMap.merge
(fun l r ->
match (l, r) with
| Left, Right -> Catch
| _ ->
let l =
match l with
| Left -> -1
| Nil -> -2
| Stack i -> i
| _ -> assert false
in
let r =
match r with
| Right -> -1
| Nil -> -2
| Stack i -> i
| _ -> assert false
in
Recompose (l, r))
lres rres
in
IdMap.map_to_list
(fun x -> x)
(IdMap.union_disj sub (IdMap.map conv_source extra))
module TypeList = SortedList.Make (Types)
let dispatch_basic disp : (Types.t * result) list =
let tests =
let accu = ref [] in
let aux i res t = accu := (t, [ (i, res) ]) :: !accu in
Array.iteri (fun i p -> Normal.basic_tests (aux i) p) disp.pl;
TypeList.Map.get (TypeList.Map.from_list ( @ ) !accu)
in
let t = Types.cap any_basic disp.t in
let accu = ref [] in
let rec aux (success : (int * Normal.result) list) t l =
if Types.non_empty t then
match l with
| [] ->
let selected = Array.make (Array.length disp.pl) [] in
let add (i, res) = selected.(i) <- res :: selected.(i) in
List.iter add success;
accu := (t, return_basic disp selected) :: !accu
| (ty, i) :: rem ->
aux (i @ success) (Types.cap t ty) rem;
aux success (Types.diff t ty) rem
in
aux [] t tests;
!accu
let get_tests facto pl f t d post =
let pl = Array.map (List.map f) pl in
let factorized = ref NfMap.empty in
(* Collect all subrequests *)
let aux reqs (req, _) =
let ((_, _, ((_, tr, xs) as r')) as req') =
if facto then Normal.factorize t req else IdSet.(empty, empty, req)
in
factorized := NfMap.add req req' !factorized;
if IdSet.is_empty xs && Types.subtype t tr then reqs
else NfSet.add r' reqs
in
let reqs = Array.fold_left (List.fold_left aux) NfSet.empty pl in
let reqs = Array.of_list (NfSet.elements reqs) in
(* Map subrequest -> idx in reqs *)
let idx = ref NfMap.empty in
Array.iteri (fun i req -> idx := NfMap.add req i !idx) reqs;
let idx = !idx in
(* Build dispatcher *)
(* if Array.length reqs = 0 then print_endline "NOREQ!"; *)
let disp =
dispatcher
(if Array.length reqs = 0 then Types.Record.any_or_absent else t)
reqs
in
(* Build continuation *)
let result (t, ar, m) =
let get (req, info) a =
let var, nil, r' =
try NfMap.find req !factorized with
| Not_found -> assert false
in
try
let i = NfMap.find r' idx in
match m.(i) with
| Some res -> ((var, nil, res), info) :: a
| _ -> a
with
| Not_found -> ((var, nil, IdMap.empty), info) :: a
in
let pl = Array.map (fun l -> List.fold_right get l []) pl in
d t ar pl
in
let res = Array.map result disp.codes in
post (disp, res)
let add_factorized disp rhs =
let result ((code, srcs, pop) as r) =
match rhs.(code) with
| Fail -> r
| Match (_, (var, nil, xs, _)) ->
let pos = ref (-1) in
let var x =
if IdSet.mem var x then Catch
else if IdSet.mem nil x then Nil
else (
incr pos;
srcs.(!pos))
in
let srcs' = Array.of_list (List.map var (IdSet.get xs)) in
assert (succ !pos = Array.length srcs);
(code, srcs', pop)
in
let dispatch1 = function
| Dispatch (s, a) -> Dispatch (s, Array.map result a)
| TailCall s ->
let f code (_, ar, _) =
let srcs = Array.init ar (fun i -> Stack (ar - i)) in
result (code, srcs, ar)
in
Dispatch (s, Array.mapi f disp.codes)
| Ignore r -> Ignore (result r)
| Impossible -> Impossible
in
let dispatch2 = function
| Dispatch (s, a) -> Dispatch (s, Array.map dispatch1 a)
| TailCall s ->
let f code (_, ar, _) =
let srcs = Array.init ar (fun i -> Stack (ar - i)) in
Ignore (result (code, srcs, ar))
in
Dispatch (s, Array.mapi f disp.codes)
| Ignore r -> Ignore (dispatch1 r)
| Impossible -> Impossible
in
let state = disp.state in
let actions =
match state.actions with
| AIgnore r -> AIgnore (result r)
| AKind k ->
AKind
{
basic = List.map (fun (t, r) -> (t, result r)) k.basic;
atoms = AtomSet.map_map result k.atoms;
chars = CharSet.map_map result k.chars;
prod = dispatch2 k.prod;
xml = dispatch2 k.xml;
record =
(match k.record with
| None -> None
| Some (RecLabel (l, x)) -> Some (RecLabel (l, dispatch2 x))
| Some (RecNolabel (x, y)) ->
Some
(RecNolabel
( (match x with
| None -> None
| Some r -> Some (result r)),
match y with
| None -> None
| Some r -> Some (result r) )));
}
in
{ state with actions }
let make_branches t brs =
let t0 = ref t in
let aux (p, e) =
let xs = fv p in
let tp = Types.descr (accept p) in
let nnf = (Normal.NodeSet.singleton p, Types.cap !t0 tp, xs) in
t0 := Types.diff !t0 tp;
[ (nnf, (xs, e)) ]
in
let has_facto = ref false in
let res _ _ pl =
let aux r = function
| [ ((var, nil, res), (xs, e)) ] ->
assert (r == Fail);
let i = ref 0 in
IdSet.iter
(fun x ->
if IdSet.mem var x || IdSet.mem nil x then has_facto := true
else (
assert (IdMap.assoc x res = !i);
incr i))
xs;
Match (IdSet.length xs, (var, nil, xs, e))
| [] -> r
| _ -> assert false
in
Array.fold_left aux Fail pl
in
(* Format.fprintf Format.std_formatter
"make_branches t=%a #branches=%i@." Types.Print.print t (List.length brs); *)
let pl = Array.map aux (Array.of_list brs) in
let disp, rhs = get_tests true pl (fun x -> x) t res (fun x -> x) in
let state = if !has_facto then add_factorized disp rhs else disp.state in
( state,
Array.map
(function
| Match (n, (_, _, _, e)) -> Match (n, e)
| Fail -> Fail)
rhs )
let rec dispatch_prod0 disp t pl =
get_tests true pl
(fun (res, p, q) -> (p, (res, q)))
(Types.Product.pi1 t) (dispatch_prod1 disp t)
(fun x -> detect_left_tail_call (combine equal_result_dispatch x))
and dispatch_prod1 disp t t1 ar1 pl =
get_tests true pl
(fun (ret1, (res, q)) -> (q, (ret1, res)))
(Types.Product.pi2_restricted t1 t)
(dispatch_prod2 disp ar1)
(fun x -> detect_right_tail_call (combine equal_result x))
and dispatch_prod2 disp ar1 t2 ar2 pl =
let aux_final (ret2, (ret1, res)) = merge_res_prod ar1 ar2 ret1 ret2 res in
return disp pl aux_final (ar1 + ar2)
let dispatch_prod disp pl =
let t = Types.Product.get disp.t in
if t == [] then Impossible
else
dispatch_prod0 disp t
(Array.map (fun p -> Normal.NLineProd.elements p.Normal.nprod) pl)
(* dispatch_prod0 disp t (Array.map Normal.prod_tests disp.pl) *)
let dispatch_xml disp pl =
let t = Types.Product.get ~kind:`XML disp.t in
if t == [] then Impossible
else
dispatch_prod0 disp t
(Array.map (fun p -> Normal.NLineProd.elements p.Normal.nxml) pl)
let dispatch_record disp pl : record option =
let t = disp.t in
if not (Types.Record.has_record t) then None
else
match disp.label with
| None ->
let some, none = Types.Record.empty_cases t in
let some =
if some then
let pl =
Array.map
(fun p ->
match p.Normal.nrecord with
| Normal.RecNolabel (Some x, _) -> [ x ]
| Normal.RecNolabel (None, _) -> []
| _ -> assert false)
pl
in
Some (return disp pl (IdMap.map_to_list conv_source) 0)
else None
in
let none =
if none then
let pl =
Array.map
(fun p ->
match p.Normal.nrecord with
| Normal.RecNolabel (_, Some x) -> [ x ]
| Normal.RecNolabel (_, None) -> []
| _ -> assert false)
pl
in
Some (return disp pl (IdMap.map_to_list conv_source) 0)
else None
in
Some (RecNolabel (some, none))
| Some lab ->
let t = Types.Record.split t lab in
let pl =
Array.map
(fun p ->
match p.Normal.nrecord with
| Normal.RecLabel (_, l) -> Normal.NLineProd.elements l
| _ -> assert false)
pl
in
Some (RecLabel (lab, dispatch_prod0 disp t pl))
(*
let iter_disp_disp f g = function
| Dispatch (d,a) -> f d; Array.iter g a
| TailCall d -> f d
| Ignore a -> g a
| Impossible -> ()
let iter_disp_prod f = iter_disp_disp f (iter_disp_disp f (fun _ -> ()))
let rec iter_disp_actions f = function
| AIgnore _ -> ()
| AKind k ->
iter_disp_prod f k.prod;
iter_disp_prod f k.xml;
(match k.record with Some (RecLabel (_,p)) -> iter_disp_prod f p
| _ -> ())
*)
let () =
compute_actions :=
fun disp ->
let pl = Array.map (Normal.nnf disp.label disp.t) disp.pl in
let a =
combine_kind (dispatch_basic disp) (dispatch_prod disp pl)
(dispatch_xml disp pl) (dispatch_record disp pl)
in
disp.state.actions <- a
let find_array pred a =
let res = ref (-1) in
for i = 0 to Array.length a - 1 do
if pred a.(i) then (
assert (!res = -1);
res := i)
done;
!res
let new_fail_res fail =
find_array (function
| code, _, _ when code = fail -> true
| _ -> false)
let new_fail_disp fail =
find_array (function
| Ignore (code, _, _) when code = fail -> true
| _ -> false)
let rec prepare_checker fail state =
if state.fail_code >= 0 then assert (state.fail_code == fail)
else (
state.fail_code <- fail;
let expect = ref Types.empty in
Array.iteri
(fun i (t, _, _) -> if i != fail then expect := Types.cup t !expect)
(Hashtbl.find dispatcher_of_state state.uid).codes;
state.expected_type <- Types.Print.to_string !expect;
prepare_checker_actions fail state.actions)
and prepare_checker_actions fail = function
| AIgnore _ -> ()
| AKind k -> (
prepare_checker_prod fail k.prod;
prepare_checker_prod fail k.xml;
match k.record with
| Some (RecLabel (_, d)) -> prepare_checker_prod fail d
| _ -> ())
and prepare_checker_prod fail = function
| Dispatch (state, cont) ->
let f = new_fail_disp fail cont in
if f >= 0 then prepare_checker f state;
Array.iter (prepare_checker_prod2 fail) cont
| TailCall state -> prepare_checker fail state
| Ignore d2 -> prepare_checker_prod2 fail d2
| Impossible -> ()
and prepare_checker_prod2 fail = function
| Dispatch (state, cont) ->
let f = new_fail_res fail cont in
assert (f >= 0);
prepare_checker f state
| TailCall state -> prepare_checker fail state
| Ignore _ -> ()
| Impossible -> ()
let make_checker t0 t =
let p = make IdSet.empty in
define p (constr t);
let d, rhs = make_branches t0 [ (p, ()) ] in
let code = ref (-1) in
Array.iteri
(fun (i : int) rhs ->
match rhs with
| Fail ->
assert (!code < 0);
code := i
| _ -> ())
rhs;
if !code >= 0 then prepare_checker !code d;
d
end
module Print = PrintN
lang/typing/typer.ml
open Cduce_loc
open Ast
open Ident
let ( = ) (x : int) y = x = y
let ( <= ) (x : int) y = x <= y
let ( < ) (x : int) y = x < y
let ( >= ) (x : int) y = x >= y
let ( > ) (x : int) y = x > y
let _warning loc msg =
let ppf = Format.err_formatter in
Cduce_loc.print_loc ppf (loc, `Full);
Format.fprintf ppf ": Warning: %s@." msg
type schema = {
sch_uri : string;
sch_ns : Ns.Uri.t;
sch_comps : (Types.t * Schema_validator.t) Ident.Env.t;
}
type item =
(* These are really exported by CDuce units: *)
| Type of (Types.t * Var.t list)
| Val of Types.t
| ECDuce of Compunit.t
| ESchema of schema
| ENamespace of Ns.Uri.t
(* These are only used internally: *)
| EVal of Compunit.t * id * Types.t
| EOCaml of string
| EOCamlComponent of string
| ESchemaComponent of (Types.t * Schema_validator.t)
type t = {
ids : item Env.t;
ids_loc : loc Env.t;
ns : Ns.table;
keep_ns : bool;
poly_vars : (U.t * Var.t) list;
mono_vars : Var.Set.t;
mutable weak_vars : Types.t option Var.Map.map;
}
(* Namespaces *)
let set_ns_table_for_printer env = Ns.InternalPrinter.set_table env.ns
let get_ns_table tenv = tenv.ns
let type_keep_ns env k = { env with keep_ns = k }
let protect_error_ns loc f x =
try f x with
| Ns.UnknownPrefix ns ->
Cduce_error.(raise_err_loc ~loc Generic ("Undefined namespace prefix " ^ U.to_string ns))
let qname env loc t = protect_error_ns loc (Ns.map_tag env.ns) t
let ident env loc t = protect_error_ns loc (Ns.map_attr env.ns) t
let parse_atom env loc t = AtomSet.V.mk (qname env loc t)
let parse_ns env loc ns = protect_error_ns loc (Ns.map_prefix env.ns) ns
let parse_label env loc t =
Label.mk (protect_error_ns loc (Ns.map_attr env.ns) t)
let parse_record env loc f r =
let r = List.map (fun (l, x) -> (parse_label env loc l, f x)) r in
LabelMap.from_list
(fun _ _ -> Cduce_error.raise_err_loc ~loc Generic "Duplicated record field")
r
(*fun _ _ -> assert false*)
let from_comp_unit = ref (fun _ -> assert false)
let load_comp_unit = ref (fun _ -> assert false)
let has_ocaml_unit = ref (fun _ -> false)
let has_static_external = ref (fun _ -> assert false)
let type_schema env loc name uri =
let x = ident env loc name in
let ns, sch = Schema_converter.load_schema (U.to_string name) uri in
let sch = { sch_uri = uri; sch_comps = sch; sch_ns = ns } in
{ env with ids = Env.add x (ESchema sch) env.ids }
let empty_env =
{
ids = Env.empty;
ids_loc = Env.empty;
ns = Ns.def_table;
keep_ns = false;
poly_vars = [];
mono_vars = Var.Set.empty;
weak_vars = Var.Map.empty;
}
let enter_id x i env = { env with ids = Env.add x i env.ids }
let type_using env loc x cu =
try
let cu = !load_comp_unit cu in
enter_id (ident env loc x) (ECDuce cu) env
with
| Not_found -> Cduce_error.raise_err_loc ~loc Typer_Error ("Cannot find external unit " ^ U.to_string cu)
let enter_type id t env = enter_id id (Type t) env
let enter_types l env =
{
env with
ids =
List.fold_left
(fun accu (id, t, al) -> Env.add id (Type (t, al)) accu)
env.ids l;
}
let find_id env0 env loc head x =
let id = ident env0 loc x in
try Env.find id env.ids with
| Not_found when head -> (
try ECDuce (!load_comp_unit x) with
| Not_found -> Cduce_error.(raise_err_loc ~loc Typer_Error ("Cannot resolve this identifier: " ^ U.get_str x)))
let find_id_comp env0 env loc x =
if
(match (U.get_str x).[0] with
| 'A' .. 'Z' -> true
| _ -> false)
&& !has_ocaml_unit x
then EOCaml (U.get_str x)
else find_id env0 env loc true x
let enter_value id t env = { env with ids = Env.add id (Val t) env.ids }
let enter_values l env =
{
env with
ids = List.fold_left (fun accu (id, t) -> Env.add id (Val t) accu) env.ids l;
}
let enter_values_dummy l env =
{
env with
ids =
List.fold_left
(fun accu id -> Env.add id (Val Types.empty) accu)
env.ids l;
}
let value_name_ok id env =
try
match Env.find id env.ids with
| Val _
| EVal _ ->
true
| _ -> false
with
| Not_found -> true
let iter_values env f =
Env.iter
(fun x -> function
| Val t -> f x t
| _ -> ())
env.ids
let register_types cu env =
Env.iter
(fun x t ->
match t with
| Type (t, vparams) ->
let params = List.map Types.var vparams in
Types.Print.register_global cu x ~params t
| _ -> ())
env.ids
let rec const env loc = function
| LocatedExpr (loc, e) -> const env loc e
| Pair (x, y) -> Types.Pair (const env loc x, const env loc y)
| Xml (x, y) -> Types.Xml (const env loc x, const env loc y)
| RecordLitt x -> Types.Record (parse_record env loc (const env loc) x)
| String (i, j, s, c) -> Types.String (i, j, s, const env loc c)
| Atom t -> Types.Atom (parse_atom env loc t)
| Integer i -> Types.Integer i
| Char c -> Types.Char c
| Const c -> c
| _ -> Cduce_error.raise_err_loc ~loc Typer_InvalidConstant ()
(* I. Transform the abstract syntax of types and patterns into
the internal form *)
let find_schema_component sch name =
try ESchemaComponent (Env.find name sch.sch_comps) with
| Not_found ->
Cduce_error.(raise_err Typer_Error
(Printf.sprintf "No component named '%s' found in schema '%s'"
(Ns.QName.to_string name) sch.sch_uri))
let navig loc env0 (env, comp) id =
match comp with
| ECDuce cu ->
let env = !from_comp_unit cu in
let c =
try find_id env0 env loc false id with
| Not_found -> Cduce_error.raise_err_loc ~loc Typer_UnboundId ((Ns.empty, id), false)
in
let c =
match c with
| Val t -> EVal (cu, ident env0 loc id, t)
| c -> c
in
(env, c)
| EOCaml cu -> (
let s = cu ^ "." ^ U.get_str id in
match (U.get_str id).[0] with
| 'A' .. 'Z' -> (env, EOCaml s)
| _ -> (env, EOCamlComponent s))
| ESchema sch -> (env, find_schema_component sch (ident env0 loc id))
| Type _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "Types don't have components")
| Val _
| EVal _ ->
Cduce_error.(raise_err_loc ~loc Typer_Error "Values don't have components")
| ENamespace _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "Namespaces don't have components")
| EOCamlComponent _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "Caml values don't have components")
| ESchemaComponent _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "Schema components don't have components")
(*
| _ -> error loc "Invalid dot access"
*)
let rec find_global env loc ids =
match ids with
| id :: rest ->
let comp = find_id env env loc true id in
snd (List.fold_left (navig loc env) (env, comp) rest)
| _ -> assert false
let eval_ns env loc = function
| `Uri ns -> ns
| `Path ids -> (
match find_global env loc ids with
| ENamespace ns -> ns
| ESchema sch -> sch.sch_ns
| _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "This path does not refer to a namespace or schema"))
let type_ns env loc p ns =
(* TODO: check that p has no prefix *)
let ns = eval_ns env loc ns in
{
env with
ns = Ns.add_prefix p ns env.ns;
ids = Env.add (Ns.empty, p) (ENamespace ns) env.ids;
}
let find_global_type env loc ids =
match find_global env loc ids with
| Type (t, pargs) -> (t, pargs)
| ESchemaComponent (t, _) -> (t, []) (*TODO CHECK*)
| _ -> Cduce_error.raise_err_loc ~loc Typer_Error "This path does not refer to a type"
let find_global_schema_component env loc ids =
match find_global env loc ids with
| ESchemaComponent c -> c
| _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "This path does not refer to a schema component")
let find_local_type env loc id =
match Env.find id env.ids with
| Type t -> t
| _ -> raise Not_found
let find_value id env =
match Env.find id env.ids with
| Val t
| EVal (_, _, t) ->
t
| _ -> raise Not_found
let do_open env cu =
let env_cu = !from_comp_unit cu in
let ids =
Env.fold
(fun n d ids ->
let d =
match d with
| Val t -> EVal (cu, n, t)
| d -> d
in
Env.add n d ids)
env_cu.ids env.ids
in
{ env with ids; ns = Ns.merge_tables env.ns env_cu.ns }
let type_open env loc ids =
match find_global env loc ids with
| ECDuce cu -> do_open env cu
| _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "This path does not refer to a CDuce unit")
module IType = struct
open Typepat
(* From AST to the intermediate representation *)
(* We need to be careful about the order of type definitions in case
of polymorphic types. Mutually recursive polymorphic types
cannot be recursively called with different parameters within
their recursive groups. We build a graph from the type
definitions and use Tarjan's algorithm to find all strongly
connected components in topological order. Then we translate the
AST into intermediate representation in that order. *)
(* [scc defs] takes a list of definitions and returns [ldefs, map] where
- [ldefs] is a list of list of definitions in topological order. Each
internal list is a group of mutually recursive definitions.
- [map] is mapping from type names to their rank and list of parameters.
*)
let scc defs =
let module Info = struct
type t = {
mutable index : int;
mutable lowlink : int;
mutable is_removed : bool;
def : loc * U.t * U.t list * ppat;
}
let empty =
{
index = -1;
lowlink = -1;
is_removed = false;
def = (noloc, U.empty, [], mknoloc (Internal Types.empty));
}
end in
let open Info in
let index = ref 0
and stack = ref [] in
let res = ref []
and map = Hashtbl.create 17
and rank = ref ~-1 in
let g = Hashtbl.create 17 in
List.iter
(fun ((_, v, _, _) as def) -> Hashtbl.add g v { empty with def })
defs;
let rec strong_connect v vinfo =
vinfo.index <- !index;
vinfo.lowlink <- !index;
incr index;
stack := v :: !stack;
vinfo.is_removed <- false;
let _, _, _, vdef = vinfo.def in
pat_iter
(fun p ->
match p.descr with
| PatVar ([ w ], _) -> (
let winfo =
try Some (Hashtbl.find g w) with
| Not_found -> None
in
match winfo with
| Some winfo ->
if winfo.index == -1 then begin
strong_connect w winfo;
vinfo.lowlink <- min vinfo.lowlink winfo.lowlink
end
else if not winfo.is_removed then
vinfo.lowlink <- min vinfo.lowlink winfo.index
| _ -> ())
| _ -> ())
vdef;
if vinfo.lowlink == vinfo.index then begin
let cc = ref [] in
incr rank;
while
let w = List.hd !stack in
stack := List.tl !stack;
let winfo = Hashtbl.find g w in
let _, _, params, _ = winfo.def in
(*TODO remove U.get_str*)
Hashtbl.add map w
(!rank, List.map (fun v -> (v, Var.mk (U.get_str v))) params);
cc := winfo.def :: !cc;
winfo.is_removed <- true;
not (U.equal w v)
do
()
done;
res := (!cc, Hashtbl.copy map) :: !res;
Hashtbl.clear map
end
in
let () =
List.iter
(fun (_, v, _, _) ->
let vinfo = Hashtbl.find g v in
if vinfo.index == -1 then strong_connect v vinfo)
defs
in
List.rev !res
type penv = {
penv_tenv : t;
penv_derec : (node * U.t list) Env.t;
mutable penv_var : (U.t * Var.t) list;
}
let penv tenv = { penv_tenv = tenv; penv_derec = Env.empty; penv_var = [] }
let all_delayed = ref []
let dummy_params = (-1, [], Hashtbl.create 0)
let current_params = ref dummy_params
let to_register = ref []
let clean_params () =
current_params := dummy_params;
to_register := []
let clean_on_err () =
all_delayed := [];
clean_params ()
let delayed loc =
let s = mk_delayed () in
all_delayed := (loc, s) :: !all_delayed;
s
let check_one_delayed (loc, p) =
if not (check_wf p) then Cduce_error.(raise_err_loc ~loc Typer_Error "Ill-formed recursion")
let check_delayed () =
let l = !all_delayed in
all_delayed := [];
List.iter check_one_delayed l
let rec comp_var_pat vl pl =
match (vl, pl) with
| [], [] -> true
| (v, _) :: vll, { descr = Poly p; _ } :: pll when U.equal v p ->
comp_var_pat vll pll
| _ -> false
let rec untuple p =
match p.descr with
| Prod (p1, p2) -> p1 :: untuple p2
| _ -> [ p ]
let rec tuple = function
| [] -> assert false
| [ p ] -> p
| p1 :: rest -> Cduce_loc.mknoloc (Prod (p1, tuple rest))
let match_type_params l args =
let aux l args =
if List.length l == List.length args then Some args else None
in
match (l, args) with
| [ _ ], _ :: _ :: _ -> aux l [ tuple args ]
| _ :: _ :: _, [ p ] -> aux l (untuple p)
| _ -> aux l args
let invalid_instance_error loc s =
Cduce_error.raise_err_loc ~loc Typer_InvalidRecInst (U.to_string s)
let rec clean_regexp r =
match r with
| Epsilon
| Elem _
| Guard _ ->
r
| Alt (r1, r2) -> Alt (clean_regexp r1, clean_regexp r2)
| Star r -> Star (clean_regexp r)
| WeakStar r -> WeakStar (clean_regexp r)
| SeqCapture (loc, id, r) -> SeqCapture (loc, id, clean_regexp r)
| Seq (r1, r2) -> (
match clean_regexp r1 with
| Seq (e, rr1) -> Seq (e, clean_regexp (Seq (rr1, r2)))
| e -> Seq (e, clean_regexp r2))
let rec derecurs env p =
let err s = Cduce_error.(mk_loc p.loc (Typer_Pattern, s)) in
match p.descr with
| Poly v ->
let vv =
try List.assoc v env.penv_var with
| Not_found ->
let vv = Var.mk (U.get_str v) in
env.penv_var <- (v, vv) :: env.penv_var;
vv
in
mk_type (Types.var vv)
| PatVar ids -> derecurs_var env p.loc ids
| Recurs (p, b) ->
let b = List.map (fun (l, n, p) -> (l, n, [], p)) b in
derecurs (fst (derecurs_def env b)) p
| Internal t -> mk_type t
| NsT ns ->
mk_type
(Types.atom (AtomSet.any_in_ns (parse_ns env.penv_tenv p.loc ns)))
| Or (p1, p2) -> mk_or ~err (derecurs env p1) (derecurs env p2)
| And (p1, p2) -> mk_and ~err (derecurs env p1) (derecurs env p2)
| Diff (p1, p2) -> mk_diff ~err (derecurs env p1) (derecurs env p2)
| Prod (p1, p2) -> mk_prod (derecurs env p1) (derecurs env p2)
| XmlT (p1, p2) -> mk_xml (derecurs env p1) (derecurs env p2)
| Arrow (p1, p2) -> mk_arrow (derecurs env p1) (derecurs env p2)
| Optional p -> mk_optional ~err (derecurs env p)
| Record (o, r) ->
let aux = function
| p, Some e -> (derecurs env p, Some (derecurs env e))
| p, None -> (derecurs env p, None)
in
mk_record ~err o (parse_record env.penv_tenv p.loc aux r)
| Constant (x, c) ->
mk_constant (ident env.penv_tenv p.loc x) (const env.penv_tenv p.loc c)
| Cst c -> mk_type (Types.constant (const env.penv_tenv p.loc c))
| Regexp r -> rexp (derecurs_regexp env (clean_regexp r))
| Concat (p1, p2) -> mk_concat ~err (derecurs env p1) (derecurs env p2)
| Merge (p1, p2) -> mk_merge ~err (derecurs env p1) (derecurs env p2)
and derecurs_regexp env = function
| Epsilon -> mk_epsilon
| Elem p -> mk_elem (derecurs env p)
| Guard p -> mk_guard (derecurs env p)
| Seq
( (Elem { descr = PatVar ((id :: rest as ids), []); loc } as p1),
((Elem _ | Seq (Elem _, _)) as p2) ) ->
let arg, make =
match p2 with
| Elem arg -> (arg, fun x -> x)
| Seq (Elem arg, pp2) ->
(arg, fun x -> mk_seq x (derecurs_regexp env pp2))
| _ -> assert false
in
let v = ident env.penv_tenv loc id in
let patch_arg =
try
try snd (Env.find v env.penv_derec) != [] with
| Not_found ->
let _, pargs =
if rest == [] then find_local_type env.penv_tenv loc v
else find_global_type env.penv_tenv loc ids
in
pargs != []
with
| Not_found -> false
in
if patch_arg then
make (mk_elem (derecurs env { descr = PatVar (ids, [ arg ]); loc }))
else mk_seq (derecurs_regexp env p1) (derecurs_regexp env p2)
| Seq (p1, p2) -> mk_seq (derecurs_regexp env p1) (derecurs_regexp env p2)
| Alt (p1, p2) -> mk_alt (derecurs_regexp env p1) (derecurs_regexp env p2)
| Star p -> mk_star (derecurs_regexp env p)
| WeakStar p -> mk_weakstar (derecurs_regexp env p)
| SeqCapture (loc, x, p) ->
mk_seqcapt (ident env.penv_tenv loc x) (derecurs_regexp env p)
and derecurs_var env loc ids =
match ids with
| (id :: rest as ids), args -> (
let cidx, cparams, cmap = !current_params in
let v = ident env.penv_tenv loc id in
try
let node, _ = Env.find v env.penv_derec in
if args == [] || comp_var_pat cparams args then node
else invalid_instance_error loc id
with
| Not_found -> (
try
let (cu, name), (t, pargs), tidx =
if rest == [] then
( ("", v),
find_local_type env.penv_tenv loc v,
try fst (Hashtbl.find cmap id) with
| Not_found -> ~-1 )
else
let t, pargs = find_global_type env.penv_tenv loc ids in
match find_id env.penv_tenv env.penv_tenv loc true id with
| ECDuce _
| EOCaml _ ->
( ( U.get_str id,
ident env.penv_tenv loc
(U.mk @@ String.concat "."
@@ List.map U.get_str rest) ),
(t, pargs),
~-1 )
| _ -> assert false
in
if cidx >= 0 && tidx == cidx && not (comp_var_pat cparams args)
then invalid_instance_error loc id;
let _err s = Error s in
let l =
match match_type_params pargs args with
| Some args ->
List.map2 (fun v p -> (v, typ (derecurs env p))) pargs args
| None ->
Cduce_error.raise_err_loc
~loc Typer_InvalidInstArity
(U.to_string id, List.length pargs, List.length args)
in
let sub = Types.Subst.from_list l in
let ti = mk_type (Types.Subst.apply_full sub t) in
to_register := (cu, name, List.map snd l, ti, loc) :: !to_register;
ti
with
| Not_found ->
assert (rest == []);
if args != [] then
Cduce_error.raise_err_loc ~loc Typer_UnboundId (v, true)
else mk_capture v))
| _ -> assert false
and derecurs_def env b =
let seen = ref IdMap.empty in
let b =
List.map
(fun (loc, v, args, p) ->
let v = ident env.penv_tenv loc v in
try
let old_loc = IdMap.assoc v !seen in
Cduce_error.raise_err_loc ~loc Typer_MultipleTypeDef (v, old_loc)
with Not_found ->
seen := IdMap.add v loc !seen ;
(v, loc, args, p, delayed loc))
b
in
let env =
List.fold_left
(fun env (v, _, a, p, s) ->
{
env with
penv_derec = Env.add v (s, a) env.penv_derec;
penv_var =
List.fold_left
(fun acc v -> (v, Var.mk (U.get_str v)) :: acc)
env.penv_var a;
})
env b
in
List.iter
(fun (v, _, a, p, s) ->
(* Copy. The unknown polymorphic variables that are not already in
penv_var are introduced for the scope of the current type and
discarded afterwards.
*)
let env = { env with penv_var = env.penv_var } in
link s (derecurs env p))
b;
(env, b)
let derec penv p =
let d = derecurs penv p in
elim_concats ();
check_delayed ();
internalize d;
d
(* API *)
let check_no_fv loc n =
match peek_fv n with
| None -> ()
| Some x ->
Cduce_error.raise_err_loc ~loc Typer_CaptureNotAllowed x
let type_defs env b =
let penv, b = derecurs_def (penv env) b in
elim_concats ();
check_delayed ();
let aux loc d =
internalize d;
check_no_fv loc d;
try typ d with
| Cduce_error.Error ((Located loc' | PreciselyLocated (loc', _)) , (Typer_Pattern, s)) ->
Cduce_error.raise_err_loc ~loc:loc' Typer_Error s
| Cduce_error.Error (Unlocated, (Typer_Pattern, s)) -> Cduce_error.raise_err_loc ~loc Generic s
in
let b =
List.map
(fun (v, loc, args, _, d) ->
let t_rhs = aux loc d in
if loc <> noloc && Types.is_empty t_rhs then
Cduce_error.(warning ~loc
("This definition yields an empty type for " ^ Ident.to_string v) ());
let vars_rhs = Types.Subst.vars t_rhs in
let vars_lhs = Var.Set.from_list (List.map snd penv.penv_var) in
let undecl = Var.Set.diff vars_rhs vars_lhs in
if not (Var.Set.is_empty undecl) then
Cduce_error.raise_err_loc ~loc
Typer_UnboundTypeVariable (v, Var.Set.choose undecl);
(* recreate the mapping in the correct order *)
let vars_args = List.map (fun v -> List.assoc v penv.penv_var) args in
let final_vars =
(* create a sequence 'a -> 'a_0 for all variables *)
List.map (fun v -> (v, Var.(mk (name v)))) vars_args
in
let subst =
Types.Subst.from_list
(List.map (fun (v, vv) -> (v, Types.var vv)) final_vars)
in
let t_rhs = Types.Subst.apply_full subst t_rhs in
(v, t_rhs, List.map snd final_vars))
(List.rev b)
in
List.iter
(fun (v, t, al) ->
let params = List.map Types.var al in
Types.Print.register_global "" v ~params t)
b;
let env = enter_types b env in
List.iter
(fun (cu, name, params, ti, loc) ->
let tti = aux loc ti in
Types.Print.register_global cu name ~params tti)
!to_register;
env
let equal_params l1 l2 =
try List.for_all2 U.equal l1 l2 with
| _ -> false
let check_params l =
match l with
| [] -> assert false
| [ (_, u, _, _) ] -> u
| (loc1, u, p, _) :: r -> (
try
let loc2, v, _, _ =
List.find (fun (_, _, q, _) -> not (equal_params p q)) r
in
let loc = merge_loc loc1 loc2 in
Cduce_error.raise_err_loc ~loc
Typer_Error (Printf.sprintf (* TODO create an error *)
"mutually recursive types %s and %s have different arities"
(U.to_string u) (U.to_string v))
with
| Not_found -> u)
let type_defs env b =
try
let b = scc b in
let r =
List.fold_left
(fun env (b, map) ->
let u = check_params b in
let idx, params = Hashtbl.find map u in
current_params := (idx, params, map);
type_defs env b)
env b
in
clean_params ();
r
with
| exn ->
clean_on_err ();
raise exn
let typ vars env t =
let aux loc d =
internalize d;
check_no_fv loc d;
try typ_node d with
| Cduce_error.Error (_, (Typer_Pattern, s)) -> Cduce_error.raise_err_loc ~loc Typer_Error s
in
try
let penv = { (penv env) with penv_var = vars } in
let d = derec penv t in
let res = aux t.loc d in
List.iter
(fun (cu, name, params, ti, loc) ->
let tti = aux loc ti in
Types.Print.register_global cu name ~params (Types.descr tti))
!to_register;
clean_params ();
res
with
| exn ->
clean_on_err ();
raise exn
let pat env t =
try
let d = derec (penv env) t in
try pat_node d with
| Cduce_error.Error (_, (Typer_Pattern, s)) ->
Cduce_error.raise_err_loc ~loc:t.loc Typer_Error s
with
| exn ->
clean_on_err ();
raise exn
end
let typ = IType.typ []
let var_typ = IType.typ
let pat = IType.pat
let type_defs = IType.type_defs
let dump_types ppf env =
Env.iter
(fun v -> function
| Type _ -> Format.fprintf ppf " %a" Ident.print v
| _ -> ())
env.ids
let dump_ns ppf env = Ns.dump_table ppf env.ns
(* II. Build skeleton *)
type type_fun = Cduce_loc.loc -> Types.t -> bool -> Types.t
module Fv = IdSet
type branch = Branch of Typed.branch * branch list
let cur_branch : branch list ref = ref []
let exp' loc e =
{ Typed.exp_loc = loc; Typed.exp_typ = Types.empty; Typed.exp_descr = e }
let exp loc fv e = (fv, exp' loc e)
let exp_nil = exp' noloc (Typed.Cst Types.Sequence.nil_cst)
let pat_true =
let n = Patterns.make Fv.empty in
Patterns.define n (Patterns.constr Builtin_defs.true_type);
n
let pat_false =
let n = Patterns.make Fv.empty in
Patterns.define n (Patterns.constr Builtin_defs.false_type);
n
let ops = Hashtbl.create 13
let register_op op arity f = Hashtbl.add ops op (arity, f)
let typ_op op = snd (Hashtbl.find ops op)
let fun_name env a =
match a.fun_name with
| None -> None
| Some (loc, s) -> Some (ident env loc s)
let is_op env s =
if Env.mem s env.ids then None
else
let ns, s = s in
if Ns.Uri.equal ns Ns.empty then
let s = U.get_str s in
try
let o = Hashtbl.find ops s in
Some (s, fst o)
with
| Not_found -> None
else None
module USet = Set.Make (U)
let collect_vars acc p =
let vset = ref acc in
pat_iter
(function
| { descr = Poly v; _ } -> vset := USet.add v !vset
| _ -> ())
p;
!vset
let rec get_dot_for_annot loc expr =
match expr with
| Dot _ -> expr
| LocatedExpr (_, e) -> get_dot_for_annot loc e
| e -> Cduce_error.raise_err_loc ~loc Typer_Error "Only OCaml external can have type arguments"
let rec expr env loc = function
| LocatedExpr (loc, e) -> expr env loc e
| Forget (e, t) ->
let fv, e = expr env loc e
and t = typ env t in
exp loc fv (Typed.Forget (e, t))
| Check (e, t) ->
let fv, e = expr env loc e
and t = typ env t in
exp loc fv (Typed.Check (ref Types.empty, e, t))
| Var s -> var env loc s
| Apply (e1, e2) -> (
let fv1, e1 = expr env loc e1
and fv2, e2 = expr env loc e2 in
let fv = Fv.cup fv1 fv2 in
match e1.Typed.exp_descr with
| Typed.Op (op, arity, args) when arity > 0 ->
exp loc fv (Typed.Op (op, arity - 1, args @ [ e2 ]))
| _ -> exp loc fv (Typed.Apply (e1, e2)))
| Abstraction a -> abstraction env loc a
| (Integer _ | Char _ | Atom _ | Const _) as c ->
exp loc Fv.empty (Typed.Cst (const env loc c))
| Abstract v -> exp loc Fv.empty (Typed.Abstract v)
| Pair (e1, e2) ->
let fv1, e1 = expr env loc e1
and fv2, e2 = expr env loc e2 in
exp loc (Fv.cup fv1 fv2) (Typed.Pair (e1, e2))
| Xml (e1, e2) ->
let fv1, e1 = expr env loc e1
and fv2, e2 = expr env loc e2 in
let n = if env.keep_ns then Some env.ns else None in
exp loc (Fv.cup fv1 fv2) (Typed.Xml (e1, e2, n))
| Dot _ as e -> dot loc env e []
| TyArgs (e, args) ->
(*let e = get_dot_for_annot loc e in*)
dot loc env e args
| RemoveField (e, l) ->
let fv, e = expr env loc e in
exp loc fv (Typed.RemoveField (e, parse_label env loc l))
| RecordLitt r ->
let fv = ref Fv.empty in
let r =
parse_record env loc
(fun e ->
let fv2, e = expr env loc e in
fv := Fv.cup !fv fv2;
e)
r
in
exp loc !fv (Typed.RecordLitt r)
| String (i, j, s, e) ->
let fv, e = expr env loc e in
exp loc fv (Typed.String (i, j, s, e))
| Match (e, b) ->
let fv1, e = expr env loc e
and fv2, b = branches env b in
exp loc (Fv.cup fv1 fv2) (Typed.Match (e, b))
| Map (e, b) ->
let fv1, e = expr env loc e
and fv2, b = branches env b in
exp loc (Fv.cup fv1 fv2) (Typed.Map (e, b))
| Transform (e, b) ->
let fv1, e = expr env loc e
and fv2, b = branches env b in
exp loc (Fv.cup fv1 fv2) (Typed.Transform (e, b))
| Xtrans (e, b) ->
let fv1, e = expr env loc e
and fv2, b = branches env b in
exp loc (Fv.cup fv1 fv2) (Typed.Xtrans (e, b))
| Validate (e, ids) ->
let fv, e = expr env loc e in
let t, v = find_global_schema_component env loc ids in
exp loc fv (Typed.Validate (e, t, v))
| SelectFW (e, from, where) -> select_from_where env loc e from where
| Try (e, b) ->
let fv1, e = expr env loc e
and fv2, b = branches env b in
exp loc (Fv.cup fv1 fv2) (Typed.Try (e, b))
| NamespaceIn (pr, ns, e) ->
let env = type_ns env loc pr ns in
expr env loc e
| KeepNsIn (k, e) -> expr (type_keep_ns env k) loc e
| Ref (e, t) ->
let fv, e = expr env loc e
and t = var_typ env.poly_vars env t in
exp loc fv (Typed.Ref (e, t))
and if_then_else loc cond yes no =
let b =
{
Typed.br_typ = Types.empty;
Typed.br_branches =
[
{
Typed.br_loc = yes.Typed.exp_loc;
Typed.br_used = false;
Typed.br_ghost = false;
Typed.br_vars_empty = Fv.empty;
Typed.br_pat = pat_true;
Typed.br_body = yes;
};
{
Typed.br_loc = no.Typed.exp_loc;
Typed.br_used = false;
Typed.br_ghost = false;
Typed.br_vars_empty = Fv.empty;
Typed.br_pat = pat_false;
Typed.br_body = no;
};
];
Typed.br_accept = Builtin_defs.bool;
}
in
exp' loc (Typed.Match (cond, b))
and dot loc env0 e args =
let dot_access loc (fv, e) l =
exp loc fv (Typed.Dot (e, parse_label env0 loc l))
in
let no_args () =
if args <> [] then Cduce_error.raise_err_loc ~loc Typer_Error "Only OCaml externals can have type arguments"
in
let rec aux loc = function
| LocatedExpr (loc, e) -> aux loc e
| Dot (e, id) -> (
match aux loc e with
| `Val e -> `Val (dot_access loc e id)
| `Comp c -> `Comp (navig loc env0 c id))
| Var id -> (
match find_id_comp env0 env0 loc id with
| Val _ -> `Val (var env0 loc id)
| c -> `Comp (env0, c))
| e -> `Val (expr env0 loc e)
in
match aux loc e with
| `Val e ->
no_args ();
e
| `Comp (_, EVal (cu, id, t)) ->
no_args ();
exp loc Fv.empty (Typed.ExtVar (cu, id, t))
| `Comp (_, EOCamlComponent s) -> extern loc env0 s args
| _ -> Cduce_error.raise_err_loc ~loc Typer_Error "This dot notation does not refer to a value"
and extern loc env s args =
let args = List.map (typ env) args in
try
let i, t =
let i, t = Externals.resolve s args in
if !has_static_external s then
(`Builtin (s,i), t)
else
(`Ext i, t)
in
exp loc Fv.empty (Typed.External (t, i))
with
| Cduce_error.Error (Unlocated, (err, arg)) -> Cduce_error.raise_err_loc ~loc err arg
| Cduce_error.Error (Located loc, (err, arg)) -> Cduce_error.raise_err_loc ~loc err arg
| Cduce_error.Error (PreciselyLocated (loc, _), (err, arg)) -> Cduce_error.raise_err_loc ~loc err arg
| exn -> Cduce_error.raise_err_loc ~loc Other_Exn exn
and var env loc s =
let id = ident env loc s in
match is_op env id with
| Some (s, arity) ->
let e =
match s with
| "print_xml"
| "print_xml_utf8" ->
Typed.NsTable (env.ns, Typed.Op (s, arity, []))
| "load_xml" when env.keep_ns -> Typed.Op ("!load_xml", arity, [])
| _ -> Typed.Op (s, arity, [])
in
exp loc Fv.empty e
| None -> (
try
match Env.find id env.ids with
| Val _ -> exp loc (Fv.singleton id) (Typed.Var id)
| EVal (cu, id, t) -> exp loc Fv.empty (Typed.ExtVar (cu, id, t))
| _ -> Cduce_error.raise_err_loc ~loc Typer_Error "This identifier does not refer to a value"
with
| Not_found -> Cduce_error.raise_err_loc ~loc Typer_UnboundId (id, false))
and abstraction env loc a =
(* When entering a function (fun 'a 'b ... .('a -> 'b -> … ))
for each variable 'x from the interface
- if 'x is in env.poly_vars, it is bound higher in the AST, we need
to keep the associated unique variable
- if 'x is not in env.poly_vars or 'x is in a.fun_poly, a fresh
name must be generated and kept for subsequent use of the variable.
*)
let vset =
(* collect all type variables from the interface*)
List.fold_left
(fun acc (t1, t2) -> collect_vars (collect_vars acc t1) t2)
USet.empty a.fun_iface
in
let vset =
(* remove variables that are in scope *)
List.fold_left (fun acc (v, _) -> USet.remove v acc) vset env.poly_vars
in
let vset = List.fold_left (fun acc v -> USet.add v acc) vset a.fun_poly in
(* add those that are explicitely polymorphic. *)
let all_vars =
USet.fold (fun v acc -> (v, Var.mk (U.get_str v)) :: acc) vset env.poly_vars
in
let iface =
List.map
(fun (t1, t2) -> (var_typ all_vars env t1, var_typ all_vars env t2))
a.fun_iface
in
let env = { env with poly_vars = all_vars } in
let t =
List.fold_left
(fun accu (t1, t2) -> Types.cap accu (Types.arrow t1 t2))
Types.any iface
in
let iface =
List.map (fun (t1, t2) -> (Types.descr t1, Types.descr t2)) iface
in
let fun_name = fun_name env a in
let env' =
match fun_name with
| None -> env
| Some f -> enter_values_dummy [ f ] env
in
let fv0, body = branches env' a.fun_body in
let fv =
match fun_name with
| None -> fv0
| Some f -> Fv.remove f fv0
in
let e =
Typed.Abstraction
{
Typed.fun_name;
Typed.fun_iface = iface;
Typed.fun_body = body;
Typed.fun_typ = t;
Typed.fun_fv = fv;
Typed.fun_is_poly = not (Var.Set.is_empty (Types.Subst.vars t));
}
in
exp loc fv e
and branches env b =
let fv = ref Fv.empty in
let accept = ref Types.empty in
let branch (p, e) =
let cur_br = !cur_branch in
cur_branch := [];
let ploc = p.loc in
let p = pat env p in
let fvp = Patterns.fv p in
let fv2, e = expr (enter_values_dummy (fvp :> Id.t list) env) noloc e in
let br_loc = merge_loc ploc e.Typed.exp_loc in
(match Fv.pick (Fv.diff fvp fv2) with
| None -> ()
| Some x ->
let x = Ident.to_string x in
warning br_loc
("The capture variable " ^ x
^ " is declared in the pattern but not used in the body of this \
branch. It might be a misspelled or undeclared type or name (if it \
isn't, use _ instead)."));
let fv2 = Fv.diff fv2 fvp in
fv := Fv.cup !fv fv2;
accept := Types.cup !accept (Types.descr (Patterns.accept p));
let ghost = br_loc == noloc in
let br =
{
Typed.br_loc;
Typed.br_used = ghost;
Typed.br_ghost = ghost;
Typed.br_vars_empty = fvp;
Typed.br_pat = p;
Typed.br_body = e;
}
in
cur_branch := Branch (br, !cur_branch) :: cur_br;
br
in
let b = List.map branch b in
( !fv,
{
Typed.br_typ = Types.empty;
Typed.br_branches = b;
Typed.br_accept = !accept;
} )
and select_from_where env loc e from where =
let env = ref env in
let all_fv = ref Fv.empty in
let bound_fv = ref Fv.empty in
let clause (p, e) =
let ploc = p.loc in
let p = pat !env p in
let fvp = Patterns.fv p in
let fv2, e = expr !env noloc e in
env := enter_values_dummy (fvp :> Id.t list) !env;
all_fv := Fv.cup (Fv.diff fv2 !bound_fv) !all_fv;
bound_fv := Fv.cup fvp !bound_fv;
(ploc, p, fvp, e)
in
let from = List.map clause from in
let where = List.map (expr !env noloc) where in
let put_cond rest (fv, cond) =
all_fv := Fv.cup (Fv.diff fv !bound_fv) !all_fv;
if_then_else loc cond rest exp_nil
in
let aux (ploc, p, fvp, e) (where, rest) =
(* Put here the conditions that depends on variables in fvp *)
let above, here = List.partition (fun (v, _) -> Fv.disjoint v fvp) where in
(* if cond then ... else [] *)
let rest = List.fold_left put_cond rest here in
(* transform e with p -> ... *)
let br =
{
Typed.br_loc = ploc;
Typed.br_used = false;
Typed.br_ghost = false;
Typed.br_vars_empty = fvp;
Typed.br_pat = p;
Typed.br_body = rest;
}
in
cur_branch := [ Branch (br, !cur_branch) ];
let b =
{
Typed.br_typ = Types.empty;
Typed.br_branches = [ br ];
Typed.br_accept = Types.descr (Patterns.accept p);
}
in
let br_loc = merge_loc ploc e.Typed.exp_loc in
(above, exp' br_loc (Typed.Transform (e, b)))
in
let cur_br = !cur_branch in
cur_branch := [];
let fv, e = expr !env noloc (Pair (e, cst_nil)) in
cur_branch := !cur_branch @ cur_br;
let where, rest = List.fold_right aux from (where, e) in
(* The remaining conditions are constant. Gives a warning for that. *)
(match where with
| (_, e) :: _ ->
warning e.Typed.exp_loc
"This 'where' condition does not depend on any captured variable"
| _ -> ());
let rest = List.fold_left put_cond rest where in
(Fv.cup !all_fv (Fv.diff fv !bound_fv), rest)
let expr env e = snd (expr env noloc e)
let let_decl env p e =
{ Typed.let_pat = pat env p; Typed.let_body = expr env e }
(* Hide global "typing/parsing" environment *)
(* III. Type-checks *)
open Typed
let any_node = Types.(cons any)
let localize loc f x =
try f x with
| Cduce_error.Error (_, (Typer_Error, msg)) -> Cduce_error.raise_err_loc ~loc Typer_Error msg
| Cduce_error.Error (_,(Typer_Constraint, arg)) -> Cduce_error.raise_err_loc ~loc Typer_Constraint arg
let raise_constraint_exn ?loc ?precise t s =
let open Cduce_error in
let r (type a) (e : a error_t) (a : a) =
match loc, precise with
None, _ -> raise_err e a
| Some loc, Some precise -> raise_err_precise ~loc precise e a
| Some loc, None -> raise_err_loc ~loc e a
in
if
Var.Set.is_empty (Types.Subst.vars t)
&& Var.Set.is_empty (Types.Subst.vars s)
then r Typer_Constraint (t, s)
else r Typer_ShouldHave2 (t, "but its inferred type is:", s)
let require loc t s =
if not (Types.subtype t s) then raise_constraint_exn ~loc t s
let verify loc t s =
require loc t s;
t
let verify_noloc t s =
if not (Types.subtype t s) then raise_constraint_exn t s;
t
let check_str loc ofs t s =
if not (Types.subtype t s) then raise_constraint_exn ~loc ~precise:(`Char ofs) t s;
t
let should_have loc constr s = Cduce_error.raise_err_loc ~loc Typer_ShouldHave (constr, s)
let should_have_str loc ofs constr s =
Cduce_error.raise_err_precise ~loc (`Char ofs) Typer_ShouldHave (constr, s)
let flatten arg loc constr precise =
let open Types in
let constr' =
Sequence.star (Sequence.approx (Types.cap Sequence.any constr))
in
let sconstr' = Sequence.star constr' in
let exact = Types.subtype constr' constr in
if exact then
let t = arg loc sconstr' precise in
if precise then Sequence.flatten t else constr
else
let t = arg loc sconstr' true in
verify loc (Sequence.flatten t) constr
let pat_any () =
let n = Patterns.make IdSet.empty in
Patterns.define n (Patterns.constr Types.any);
n
let pat_var id =
let s = IdSet.singleton id in
let n = Patterns.make s in
Patterns.define n (Patterns.capture id);
n
let pat_node d fv =
let n = Patterns.make fv in
Patterns.define n d;
n
let pat_pair kind e1 e2 fv =
pat_node
((match kind with
| `Normal -> Patterns.times
| `Xml -> Patterns.xml)
e1 e2)
fv
let pat_cap p1 p2 =
let fv1 = Patterns.fv p1 in
let fv2 = Patterns.fv p2 in
pat_node Patterns.(cap (descr p1) (descr p2)) (IdSet.cup fv1 fv2)
let rec pat_of_expr fv te =
match te.exp_descr with
| Var s when not (IdSet.mem fv s) -> pat_var s
| Pair (e1, e2)
| Xml (e1, e2, _) ->
let kind =
match te.exp_descr with
| Pair _ -> `Normal
| _ -> `Xml
in
let p1 = pat_of_expr fv e1
and p2 = pat_of_expr fv e2 in
let fv1 = Patterns.fv p1 in
let fv2 = Patterns.fv p2 in
pat_pair kind p1 p2 (IdSet.cup fv1 fv2)
| RecordLitt lmap ->
List.fold_left
(fun acc (lab, tel) ->
let pel = pat_of_expr fv tel in
let prec = pat_node (Patterns.record lab pel) (Patterns.fv pel) in
pat_cap prec acc)
(pat_any ()) (LabelMap.get lmap)
| _ -> pat_any ()
let refine_pat p1 op2 =
match op2 with
| None -> p1
| Some te ->
let fv = Patterns.fv p1 in
let p2 = pat_of_expr fv te in
pat_cap p1 p2
let rec type_check env e constr precise =
let d = type_check' e.exp_loc env e.exp_descr constr precise in
let d = if precise then d else constr in
e.exp_typ <- Types.cup e.exp_typ d;
d
and type_check' loc env e constr precise =
match e with
| Forget (e, t) ->
let t = Types.descr t in
ignore (type_check env e t false);
verify loc t constr
| Check (t0, e, t) ->
if Var.Set.is_empty Types.(Subst.vars (descr t)) then (
let te = type_check env e Types.any true in
t0 := Types.cup !t0 te;
verify loc (Types.cap te (Types.descr t)) constr)
else
Cduce_error.raise_err_loc
~loc Typer_Error "Polymorphic type variables cannot occur in dynamic type-checks"
| Abstraction a ->
let t =
if Types.subtype a.fun_typ constr then a.fun_typ
else
let name =
match a.fun_name with
| Some s -> "abstraction " ^ Ident.to_string s
| None -> "the abstraction"
in
should_have loc constr
(Format.asprintf
"but the interface (%a) of %s is not compatible with type\n\
\ (%a)" Types.Print.print a.fun_typ name
Types.Print.print constr)
in
let env =
{
env with
mono_vars = Var.Set.cup env.mono_vars (Types.Subst.vars a.fun_typ);
}
in
let env =
match a.fun_name with
| None -> env
| Some f -> enter_value f a.fun_typ env
in
List.iter
(fun (t1, t2) ->
let acc = a.fun_body.br_accept in
if not (Types.subtype t1 acc) then
Cduce_error.(raise_err_loc ~loc Typer_NonExhaustive (Types.diff t1 acc));
ignore (type_check_branches loc env t1 a.fun_body t2 false))
a.fun_iface;
t
| Match (e, b) ->
let t = type_check env e b.br_accept true in
type_check_branches loc env t b constr precise
| Try (e, b) ->
let te = type_check env e constr precise in
let tb = type_check_branches loc env Types.any b constr precise in
Types.cup te tb
| Pair (e1, e2) -> type_check_pair loc env e1 e2 constr precise
| Xml (e1, e2, _) -> type_check_pair ~kind:`XML loc env e1 e2 constr precise
| RecordLitt r -> type_record loc env r constr precise
| Map (e, b) -> type_map loc env false e b constr precise
| Transform (e, b) ->
localize loc (flatten (fun l -> type_map loc env true e b) loc constr) precise
| Apply (e1, e2) ->
let t1 = type_check env e1 Types.Function.any true in
let t1arrow = Types.Arrow.get t1 in
let dom = Types.Arrow.domain t1arrow in
let t2 = type_check env e2 Types.any true in
let res =
if
Var.Set.is_empty (Types.Subst.vars t1)
&& Var.Set.is_empty (Types.Subst.vars t2)
then
(* TODO don't retype e2 *)
let t2 = type_check env e2 dom true in
Types.Arrow.apply t1arrow t2
else
match Types.Tallying.apply_raw env.mono_vars t1 t2 with
| None -> raise_constraint_exn ~loc t2 dom
| Some (subst, tx, ty, res) ->
List.iter
(fun s ->
Var.Map.iteri
(fun wv t ->
match Var.kind wv with
| `generated
| `user ->
()
| `weak ->
let tt = Types.Subst.clean_type env.mono_vars t in
env.weak_vars <-
Var.Map.update
(fun prev next ->
match (prev, next) with
| None, Some t -> next
| Some tp, Some tn when Types.equiv tp tn -> next
| Some tp, Some tn ->
Cduce_error.(raise_err_loc ~loc
Typer_Error (Format.asprintf
"the weak polymorphic variable %a is \
instantiated several times in the \
same expression."
Var.print wv))
| _ -> assert false)
wv (Some tt) env.weak_vars)
s)
subst;
res
(*
if Types.Arrow.need_arg t1 then
let t2 = type_check env e2 dom true in
Types.Arrow.apply t1 t2
else (
ignore (type_check env e2 dom false);
Types.Arrow.apply_noarg t1) *)
in
verify loc res constr
| Var s -> verify loc (find_value s env) constr
| ExtVar (cu, s, t) -> verify loc t constr
| Cst c -> verify loc (Types.constant c) constr
| Abstract (t, _) -> verify loc Types.(abstract (AbstractSet.atom t)) constr
| String (i, j, s, e) -> type_check_string loc env 0 s i j e constr precise
| Dot (e, l) -> (
let expect_rec = Types.record l (Types.cons constr) in
let expect_elt =
Types.xml any_node
(Types.cons (Types.times (Types.cons expect_rec) any_node))
in
let t = type_check env e (Types.cup expect_rec expect_elt) precise in
let t_elt =
let t = Types.Product.pi2 (Types.Product.get ~kind:`XML t) in
let t = Types.Product.pi1 (Types.Product.get t) in
t
in
if not precise then constr
else
try Types.Record.project (Types.cup t t_elt) l with
| Not_found -> assert false)
| RemoveField (e, l) ->
let t = type_check env e Types.Rec.any true in
let t = Types.Record.remove_field t l in
verify loc t constr
| Xtrans (e, b) ->
let t = type_check env e Types.Sequence.any true in
let t =
try
Types.Sequence.map_tree constr
(fun cstr t ->
let resid = Types.diff t b.br_accept in
let res = type_check_branches loc env t b cstr true in
(res, resid))
t
with
| Types.Sequence.Error _ as exn -> (
let rec find_loc = function
| Cduce_error.Error (PreciselyLocated (loc, precise), _) -> ((loc, precise), exn)
| Types.Sequence.(Error (Types.Sequence.UnderTag (t, exn))) ->
let l, exn = find_loc exn in
(l, Types.Sequence.Error (Types.Sequence.UnderTag (t, exn)))
| exn -> raise Not_found
in
try
let (loc, precise), exn = find_loc exn in
Cduce_error.raise_err_precise ~loc precise Other_Exn exn
with
| Not_found -> Cduce_error.raise_err_loc ~loc Other_Exn exn)
in
verify loc t constr
| Validate (e, t, _) ->
ignore (type_check env e Types.any false);
verify loc t constr
| Ref (e, t) ->
ignore (type_check env e (Types.descr t) false);
verify loc (Builtin_defs.ref_type t) constr
| External (t, _) -> verify loc t constr
| Op (op, _, args) ->
let args : type_fun list = List.map (fun e (_:Cduce_loc.loc) -> type_check env e ) args in
let t = localize loc (typ_op op args loc constr) precise in
verify loc t constr
| NsTable (ns, e) -> type_check' loc env e constr precise
and type_check_pair ?(kind = `Normal) loc env e1 e2 constr precise =
let rects = Types.Product.normal ~kind constr in
(if Types.Product.is_empty rects then
match kind with
| `Normal -> should_have loc constr "but it is a pair"
| `XML -> should_have loc constr "but it is an XML element");
let need_s = Types.Product.need_second rects in
let t1 = type_check env e1 (Types.Product.pi1 rects) (precise || need_s) in
let c2 = Types.Product.constraint_on_2 rects t1 in
if Types.is_empty c2 then
Cduce_error.raise_err_loc ~loc
Typer_ShouldHave2 (constr, "but the first component has type:", t1);
let t2 = type_check env e2 c2 precise in
if precise then
match kind with
| `Normal -> Types.times (Types.cons t1) (Types.cons t2)
| `XML -> Types.xml (Types.cons t1) (Types.cons t2)
else constr
and type_check_string loc env ofs s i j e constr precise =
if U.equal_index i j then type_check env e constr precise
else
let rects = Types.Product.normal constr in
if Types.Product.is_empty rects then
should_have_str loc ofs constr "but it is a string"
else
let ch, i' = U.next s i in
let ch = CharSet.V.mk_int ch in
let tch = Types.constant (Types.Char ch) in
let t1 = check_str loc ofs tch (Types.Product.pi1 rects) in
let c2 = Types.Product.constraint_on_2 rects t1 in
let t2 = type_check_string loc env (ofs + 1) s i' j e c2 precise in
if precise then Types.times (Types.cons t1) (Types.cons t2) else constr
and type_record loc env r constr precise =
if not (Types.Record.has_record constr) then
should_have loc constr "but it is a record";
let rconstr, res =
List.fold_left
(fun (rconstr, res) (l, e) ->
let r = Types.Record.focus rconstr l in
let pi = Types.Record.get_this r in
(if Types.is_empty pi then
let l = Label.string_of_attr l in
should_have loc constr
(Printf.sprintf "Field %s is not allowed here." l));
let t = type_check env e pi (precise || Types.Record.need_others r) in
let rconstr = Types.Record.constraint_on_others r t in
(if Types.is_empty rconstr then
let l = Label.string_of_attr l in
should_have loc constr
(Printf.sprintf "Type of field %s is not precise enough." l));
let res = if precise then LabelMap.add l (Types.cons t) res else res in
(rconstr, res))
(constr, LabelMap.empty) (LabelMap.get r)
in
if not (Types.Record.has_empty_record rconstr) then
should_have loc constr "More fields should be present";
if precise then Types.record_fields (false, res) else constr
and type_check_branches ?expr loc env targ brs constr precise =
if Types.is_empty targ then Types.empty
else (
brs.br_typ <- Types.cup brs.br_typ targ;
branches_aux expr loc env targ
(if precise then Types.empty else constr)
constr precise brs.br_branches)
and branches_aux expr loc env targ tres constr precise = function
| [] -> tres
| b :: rem ->
let p = refine_pat b.br_pat expr in
let acc = Types.descr (Patterns.accept p) in
let targ' = Types.cap targ acc in
if Types.is_empty targ' then
branches_aux expr loc env targ tres constr precise rem
else (
b.br_used <- true;
let res = Patterns.filter targ' p in
let res = IdMap.map Types.descr res in
b.br_vars_empty <-
IdMap.domain
(IdMap.filter
(fun x t -> Types.(subtype t Sequence.nil_type))
(IdMap.restrict res b.br_vars_empty));
let env' = enter_values (IdMap.get res) env in
let t = type_check env' b.br_body constr precise in
let tres = if precise then Types.cup t tres else tres in
let targ'' = Types.diff targ acc in
if Types.non_empty targ'' then
branches_aux expr loc env targ'' tres constr precise rem
else tres)
and type_map loc env def e b constr precise =
let open Types in
let acc = if def then Sequence.any else Sequence.star b.br_accept in
let t = type_check env e acc true in
let constr' = Sequence.approx (Types.cap Sequence.any constr) in
let exact = Types.subtype (Sequence.star constr') constr in
(* Note:
- could be more precise by integrating the decomposition
of constr inside Sequence.map.
*)
let res =
Sequence.map
(fun t ->
let res =
type_check_branches loc env t b constr' (precise || not exact)
in
if def && not (Types.subtype t b.br_accept) then (
require loc Sequence.nil_type constr';
Types.cup res Sequence.nil_type)
else res)
t
in
if exact then res else verify loc res constr
and type_let_decl env l =
let acc = Types.descr (Patterns.accept l.let_pat) in
let t = type_check env l.let_body acc true in
let res = Patterns.filter t l.let_pat in
IdMap.mapi_to_list (fun x t -> (x, Types.descr t)) res
and type_rec_funs env l =
let typs =
List.fold_left
(fun accu -> function
| {
exp_descr = Abstraction { fun_typ = t; fun_name = Some f };
exp_loc = loc;
} ->
if not (value_name_ok f env) then
Cduce_error.raise_err_loc ~loc
Typer_Error "This function name clashes with another kind of identifier";
(f, t) :: accu
| _ -> assert false)
[] l
in
let env = enter_values typs env in
List.iter (fun e -> ignore (type_check env e Types.any false)) l;
typs
let rec unused_branches b =
List.iter
(fun (Branch (br, s)) ->
if br.br_ghost then ()
else if not br.br_used then warning br.br_loc "This branch is not used"
else (
(if not (IdSet.is_empty br.br_vars_empty) then
let msg =
try
let l =
List.map
(fun x ->
let x = Ident.to_string x in
if String.compare x "$$$" = 0 then raise Exit else x)
(br.br_vars_empty :> Id.t list)
in
let l = String.concat "," l in
"The following variables always match the empty sequence: " ^ l
with
| Exit -> "This projection always returns the empty sequence"
in
warning br.br_loc msg);
unused_branches s))
b
let report_unused_branches () =
unused_branches !cur_branch;
cur_branch := []
let clear_unused_branches () = cur_branch := []
(* API *)
let update_weak_variables env =
let to_patch, to_keep =
Var.Map.split
(fun v -> function
| Some _ -> true
| None -> false)
env.weak_vars
in
let to_patch =
Var.Map.map
(function
| Some t -> t
| None -> assert false)
to_patch
in
{
env with
ids =
Env.mapi
(fun v -> function
| Val t ->
let tt = Types.Subst.apply_full to_patch t in
Val tt
| x -> x)
env.ids;
weak_vars = to_keep;
}
let type_expr env e =
clear_unused_branches ();
let e = expr env e in
let t = type_check env e Types.any true in
report_unused_branches ();
(update_weak_variables env, e, t)
let type_let_decl env p e =
clear_unused_branches ();
let decl = let_decl env p e in
let typs = type_let_decl env decl in
report_unused_branches ();
let is_value = Typed.is_value decl.let_body in
(* patch env to update weak variables *)
let env = update_weak_variables env in
let typs =
List.map
(fun (id, t) ->
let tt = Types.Subst.clean_type Var.Set.empty t in
let vars = Types.Subst.vars tt in
if (not (Var.Set.is_empty vars)) && not is_value then
let weak_vars, all_weak_vars, _ =
Var.Set.fold
(fun (acc, accw, i) v ->
let wv = Var.mk ~kind:`weak ("_weak" ^ string_of_int i) in
((v, Types.var wv) :: acc, Var.Map.add wv None accw, i + 1))
([], env.weak_vars, Var.Map.length env.weak_vars)
vars
in
let () = env.weak_vars <- all_weak_vars in
let subst = Types.Subst.from_list weak_vars in
(id, Types.Subst.apply_full subst tt)
else
(* raise_loc_generic p.loc
(Format.asprintf
"The type of identifier %a is %a.@\n\
It contains polymorphic variables that cannot be generalized."
Ident.print id Types.Print.print tt);*)
(id, tt))
typs
in
let env = enter_values typs env in
( {
env with
ids_loc =
List.fold_left
(fun acc (id, _) -> Env.add id p.loc acc)
env.ids_loc typs;
mono_vars = Var.Set.empty;
poly_vars = [];
},
decl,
typs )
let type_let_funs env funs =
clear_unused_branches ();
let rec id = function
| Ast.LocatedExpr (_, e) -> id e
| Ast.Abstraction a -> fun_name env a
| _ -> assert false
in
let ids =
List.fold_left
(fun accu f ->
match id f with
| Some x -> x :: accu
| None -> accu)
[] funs
in
let env' = enter_values_dummy ids env in
let funs = List.map (expr env') funs in
let typs = type_rec_funs env funs in
report_unused_branches ();
let env = update_weak_variables env in
let env = enter_values typs env in
let env = { env with mono_vars = Var.Set.empty; poly_vars = [] } in
(env, funs, typs)
(*
let find_cu x env =
match find_cu noloc x env with
| ECDuce cu -> cu
| _ -> raise (Error ("Cannot find external unit " ^ (U.to_string x)))
*)
let check_weak_variables env =
Env.iter
(fun id -> function
| Val t ->
let vrs = Types.Subst.vars t in
Var.Set.iter
(fun v ->
match Var.kind v with
| `generated
| `user ->
()
| `weak ->
let loc = Env.find id env.ids_loc in
Cduce_error.(raise_err_loc ~loc Typer_WeakVar (id, t)))
vrs
| _ -> ())
env.ids