(*  File: typing.ml

    AIFAD - Automated Induction of Functions over Algebraic Datatypes

    Author: Markus Mottl
    email:  markus.mottl@gmail.com
    WWW:    http://www.ocaml.info

    Copyright (C) 2002  Austrian Research Institute for Artificial Intelligence
    Copyright (C) 2003- Markus Mottl

    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; either version 2 of the License, or
    (at your option) any later version.

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*)

(* $Id: typing.ml,v 1.21 2006/01/17 00:23:39 mottl Exp $ *)

open Printf
open Utils
open Algdt_types
open Algdt_utils


(* Compute immediate representation of types *)

let hash_size = 541

let iprod_of_prod ~fail_tp ~tp_htbl =
  let rec loop = function
    | ProdEls ps -> ProdEls (Array.map loop ps)
    | TpVal tp ->
        try TpVal (Hashtbl.find tp_htbl tp)
        with Not_found -> fail_tp tp in
  loop

let sum_name = function Atom name | Strct (name, _) -> name

let calc_pre_ispec_info defs =
  let tp_htbl = Hashtbl.create hash_size in

  let tp_tbl, cnstr_tbl =
    let cnv_def n (lhs, rhs) =
      if Hashtbl.mem tp_htbl lhs then
        let err = sprintf "type defined more than once: '%s'" lhs in
        failwith err
      else (
        Hashtbl.add tp_htbl lhs n;
        match rhs with
        | Sums ss -> lhs, Array.map sum_name ss
        | Prod _ -> lhs, [||]) in
    array_mapi2 cnv_def defs in

  let ispec =
    let cnstr_htbl = Hashtbl.create hash_size in
    let cnv_def (lhs, rhs) =
      let fail_tp tp =
        let err =
          sprintf "rhs-type '%s' unknown in definition of '%s'" tp lhs in
        failwith err in
      match rhs with
      | Prod pe -> Prod (iprod_of_prod fail_tp tp_htbl pe)
      | Sums ss ->
          let fail_tag lhs tag =
            let err =
              sprintf
                "tag '%s' used another time in definition of '%s'" tag lhs in
            failwith err in
          let cnv_sum n = function
            | Atom tag ->
                let htbl_el = lhs, tag in
                if Hashtbl.mem cnstr_htbl htbl_el then fail_tag lhs tag
                else (Hashtbl.add cnstr_htbl htbl_el n; IAtom)
            | Strct (tag, pe) ->
                let htbl_el = lhs, tag in
                if Hashtbl.mem cnstr_htbl htbl_el then fail_tag lhs tag
                else (
                  Hashtbl.add cnstr_htbl htbl_el n;
                  IStrct (iprod_of_prod fail_tp tp_htbl pe)) in
          Sums (Array.mapi cnv_sum ss) in
    Array.map cnv_def defs in
  tp_tbl, cnstr_tbl, tp_htbl, ispec


(* CFG *)

type cfg_prod = CProd | CSum of cnstr

module AlgDtCfgSpec = struct
  type t = unit
  type nt = tp
  type symbol = NT of nt | T of t
  type prod = cfg_prod
  let compare_t = compare
  let compare_nt = compare
  let compare_prod = compare
end

module Cfg = Cfg_impl.Make (AlgDtCfgSpec)

open AlgDtCfgSpec
open Cfg

let syms_of_prod_el pe =
  let acc_ref = ref [] in
  let rec loop = function
    | TpVal tp -> acc_ref := NT tp :: !acc_ref
    | ProdEls ps -> array_rev_iter loop ps in
  loop pe;
  !acc_ref

let cfg_coll_def nt_n cfg = function
  | Prod pe -> add_prod cfg nt_n CProd (syms_of_prod_el pe)
  | Sums ss ->
      let coll_sum prod_n cfg = function
        | IAtom -> add_prod cfg nt_n (CSum prod_n)  [T ()]
        | IStrct pe -> add_prod cfg nt_n (CSum prod_n) (syms_of_prod_el pe) in
      array_fold_lefti coll_sum cfg ss

let cfg_of_pre_ispec_info (_, _, _, ispec) =
  array_fold_lefti cfg_coll_def Cfg.empty ispec


(* Compute ispec for some domain *)

let map_prod_el f =
  let rec loop = function
    | TpVal tp -> TpVal (f tp)
    | ProdEls ps -> ProdEls (Array.map loop ps) in
  loop

