(**************************************************************************)

(*  Mini, a type inference engine based on constraint solving.            *)
(*  Copyright (C) 2006. François Pottier, Yann Régis-Gianas               *)
(*  and Didier Rémy.                                                      *)
(*                                                                        *)
(*  This program is free software; you can redistribute it and/or modify  *)
(*  it under the terms of the GNU General Public License as published by  *)
(*  the Free Software Foundation; version 2 of the License.               *)
(*                                                                        *)
(*  This program is distributed in the hope that it will be useful, but   *)
(*  WITHOUT ANY WARRANTY; without even the implied warranty of            *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *)
(*  General Public License for more details.                              *)
(*                                                                        *)
(*  You should have received a copy of the GNU General Public License     *)
(*  along with this program; if not, write to the Free Software           *)
(*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA         *)
(*  02110-1301 USA                                                        *)
(*                                                                        *)
(**************************************************************************)


(* $Id: constraint.ml 421 2006-12-22 09:27:42Z regisgia $ *)

open Positions
open Misc
open MultiEquation
open CoreAlgebra

(** sname is the type of the names that are used to refer to type schemes inside constraints. These names are bound by CLet constraints and referred to by CInstance constraints. *)

type sname = SName of string

(** type_constraint defines a syntax for the constraints between types. *)

type ('crterm, 'variable) type_constraint =
  | CTrue of position
  | CDump of position
  | CEquation of position * 'crterm * 'crterm
  | CConjunction of ('crterm, 'variable) type_constraint list
  | CLet of ('crterm, 'variable) scheme list 
      * ('crterm, 'variable) type_constraint
  | CInstance of position * sname * 'crterm
  | CDisjunction of ('crterm, 'variable) type_constraint list

(** A type scheme is a pair of a constraint c and a header h, wrapped within two sets of universal quantifiers rqs and fqs. The former are considered rigid, while the latter are considered flexible. More precisely, for the type scheme to be considered consistent, the constraint forall rqs.exists fqs.c must hold. Rigid and flexible quantifiers otherwise play the same role, that is, they all end up universally quantified in the type scheme. A header is a mapping of names to types. *)

and ('crterm, 'variable) scheme =
  | Scheme of position * 'variable list * 'variable list 
      * ('crterm, 'variable) type_constraint * ('crterm * position) StringMap.t

type variable = MultiEquation.variable

type variable_kind = MultiEquation.variable_kind

type crterm =
    variable arterm

type tconstraint =
    (crterm, variable) type_constraint

type tscheme = 
    (crterm, variable) scheme

(* TEMPORARY ne pas oublier d'expliquer que les rangs et les pools ne sont
   pas toujours d'accord entre eux *)


let rec expand_term = function
  | App (l, r) -> 
      TTerm (map (fun v -> TVariable v ) (App (l, r)))

  | _ -> assert false

let rec expand_term_in_depth t = 
  let expand v = 
    let desc = UnionFind.find v in
      match desc.structure with
        | None -> TVariable v
        | Some t -> expand_term_in_depth t
  in
    TTerm (map expand t)

let rec cposition = function
  | CTrue pos -> 
      pos

  | CDump pos -> 
      pos

  | CLet ([], c) -> 
      cposition c

  | (CConjunction [] | CDisjunction []) -> 
      undefined_position

  | (CConjunction l | CDisjunction l) -> 
      join (cposition (List.hd l)) (cposition (last l))

  | CLet (l, _) ->
      join (sposition (List.hd l)) (sposition (last l))

  | CEquation (p, _, _) -> 
      p

  | CInstance (p, _, _) -> 
      p

and sposition = function 
  | Scheme (p, _, _, _, _) -> 
      p

(* TEMPORARY expliquer qu' on emploie la pile native pour les let 
   et conj. frames, plus des pools s'epar'es pour les let frames *)


(** x <? t is an instance constraint. *)

let (<?) x t pos =
  CInstance (pos, x, t)

(** t1 =?= t2 is an equality constraint. *)

let (=?=) t1 t2 pos =
  CEquation (pos, t1, t2)

(** c1 ^ c2 is a conjunction constraint. *)

let (^) c1 c2 =
  match c1, c2 with
    | CTrue _, c
    | c, CTrue _ ->
      c
    | c, CConjunction cl ->
        CConjunction (c :: cl)
    | _, _ ->
        CConjunction [c1; c2]

let conj cs = 
  List.fold_left ( ^ ) (CTrue undefined_position) cs

(** ex qs c returns the constraint exists qs.c. We encode existential constraints in terms of let constraints, since the latter are more general. *)

let ex ?pos qs c =
  CLet ([ Scheme (pos_or_undef pos, [], qs, c, StringMap.empty) ], 
        CTrue (pos_or_undef pos))

(** fl qs c returns the constraint forall qs.c. We encode universal constraints in terms of let constraints, since the latter are more general. *)

let fl ?pos qs c =
  CLet ([ Scheme (pos_or_undef pos, qs, [], c, StringMap.empty) ], 
        CTrue (pos_or_undef pos))

(** exists f creates a fresh variable v and returns the constraint exists v.(f v). *)

let exists ?pos f =
  let v = variable Flexible () in
  let c = f (TVariable v) in
  ex ~pos:(pos_or_undef pos) [ v ] c

let exists3 ?pos f = 
  exists (fun x -> exists (fun y -> exists (fun z -> f x y z)))

(** exists_list l f associates a fresh variable with every element in the list l, yielding an association list m, and returns the constraint exists m.(f m). *)

let exists_list ?pos l f =
  let l, m = variable_list Flexible l in
  ex ?pos:pos l (f m)

(** forall_list l f associates a fresh variable with every element in the list l, yielding an association list m, and returns the constraint forall m.(f m). *)

let forall_list ?pos l f =
  let l, m = 
    List.fold_right (fun x (vs, xts) ->
                       let v = variable Rigid ~name:x () in
                         v :: vs, (x, TVariable v) :: xts
                    ) l ([], []) 
  in
  fl ~pos:(pos_or_undef pos) l (f m)

(** exists_set names f associates a fresh variable with every name in the set names, yielding a map m of names to variables, and returns the constraint exists m.(f m). *)

let exists_set ?pos names f =
  let l, m = variable_set (const (FlexibleNone)) names in
  ex ~pos:(pos_or_undef pos) l (f m)

(** monoscheme header turns header into a monomorphic type scheme. *)

let monoscheme ?pos header =
  Scheme (pos_or_undef pos, [], [], CTrue (pos_or_undef pos), header)

(** scheme rqs names f associates a fresh variable with every name in the set names, yielding a map m of names to variables, and returns the type scheme forall rqs m [f m] m, where the variables in rqs are rigid and the variables in m are flexible. *)

let scheme ?pos rqs names f =
  let l, m = variable_set (const (FlexibleNone)) names in
    Scheme (pos_or_undef pos, rqs, l, f m, m)

(** scheme' rqs rnames fnames f associates a fresh variable with every name in the set fnames and rnames, yielding a map m of names to variables, and returns the type scheme forall (rqs @ rm) fm [f m] m, where the variables in rqs and rm are rigid and the variables in fm are flexible. *)

let scheme' ?pos rqs rnames fnames f =
  let fl, fm = variable_set (const (FlexibleNone)) fnames in
  let rl, rm = variable_set (fun v -> (RigidSome v)) rnames in
  let m = map_union fm rm in
  Scheme (pos_or_undef pos, rqs @ rl, fl, f m, m)