elipl/cduce_code.md
Kacper Marzecki 748f87636a checkpoint
checkpoint

failing test

after fixing tests

checkpoint

checkpoint

checkpoint

re-work

asd

checkpoint

checkpoint

checkpoint

mix proj

checkpoint mix

first parser impl

checkpoint

fix tests

re-org parser

checkpoint strings

fix multiline strings

tuples

checkpoint maps

checkpoint

checkpoint

checkpoint

checkpoint

fix weird eof expression parse error

checkpoint before typing

checkpoint

checpoint

checkpoint

checkpoint

checkpoint ids in primitive types

checkpoint

checkpoint

fix tests

initial annotation

checkpoint

checkpoint

checkpoint

union subtyping

conventions

refactor - split typer

typing tuples

checkpoint test refactor

checkpoint test refactor

parsing atoms

checkpoint atoms

wip lists

checkpoint typing lists

checkopint

checkpoint

wip fixing

correct list typing

map discussion

checkpoint map basic typing

fix tests checkpoint

checkpoint

checkpoint

checkpoint

fix condition typing

fix literal keys in map types

checkpoint union types

checkpoint union type

checkpoint row types discussion & bidirectional typecheck

checkpoint

basic lambdas

checkpoint lambdas typing application

wip function application

checkpoint

checkpoint

checkpoint cduce

checkpoint

checkpoint

checkpoint

checkpoint

checkpoint

checkpoint

checkpoint
2025-06-13 23:48:07 +02:00

192 KiB
Raw Permalink Blame History

CDuce Source Code Reference

Below is the full content of relevant CDuce source files for context.

types/bdd.ml

type 'a line = 'a list * 'a list
type 'a dnf = 'a line list

type ('elem, 'leaf) bdd =
  | False
  | True
  | Leaf of 'leaf
  | Split of
      int * 'elem * ('elem, 'leaf) bdd * ('elem, 'leaf) bdd * ('elem, 'leaf) bdd

let empty = False

type 'atom var_bdd = (Var.t, ('atom, bool) bdd) bdd

(* Ternary decision diagrams:
   False → 𝟘
   True → 𝟙
   Leaf (v) → ⟦ v ⟧
   Split (h, x, p, i, n) →
        (⟦ x ⟧ ∩ ⟦ p ⟧)  ⟦ i ⟧  (⟦ n ⟧ \ ⟦ x ⟧)

   Invariants maintained (see simplify split and leaf smart constructors and simplify functions:)
   - p ≠ n
   - ∀ i st. t ≡ Split (h, x, p, Split (…, Split (_, _, i, _) …), n), p ≠ i
     Indeed, given the equation above, if some bdd is in ⟦ i ⟧, it can be removed,
     from ⟦ p ⟧ and ⟦ n ⟧ safely. A corollary is that if ⟦ i ⟧ contains True, both p and
      n are useless, and therefore the whole bdd is replaced by true.

   - if t ≡ Leaf (v), ⟦ v ⟧ ≠ 𝟘 and ⟦ v ⟧ ≠ 𝟙
    (if v is recursively a bdd of atoms (while t is a bdd of variables) then
     it means that v is not trivially False or True, but it could be a complex
     bdd that is in fine equal to True, but we would need subtyping to decide this).
*)

module MK
    (E : Custom.T) (Leaf : sig
      include Tset.S

      val iter : (elem -> unit) -> t -> unit
    end) =