let calc_ispec_info live_gr dtp (tp_tbl, cnstr_tbl, _, ispec) =
  let gr_dom = grammar_contents (Cfg.grammar_of_live live_gr) in
  let tp_tbl_len = Array.length tp_tbl in
  let tp_cnv_tbl = Array.make tp_tbl_len 0 in
  let cnv_tp tp = tp_cnv_tbl.(tp) in
  let cnstr_cnv_tbl = Array.make tp_tbl_len [||] in
  let dtp_htbl = Hashtbl.create hash_size in
  let coll_nts nt _ new_nt =
    let the_nt, next_nt =
      if dtp = nt then (Hashtbl.add dtp_htbl tp_tbl.(nt) 0; 0, new_nt)
      else (Hashtbl.add dtp_htbl tp_tbl.(nt) new_nt; new_nt, new_nt + 1) in
    tp_cnv_tbl.(nt) <- the_nt;
    (match ispec.(nt) with
    | Sums sums -> cnstr_cnv_tbl.(nt) <- Array.make (Array.length sums) 0
    | _ -> ());
    next_nt in
  let n_nt = NTMap.fold coll_nts gr_dom 1 in
  let dtp_tbl = Array.make n_nt "" in
  let dcnstr_tbl = Array.make n_nt [||] in
  let dcnstr_htbl = Hashtbl.create hash_size in
  let dispec = Array.make n_nt (Sums [||]) in
  let act_nts nt rhs =
    let new_nt = tp_cnv_tbl.(nt) in
    dtp_tbl.(new_nt) <- tp_tbl.(nt);
    match ispec.(nt) with
    | Prod pe -> dispec.(new_nt) <- Prod (map_prod_el cnv_tp pe)
    | Sums sums ->
        let rhs_lst = ProdSet.elements rhs in
        let sum_cnv_tbl = cnstr_cnv_tbl.(nt) in
        let cnstrs = cnstr_tbl.(nt) in
        let coll_tag new_tag = function
          | CSum tag, _ ->
              sum_cnv_tbl.(tag) <- new_tag;
              Hashtbl.add dcnstr_htbl (new_nt, cnstrs.(tag)) new_tag;
              new_tag + 1
          | CProd, _ -> assert false in
        let n_sums = List.fold_left coll_tag 0 rhs_lst in
        dcnstr_tbl.(new_nt) <- Array.make n_sums "";
        let new_sums = Array.make n_sums IAtom in
        let act_tag new_tag = function
          | CSum tag, _ ->
              dcnstr_tbl.(new_nt).(new_tag) <- cnstrs.(tag);
              (match sums.(tag) with
              | IStrct pe ->
                  new_sums.(new_tag) <- IStrct (map_prod_el cnv_tp pe)
              | _ -> ())
          | CProd, _ -> assert false in
        list_iteri act_tag rhs_lst;
        dispec.(new_nt) <- Sums new_sums in
  NTMap.iter act_nts gr_dom;
  {
    tp_tbl = dtp_tbl;
    cnstr_tbl = dcnstr_tbl;
    tp_htbl = dtp_htbl;
    cnstr_htbl = dcnstr_htbl;
    ispec = dispec;
  }


(* Flatten ispec *)

let flatten_ispec ispec =
  let rec flt_prod_el2 acc = function
    | ProdEls ps -> Array.fold_left flt_prod_el2 acc ps
    | TpVal tp ->
        match ispec.(tp) with
        | Sums _ -> tp :: acc
        | Prod pe -> flt_prod_el2 acc pe in
  let rec flt_prod_el1 pss = function
    | ProdEls ps -> flt_prod_el1 (ps :: pss) ps.(0)
    | TpVal tp ->
        match ispec.(tp) with
        | Sums _ -> List.fold_left (array_fold_left1 flt_prod_el2) [tp] pss
        | Prod pe -> flt_prod_el1 pss pe in
  let flt_prod_el pe = array_of_rev_list (flt_prod_el1 [] pe) in
  let flt_sum = function IAtom -> [||] | IStrct pe -> flt_prod_el pe in
  let flt_rhs = function
    | Prod pe -> [| flt_prod_el pe |]
    | Sums ss -> Array.map flt_sum ss in
  let fspec = Array.map flt_rhs ispec in
  fspec, get_init_tps ispec fspec
