(*  File: model_pp_impl.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: model_pp_impl.ml,v 1.27 2006/01/17 00:23:38 mottl Exp $ *)

open Format
open Utils
open Algdt_pp
open Algdt_types
open Deco_intf
open Model_pp_intf

module TpMap = IntMap
module VarMap = IntMap

module Make (Spec : SPEC) = struct

open Spec

(* Conversion tables from immediate to string encodings *)

let { tp_tbl = dtp_tbl; cnstr_tbl = dcnstr_tbl; } = dispec_info
let { tp_tbl = ctp_tbl; cnstr_tbl = ccnstr_tbl; } = cispec_info

(* Specialized pretty-printer *)

module CIDataPP = Algdt_pp.Make_IData (struct let cnstr_tbl = ccnstr_tbl end)


(* Variable suffixes *)

let dsuf = "d"
let csuf = "c"


(* Print variable bindings in let-statements *)

let pp_vars ppf tps =
  if Array.length tps > 1 then pp_print_char ppf '(';
  fprintf ppf "@[%s_%s%d" ctp_tbl.(tps.(0)) csuf 1;
  for n = 1 to Array.length tps - 1 do
    fprintf ppf ",@ %s_%s%d" ctp_tbl.(tps.(n)) csuf (n + 1)
  done;
  fprintf ppf "@]";
  if Array.length tps > 1 then pp_print_char ppf ')'


(* Print variable models *)

let rec pp_vprod_el tps n ppf = function
  | TpVal dvar_mod -> pp_dvar_mod tps n ppf dvar_mod
  | ProdEls vprod_els -> pp_vprod_els tps n ppf vprod_els

and pp_vprod_el_arg tps n ppf = function
  | TpVal dvar_mod -> pp_vmod_arg tps n ppf dvar_mod
  | ProdEls vprod_els -> pp_vprod_els tps n ppf vprod_els

and pp_vprod_els tps n ppf vprod_els =
  pp_open_par_box ppf ();
  let new_n = pp_vprod_el tps n ppf vprod_els.(0) in
  let coll n vprod_el = fprintf ppf ",@ "; pp_vprod_el tps n ppf vprod_el in
  let new_n = array_fold_left1 coll new_n vprod_els in
  pp_close_par_box ppf ();
  new_n

and pp_dvar_mod tps n ppf = function
  | DVarStrct (tp, cnstr, vprod_el) ->
      pp_open_box ppf 2;
      pp_print_string ppf ccnstr_tbl.(tp).(cnstr);
      pp_print_space ppf ();
      let new_n = pp_vprod_el_arg tps n ppf vprod_el in
      pp_close_box ppf ();
      new_n
  | DVarFree dsum -> CIDataPP.pp_sum ppf dsum; n
  | DVar tp -> fprintf ppf "%s_%s%d" ctp_tbl.(tp) csuf n; n + 1

and pp_vmod_arg tps n ppf = function
  | DVarStrct (tp, cnstr, vprod_el) ->
      pp_open_par_box ppf ();
      pp_print_string ppf ccnstr_tbl.(tp).(cnstr);
      pp_print_space ppf ();
      let new_n = pp_vprod_el_arg tps n ppf vprod_el in
      pp_close_par_box ppf ();
      new_n
  | DVarFree dsum -> CIDataPP.pp_sum_arg ppf dsum; n
  | DVar tp -> fprintf ppf "%s_%s%d" ctp_tbl.(tp) csuf n; n + 1

let pp_vprod_els_start tps ppf vprod_els =
  if Array.length vprod_els = 1 then
    ignore (pp_vprod_el tps 1 ppf vprod_els.(0))
  else ignore (pp_vprod_els tps 1 ppf vprod_els)


(* Print opening of pattern-matching *)

let pp_open_match ppf var_ix var_map =
  fprintf ppf "@[(match %s with" (VarMap.find var_ix var_map)


(* Print variable bindings *)

