Skip to content
Snippets Groups Projects
unifyType.ml 8.02 KiB
Newer Older
(** Time-stamp: <modified the 03/03/2009 (at 18:00) by Erwan Jahier> *)
open Eff
  | Unif of Eff.type_
  | Ko of string (* a msg explaining why the unification failed *)

let teff2str = LicDump.string_of_type_eff4msg
let (is_overloadable : Eff.type_ -> bool) = function
  | Int_type_eff -> true
  | Real_type_eff -> true
  | _ -> false
  

(* [contains t1 t2] is true iff t2 appears in t1 *)
let rec (contains : Eff.type_ -> Eff.type_ -> bool) =
  fun t1 t2 -> 
    if t1 = t2 then true else
    match t1 with
    | Any 
    | Overload
    | Bool_type_eff 
    | Int_type_eff 
    | Real_type_eff 
    | Enum_type_eff(_,_) 
    | External_type_eff _ -> false
    | Abstract_type_eff (_, teff) -> false
    | Array_type_eff(teff,i) -> contains teff t2
    | Struct_type_eff(l, fl) -> 
	List.exists (fun (_,(teff,_)) -> contains teff t2) fl
    

(* exported *)
let (f : Eff.type_ list -> Eff.type_ list -> t) = fun l1 l2 -> 
  let rec (unify_type_eff : Eff.type_ -> Eff.type_ -> t) =
    fun t1 t2 -> 
      if t1 = t2 then Equal else  
	match (t1,t2) with
	  | Array_type_eff(teff_ext1,i1), Array_type_eff(teff_ext2,i2) -> 
	      if i1 <> i2 then Ko "\n*** incompatible array size" else
		unify_type_eff teff_ext1 teff_ext2
		  
	  | Struct_type_eff(l1, fl1), Struct_type_eff(l2, fl2) -> 
	      if l1 <> l2 then Ko "\n*** incompatible structure" else
		let fl1 = List.map (fun (_,(te,_)) -> te) fl1
		and fl2 = List.map (fun (_,(te,_)) -> te) fl2 in
		  List.fold_left2 unify_do_acc Equal fl1 fl2
		    
	  | Overload, Any
	  | Any, Overload -> Unif Overload

	  | t, Any 
	  | Any, t -> 
	      if contains t Any || contains t Overload then 
		Ko(("\n*** " ^ teff2str t1) ^ " and " ^ (teff2str t2) ^ 
		     " are not unifiable (there is a cycle)") 
	      else 
		Unif t

	  | t, Overload 
	  | Overload, t -> 
	      if contains t Any || contains t Overload then 
		Ko("\n*** " ^ (teff2str t1) ^ " and " ^ (teff2str t2) ^ 
		     " are not unifiable (there is a cycle)") 
	      else if is_overloadable t then 
		Unif t 
	      else 
		Ko("\n*** " ^ (teff2str t) ^ " should be an integer or a real")
	      Ko("\n*** " ^ (teff2str t1) ^ " and " ^ (teff2str t2) ^
  and (unify_do_acc: t -> Eff.type_ -> Eff.type_ -> t) =
    fun acc te1 te2 -> 
      match acc, unify_type_eff te1 te2 with
	| Equal, Equal -> Equal
	| Ko msg, _ 
	| _, Ko msg -> Ko msg
	| Unif t, Equal
	| Equal, Unif t -> Unif t
	| Unif t1, Unif t2 -> if t1 = t2 then acc else 
	    Ko("\n*** " ^ (teff2str t1) ^ " and " ^ (teff2str t2) ^
    assert (List.length l1 = List.length l2);
    List.fold_left2 unify_do_acc Equal l1 l2


(************************************************************************************)
(* Some unit tests *)
let i = Int_type_eff
let r = Real_type_eff
let b = Bool_type_eff
let e = External_type_eff (Ident.long_of_string "Toto::t")
let array t c = Array_type_eff(t,c)
let struc t = Struct_type_eff ((Ident.long_of_string "T::t"), 
				   [(Ident.of_string "x"),(t,None)])

let unify_failed = function Ko(_) -> true | _  -> false
let to_string = function 
  | Ko(msg) -> msg
  | Equal -> "types are equals\n"
  | Unif t -> "types are unifiable via " ^ (teff2str t) ^ "\n"

  

let proposition1 t1 t2 =
  (* two lists of type are unifiable iff there exists a substitution
     that makes them equal. Hence, if [f] and [subst_type] are
     correct, the following function should always return true *)
  match f t1 t2 with
    | Unif t -> List.map (subst_type t) t1 = List.map (subst_type t) t2
    | _ -> true

(* Since i don't know how to prove proposition1, I generate some random tests *)
let rec gen_random_type_eff () = 
  match Random.int 8 with
    | 0 -> b
    | 1 -> i
    | 2 -> r
    | 3 -> e
    | 4 -> array (gen_random_type_eff ()) 42
    | 5 -> struc (gen_random_type_eff ())
    | 6 -> Any
    | _ -> Overload 

let gen_unifiable_typeff_of_size size =
  let rec aux tl1 tl2 =
    let ntl1 = (gen_random_type_eff ())::tl1 
    and ntl2 = (gen_random_type_eff ())::tl2 in
      if unify_failed (f ntl1 ntl2) then
	aux tl1 tl2
      else
	if List.length ntl1 = size then (ntl1,ntl2) else aux ntl1 ntl2
  in
    aux [] []


let (type_eff_list_to_string : Eff.type_ list -> string) =
  fun tel -> 
    let str_l = List.map LicDump.string_of_type_eff4msg tel in
      String.concat "*" str_l    


let unit_test () = 
  assert( f [i;r;b] [i;r;b] = Equal  );
  assert( f [i;r;b] [i;r;a] = Unif b );
  assert( f [i;r;a] [i;r;a] = Equal  );
  assert( f [i;a;b] [i;b;a] = Unif b );
  assert( f [a] [o]         = Unif o );
  assert( unify_failed(f [a] [ array o 3]) );
  assert( unify_failed(f [a] [ array a 3]) );
  assert( unify_failed(f [o] [ array o 3]) );
  assert( unify_failed(f [array i 4] [array o 3]));
  assert( unify_failed(f [a;a] [r;b]));

  for i = 1 to 1000 do
    let (tl1, tl2) = gen_unifiable_typeff_of_size (1+ Random.int 10) in
	" ==> try UnifyType.proposition1 with lists " ^ 
	  (type_eff_list_to_string tl1) ^ " and " ^ 
	  (type_eff_list_to_string tl2) ^ "\n");
      assert (proposition1 tl1 tl2)
  done

(*****************************************)
(* Check that the interface and the implementation types profile
   declarations are compatible.

   The idea is to consider abstract types as alpha types. The problem
   is that my unification algo works for one type variable only.
   However, it is a very simple [*] case of unification with several
   alpha var, that does not require the general unification
   algorithm. By considering variables one by one, I can use [UnifyType.f].

   [*] it is simpler than the general case because we can have alpha
   var in the provide part.

*)
let (profile_is_compatible: node_key -> Lxm.t -> Eff.type_ list * Eff.type_ list -> 
      Eff.type_ list * Eff.type_ list -> Eff.type_ option) =
  fun nk lxm (ins1, ins2) (outs1, outs2) -> 
    let to_any t = match t with (* consider abstract types as alpha types *)
      | Abstract_type_eff(name, _) -> Any
      | t -> t
    in
    let sot = LicDump.string_of_type_eff4msg in
    let msg_prefix = ("provided node for " ^ (Ident.string_of_long (fst nk)) ^ 
                        " is not compatible with its implementation: ")
    in
    let apply_subst s t = try List.assoc t s with Not_found -> t in
    let unif_types (subst,topt) t_prov t_body = 
      if t_body = Any || t_body = Overload then
        (* Migth occur when a model is instanciated with a polymorphic operator,
           such as Lustre::eq. In such a case, we obtain an Any or an Overload 
           from the implementation part ; the solution then is to replace that Any
           type by the type of the provided part.
        *)
        ( 
          assert (t_prov <> Any); 
          assert (t_prov <> Overload); 
          assert (topt = None || topt = Some t_prov); (* at most one type var per op *)
          (subst, Some t_prov) 
        )
      else
        let t_prov = apply_subst subst t_prov and t_body = apply_subst subst t_body in
        let nt_prov = to_any t_prov in
          match f [nt_prov] [t_body] with
            | Equal -> subst, topt
            | Unif t -> 
                (match t_prov with
                   | Abstract_type_eff(_, tbody) -> 
                       if tbody <> t && t_prov <> t_body then
                         let msg = msg_prefix ^ "\n*** "^(sot tbody)^" <> "^(sot t) in
                           raise(Errors.Compile_error (lxm, msg))
                   | _ -> ()
                );
                (t_prov,t)::subst, topt
            | Ko(msg) -> raise(Errors.Compile_error (lxm, msg_prefix ^ msg))
    in

    let acc = [], None in
    let _ = 
      if List.length ins1 <> List.length ins2 then 
        raise(Errors.Compile_error (lxm, msg_prefix ^ " wrong number of inputs"));
      if List.length outs1 <> List.length outs2 then 
        raise(Errors.Compile_error (lxm, msg_prefix ^ " wrong number of outputs"))
    in
    let acc = List.fold_left2 unif_types acc ins1 ins2 in
    let acc = List.fold_left2 unif_types acc outs1 outs2 in
      snd acc