(* I. Transform the abstract syntax of types and patterns into the internal form *) open Location open Ast exception Pattern of string exception NonExhaustive of Types.descr exception MultipleLabel of Types.label exception Constraint of Types.descr * Types.descr * string exception ShouldHave of Types.descr * string exception WrongLabel of Types.descr * Types.label exception UnboundId of string let raise_loc loc exn = raise (Location (loc,exn)) (* Internal representation as a graph (desugar recursive types and regexp), to compute freevars, etc... *) type ti = { id : int; mutable loc' : loc; mutable fv : string SortedList.t option; mutable descr': descr; mutable type_node: Types.node option; mutable pat_node: Patterns.node option } and descr = [ `Alias of string * ti | `Type of Types.descr | `Or of ti * ti | `And of ti * ti | `Diff of ti * ti | `Times of ti * ti | `Arrow of ti * ti | `Record of Types.label * bool * ti | `Capture of Patterns.capture | `Constant of Patterns.capture * Types.const ] module S = struct type t = string let compare = compare end module StringMap = Map.Make(S) module StringSet = Set.Make(S) let mk' = let counter = ref 0 in fun loc -> incr counter; let rec x = { id = !counter; loc' = loc; fv = None; descr' = `Alias ("__dummy__", x); type_node = None; pat_node = None } in x let cons loc d = let x = mk' loc in x.descr' <- d; x (* Note: Compilation of Regexp is implemented as a ``rewriting'' of the parsed syntax, in order to be able to print its result (for debugging for instance) It would be possible (and a little more efficient) to produce directly ti nodes. *) module Regexp = struct let memo = Hashtbl.create 51 let defs = ref [] let name = let c = ref 0 in fun () -> incr c; "#" ^ (string_of_int !c) let rec seq_vars accu = function | Epsilon | Elem _ -> accu | Seq (r1,r2) | Alt (r1,r2) -> seq_vars (seq_vars accu r1) r2 | Star r | WeakStar r -> seq_vars accu r | SeqCapture (v,r) -> seq_vars (StringSet.add v accu) r let rec propagate vars = function | Epsilon -> `Epsilon | Elem x -> `Elem (vars,x) | Seq (r1,r2) -> `Seq (propagate vars r1,propagate vars r2) | Alt (r1,r2) -> `Alt (propagate vars r1, propagate vars r2) | Star r -> `Star (propagate vars r) | WeakStar r -> `WeakStar (propagate vars r) | SeqCapture (v,x) -> propagate (StringSet.add v vars) x let cup r1 r2 = match (r1,r2) with | (_, `Empty) -> r1 | (`Empty, _) -> r2 | (`Res t1, `Res t2) -> `Res (mk noloc (Or (t1,t2))) let rec compile fin e seq : [`Res of Ast.ppat | `Empty] = if List.mem seq e then `Empty else let e = seq :: e in match seq with | [] -> `Res fin | `Epsilon :: rest -> compile fin e rest | `Elem (vars,x) :: rest -> let capt = StringSet.fold (fun v t -> mk noloc (And (t, (mk noloc (Capture v))))) vars x in `Res (mk noloc (Prod (capt, guard_compile fin rest))) | `Seq (r1,r2) :: rest -> compile fin e (r1 :: r2 :: rest) | `Alt (r1,r2) :: rest -> cup (compile fin e (r1::rest)) (compile fin e (r2::rest)) | `Star r :: rest -> cup (compile fin e (r::seq)) (compile fin e rest) | `WeakStar r :: rest -> cup (compile fin e rest) (compile fin e (r::seq)) and guard_compile fin seq = try Hashtbl.find memo seq with Not_found -> let n = name () in let v = mk noloc (PatVar n) in Hashtbl.add memo seq v; let d = compile fin [] seq in (match d with | `Empty -> assert false | `Res d -> defs := (n,d) :: !defs); v let atom_nil = Types.mk_atom "nil" let constant_nil v t = mk noloc (And (t, (mk noloc (Constant (v, Types.Atom atom_nil))))) let compile regexp queue : ppat = let vars = seq_vars StringSet.empty regexp in let fin = StringSet.fold constant_nil vars queue in let n = guard_compile fin [propagate StringSet.empty regexp] in Hashtbl.clear memo; let d = !defs in defs := []; mk noloc (Recurs (n,d)) end let compile_regexp = Regexp.compile let rec compile env { loc = loc; descr = d } : ti = match (d : Ast.ppat') with | PatVar s -> (try StringMap.find s env with Not_found -> raise_loc loc (Pattern ("Undefined type variable " ^ s)) ) | Recurs (t, b) -> compile (compile_many env b) t | Regexp (r,q) -> compile env (Regexp.compile r q) | Internal t -> cons loc (`Type t) | Or (t1,t2) -> cons loc (`Or (compile env t1, compile env t2)) | And (t1,t2) -> cons loc (`And (compile env t1, compile env t2)) | Diff (t1,t2) -> cons loc (`Diff (compile env t1, compile env t2)) | Prod (t1,t2) -> cons loc (`Times (compile env t1, compile env t2)) | Arrow (t1,t2) -> cons loc (`Arrow (compile env t1, compile env t2)) | Record (l,o,t) -> cons loc (`Record (l,o,compile env t)) | Constant (x,v) -> cons loc (`Constant (x,v)) | Capture x -> cons loc (`Capture x) and compile_many env b = let b = List.map (fun (v,t) -> (v,t,mk' t.loc)) b in let env = List.fold_left (fun env (v,t,x) -> StringMap.add v x env) env b in List.iter (fun (v,t,x) -> x.descr' <- `Alias (v, compile env t)) b; env let rec comp_fv seen s = match s.fv with | Some l -> l | None -> let l = match s.descr' with | `Alias (_,x) -> if List.memq s seen then [] else comp_fv (s :: seen) x | `Or (s1,s2) | `And (s1,s2) | `Diff (s1,s2) | `Times (s1,s2) | `Arrow (s1,s2) -> SortedList.cup (comp_fv seen s1) (comp_fv seen s2) | `Record (l,opt,s) -> comp_fv seen s | `Type _ -> [] | `Capture x | `Constant (x,_) -> [x] in if seen = [] then s.fv <- Some l; l let fv = comp_fv [] let rec typ seen s : Types.descr = match s.descr' with | `Alias (v,x) -> if List.memq s seen then raise_loc s.loc' (Pattern ("Unguarded recursion on variable " ^ v ^ " in this type")) else typ (s :: seen) x | `Type t -> t | `Or (s1,s2) -> Types.cup (typ seen s1) (typ seen s2) | `And (s1,s2) -> Types.cap (typ seen s1) (typ seen s2) | `Diff (s1,s2) -> Types.diff (typ seen s1) (typ seen s2) | `Times (s1,s2) -> Types.times (typ_node s1) (typ_node s2) | `Arrow (s1,s2) -> Types.arrow (typ_node s1) (typ_node s2) | `Record (l,o,s) -> Types.record l o (typ_node s) | `Capture _ | `Constant _ -> assert false and typ_node s : Types.node = match s.type_node with | Some x -> x | None -> let x = Types.make () in s.type_node <- Some x; let t = typ [] s in Types.define x t; x let type_node s = Types.internalize (typ_node s) let rec pat seen s : Patterns.descr = if fv s = [] then Patterns.constr (type_node s) else match s.descr' with | `Alias (v,x) -> if List.memq s seen then raise_loc s.loc' (Pattern ("Unguarded recursion on variable " ^ v ^ " in this pattern")) else pat (s :: seen) x | `Or (s1,s2) -> Patterns.cup (pat seen s1) (pat seen s2) | `And (s1,s2) -> Patterns.cap (pat seen s1) (pat seen s2) | `Diff (s1,s2) when fv s2 = [] -> let s2 = Types.cons (Types.neg (Types.descr (type_node s2)))in Patterns.cap (pat seen s1) (Patterns.constr s2) | `Diff _ -> raise_loc s.loc' (Pattern "Difference not allowed in patterns") | `Times (s1,s2) -> Patterns.times (pat_node s1) (pat_node s2) | `Record (l,false,s) -> Patterns.record l (pat_node s) | `Record _ -> raise_loc s.loc' (Pattern "Optional field not allowed in record patterns") | `Capture x -> Patterns.capture x | `Constant (x,c) -> Patterns.constant x c | `Arrow _ -> raise_loc s.loc' (Pattern "Arrow not allowed in patterns") | `Type _ -> assert false and pat_node s : Patterns.node = match s.pat_node with | Some x -> x | None -> let x = Patterns.make (fv s) in s.pat_node <- Some x; let t = pat [] s in Patterns.define x t; x let global_types = ref StringMap.empty let mk_typ e = if fv e = [] then type_node e else raise_loc e.loc' (Pattern "Capture variables are not allowed in types") let typ e = mk_typ (compile !global_types e) let pat e = let e = compile !global_types e in pat_node e let register_global_types b = let env = compile_many !global_types b in List.iter (fun (v,_) -> let d = Types.descr (mk_typ (StringMap.find v env)) in let d = Types.normalize d in Types.Print.register_global v d ) b; global_types := env (* II. Build skeleton *) module Fv = StringSet let rec expr { loc = loc; descr = d } = let (fv,td) = match d with | DebugTyper t -> (Fv.empty, Typed.DebugTyper (typ t)) | Var s -> (Fv.singleton s, Typed.Var s) | Apply (e1,e2) -> let (fv1,e1) = expr e1 and (fv2,e2) = expr e2 in (Fv.union fv1 fv2, Typed.Apply (e1,e2)) | Abstraction a -> let iface = List.map (fun (t1,t2) -> (typ t1, typ t2)) a.fun_iface 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 (fv0,body) = branches a.fun_body in let fv = match a.fun_name with | None -> fv0 | Some f -> Fv.remove f fv0 in (fv, Typed.Abstraction { Typed.fun_name = a.fun_name; Typed.fun_iface = iface; Typed.fun_body = body; Typed.fun_typ = t; Typed.fun_fv = Fv.elements fv0 } ) | Cst c -> (Fv.empty, Typed.Cst c) | Pair (e1,e2) -> let (fv1,e1) = expr e1 and (fv2,e2) = expr e2 in (Fv.union fv1 fv2, Typed.Pair (e1,e2)) | Dot (e,l) -> let (fv,e) = expr e in (fv, Typed.Dot (e,l)) | RecordLitt r -> let fv = ref Fv.empty in let r = List.sort (fun (l1,_) (l2,_) -> compare l1 l2) r in let r = List.map (fun (l,e) -> let (fv2,e) = expr e in fv := Fv.union !fv fv2; (l,e)) r in let rec check = function | (l1,_) :: (l2,_) :: _ when l1 = l2 -> raise_loc loc (MultipleLabel l1) | _ :: rem -> check rem | _ -> () in check r; (!fv, Typed.RecordLitt r) | Op (op,le) -> let (fvs,ltes) = List.split (List.map expr le) in let fv = List.fold_left Fv.union Fv.empty fvs in (fv, Typed.Op (op,ltes)) | Match (e,b) -> let (fv1,e) = expr e and (fv2,b) = branches b in (Fv.union fv1 fv2, Typed.Match (e, b)) | Map (e,b) -> let (fv1,e) = expr e and (fv2,b) = branches b in (Fv.union fv1 fv2, Typed.Map (e, b)) in fv, { Typed.exp_loc = loc; Typed.exp_typ = Types.empty; Typed.exp_descr = td; } and branches b = let fv = ref Fv.empty in let accept = ref Types.empty in let b = List.map (fun (p,e) -> let (fv2,e) = expr e in fv := Fv.union !fv fv2; let p = pat p in accept := Types.cup !accept (Types.descr (Patterns.accept p)); { Typed.br_used = false; Typed.br_pat = p; Typed.br_body = e } ) b in (!fv, { Typed.br_typ = Types.empty; Typed.br_branches = b; Typed.br_accept = !accept; Typed.br_compiled = None; } ) module Env = StringMap open Typed let check loc t s msg = if not (Types.subtype t s) then raise_loc loc (Constraint (t, s, msg)) let rec type_check env e constr precise = (* Format.fprintf Format.std_formatter "constr=%a precise=%b@\n" Types.Print.print_descr constr precise; *) let d = type_check' e.exp_loc env e.exp_descr constr precise in e.exp_typ <- Types.cup e.exp_typ d; d and type_check' loc env e constr precise = match e with | Abstraction a -> let t = try Types.Arrow.check_strenghten a.fun_typ constr with Not_found -> raise_loc loc (ShouldHave (constr, "but the interface of the abstraction is not compatible")) in let env = match a.fun_name with | None -> env | Some f -> Env.add f a.fun_typ env in List.iter (fun (t1,t2) -> 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 | Pair (e1,e2) -> let rects = Types.Product.get constr in if Types.Product.is_empty rects then raise_loc loc (ShouldHave (constr,"but it is a pair.")); let pi1 = Types.Product.pi1 rects in let t1 = type_check env e1 (Types.Product.pi1 rects) (precise || (Types.Product.need_second rects))in let rects = Types.Product.restrict_1 rects t1 in let t2 = type_check env e2 (Types.Product.pi2 rects) precise in if precise then Types.times (Types.cons t1) (Types.cons t2) else constr | RecordLitt r -> let rconstr = Types.Record.get constr in if Types.Record.is_empty rconstr then raise_loc loc (ShouldHave (constr,"but it is a record.")); let (rconstr,res) = List.fold_left (fun (rconstr,res) (l,e) -> let rconstr = Types.Record.restrict_label_present rconstr l in let pi = Types.Record.project_field rconstr l in if Types.Record.is_empty rconstr then raise_loc loc (ShouldHave (constr,(Printf.sprintf "Field %s is not allowed here." (Types.label_name l) ) )); let t = type_check env e pi true in let rconstr = Types.Record.restrict_field rconstr l t in let res = if precise then Types.cap res (Types.record l false (Types.cons t)) else res in (rconstr,res) ) (rconstr, if precise then Types.Record.any else constr) r in res | Map (e,b) -> let t = type_check env e (Sequence.star b.br_accept) true in let constr' = Sequence.approx (Types.cap Sequence.any constr) in let exact = Types.subtype (Sequence.star constr') constr in if exact then ( (* Note: typing mail fail because of the approx on t *) let res = type_check_branches loc env (Sequence.approx t) b constr' precise in if precise then Sequence.star res else constr ) else (* Note: - could be more precise by integrating the decomposition of constr inside Sequence.map. *) let res = Sequence.map (fun t -> type_check_branches loc env t b constr' true) t in if not exact then check loc res constr ""; if precise then res else constr | Op ("@", [e1;e2]) -> let constr' = Sequence.star (Sequence.approx (Types.cap Sequence.any constr)) in let exact = Types.subtype constr' constr in if exact then let t1 = type_check env e1 constr' precise and t2 = type_check env e2 constr' precise in if precise then Sequence.concat t1 t2 else constr else (* Note: the knownledge of t1 may makes it useless to check t2 with 'precise' ... *) let t1 = type_check env e1 constr' true and t2 = type_check env e2 constr' true in let res = Sequence.concat t1 t2 in check loc res constr ""; if precise then res else constr | Op ("flatten", [e]) -> 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 = type_check env e sconstr' precise in if precise then Sequence.flatten t else constr else let t = type_check env e sconstr' true in let res = Sequence.flatten t in check loc res constr ""; if precise then res else constr | _ -> let t : Types.descr = compute_type' loc env e in check loc t constr ""; t and compute_type env e = type_check env e Types.any true and compute_type' loc env = function | DebugTyper t -> Types.descr t | Var s -> (try Env.find s env with Not_found -> raise_loc loc (UnboundId s) ) | Apply (e1,e2) -> let t1 = type_check env e1 Types.Arrow.any true in let t1 = Types.Arrow.get t1 in let dom = Types.Arrow.domain t1 in 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) | Cst c -> Types.constant c | Dot (e,l) -> let t = type_check env e Types.Record.any true in (try (Types.Record.project t l) with Not_found -> raise_loc loc (WrongLabel(t,l))) | Op (op, el) -> let args = List.map (fun e -> (e.exp_loc, compute_type env e)) el in type_op loc op args | Map (e,b) -> let t = compute_type env e in Sequence.map (fun t -> type_check_branches loc env t b Types.any true) t (* We keep these cases here to allow comparison and benchmarking ... Just comment the corresponding cases in type_check' to activate these ones. *) | Pair (e1,e2) -> let t1 = compute_type env e1 and t2 = compute_type env e2 in Types.times (Types.cons t1) (Types.cons t2) | RecordLitt r -> List.fold_left (fun accu (l,e) -> let t = compute_type env e in let t = Types.record l false (Types.cons t) in Types.cap accu t ) Types.Record.any r | _ -> assert false and type_check_branches 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 loc env targ (if precise then Types.empty else constr) constr precise brs.br_branches ) and branches_aux loc env targ tres constr precise = function | [] -> raise_loc loc (NonExhaustive targ) | b :: rem -> let p = b.br_pat in let acc = Types.descr (Patterns.accept p) in let targ' = Types.cap targ acc in if Types.is_empty targ' then branches_aux loc env targ tres constr precise rem else ( b.br_used <- true; let res = Patterns.filter targ' p in let env' = List.fold_left (fun env (x,t) -> Env.add x (Types.descr t) env) env res 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 loc env targ'' tres constr precise rem else tres ) and type_op loc op args = match (op,args) with | "+", [loc1,t1; loc2,t2] -> type_int_binop Intervals.add loc1 t1 loc2 t2 | "-", [loc1,t1; loc2,t2] -> type_int_binop Intervals.sub loc1 t1 loc2 t2 | ("*" | "/"), [loc1,t1; loc2,t2] -> type_int_binop (fun i1 i2 -> Intervals.any) loc1 t1 loc2 t2 | "@", [loc1,t1; loc2,t2] -> check loc1 t1 Sequence.any "The first argument of @ must be a sequence"; Sequence.concat t1 t2 | "flatten", [loc1,t1] -> check loc1 t1 Sequence.seqseq "The argument of flatten must be a sequence of sequences"; Sequence.flatten t1 | _ -> assert false and type_int_binop f loc1 t1 loc2 t2 = if not (Types.Int.is_int t1) then raise_loc loc1 (Constraint (t1,Types.Int.any, "The first argument must be an integer")); if not (Types.Int.is_int t2) then raise_loc loc2 (Constraint (t2,Types.Int.any, "The second argument must be an integer")); Types.Int.put (f (Types.Int.get t1) (Types.Int.get t2));