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

(*  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: miniTypes.ml 421 2006-12-22 09:27:42Z regisgia $ *)

(** This module transforms types from the user's syntax to the internal representation of the inference engine. *)


open Positions
open Misc
open MiniKindInferencer
open Constraint
open MiniAlgebra
open CoreAlgebra
open MultiEquation
open MiniTypingEnvironment
open MiniTypingExceptions
open MiniAst

let rec extract_type = function
    
  | ETypeConstraint (_, e, typ) ->
      typ, e

  | ELambda (pos, PTypeConstraint (pos', p, typ1), e2) ->
      let typ2, e2 = extract_type e2 in
        TypApp (pos', TypVar (pos', TName "->"), [typ1; typ2]), 
        ELambda (pos, p, e2)

  | _ ->
      raise Not_found

type recursive_value_definition_kind =
  | Implicit of name * expression
  | Explicit of name * typ * expression
  | NotPVar
      
(** explicit_or_implicit examines a value definition and determines whether it carries an explicit type annotation. It optionally checks that the left-hand side is a variable. *)

let rec explicit_or_implicit p e =
  match p with
    | PTypeConstraint (pos, p, typ) ->
        explicit_or_implicit p (ETypeConstraint (pos, e, typ))
          
    | PVar (_, name) -> (
        try
          let typ, e = extract_type e in
            Explicit (name, typ, e)
        with Not_found ->
          Implicit (name, e)
      )

    | _ -> NotPVar
        
(**

From user's syntax to internal term representation

*)

        
let variables_of_typ = 
  let rec vtyp acu = function
    | TypVar (_, TName x) -> 
        StringSet.add x acu

    | TypApp (_, t, ts) -> 
        List.fold_left vtyp (vtyp acu t) ts

    | TypRowCons (_, attributes, t) -> 
        List.fold_left vtyp (vtyp acu t) (assoc_proj2 attributes)

    | TypRowUniform (_, x) -> 
        vtyp acu x
  in
    vtyp StringSet.empty 

let arrow tenv = 
  arrow (typcon_variable tenv)

let rec type_of_args t =
  let rec chop acu = function
    | TypApp (_, TypVar (_, TName "->"), [ t1; t2 ]) -> 
        chop (t1 :: acu) t2

    | t -> 
        acu 
  in List.rev (chop [] t)

let arity t =
  List.length (type_of_args t)
       
let tycon tenv t =
  app (lookup_type_variable tenv t) 
    
let rec intern' pos tenv ty : crterm = 
  match ty with
      
    | TypVar (pos, name) -> 
        as_fun tenv name

    | TypApp (pos, t, typs) ->
        let iargs = List.map (intern' pos tenv) typs in
          app (intern' pos tenv t) iargs 

    | TypRowCons (pos, attributes, t) ->
        let typed_labels = 
          List.map (fun (l, t) -> l, intern' pos tenv t) attributes
        in
          n_rowcons typed_labels (intern' pos tenv t)

    | TypRowUniform (pos, t) ->
        uniform (intern' pos tenv t)

(** intern tenv typ converts the type expression typ to a type. The environment tenv maps type identifiers to types. *)

let rec intern pos tenv ty = 
  let kind_env = as_kind_env tenv in
  let _ = MiniKindInferencer.check pos kind_env ty star in 
    intern' pos tenv ty 

let intern_let_env pos tenv rs fs = 
  let fqs, rtenv = fresh_flexible_vars pos tenv fs in
  let rqs, rtenv' = fresh_rigid_vars pos tenv rs in
    rqs, fqs, add_type_variables (rtenv @ rtenv') tenv 

(** intern_scheme tenv name qs typ produces a type scheme that binds name to forall qs.typ. *)

let intern_scheme pos tenv name qs typ =
  let fqs, rtenv = fresh_flexible_vars pos tenv qs in
    Scheme (pos, [], fqs, CTrue pos, 
            StringMap.singleton name 
              ((intern pos (add_type_variables rtenv tenv) typ),
               pos))