## CDuce Source Code Reference Below is the full content of relevant CDuce source files for context. ### `types/bdd.ml` ```ocaml 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` ```ocaml 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 "" 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` ```ocaml 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` ```ocaml 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` ```ocaml 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 ```