let pp_dmodel ppf dmodel ddpat =
  let ix_cnt_ref = ref 0 in
  let var_map_ref = ref VarMap.empty in
  let tp_cnts_ref = ref TpMap.empty in

  (* Print variable bindings in patterns *)
  let pp_pat ppf prod_el =
    let rec loop = function
      | None -> fprintf ppf "_"
      | Some (`TpVal tp) ->
          let ix_cnt = !ix_cnt_ref in
          ix_cnt_ref := ix_cnt + 1;
          let tp_cnts = !tp_cnts_ref in
          let tp_cnt = try TpMap.find tp tp_cnts with Not_found -> 1 in
          tp_cnts_ref := TpMap.add tp (tp_cnt + 1) tp_cnts;
          let var_name = sprintf "%s_%s%d" dtp_tbl.(tp) dsuf tp_cnt in
          var_map_ref := VarMap.add ix_cnt var_name !var_map_ref;
          pp_print_string ppf var_name;
      | Some (`ProdEls prod_els) ->
          pp_open_par_box ppf ();
          loop prod_els.(0);
          let act prod_el = fprintf ppf ",@ "; loop prod_el in
          array_iter1 act prod_els;
          pp_close_par_box ppf () in
    loop prod_el in

  (* Print rule in pattern-matching *)
  let pp_match tp cnstr pat ppf =
    pp_open_box ppf 2;
    pp_print_string ppf dcnstr_tbl.(tp).(cnstr);
    if Array.length dfspec.(tp).(cnstr) > 0 then fprintf ppf "@ %a" pp_pat pat;
    pp_close_box ppf () in

  (* Print models *)
  let rec pp_dmodel ppf = function
    | DVal deco_sums ->
        if Array.length deco_sums = 1 then CIDataPP.pp_data_el ppf deco_sums.(0)
        else CIDataPP.pp_prod_els ppf deco_sums
    | DLet (dmatch_mod, tps, vprod_els) ->
        fprintf ppf "@[<2>@[let@ %a@ =@]@\n@[%a@]@]@ in@\n@[%a@]"
          pp_vars tps
          pp_dmatch_mod dmatch_mod
          (pp_vprod_els_start tps) vprod_els
    | DMatchMod dmatch_mod -> pp_dmatch_mod ppf dmatch_mod

  and pp_branch ppf dmodel =
    pp_print_string ppf " ->";
    (match dmodel with
    | DVal _ -> pp_print_space ppf ()
    | _ -> pp_force_newline ppf ());
    pp_dmodel ppf dmodel

  (* Print match models *)
  and pp_dmatch_mod ppf dmatch_mod =
    let ix_cnt = !ix_cnt_ref in
    let var_map = !var_map_ref in
    let tp_cnts = !tp_cnts_ref in
    (match dmatch_mod with
    | DShave (var_ix, tp, cnstr, pat, dsh_mod, dmodel) ->
        pp_open_match ppf var_ix var_map;
        fprintf ppf "@\n@[<4>| @[%t@]%a@]"
          (pp_match tp cnstr pat) pp_branch dsh_mod;
        fprintf ppf "@\n@[<4>| _%a@]" pp_branch dmodel
    | DSplit (var_ix, tp, dmodels) ->
        let pp_pat_match cnstr pat dmodel =
          ix_cnt_ref := ix_cnt;
          var_map_ref := var_map;
          tp_cnts_ref := tp_cnts;
          fprintf ppf "@\n@[<4>| @[%t@]%a@]"
            (pp_match tp cnstr pat) pp_branch dmodel in
        pp_open_match ppf var_ix var_map;
        let acti cnstr (pat, dmodel) = pp_pat_match cnstr pat dmodel in
        Array.iteri acti dmodels
    | DPSplit (var_ix, tp, maybe_dmodels, dmodel) ->
        pp_open_match ppf var_ix var_map;
        let pp_pat_match cnstr pat dmodel =
          ix_cnt_ref := ix_cnt;
          var_map_ref := var_map;
          tp_cnts_ref := tp_cnts;
          fprintf ppf "@\n@[<4>| @[%t@]%a@]"
            (pp_match tp cnstr pat) pp_branch dmodel in
        let acti cnstr = function
          | Some (pat, dmodel) -> pp_pat_match cnstr pat dmodel
          | None -> () in
        Array.iteri acti maybe_dmodels;
        ix_cnt_ref := ix_cnt;
        var_map_ref := var_map;
        tp_cnts_ref := tp_cnts;
        fprintf ppf "@\n@[<4>| _%a@]" pp_branch dmodel);
    fprintf ppf "@])" in

  fprintf ppf "@[<2>@[let@ model@ @[%a@]@ =@]@\n@[%a@]@]"
    pp_pat ddpat
    pp_dmodel dmodel

end
