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

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

open Misc
open Positions

(** lname is the type of label. *)

type lname = LName of string

(** This module implements the internal representation of terms. *)


module RowLabel : sig
  
  
  (** This module maintains a global mapping from identifiers to abstract ``labels'', which are internally represented as integers, and back. *)

  
  type t

  
  (** compare x y is a total ordering. It returns a negative integer if x is less than y, 0 if x and y are equal, and a positive integer if x is greater than y. *)

  val compare: t -> t -> int

  
  (** import s associates a unique label with the identifier s, possibly extending the global mapping if s was never encountered so far. Thus, if s and t are equal strings, possibly allocated in different memory locations, import s and import t return the same label. The identifier s is recorded and may be later recovered via export. *)

    val import: lname -> t

    
    (** export i provides access to the inverse of the global mapping, that is, associates a unique identifier with every label. The identifier associated with a label is the one originally supplied to import. *)

    val export: t -> lname

  end = struct

    
    (** A row label is an object of type t, that is, an integer. *)

    type t = int

    let compare = (-)

    
    (** A hash table maps all known identifiers to integer values. It provides one direction of the global mapping. *)

    let table =
      Hashtbl.create 1023

    
    (** An infinite array maps all known integer values to identifiers. It provides the other direction of the global mapping. *)

    let array =
      InfiniteArray.make "<BUG>" (* Dummy data. *)

    
    (** A global counter contains the next available integer label. *)

    let counter =
      ref 0

    
    (** import s associates a unique label with the identifier s, possibly extending the global mapping if s was never encountered so far. Thus, if s and t are equal strings, possibly allocated in different memory locations, import s and import t return the same label. The identifier s is recorded and may be later recovered via export. *)

    let import (LName s) =
      try
        Hashtbl.find table s
      with Not_found ->
        let i = !counter in
          Hashtbl.add table s i;
          InfiniteArray.set array i s;
          counter := i + 1;
          i

    
    (** export i provides access to the inverse of the global mapping, that is, associates a unique identifier with every label. The identifier associated with a label is the one originally supplied to import. *)

    let export i =
      assert (i < !counter);
      LName (InfiniteArray.get array i)

  end

(** The terms of a row algebra include a binary row extension constructor for every row label, the unary constant row constructor, and the terms of the underlying free algebra. *)

type 'a term =
  | RowCons of RowLabel.t * 'a * 'a
  | RowUniform of 'a
  | App of 'a * 'a
  | Var of 'a
      
(** Terms whose parameters are either leaves of type 'a, or terms. arterm stands for ``abstract recursive term''. *)

type 'a arterm =
  | TVariable of 'a
  | TTerm of ('a arterm) term
      
let rec iter f = function
  | RowCons (_, hd, tl) ->
      f hd; f tl
  | RowUniform content ->
      f content
  | App (l, r) -> 
      f l; f r
  | Var v -> 
      f v
      
let rec map f = function
  | RowCons (label, hd, tl) ->
      RowCons (label, f hd, f tl)
  | RowUniform content ->
      RowUniform (f content)
  | App (l, r) ->
      App (f l, f r)
  | Var v ->
      Var (f v)
      
let rec fold f term accu =
  match term with
    | RowCons (_, hd, tl) ->
        f hd (f tl accu)
    | RowUniform content ->
        f content accu
    | App (l, r) -> 
        f r (f l accu)
    | Var v ->
        f v accu

let rec fold2 f term term' accu =
  match term, term' with
    | RowCons (_, hd, tl), RowCons (_, hd', tl') ->
        f hd hd' (f tl tl' accu)
    | RowUniform content, RowUniform content' ->
        f content content' accu
    | App (l, r), App (l', r') -> 
        f r r' (f l l' accu)
    | Var v, Var v' ->
        f v v' accu
    | _ -> failwith "fold2"
          
let app t args = 
  List.fold_left (fun acu x -> TTerm (App (acu, x))) t args
    
exception InvalidSymbolString of string
  
exception InvalidSymbolUse of string * int
  
let rec change_term_vars c = 
  map (change_arterm_vars c)
    
and change_arterm_vars c = 
  function
    | TTerm term -> TTerm (change_term_vars c term)
    | TVariable x -> TVariable (
        try 
          List.assq x c
        with Not_found -> x)
        
let rec gen_change_term_vars c = 
  map (gen_change_arterm_vars c)
    
and gen_change_arterm_vars c = 
    function
      | TTerm term -> TTerm (gen_change_term_vars c term)
      | TVariable x -> (
          try 
            List.assq x c
          with Not_found -> TVariable x)
          
let uniform v = 
  TTerm (RowUniform v)

let rowcons label x y = 
  let intern_label = RowLabel.import label in
    TTerm (RowCons (intern_label, x, y))

let n_rowcons typed_labels y = 
  List.fold_left (fun acu (l, t) -> rowcons l t acu) y typed_labels