struct
  type elem = E.t
  type t = (E.t, Leaf.t) bdd

  let test x =
    match x with
    | Leaf l -> Leaf.test l
    | _ -> Tset.Unknown

  let rec equal a b =
    a == b
    ||
    match (a, b) with
    | Split (h1, x1, p1, i1, n1), Split (h2, x2, p2, i2, n2) ->
        h1 == h2 && E.equal x1 x2 && equal p1 p2 && equal i1 i2 && equal n1 n2
    | Leaf l1, Leaf l2 -> Leaf.equal l1 l2
    | _ -> false

  let rec compare a b =
    if a == b then 0
    else
      match (a, b) with
      | Split (h1, x1, p1, i1, n1), Split (h2, x2, p2, i2, n2) ->
          if h1 < h2 then -1
          else if h1 > h2 then 1
          else
            let c = E.compare x1 x2 in
            if c <> 0 then c
            else
              let c = compare p1 p2 in
              if c <> 0 then c
              else
                let c = compare i1 i2 in
                if c <> 0 then c else compare n1 n2
      | Leaf l1, Leaf l2 -> Leaf.compare l1 l2
      | True, _ -> -1
      | _, True -> 1
      | False, _ -> -1
      | _, False -> 1
      | Leaf _, Split _ -> 1
      | Split _, Leaf _ -> -1

  let hash = function
    | False -> 0
    | True -> 1
    | Leaf l -> 17 * Leaf.hash l
    | Split (h, _, _, _, _) -> h

  let compute_hash x p i n =
    let h = E.hash x in
    let h = h lxor (257 * hash p) in
    let h = h lxor (8191 * hash i) in
    let h = h lxor (16637 * hash n) in
    h

  let rec check = function
    | True
    | False ->
        ()
    | Leaf l -> Leaf.check l
    | Split (h, x, p, i, n) ->
        assert (h = compute_hash x p i n);
        (match p with
        | Split (_, y, _, _, _) -> assert (E.compare x y < 0)
        | _ -> ());
        (match i with
        | Split (_, y, _, _, _) -> assert (E.compare x y < 0)
        | _ -> ());
        (match n with
        | Split (_, y, _, _, _) -> assert (E.compare x y < 0)
        | _ -> ());
        E.check x;
        check p;
        check i;
        check n

  let any = True
  let empty = empty

  let leaf l =
    match Leaf.test l with
    | Tset.Empty -> empty
    | Tset.Full -> any
    | _ -> Leaf l

  let rec iter_partial fe fleaf = function
    | Split (_, x, p, i, n) ->
        fe x;
        iter_partial fe fleaf p;
        iter_partial fe fleaf i;
        iter_partial fe fleaf n
    | Leaf leaf -> fleaf leaf
    | _ -> ()

  let iter_full fe fl = iter_partial fe (Leaf.iter fl)

  let rec dump s ppf = function
    | False -> Format.fprintf ppf "F"
    | True -> Format.fprintf ppf "T"
    | Leaf l -> Format.fprintf ppf "%s%a" s Leaf.dump l
    | Split (_, x, p, i, n) ->
        Format.fprintf ppf "@[%s%a@[(@[%a@],@,@[%a@],@,@[%a@])@]@]" s E.dump x
          (dump "+") p (dump "=") i (dump "-") n

  let dump ppf a = (dump "") ppf a

  let rec get accu pos neg = function
    | False -> accu
    | True -> ((pos, neg), Leaf.any) :: accu
    | Leaf leaf -> ((pos, neg), leaf) :: accu
    | Split (_, x, p, i, n) ->
        (*OPT: can avoid creating this list cell when pos or neg =False *)
        let accu = get accu (x :: pos) neg p in
        let accu = get accu pos (x :: neg) n in
        let accu = get accu pos neg i in
        accu

  let get (l : t) = get [] [] [] l

  let compute_full ~empty ~any ~cup ~cap ~diff ~atom ~leaf b =
    let rec aux t =
      match t with
      | False -> empty
      | True -> any
      | Leaf l -> leaf l
      | Split (_, x, p, i, n) ->
          let atx = atom x in
          let p = cap atx (aux p)
          and i = aux i
          and n = diff (aux n) atx in
          cup (cup p i) n
    in
    aux b

  let split0 x pos ign neg = Split (compute_hash x pos ign neg, x, pos, ign, neg)
  let atom x = split0 x any empty empty

  (* Smart constructor for split, ensures that the envariants are preserved
     with the use of simplify *)
  let rec split x p i n =
    if i == True then any
    else if equal p n then if equal p i then p else p ++ i
    else
      let p = simplify p [ i ]
      and n = simplify n [ i ] in
      if equal p n then if equal p i then p else p ++ i else split0 x p i n

  (* ensures that a does not contain any of the bdds in l,
     if it does, replace it does, remove it. *)
  and simplify a l =
    match a with
    | False -> empty
    | Split (_, x, p, i, n) -> restrict_from_list a x p i n [] [] [] False l
    | _ -> restrict_true_or_same a l

  and restrict_true_or_same a = function
    | [] -> a
    | True :: _ -> empty
    | b :: l -> if equal a b then empty else restrict_true_or_same a l

  (* traverses a list of bdds b and
     accumulate in ap ai an those that might be present in each
     component p, i, n of a (a   ≡ Split (h, x, p, i, n)) the arguments
     are passed to avoid reconstructing it.
     The _dummy argument keeps the arguments of both functions
     in the same order which prevent ocaml from spilling too much on the stack.
  *)
  and restrict_from_list a x p i n ap ai an _dummy = function
    | [] ->
        let p = simplify p ap
        and n = simplify n an
        and i = simplify i ai in
        if equal p n then p ++ i else split0 x p i n
    | b :: l -> restrict_elem a x p i n ap ai an b l

  and restrict_elem a x p i n ap ai an b l =
    match b with
    | False -> restrict_from_list a x p i n ap ai an b l
    | True -> empty
    | Leaf _ as b ->
        (* inline then next case, knowing that a = Split ...
           and b is Leaf b is greater*)
        restrict_from_list a x p i n (b :: ap) (b :: ai) (b :: an) b l
    | Split (_, x2, p2, i2, n2) as b ->
        let c = E.compare x2 x in
        if c < 0 then restrict_elem a x p i n ap ai an i2 l
        else if c > 0 then
          restrict_from_list a x p i n (b :: ap) (b :: ai) (b :: an) b l
        else if equal a b then empty
        else
          restrict_from_list a x p i n (p2 :: i2 :: ap) (i2 :: ai)
            (n2 :: i2 :: an) b l

  and ( ++ ) a b =
    if a == b then a
    else
      match (a, b) with
      | True, _
      | _, True ->
          any
      | False, _ -> b
      | _, False -> a
      | Leaf l1, Leaf l2 -> leaf (Leaf.cup l1 l2)
      | Split (_, x1, p1, i1, n1), Split (_, x2, p2, i2, n2) ->
          let c = E.compare x1 x2 in
          if c = 0 then split x1 (p1 ++ p2) (i1 ++ i2) (n1 ++ n2)
          else if c < 0 then split x1 p1 (i1 ++ b) n1
          else split x2 p2 (i2 ++ a) n2
      | Split (_, x1, p1, i1, n1), Leaf _ -> split x1 p1 (i1 ++ b) n1
      | Leaf _, Split (_, x2, p2, i2, n2) -> split x2 p2 (i2 ++ a) n2

  let rec ( ** ) a b =
    if a == b then a
    else
      match (a, b) with
      | False, _
      | _, False ->
          empty
      | True, _ -> b
      | _, True -> a
      | Leaf l1, Leaf l2 -> leaf (Leaf.cap l1 l2)
      | Split (_, x1, p1, i1, n1), Split (_, x2, p2, i2, n2) ->
          let c = E.compare x1 x2 in
          if c = 0 then
            split x1
              ((p1 ** (p2 ++ i2)) ++ (p2 ** i1))
              (i1 ** i2)
              ((n1 ** (n2 ++ i2)) ++ (n2 ** i1))
          else if c < 0 then split x1 (p1 ** b) (i1 ** b) (n1 ** b)
          else split x2 (p2 ** a) (i2 ** a) (n2 ** a)
      | Split (_, x1, p1, i1, n1), Leaf _ ->
          split x1 (p1 ** b) (i1 ** b) (n1 ** b)
      | Leaf _, Split (_, x2, p2, i2, n2) ->
          split x2 (p2 ** a) (i2 ** a) (n2 ** a)

  let rec neg = function
    | False -> any
    | True -> empty
    | Leaf l -> leaf (Leaf.neg l)
    | Split (_, x, p, i, False) -> split x empty (neg (i ++ p)) (neg i)
    | Split (_, x, False, i, n) -> split x (neg i) (neg (i ++ n)) empty
    | Split (_, x, p, False, n) -> split x (neg p) (neg (p ++ n)) (neg n)
    | Split (_, x, p, i, n) -> neg i ** split x (neg p) (neg (p ++ n)) (neg n)

  let rec ( // ) a b =
    if a == b then empty
    else
      match (a, b) with
      | False, _
      | _, True ->
          empty
      | _, False -> a
      | True, _ -> neg b
      | Leaf l1, Leaf l2 -> leaf (Leaf.diff l1 l2)
      | Split (_, x1, p1, i1, n1), Split (_, x2, p2, i2, n2) ->
          let c = E.compare x1 x2 in
          if c = 0 then
            if i2 == empty && n2 == empty then
              split x1 (p1 // p2) (i1 // p2) (n1 ++ i1)
            else
              split x1
                ((p1 ++ i1) // (p2 ++ i2))
                empty
                ((n1 ++ i1) // (n2 ++ i2))
          else if c < 0 then split x1 (p1 // b) (i1 // b) (n1 // b)
          else split x2 (a // (i2 ++ p2)) empty (a // (i2 ++ n2))
      | _ -> a ** neg b

  let ( ~~ ) = neg
  let cup = ( ++ )
  let cap = ( ** )
  let diff = ( // )

  let extract_var = function
    | Split
        ( _,
          v,
          ((False | True | Leaf _) as p),
          ((False | True | Leaf _) as i),
          ((False | True | Leaf _) as n) ) ->
        Some (v, p, i, n)
    | _ -> None
end

module Bool : sig
  include Tset.S with type t = bool and type elem = bool

  val iter : (elem -> unit) -> t -> unit
end = struct
  include Custom.Bool

  type elem = bool

  let empty = false
  let any = true
  let test (x : bool) : Tset.cardinal = Obj.magic x
  let atom b = b
  let cup a b = a || b
  let cap a b = a && b
  let neg a = not a
  let diff a b = a && not b
  let iter _ _ = ()
end

module type S = sig
  type atom
  (** The type of atoms in the Boolean combinations *)

  type mono
  (** The type of Boolean combinations of atoms. *)

  include Custom.T with type t = (Var.t, mono) bdd

  type line
  (** An explicit representation of conjunctions of atoms. *)

  type dnf
  (** An explicit representation fo the DNF. *)

  val atom : atom -> t
  val mono : mono -> t
  val mono_dnf : mono -> dnf
  val any : t
  val empty : t
  val cup : t -> t -> t
  val cap : t -> t -> t
  val diff : t -> t -> t
  val neg : t -> t
  val get : t -> dnf
  val get_mono : t -> mono
  val iter : (atom -> unit) -> t -> unit

  val compute :
    empty:'b ->
    any:'b ->
    cup:('b -> 'b -> 'b) ->
    cap:('b -> 'b -> 'b) ->
    diff:('b -> 'b -> 'b) ->
    atom:(atom -> 'b) ->
    t ->
    'b

  val var : Var.t -> t
  val extract_var : t -> (Var.t * t * t * t) option

  (** {2 Polymorphic interface. }*)

  val get_partial : t -> ((Var.t list * Var.t list) * mono) list
  val get_full : t -> ((Var.t list * Var.t list) * line) list
  val iter_partial : (Var.t -> unit) -> (mono -> unit) -> t -> unit
  val iter_full : (Var.t -> unit) -> (atom -> unit) -> t -> unit

  val compute_partial :
    empty:'b ->
    any:'b ->
    cup:('b -> 'b -> 'b) ->
    cap:('b -> 'b -> 'b) ->
    diff:('b -> 'b -> 'b) ->
    mono:(mono -> 'b) ->
    var:(Var.t -> 'b) ->
    t ->
    'b

  val compute_full :
    empty:'b ->
    any:'b ->
    cup:('b -> 'b -> 'b) ->
    cap:('b -> 'b -> 'b) ->
    diff:('b -> 'b -> 'b) ->
    atom:(atom -> 'b) ->
    var:(Var.t -> 'b) ->
    t ->
    'b

  val ( ++ ) : t -> t -> t
  val ( ** ) : t -> t -> t
  val ( // ) : t -> t -> t
  val ( ~~ ) : t -> t
end

module Make (E : Custom.T) :
  S
    with type atom = E.t
     and type line = E.t list * E.t list
     and type dnf = (E.t list * E.t list) list
     and type mono = (E.t, Bool.t) bdd = struct
  module Atom = MK (E) (Bool)

  type atom = E.t
  type mono = (E.t, Bool.t) bdd

  include
    MK
      (Var)
      (struct
        include Atom

        let iter f b = iter_full f ignore b
      end)

  type line = E.t list * E.t list
  type dnf = (E.t list * E.t list) list

  let var x = atom x
  let atom x = leaf (Atom.atom x)
  let mono x = leaf x

  let get_aux combine acc b =
    List.fold_left
      (fun acc ((p, n), dnf) ->
        let p = List.rev p in
        let n = List.rev n in
        let dnf = Atom.get dnf in
        List.fold_left
          (fun acc ((p2, n2), b) -> if b then combine p n p2 n2 acc else acc)
          acc dnf)
      acc (get b)

  let get_full = get_aux (fun p n p2 n2 acc -> ((p, n), (p2, n2)) :: acc) []

  let mono_dnf mono =
    let l = Atom.get mono in
    List.map fst l

  let get_partial b : ((Var.t list * Var.t list) * mono) list =
    List.rev_map (fun ((p, n), m) -> ((List.rev p, List.rev n), m)) (get b)

  let get b = get_aux (fun _ _ p2 n2 acc -> (p2, n2) :: acc) [] b

  let get_mono b : mono =
    List.fold_left
      (fun acc (_, dnf) -> Atom.cup acc dnf)
      Atom.empty (get_partial b)

  (* Temporary List.rev to order elements as before introduction of
     polymorphic variables.
  *)
  let iter = iter_full (fun _ -> ())

  let compute_partial ~empty ~any ~cup ~cap ~diff ~mono ~(var : Var.t -> 'a) b =
    compute_full ~empty ~any ~cup ~cap ~diff ~atom:var ~leaf:mono b

  let compute_full ~empty ~any ~cup ~cap ~diff ~atom ~(var : Var.t -> 'a) b =
    compute_full ~empty ~any ~cup ~cap ~diff ~atom:var
      ~leaf:
        (Atom.compute_full ~empty ~any ~cup ~cap ~diff ~atom ~leaf:(function
          | false -> empty
          | true -> any))
      b

  let compute ~empty ~any ~cup ~cap ~diff ~atom b =
    compute_full ~empty ~any ~cup ~cap ~diff ~atom ~var:(fun _ -> any) b
end

module MKBasic (T : Tset.S) :
  S with type atom = T.elem and type mono = T.t and type dnf = T.t = struct
  type atom = T.elem
  type mono = T.t

  include
    MK
      (Var)
      (struct
        include T

        let iter _ _ = assert false
      end)

  type line
  type dnf = T.t

  let var v = atom v
  let mono x = leaf x
  let atom x = leaf (T.atom x)

  let get_partial t =
    List.map (fun ((p, n), m) -> ((List.rev p, List.rev n), m)) (get t)

  let mono_dnf x = x

  let get_mono t =
    List.fold_left (fun acc ((_, _), dnf) -> T.cup acc dnf) T.empty (get t)

  let get _ = assert false
  let get_full _ = assert false

  let compute_partial ~empty ~any ~cup ~cap ~diff ~mono ~var b =
    compute_full ~empty ~any ~cup ~cap ~diff ~atom:var ~leaf:mono b

  let[@ocaml.warning "-27"] compute_full
      ~empty
      ~any
      ~cup
      ~cap
      ~diff
      ~atom
      ~var
      b =
    assert false

  let[@ocaml.warning "-27"] compute ~empty ~any ~cup ~cap ~diff ~atom b =
    assert false

  let iter _ _ = assert false
  let iter_full _ _ _ = assert false
end

module VarIntervals = MKBasic (Intervals)
module VarCharSet = MKBasic (CharSet)
module VarAtomSet = MKBasic (AtomSet)
module VarAbstractSet = MKBasic (AbstractSet)

types/types.ml

let std_compare = compare

open Ident
module U = Encodings.Utf8

let count = ref 0

let () =
  Stats.register Stats.Summary (fun ppf ->
      Format.fprintf ppf "Allocated type nodes:%i@\n" !count)

(*
To be sure not to use generic comparison ...
*)
let ( = ) : int -> int -> bool = ( == )
let ( < ) : int -> int -> bool = ( < )
let[@ocaml.warning "-32"] ( <= ) : int -> int -> bool = ( <= )
let[@ocaml.warning "-32"] ( <> ) : int -> int -> bool = ( <> )
let[@ocaml.warning "-32"] compare = 1

type const =
  | Integer of Intervals.V.t
  | Atom of AtomSet.V.t
  | Char of CharSet.V.t
  | Pair of const * const
  | Xml of const * const
  | Record of const label_map
  | String of U.uindex * U.uindex * U.t * const

module Const = struct
  type t = const

  let check _ = ()
  let dump ppf _ = Format.fprintf ppf "<Types.Const.t>"

  let rec compare c1 c2 =
    match (c1, c2) with
    | Integer x, Integer y -> Intervals.V.compare x y
    | Integer _, _ -> -1
    | _, Integer _ -> 1
    | Atom x, Atom y -> AtomSet.V.compare x y
    | Atom _, _ -> -1
    | _, Atom _ -> 1
    | Char x, Char y -> CharSet.V.compare x y
    | Char _, _ -> -1
    | _, Char _ -> 1
    | Pair (x1, x2), Pair (y1, y2) ->
      let c = compare x1 y1 in
      if c <> 0 then c else compare x2 y2
    | Pair (_, _), _ -> -1
    | _, Pair (_, _) -> 1
    | Xml (x1, x2), Xml (y1, y2) ->
      let c = compare x1 y1 in
      if c <> 0 then c else compare x2 y2
    | Xml (_, _), _ -> -1
    | _, Xml (_, _) -> 1
    | Record x, Record y -> LabelMap.compare compare x y
    | Record _, _ -> -1
    | _, Record _ -> 1
    | String (i1, j1, s1, r1), String (i2, j2, s2, r2) ->
      let c = std_compare i1 i2 in
      if c <> 0 then c
      else
        let c = std_compare j1 j2 in
        if c <> 0 then c
        else
          let c = U.compare s1 s2 in
          if c <> 0 then c
          (* Should compare
             only the substring *)
          else compare r1 r2

  let rec hash = function
    | Integer x -> 1 + (17 * Intervals.V.hash x)
    | Atom x -> 2 + (17 * AtomSet.V.hash x)
    | Char x -> 3 + (17 * CharSet.V.hash x)
    | Pair (x, y) -> 4 + (17 * hash x) + (257 * hash y)
    | Xml (x, y) -> 5 + (17 * hash x) + (257 * hash y)
    | Record x -> 6 + (17 * LabelMap.hash hash x)
    | String (_, _, s, r) -> 7 + (17 * U.hash s) + (257 * hash r)

  (* Note: improve hash for String *)

  let equal c1 c2 = compare c1 c2 = 0
end

type pair_kind =
  [ `Normal
  | `XML
  ]

type descr = {
  atoms : Bdd.VarAtomSet.t;
  ints : Bdd.VarIntervals.t;
  chars : Bdd.VarCharSet.t;
  times : (node * node) Bdd.var_bdd;
  xml : (node * node) Bdd.var_bdd;
  arrow : (node * node) Bdd.var_bdd;
  record : (bool * node label_map) Bdd.var_bdd;
  abstract : Bdd.VarAbstractSet.t;
  absent : bool;
  mutable hash : int;
}

and node = {
  id : int;
  cu : Compunit.t;
  mutable descr : descr;
}

let empty =
  {
    atoms = Bdd.empty;
    ints = Bdd.empty;
    chars = Bdd.empty;
    times = Bdd.empty;
    xml = Bdd.empty;
    arrow = Bdd.empty;
    record = Bdd.empty;
    abstract = Bdd.empty;
    absent = false;
    hash = -1;
  }

let todo_dump = Hashtbl.create 16
let forward_print = ref (fun _ _ -> assert false)

module Node = struct
  type t = node

  let check _ = ()

  let dump ppf n =
    if not (Hashtbl.mem todo_dump n.id) then
      Hashtbl.add todo_dump n.id (n, false);
    Format.fprintf ppf "X%i" n.id

  let hash x = x.id + Compunit.hash x.cu

  let compare x y =
    let c = x.id - y.id in
    if c = 0 then Compunit.compare x.cu y.cu else c

  let equal x y = x == y || (x.id == y.id && Compunit.equal x.cu y.cu)
  let mk id d = { id; cu = Compunit.current (); descr = d }
end

module BoolPair = Bdd.Make (Custom.Pair (Node) (Node))
module BoolRec = Bdd.Make (Custom.Pair (Custom.Bool) (LabelSet.MakeMap (Node)))

module Descr = struct
  type t = descr

  let _print_lst ppf =
    List.iter (fun f ->
        f ppf;
        Format.fprintf ppf " |")

  let hash a =
    if a.hash >= 0 then a.hash
    else
      let accu = Bdd.VarCharSet.hash a.chars in
      let accu = accu + (accu lsl 4) + Bdd.VarIntervals.hash a.ints in
      let accu = accu + (accu lsl 4) + Bdd.VarAtomSet.hash a.atoms in
      let accu = accu + (accu lsl 4) + BoolPair.hash a.times in
      let accu = accu + (accu lsl 4) + BoolPair.hash a.xml in
      let accu = accu + (accu lsl 4) + BoolPair.hash a.arrow in
      let accu = accu + (accu lsl 4) + BoolRec.hash a.record in
      let accu = accu + (accu lsl 4) + Bdd.VarAbstractSet.hash a.abstract in
      let accu = if a.absent then accu + 5 else accu in
      let accu = max_int land accu in
      let () = a.hash <- accu in
      accu

  let equal a b =
    a == b
    || hash a == hash b
       && Bdd.VarAtomSet.equal a.atoms b.atoms
       && Bdd.VarCharSet.equal a.chars b.chars
       && Bdd.VarIntervals.equal a.ints b.ints
       && BoolPair.equal a.times b.times
       && BoolPair.equal a.xml b.xml
       && BoolPair.equal a.arrow b.arrow
       && BoolRec.equal a.record b.record
       && Bdd.VarAbstractSet.equal a.abstract b.abstract
       && a.absent == b.absent

  let compare a b =
    if a == b then 0
    else
      let c = Bdd.VarAtomSet.compare a.atoms b.atoms in
      if c <> 0 then c
      else
        let c = Bdd.VarCharSet.compare a.chars b.chars in
        if c <> 0 then c
        else
          let c = Bdd.VarIntervals.compare a.ints b.ints in
          if c <> 0 then c
          else
            let c = BoolPair.compare a.times b.times in
            if c <> 0 then c
            else
              let c = BoolPair.compare a.xml b.xml in
              if c <> 0 then c
              else
                let c = BoolPair.compare a.arrow b.arrow in
                if c <> 0 then c
                else
                  let c = BoolRec.compare a.record b.record in
                  if c <> 0 then c
                  else
                    let c = Bdd.VarAbstractSet.compare a.abstract b.abstract in
                    if c <> 0 then c else Bdd.Bool.compare a.absent b.absent

  let check a =
    Bdd.VarCharSet.check a.chars;
    Bdd.VarIntervals.check a.ints;
    Bdd.VarAtomSet.check a.atoms;
    BoolPair.check a.times;
    BoolPair.check a.xml;
    BoolPair.check a.arrow;
    BoolRec.check a.record;
    Bdd.VarAbstractSet.check a.abstract;
    ()

  let forward_any = ref empty

  let dump_descr ppf d =
    if equal d empty then Format.fprintf ppf "EMPTY"
    else if equal d !forward_any then Format.fprintf ppf "ANY"
    else
      Format.fprintf ppf
        "<@[types = @[%a@]@\n\
        \  ints(@[%a@])@\n\
        \  atoms(@[%a@])@\n\
        \  chars(@[%a@])@\n\
        \  abstract(@[%a@])@\n\
        \  times(@[%a@])@\n\
        \  xml(@[%a@])@\n\
        \  record(@[%a@])@\n\
        \  arrow(@[%a@])@\n\
        \  absent=%b@]>" !forward_print d Bdd.VarIntervals.dump d.ints
        Bdd.VarAtomSet.dump d.atoms Bdd.VarCharSet.dump d.chars
        Bdd.VarAbstractSet.dump d.abstract BoolPair.dump d.times BoolPair.dump
        d.xml BoolRec.dump d.record BoolPair.dump d.arrow d.absent

  let dump ppf d =
    Hashtbl.clear todo_dump;
    dump_descr ppf d;
    let continue = ref true in
    let seen_list = ref [] in
    while !continue do
      continue := false;
      Hashtbl.iter
        (fun _ (n, seen) ->
           if not seen then begin
             seen_list := n :: !seen_list;
             continue := true
           end)
        todo_dump;
      List.iter
        (fun n ->
           Hashtbl.replace todo_dump n.id (n, true);
           Format.fprintf ppf "@\n%a=@[" Node.dump n;
           dump_descr ppf n.descr;
           Format.fprintf ppf "@]")
        !seen_list;
      seen_list := []
    done;
    Hashtbl.clear todo_dump
end

module DescrHash = Hashtbl.Make (Descr)
module DescrMap = Map.Make (Descr)
module DescrSet = Set.Make (Descr)
module DescrSList = SortedList.Make (Descr)

let dummy_descr =
  let () = incr count in
  let loop = Node.mk !count empty in
  let dummy =
    { empty with times = BoolPair.atom (loop, loop); absent = true; hash = -1 }
  in
  let () = loop.descr <- dummy in
  dummy

let make () =
  incr count;
  let res = Node.mk !count dummy_descr in
  res

let is_opened n = n.descr == dummy_descr

let define n d =
  assert (n.descr == dummy_descr);
  n.descr <- d

let cons d =
  incr count;
  Node.mk !count d

let any =
  {
    times = BoolPair.any;
    xml = BoolPair.any;
    arrow = BoolPair.any;
    record = BoolRec.any;
    ints = Bdd.VarIntervals.any;
    atoms = Bdd.VarAtomSet.any;
    chars = Bdd.VarCharSet.any;
    abstract = Bdd.VarAbstractSet.any;
    absent = false;
    hash = -1;
  }

let () = Descr.forward_any := any

module type Kind = sig
  module Dnf : Bdd.S

  val any : Descr.t
  val get : Descr.t -> Dnf.mono
  val get_vars : Descr.t -> Dnf.t
  val mk : Dnf.t -> Descr.t
  val update : Descr.t -> Dnf.t -> Descr.t
end

module Int = struct
  module Dnf = Bdd.VarIntervals

  let get_vars d = d.ints
  let get d = Dnf.get_mono d.ints
  let update t c = { t with ints = c; hash = -1 }
  let mk c = update empty c
  let any = mk (get_vars any)
end

module Abstract = struct
  module Dnf = Bdd.VarAbstractSet

  let get_vars d = d.abstract
  let get d = Dnf.get_mono d.abstract
  let update t c = { t with abstract = c; hash = -1 }
  let mk c = update empty c
  let any = mk (get_vars any)
end

module Atom = struct
  module Dnf = Bdd.VarAtomSet

  let get_vars d = d.atoms
  let get d = Dnf.get_mono d.atoms
  let update t c = { t with atoms = c; hash = -1 }
  let mk c = update empty c
  let any = mk (get_vars any)
end

module Char = struct
  module Dnf = Bdd.VarCharSet

  let get_vars d = d.chars
  let get d = Dnf.get_mono d.chars
  let update t c = { t with chars = c; hash = -1 }
  let mk c = update empty c
  let any = mk (get_vars any)
end

module Times = struct
  module Dnf = BoolPair

  let get d = Dnf.get_mono d.times
  let get_vars d = d.times
  let update t c = { t with times = c; hash = -1 }
  let mk c = update empty c
  let any = mk (get_vars any)
end

module Xml = struct
  module Dnf = BoolPair

  let get_vars d = d.xml
  let get d = Dnf.get_mono (get_vars d)
  let update t c = { t with xml = c; hash = -1 }
  let mk c = update empty c
  let any = mk (get_vars any)
end

module Function = struct
  module Dnf = BoolPair

  let get_vars d = d.arrow
  let get d = Dnf.get_mono (get_vars d)
  let update t c = { t with arrow = c; hash = -1 }
  let mk c = update empty c
  let any = mk (get_vars any)
end

module Rec = struct
  module Dnf = BoolRec

  let get_vars d = d.record
  let get d = Dnf.get_mono (get_vars d)
  let update t c = { t with record = c; hash = -1 }
  let mk c = update empty c
  let any = mk (get_vars any)
end

module Absent = struct
  module Dnf = Bdd.Bool

  let get d = d.absent
  let update t c = { t with absent = c; hash = -1 }
  let mk c = update empty c
  let any = mk true
end

include Descr

let non_constructed =
  {
    any with
    times = empty.times;
    xml = empty.xml;
    record = empty.record;
    hash = -1;
  }

let interval i = Int.mk (Int.Dnf.mono i)
let times x y = Times.mk (Times.Dnf.atom (x, y))
let xml x y = Xml.mk (Xml.Dnf.atom (x, y))
let arrow x y = Function.mk (Function.Dnf.atom (x, y))
let record label t = Rec.mk (Rec.Dnf.atom (true, LabelMap.singleton label t))
let record_fields (x : bool * node Ident.label_map) = Rec.mk (Rec.Dnf.atom x)
let atom a = Atom.mk (Atom.Dnf.mono a)
let char c = Char.mk (Char.Dnf.mono c)
let abstract a = Abstract.mk (Abstract.Dnf.mono a)
let get_abstract = Abstract.get

let var x =
  {
    times = BoolPair.var x;
    xml = BoolPair.var x;
    arrow = BoolPair.var x;
    record = BoolRec.var x;
    ints = Bdd.VarIntervals.var x;
    atoms = Bdd.VarAtomSet.var x;
    chars = Bdd.VarCharSet.var x;
    abstract = Bdd.VarAbstractSet.var x;
    absent = false;
    hash = -1;
  }

let cup x y =
  if x == y then x
  else
    {
      times = Times.Dnf.cup x.times y.times;
      xml = Xml.Dnf.cup x.xml y.xml;
      arrow = Function.Dnf.cup x.arrow y.arrow;
      record = Rec.Dnf.cup x.record y.record;
      ints = Int.Dnf.cup x.ints y.ints;
      atoms = Atom.Dnf.cup x.atoms y.atoms;
      chars = Char.Dnf.cup x.chars y.chars;
      abstract = Abstract.Dnf.cup x.abstract y.abstract;
      absent = Absent.Dnf.cup x.absent y.absent;
      hash = -1;
    }

let cap x y =
  if x == y then x
  else
    {
      times = Times.Dnf.cap x.times y.times;
      xml = Xml.Dnf.cap x.xml y.xml;
      arrow = Function.Dnf.cap x.arrow y.arrow;
      record = Rec.Dnf.cap x.record y.record;
      ints = Int.Dnf.cap x.ints y.ints;
      atoms = Atom.Dnf.cap x.atoms y.atoms;
      chars = Char.Dnf.cap x.chars y.chars;
      abstract = Abstract.Dnf.cap x.abstract y.abstract;
      absent = Absent.Dnf.cap x.absent y.absent;
      hash = -1;
    }

let diff x y =
  if x == y then empty
  else
    {
      times = Times.Dnf.diff x.times y.times;
      xml = Xml.Dnf.diff x.xml y.xml;
      arrow = Function.Dnf.diff x.arrow y.arrow;
      record = Rec.Dnf.diff x.record y.record;
      ints = Int.Dnf.diff x.ints y.ints;
      atoms = Atom.Dnf.diff x.atoms y.atoms;
      chars = Char.Dnf.diff x.chars y.chars;
      abstract = Abstract.Dnf.diff x.abstract y.abstract;
      absent = Absent.Dnf.diff x.absent y.absent;
      hash = -1;
    }

let descr n =
  assert (n.descr != dummy_descr);
  n.descr

let internalize n = n
let id n = n.id

let rec constant = function
  | Integer i -> interval (Intervals.atom i)
  | Atom a -> atom (AtomSet.atom a)
  | Char c -> char (CharSet.atom c)
  | Pair (x, y) -> times (const_node x) (const_node y)
  | Xml (x, y) -> xml (const_node x) (const_node y)
  | Record x -> record_fields (false, LabelMap.map const_node x)
  | String (i, j, s, c) ->
    if U.equal_index i j then constant c
    else
      let ch, i' = U.next s i in
      constant (Pair (Char (CharSet.V.mk_int ch), String (i', j, s, c)))

and const_node c = cons (constant c)

let neg x = diff any x

module LabelS = Set.Make (Label)

let any_or_absent = { any with absent = true; hash = -1 }
let only_absent = { empty with absent = true; hash = -1 }

let get_single_record r =
  let labs accu (_, r) =
    List.fold_left (fun accu (l, _) -> LabelS.add l accu) accu (LabelMap.get r)
  in
  let extend descrs labs (o, r) =
    let rec aux i labs r =
      match labs with
      | [] -> ()
      | l1 :: labs -> (
          match r with
          | (l2, x) :: r when l1 == l2 ->
            descrs.(i) <- cap descrs.(i) (descr x);
            aux (i + 1) labs r
          | r ->
            if not o then descrs.(i) <- cap descrs.(i) only_absent;
            (* TODO:OPT *)
            aux (i + 1) labs r)
    in
    aux 0 labs (LabelMap.get r);
    o
  in
  let line (p, n) =
    let labels = List.fold_left labs (List.fold_left labs LabelS.empty p) n in
    let labels = LabelS.elements labels in
    let nlab = List.length labels in
    let mk () = Array.make nlab any_or_absent in

    let pos = mk () in
    let opos =
      List.fold_left (fun accu x -> extend pos labels x && accu) true p
    in
    let p = (opos, pos) in

    let n =
      List.map
        (fun x ->
           let neg = mk () in
           let o = extend neg labels x in
           (o, neg))
        n
    in
    (labels, p, n)
  in
  line r

let[@ocaml.warning "-32"] get_record r = List.map get_single_record (BoolRec.get r)
let get_record_full r =
  List.map (fun (vars, r) ->
      vars, get_single_record r)
    Rec.(r |> get_vars |> Dnf.get_full)


(* Subtyping algorithm *)

let diff_t d t = diff d (descr t)
let cap_t d t = cap d (descr t)

let cap_product any_left any_right l =
  List.fold_left
    (fun (d1, d2) (t1, t2) -> (cap_t d1 t1, cap_t d2 t2))
    (any_left, any_right) l

let any_pair = { empty with times = any.times; hash = -1 }
let rec exists max f = max > 0 && (f (max - 1) || exists (max - 1) f)

exception NotEmpty

module Witness = struct
  module NodeSet = Set.Make (Node)

  type witness =
    | WInt of Intervals.V.t
    | WAtom of AtomSet.sample
    | WChar of CharSet.V.t
    | WAbsent
    | WAbstract of AbstractSet.elem option
    | WPair of witness * witness * witness_slot
    | WXml of witness * witness * witness_slot
    | WRecord of witness label_map * bool * witness_slot
    (* Invariant: WAbsent cannot actually appear *)
    | WFun of (witness * witness option) list * witness_slot
    (* Poly *)
    | WPoly of Var.Set.t * Var.Set.t * witness

  and witness_slot = {
    mutable wnodes_in : NodeSet.t;
    mutable wnodes_out : NodeSet.t;
    mutable wuid : int;
  }

  module WHash = Hashtbl.Make (struct
      type t = witness

      let rec hash_small = function
        | WInt i -> 17 * Intervals.V.hash i
        | WChar c -> 1 + (17 * CharSet.V.hash c)
        | WAtom None -> 2
        | WAtom (Some (ns, None)) -> 3 + (17 * Ns.Uri.hash ns)
        | WAtom (Some (_, Some t)) -> 4 + (17 * Ns.Label.hash t)
        | WAbsent -> 5
        | WAbstract None -> 6
        | WAbstract (Some t) -> 7 + (17 * AbstractSet.T.hash t)
        | WPair (_, _, s)
        | WXml (_, _, s)
        | WRecord (_, _, s)
        | WFun (_, s) ->
          8 + (17 * s.wuid)
        | WPoly (pos, neg, w) ->
          9 + Var.Set.hash pos + 17 * Var.Set.hash neg + 257 * hash_small w

      let hash = function
        | WPair (p1, p2, _) -> (257 * hash_small p1) + (65537 * hash_small p2)
        | WXml (p1, p2, _) -> 1 + (257 * hash_small p1) + (65537 * hash_small p2)
        | WRecord (r, o, _) ->
          (if o then 2 else 3) + (257 * LabelMap.hash hash_small r)
        | WFun (f, _) ->
          4
          + 257
            * Hashtbl.hash
              (List.map
                 (function
                   | x, None -> 17 * hash_small x
                   | x, Some y ->
                     1 + (17 * hash_small x) + (257 * hash_small y))
                 f)
        | _ -> assert false

      let rec equal_small w1 w2 =
        match (w1, w2) with
        | WInt i1, WInt i2 -> Intervals.V.equal i1 i2
        | WChar c1, WChar c2 -> CharSet.V.equal c1 c2
        | WAtom None, WAtom None -> true
        | WAtom (Some (ns1, None)), WAtom (Some (ns2, None)) ->
          Ns.Uri.equal ns1 ns2
        | WAtom (Some (_, Some t1)), WAtom (Some (_, Some t2)) ->
          Ns.Label.equal t1 t2
        | WAbsent, WAbsent -> true
        | WAbstract None, WAbstract None -> false
        | WAbstract (Some t1), WAbstract (Some t2) -> AbstractSet.T.equal t1 t2
        | WPoly (p1, n1, w1), WPoly (p2, n2, w2) ->
          Var.Set.equal p1 p2 &&
          Var.Set.equal n1 n2 &&
          equal_small w1 w2
        | _ -> w1 == w2

      let equal w1 w2 =
        match (w1, w2) with
        | WPair (p1, q1, _), WPair (p2, q2, _)
        | WXml (p1, q1, _), WXml (p2, q2, _) ->
          equal_small p1 p2 && equal_small q1 q2
        | WRecord (r1, o1, _), WRecord (r2, o2, _) ->
          o1 == o2 && LabelMap.equal equal_small r1 r2
        | WFun (f1, _), WFun (f2, _) ->
          List.length f1 = List.length f2
          && List.for_all2
            (fun (x1, y1) (x2, y2) ->
               equal_small x1 x2
               &&
               match (y1, y2) with
               | Some y1, Some y2 -> equal_small y1 y2
               | None, None -> true
               | _ -> false)
            f1 f2
        | _ -> false
    end)

  let wmemo = WHash.create 1024
  let wuid = ref 0

  let wslot () =
    { wuid = !wuid; wnodes_in = NodeSet.empty; wnodes_out = NodeSet.empty }

  let () =
    Stats.register Stats.Summary (fun ppf ->
        Format.fprintf ppf "Allocated witnesses:%i@\n" !wuid)

  let rec print_witness ppf = function
    | WInt i -> Format.fprintf ppf "%a" Intervals.V.print i
    | WChar c -> Format.fprintf ppf "%a" CharSet.V.print c
    | WAtom None -> Format.fprintf ppf "`#:#"
    | WAtom (Some (ns, None)) ->
      Format.fprintf ppf "`%a" Ns.InternalPrinter.print_any_ns ns
    | WAtom (Some (_, Some t)) -> Format.fprintf ppf "`%a" Ns.Label.print_attr t
    | WPair (w1, w2, _) ->
      Format.fprintf ppf "(%a,%a)" print_witness w1 print_witness w2
    | WXml (w1, w2, _) ->
      Format.fprintf ppf "XML(%a,%a)" print_witness w1 print_witness w2
    | WRecord (ws, o, _) ->
      Format.fprintf ppf "{";
      LabelMap.iteri
        (fun l w ->
           Format.fprintf ppf " %a=%a" Label.print_attr l print_witness w)
        ws;
      if o then Format.fprintf ppf " ..";
      Format.fprintf ppf " }"
    | WFun (f, _) ->
      Format.fprintf ppf "FUN{";
      List.iter
        (fun (x, y) ->
           Format.fprintf ppf " %a->" print_witness x;
           match y with
           | None -> Format.fprintf ppf "#"
           | Some y -> print_witness ppf y)
        f;
      Format.fprintf ppf " }"
    | WAbstract None -> Format.fprintf ppf "Abstract(..)"
    | WAbstract (Some s) -> Format.fprintf ppf "Abstract(%s)" s
    | WAbsent -> Format.fprintf ppf "Absent"
    | WPoly (pos, neg, w) ->
      Format.fprintf ppf "Poly(%a, %a, %a)"
        Var.Set.print pos
        Var.Set.print neg
        print_witness w
  let wmk w =
    (* incr wuid; w *)
    (* hash-consing disabled *)
    try WHash.find wmemo w with
    | Not_found ->
      incr wuid;
      WHash.add wmemo w w;
      (* Format.fprintf Format.std_formatter "W:%a@."
         print_witness w; *)
      w

  let wpair p1 p2 = wmk (WPair (p1, p2, wslot ()))
  let wxml p1 p2 = wmk (WXml (p1, p2, wslot ()))
  let wrecord r o = wmk (WRecord (r, o, wslot ()))
  let wfun f = wmk (WFun (f, wslot ()))

  (* A witness with variables wpos wneg
     belongs to type with variables tpos tneg
     if :
       - wpos and tneg are disjoint (a witness 'a&0 cannot belong
        to a type T\'a)
       - likewise for wneg and tpos
       - wpos is a superset of tpos, that is 'a&'b&'c&42 is
         belongs to the type 'a&Int
       - wneg is a super set of tneg
  *)
  let subset_vars wpos wneg tpos tneg =
    let sneg = Var.Set.from_list tneg in
    Var.Set.disjoint wpos sneg &&
    Var.Set.subset sneg wneg &&
    let spos = Var.Set.from_list tpos in
    Var.Set.disjoint wneg spos &&
    Var.Set.subset spos wpos

  let basic_dnf (type mono) (module M : Kind with type Dnf.mono = mono)
      pos neg t f =
    M.get_vars t
    |> M.Dnf.get_partial
    |> List.exists (fun ((vp, vn), mono) ->
        subset_vars pos neg vp vn &&
        f mono)

  let full_dnf (type atom) (module M : Kind
                             with type Dnf.atom = atom
                              and type Dnf.line = atom list * atom list)
      pos neg t f =
    M.get_vars t
    |> M.Dnf.get_full
    |> List.exists (fun ((vp, vn), (ap, an)) ->
        subset_vars pos neg vp vn &&
        List.for_all f ap &&
        not (List.exists f an)
      )

  let rec node_has n = function
    | (WXml (_, _, s) | WPair (_, _, s) | WFun (_, s) | WRecord (_, _, s)) as w
      ->
      if NodeSet.mem n s.wnodes_in then true
      else if NodeSet.mem n s.wnodes_out then false
      else
        let r = type_has (descr n) w in
        if r then s.wnodes_in <- NodeSet.add n s.wnodes_in
        else s.wnodes_out <- NodeSet.add n s.wnodes_out;
        r
    | w -> type_has (descr n) w
  and type_has t w =
    let pos, neg, w =
      match w with
        WPoly (pos, neg, w) -> pos, neg, w
      | _ -> Var.Set.empty, Var.Set.empty, w
    in
    match w with
      WPoly _ -> assert false
    | WInt i ->
      basic_dnf (module Int) pos neg t (Intervals.contains i)
    | WChar c ->
      basic_dnf (module Char) pos neg t (CharSet.contains c)
    | WAtom a ->
      basic_dnf (module Atom) pos neg t (AtomSet.contains_sample a)
    | WAbsent -> t.absent
    | WAbstract a ->
      basic_dnf (module Abstract) pos neg t (AbstractSet.contains_sample a)
    | WPair (w1, w2, _) ->
      full_dnf (module Times) pos neg t
        (fun (n1, n2) -> node_has n1 w1 && node_has n2 w2)
    | WXml (w1, w2, _) ->
      full_dnf (module Xml) pos neg t
        (fun (n1, n2) -> node_has n1 w1 && node_has n2 w2)
    | WFun (f, _) ->
      full_dnf (module Function) pos neg t
        (fun (n1, n2) ->
           List.for_all
             (fun (x, y) ->
                (not (node_has n1 x))
                ||
                match y with
                | None -> false
                | Some y -> node_has n2 y)
             f)
    | WRecord (f, o, _) ->
      full_dnf (module Rec) pos neg t
        (fun (o', f') ->
           ((not o) || o')
           &&
           let checked = ref 0 in
           try
             LabelMap.iteri
               (fun l n ->
                  let w =
                    try
                      let w = LabelMap.assoc l f in
                      incr checked;
                      w
                    with
                    | Not_found -> WAbsent
                  in
                  if not (node_has n w) then raise Exit)
               f';
             o' || LabelMap.length f == !checked
           (* All the remaining fields cannot be WAbsent
              because of an invariant. Otherwise, we must
              check that all are WAbsent here. *)
           with
           | Exit -> false)

end

type slot = {
  mutable status : status;
  mutable notify : notify;
  mutable active : bool;
}

and status =
  | Empty
  | NEmpty of Witness.witness
  | Maybe

and notify =
  | Nothing
  | Do of slot * (Witness.witness -> unit) * notify

let slot_nempty w = { status = NEmpty w; active = false; notify = Nothing }

let rec notify w = function
  | Nothing -> ()
  | Do (n, f, rem) ->
    (if n.status == Maybe then
       try f w with
       | NotEmpty -> ());
    notify w rem

let mk_poly vars w =
  match vars with
    [], [] -> w
  | p, n -> Witness.WPoly (Var.Set.from_list p,
                           Var.Set.from_list n, w)
let rec iter_s s f = function
  | [] -> ()
  | (x,y) :: rem ->
    f x y s;
    iter_s s f rem

let set s w =
  s.status <- NEmpty w;
  notify w s.notify;
  s.notify <- Nothing;
  raise NotEmpty

let rec big_conj f l n w =
  match l with
  | [] -> set n w
  | [ arg ] -> f w arg n
  | arg :: rem -> (
      let s =
        {
          status = Maybe;
          active = false;
          notify = Do (n, big_conj f rem n, Nothing);
        }
      in
      try
        f w arg s;
        if s.active then n.active <- true
      with
      | NotEmpty when n.status == Empty || n.status == Maybe -> ())

let memo = DescrHash.create 8191
let marks = ref []
let count_subtype = Stats.Counter.create "Subtyping internal loop"

let rec find_map f = function
    [] -> None
  | e :: l ->
    match f e with
      None -> find_map f l
    | r -> r

let check_basic (type mono) (module M : Kind with type Dnf.mono = mono) d is_empty mk =
  M.get_vars d
  |> M.Dnf.get_partial
  |> find_map (fun ((pos, neg), m) ->
      if is_empty m then None
      else
        let w = mk m in
        let w = match pos, neg with
            [], [] -> w
          | _ -> Witness.WPoly (Var.Set.from_list pos,
                                Var.Set.from_list neg, w)
        in
        Some (slot_nempty w))

let (let*) o f =
  match o with
    Some e -> e
  | None ->  f ()

let rec slot d =
  Stats.Counter.incr count_subtype;
  if d == empty then { status = Empty; active = false; notify = Nothing }
  else if d.absent then slot_nempty Witness.WAbsent
  else
    let* () = check_basic (module Int) d Intervals.is_empty
        (fun i -> Witness.WInt (Intervals.sample i)) in
    let* () = check_basic (module Atom) d AtomSet.is_empty
        (fun a -> Witness.WAtom (AtomSet.sample a)) in
    let* () = check_basic (module Char) d CharSet.is_empty
        (fun c -> Witness.WChar (CharSet.sample c)) in
    let* () = check_basic (module Abstract) d AbstractSet.is_empty
        (fun a -> Witness.WAbstract (AbstractSet.sample a)) in
    try DescrHash.find memo d with
    | Not_found ->
      let s = { status = Maybe; active = false; notify = Nothing } in
      DescrHash.add memo d s;
      (try
         iter_s s check_times Times.(d |> get_vars |> Dnf.get_full);
         iter_s s check_xml Xml.(d |> get_vars |> Dnf.get_full);
         iter_s s check_arrow Function.(d |> get_vars |> Dnf.get_full);
         iter_s s check_record (get_record_full d);
         if s.active then marks := s :: !marks else s.status <- Empty
       with
       | NotEmpty -> ());
      s

and guard n t f =
  match slot t with
  | { status = Empty; _ } -> ()
  | { status = Maybe; _ } as s ->
    n.active <- true;
    s.notify <- Do (n, f, s.notify)
  | { status = NEmpty v; _ } -> f v

and check_product vars any_right mk_witness (left, right) s =
  let rec aux w1 w2 accu1 accu2 seen = function
    (* Find a product in right which contains (w1,w2) *)
    | [] ->
      (* no such product: the current witness is in the difference. *)
      set s (mk_poly vars (mk_witness w1 w2))
    | (n1, n2) :: rest when Witness.node_has n1 w1 && Witness.node_has n2 w2 ->
      let right = List.rev_append seen rest in
      let accu2' = diff accu2 (descr n2) in
      guard s accu2' (fun w2 -> aux w1 w2 accu1 accu2' [] right);
      let accu1' = diff accu1 (descr n1) in
      guard s accu1' (fun w1 -> aux w1 w2 accu1' accu2 [] right)
    | k :: rest -> aux w1 w2 accu1 accu2 (k :: seen) rest
  in
  let t1, t2 = cap_product any any_right left in
  guard s t1 (fun w1 -> guard s t2 (fun w2 -> aux w1 w2 t1 t2 [] right))

and check_times vars prod s = check_product vars any Witness.wpair prod s
and check_xml vars prod s = check_product vars any_pair Witness.wxml prod s

and check_arrow (vpos, vneg) (left, right) s =
  let single_right f (s1, s2) s =
    let rec aux w1 w2 accu1 accu2 left =
      match left with
      | (t1, t2) :: left ->
        let accu1' = diff_t accu1 t1 in
        guard s accu1' (fun w1 -> aux w1 w2 accu1' accu2 left);

        let accu2' = cap_t accu2 t2 in
        guard s accu2' (fun w2 -> aux w1 (Some w2) accu1 accu2' left)
      | [] ->
        let op, on, f =
          match f with
          | Witness.WFun (f, _) -> [], [], f
          | Witness.WPoly (op, on, Witness.WFun (f, _)) ->
            Var.Set.(get op, get on, f)
          | _ -> assert false
        in
        set s (mk_poly (op @ vpos, on@vneg) (Witness.wfun ((w1, w2) :: f)))
    in
    let accu1 = descr s1 in
    guard s accu1 (fun w1 -> aux w1 None accu1 (neg (descr s2)) left)
  in
  big_conj single_right right s (Witness.wfun [])

and check_record vars (labels, (oleft, left), rights) s =
  let rec aux ws accus seen = function
    | [] ->
      let rec aux w i = function
        | [] ->
          assert (i == Array.length ws);
          w
        | l :: labs ->
          let w =
            match ws.(i) with
            | Witness.WAbsent -> w
            | wl -> LabelMap.add l wl w
          in
          aux w (succ i) labs
      in
      set s (mk_poly vars (Witness.wrecord (aux LabelMap.empty 0 labels) oleft))
    | (false, _) :: rest when oleft -> aux ws accus seen rest
    | (_, f) :: rest
      when not
          (exists (Array.length left) (fun i ->
               not (Witness.type_has f.(i) ws.(i)))) ->
      (* TODO: a version f get_record which keeps nodes in neg records. *)
      let right = seen @ rest in
      for i = 0 to Array.length left - 1 do
        let di = diff accus.(i) f.(i) in
        guard s di (fun wi ->
            let accus' = Array.copy accus in
            accus'.(i) <- di;
            let ws' = Array.copy ws in
            ws'.(i) <- wi;
            aux ws' accus' [] right)
      done
    | k :: rest -> aux ws accus (k :: seen) rest
  in
  let rec start wl i =
    if i < 0 then aux (Array.of_list wl) left [] rights
    else guard s left.(i) (fun w -> start (w :: wl) (i - 1))
  in
  start [] (Array.length left - 1)

let timer_subtype = Stats.Timer.create "Types.is_empty"

let is_empty d =
  Stats.Timer.start timer_subtype;
  let s = slot d in
  List.iter
    (fun s' ->
       if s'.status == Maybe then s'.status <- Empty;
       s'.notify <- Nothing)
    !marks;
  marks := [];
  Stats.Timer.stop timer_subtype (s.status == Empty)

let getwit t =
  match (slot t).status with
  | NEmpty w -> w
  | _ -> assert false

(* Assumes that is_empty has been called on t before. *)

let witness t = if is_empty t then raise Not_found else getwit t
let print_witness ppf t = Witness.print_witness ppf (witness t)
let non_empty d = not (is_empty d)
let disjoint d1 d2 = is_empty (cap d1 d2)
let subtype d1 d2 = is_empty (diff d1 d2)
let equiv d1 d2 = subtype d1 d2 && subtype d2 d1

(* redefine operations to take subtyping into account and perform hash consing *)
let forward_pointers = DescrHash.create 16

module NL = SortedList.Make (Node)

let get_all n =
  if is_opened n then NL.singleton n
  else
    try DescrHash.find forward_pointers (descr n) with
    | Not_found -> NL.empty

let add t n =
  let lold =
    try DescrHash.find forward_pointers t with
    | Not_found -> NL.empty
  in
  DescrHash.replace forward_pointers t (NL.cup n lold)

let times n1 n2 =
  let f1 = get_all n1 in
  let f2 = get_all n2 in
  let t = times n1 n2 in
  add t f1;
  add t f2;
  t

module Cache = struct
  (*
  let type_has_witness t w =
    Format.fprintf Format.std_formatter
      "check wit:%a@." print_witness w;
    let r = type_has_witness t w in
    Format.fprintf Format.std_formatter "Done@.";
    r
*)

  type 'a cache =
    | Empty
    | Type of t * 'a
    | Split of Witness.witness * 'a cache * 'a cache

  let rec find f t = function
    | Empty ->
      let r = f t in
      (Type (t, r), r)
    | Split (w, yes, no) ->
      if Witness.type_has t w then
        let yes, r = find f t yes in
        (Split (w, yes, no), r)
      else
        let no, r = find f t no in
        (Split (w, yes, no), r)
    | Type (s, rs) as c -> (
        let f1 () =
          let w = witness (diff t s) in
          let rt = f t in
          (Split (w, Type (t, rt), c), rt)
        and f2 () =
          let w = witness (diff s t) in
          let rt = f t in
          (Split (w, c, Type (t, rt)), rt)
        in

        if Random.int 2 = 0 then
          try f1 () with
          | Not_found -> (
              try f2 () with
              | Not_found -> (c, rs))
        else
          try f2 () with
          | Not_found -> (
              try f1 () with
              | Not_found -> (c, rs)))

  let rec lookup t = function
    | Empty -> None
    | Split (w, yes, no) -> lookup t (if Witness.type_has t w then yes else no)
    | Type (s, rs) -> if equiv s t then Some rs else None

  let emp = Empty

  let[@ocaml.warning "-32"] rec dump_cache f ppf = function
    | Empty -> Format.fprintf ppf "Empty"
    | Type (_, s) -> Format.fprintf ppf "*%a" f s
    | Split (_, c1, c2) ->
      Format.fprintf ppf "?(%a,%a)"
        (*Witness.print_witness w *) (dump_cache f)
        c1 (dump_cache f) c2

  let memo f =
    let c = ref emp in
    fun t ->
      let c', r = find f t !c in
      c := c';
      r
end

module Product = struct
  type t = (descr * descr) list

  let _other ?(kind = `Normal) d =
    match kind with
    | `Normal -> { d with times = empty.times; hash = -1 }
    | `XML -> { d with xml = empty.xml; hash = -1 }

  let _is_product ?kind d = is_empty (_other ?kind d)

  let need_second = function
    | _ :: _ :: _ -> true
    | _ -> false

  let normal_aux = function
    | ([] | [ _ ]) as d -> d
    | d ->
      let res = ref [] in

      let add (t1, t2) =
        let rec loop t1 t2 = function
          | [] -> res := ref (t1, t2) :: !res
          | ({ contents = d1, d2 } as r) :: l ->
            (*OPT*)
            (*	    if equal_descr d1 t1 then r := (d1,cup d2 t2) else*)
            let i = cap t1 d1 in
            if is_empty i then loop t1 t2 l
            else (
              r := (i, cup t2 d2);
              let k = diff d1 t1 in
              if non_empty k then res := ref (k, d2) :: !res;

              let j = diff t1 d1 in
              if non_empty j then loop j t2 l)
        in
        loop t1 t2 !res
      in
      List.iter add d;
      List.map ( ! ) !res

  (* Partitioning:
     (t,s) - ((t1,s1) | (t2,s2) | ... | (tn,sn))
      =
      (t & t1, s - s1) | ... | (t & tn, s - sn) | (t - (t1|...|tn), s)
  *)
  let get_aux any_right d =
    let accu = ref [] in
    let line (left, right) =
      let d1, d2 = cap_product any any_right left in
      if non_empty d1 && non_empty d2 then
        let right = List.map (fun (t1, t2) -> (descr t1, descr t2)) right in
        let right = normal_aux right in
        let resid1 = ref d1 in
        let () =
          List.iter
            (fun (t1, t2) ->
               let t1 = cap d1 t1 in
               if non_empty t1 then
                 let () = resid1 := diff !resid1 t1 in
                 let t2 = diff d2 t2 in
                 if non_empty t2 then accu := (t1, t2) :: !accu)
            right
        in
        if non_empty !resid1 then accu := (!resid1, d2) :: !accu
    in
    List.iter line (BoolPair.get d);
    !accu

  let partition = get_aux
  (* Maybe, can improve this function with:
       (t,s) \ (t1,s1) = (t&t',s\s') | (t\t',s),
     don't call normal_aux *)

  let get ?(kind = `Normal) d =
    match kind with
    | `Normal -> get_aux any d.times
    | `XML -> get_aux any_pair d.xml

  let pi1 = List.fold_left (fun acc (t1, _) -> cup acc t1) empty
  let pi2 = List.fold_left (fun acc (_, t2) -> cup acc t2) empty

  let pi2_restricted restr =
    List.fold_left
      (fun acc (t1, t2) -> if disjoint t1 restr then acc else cup acc t2)
      empty

  let restrict_1 rects pi1 =
    let aux acc (t1, t2) =
      let t1 = cap t1 pi1 in
      if is_empty t1 then acc else (t1, t2) :: acc
    in
    List.fold_left aux [] rects

  type normal = t

  module Memo = Map.Make (BoolPair)

  (* TODO: try with an hashtable *)
  (* Also, avoid lookup for simple products (t1,t2) *)
  let memo = ref Memo.empty

  let normal_times d =
    try Memo.find d !memo with
    | Not_found ->
      let gd = get_aux any d in
      let n = normal_aux gd in
      (* Could optimize this call to normal_aux because one already
         know that each line is normalized ... *)
      memo := Memo.add d n !memo;
      n

  let memo_xml = ref Memo.empty

  let normal_xml d =
    try Memo.find d !memo_xml with
    | Not_found ->
      let gd = get_aux any_pair d in
      let n = normal_aux gd in
      memo_xml := Memo.add d n !memo_xml;
      n

  let normal ?(kind = `Normal) d =
    match kind with
    | `Normal -> normal_times d.times
    | `XML -> normal_xml d.xml

  (*
  let merge_same_2 r =
    let r =
      List.fold_left
	(fun accu (t1,t2) ->
	   let t = try DescrMap.find t2 accu with Not_found -> empty in
	   DescrMap.add t2 (cup t t1) accu
	) DescrMap.empty r in
    DescrMap.fold (fun t2 t1 accu -> (t1,t2)::accu) r []
*)

  let constraint_on_2 n t1 =
    List.fold_left
      (fun accu (d1, d2) -> if disjoint d1 t1 then accu else cap accu d2)
      any n

  let merge_same_first tr =
    let trs = ref [] in
    let _ =
      List.fold_left
        (fun memo (t1, t2) ->
           let memo', l =
             Cache.find
               (fun t1 ->
                  let l = ref empty in
                  trs := (t1, l) :: !trs;
                  l)
               t1 memo
           in
           l := cup t2 !l;
           memo')
        Cache.emp tr
    in
    List.map (fun (t1, l) -> (t1, !l)) !trs

  (* same on second component: use the same implem? *)
  let clean_normal l =
    let rec aux accu (t1, t2) =
      match accu with
      | [] -> [ (t1, t2) ]
      | (s1, s2) :: rem when equiv t2 s2 -> (cup s1 t1, s2) :: rem
      | (s1, s2) :: rem -> (s1, s2) :: aux rem (t1, t2)
    in
    List.fold_left aux [] l

  let is_empty d = d == []
end

module Record = struct
  let has_record d = not (is_empty { empty with record = d.record; hash = -1 })
  let or_absent d = { d with absent = true; hash = -1 }
  let absent = or_absent empty
  let any_or_absent = any_or_absent
  let has_absent d = d.absent
  let absent_node = cons absent

  module T = struct
    type t = descr

    let any = any_or_absent
    let cap = cap
    let cup = cup
    let diff = diff
    let is_empty = is_empty
    let empty = empty
  end

  module R = struct
    type t = descr

    let any = { empty with record = any.record; hash = -1 }
    let cap = cap
    let cup = cup
    let diff = diff
    let is_empty = is_empty
    let empty = empty
  end

  module TR = Normal.Make (T) (R)

  let any_record = { empty with record = BoolRec.any; hash = -1 }

  let atom o l =
    if o && LabelMap.is_empty l then any_record
    else { empty with record = BoolRec.atom (o, l); hash = -1 }

  type zor =
    | Pair of descr * descr
    | Any

  (* given a type t and a label l, this function computes the projection on
     l on each component of the DNF *)
  let aux_split d l =
    let f (o, r) =
      try
        (* separate a record type between the type of its label l, if it appears
           explicitely and the type of the reminder of the record *)
        let lt, rem = LabelMap.assoc_remove l r in
        Pair (descr lt, atom o rem)
      with
      | Not_found ->
        (* if the label l is not present explicitely *)
        if o then
          (* if the record is open *)
          if LabelMap.is_empty r then Any
          (* if there are no explicity fields return Any,
             the record type is not splited *)
          else
            (* otherwise returns the fact that the field may or may not be present
            *)
            Pair
              ( any_or_absent,
                { empty with record = BoolRec.atom (o, r); hash = -1 } )
        else
          (* for closed records, return the fact that the label was absent *)
          Pair (absent, { empty with record = BoolRec.atom (o, r); hash = -1 })
    in
    List.fold_left
      (fun b (p, n) ->
         (* for each positive/negative intersections*)
         let rec aux_p accu = function
           | x :: p -> (
               (*get the ucrrent positive record, and split according to l*)
               match f x with
               (* if something, add (typof l, rest) to the positive accumulator*)
               | Pair (t1, t2) -> aux_p ((t1, t2) :: accu) p
               | Any ->
                 aux_p accu p
                 (* if we have { ..} in this positive
                    intersection, we can ignore it. *))
           | [] -> aux_n accu [] n
         (* now follow up with negative*)
         and aux_n p accu = function
           | x :: n -> (
               match f x with
               (* if we have a pair add it to the current negative accmulator*)
               | Pair (t1, t2) -> aux_n p ((t1, t2) :: accu) n
               (* if { .. } is in a negative intersection, the whole branch (p,n)
                  can be discarded *)
               | Any -> b)
           | [] -> (p, accu) :: b
           (* add the current pair of line *)
         in
         aux_p [] p)
      [] (BoolRec.get d.record)

  (* We now have a DNF of pairs where the left component is the type of
     a label l and the right component the rest of the record types.
     split returns a simplified DNF where the intersection are pushed
     below the products.

      Given a type d and a label l, this function returns
      a list of pairs : the first component represents the disjoint union
      of types associated to l
      the second projection the remaining types (records \ l)
  *)
  let split (d : descr) l = TR.boolean (aux_split d l)

  (* same as above, but the types for .l are disjoint *)
  let split_normal d l = TR.boolean_normal (aux_split d l)

  (* returns the union of the first projections. If one of the record
     had an absent l, then absent will end up in the result. *)
  let pi l d = TR.pi1 (split d l)

  (* Same but check that the resulting type does not contain absent *)
  let project d l =
    let t = pi l d in
    if t.absent then raise Not_found;
    t

  (* Same but erase the status of absent : meaning return the type of l
     if it is present.*)
  let project_opt d l =
    let t = pi l d in
    { t with absent = false; hash = -1 }

  let _condition d l t = TR.pi2_restricted t (split d l)

  (* TODO: eliminate this cap ... (record l absent_node) when
     not necessary. eg. { ..... } \ l *)

  (* get the pi2 part of split, that is the union of all the record types where
     l has been removed explicitely. And cap it with an open record where l
     is absent, to eliminate l from open record types where it was implicitely present.
  *)
  let remove_field d l = cap (TR.pi2 (split d l)) (record l absent_node)

  let _all_labels d =
    let res = ref LabelSet.empty in
    let aux (_, r) =
      let ls = LabelMap.domain r in
      res := LabelSet.cup ls !res
    in
    BoolRec.iter aux d.record;
    !res

  let first_label d =
    let min = ref Label.dummy in
    let aux (_, r) =
      match LabelMap.get r with
      | (l, _) :: _ -> min := Label.min l !min
      | _ -> ()
    in
    Rec.Dnf.iter aux d.record;
    !min

  let empty_cases d =
    let x =
      BoolRec.compute ~empty:0 ~any:3
        ~cup:(fun x y -> x lor y)
        ~cap:(fun x y -> x land y)
        ~diff:(fun a b -> a land lnot b)
        ~atom:(function
            | o, r ->
              assert (LabelMap.get r == []);
              if o then 3 else 1)
        d.record
    in
    (x land 2 <> 0, x land 1 <> 0)

  let has_empty_record d =
    BoolRec.compute ~empty:false ~any:true ~cup:( || ) ~cap:( && )
      ~diff:(fun a b -> a && not b)
      ~atom:(function
          | _, r -> List.for_all (fun (_, t) -> (descr t).absent) (LabelMap.get r))
      d.record

  (*TODO: optimize merge
    - pre-compute the sequence of labels
    - remove empty or full { l = t }
  *)

  let merge d1 d2 =
    let res = ref empty in
    let rec aux accu d1 d2 =
      let l = Label.min (first_label d1) (first_label d2) in
      if l == Label.dummy then
        let some1, none1 = empty_cases d1
        and some2, none2 = empty_cases d2 in
        let _none = none1 && none2
        and some = some1 || some2 in
        let accu = LabelMap.from_list (fun _ _ -> assert false) accu in
        (* approx for the case (some && not none) ... *)
        res := cup !res (record_fields (some, accu))
      else
        let l1 = split d1 l
        and l2 = split d2 l in
        let loop (t1, d1) (t2, d2) =
          let t =
            if t2.absent then cup t1 { t2 with absent = false; hash = -1 }
            else t2
          in
          aux ((l, cons t) :: accu) d1 d2
        in
        List.iter (fun x -> List.iter (loop x) l2) l1
    in

    aux [] d1 d2;
    !res

  let get d =
    let rec aux r accu d =
      let l = first_label d in
      if l == Label.dummy then
        let o1, o2 = empty_cases d in
        if o1 || o2 then (LabelMap.from_list_disj r, o1, o2) :: accu else accu
      else
        List.fold_left
          (fun accu (t1, t2) ->
             let x = (t1.absent, { t1 with absent = false }) in
             aux ((l, x) :: r) accu t2)
          accu (split d l)
    in
    aux [] [] d

  type t = TR.t

  let focus = split_normal
  let get_this r = { (TR.pi1 r) with absent = false; hash = -1 }

  let need_others = function
    | _ :: _ :: _ -> true
    | _ -> false

  let constraint_on_others r t1 =
    List.fold_left
      (fun accu (d1, d2) -> if disjoint d1 t1 then accu else cap accu d2)
      any_record r
end

let memo_normalize = ref DescrMap.empty

let rec rec_normalize d =
  try DescrMap.find d !memo_normalize with
  | Not_found ->
    let n = make () in
    memo_normalize := DescrMap.add d n !memo_normalize;
    let times =
      List.fold_left
        (fun accu (d1, d2) ->
           BoolPair.cup accu
             (BoolPair.atom (rec_normalize d1, rec_normalize d2)))
        BoolPair.empty (Product.normal d)
    in
    let xml =
      List.fold_left
        (fun accu (d1, d2) ->
           BoolPair.cup accu
             (BoolPair.atom (rec_normalize d1, rec_normalize d2)))
        BoolPair.empty
        (Product.normal ~kind:`XML d)
    in
    let record = d.record in
    define n { d with times; xml; record; hash = -1 };
    n

let normalize n = descr (internalize (rec_normalize n))

module Arrow = struct
  let trivially_arrow t =
    if subtype Function.any t then `Arrow
    else if is_empty { empty with arrow = t.arrow; hash = -1 } then `NotArrow
    else `Other

  let check_simple left (s1, s2) =
    let rec aux accu1 accu2 = function
      | (t1, t2) :: left ->
        let accu1' = diff_t accu1 t1 in
        if non_empty accu1' then aux accu1 accu2 left;
        let accu2' = cap_t accu2 t2 in
        if non_empty accu2' then aux accu1 accu2 left
      | [] -> raise NotEmpty
    in
    let accu1 = descr s1 in
    is_empty accu1
    ||
    try
      aux accu1 (diff any (descr s2)) left;
      true
    with
    | NotEmpty -> false

  let check_line_non_empty (left, right) =
    not (List.exists (check_simple left) right)

  let sample t =
    let left, _right =
      List.find check_line_non_empty (Function.Dnf.get t.arrow)
    in
    List.fold_left
      (fun accu (t, s) -> cap accu (arrow t s))
      { empty with arrow = any.arrow; hash = -1 }
      left

  (* [check_strenghten t s]
     Assume that [t] is an intersection of arrow types
     representing the interface of an abstraction;
     check that this abstraction has type [s] (otherwise raise Not_found)
     and returns a refined type for this abstraction.
  *)

  let _check_strenghten t s =
    (*
    let left = match (BoolPair.get t.arrow) with [ (p,[]) ] -> p | _ -> assert false in
    let rec aux = function
      | [] -> raise Not_found
      | (p,n) :: rem ->
	  if (List.for_all (fun (a,b) -> check_simple left a b) p) &&
	    (List.for_all (fun (a,b) -> not (check_simple left a b)) n) then
	      { empty with arrow = Obj.magic [ (SortedList.cup left p, n) ] }  (* rework this ! *)
	  else aux rem
    in
    aux (BoolPair.get s.arrow)
*)
    if subtype t s then t else raise Not_found

  let check_simple_iface left s1 s2 =
    let rec aux accu1 accu2 = function
      | (t1, t2) :: left ->
        let accu1' = diff accu1 t1 in
        if non_empty accu1' then aux accu1 accu2 left;
        let accu2' = cap accu2 t2 in
        if non_empty accu2' then aux accu1 accu2 left
      | [] -> raise NotEmpty
    in
    let accu1 = descr s1 in
    is_empty accu1
    ||
    try
      aux accu1 (diff any (descr s2)) left;
      true
    with
    | NotEmpty -> false

  let check_iface iface s =
    let rec aux = function
      | [] -> false
      | (p, n) :: rem ->
        List.for_all (fun (a, b) -> check_simple_iface iface a b) p
        && List.for_all (fun (a, b) -> not (check_simple_iface iface a b)) n
        || aux rem
    in
    aux (Function.Dnf.get s.arrow)

  type t = descr * (descr * descr) list list

  let get t =
    List.fold_left
      (fun ((dom, arr) as accu) (left, right) ->
         if check_line_non_empty (left, right) then
           let left = List.map (fun (t, s) -> (descr t, descr s)) left in
           let d = List.fold_left (fun d (t, _) -> cup d t) empty left in
           (cap dom d, left :: arr)
         else accu)
      (any, []) (BoolPair.get t.arrow)

  let domain (dom, _) = dom

  let apply_simple t result left =
    let rec aux result accu1 accu2 = function
      | (t1, s1) :: left ->
        let result =
          let accu1 = diff accu1 t1 in
          if non_empty accu1 then aux result accu1 accu2 left else result
        in
        let result =
          let accu2 = cap accu2 s1 in
          aux result accu1 accu2 left
        in
        result
      | [] -> if subtype accu2 result then result else cup result accu2
    in
    aux result t any left

  let apply (_, arr) t =
    if is_empty t then empty else List.fold_left (apply_simple t) empty arr

  let need_arg (_dom, arr) =
    List.exists
      (function
        | [ _ ] -> false
        | _ -> true)
      arr

  let apply_noarg (_, arr) =
    List.fold_left
      (fun accu -> function
         | [ (_t, s) ] -> cup accu s
         | _ -> assert false)
      empty arr

  let is_empty (_, arr) = arr == []
end

let rec tuple = function
  | [ t1; t2 ] -> times t1 t2
  | t :: tl -> times t (cons (tuple tl))
  | _ -> failwith "tuple: invalid length"

let rec_of_list o l =
  let map =
    LabelMap.from_list
      (fun _ _ -> failwith "rec_of_list: duplicate fields")
      (List.map
         (fun (opt, qname, typ) ->
            (qname, cons (if opt then Record.or_absent typ else typ)))
         l)
  in
  record_fields (o, map)

let empty_closed_record = rec_of_list false []
let empty_open_record = Rec.any

let cond_partition univ qs =
  let add accu (t, s) =
    let t = if subtype t s then t else cap t s in
    if subtype s t || is_empty t then accu
    else
      let aux accu u =
        let c = cap u t in
        if is_empty c || subtype (cap u s) t then u :: accu
        else c :: diff u t :: accu
      in
      List.fold_left aux [] accu
  in
  List.fold_left add [ univ ] qs

lang/typing/typepat.ml

open Ident
open Cduce_error

let any_node = Types.(cons any)
let any_or_absent_node = Types.(cons Record.any_or_absent)

type err = string -> exn

let deferr s = raise_err Typer_Pattern s

type node = {
  mutable desc : desc;
  mutable smallhash : int;
  (* Local hash *)
  mutable rechash : int;
  (* Global (recursive) hash *)
  mutable sid : int;
  (* Sequential id used to compute rechash *)
  mutable t : Types.t option;
  mutable tnode : Types.Node.t option;
  mutable p : Patterns.descr option;
  mutable pnode : Patterns.node option;
  mutable fv : fv option;
}

and desc =
  | ILink of node
  | IType of Types.descr * int
  | IOr of node * node * err
  | IAnd of node * node * err
  | IDiff of node * node * err
  | ITimes of node * node
  | IXml of node * node
  | IArrow of node * node
  | IOptional of node * err
  | IRecord of bool * (node * node option) label_map * err
  | ICapture of id
  | IConstant of id * Types.const
  | IConcat of node * node * err
  | IMerge of node * node * err

let concats = ref []

let rec node_temp =
  {
    desc = ILink node_temp;
    smallhash = 0;
    rechash = 0;
    sid = 0;
    t = None;
    tnode = None;
    p = None;
    pnode = None;
    fv = None;
  }

let mk d = { node_temp with desc = d }
let mk_delayed () = { node_temp with desc = ILink node_temp }
let mk_type t = mk (IType (t, Types.hash t))
let mk_or ?(err = deferr) n1 n2 = mk (IOr (n1, n2, err))
let mk_and ?(err = deferr) n1 n2 = mk (IAnd (n1, n2, err))
let mk_diff ?(err = deferr) n1 n2 = mk (IDiff (n1, n2, err))
let mk_prod n1 n2 = mk (ITimes (n1, n2))
let mk_xml n1 n2 = mk (IXml (n1, n2))
let mk_arrow n1 n2 = mk (IArrow (n1, n2))
let mk_optional ?(err = deferr) n = mk (IOptional (n, err))
let mk_record ?(err = deferr) n1 n2 = mk (IRecord (n1, n2, err))
let mk_capture n = mk (ICapture n)
let mk_constant n1 n2 = mk (IConstant (n1, n2))

let mk_concat ?(err = deferr) n1 n2 =
  let n = mk (IConcat (n1, n2, err)) in
  concats := n :: !concats;
  n

let mk_merge ?(err = deferr) n1 n2 =
  let n = mk (IMerge (n1, n2, err)) in
  concats := n :: !concats;
  n

let iempty = mk_type Types.empty

let mk_or ?err p1 p2 =
  if p1.desc == iempty.desc then p2
  else if p2.desc == iempty.desc then p1
  else mk_or ?err p1 p2

let mk_and ?err p1 p2 =
  if p1.desc == iempty.desc || p2.desc == iempty.desc then iempty
  else mk_and ?err p1 p2

(* Recursive hash-consing *)

let hash_field f = function
  | p, Some e -> 1 + (17 * f p) + (257 * f e)
  | p, None -> 2 + (17 * f p)

let rec hash f n =
  match n.desc with
  | ILink n -> hash f n
  | IType (t, h) -> 1 + (17 * h)
  | IOr (p1, p2, _) -> 2 + (17 * f p1) + (257 * f p2)
  | IAnd (p1, p2, _) -> 3 + (17 * f p1) + (257 * f p2)
  | IDiff (p1, p2, _) -> 4 + (17 * f p1) + (257 * f p2)
  | ITimes (p1, p2) -> 5 + (17 * f p1) + (257 * f p2)
  | IXml (p1, p2) -> 6 + (17 * f p1) + (257 * f p2)
  | IArrow (p1, p2) -> 7 + (17 * f p1) + (257 * f p2)
  | IOptional (p, _) -> 8 + (17 * f p)
  | IRecord (o, r, _) ->
    9 + (if o then 17 else 0) + (257 * LabelMap.hash (hash_field f) r)
  | ICapture x -> 10 + (17 * Id.hash x)
  | IConstant (x, c) -> 11 + (17 * Id.hash x) + (257 * Types.Const.hash c)
  | IConcat _
  | IMerge _ ->
    assert false

let hash0 = hash (fun n -> 1)
let hash1 = hash hash0
let hash2 = hash hash1
let hash3 = hash hash2

let smallhash n =
  if n.smallhash != 0 then n.smallhash
  else
    let h = hash2 n in
    n.smallhash <- h;
    h

let rec repr = function
  | { desc = ILink n } as m ->
    let z = repr n in
    m.desc <- ILink z;
    z
  | n -> n

let back = ref []

let rec prot_repr = function
  | { desc = ILink n } -> repr n
  | n -> n

let link x y =
  match (x, y) with
  | ({ t = None } as x), y
  | y, ({ t = None } as x) ->
    back := (x, x.desc) :: !back;
    x.desc <- ILink y
  | _ -> assert false

let rec unify x y =
  if x == y then ()
  else
    let x = prot_repr x
    and y = prot_repr y in
    if x == y then ()
    else if smallhash x != smallhash y then raise_err Typepat_Unify ()
    else if x.t != None && y.t != None then raise_err Typepat_Unify ()
    (* x and y have been internalized; if they were equivalent,
       they would be equal *)
    else
      match (x.desc, y.desc) with
      | IType (tx, _), IType (ty, _) when Types.equal tx ty -> link x y
      | IOr (x1, x2, _), IOr (y1, y2, _)
      | IAnd (x1, x2, _), IAnd (y1, y2, _)
      | IDiff (x1, x2, _), IDiff (y1, y2, _)
      | ITimes (x1, x2), ITimes (y1, y2)
      | IXml (x1, x2), IXml (y1, y2)
      | IArrow (x1, x2), IArrow (y1, y2) ->
        link x y;
        unify x1 y1;
        unify x2 y2
      | IOptional (x1, _), IOptional (y1, _) ->
        link x y;
        unify x1 y1
      | IRecord (xo, xr, _), IRecord (yo, yr, _) when xo == yo ->
        link x y;
        LabelMap.may_collide unify_field (Error (Unlocated, (Typepat_Unify, ()))) xr yr
      | ICapture xv, ICapture yv when Id.equal xv yv -> ()
      | IConstant (xv, xc), IConstant (yv, yc)
        when Id.equal xv yv && Types.Const.equal xc yc ->
        ()
      | _ -> raise_err Typepat_Unify ()

and unify_field f1 f2 =
  match (f1, f2) with
  | (p1, Some e1), (p2, Some e2) ->
    unify p1 p2;
    unify e1 e2
  | (p1, None), (p2, None) -> unify p1 p2
  | _ -> raise_err Typepat_Unify ()

let may_unify x y =
  try
    unify x y;
    back := [];
    true
  with
  | Error (_, (Typepat_Unify, ())) ->
    List.iter (fun (x, xd) -> x.desc <- xd) !back;
    back := [];
    false

module SmallHash = Hashtbl.Make (struct
    type t = node

    let equal = may_unify
    let hash = smallhash
  end)

let iter_field f = function
  | x, Some y ->
    f x;
    f y
  | x, None -> f x

let iter f = function
  | IOr (x, y, _)
  | IAnd (x, y, _)
  | IDiff (x, y, _)
  | ITimes (x, y)
  | IXml (x, y)
  | IArrow (x, y) ->
    f x;
    f y
  | IOptional (x, _) -> f x
  | IRecord (_, r, _) -> LabelMap.iter (iter_field f) r
  | _ -> ()

let minimize (mem, add) =
  let rec aux n =
    let n = repr n in
    if mem n then ()
    else
      let n = repr n in
      add n ();
      if n.t == None then iter aux n.desc
  in
  aux

let to_clear = ref []
let sid = ref 0

let rec rechash n =
  let n = repr n in
  if n.sid != 0 then 17 * n.sid
  else (
    incr sid;
    n.sid <- !sid;
    to_clear := n :: !to_clear;
    hash rechash n)

let clear () =
  sid := 0;
  List.iter (fun x -> x.sid <- 0) !to_clear;
  to_clear := []

let rechash n =
  let n = repr n in
  if n.rechash != 0 then n.rechash
  else
    let h = rechash n in
    clear ();
    n.rechash <- h;
    h

module RecHash = Hashtbl.Make (struct
    type t = node

    let equal = may_unify
    let hash = smallhash
  end)

(** Two-phases recursive hash-consing **)
(*
  let gtable = RecHash.create 17577

  let internalize n =
    let local = SmallHash.create 17 in
    minimize (SmallHash.mem local, SmallHash.add local) n; 
    minimize (RecHash.mem gtable, RecHash.add gtable) n;
    ()
*)

(** Single-phase hash-consing **)
let gtable = SmallHash.create 17

let internalize n = minimize (SmallHash.mem gtable, SmallHash.add gtable) n

(*  let internalize n = () *)

(* Compute free variables *)

let fv n =
  let fv = ref IdSet.empty in
  let rec aux n =
    let n = repr n in
    if n.sid = 0 then (
      n.sid <- 1;
      to_clear := n :: !to_clear;
      match (n.fv, n.desc) with
      | Some x, _ -> fv := IdSet.cup !fv x
      | None, (ICapture x | IConstant (x, _)) -> fv := IdSet.add x !fv
      | None, d -> iter aux d)
  in
  assert (!to_clear == []);
  match n.fv with
  | Some x -> x
  | None ->
    aux n;
    clear ();
    n.fv <- Some !fv;
    !fv

(* optimized version to check closedness *)

let no_fv = Some IdSet.empty

let peek_fv n =
  let err x = raise_err Typepat_FoundFv x in
  let rec aux n =
    let n = repr n in
    if n.sid = 0 then (
      n.sid <- 1;
      to_clear := n :: !to_clear;
      match (n.fv, n.desc) with
      | Some x, _ when IdSet.is_empty x -> ()
      | Some x, _ -> err (IdSet.choose x)
      | None, (ICapture x | IConstant (x, _)) -> err x
      | None, d -> iter aux d)
  in
  assert (!to_clear == []);
  try
    match n.fv with
    | Some x when IdSet.is_empty x -> ()
    | Some x -> err (IdSet.choose x)
    | None ->
      aux n;
      List.iter
        (fun n ->
           n.sid <- 0;
           n.fv <- no_fv)
        !to_clear;
      to_clear := []
  with
  | exn ->
    clear ();
    raise exn

let has_no_fv n =
  try
    peek_fv n;
    true
  with
  | Error (_, (Typepat_FoundFv, _)) -> false

let peek_fv n =
  try
    peek_fv n;
    None
  with
  | Cduce_error.Error (_, (Typepat_FoundFv, x)) -> Some (x : Ident.id)

(* From the intermediate representation to the internal one *)

let rec typ n =
  let n = repr n in
  match n.t with
  | Some t -> t
  | None ->
    let t = compute_typ n.desc in
    n.t <- Some t;
    t

and compute_typ = function
  | IType (t, _) -> t
  | IOr (s1, s2, _) -> Types.cup (typ s1) (typ s2)
  | IAnd (s1, s2, _) -> Types.cap (typ s1) (typ s2)
  | IDiff (s1, s2, _) -> Types.diff (typ s1) (typ s2)
  | ITimes (s1, s2) -> Types.times (typ_node s1) (typ_node s2)
  | IXml (s1, s2) -> Types.xml (typ_node s1) (typ_node s2)
  | IArrow (s1, s2) -> Types.arrow (typ_node s1) (typ_node s2)
  | IOptional (s, _) -> Types.Record.or_absent (typ s)
  | IRecord (o, r, err) ->
    Types.record_fields (o, LabelMap.map (compute_typ_field err) r)
  | ILink _ -> assert false
  | ICapture _
  | IConstant (_, _) ->
    assert false
  | IConcat _
  | IMerge _ ->
    assert false

and compute_typ_field err = function
  | s, None -> typ_node s
  | s, Some _ -> raise (err "Or-else clauses are not allowed in types")

and typ_node n =
  let n = repr n in
  match n.tnode with
  | Some t -> t
  | None ->
    let x = Types.make () in
    n.tnode <- Some x;
    Types.define x (typ n);
    x

let rec pat n =
  let n = repr n in
  if has_no_fv n then
    let t = typ n in
    if Var.Set.is_empty (Types.Subst.vars t) then Patterns.constr t
    else raise_err Typer_Pattern "Polymorphic types are not allowed in patterns"
  else
    match n.p with
    | Some p -> p
    | None ->
      let p = compute_pat n.desc in
      n.p <- Some p;
      p

and compute_pat = function
  | IOr (s1, s2, err) -> (
      try Patterns.cup (pat s1) (pat s2) with
      | Cduce_error.Error (_, (Typer_Pattern, s)) -> raise (err s))
  | IAnd (s1, s2, err) -> (
      try Patterns.cap (pat s1) (pat s2) with
      | Cduce_error.Error (_, (Typer_Pattern, s)) -> raise (err s))
  | IDiff (s1, s2, _) when IdSet.is_empty (fv s2) ->
    let s2 = Types.neg (typ s2) in
    Patterns.cap (pat s1) (Patterns.constr s2)
  | IDiff (_, _, err) -> raise (err "Differences are not allowed in patterns")
  | ITimes (s1, s2) -> Patterns.times (pat_node s1) (pat_node s2)
  | IXml (s1, s2) -> Patterns.xml (pat_node s1) (pat_node s2)
  | IOptional (_, err) ->
    raise (err "Optional fields are not allowed in record patterns")
  | IRecord (o, r, err) ->
    let pats = ref [] in
    let aux l = function
      | s, None ->
        if IdSet.is_empty (fv s) then typ_node s
        else (
          pats := Patterns.record l (pat_node s) :: !pats;
          any_node)
      | s, Some e ->
        if IdSet.is_empty (fv s) then
          raise (err "Or-else clauses are not allowed in types")
        else (
          pats :=
            Patterns.cup (Patterns.record l (pat_node s)) (pat e) :: !pats;
          any_or_absent_node)
    in
    let constr = Types.record_fields (o, LabelMap.mapi aux r) in
    List.fold_left Patterns.cap (Patterns.constr constr) !pats
  (* TODO: can avoid constr when o=true, and all fields have fv *)
  | ICapture x -> Patterns.capture x
  | IConstant (x, c) -> Patterns.constant x c
  | IArrow _ -> raise_err Typer_Pattern "Arrows are not allowed in patterns"
  | IType _
  | ILink _
  | IConcat _
  | IMerge _ ->
    assert false

and pat_node n =
  let n = repr n in
  match n.pnode with
  | Some p -> p
  | None -> (
      let x = Patterns.make (fv n) in
      try
        n.pnode <- Some x;
        Patterns.define x (pat n);
        x
      with
      | exn ->
        n.pnode <- None;
        raise exn)

type regexp =
  | PElem of node
  | PGuard of node
  | PSeq of regexp list
  | PAlt of regexp list
  | PStar of regexp
  | PWeakStar of regexp

let rec nullable = function
  | PElem _ -> false
  | PSeq rl -> List.for_all nullable rl
  | PAlt rl -> List.exists nullable rl
  | PStar _
  | PWeakStar _
  | PGuard _ ->
    true

let eps = PSeq []
let emp = PAlt []
let star x = PStar x
let elem x = PElem x

let seq r1 r2 =
  let r1 =
    match r1 with
    | PSeq l -> l
    | x -> [ x ]
  in
  let r2 =
    match r2 with
    | PSeq l -> l
    | x -> [ x ]
  in
  match r1 @ r2 with
  | [ x ] -> x
  | l -> PSeq l

let alt r1 r2 =
  let r1 =
    match r1 with
    | PAlt l -> l
    | x -> [ x ]
  in
  let r2 =
    match r2 with
    | PAlt l -> l
    | x -> [ x ]
  in
  match r1 @ r2 with
  | [ x ] -> x
  | l -> PAlt l

let rec merge_alt = function
  | PElem p :: PElem q :: l -> merge_alt (PElem (mk_or p q) :: l)
  | r :: l -> r :: merge_alt l
  | [] -> []

(* Works only for types, not patterns, because
   [ (x&Int|_) R' ] is possible *)
let rec simplify_regexp = function
  | PSeq l -> PSeq (List.map simplify_regexp l)
  | PAlt l -> PAlt (merge_alt (List.map simplify_regexp l))
  | PStar r
  | PWeakStar r ->
    PStar (simplify_regexp r)
  | x -> x

let rec print_regexp ppf = function
  | PElem _ -> Format.fprintf ppf "Elem"
  | PGuard _ -> Format.fprintf ppf "Guard"
  | PSeq l -> Format.fprintf ppf "Seq(%a)" print_regexp_list l
  | PAlt l -> Format.fprintf ppf "Alt(%a)" print_regexp_list l
  | PStar r -> Format.fprintf ppf "Star(%a)" print_regexp r
  | PWeakStar r -> Format.fprintf ppf "WStar(%a)" print_regexp r

and print_regexp_list ppf l =
  List.iter (fun x -> Format.fprintf ppf "%a;" print_regexp x) l

let rec remove_regexp r q =
  match r with
  | PElem p -> mk_prod p q
  | PGuard p -> mk_and p q
  | PSeq l -> List.fold_right (fun r a -> remove_regexp r a) l q
  | PAlt rl -> List.fold_left (fun a r -> mk_or a (remove_regexp r q)) iempty rl
  | PStar r ->
    let x = mk_delayed () in
    let res = mk_or x q in
    x.desc <- ILink (remove_regexp_nullable r res iempty);
    res
  | PWeakStar r ->
    let x = mk_delayed () in
    let res = mk_or q x in
    x.desc <- ILink (remove_regexp_nullable r res iempty);
    res

and remove_regexp_nullable r q_nonempty q_empty =
  if nullable r then remove_regexp2 r q_nonempty q_empty
  else remove_regexp r q_nonempty

and remove_regexp2 r q_nonempty q_empty =
  (* Assume r is nullable *)
  if q_nonempty == q_empty then remove_regexp r q_nonempty
  else
    match r with
    | PSeq [] -> q_empty
    | PElem p -> assert false
    | PGuard p -> mk_and p q_empty
    | PSeq (r :: rl) ->
      remove_regexp2 r
        (remove_regexp (PSeq rl) q_nonempty)
        (remove_regexp2 (PSeq rl) q_nonempty q_empty)
    | PAlt rl ->
      List.fold_left
        (fun a r -> mk_or a (remove_regexp_nullable r q_nonempty q_empty))
        iempty rl
    | PStar r ->
      let x = mk_delayed () in
      x.desc <- ILink (remove_regexp_nullable r (mk_or x q_nonempty) iempty);
      mk_or x q_empty
    | PWeakStar r ->
      let x = mk_delayed () in
      x.desc <- ILink (remove_regexp_nullable r (mk_or q_nonempty x) iempty);
      mk_or q_empty x

let pcdata = star (PElem (mk_type Types.(char CharSet.any)))

let mix_regexp regexp =
  let rec aux = function
    | PSeq [] -> eps
    | PElem re -> PElem re
    | PGuard re -> assert false
    | PSeq (r :: rl) -> seq (aux r) (seq pcdata (aux (PSeq rl)))
    | PAlt rl -> PAlt (List.map aux rl)
    | PStar re -> star (seq pcdata (aux re))
    | PWeakStar re -> assert false
  in
  seq pcdata (seq (aux regexp) pcdata)

let cst_nil = Types.(Atom Sequence.nil_atom)
let capture_all vars p = IdSet.fold (fun p x -> mk_and p (mk_capture x)) p vars

let termin b vars p =
  if b then p
  else IdSet.fold (fun p x -> seq p (PGuard (mk_constant x cst_nil))) p vars

type re =
  | Epsilon
  | Empty
  | Elem of node
  | Guard of node
  | Seq of re * re
  | Alt of re * re
  | Star of re
  | WeakStar of re
  | SeqCapture of id * re

let mk_empty = Empty
let mk_epsilon = Epsilon
let mk_elem n = Elem n
let mk_guard n = Guard n
let mk_seq n1 n2 = Seq (n1, n2)
let mk_alt n1 n2 = Alt (n1, n2)
let mk_star n = Star n
let mk_weakstar n = WeakStar n
let mk_seqcapt x n = SeqCapture (x, n)

let mk_str s =
  let j = Encodings.Utf8.end_index s in
  let rec aux i =
    if Encodings.Utf8.equal_index i j then Epsilon
    else
      let c, i = Encodings.Utf8.next s i in
      let t = Types.(char CharSet.(atom (V.mk_int c))) in
      Seq (Elem (mk_type t), aux i)
  in
  aux (Encodings.Utf8.start_index s)

let rec prepare_regexp vars b rvars f = function
  (* - vars: seq variables to be propagated top-down and added
     to each captured element
     - b: below a star ?
     - rvars: seq variables that appear on the right of the regexp
     - f: tail position

     returns the set of seq variable of the regexp minus rvars
     (they have already been terminated if not below a star)
  *)
  | Epsilon -> (PSeq [], IdSet.empty)
  | Empty -> (PAlt [], IdSet.empty)
  | Elem p -> (PElem (capture_all vars p), IdSet.empty)
  | Guard p -> (PGuard p, IdSet.empty)
  | Seq (p1, p2) ->
    let p2, v2 = prepare_regexp vars b rvars f p2 in
    let p1, v1 = prepare_regexp vars b (IdSet.cup rvars v2) false p1 in
    (seq p1 p2, IdSet.cup v1 v2)
  | Alt (p1, p2) ->
    let p1, v1 = prepare_regexp vars b rvars f p1
    and p2, v2 = prepare_regexp vars b rvars f p2 in
    ( alt (termin b (IdSet.diff v2 v1) p1) (termin b (IdSet.diff v1 v2) p2),
      IdSet.cup v1 v2 )
  | Star p ->
    let p, v = prepare_regexp vars true rvars false p in
    (termin b v (PStar p), v)
  | WeakStar p ->
    let p, v = prepare_regexp vars true rvars false p in
    (termin b v (PWeakStar p), v)
  | SeqCapture (x, p) ->
    let vars = if f then vars else IdSet.add x vars in
    let after = IdSet.mem rvars x in
    let rvars = IdSet.add x rvars in
    let p, v = prepare_regexp vars b rvars false p in
    ( (if f then seq (PGuard (mk (ICapture x))) p
       else termin (after || b) (IdSet.singleton x) p),
      if after then v else IdSet.add x v )

let rexp r =
  let r, _ = prepare_regexp IdSet.empty false IdSet.empty true r in
  remove_regexp r (mk_type Types.Sequence.nil_type)

let rexp_simplify ~mix r =
  let r, _ = prepare_regexp IdSet.empty false IdSet.empty true r in
  let r = if mix then mix_regexp r else r in
  let r = simplify_regexp r in
  remove_regexp r (mk_type Types.Sequence.nil_type)

let check_wf p =
  let rec aux q =
    if p == q then raise Exit;
    aux2 q.desc
  and aux2 = function
    | IOr (q1, q2, _)
    | IAnd (q1, q2, _)
    | IDiff (q1, q2, _) ->
      aux q1;
      aux q2
    | ILink q -> aux q
    | _ -> ()
  in
  try
    aux2 p.desc;
    true
  with
  | Exit -> false

module H = Hashtbl.Make (Types)

let rec elim_concat n =
  match n.desc with
  | IConcat (a, b, err) ->
    if n.sid > 0 then raise (err "Ill-formed concatenation loop");
    n.sid <- 1;
    n.desc <- ILink (elim_conc a b err)
  | IMerge (a, b, err) ->
    if n.sid > 0 then raise (err "Ill-formed merge loop");
    n.sid <- 1;
    n.desc <- ILink (elim_merge a b err)
  | _ -> ()

and elim_merge a b err =
  let get_rec t =
    let t = Types.Record.get t in
    List.map
      (fun (l, o, _) ->
         ( o,
           LabelMap.map
             (fun (opt, x) ->
                let x = mk_type x in
                ((if opt then mk_optional x else x), None))
             l ))
      t
  in
  let merge (o1, l1) (o2, l2) =
    mk_record (o1 || o2) (LabelMap.merge (fun _ x -> x) l1 l2)
  in

  (* Problem: repr can loop with ill-formed recursion.
     type t = s + t where s = s | s;; *)
  let a = repr a
  and b = repr b in
  elim_concat a;
  elim_concat b;
  let a = repr a
  and b = repr b in
  match (a.desc, b.desc) with
  | IType (t1, _), IType (t2, _) ->
    if not (Types.subtype t1 Types.Rec.any) then
      raise (err "Left argument of record concatenation is not a record type");
    if not (Types.subtype t2 Types.Rec.any) then
      raise
        (err "Right argument of record concatenation is not a record type");
    mk_type (Types.Record.merge t1 t2)
  | IOr (a1, a2, _), _ -> mk_or (elim_merge a1 b err) (elim_merge a2 b err)
  | _, IOr (b1, b2, _) -> mk_or (elim_merge a b1 err) (elim_merge a b2 err)
  | IRecord (o1, l1, _), IRecord (o2, l2, _) -> merge (o1, l1) (o2, l2)
  | IType (t1, _), IRecord (o2, l2, _) ->
    if not (Types.subtype t1 Types.Rec.any) then
      raise (err "Left argument of record concatenation is not a record type");
    List.fold_left
      (fun accu (o1, l1) -> mk_or accu (merge (o1, l1) (o2, l2)))
      iempty (get_rec t1)
  | IRecord (o1, l1, _), IType (t2, _) ->
    if not (Types.subtype t2 Types.Rec.any) then
      raise
        (err "Right argument of record concatenation is not a record type");
    List.fold_left
      (fun accu (o2, l2) -> mk_or accu (merge (o1, l1) (o2, l2)))
      iempty (get_rec t2)
  | _ -> raise (err "Cannot compute record concatenation")

and elim_conc n q err =
  let mem = ref [] in
  let rec aux n =
    try List.assq n !mem with
    | Not_found ->
      let r = mk_delayed () in
      mem := (n, r) :: !mem;
      let rec aux2 n =
        match n.desc with
        | ILink n' -> aux2 n'
        | IOr (a, b, _) -> mk_or (aux a) (aux b)
        | ITimes (a, b) -> mk_prod a (aux b)
        | IConcat (a, b, _) ->
          elim_concat n;
          aux2 n
        | IType (t, _) -> elim_concat_type t q err
        | _ -> raise (err "Cannot compute concatenation")
      in
      r.desc <- ILink (aux2 n);
      r
  in
  aux n

and elim_concat_type t q err =
  if not Types.(subtype t Sequence.any) then
    raise (err "Left argument of concatenation is not a sequence type");
  let mem = H.create 17 in
  let rec aux t =
    try H.find mem t with
    | Not_found ->
      let n = mk_delayed () in
      H.add mem t n;
      let d =
        List.fold_left
          (fun accu (t1, t2) -> mk_or accu (mk_prod (mk_type t1) (aux t2)))
          (if Types.(AtomSet.contains Sequence.nil_atom (Atom.get t)) then q
           else iempty)
          (Types.Product.get t)
      in
      n.desc <- ILink d;
      n
  in
  aux t

let elim_concats () =
  try
    List.iter elim_concat !concats;
    List.iter (fun n -> n.sid <- 0) !concats;
    concats := []
  with
  | exn ->
    List.iter (fun n -> n.sid <- 0) !concats;
    concats := [];
    raise exn

let link a b = a.desc <- ILink b

let get_ct c =
  let c = repr c in
  match c.desc with
  | ITimes (k, content) -> (
      match (repr k).desc with
      | IType (t, _) -> (t, content)
      | _ -> assert false)
  | _ -> assert false

lang/typing/patterns.ml

open Ident

let get_opt = function
  | Some x -> x
  | None -> assert false

(*
To be sure not to use generic comparison ...
*)
let ( = ) : int -> int -> bool = ( == )
let ( < ) : int -> int -> bool = ( < )
let ( <= ) : int -> int -> bool = ( <= )
let ( <> ) : int -> int -> bool = ( <> )
let compare = 1

(* Syntactic algebra *)
(* Constraint: any node except Constr has fv<>[] ... *)
type d =
  | Constr of Types.t
  | Cup of descr * descr
  | Cap of descr * descr
  | Times of node * node
  | Xml of node * node
  | Record of label * node
  | Capture of id
  | Constant of id * Types.const
  | Dummy

and node = {
  id : int;
  mutable descr : descr;
  accept : Types.Node.t;
  fv : fv;
}

and descr = Types.t * fv * d

let id x = x.id
let descr x = x.descr
let fv x = x.fv
let accept x = Types.internalize x.accept
let printed = ref []
let to_print = ref []

let rec print ppf (a, _, d) =
  match d with
  | Constr t -> Types.Print.print ppf t
  | Cup (p1, p2) -> Format.fprintf ppf "(%a | %a)" print p1 print p2
  | Cap (p1, p2) -> Format.fprintf ppf "(%a & %a)" print p1 print p2
  | Times (n1, n2) ->
      Format.fprintf ppf "(P%i,P%i)" n1.id n2.id;
      to_print := n1 :: n2 :: !to_print
  | Xml (n1, n2) ->
      Format.fprintf ppf "XML(P%i,P%i)" n1.id n2.id;
      to_print := n1 :: n2 :: !to_print
  | Record (l, n) ->
      Format.fprintf ppf "{ %a =  P%i }" Label.print_attr l n.id;
      to_print := n :: !to_print
  | Capture x -> Format.fprintf ppf "%a" Ident.print x
  | Constant (x, c) ->
      Format.fprintf ppf "(%a := %a)" Ident.print x Types.Print.print_const c
  | Dummy -> Format.fprintf ppf "*DUMMY*"

let dump_print ppf =
  while !to_print != [] do
    let p = List.hd !to_print in
    to_print := List.tl !to_print;
    if not (List.mem p.id !printed) then (
      printed := p.id :: !printed;
      Format.fprintf ppf "P%i:=%a\n" p.id print (descr p))
  done

let print ppf d =
  Format.fprintf ppf "%a@\n" print d;
  dump_print ppf

let print_node ppf n =
  Format.fprintf ppf "P%i" n.id;
  to_print := n :: !to_print;
  dump_print ppf

let counter = ref 0
let dummy = (Types.empty, IdSet.empty, Dummy)

let make fv =
  incr counter;
  { id = !counter; descr = dummy; accept = Types.make (); fv }

let define x ((accept, fv, _) as d) =
  (* assert (x.fv = fv); *)
  Types.define x.accept accept;
  x.descr <- d

let cons fv d =
  let q = make fv in
  define q d;
  q

let constr x = (x, IdSet.empty, Constr x)

let cup ((acc1, fv1, _) as x1) ((acc2, fv2, _) as x2) =
  (if not (IdSet.equal fv1 fv2) then
   let x =
     match IdSet.pick (IdSet.diff fv1 fv2) with
     | Some x -> x
     | None -> get_opt (IdSet.pick (IdSet.diff fv2 fv1))
   in
   Cduce_error.(raise_err Typer_Pattern
     ("The capture variable " ^ Ident.to_string x
    ^ "should appear on both sides of this | pattern")));
   Types.cup acc1 acc2, IdSet.cup fv1 fv2, Cup (x1, x2)

let cap ((acc1, fv1, _) as x1) ((acc2, fv2, _) as x2) =
  (if not (IdSet.disjoint fv1 fv2) then
   let x = get_opt (IdSet.pick (IdSet.cap fv1 fv2)) in
   Cduce_error.(raise_err Typer_Pattern
     ("The capture variable " ^ Ident.to_string x
    ^ "cannot appear on both sides of this & pattern")));
  (Types.cap acc1 acc2, IdSet.cup fv1 fv2, Cap (x1, x2))

let times x y =
  (Types.times x.accept y.accept, IdSet.cup x.fv y.fv, Times (x, y))

let xml x y = (Types.xml x.accept y.accept, IdSet.cup x.fv y.fv, Xml (x, y))
let record l x = (Types.record l x.accept, x.fv, Record (l, x))
let capture x = (Types.any, IdSet.singleton x, Capture x)
let constant x c = (Types.any, IdSet.singleton x, Constant (x, c))
let print_node = ref (fun _ _ -> assert false)

module Node = struct
  type t = node

  let compare n1 n2 = n1.id - n2.id
  let equal n1 n2 = n1.id == n2.id
  let hash n = n.id
  let check n = ()
  let dump ppf x = !print_node ppf x
end

(* Pretty-print *)

module Pat = struct
  type t = descr

  let rec compare (_, _, d1) (_, _, d2) =
    if d1 == d2 then 0
    else
      match (d1, d2) with
      | Constr t1, Constr t2 -> Types.compare t1 t2
      | Constr _, _ -> -1
      | _, Constr _ -> 1
      | Cup (x1, y1), Cup (x2, y2)
      | Cap (x1, y1), Cap (x2, y2) ->
          let c = compare x1 x2 in
          if c <> 0 then c else compare y1 y2
      | Cup _, _ -> -1
      | _, Cup _ -> 1
      | Cap _, _ -> -1
      | _, Cap _ -> 1
      | Times (x1, y1), Times (x2, y2)
      | Xml (x1, y1), Xml (x2, y2) ->
          let c = Node.compare x1 x2 in
          if c <> 0 then c else Node.compare y1 y2
      | Times _, _ -> -1
      | _, Times _ -> 1
      | Xml _, _ -> -1
      | _, Xml _ -> 1
      | Record (x1, y1), Record (x2, y2) ->
          let c = Label.compare x1 x2 in
          if c <> 0 then c else Node.compare y1 y2
      | Record _, _ -> -1
      | _, Record _ -> 1
      | Capture x1, Capture x2 -> Id.compare x1 x2
      | Capture _, _ -> -1
      | _, Capture _ -> 1
      | Constant (x1, y1), Constant (x2, y2) ->
          let c = Id.compare x1 x2 in
          if c <> 0 then c else Types.Const.compare y1 y2
      | Constant _, _ -> -1
      | _, Constant _ -> 1
      | Dummy, Dummy -> assert false

  let equal p1 p2 = compare p1 p2 == 0

  let rec hash (_, _, d) =
    match d with
    | Constr t -> 1 + (17 * Types.hash t)
    | Cup (p1, p2) -> 2 + (17 * hash p1) + (257 * hash p2)
    | Cap (p1, p2) -> 3 + (17 * hash p1) + (257 * hash p2)
    | Times (q1, q2) -> 4 + (17 * q1.id) + (257 * q2.id)
    | Xml (q1, q2) -> 5 + (17 * q1.id) + (257 * q2.id)
    | Record (l, q) -> 6 + (17 * Label.hash l) + (257 * q.id)
    | Capture x -> 7 + Id.hash x
    | Constant (x, c) -> 8 + (17 * Id.hash x) + (257 * Types.Const.hash c)
    | Dummy -> assert false

  let check _ = assert false
  let dump _ = assert false
end

module PrintN = struct
  module M = Map.Make (Pat)
  module S = Set.Make (Pat)

  let names = ref M.empty
  let printed = ref S.empty
  let toprint = Queue.create ()
  let id = ref 0

  let rec mark seen ((_, _, d) as p) =
    if M.mem p !names then ()
    else if S.mem p seen then (
      incr id;
      names := M.add p !id !names;
      Queue.add p toprint)
    else
      let seen = S.add p seen in
      match d with
      | Cup (p1, p2)
      | Cap (p1, p2) ->
          mark seen p1;
          mark seen p2
      | Times (q1, q2)
      | Xml (q1, q2) ->
          mark seen q1.descr;
          mark seen q2.descr
      | Record (_, q) -> mark seen q.descr
      | _ -> ()

  let rec print ppf p =
    try
      let i = M.find p !names in
      Format.fprintf ppf "P%i" i
    with
    | Not_found -> real_print ppf p

  and real_print ppf (_, _, d) =
    match d with
    | Constr t -> Types.Print.print ppf t
    | Cup (p1, p2) -> Format.fprintf ppf "(%a | %a)" print p1 print p2
    | Cap (p1, p2) -> Format.fprintf ppf "(%a & %a)" print p1 print p2
    | Times (q1, q2) ->
        Format.fprintf ppf "(%a,%a)" print q1.descr print q2.descr
    | Xml (q1, { descr = _, _, Times (q2, q3) }) ->
        Format.fprintf ppf "<(%a) (%a)>(%a)" print q1.descr print q2.descr print
          q3.descr
    | Xml _ -> assert false
    | Record (l, q) ->
        Format.fprintf ppf "{%a=%a}" Label.print_attr l print q.descr
    | Capture x -> Format.fprintf ppf "%a" Ident.print x
    | Constant (x, c) ->
        Format.fprintf ppf "(%a:=%a)" Ident.print x Types.Print.print_const c
    | Dummy -> assert false

  let print ppf p =
    mark S.empty p;
    print ppf p;
    let first = ref true in
    (try
       while true do
         let p = Queue.pop toprint in
         if not (S.mem p !printed) then (
           printed := S.add p !printed;
           Format.fprintf ppf " %s@ @[%a=%a@]"
             (if !first then (
              first := false;
              "where")
             else "and")
             print p real_print p)
       done
     with
    | Queue.Empty -> ());
    id := 0;
    names := M.empty;
    printed := S.empty

  let print_xs ppf xs =
    Format.fprintf ppf "{";
    let rec aux = function
      | [] -> ()
      | [ x ] -> Ident.print ppf x
      | x :: q ->
          Ident.print ppf x;
          Format.fprintf ppf ",";
          aux q
    in
    aux xs;
    Format.fprintf ppf "}"
end

let () = print_node := fun ppf n -> PrintN.print ppf (descr n)

(* Static semantics *)

let cup_res v1 v2 = Types.Positive.cup [ v1; v2 ]
let empty_res fv = IdMap.constant Types.(Positive.ty empty) fv
let times_res v1 v2 = Types.Positive.times v1 v2

(* Try with a hash-table *)
module MemoFilter = Map.Make (struct
  type t = Types.t * node

  let compare (t1, n1) (t2, n2) =
    if n1.id < n2.id then -1
    else if n1.id > n2.id then 1
    else Types.compare t1 t2
end)

let memo_filter = ref MemoFilter.empty

let rec filter_descr t (_, fv, d) : Types.Positive.v id_map =
  (* TODO: avoid is_empty t when t is not changing (Cap) *)
  if Types.is_empty t then empty_res fv
  else
    match d with
    | Constr _ -> IdMap.empty
    | Cup (((a, _, _) as d1), d2) ->
        IdMap.merge cup_res
          (filter_descr (Types.cap t a) d1)
          (filter_descr (Types.diff t a) d2)
    | Cap (d1, d2) ->
        IdMap.merge cup_res (filter_descr t d1) (filter_descr t d2)
    | Times (p1, p2) -> filter_prod fv p1 p2 t
    | Xml (p1, p2) -> filter_prod ~kind:`XML fv p1 p2 t
    | Record (l, p) -> filter_node (Types.Record.project t l) p
    | Capture c -> IdMap.singleton c (Types.Positive.ty t)
    | Constant (c, cst) ->
        IdMap.singleton c (Types.Positive.ty (Types.constant cst))
    | Dummy -> assert false

and filter_prod ?kind fv p1 p2 t =
  List.fold_left
    (fun accu (d1, d2) ->
      let term =
        IdMap.merge times_res (filter_node d1 p1) (filter_node d2 p2)
      in
      IdMap.merge cup_res accu term)
    (empty_res fv)
    (Types.(Product.normal ?kind t) :> Types.Product.t)

and filter_node t p : Types.Positive.v id_map =
  try MemoFilter.find (t, p) !memo_filter with
  | Not_found ->
      let _, fv, _ = descr p in
      let res = IdMap.map_from_slist (fun _ -> Types.Positive.forward ()) fv in
      memo_filter := MemoFilter.add (t, p) res !memo_filter;
      let r = filter_descr t (descr p) in
      IdMap.collide Types.Positive.define res r;
      r

let filter t p =
  let r = filter_node t p in
  memo_filter := MemoFilter.empty;
  IdMap.map Types.Positive.solve r

let filter_descr t p =
  let r = filter_descr t p in
  memo_filter := MemoFilter.empty;
  IdMap.get (IdMap.map Types.Positive.solve r)

(* Factorization of capture variables and constant patterns *)

module Factorize = struct
  module NodeTypeSet = Set.Make (Custom.Pair (Node) (Types))

  let pi1 ~kind t = Types.Product.pi1 (Types.Product.get ~kind t)
  let pi2 ~kind t = Types.Product.pi2 (Types.Product.get ~kind t)

  (* Note: this is incomplete because of non-atomic constant patterns:
     # debug approx (_,(x:=(1,2))) (1,2);;
     [DEBUG:approx]
     x=(1,2)
  *)
  let rec approx_var seen (a, fv, d) t xs =
    (* assert (Types.subtype t a);
       assert (IdSet.subset xs fv); *)
    if IdSet.is_empty xs || Types.is_empty t then xs
    else
      match d with
      | Cup (((a1, _, _) as p1), p2) ->
          approx_var seen p2 (Types.diff t a1)
            (approx_var seen p1 (Types.cap t a1) xs)
      | Cap (((_, fv1, _) as p1), ((_, fv2, _) as p2)) ->
          IdSet.cup
            (approx_var seen p1 t (IdSet.cap fv1 xs))
            (approx_var seen p2 t (IdSet.cap fv2 xs))
      | Capture _ -> xs
      | Constant (_, c) ->
          if Types.subtype t (Types.constant c) then xs else IdSet.empty
      | Times (q1, q2) ->
          let xs = IdSet.cap xs (IdSet.cap q1.fv q2.fv) in
          IdSet.cap
            (approx_var_node seen q1 (pi1 ~kind:`Normal t) xs)
            (approx_var_node seen q2 (pi2 ~kind:`Normal t) xs)
      | Xml (q1, q2) ->
          let xs = IdSet.cap xs (IdSet.cap q1.fv q2.fv) in
          IdSet.cap
            (approx_var_node seen q1 (pi1 ~kind:`XML t) xs)
            (approx_var_node seen q2 (pi2 ~kind:`XML t) xs)
      | Record _ -> IdSet.empty
      | _ -> assert false

  and approx_var_node seen q t xs =
    if NodeTypeSet.mem (q, t) seen then xs
    else approx_var (NodeTypeSet.add (q, t) seen) q.descr t xs

  (* Obviously not complete ! *)
  let rec approx_nil seen (a, fv, d) t xs =
    assert (Types.subtype t a);
    assert (IdSet.subset xs fv);
    if IdSet.is_empty xs || Types.is_empty t then xs
    else
      match d with
      | Cup (((a1, _, _) as p1), p2) ->
          approx_nil seen p2 (Types.diff t a1)
            (approx_nil seen p1 (Types.cap t a1) xs)
      | Cap (((_, fv1, _) as p1), ((_, fv2, _) as p2)) ->
          IdSet.cup
            (approx_nil seen p1 t (IdSet.cap fv1 xs))
            (approx_nil seen p2 t (IdSet.cap fv2 xs))
      | Constant (_, c) when Types.(Const.equal c Sequence.nil_cst) -> xs
      | Times (q1, q2) ->
          let xs = IdSet.cap q2.fv (IdSet.diff xs q1.fv) in
          approx_nil_node seen q2 (pi2 ~kind:`Normal t) xs
      | _ -> IdSet.empty

  and approx_nil_node seen q t xs =
    if NodeTypeSet.mem (q, t) seen then xs
    else approx_nil (NodeTypeSet.add (q, t) seen) q.descr t xs

  (*
  let cst ((a,_,_) as p) t xs =
    if IdSet.is_empty xs then IdMap.empty
    else
      let rec aux accu (x,t) =
	if (IdSet.mem xs x) then
	  match Sample.single_opt (Types.descr t) with
	    | Some c -> (x,c)::accu
	    | None -> accu
	else accu in
      let t = Types.cap t a in
      IdMap.from_list_disj (List.fold_left aux [] (filter_descr t p))
*)

  let var ((a, _, _) as p) t = approx_var NodeTypeSet.empty p (Types.cap t a)
  let nil ((a, _, _) as p) t = approx_nil NodeTypeSet.empty p (Types.cap t a)
end

(* Normal forms for patterns and compilation *)

let min (a : int) (b : int) = if a < b then a else b
let any_basic = Types.Record.or_absent Types.non_constructed

let rec first_label (acc, fv, d) =
  if Types.is_empty acc then Label.dummy
  else
    match d with
    | Constr t -> Types.Record.first_label t
    | Cap (p, q) -> Label.min (first_label p) (first_label q)
    | Cup (((acc1, _, _) as p), q) -> Label.min (first_label p) (first_label q)
    | Record (l, p) -> l
    | _ -> Label.dummy

module Normal = struct
  type source =
    | SCatch
    | SConst of Types.const

  type result = source id_map

  let compare_source s1 s2 =
    if s1 == s2 then 0
    else
      match (s1, s2) with
      | SCatch, _ -> -1
      | _, SCatch -> 1
      | SConst c1, SConst c2 -> Types.Const.compare c1 c2

  (*
  let hash_source = function
    | SCatch -> 1
    | SConst c -> Types.Const.hash c
*)

  let compare_result r1 r2 = IdMap.compare compare_source r1 r2

  module ResultMap = Map.Make (struct
    type t = result

    let compare = compare_result
  end)

  module NodeSet = SortedList.Make (Node)

  module Nnf = struct
    include Custom.Dummy

    type t = NodeSet.t * Types.t * IdSet.t (* pl,t;   t <= \accept{pl} *)

    let check (pl, t, xs) =
      List.iter
        (fun p -> assert (Types.subtype t (Types.descr p.accept)))
        (NodeSet.get pl)

    let print ppf (pl, t, xs) =
      Format.fprintf ppf "@[(pl=%a;t=%a;xs=%a)@]" NodeSet.dump pl
        Types.Print.print t IdSet.dump xs

    let compare (l1, t1, xs1) (l2, t2, xs2) =
      let c = NodeSet.compare l1 l2 in
      if c <> 0 then c
      else
        let c = Types.compare t1 t2 in
        if c <> 0 then c else IdSet.compare xs1 xs2

    let hash (l, t, xs) =
      NodeSet.hash l + (17 * Types.hash t) + (257 * IdSet.hash xs)

    let equal x y = compare x y == 0

    let first_label (pl, t, xs) =
      NodeSet.fold
        (fun l p -> Label.min l (first_label (descr p)))
        (Types.Record.first_label t)
        pl
  end

  module NProd = struct
    type t = result * Nnf.t * Nnf.t

    let compare (r1, x1, y1) (r2, x2, y2) =
      let c = compare_result r1 r2 in
      if c <> 0 then c
      else
        let c = Nnf.compare x1 x2 in
        if c <> 0 then c else Nnf.compare y1 y2
  end

  module NLineProd = Set.Make (NProd)

  type record =
    | RecNolabel of result option * result option
    | RecLabel of label * NLineProd.t

  type t = {
    nprod : NLineProd.t;
    nxml : NLineProd.t;
    nrecord : record;
  }

  let fus = IdMap.union_disj

  let nempty lab =
    {
      nprod = NLineProd.empty;
      nxml = NLineProd.empty;
      nrecord =
        (match lab with
        | Some l -> RecLabel (l, NLineProd.empty)
        | None -> RecNolabel (None, None));
    }

  let dummy = nempty None

  let ncup nf1 nf2 =
    {
      nprod = NLineProd.union nf1.nprod nf2.nprod;
      nxml = NLineProd.union nf1.nxml nf2.nxml;
      nrecord =
        (match (nf1.nrecord, nf2.nrecord) with
        | RecLabel (l1, r1), RecLabel (l2, r2) ->
            (* assert (l1 = l2); *)
            RecLabel (l1, NLineProd.union r1 r2)
        | RecNolabel (x1, y1), RecNolabel (x2, y2) ->
            RecNolabel
              ((if x1 == None then x2 else x1), if y1 == None then y2 else y1)
        | _ -> assert false);
    }

  let double_fold_prod f l1 l2 =
    NLineProd.fold
      (fun x1 accu -> NLineProd.fold (fun x2 accu -> f accu x1 x2) l2 accu)
      l1 NLineProd.empty

  let ncap nf1 nf2 =
    let prod
        accu
        (res1, (pl1, t1, xs1), (ql1, s1, ys1))
        (res2, (pl2, t2, xs2), (ql2, s2, ys2)) =
      let t = Types.cap t1 t2 in
      if Types.is_empty t then accu
      else
        let s = Types.cap s1 s2 in
        if Types.is_empty s then accu
        else
          NLineProd.add
            ( fus res1 res2,
              (NodeSet.cup pl1 pl2, t, IdSet.cup xs1 xs2),
              (NodeSet.cup ql1 ql2, s, IdSet.cup ys1 ys2) )
            accu
    in
    let record r1 r2 =
      match (r1, r2) with
      | RecLabel (l1, r1), RecLabel (l2, r2) ->
          (* assert (l1 = l2); *)
          RecLabel (l1, double_fold_prod prod r1 r2)
      | RecNolabel (x1, y1), RecNolabel (x2, y2) ->
          let x =
            match (x1, x2) with
            | Some res1, Some res2 -> Some (fus res1 res2)
            | _ -> None
          and y =
            match (y1, y2) with
            | Some res1, Some res2 -> Some (fus res1 res2)
            | _ -> None
          in
          RecNolabel (x, y)
      | _ -> assert false
    in
    {
      nprod = double_fold_prod prod nf1.nprod nf2.nprod;
      nxml = double_fold_prod prod nf1.nxml nf2.nxml;
      nrecord = record nf1.nrecord nf2.nrecord;
    }

  let nnode p xs = (NodeSet.singleton p, Types.descr p.accept, xs)
  let nc t = (NodeSet.empty, t, IdSet.empty)
  let ncany = nc Types.any
  let ncany_abs = nc Types.Record.any_or_absent
  let empty_res = IdMap.empty
  let single_prod src p q = NLineProd.singleton (src, p, q)

  let ntimes lab acc p q xs =
    let xsp = IdSet.cap xs p.fv
    and xsq = IdSet.cap xs q.fv in
    {
      (nempty lab) with
      nprod = single_prod empty_res (nnode p xsp) (nnode q xsq);
    }

  let nxml lab acc p q xs =
    let xsp = IdSet.cap xs p.fv
    and xsq = IdSet.cap xs q.fv in
    {
      (nempty lab) with
      nxml = single_prod empty_res (nnode p xsp) (nnode q xsq);
    }

  let nrecord (lab : Label.t option) acc (l : Label.t) p xs =
    let label = get_opt lab in
    (* Format.fprintf
       Format.std_formatter "label=%a l=%a@."
       Label.print label Label.print l; *)
    assert (Label.compare label l <= 0);
    let lft, rgt =
      if Label.equal l label then (nnode p xs, ncany)
      else (ncany_abs, nnode (cons p.fv (record l p)) xs)
    in
    {
      (nempty lab) with
      nrecord = RecLabel (label, single_prod empty_res lft rgt);
    }

  let nconstr lab t =
    let aux l =
      List.fold_left
        (fun accu (t1, t2) -> NLineProd.add (empty_res, nc t1, nc t2) accu)
        NLineProd.empty l
    in
    let record =
      match lab with
      | None ->
          let x, y = Types.Record.empty_cases t in
          RecNolabel
            ( (if x then Some empty_res else None),
              if y then Some empty_res else None )
      | Some l -> RecLabel (l, aux (Types.Record.split_normal t l))
    in
    {
      nprod = aux (Types.Product.clean_normal (Types.Product.normal t));
      nxml =
        aux (Types.Product.clean_normal (Types.Product.normal ~kind:`XML t));
      nrecord = record;
    }

  let nany lab res =
    {
      nprod = single_prod res ncany ncany;
      nxml = single_prod res ncany ncany;
      nrecord =
        (match lab with
        | None -> RecNolabel (Some res, Some res)
        | Some lab -> RecLabel (lab, single_prod res ncany_abs ncany));
    }

  let nconstant lab x c = nany lab (IdMap.singleton x (SConst c))
  let ncapture lab x = nany lab (IdMap.singleton x SCatch)

  let rec nnormal lab (acc, fv, d) xs =
    let xs = IdSet.cap xs fv in
    if Types.is_empty acc then nempty lab
    else if IdSet.is_empty xs then nconstr lab acc
    else
      match d with
      | Constr t -> assert false
      | Cap (p, q) -> ncap (nnormal lab p xs) (nnormal lab q xs)
      | Cup (((acc1, _, _) as p), q) ->
          ncup (nnormal lab p xs)
            (ncap (nnormal lab q xs) (nconstr lab (Types.neg acc1)))
      | Times (p, q) -> ntimes lab acc p q xs
      | Xml (p, q) -> nxml lab acc p q xs
      | Capture x -> ncapture lab x
      | Constant (x, c) -> nconstant lab x c
      | Record (l, p) -> nrecord lab acc l p xs
      | Dummy -> assert false

  (*TODO: when an operand of Cap has its first_label > lab,
    directly shift it*)

  let facto f t xs pl =
    NodeSet.fold
      (fun vs p -> IdSet.cup vs (f (descr p) t (IdSet.cap (fv p) xs)))
      IdSet.empty pl

  let factorize t0 (pl, t, xs) =
    let t0 = if Types.subtype t t0 then t else Types.cap t t0 in
    let vs_var = facto Factorize.var t0 xs pl in
    let xs = IdSet.diff xs vs_var in
    let vs_nil = facto Factorize.nil t0 xs pl in
    let xs = IdSet.diff xs vs_nil in
    (vs_var, vs_nil, (pl, t, xs))

  let normal l t pl xs =
    NodeSet.fold (fun a p -> ncap a (nnormal l (descr p) xs)) (nconstr l t) pl

  let nnf lab t0 (pl, t, xs) =
    (*    assert (not (Types.disjoint t t0)); *)
    let t = if Types.subtype t t0 then t else Types.cap t t0 in
    normal lab t pl xs

  let basic_tests f ((pl, t, xs) : NodeSet.t * _ * _) =
    let rec aux more s accu t res = function
      (* Invariant: t and s disjoint, t not empty *)
      | [] ->
          let accu =
            try
              let t' = ResultMap.find res accu in
              ResultMap.add res (Types.cup t t') accu
            with
            | Not_found -> ResultMap.add res t accu
          in
          cont (Types.cup t s) accu more
      | (tp, xp, d) :: r -> (
          if IdSet.disjoint xp xs then
            aux_check more s accu (Types.cap t tp) res r
          else
            match d with
            | Cup (p1, p2) ->
                aux ((t, res, p2 :: r) :: more) s accu t res (p1 :: r)
            | Cap (p1, p2) -> aux more s accu t res (p1 :: p2 :: r)
            | Capture x -> aux more s accu t (IdMap.add x SCatch res) r
            | Constant (x, c) ->
                aux more s accu t (IdMap.add x (SConst c) res) r
            | _ -> cont s accu more)
    and aux_check more s accu t res pl =
      if Types.is_empty t then cont s accu more else aux more s accu t res pl
    and cont s accu = function
      | [] -> ResultMap.iter f accu
      | (t, res, pl) :: tl -> aux_check tl s accu (Types.diff t s) res pl
    in
    aux_check [] Types.empty ResultMap.empty (Types.cap t any_basic) IdMap.empty
      (List.map descr (pl :> Node.t list))

  (*
  let prod_tests (pl,t,xs) =
    let rec aux accu q1 q2 res = function
      | [] -> (res,q1,q2) :: accu
      | (tp,xp,d) :: r ->
	  if (IdSet.disjoint xp xs)
	  then aux_check accu q1 q2 res tp r
	  else match d with
	    | Cup (p1,p2) -> aux (aux accu q1 q2 res (p2::r)) q1 q2 res (p1::r)
	    | Cap (p1,p2) -> aux accu q1 q2 res (p1 :: p2 :: r)
	    | Capture x -> aux accu q1 q2 (IdMap.add x SCatch res) r
	    | Constant (x,c) -> aux accu q1 q2 (IdMap.add x (SConst c) res) r
	    | Times (p1,p2) ->
		let (pl1,t1,xs1) = q1 and (pl2,t2,xs2) = q2 in
		let t1 = Types.cap t1 (Types.descr (accept p1)) in
		if Types.is_empty t1 then accu
		else let t2 = Types.cap t2 (Types.descr (accept p2)) in
		if Types.is_empty t2 then accu
		else
		  let q1 =
		    let xs1' = IdSet.cap (fv p1) xs in
		    if IdSet.is_empty xs1' then (pl1,t1,xs1)
		    else (NodeSet.add p1 pl1, t1, IdSet.cup xs1 xs1')
		  and q2 =
		    let xs2' = IdSet.cap (fv p2) xs in
		    if IdSet.is_empty xs2' then (pl2,t2,xs2)
		    else (NodeSet.add p2 pl2, t2, IdSet.cup xs2 xs2')
		  in
		  aux accu q1 q2 res r
	    | _ -> accu
    and aux_check accu q1 q2 res t r =
      List.fold_left
	(fun accu (t1',t2') ->
	   let (pl1,t1,xs1) = q1 and (pl2,t2,xs2) = q2 in
	   let t1 = Types.cap t1 t1' in
	   if Types.is_empty t1 then accu
	   else let t2 = Types.cap t2 t2' in
	   if Types.is_empty t2 then accu
	   else aux accu (pl1,t1,xs1) (pl2,t2,xs2) res r)
	accu (Types.Product.clean_normal (Types.Product.normal t))
    in
    aux_check [] ncany ncany IdMap.empty t (List.map descr pl)
*)
end

module Compile = struct
  open Auto_pat

  type return_code =
    Types.t * int * (* accepted type, arity *)
    int id_map option array

  type interface =
    [ `Result of int
    | `Switch of interface * interface
    | `None
    ]

  type dispatcher = {
    id : int;
    t : Types.t;
    pl : Normal.Nnf.t array;
    label : label option;
    interface : interface;
    codes : return_code array;
    state : state;
  }

  let dispatcher_of_state = Hashtbl.create 1024

  let equal_array f a1 a2 =
    let rec aux i = i < 0 || (f a1.(i) a2.(i) && aux (i - 1)) in
    let l1 = Array.length a1
    and l2 = Array.length a2 in
    l1 == l2 && aux (l1 - 1)

  let array_for_all f a =
    let rec aux f a i = i < 0 || (f a.(i) && aux f a (pred i)) in
    aux f a (Array.length a - 1)

  let array_for_all_i f a =
    let rec aux f a i = i < 0 || (f i a.(i) && aux f a (pred i)) in
    aux f a (Array.length a - 1)

  let equal_source s1 s2 =
    s1 == s2
    ||
    match (s1, s2) with
    | Const x, Const y -> Types.Const.equal x y
    | Stack x, Stack y -> x == y
    | Recompose (x1, x2), Recompose (y1, y2) -> x1 == y1 && x2 == y2
    | _ -> false

  let equal_result (r1, s1, l1) (r2, s2, l2) =
    r1 == r2 && equal_array equal_source s1 s2 && l1 == l2

  let equal_result_dispatch d1 d2 =
    d1 == d2
    ||
    match (d1, d2) with
    | Dispatch (d1, a1), Dispatch (d2, a2) ->
        d1 == d2 && equal_array equal_result a1 a2
    | TailCall d1, TailCall d2 -> d1 == d2
    | Ignore a1, Ignore a2 -> equal_result a1 a2
    | _ -> false

  let immediate_res basic prod xml record =
    let res : result option ref = ref None in
    let chk = function
      | Catch
      | Const _ ->
          true
      | _ -> false
    in
    let f ((_, ret, _) as r) =
      match !res with
      | Some r0 when equal_result r r0 -> ()
      | None when array_for_all chk ret -> res := Some r
      | _ -> raise Exit
    in
    (match basic with
    | [ (_, r) ] -> f r
    | [] -> ()
    | _ -> raise Exit);
    (match prod with
    | Ignore (Ignore r) -> f r
    | Impossible -> ()
    | _ -> raise Exit);
    (match xml with
    | Ignore (Ignore r) -> f r
    | Impossible -> ()
    | _ -> raise Exit);
    (match record with
    | None -> ()
    | Some (RecLabel (_, Ignore (Ignore r))) -> f r
    | Some (RecNolabel (Some r1, Some r2)) ->
        f r1;
        f r2
    | _ -> raise Exit);
    match !res with
    | Some r -> r
    | None -> raise Exit

  let split_kind basic prod xml record =
    {
      basic;
      atoms =
        AtomSet.mk_map (List.map (fun (t, r) -> (Types.Atom.get t, r)) basic);
      chars =
        CharSet.mk_map (List.map (fun (t, r) -> (Types.Char.get t, r)) basic);
      prod;
      xml;
      record;
    }

  let combine_kind basic prod xml record =
    try AIgnore (immediate_res basic prod xml record) with
    | Exit -> AKind (split_kind basic prod xml record)

  let combine f (disp, act) =
    if Array.length act == 0 then Impossible
    else if
      array_for_all (fun (_, ar, _) -> ar == 0) disp.codes
      && array_for_all (f act.(0)) act
    then Ignore act.(0)
    else Dispatch (disp.state, act)

  let detect_tail_call f = function
    | Dispatch (disp, branches) when array_for_all_i f branches -> TailCall disp
    | x -> x

  let detect_right_tail_call =
    detect_tail_call (fun i (code, ret, _) ->
        i == code
        &&
        let ar = Array.length ret in
        array_for_all_i
          (fun pos -> function
            | Stack j when pos + j == ar -> true
            | _ -> false)
          ret)

  let detect_left_tail_call =
    detect_tail_call (fun i -> function
      | Ignore (code, ret, _) when i == code ->
          let ar = Array.length ret in
          array_for_all_i
            (fun pos -> function
              | Stack j when pos + j == ar -> true
              | _ -> false)
            ret
      | _ -> false)

  let cur_id = ref 0

  module NfMap = Map.Make (Normal.Nnf)
  module NfSet = Set.Make (Normal.Nnf)
  module DispMap = Map.Make (Custom.Pair (Types) (Custom.Array (Normal.Nnf)))

  (* Try with a hash-table ! *)

  let dispatchers = ref DispMap.empty

  let rec print_iface ppf = function
    | `Result i -> Format.fprintf ppf "Result(%i)" i
    | `Switch (yes, no) ->
        Format.fprintf ppf "Switch(%a,%a)" print_iface yes print_iface no
    | `None -> Format.fprintf ppf "None"

  let dump_disp disp =
    let ppf = Format.std_formatter in
    Format.fprintf ppf "Dispatcher t=%a@." Types.Print.print disp.t;
    Array.iter
      (fun p -> Format.fprintf ppf "  pat %a@." Normal.Nnf.print p)
      disp.pl

  let first_lab t reqs =
    let aux l req = Label.min l (Normal.Nnf.first_label req) in
    let lab = Array.fold_left aux (Types.Record.first_label t) reqs in
    if lab == Label.dummy then None else Some lab

  let dummy_actions = AIgnore (-1, [||], -1)
  let compute_actions = ref (fun _ -> assert false)

  let dispatcher t pl : dispatcher =
    try DispMap.find (t, pl) !dispatchers with
    | Not_found ->
        let lab = first_lab t pl in
        let nb = ref 0 in
        let codes = ref [] in
        let rec aux t arity i accu =
          if i == Array.length pl then (
            incr nb;
            let r = Array.of_list (List.rev accu) in
            codes := (t, arity, r) :: !codes;
            `Result (!nb - 1))
          else
            let _, tp, v = pl.(i) in
            let a1 = Types.cap t tp in
            if Types.is_empty a1 then
              `Switch (`None, aux t arity (i + 1) (None :: accu))
            else
              let a2 = Types.diff t tp in
              let accu' = Some (IdMap.num arity v) :: accu in
              if Types.is_empty a2 then
                `Switch (aux t (arity + IdSet.length v) (i + 1) accu', `None)
              else
                `Switch
                  ( aux a1 (arity + IdSet.length v) (i + 1) accu',
                    aux a2 arity (i + 1) (None :: accu) )
          (* Unopt version:
             `Switch
               (
                aux (Types.cap t tp) (arity + (IdSet.length v)) (i+1) accu',
                aux (Types.diff t tp) arity (i+1) accu
               )
          *)
        in

        let iface = if Types.is_empty t then `None else aux t 0 0 [] in
        let codes = Array.of_list (List.rev !codes) in
        let state =
          {
            uid = !cur_id;
            arity = Array.map (fun (_, ar, _) -> ar) codes;
            actions = dummy_actions;
            fail_code = -1;
            expected_type = "";
          }
        in
        let disp =
          { id = !cur_id; t; label = lab; pl; interface = iface; codes; state }
        in
        incr cur_id;
        Hashtbl.add dispatcher_of_state state.uid disp;
        dispatchers := DispMap.add (t, pl) disp !dispatchers;
        !compute_actions disp;
        disp

  let find_code d a =
    let rec aux i = function
      | `Result code -> code
      | `None ->
          Format.fprintf Format.std_formatter "IFACE=%a@." print_iface
            d.interface;
          for i = 0 to Array.length a - 1 do
            Format.fprintf Format.std_formatter "a.(%i)=%b@." i (a.(i) != None)
          done;
          assert false
      | `Switch (yes, _) when a.(i) != None -> aux (i + 1) yes
      | `Switch (_, no) -> aux (i + 1) no
    in
    aux 0 d.interface

  let create_result pl =
    let aux x accu =
      match x with
      | Some b -> b @ accu
      | None -> accu
    in
    Array.of_list (Array.fold_right aux pl [])

  let return disp pl f ar =
    let aux = function
      | x :: _ -> Some (f x)
      | [] -> None
    in
    let final = Array.map aux pl in
    (find_code disp final, create_result final, ar)

  let conv_source = function
    | Normal.SCatch -> Catch
    | Normal.SConst c -> Const c

  let return_basic disp selected =
    let aux_final res = IdMap.map_to_list conv_source res in
    return disp selected aux_final 0

  (* let print_idset ppf s =
       let s = String.concat "," (List.map (fun x -> Ident.to_string x) s) in
       Format.fprintf ppf "{%s}" s
     let print_idmap ppf s =
       print_idset ppf (IdMap.domain s) *)

  let merge_res_prod ofs1 ofs2 (lvars, lnils, lres) (rvars, rnils, rres) extra =
    let lres =
      IdMap.union_disj
        (IdMap.map (fun i -> Stack (ofs1 + ofs2 - i)) lres)
        (IdMap.union_disj
           (IdMap.constant Left lvars)
           (IdMap.constant Nil lnils))
    in
    let rres =
      IdMap.union_disj
        (IdMap.map (fun i -> Stack (ofs2 - i)) rres)
        (IdMap.union_disj
           (IdMap.constant Right rvars)
           (IdMap.constant Nil rnils))
    in
    let sub =
      IdMap.merge
        (fun l r ->
          match (l, r) with
          | Left, Right -> Catch
          | _ ->
              let l =
                match l with
                | Left -> -1
                | Nil -> -2
                | Stack i -> i
                | _ -> assert false
              in
              let r =
                match r with
                | Right -> -1
                | Nil -> -2
                | Stack i -> i
                | _ -> assert false
              in
              Recompose (l, r))
        lres rres
    in
    IdMap.map_to_list
      (fun x -> x)
      (IdMap.union_disj sub (IdMap.map conv_source extra))

  module TypeList = SortedList.Make (Types)

  let dispatch_basic disp : (Types.t * result) list =
    let tests =
      let accu = ref [] in
      let aux i res t = accu := (t, [ (i, res) ]) :: !accu in
      Array.iteri (fun i p -> Normal.basic_tests (aux i) p) disp.pl;
      TypeList.Map.get (TypeList.Map.from_list ( @ ) !accu)
    in

    let t = Types.cap any_basic disp.t in
    let accu = ref [] in
    let rec aux (success : (int * Normal.result) list) t l =
      if Types.non_empty t then
        match l with
        | [] ->
            let selected = Array.make (Array.length disp.pl) [] in
            let add (i, res) = selected.(i) <- res :: selected.(i) in
            List.iter add success;
            accu := (t, return_basic disp selected) :: !accu
        | (ty, i) :: rem ->
            aux (i @ success) (Types.cap t ty) rem;
            aux success (Types.diff t ty) rem
    in
    aux [] t tests;
    !accu

  let get_tests facto pl f t d post =
    let pl = Array.map (List.map f) pl in

    let factorized = ref NfMap.empty in
    (* Collect all subrequests *)
    let aux reqs (req, _) =
      let ((_, _, ((_, tr, xs) as r')) as req') =
        if facto then Normal.factorize t req else IdSet.(empty, empty, req)
      in
      factorized := NfMap.add req req' !factorized;

      if IdSet.is_empty xs && Types.subtype t tr then reqs
      else NfSet.add r' reqs
    in
    let reqs = Array.fold_left (List.fold_left aux) NfSet.empty pl in
    let reqs = Array.of_list (NfSet.elements reqs) in

    (* Map subrequest -> idx in reqs *)
    let idx = ref NfMap.empty in
    Array.iteri (fun i req -> idx := NfMap.add req i !idx) reqs;
    let idx = !idx in

    (* Build dispatcher *)
    (*    if Array.length reqs = 0 then print_endline "NOREQ!"; *)
    let disp =
      dispatcher
        (if Array.length reqs = 0 then Types.Record.any_or_absent else t)
        reqs
    in

    (* Build continuation *)
    let result (t, ar, m) =
      let get (req, info) a =
        let var, nil, r' =
          try NfMap.find req !factorized with
          | Not_found -> assert false
        in
        try
          let i = NfMap.find r' idx in
          match m.(i) with
          | Some res -> ((var, nil, res), info) :: a
          | _ -> a
        with
        | Not_found -> ((var, nil, IdMap.empty), info) :: a
      in
      let pl = Array.map (fun l -> List.fold_right get l []) pl in
      d t ar pl
    in
    let res = Array.map result disp.codes in
    post (disp, res)

  let add_factorized disp rhs =
    let result ((code, srcs, pop) as r) =
      match rhs.(code) with
      | Fail -> r
      | Match (_, (var, nil, xs, _)) ->
          let pos = ref (-1) in
          let var x =
            if IdSet.mem var x then Catch
            else if IdSet.mem nil x then Nil
            else (
              incr pos;
              srcs.(!pos))
          in
          let srcs' = Array.of_list (List.map var (IdSet.get xs)) in
          assert (succ !pos = Array.length srcs);
          (code, srcs', pop)
    in
    let dispatch1 = function
      | Dispatch (s, a) -> Dispatch (s, Array.map result a)
      | TailCall s ->
          let f code (_, ar, _) =
            let srcs = Array.init ar (fun i -> Stack (ar - i)) in
            result (code, srcs, ar)
          in
          Dispatch (s, Array.mapi f disp.codes)
      | Ignore r -> Ignore (result r)
      | Impossible -> Impossible
    in
    let dispatch2 = function
      | Dispatch (s, a) -> Dispatch (s, Array.map dispatch1 a)
      | TailCall s ->
          let f code (_, ar, _) =
            let srcs = Array.init ar (fun i -> Stack (ar - i)) in
            Ignore (result (code, srcs, ar))
          in
          Dispatch (s, Array.mapi f disp.codes)
      | Ignore r -> Ignore (dispatch1 r)
      | Impossible -> Impossible
    in
    let state = disp.state in
    let actions =
      match state.actions with
      | AIgnore r -> AIgnore (result r)
      | AKind k ->
          AKind
            {
              basic = List.map (fun (t, r) -> (t, result r)) k.basic;
              atoms = AtomSet.map_map result k.atoms;
              chars = CharSet.map_map result k.chars;
              prod = dispatch2 k.prod;
              xml = dispatch2 k.xml;
              record =
                (match k.record with
                | None -> None
                | Some (RecLabel (l, x)) -> Some (RecLabel (l, dispatch2 x))
                | Some (RecNolabel (x, y)) ->
                    Some
                      (RecNolabel
                         ( (match x with
                           | None -> None
                           | Some r -> Some (result r)),
                           match y with
                           | None -> None
                           | Some r -> Some (result r) )));
            }
    in
    { state with actions }

  let make_branches t brs =
    let t0 = ref t in
    let aux (p, e) =
      let xs = fv p in
      let tp = Types.descr (accept p) in
      let nnf = (Normal.NodeSet.singleton p, Types.cap !t0 tp, xs) in
      t0 := Types.diff !t0 tp;
      [ (nnf, (xs, e)) ]
    in
    let has_facto = ref false in
    let res _ _ pl =
      let aux r = function
        | [ ((var, nil, res), (xs, e)) ] ->
            assert (r == Fail);
            let i = ref 0 in
            IdSet.iter
              (fun x ->
                if IdSet.mem var x || IdSet.mem nil x then has_facto := true
                else (
                  assert (IdMap.assoc x res = !i);
                  incr i))
              xs;
            Match (IdSet.length xs, (var, nil, xs, e))
        | [] -> r
        | _ -> assert false
      in
      Array.fold_left aux Fail pl
    in
    (* Format.fprintf Format.std_formatter
       "make_branches t=%a   #branches=%i@." Types.Print.print t (List.length brs); *)
    let pl = Array.map aux (Array.of_list brs) in
    let disp, rhs = get_tests true pl (fun x -> x) t res (fun x -> x) in
    let state = if !has_facto then add_factorized disp rhs else disp.state in
    ( state,
      Array.map
        (function
          | Match (n, (_, _, _, e)) -> Match (n, e)
          | Fail -> Fail)
        rhs )

  let rec dispatch_prod0 disp t pl =
    get_tests true pl
      (fun (res, p, q) -> (p, (res, q)))
      (Types.Product.pi1 t) (dispatch_prod1 disp t)
      (fun x -> detect_left_tail_call (combine equal_result_dispatch x))

  and dispatch_prod1 disp t t1 ar1 pl =
    get_tests true pl
      (fun (ret1, (res, q)) -> (q, (ret1, res)))
      (Types.Product.pi2_restricted t1 t)
      (dispatch_prod2 disp ar1)
      (fun x -> detect_right_tail_call (combine equal_result x))

  and dispatch_prod2 disp ar1 t2 ar2 pl =
    let aux_final (ret2, (ret1, res)) = merge_res_prod ar1 ar2 ret1 ret2 res in
    return disp pl aux_final (ar1 + ar2)

  let dispatch_prod disp pl =
    let t = Types.Product.get disp.t in
    if t == [] then Impossible
    else
      dispatch_prod0 disp t
        (Array.map (fun p -> Normal.NLineProd.elements p.Normal.nprod) pl)
  (*    dispatch_prod0 disp t (Array.map Normal.prod_tests disp.pl)  *)

  let dispatch_xml disp pl =
    let t = Types.Product.get ~kind:`XML disp.t in
    if t == [] then Impossible
    else
      dispatch_prod0 disp t
        (Array.map (fun p -> Normal.NLineProd.elements p.Normal.nxml) pl)

  let dispatch_record disp pl : record option =
    let t = disp.t in
    if not (Types.Record.has_record t) then None
    else
      match disp.label with
      | None ->
          let some, none = Types.Record.empty_cases t in
          let some =
            if some then
              let pl =
                Array.map
                  (fun p ->
                    match p.Normal.nrecord with
                    | Normal.RecNolabel (Some x, _) -> [ x ]
                    | Normal.RecNolabel (None, _) -> []
                    | _ -> assert false)
                  pl
              in
              Some (return disp pl (IdMap.map_to_list conv_source) 0)
            else None
          in
          let none =
            if none then
              let pl =
                Array.map
                  (fun p ->
                    match p.Normal.nrecord with
                    | Normal.RecNolabel (_, Some x) -> [ x ]
                    | Normal.RecNolabel (_, None) -> []
                    | _ -> assert false)
                  pl
              in
              Some (return disp pl (IdMap.map_to_list conv_source) 0)
            else None
          in
          Some (RecNolabel (some, none))
      | Some lab ->
          let t = Types.Record.split t lab in
          let pl =
            Array.map
              (fun p ->
                match p.Normal.nrecord with
                | Normal.RecLabel (_, l) -> Normal.NLineProd.elements l
                | _ -> assert false)
              pl
          in
          Some (RecLabel (lab, dispatch_prod0 disp t pl))

  (*
  let iter_disp_disp f g = function
    | Dispatch (d,a) -> f d; Array.iter g a
    | TailCall d -> f d
    | Ignore a -> g a
    | Impossible -> ()

  let iter_disp_prod f = iter_disp_disp f (iter_disp_disp f (fun _ -> ()))

  let rec iter_disp_actions f = function
    | AIgnore _ -> ()
    | AKind k ->
	iter_disp_prod f k.prod;
	iter_disp_prod f k.xml;
	(match k.record with Some (RecLabel (_,p)) -> iter_disp_prod f p
	   | _ -> ())
*)

  let () =
    compute_actions :=
      fun disp ->
        let pl = Array.map (Normal.nnf disp.label disp.t) disp.pl in
        let a =
          combine_kind (dispatch_basic disp) (dispatch_prod disp pl)
            (dispatch_xml disp pl) (dispatch_record disp pl)
        in
        disp.state.actions <- a

  let find_array pred a =
    let res = ref (-1) in
    for i = 0 to Array.length a - 1 do
      if pred a.(i) then (
        assert (!res = -1);
        res := i)
    done;
    !res

  let new_fail_res fail =
    find_array (function
      | code, _, _ when code = fail -> true
      | _ -> false)

  let new_fail_disp fail =
    find_array (function
      | Ignore (code, _, _) when code = fail -> true
      | _ -> false)

  let rec prepare_checker fail state =
    if state.fail_code >= 0 then assert (state.fail_code == fail)
    else (
      state.fail_code <- fail;
      let expect = ref Types.empty in
      Array.iteri
        (fun i (t, _, _) -> if i != fail then expect := Types.cup t !expect)
        (Hashtbl.find dispatcher_of_state state.uid).codes;
      state.expected_type <- Types.Print.to_string !expect;
      prepare_checker_actions fail state.actions)

  and prepare_checker_actions fail = function
    | AIgnore _ -> ()
    | AKind k -> (
        prepare_checker_prod fail k.prod;
        prepare_checker_prod fail k.xml;
        match k.record with
        | Some (RecLabel (_, d)) -> prepare_checker_prod fail d
        | _ -> ())

  and prepare_checker_prod fail = function
    | Dispatch (state, cont) ->
        let f = new_fail_disp fail cont in
        if f >= 0 then prepare_checker f state;
        Array.iter (prepare_checker_prod2 fail) cont
    | TailCall state -> prepare_checker fail state
    | Ignore d2 -> prepare_checker_prod2 fail d2
    | Impossible -> ()

  and prepare_checker_prod2 fail = function
    | Dispatch (state, cont) ->
        let f = new_fail_res fail cont in
        assert (f >= 0);
        prepare_checker f state
    | TailCall state -> prepare_checker fail state
    | Ignore _ -> ()
    | Impossible -> ()

  let make_checker t0 t =
    let p = make IdSet.empty in
    define p (constr t);
    let d, rhs = make_branches t0 [ (p, ()) ] in
    let code = ref (-1) in
    Array.iteri
      (fun (i : int) rhs ->
        match rhs with
        | Fail ->
            assert (!code < 0);
            code := i
        | _ -> ())
      rhs;
    if !code >= 0 then prepare_checker !code d;
    d
end

module Print = PrintN

lang/typing/typer.ml

open Cduce_loc
open Ast
open Ident

let ( = ) (x : int) y = x = y
let ( <= ) (x : int) y = x <= y
let ( < ) (x : int) y = x < y
let ( >= ) (x : int) y = x >= y
let ( > ) (x : int) y = x > y

let _warning loc msg =
  let ppf = Format.err_formatter in
  Cduce_loc.print_loc ppf (loc, `Full);
  Format.fprintf ppf ": Warning: %s@." msg

type schema = {
  sch_uri : string;
  sch_ns : Ns.Uri.t;
  sch_comps : (Types.t * Schema_validator.t) Ident.Env.t;
}

type item =
  (* These are really exported by CDuce units: *)
  | Type of (Types.t * Var.t list)
  | Val of Types.t
  | ECDuce of Compunit.t
  | ESchema of schema
  | ENamespace of Ns.Uri.t
  (* These are only used internally: *)
  | EVal of Compunit.t * id * Types.t
  | EOCaml of string
  | EOCamlComponent of string
  | ESchemaComponent of (Types.t * Schema_validator.t)

type t = {
  ids : item Env.t;
  ids_loc : loc Env.t;
  ns : Ns.table;
  keep_ns : bool;
  poly_vars : (U.t * Var.t) list;
  mono_vars : Var.Set.t;
  mutable weak_vars : Types.t option Var.Map.map;
}

(* Namespaces *)

let set_ns_table_for_printer env = Ns.InternalPrinter.set_table env.ns
let get_ns_table tenv = tenv.ns
let type_keep_ns env k = { env with keep_ns = k }

let protect_error_ns loc f x =
  try f x with
  | Ns.UnknownPrefix ns ->
    Cduce_error.(raise_err_loc ~loc Generic ("Undefined namespace prefix " ^ U.to_string ns))

let qname env loc t = protect_error_ns loc (Ns.map_tag env.ns) t
let ident env loc t = protect_error_ns loc (Ns.map_attr env.ns) t
let parse_atom env loc t = AtomSet.V.mk (qname env loc t)
let parse_ns env loc ns = protect_error_ns loc (Ns.map_prefix env.ns) ns

let parse_label env loc t =
  Label.mk (protect_error_ns loc (Ns.map_attr env.ns) t)

let parse_record env loc f r =
  let r = List.map (fun (l, x) -> (parse_label env loc l, f x)) r in
  LabelMap.from_list
    (fun _ _ -> Cduce_error.raise_err_loc ~loc Generic "Duplicated record field")
    r

(*fun _ _ -> assert false*)
let from_comp_unit = ref (fun _ -> assert false)
let load_comp_unit = ref (fun _ -> assert false)
let has_ocaml_unit = ref (fun _ -> false)
let has_static_external = ref (fun _ -> assert false)

let type_schema env loc name uri =
  let x = ident env loc name in
  let ns, sch = Schema_converter.load_schema (U.to_string name) uri in
  let sch = { sch_uri = uri; sch_comps = sch; sch_ns = ns } in
  { env with ids = Env.add x (ESchema sch) env.ids }

let empty_env =
  {
    ids = Env.empty;
    ids_loc = Env.empty;
    ns = Ns.def_table;
    keep_ns = false;
    poly_vars = [];
    mono_vars = Var.Set.empty;
    weak_vars = Var.Map.empty;
  }

let enter_id x i env = { env with ids = Env.add x i env.ids }

let type_using env loc x cu =
  try
    let cu = !load_comp_unit cu in
    enter_id (ident env loc x) (ECDuce cu) env
  with
  | Not_found -> Cduce_error.raise_err_loc ~loc Typer_Error ("Cannot find external unit " ^ U.to_string cu)

let enter_type id t env = enter_id id (Type t) env

let enter_types l env =
  {
    env with
    ids =
      List.fold_left
        (fun accu (id, t, al) -> Env.add id (Type (t, al)) accu)
        env.ids l;
  }

let find_id env0 env loc head x =
  let id = ident env0 loc x in
  try Env.find id env.ids with
  | Not_found when head -> (
      try ECDuce (!load_comp_unit x) with
      | Not_found -> Cduce_error.(raise_err_loc ~loc Typer_Error ("Cannot resolve this identifier: " ^ U.get_str x)))
let find_id_comp env0 env loc x =
  if
    (match (U.get_str x).[0] with
     | 'A' .. 'Z' -> true
     | _ -> false)
    && !has_ocaml_unit x
  then EOCaml (U.get_str x)
  else find_id env0 env loc true x

let enter_value id t env = { env with ids = Env.add id (Val t) env.ids }

let enter_values l env =
  {
    env with
    ids = List.fold_left (fun accu (id, t) -> Env.add id (Val t) accu) env.ids l;
  }

let enter_values_dummy l env =
  {
    env with
    ids =
      List.fold_left
        (fun accu id -> Env.add id (Val Types.empty) accu)
        env.ids l;
  }

let value_name_ok id env =
  try
    match Env.find id env.ids with
    | Val _
    | EVal _ ->
      true
    | _ -> false
  with
  | Not_found -> true

let iter_values env f =
  Env.iter
    (fun x -> function
       | Val t -> f x t
       | _ -> ())
    env.ids

let register_types cu env =
  Env.iter
    (fun x t ->
       match t with
       | Type (t, vparams) ->
         let params = List.map Types.var vparams in
         Types.Print.register_global cu x ~params t
       | _ -> ())
    env.ids

let rec const env loc = function
  | LocatedExpr (loc, e) -> const env loc e
  | Pair (x, y) -> Types.Pair (const env loc x, const env loc y)
  | Xml (x, y) -> Types.Xml (const env loc x, const env loc y)
  | RecordLitt x -> Types.Record (parse_record env loc (const env loc) x)
  | String (i, j, s, c) -> Types.String (i, j, s, const env loc c)
  | Atom t -> Types.Atom (parse_atom env loc t)
  | Integer i -> Types.Integer i
  | Char c -> Types.Char c
  | Const c -> c
  | _ -> Cduce_error.raise_err_loc ~loc Typer_InvalidConstant ()

(* I. Transform the abstract syntax of types and patterns into
      the internal form *)

let find_schema_component sch name =
  try ESchemaComponent (Env.find name sch.sch_comps) with
  | Not_found ->
    Cduce_error.(raise_err Typer_Error
                   (Printf.sprintf "No component named '%s' found in schema '%s'"
                      (Ns.QName.to_string name) sch.sch_uri))

let navig loc env0 (env, comp) id =
  match comp with
  | ECDuce cu ->
    let env = !from_comp_unit cu in
    let c =
      try find_id env0 env loc false id with
      | Not_found -> Cduce_error.raise_err_loc ~loc Typer_UnboundId ((Ns.empty, id), false)
    in
    let c =
      match c with
      | Val t -> EVal (cu, ident env0 loc id, t)
      | c -> c
    in
    (env, c)
  | EOCaml cu -> (
      let s = cu ^ "." ^ U.get_str id in
      match (U.get_str id).[0] with
      | 'A' .. 'Z' -> (env, EOCaml s)
      | _ -> (env, EOCamlComponent s))
  | ESchema sch -> (env, find_schema_component sch (ident env0 loc id))
  | Type _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "Types don't have components") 
  | Val _
  | EVal _ ->
    Cduce_error.(raise_err_loc ~loc Typer_Error "Values don't have components")
  | ENamespace _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "Namespaces don't have components")
  | EOCamlComponent _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "Caml values don't have components")
  | ESchemaComponent _ -> Cduce_error.(raise_err_loc  ~loc Typer_Error "Schema components don't have components")

(*
    | _ -> error loc "Invalid dot access"
*)

let rec find_global env loc ids =
  match ids with
  | id :: rest ->
    let comp = find_id env env loc true id in
    snd (List.fold_left (navig loc env) (env, comp) rest)
  | _ -> assert false

let eval_ns env loc = function
  | `Uri ns -> ns
  | `Path ids -> (
      match find_global env loc ids with
      | ENamespace ns -> ns
      | ESchema sch -> sch.sch_ns
      | _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "This path does not refer to a namespace or schema"))

let type_ns env loc p ns =
  (* TODO: check that p has no prefix *)
  let ns = eval_ns env loc ns in
  {
    env with
    ns = Ns.add_prefix p ns env.ns;
    ids = Env.add (Ns.empty, p) (ENamespace ns) env.ids;
  }

let find_global_type env loc ids =
  match find_global env loc ids with
  | Type (t, pargs) -> (t, pargs)
  | ESchemaComponent (t, _) -> (t, []) (*TODO CHECK*)
  | _ -> Cduce_error.raise_err_loc ~loc Typer_Error "This path does not refer to a type"

let find_global_schema_component env loc ids =
  match find_global env loc ids with
  | ESchemaComponent c -> c
  | _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "This path does not refer to a schema component")

let find_local_type env loc id =
  match Env.find id env.ids with
  | Type t -> t
  | _ -> raise Not_found

let find_value id env =
  match Env.find id env.ids with
  | Val t
  | EVal (_, _, t) ->
    t
  | _ -> raise Not_found

let do_open env cu =
  let env_cu = !from_comp_unit cu in
  let ids =
    Env.fold
      (fun n d ids ->
         let d =
           match d with
           | Val t -> EVal (cu, n, t)
           | d -> d
         in
         Env.add n d ids)
      env_cu.ids env.ids
  in
  { env with ids; ns = Ns.merge_tables env.ns env_cu.ns }

let type_open env loc ids =
  match find_global env loc ids with
  | ECDuce cu -> do_open env cu
  | _ -> Cduce_error.(raise_err_loc ~loc Typer_Error "This path does not refer to a CDuce unit")

module IType = struct
  open Typepat

  (* From AST to the intermediate representation *)
  (* We need to be careful about the order of type definitions in case
      of polymorphic types.  Mutually recursive polymorphic types
      cannot be recursively called with different parameters within
      their recursive groups. We build a graph from the type
      definitions and use Tarjan's algorithm to find all strongly
      connected components in topological order.  Then we translate the
      AST into intermediate representation in that order. *)

  (* [scc defs] takes a list of definitions and returns [ldefs, map] where
     - [ldefs] is a list of list of definitions in topological order. Each
          internal list is a group of mutually recursive definitions.
     - [map] is mapping from type names to their rank and list of parameters.
  *)
  let scc defs =
    let module Info = struct
      type t = {
        mutable index : int;
        mutable lowlink : int;
        mutable is_removed : bool;
        def : loc * U.t * U.t list * ppat;
      }

      let empty =
        {
          index = -1;
          lowlink = -1;
          is_removed = false;
          def = (noloc, U.empty, [], mknoloc (Internal Types.empty));
        }
    end in
    let open Info in
    let index = ref 0
    and stack = ref [] in
    let res = ref []
    and map = Hashtbl.create 17
    and rank = ref ~-1 in
    let g = Hashtbl.create 17 in
    List.iter
      (fun ((_, v, _, _) as def) -> Hashtbl.add g v { empty with def })
      defs;
    let rec strong_connect v vinfo =
      vinfo.index <- !index;
      vinfo.lowlink <- !index;
      incr index;
      stack := v :: !stack;
      vinfo.is_removed <- false;
      let _, _, _, vdef = vinfo.def in
      pat_iter
        (fun p ->
           match p.descr with
           | PatVar ([ w ], _) -> (
               let winfo =
                 try Some (Hashtbl.find g w) with
                 | Not_found -> None
               in
               match winfo with
               | Some winfo ->
                 if winfo.index == -1 then begin
                   strong_connect w winfo;
                   vinfo.lowlink <- min vinfo.lowlink winfo.lowlink
                 end
                 else if not winfo.is_removed then
                   vinfo.lowlink <- min vinfo.lowlink winfo.index
               | _ -> ())
           | _ -> ())
        vdef;
      if vinfo.lowlink == vinfo.index then begin
        let cc = ref [] in
        incr rank;
        while
          let w = List.hd !stack in
          stack := List.tl !stack;
          let winfo = Hashtbl.find g w in
          let _, _, params, _ = winfo.def in
          (*TODO remove U.get_str*)
          Hashtbl.add map w
            (!rank, List.map (fun v -> (v, Var.mk (U.get_str v))) params);
          cc := winfo.def :: !cc;
          winfo.is_removed <- true;
          not (U.equal w v)
        do
          ()
        done;
        res := (!cc, Hashtbl.copy map) :: !res;
        Hashtbl.clear map
      end
    in
    let () =
      List.iter
        (fun (_, v, _, _) ->
           let vinfo = Hashtbl.find g v in
           if vinfo.index == -1 then strong_connect v vinfo)
        defs
    in
    List.rev !res

  type penv = {
    penv_tenv : t;
    penv_derec : (node * U.t list) Env.t;
    mutable penv_var : (U.t * Var.t) list;
  }

  let penv tenv = { penv_tenv = tenv; penv_derec = Env.empty; penv_var = [] }
  let all_delayed = ref []
  let dummy_params = (-1, [], Hashtbl.create 0)
  let current_params = ref dummy_params
  let to_register = ref []

  let clean_params () =
    current_params := dummy_params;
    to_register := []

  let clean_on_err () =
    all_delayed := [];
    clean_params ()

  let delayed loc =
    let s = mk_delayed () in
    all_delayed := (loc, s) :: !all_delayed;
    s

  let check_one_delayed (loc, p) =
    if not (check_wf p) then Cduce_error.(raise_err_loc ~loc Typer_Error "Ill-formed recursion")

  let check_delayed () =
    let l = !all_delayed in
    all_delayed := [];
    List.iter check_one_delayed l

  let rec comp_var_pat vl pl =
    match (vl, pl) with
    | [], [] -> true
    | (v, _) :: vll, { descr = Poly p; _ } :: pll when U.equal v p ->
      comp_var_pat vll pll
    | _ -> false

  let rec untuple p =
    match p.descr with
    | Prod (p1, p2) -> p1 :: untuple p2
    | _ -> [ p ]

  let rec tuple = function
    | [] -> assert false
    | [ p ] -> p
    | p1 :: rest -> Cduce_loc.mknoloc (Prod (p1, tuple rest))

  let match_type_params l args =
    let aux l args =
      if List.length l == List.length args then Some args else None
    in
    match (l, args) with
    | [ _ ], _ :: _ :: _ -> aux l [ tuple args ]
    | _ :: _ :: _, [ p ] -> aux l (untuple p)
    | _ -> aux l args

  let invalid_instance_error loc s =
    Cduce_error.raise_err_loc ~loc Typer_InvalidRecInst (U.to_string s)

  let rec clean_regexp r =
    match r with
    | Epsilon
    | Elem _
    | Guard _ ->
      r
    | Alt (r1, r2) -> Alt (clean_regexp r1, clean_regexp r2)
    | Star r -> Star (clean_regexp r)
    | WeakStar r -> WeakStar (clean_regexp r)
    | SeqCapture (loc, id, r) -> SeqCapture (loc, id, clean_regexp r)
    | Seq (r1, r2) -> (
        match clean_regexp r1 with
        | Seq (e, rr1) -> Seq (e, clean_regexp (Seq (rr1, r2)))
        | e -> Seq (e, clean_regexp r2))

  let rec derecurs env p =
    let err s = Cduce_error.(mk_loc p.loc (Typer_Pattern, s)) in
    match p.descr with
    | Poly v ->
      let vv =
        try List.assoc v env.penv_var with
        | Not_found ->
          let vv = Var.mk (U.get_str v) in
          env.penv_var <- (v, vv) :: env.penv_var;
          vv
      in
      mk_type (Types.var vv)
    | PatVar ids -> derecurs_var env p.loc ids
    | Recurs (p, b) ->
      let b = List.map (fun (l, n, p) -> (l, n, [], p)) b in
      derecurs (fst (derecurs_def env b)) p
    | Internal t -> mk_type t
    | NsT ns ->
      mk_type
        (Types.atom (AtomSet.any_in_ns (parse_ns env.penv_tenv p.loc ns)))
    | Or (p1, p2) -> mk_or ~err (derecurs env p1) (derecurs env p2)
    | And (p1, p2) -> mk_and ~err (derecurs env p1) (derecurs env p2)
    | Diff (p1, p2) -> mk_diff ~err (derecurs env p1) (derecurs env p2)
    | Prod (p1, p2) -> mk_prod (derecurs env p1) (derecurs env p2)
    | XmlT (p1, p2) -> mk_xml (derecurs env p1) (derecurs env p2)
    | Arrow (p1, p2) -> mk_arrow (derecurs env p1) (derecurs env p2)
    | Optional p -> mk_optional ~err (derecurs env p)
    | Record (o, r) ->
      let aux = function
        | p, Some e -> (derecurs env p, Some (derecurs env e))
        | p, None -> (derecurs env p, None)
      in
      mk_record ~err o (parse_record env.penv_tenv p.loc aux r)
    | Constant (x, c) ->
      mk_constant (ident env.penv_tenv p.loc x) (const env.penv_tenv p.loc c)
    | Cst c -> mk_type (Types.constant (const env.penv_tenv p.loc c))
    | Regexp r -> rexp (derecurs_regexp env (clean_regexp r))
    | Concat (p1, p2) -> mk_concat ~err (derecurs env p1) (derecurs env p2)
    | Merge (p1, p2) -> mk_merge ~err (derecurs env p1) (derecurs env p2)

  and derecurs_regexp env = function
    | Epsilon -> mk_epsilon
    | Elem p -> mk_elem (derecurs env p)
    | Guard p -> mk_guard (derecurs env p)
    | Seq
        ( (Elem { descr = PatVar ((id :: rest as ids), []); loc } as p1),
          ((Elem _ | Seq (Elem _, _)) as p2) ) ->
      let arg, make =
        match p2 with
        | Elem arg -> (arg, fun x -> x)
        | Seq (Elem arg, pp2) ->
          (arg, fun x -> mk_seq x (derecurs_regexp env pp2))
        | _ -> assert false
      in
      let v = ident env.penv_tenv loc id in
      let patch_arg =
        try
          try snd (Env.find v env.penv_derec) != [] with
          | Not_found ->
            let _, pargs =
              if rest == [] then find_local_type env.penv_tenv loc v
              else find_global_type env.penv_tenv loc ids
            in
            pargs != []
        with
        | Not_found -> false
      in
      if patch_arg then
        make (mk_elem (derecurs env { descr = PatVar (ids, [ arg ]); loc }))
      else mk_seq (derecurs_regexp env p1) (derecurs_regexp env p2)
    | Seq (p1, p2) -> mk_seq (derecurs_regexp env p1) (derecurs_regexp env p2)
    | Alt (p1, p2) -> mk_alt (derecurs_regexp env p1) (derecurs_regexp env p2)
    | Star p -> mk_star (derecurs_regexp env p)
    | WeakStar p -> mk_weakstar (derecurs_regexp env p)
    | SeqCapture (loc, x, p) ->
      mk_seqcapt (ident env.penv_tenv loc x) (derecurs_regexp env p)

  and derecurs_var env loc ids =
    match ids with
    | (id :: rest as ids), args -> (
        let cidx, cparams, cmap = !current_params in
        let v = ident env.penv_tenv loc id in
        try
          let node, _ = Env.find v env.penv_derec in
          if args == [] || comp_var_pat cparams args then node
          else invalid_instance_error loc id
        with
        | Not_found -> (
            try
              let (cu, name), (t, pargs), tidx =
                if rest == [] then
                  ( ("", v),
                    find_local_type env.penv_tenv loc v,
                    try fst (Hashtbl.find cmap id) with
                    | Not_found -> ~-1 )
                else
                  let t, pargs = find_global_type env.penv_tenv loc ids in
                  match find_id env.penv_tenv env.penv_tenv loc true id with
                  | ECDuce _
                  | EOCaml _ ->
                    ( ( U.get_str id,
                        ident env.penv_tenv loc
                          (U.mk @@ String.concat "."
                           @@ List.map U.get_str rest) ),
                      (t, pargs),
                      ~-1 )
                  | _ -> assert false
              in
              if cidx >= 0 && tidx == cidx && not (comp_var_pat cparams args)
              then invalid_instance_error loc id;
              let _err s = Error s in
              let l =
                match match_type_params pargs args with
                | Some args ->
                  List.map2 (fun v p -> (v, typ (derecurs env p))) pargs args
                | None ->
                  Cduce_error.raise_err_loc
                    ~loc Typer_InvalidInstArity
                    (U.to_string id, List.length pargs, List.length args)
              in
              let sub = Types.Subst.from_list l in
              let ti = mk_type (Types.Subst.apply_full sub t) in
              to_register := (cu, name, List.map snd l, ti, loc) :: !to_register;
              ti
            with
            | Not_found ->
              assert (rest == []);
              if args != [] then
                Cduce_error.raise_err_loc ~loc Typer_UnboundId (v, true)
              else mk_capture v))
    | _ -> assert false

  and derecurs_def env b =
    let seen = ref IdMap.empty in
    let b =
      List.map
        (fun (loc, v, args, p) ->
           let v = ident env.penv_tenv loc v in
           try
             let old_loc = IdMap.assoc v !seen in
             Cduce_error.raise_err_loc ~loc Typer_MultipleTypeDef (v, old_loc)
           with Not_found ->
             seen := IdMap.add v loc !seen ;
             (v, loc, args, p, delayed loc))
        b
    in
    let env =
      List.fold_left
        (fun env (v, _, a, p, s) ->
           {
             env with
             penv_derec = Env.add v (s, a) env.penv_derec;
             penv_var =
               List.fold_left
                 (fun acc v -> (v, Var.mk (U.get_str v)) :: acc)
                 env.penv_var a;
           })
        env b
    in
    List.iter
      (fun (v, _, a, p, s) ->
         (* Copy. The unknown polymorphic variables that are not already in
               penv_var are introduced for the scope of the current type and
               discarded afterwards.
         *)
         let env = { env with penv_var = env.penv_var } in
         link s (derecurs env p))
      b;
    (env, b)

  let derec penv p =
    let d = derecurs penv p in
    elim_concats ();
    check_delayed ();
    internalize d;
    d

  (* API *)

  let check_no_fv loc n =
    match peek_fv n with
    | None -> ()
    | Some x ->
      Cduce_error.raise_err_loc ~loc Typer_CaptureNotAllowed x
  let type_defs env b =
    let penv, b = derecurs_def (penv env) b in
    elim_concats ();
    check_delayed ();
    let aux loc d =
      internalize d;
      check_no_fv loc d;
      try typ d with
      | Cduce_error.Error ((Located loc' | PreciselyLocated (loc', _)) , (Typer_Pattern, s)) ->
        Cduce_error.raise_err_loc ~loc:loc' Typer_Error s
      | Cduce_error.Error (Unlocated, (Typer_Pattern, s)) -> Cduce_error.raise_err_loc ~loc Generic s
    in
    let b =
      List.map
        (fun (v, loc, args, _, d) ->
           let t_rhs = aux loc d in
           if loc <> noloc && Types.is_empty t_rhs then
             Cduce_error.(warning ~loc
                            ("This definition yields an empty type for " ^ Ident.to_string v) ());

           let vars_rhs = Types.Subst.vars t_rhs in
           let vars_lhs = Var.Set.from_list (List.map snd penv.penv_var) in
           let undecl = Var.Set.diff vars_rhs vars_lhs in
           if not (Var.Set.is_empty undecl) then
             Cduce_error.raise_err_loc ~loc
               Typer_UnboundTypeVariable (v, Var.Set.choose undecl);
           (* recreate the mapping in the correct order *)
           let vars_args = List.map (fun v -> List.assoc v penv.penv_var) args in
           let final_vars =
             (* create a sequence 'a -> 'a_0 for all variables *)
             List.map (fun v -> (v, Var.(mk (name v)))) vars_args
           in
           let subst =
             Types.Subst.from_list
               (List.map (fun (v, vv) -> (v, Types.var vv)) final_vars)
           in
           let t_rhs = Types.Subst.apply_full subst t_rhs in
           (v, t_rhs, List.map snd final_vars))
        (List.rev b)
    in
    List.iter
      (fun (v, t, al) ->
         let params = List.map Types.var al in
         Types.Print.register_global "" v ~params t)
      b;
    let env = enter_types b env in
    List.iter
      (fun (cu, name, params, ti, loc) ->
         let tti = aux loc ti in
         Types.Print.register_global cu name ~params tti)
      !to_register;
    env

  let equal_params l1 l2 =
    try List.for_all2 U.equal l1 l2 with
    | _ -> false

  let check_params l =
    match l with
    | [] -> assert false
    | [ (_, u, _, _) ] -> u
    | (loc1, u, p, _) :: r -> (
        try
          let loc2, v, _, _ =
            List.find (fun (_, _, q, _) -> not (equal_params p q)) r
          in
          let loc = merge_loc loc1 loc2 in
          Cduce_error.raise_err_loc ~loc
            Typer_Error (Printf.sprintf (* TODO create an error *)
                           "mutually recursive types %s and %s have different arities"
                           (U.to_string u) (U.to_string v))
        with
        | Not_found -> u)

  let type_defs env b =
    try
      let b = scc b in
      let r =
        List.fold_left
          (fun env (b, map) ->
             let u = check_params b in
             let idx, params = Hashtbl.find map u in
             current_params := (idx, params, map);
             type_defs env b)
          env b
      in
      clean_params ();
      r
    with
    | exn ->
      clean_on_err ();
      raise exn

  let typ vars env t =
    let aux loc d =
      internalize d;
      check_no_fv loc d;
      try typ_node d with
      | Cduce_error.Error (_, (Typer_Pattern, s)) -> Cduce_error.raise_err_loc ~loc Typer_Error s
    in
    try
      let penv = { (penv env) with penv_var = vars } in
      let d = derec penv t in
      let res = aux t.loc d in
      List.iter
        (fun (cu, name, params, ti, loc) ->
           let tti = aux loc ti in
           Types.Print.register_global cu name ~params (Types.descr tti))
        !to_register;
      clean_params ();
      res
    with
    | exn ->
      clean_on_err ();
      raise exn

  let pat env t =
    try
      let d = derec (penv env) t in
      try pat_node d with
      | Cduce_error.Error (_, (Typer_Pattern, s)) ->
        Cduce_error.raise_err_loc ~loc:t.loc Typer_Error s
    with
    | exn ->
      clean_on_err ();
      raise exn
end

let typ = IType.typ []
let var_typ = IType.typ
let pat = IType.pat
let type_defs = IType.type_defs

let dump_types ppf env =
  Env.iter
    (fun v -> function
       | Type _ -> Format.fprintf ppf " %a" Ident.print v
       | _ -> ())
    env.ids

let dump_ns ppf env = Ns.dump_table ppf env.ns

(* II. Build skeleton *)

type type_fun = Cduce_loc.loc -> Types.t -> bool -> Types.t

module Fv = IdSet

type branch = Branch of Typed.branch * branch list

let cur_branch : branch list ref = ref []

let exp' loc e =
  { Typed.exp_loc = loc; Typed.exp_typ = Types.empty; Typed.exp_descr = e }

let exp loc fv e = (fv, exp' loc e)
let exp_nil = exp' noloc (Typed.Cst Types.Sequence.nil_cst)

let pat_true =
  let n = Patterns.make Fv.empty in
  Patterns.define n (Patterns.constr Builtin_defs.true_type);
  n

let pat_false =
  let n = Patterns.make Fv.empty in
  Patterns.define n (Patterns.constr Builtin_defs.false_type);
  n

let ops = Hashtbl.create 13
let register_op op arity f = Hashtbl.add ops op (arity, f)
let typ_op op = snd (Hashtbl.find ops op)

let fun_name env a =
  match a.fun_name with
  | None -> None
  | Some (loc, s) -> Some (ident env loc s)

let is_op env s =
  if Env.mem s env.ids then None
  else
    let ns, s = s in
    if Ns.Uri.equal ns Ns.empty then
      let s = U.get_str s in
      try
        let o = Hashtbl.find ops s in
        Some (s, fst o)
      with
      | Not_found -> None
    else None

module USet = Set.Make (U)

let collect_vars acc p =
  let vset = ref acc in
  pat_iter
    (function
      | { descr = Poly v; _ } -> vset := USet.add v !vset
      | _ -> ())
    p;
  !vset

let rec get_dot_for_annot loc expr =
  match expr with
  | Dot _ -> expr
  | LocatedExpr (_, e) -> get_dot_for_annot loc e
  | e -> Cduce_error.raise_err_loc ~loc Typer_Error "Only OCaml external can have type arguments"

let rec expr env loc = function
  | LocatedExpr (loc, e) -> expr env loc e
  | Forget (e, t) ->
    let fv, e = expr env loc e
    and t = typ env t in
    exp loc fv (Typed.Forget (e, t))
  | Check (e, t) ->
    let fv, e = expr env loc e
    and t = typ env t in
    exp loc fv (Typed.Check (ref Types.empty, e, t))
  | Var s -> var env loc s
  | Apply (e1, e2) -> (
      let fv1, e1 = expr env loc e1
      and fv2, e2 = expr env loc e2 in
      let fv = Fv.cup fv1 fv2 in
      match e1.Typed.exp_descr with
      | Typed.Op (op, arity, args) when arity > 0 ->
        exp loc fv (Typed.Op (op, arity - 1, args @ [ e2 ]))
      | _ -> exp loc fv (Typed.Apply (e1, e2)))
  | Abstraction a -> abstraction env loc a
  | (Integer _ | Char _ | Atom _ | Const _) as c ->
    exp loc Fv.empty (Typed.Cst (const env loc c))
  | Abstract v -> exp loc Fv.empty (Typed.Abstract v)
  | Pair (e1, e2) ->
    let fv1, e1 = expr env loc e1
    and fv2, e2 = expr env loc e2 in
    exp loc (Fv.cup fv1 fv2) (Typed.Pair (e1, e2))
  | Xml (e1, e2) ->
    let fv1, e1 = expr env loc e1
    and fv2, e2 = expr env loc e2 in
    let n = if env.keep_ns then Some env.ns else None in
    exp loc (Fv.cup fv1 fv2) (Typed.Xml (e1, e2, n))
  | Dot _ as e -> dot loc env e []
  | TyArgs (e, args) ->
    (*let e = get_dot_for_annot loc e in*)
    dot loc env e args
  | RemoveField (e, l) ->
    let fv, e = expr env loc e in
    exp loc fv (Typed.RemoveField (e, parse_label env loc l))
  | RecordLitt r ->
    let fv = ref Fv.empty in
    let r =
      parse_record env loc
        (fun e ->
           let fv2, e = expr env loc e in
           fv := Fv.cup !fv fv2;
           e)
        r
    in
    exp loc !fv (Typed.RecordLitt r)
  | String (i, j, s, e) ->
    let fv, e = expr env loc e in
    exp loc fv (Typed.String (i, j, s, e))
  | Match (e, b) ->
    let fv1, e = expr env loc e
    and fv2, b = branches env b in
    exp loc (Fv.cup fv1 fv2) (Typed.Match (e, b))
  | Map (e, b) ->
    let fv1, e = expr env loc e
    and fv2, b = branches env b in
    exp loc (Fv.cup fv1 fv2) (Typed.Map (e, b))
  | Transform (e, b) ->
    let fv1, e = expr env loc e
    and fv2, b = branches env b in
    exp loc (Fv.cup fv1 fv2) (Typed.Transform (e, b))
  | Xtrans (e, b) ->
    let fv1, e = expr env loc e
    and fv2, b = branches env b in
    exp loc (Fv.cup fv1 fv2) (Typed.Xtrans (e, b))
  | Validate (e, ids) ->
    let fv, e = expr env loc e in
    let t, v = find_global_schema_component env loc ids in
    exp loc fv (Typed.Validate (e, t, v))
  | SelectFW (e, from, where) -> select_from_where env loc e from where
  | Try (e, b) ->
    let fv1, e = expr env loc e
    and fv2, b = branches env b in
    exp loc (Fv.cup fv1 fv2) (Typed.Try (e, b))
  | NamespaceIn (pr, ns, e) ->
    let env = type_ns env loc pr ns in
    expr env loc e
  | KeepNsIn (k, e) -> expr (type_keep_ns env k) loc e
  | Ref (e, t) ->
    let fv, e = expr env loc e
    and t = var_typ env.poly_vars env t in
    exp loc fv (Typed.Ref (e, t))

and if_then_else loc cond yes no =
  let b =
    {
      Typed.br_typ = Types.empty;
      Typed.br_branches =
        [
          {
            Typed.br_loc = yes.Typed.exp_loc;
            Typed.br_used = false;
            Typed.br_ghost = false;
            Typed.br_vars_empty = Fv.empty;
            Typed.br_pat = pat_true;
            Typed.br_body = yes;
          };
          {
            Typed.br_loc = no.Typed.exp_loc;
            Typed.br_used = false;
            Typed.br_ghost = false;
            Typed.br_vars_empty = Fv.empty;
            Typed.br_pat = pat_false;
            Typed.br_body = no;
          };
        ];
      Typed.br_accept = Builtin_defs.bool;
    }
  in
  exp' loc (Typed.Match (cond, b))

and dot loc env0 e args =
  let dot_access loc (fv, e) l =
    exp loc fv (Typed.Dot (e, parse_label env0 loc l))
  in

  let no_args () =
    if args <> [] then Cduce_error.raise_err_loc ~loc Typer_Error "Only OCaml externals can have type arguments"
  in
  let rec aux loc = function
    | LocatedExpr (loc, e) -> aux loc e
    | Dot (e, id) -> (
        match aux loc e with
        | `Val e -> `Val (dot_access loc e id)
        | `Comp c -> `Comp (navig loc env0 c id))
    | Var id -> (
        match find_id_comp env0 env0 loc id with
        | Val _ -> `Val (var env0 loc id)
        | c -> `Comp (env0, c))
    | e -> `Val (expr env0 loc e)
  in
  match aux loc e with
  | `Val e ->
    no_args ();
    e
  | `Comp (_, EVal (cu, id, t)) ->
    no_args ();
    exp loc Fv.empty (Typed.ExtVar (cu, id, t))
  | `Comp (_, EOCamlComponent s) -> extern loc env0 s args
  | _ -> Cduce_error.raise_err_loc ~loc Typer_Error "This dot notation does not refer to a value"

and extern loc env s args =
  let args = List.map (typ env) args in
  try
    let i, t =
      let i, t = Externals.resolve s args in
        if !has_static_external s then
          (`Builtin (s,i), t)
        else
          (`Ext i, t)
    in
    exp loc Fv.empty (Typed.External (t, i))
  with
  | Cduce_error.Error (Unlocated, (err, arg)) -> Cduce_error.raise_err_loc ~loc err arg
  | Cduce_error.Error (Located loc, (err, arg)) -> Cduce_error.raise_err_loc ~loc err arg
  | Cduce_error.Error (PreciselyLocated (loc, _), (err, arg)) -> Cduce_error.raise_err_loc ~loc err arg
  | exn -> Cduce_error.raise_err_loc ~loc Other_Exn exn

and var env loc s =
  let id = ident env loc s in
  match is_op env id with
  | Some (s, arity) ->
    let e =
      match s with
      | "print_xml"
      | "print_xml_utf8" ->
        Typed.NsTable (env.ns, Typed.Op (s, arity, []))
      | "load_xml" when env.keep_ns -> Typed.Op ("!load_xml", arity, [])
      | _ -> Typed.Op (s, arity, [])
    in
    exp loc Fv.empty e
  | None -> (
      try
        match Env.find id env.ids with
        | Val _ -> exp loc (Fv.singleton id) (Typed.Var id)
        | EVal (cu, id, t) -> exp loc Fv.empty (Typed.ExtVar (cu, id, t))
        | _ -> Cduce_error.raise_err_loc ~loc Typer_Error "This identifier does not refer to a value"
      with
      | Not_found -> Cduce_error.raise_err_loc ~loc Typer_UnboundId (id, false))

and abstraction env loc a =
  (* When entering a function (fun 'a 'b ... .('a -> 'b -> … ))
     for each variable 'x from the interface
     - if 'x is in env.poly_vars, it is bound higher in the AST, we need
       to keep the associated unique variable
     - if 'x is not in env.poly_vars or 'x is in a.fun_poly, a fresh
       name must be generated and kept for subsequent use of the variable.
  *)
  let vset =
    (* collect all type variables from the interface*)
    List.fold_left
      (fun acc (t1, t2) -> collect_vars (collect_vars acc t1) t2)
      USet.empty a.fun_iface
  in
  let vset =
    (* remove variables that are in scope *)
    List.fold_left (fun acc (v, _) -> USet.remove v acc) vset env.poly_vars
  in
  let vset = List.fold_left (fun acc v -> USet.add v acc) vset a.fun_poly in
  (* add those that are explicitely polymorphic. *)
  let all_vars =
    USet.fold (fun v acc -> (v, Var.mk (U.get_str v)) :: acc) vset env.poly_vars
  in
  let iface =
    List.map
      (fun (t1, t2) -> (var_typ all_vars env t1, var_typ all_vars env t2))
      a.fun_iface
  in
  let env = { env with poly_vars = all_vars } in
  let t =
    List.fold_left
      (fun accu (t1, t2) -> Types.cap accu (Types.arrow t1 t2))
      Types.any iface
  in
  let iface =
    List.map (fun (t1, t2) -> (Types.descr t1, Types.descr t2)) iface
  in
  let fun_name = fun_name env a in
  let env' =
    match fun_name with
    | None -> env
    | Some f -> enter_values_dummy [ f ] env
  in
  let fv0, body = branches env' a.fun_body in
  let fv =
    match fun_name with
    | None -> fv0
    | Some f -> Fv.remove f fv0
  in
  let e =
    Typed.Abstraction
      {
        Typed.fun_name;
        Typed.fun_iface = iface;
        Typed.fun_body = body;
        Typed.fun_typ = t;
        Typed.fun_fv = fv;
        Typed.fun_is_poly = not (Var.Set.is_empty (Types.Subst.vars t));
      }
  in
  exp loc fv e

and branches env b =
  let fv = ref Fv.empty in
  let accept = ref Types.empty in
  let branch (p, e) =
    let cur_br = !cur_branch in
    cur_branch := [];
    let ploc = p.loc in
    let p = pat env p in
    let fvp = Patterns.fv p in
    let fv2, e = expr (enter_values_dummy (fvp :> Id.t list) env) noloc e in
    let br_loc = merge_loc ploc e.Typed.exp_loc in
    (match Fv.pick (Fv.diff fvp fv2) with
     | None -> ()
     | Some x ->
       let x = Ident.to_string x in
       warning br_loc
         ("The capture variable " ^ x
          ^ " is declared in the pattern but not used in the body of this \
             branch. It might be a misspelled or undeclared type or name (if it \
             isn't, use _ instead)."));
    let fv2 = Fv.diff fv2 fvp in
    fv := Fv.cup !fv fv2;
    accept := Types.cup !accept (Types.descr (Patterns.accept p));
    let ghost = br_loc == noloc in
    let br =
      {
        Typed.br_loc;
        Typed.br_used = ghost;
        Typed.br_ghost = ghost;
        Typed.br_vars_empty = fvp;
        Typed.br_pat = p;
        Typed.br_body = e;
      }
    in
    cur_branch := Branch (br, !cur_branch) :: cur_br;
    br
  in
  let b = List.map branch b in
  ( !fv,
    {
      Typed.br_typ = Types.empty;
      Typed.br_branches = b;
      Typed.br_accept = !accept;
    } )

and select_from_where env loc e from where =
  let env = ref env in
  let all_fv = ref Fv.empty in
  let bound_fv = ref Fv.empty in
  let clause (p, e) =
    let ploc = p.loc in
    let p = pat !env p in
    let fvp = Patterns.fv p in
    let fv2, e = expr !env noloc e in
    env := enter_values_dummy (fvp :> Id.t list) !env;
    all_fv := Fv.cup (Fv.diff fv2 !bound_fv) !all_fv;
    bound_fv := Fv.cup fvp !bound_fv;
    (ploc, p, fvp, e)
  in
  let from = List.map clause from in
  let where = List.map (expr !env noloc) where in

  let put_cond rest (fv, cond) =
    all_fv := Fv.cup (Fv.diff fv !bound_fv) !all_fv;
    if_then_else loc cond rest exp_nil
  in
  let aux (ploc, p, fvp, e) (where, rest) =
    (* Put here the conditions that depends on variables in fvp *)
    let above, here = List.partition (fun (v, _) -> Fv.disjoint v fvp) where in
    (* if cond then ... else [] *)
    let rest = List.fold_left put_cond rest here in
    (* transform e with p -> ... *)
    let br =
      {
        Typed.br_loc = ploc;
        Typed.br_used = false;
        Typed.br_ghost = false;
        Typed.br_vars_empty = fvp;
        Typed.br_pat = p;
        Typed.br_body = rest;
      }
    in
    cur_branch := [ Branch (br, !cur_branch) ];
    let b =
      {
        Typed.br_typ = Types.empty;
        Typed.br_branches = [ br ];
        Typed.br_accept = Types.descr (Patterns.accept p);
      }
    in
    let br_loc = merge_loc ploc e.Typed.exp_loc in
    (above, exp' br_loc (Typed.Transform (e, b)))
  in
  let cur_br = !cur_branch in
  cur_branch := [];
  let fv, e = expr !env noloc (Pair (e, cst_nil)) in
  cur_branch := !cur_branch @ cur_br;
  let where, rest = List.fold_right aux from (where, e) in
  (* The remaining conditions are constant. Gives a warning for that. *)
  (match where with
   | (_, e) :: _ ->
     warning e.Typed.exp_loc
       "This 'where' condition does not depend on any captured variable"
   | _ -> ());
  let rest = List.fold_left put_cond rest where in
  (Fv.cup !all_fv (Fv.diff fv !bound_fv), rest)

let expr env e = snd (expr env noloc e)

let let_decl env p e =
  { Typed.let_pat = pat env p; Typed.let_body = expr env e }

(* Hide global "typing/parsing" environment *)

(* III. Type-checks *)

open Typed

let any_node = Types.(cons any)

let localize loc f x =
  try f x with
  | Cduce_error.Error (_, (Typer_Error, msg)) -> Cduce_error.raise_err_loc ~loc Typer_Error msg
  | Cduce_error.Error (_,(Typer_Constraint, arg)) -> Cduce_error.raise_err_loc ~loc Typer_Constraint arg


let raise_constraint_exn ?loc ?precise t s =
  let open Cduce_error in
  let r  (type a) (e : a error_t) (a : a) =
    match loc, precise with 
      None, _ -> raise_err e a
    | Some loc, Some precise -> raise_err_precise ~loc precise e a
    | Some loc, None -> raise_err_loc ~loc e a
  in
  if
    Var.Set.is_empty (Types.Subst.vars t)
    && Var.Set.is_empty (Types.Subst.vars s)
  then r Typer_Constraint (t, s)
  else r Typer_ShouldHave2 (t, "but its inferred type is:", s)

let require loc t s =
  if not (Types.subtype t s) then raise_constraint_exn ~loc t s

let verify loc t s =
  require loc t s;
  t

let verify_noloc t s =
  if not (Types.subtype t s) then raise_constraint_exn t s;
  t

let check_str loc ofs t s =
  if not (Types.subtype t s) then raise_constraint_exn ~loc ~precise:(`Char ofs) t s;
  t

let should_have loc constr s = Cduce_error.raise_err_loc ~loc Typer_ShouldHave (constr, s)

let should_have_str loc ofs constr s =
  Cduce_error.raise_err_precise  ~loc (`Char ofs) Typer_ShouldHave (constr, s)

let flatten arg loc constr precise =
  let open Types in
  let constr' =
    Sequence.star (Sequence.approx (Types.cap Sequence.any constr))
  in
  let sconstr' = Sequence.star constr' in
  let exact = Types.subtype constr' constr in
  if exact then
    let t = arg loc sconstr' precise in
    if precise then Sequence.flatten t else constr
  else
    let t = arg loc sconstr' true in
    verify loc (Sequence.flatten t) constr

let pat_any () =
  let n = Patterns.make IdSet.empty in
  Patterns.define n (Patterns.constr Types.any);
  n

let pat_var id =
  let s = IdSet.singleton id in
  let n = Patterns.make s in
  Patterns.define n (Patterns.capture id);
  n

let pat_node d fv =
  let n = Patterns.make fv in
  Patterns.define n d;
  n

let pat_pair kind e1 e2 fv =
  pat_node
    ((match kind with
        | `Normal -> Patterns.times
        | `Xml -> Patterns.xml)
       e1 e2)
    fv

let pat_cap p1 p2 =
  let fv1 = Patterns.fv p1 in
  let fv2 = Patterns.fv p2 in
  pat_node Patterns.(cap (descr p1) (descr p2)) (IdSet.cup fv1 fv2)

let rec pat_of_expr fv te =
  match te.exp_descr with
  | Var s when not (IdSet.mem fv s) -> pat_var s
  | Pair (e1, e2)
  | Xml (e1, e2, _) ->
    let kind =
      match te.exp_descr with
      | Pair _ -> `Normal
      | _ -> `Xml
    in
    let p1 = pat_of_expr fv e1
    and p2 = pat_of_expr fv e2 in
    let fv1 = Patterns.fv p1 in
    let fv2 = Patterns.fv p2 in
    pat_pair kind p1 p2 (IdSet.cup fv1 fv2)
  | RecordLitt lmap ->
    List.fold_left
      (fun acc (lab, tel) ->
         let pel = pat_of_expr fv tel in
         let prec = pat_node (Patterns.record lab pel) (Patterns.fv pel) in
         pat_cap prec acc)
      (pat_any ()) (LabelMap.get lmap)
  | _ -> pat_any ()

let refine_pat p1 op2 =
  match op2 with
  | None -> p1
  | Some te ->
    let fv = Patterns.fv p1 in
    let p2 = pat_of_expr fv te in
    pat_cap p1 p2

let rec type_check env e constr precise =
  let d = type_check' e.exp_loc env e.exp_descr constr precise in
  let d = if precise then d else constr in
  e.exp_typ <- Types.cup e.exp_typ d;
  d

and type_check' loc env e constr precise =
  match e with
  | Forget (e, t) ->
    let t = Types.descr t in
    ignore (type_check env e t false);
    verify loc t constr
  | Check (t0, e, t) ->
    if Var.Set.is_empty Types.(Subst.vars (descr t)) then (
      let te = type_check env e Types.any true in
      t0 := Types.cup !t0 te;
      verify loc (Types.cap te (Types.descr t)) constr)
    else
      Cduce_error.raise_err_loc
        ~loc Typer_Error "Polymorphic type variables cannot occur in dynamic type-checks"
  | Abstraction a ->
    let t =
      if Types.subtype a.fun_typ constr then a.fun_typ
      else
        let name =
          match a.fun_name with
          | Some s -> "abstraction " ^ Ident.to_string s
          | None -> "the abstraction"
        in
        should_have loc constr
          (Format.asprintf
             "but the interface (%a) of %s is not compatible with type\n\
             \            (%a)" Types.Print.print a.fun_typ name
             Types.Print.print constr)
    in
    let env =
      {
        env with
        mono_vars = Var.Set.cup env.mono_vars (Types.Subst.vars a.fun_typ);
      }
    in
    let env =
      match a.fun_name with
      | None -> env
      | Some f -> enter_value f a.fun_typ env
    in
    List.iter
      (fun (t1, t2) ->
         let acc = a.fun_body.br_accept in
         if not (Types.subtype t1 acc) then
           Cduce_error.(raise_err_loc  ~loc Typer_NonExhaustive (Types.diff t1 acc));
         ignore (type_check_branches loc env t1 a.fun_body t2 false))
      a.fun_iface;
    t
  | Match (e, b) ->
    let t = type_check env e b.br_accept true in
    type_check_branches loc env t b constr precise
  | Try (e, b) ->
    let te = type_check env e constr precise in
    let tb = type_check_branches loc env Types.any b constr precise in
    Types.cup te tb
  | Pair (e1, e2) -> type_check_pair loc env e1 e2 constr precise
  | Xml (e1, e2, _) -> type_check_pair ~kind:`XML loc env e1 e2 constr precise
  | RecordLitt r -> type_record loc env r constr precise
  | Map (e, b) -> type_map loc env false e b constr precise
  | Transform (e, b) ->
    localize loc (flatten (fun l -> type_map loc env true e b) loc constr) precise
  | Apply (e1, e2) ->
    let t1 = type_check env e1 Types.Function.any true in
    let t1arrow = Types.Arrow.get t1 in
    let dom = Types.Arrow.domain t1arrow in
    let t2 = type_check env e2 Types.any true in
    let res =
      if
        Var.Set.is_empty (Types.Subst.vars t1)
        && Var.Set.is_empty (Types.Subst.vars t2)
      then
        (* TODO don't retype e2 *)
        let t2 = type_check env e2 dom true in
        Types.Arrow.apply t1arrow t2
      else
        match Types.Tallying.apply_raw env.mono_vars t1 t2 with
        | None -> raise_constraint_exn ~loc t2 dom
        | Some (subst, tx, ty, res) ->
          List.iter
            (fun s ->
               Var.Map.iteri
                 (fun wv t ->
                    match Var.kind wv with
                    | `generated
                    | `user ->
                      ()
                    | `weak ->
                      let tt = Types.Subst.clean_type env.mono_vars t in
                      env.weak_vars <-
                        Var.Map.update
                          (fun prev next ->
                             match (prev, next) with
                             | None, Some t -> next
                             | Some tp, Some tn when Types.equiv tp tn -> next
                             | Some tp, Some tn ->
                               Cduce_error.(raise_err_loc ~loc
                                              Typer_Error (Format.asprintf
                                                             "the weak polymorphic variable %a is \
                                                              instantiated several times in the \
                                                              same expression."
                                                             Var.print wv))
                             | _ -> assert false)
                          wv (Some tt) env.weak_vars)
                 s)
            subst;
          res
        (*
        if Types.Arrow.need_arg t1 then
          let t2 = type_check env e2 dom true in
          Types.Arrow.apply t1 t2
        else (
          ignore (type_check env e2 dom false);
          Types.Arrow.apply_noarg t1) *)
    in
    verify loc res constr
  | Var s -> verify loc (find_value s env) constr
  | ExtVar (cu, s, t) -> verify loc t constr
  | Cst c -> verify loc (Types.constant c) constr
  | Abstract (t, _) -> verify loc Types.(abstract (AbstractSet.atom t)) constr
  | String (i, j, s, e) -> type_check_string loc env 0 s i j e constr precise
  | Dot (e, l) -> (
      let expect_rec = Types.record l (Types.cons constr) in
      let expect_elt =
        Types.xml any_node
          (Types.cons (Types.times (Types.cons expect_rec) any_node))
      in
      let t = type_check env e (Types.cup expect_rec expect_elt) precise in
      let t_elt =
        let t = Types.Product.pi2 (Types.Product.get ~kind:`XML t) in
        let t = Types.Product.pi1 (Types.Product.get t) in
        t
      in
      if not precise then constr
      else
        try Types.Record.project (Types.cup t t_elt) l with
        | Not_found -> assert false)
  | RemoveField (e, l) ->
    let t = type_check env e Types.Rec.any true in
    let t = Types.Record.remove_field t l in
    verify loc t constr
  | Xtrans (e, b) ->
    let t = type_check env e Types.Sequence.any true in
    let t =
      try
        Types.Sequence.map_tree constr
          (fun cstr t ->
             let resid = Types.diff t b.br_accept in
             let res = type_check_branches loc env t b cstr true in
             (res, resid))
          t
      with
      | Types.Sequence.Error _ as exn -> (
          let rec find_loc = function
            | Cduce_error.Error (PreciselyLocated (loc, precise), _) -> ((loc, precise), exn)
            | Types.Sequence.(Error (Types.Sequence.UnderTag (t, exn))) ->
              let l, exn = find_loc exn in
              (l, Types.Sequence.Error (Types.Sequence.UnderTag (t, exn)))
            | exn -> raise Not_found
          in
          try
            let (loc, precise), exn = find_loc exn in
            Cduce_error.raise_err_precise ~loc precise Other_Exn exn
          with
          | Not_found -> Cduce_error.raise_err_loc ~loc Other_Exn exn)
    in
    verify loc t constr
  | Validate (e, t, _) ->
    ignore (type_check env e Types.any false);
    verify loc t constr
  | Ref (e, t) ->
    ignore (type_check env e (Types.descr t) false);
    verify loc (Builtin_defs.ref_type t) constr
  | External (t, _) -> verify loc t constr
  | Op (op, _, args) ->
    let args : type_fun list = List.map (fun e (_:Cduce_loc.loc) -> type_check env e ) args in
    let t = localize loc (typ_op op args loc constr) precise in
    verify loc t constr
  | NsTable (ns, e) -> type_check' loc env e constr precise

and type_check_pair ?(kind = `Normal) loc env e1 e2 constr precise =
  let rects = Types.Product.normal ~kind constr in
  (if Types.Product.is_empty rects then
     match kind with
     | `Normal -> should_have loc constr "but it is a pair"
     | `XML -> should_have loc constr "but it is an XML element");
  let need_s = Types.Product.need_second rects in
  let t1 = type_check env e1 (Types.Product.pi1 rects) (precise || need_s) in
  let c2 = Types.Product.constraint_on_2 rects t1 in
  if Types.is_empty c2 then
    Cduce_error.raise_err_loc ~loc
      Typer_ShouldHave2 (constr, "but the first component has type:", t1);
  let t2 = type_check env e2 c2 precise in
  if precise then
    match kind with
    | `Normal -> Types.times (Types.cons t1) (Types.cons t2)
    | `XML -> Types.xml (Types.cons t1) (Types.cons t2)
  else constr

and type_check_string loc env ofs s i j e constr precise =
  if U.equal_index i j then type_check env e constr precise
  else
    let rects = Types.Product.normal constr in
    if Types.Product.is_empty rects then
      should_have_str loc ofs constr "but it is a string"
    else
      let ch, i' = U.next s i in
      let ch = CharSet.V.mk_int ch in
      let tch = Types.constant (Types.Char ch) in
      let t1 = check_str loc ofs tch (Types.Product.pi1 rects) in
      let c2 = Types.Product.constraint_on_2 rects t1 in
      let t2 = type_check_string loc env (ofs + 1) s i' j e c2 precise in
      if precise then Types.times (Types.cons t1) (Types.cons t2) else constr

and type_record loc env r constr precise =
  if not (Types.Record.has_record constr) then
    should_have loc constr "but it is a record";
  let rconstr, res =
    List.fold_left
      (fun (rconstr, res) (l, e) ->
         let r = Types.Record.focus rconstr l in
         let pi = Types.Record.get_this r in
         (if Types.is_empty pi then
            let l = Label.string_of_attr l in
            should_have loc constr
              (Printf.sprintf "Field %s is not allowed here." l));
         let t = type_check env e pi (precise || Types.Record.need_others r) in
         let rconstr = Types.Record.constraint_on_others r t in
         (if Types.is_empty rconstr then
            let l = Label.string_of_attr l in
            should_have loc constr
              (Printf.sprintf "Type of field %s is not precise enough." l));

         let res = if precise then LabelMap.add l (Types.cons t) res else res in
         (rconstr, res))
      (constr, LabelMap.empty) (LabelMap.get r)
  in
  if not (Types.Record.has_empty_record rconstr) then
    should_have loc constr "More fields should be present";
  if precise then Types.record_fields (false, res) else constr

and type_check_branches ?expr loc env targ brs constr precise =
  if Types.is_empty targ then Types.empty
  else (
    brs.br_typ <- Types.cup brs.br_typ targ;
    branches_aux expr loc env targ
      (if precise then Types.empty else constr)
      constr precise brs.br_branches)

and branches_aux expr loc env targ tres constr precise = function
  | [] -> tres
  | b :: rem ->
    let p = refine_pat b.br_pat expr in
    let acc = Types.descr (Patterns.accept p) in
    let targ' = Types.cap targ acc in
    if Types.is_empty targ' then
      branches_aux expr loc env targ tres constr precise rem
    else (
      b.br_used <- true;
      let res = Patterns.filter targ' p in
      let res = IdMap.map Types.descr res in

      b.br_vars_empty <-
        IdMap.domain
          (IdMap.filter
             (fun x t -> Types.(subtype t Sequence.nil_type))
             (IdMap.restrict res b.br_vars_empty));

      let env' = enter_values (IdMap.get res) env in
      let t = type_check env' b.br_body constr precise in
      let tres = if precise then Types.cup t tres else tres in
      let targ'' = Types.diff targ acc in
      if Types.non_empty targ'' then
        branches_aux expr loc env targ'' tres constr precise rem
      else tres)

and type_map loc env def e b constr precise =
  let open Types in
  let acc = if def then Sequence.any else Sequence.star b.br_accept in
  let t = type_check env e acc true in

  let constr' = Sequence.approx (Types.cap Sequence.any constr) in
  let exact = Types.subtype (Sequence.star constr') constr in
  (* Note:
     - could be more precise by integrating the decomposition
       of constr inside Sequence.map.
  *)
  let res =
    Sequence.map
      (fun t ->
         let res =
           type_check_branches loc env t b constr' (precise || not exact)
         in
         if def && not (Types.subtype t b.br_accept) then (
           require loc Sequence.nil_type constr';
           Types.cup res Sequence.nil_type)
         else res)
      t
  in
  if exact then res else verify loc res constr

and type_let_decl env l =
  let acc = Types.descr (Patterns.accept l.let_pat) in
  let t = type_check env l.let_body acc true in
  let res = Patterns.filter t l.let_pat in
  IdMap.mapi_to_list (fun x t -> (x, Types.descr t)) res

and type_rec_funs env l =
  let typs =
    List.fold_left
      (fun accu -> function
         | {
           exp_descr = Abstraction { fun_typ = t; fun_name = Some f };
           exp_loc = loc;
         } ->
           if not (value_name_ok f env) then
             Cduce_error.raise_err_loc ~loc
               Typer_Error "This function name clashes with another kind of identifier";
           (f, t) :: accu
         | _ -> assert false)
      [] l
  in
  let env = enter_values typs env in
  List.iter (fun e -> ignore (type_check env e Types.any false)) l;
  typs

let rec unused_branches b =
  List.iter
    (fun (Branch (br, s)) ->
       if br.br_ghost then ()
       else if not br.br_used then warning br.br_loc "This branch is not used"
       else (
         (if not (IdSet.is_empty br.br_vars_empty) then
            let msg =
              try
                let l =
                  List.map
                    (fun x ->
                       let x = Ident.to_string x in
                       if String.compare x "$$$" = 0 then raise Exit else x)
                    (br.br_vars_empty :> Id.t list)
                in
                let l = String.concat "," l in
                "The following variables always match the empty sequence: " ^ l
              with
              | Exit -> "This projection always returns the empty sequence"
            in
            warning br.br_loc msg);
         unused_branches s))
    b

let report_unused_branches () =
  unused_branches !cur_branch;
  cur_branch := []

let clear_unused_branches () = cur_branch := []

(* API *)
let update_weak_variables env =
  let to_patch, to_keep =
    Var.Map.split
      (fun v -> function
         | Some _ -> true
         | None -> false)
      env.weak_vars
  in
  let to_patch =
    Var.Map.map
      (function
        | Some t -> t
        | None -> assert false)
      to_patch
  in
  {
    env with
    ids =
      Env.mapi
        (fun v -> function
           | Val t ->
             let tt = Types.Subst.apply_full to_patch t in
             Val tt
           | x -> x)
        env.ids;
    weak_vars = to_keep;
  }

let type_expr env e =
  clear_unused_branches ();
  let e = expr env e in
  let t = type_check env e Types.any true in
  report_unused_branches ();
  (update_weak_variables env, e, t)

let type_let_decl env p e =
  clear_unused_branches ();
  let decl = let_decl env p e in
  let typs = type_let_decl env decl in
  report_unused_branches ();
  let is_value = Typed.is_value decl.let_body in
  (* patch env to update weak variables *)
  let env = update_weak_variables env in
  let typs =
    List.map
      (fun (id, t) ->
         let tt = Types.Subst.clean_type Var.Set.empty t in
         let vars = Types.Subst.vars tt in
         if (not (Var.Set.is_empty vars)) && not is_value then
           let weak_vars, all_weak_vars, _ =
             Var.Set.fold
               (fun (acc, accw, i) v ->
                  let wv = Var.mk ~kind:`weak ("_weak" ^ string_of_int i) in
                  ((v, Types.var wv) :: acc, Var.Map.add wv None accw, i + 1))
               ([], env.weak_vars, Var.Map.length env.weak_vars)
               vars
           in
           let () = env.weak_vars <- all_weak_vars in
           let subst = Types.Subst.from_list weak_vars in
           (id, Types.Subst.apply_full subst tt)
         else
           (* raise_loc_generic p.loc
              (Format.asprintf
                 "The type of identifier %a is %a.@\n\
                  It contains polymorphic variables that cannot be generalized."
                 Ident.print id Types.Print.print tt);*)
           (id, tt))
      typs
  in
  let env = enter_values typs env in
  ( {
    env with
    ids_loc =
      List.fold_left
        (fun acc (id, _) -> Env.add id p.loc acc)
        env.ids_loc typs;
    mono_vars = Var.Set.empty;
    poly_vars = [];
  },
    decl,
    typs )

let type_let_funs env funs =
  clear_unused_branches ();
  let rec id = function
    | Ast.LocatedExpr (_, e) -> id e
    | Ast.Abstraction a -> fun_name env a
    | _ -> assert false
  in
  let ids =
    List.fold_left
      (fun accu f ->
         match id f with
         | Some x -> x :: accu
         | None -> accu)
      [] funs
  in
  let env' = enter_values_dummy ids env in
  let funs = List.map (expr env') funs in
  let typs = type_rec_funs env funs in
  report_unused_branches ();
  let env = update_weak_variables env in
  let env = enter_values typs env in
  let env = { env with mono_vars = Var.Set.empty; poly_vars = [] } in
  (env, funs, typs)

(*
let find_cu x env =
  match find_cu noloc x env with
    | ECDuce cu -> cu
    | _ -> raise (Error ("Cannot find external unit " ^ (U.to_string x)))
*)

let check_weak_variables env =
  Env.iter
    (fun id -> function
       | Val t ->
         let vrs = Types.Subst.vars t in
         Var.Set.iter
           (fun v ->
              match Var.kind v with
              | `generated
              | `user ->
                ()
              | `weak ->
                let loc = Env.find id env.ids_loc in
                Cduce_error.(raise_err_loc ~loc Typer_WeakVar (id, t)))
           vrs
       | _ -> ())
    env.ids