(* Time-stamp: <modified the 29/08/2019 (at 15:24) by Erwan Jahier> *)

(** 
    Table des infos sources : une couche au dessus de AstV6 pour mieux
    ranger les packages et les mod�les et faciliter la r�solution des
    identificateurs.

    1) expansion des mod�les

    2) pour chaque package instanci�, 2 tables de symboles :

    - une pour la vision "export�e"
    - une pour la vision interne. Chaque table de symbole, 3 "espaces"
    de noms (par nature d'items, type/const/node)

    Ces tables sont destin�es � r�soudre les r�f�rences simples, elle
    associent � une string :

    - la definition syntaxique de l'item associ� s'il est local
    - l'identificateur absolu (package+nom) si il est externe
*)

open Lxm
open AstV6
open AstCore
open Lv6errors

let dbg = (Lv6Verbose.get_flag "ast")


(** Package manager

    Un package manager (pack_mng) contient les infos ``source'' du
    package + DEUX tables de symboles, correspondant aux deux contextes
    possibles de compilation :

    - compilation du provide
    - compilation du body

    En effet, un identificateur de type, de constante ou de noeud
    n'est pas interpr�t� de la m�me mani�re suivant qu'il appara�t
    dans la partie provide ou body.

    Il contient aussi une table des items export�s pour faciliter le
    traitement des "use" du package. C'est une correspondance 

        nature + nom simple -> nom complet 

    (c.a.d. ??? + AstV6.item_ident -> Lv6Id.long)
*)

type pack_mng = {
  (* le lexeme associ� au package? *)
  pm_lxm : Lxm.t;
  (* le source brut *)
  pm_raw_src : AstV6.pack_info;
  (* le source expans� *)
  pm_actual_src : AstV6.pack_given;
  (* table "brute" des items provided *)
  (* pour les "user" du pack *)
  pm_user_items : (AstCore.item_ident, Lv6Id.long Lxm.srcflagged) Hashtbl.t;
  (* les tables de symboles pour compil ult�rieure *)
  pm_body_stab : AstTabSymbol.t;
  (* la table pour provide n'est cr��e que si besoin ... *)
  pm_provide_stab : AstTabSymbol.t option;
}

(** TYPE PRINCIPAL : t

    Packages et mod�les sont rang�s dans des tables, ce qui permet
    notamment de traiter les erreurs de multi-d�clarations
    (st_raw_mod_tab et st_raw_pack_tab)

    Les instances de modeles sont trait�es pour n'avoir plus que des
    ``pack_given'' (i.e. pack avec provide + body)

    � chaque package (�ventuellement expans�) est associ� un manager
    pour faciliter l'acc�s � ses infos (pack_mng)
*)
type t = {
  (* liste + tables des sources bruts *)
  st_list : AstV6.pack_or_model list ;
  st_raw_mod_tab  : (Lv6Id.t , model_info srcflagged) Hashtbl.t ;
  st_raw_pack_tab : (Lv6Id.pack_name , pack_info srcflagged) Hashtbl.t ;
  (* table des managers de packs *)
  st_pack_mng_tab : (Lv6Id.pack_name , pack_mng)  Hashtbl.t; 
}

(* exported *)
let (pack_list:t -> Lv6Id.pack_name list) =
  fun this -> 
    Hashtbl.fold (fun n _p l -> n::l) this.st_pack_mng_tab []


(* exported *)
let (pack_body_env: t -> Lv6Id.pack_name -> AstTabSymbol.t) =
  fun this p -> 
    try  
      (Hashtbl.find this.st_pack_mng_tab p).pm_body_stab
    with Not_found -> 
      print_string ("*** Error: can not find package '" ^ 
		                 (Lv6Id.pack_name_to_string p) ^ "' in the following packages: ");
      Hashtbl.iter 
	     (fun pn _pm -> print_string ("\n***\t '"^(Lv6Id.pack_name_to_string pn)^ "'"))
	     this.st_pack_mng_tab;
      print_string "\n";
      flush stdout;
      exit 2


(* exported *)
let pack_prov_env (this: t) (p: Lv6Id.pack_name)
  : AstTabSymbol.t option =
  try (Hashtbl.find this.st_pack_mng_tab p).pm_provide_stab
  with Not_found -> 
(*       let msg =  *)
(*         ("\n*** Could not find package " ^(Lv6Id.pack_name_to_string p) ^  *)
(*            " in the package table" ) *)
(*       in *)
	None  
(*         raise(Compile_error(lxm, msg))   *)
      


(** Insert an item in the lexeme table. Raise [Compile_error] if already defined. *)
let put_in_tab 
    (what: string)
    (tab : ('a, 'b Lxm.srcflagged) Hashtbl.t)
    (key : 'a)
    (value : 'b Lxm.srcflagged) 
    =
  try 
    let plxm = (Hashtbl.find tab key).src in
    let msg =
      Printf.sprintf "%s already declared in %s" what (Lxm.position plxm) 
    in
      raise (Lv6errors.Compile_error (value.src, msg))
  with 
      Not_found -> Hashtbl.add tab key value


(****************************************************************************
init de la table des items provided (pour les users)
****************************************************************************)
let init_user_items (this: pack_mng) = (
  let pname = Lv6Id.pack_name_of_string (Lxm.str this.pm_lxm) in

  (* Exportation D'une const_info *)
  let export_const (s:Lv6Id.t) (xci: AstCore.const_info srcflagged) =
	 Lv6Verbose.printf ~flag:dbg "       export const %s\n" (Lv6Id.to_string s);
	 put_in_tab "const" this.pm_user_items
	   (ConstItem s)
	   (Lxm.flagit (Lv6Id.make_long pname s) xci.src)
  in
  
  (* Exportation D'un type_info *)
  let export_type (s: Lv6Id.t) (xti: AstCore.type_info srcflagged) = 
    ( match (xti.it) with
	   | EnumType (_, ecl) -> (
	    (* Cas particulier des types enums *)
	    (* on exporte les constantes ... *)
	     let treat_enum_const ec = 
	       let s = ec.it in
	       let lxm = ec.src in
		    Lv6Verbose.printf ~flag:dbg "       export enum const %s\n" (Lv6Id.to_string s);
		    put_in_tab "const" this.pm_user_items
		      (ConstItem s)
		      (Lxm.flagit (Lv6Id.make_long pname s) lxm)
	     in
	     List.iter treat_enum_const ecl
	   )
	   | ExternalType _
	   | AliasedType _ 
	   | StructType _ 
	   | ArrayType _ 
	     -> ()
    );
    Lv6Verbose.printf ~flag:dbg "       export type %s\n" (Lv6Id.to_string s);
    put_in_tab "type" this.pm_user_items
      (TypeItem s)
      (Lxm.flagit (Lv6Id.make_long pname  s) xti.src)
  in

  (* Exportation D'un node_info *)
  let export_node (s: Lv6Id.t) (xoi: AstCore.node_info srcflagged) = 
    Lv6Verbose.printf ~flag:dbg "       export node %s\n" (Lv6Id.to_string s);
    put_in_tab "node" this.pm_user_items
      (NodeItem (s,xoi.it.static_params))
      (Lxm.flagit (Lv6Id.make_long pname s) xoi.src)
  in

  let pg = this.pm_actual_src in

  match pg.pg_provides with
    | None ->
	  (* On Exporte Tout Tel Quel *)
	   Hashtbl.iter export_type  pg.pg_body.pk_type_table ;
	   Hashtbl.iter export_const pg.pg_body.pk_const_table ;
	   Hashtbl.iter export_node  pg.pg_body.pk_node_table ;
    | Some spflg ->
	  (* On Exporte Les Provides *)
	   let treat_prov x = 
	     let lxm = x.src in
	     let s = Lxm.id lxm in
	     match (x.it) with
		      TypeInfo xti -> export_type s (Lxm.flagit xti lxm)
		    | ConstInfo xci -> export_const s (Lxm.flagit xci lxm)
		    | NodeInfo xoi -> export_node s (Lxm.flagit xoi lxm)
	   in
	   List.iter treat_prov spflg
)

(*
Cr�ation/initialisation d'un pack_mng :
On pr�pare juste la table des items provided
pour pouvoir traiter les �ventuels "use" des autres pack.
Les tables de symboles sont cr��es plus tard.
*)
let create_pack_mng 
    (pdata : AstV6.pack_info srcflagged)
    (pgiven : AstV6.pack_given)
    = (
      (* la table pm_provide_stab n'est cr��e que si besoin *)
  let ppstab = match pgiven.pg_provides with
	   None -> None 
	 | Some _ -> Some (AstTabSymbol.create ())
  in
  let res =
	 {
	   pm_lxm = pdata.src ;
	   pm_raw_src = pdata.it;
	   pm_actual_src = pgiven;
	   pm_user_items = Hashtbl.create 50;
	   pm_provide_stab = ppstab;
	   pm_body_stab = AstTabSymbol.create ();
	 } 
  in
  init_user_items res;
  res
)



(****************************************************************************
CREATION
-----------------------------------------------------------------------------
Se fait en plusieurs passes :

1) mise en place des tables "raw" mod et pack (string -> source pack/mod)
2) instanciations �ventuelle des packs (voir AstInstanciateModel)
   et initialisation des pack_mng (en particulier des infos pour les users)
3) pour chaque pack, cr�ation des symbol_table contextuelles
   (pour la partie provide et pour la partie body)
****************************************************************************)

let rec (create  : AstV6.pack_or_model list -> t) =
  fun sl -> 
    (* liste + tables des sources bruts *)
    let res = {
      st_list = sl ;
      st_raw_mod_tab = Hashtbl.create 50;
      st_raw_pack_tab = Hashtbl.create 50; 
      st_pack_mng_tab = Hashtbl.create 50; 
    } 
    in
    Lv6Verbose.printf ~flag:dbg "*** AstTab.create pass 1\n";
    (* passe 1 *)
    init_raw_tabs res sl ;
    (* passe 2 *)
    Lv6Verbose.printf ~flag:dbg "*** AstTab.create pass 2\n";
    let init_pack_mng pname pdata = (
	   Lv6Verbose.printf ~flag:dbg "    init pack %s\n" (Lv6Id.pack_name_to_string pname);
	   let pg = AstInstanciateModel.f res.st_raw_mod_tab pdata in
	   Hashtbl.add res.st_pack_mng_tab 
	     pname
	     (create_pack_mng pdata pg)
    ) in
	 Hashtbl.iter init_pack_mng res.st_raw_pack_tab ;
	 (* passe 3 *)
	 Lv6Verbose.printf ~flag:dbg "*** AstTab.create pass 3\n";
	 Hashtbl.iter (init_pack_mng_stabs res) res.st_pack_mng_tab ;
	 (* resultat *)
	 Lv6Verbose.printf ~flag:dbg "*** AstTab.create done\n";
	 res
and
    (***** PASSE 1 *****)
    (* init des tables string -> mod ou pack *)
    init_raw_tabs (this : t) (sl : AstV6.pack_or_model list) =
  (* on it�re pour chaque pack_or_model : *)
  let treat_ns ns = 
    match ns with
	   (* cas d'un package *)
	   | AstV6.NSPack  pi ->
	     let lxm = pi.Lxm.src in
	     let nme = (Lv6Id.pack_name_of_string (Lxm.str lxm)) in
	     put_in_tab "package" this.st_raw_pack_tab nme pi
	   	 
	   | AstV6.NSModel mi -> (* cas d'un modele *)
	     let lxm = mi.Lxm.src in
	     let nme = (Lxm.id lxm) in
	     put_in_tab "model" this.st_raw_mod_tab nme mi
  in
  List.iter treat_ns sl
and
    (***** PASSE 3 *****)
    (* Essentiellement le remplissage des champs de pack_mng : 

       pm_provide_stab : AstTabSymbol.t
       table qui permettra de r�soudre les refs simples
       � l'int�rieur de la partie provides.

       pm_body_stab : AstTabSymbol.t ;
       table qui permettra de r�soudre les refs simples
       � l'int�rieur de la partie body.

       N.B. s'il n'y a pas de provides explicite, on construit
       une unique table qui sert pour les deux !

       Comment �a marche :
       - on traite en premier les �ventuels "use", (= open de ocaml)
       - puis les d�clarations locales qui peuvent �ventuellement
       masquer les pr�c�dentes (warning ?)
    *)
    init_pack_mng_stabs (this: t) (pname: Lv6Id.pack_name) (pm: pack_mng) = (
  let pg = pm.pm_actual_src in

  Lv6Verbose.printf ~flag:dbg "   init symbol tables for pack %s\n"
    (Lv6Id.pack_name_to_string pname);
  (* ON COMMENCE PAR TRAITER LE PG_USES *)
  let treat_uses (px:Lv6Id.pack_name srcflagged) = (
    let pname = px.it in
    let lxm = px.src in
    let pum = 
	   try Hashtbl.find this.st_pack_mng_tab pname
	   with Not_found -> raise(Compile_error(lxm, "unknown package"))
    in
    let fill_used_item
	     (ii: AstCore.item_ident)
	     (iks: Lv6Id.long Lxm.srcflagged) =
	   (match ii with
	     | ConstItem n -> (
	       AstTabSymbol.add_import_const pm.pm_body_stab px.it n iks.it;
	       match pm.pm_provide_stab with
		        Some pt -> AstTabSymbol.add_import_const pt px.it n iks.it
		      | None -> ()
	     )
	     | TypeItem n -> (
	       AstTabSymbol.add_import_type pm.pm_body_stab n iks.it;
	       match pm.pm_provide_stab with
		        Some pt -> AstTabSymbol.add_import_type pt n iks.it
		      | None -> ()
	     )
	     | NodeItem (n,sparams) -> (
	       AstTabSymbol.add_import_node pm.pm_body_stab n iks.it sparams;
	       match pm.pm_provide_stab with
		        Some pt -> AstTabSymbol.add_import_node pt n iks.it sparams
		      | None -> ()
	     )
	   ) 
    in
	 Hashtbl.iter fill_used_item pum.pm_user_items
  )
  in
  List.iter treat_uses pg.pg_uses ;

  (* PUIS LES DECLARATION LOCALES *)
  (* ... dans le body : *)
  Hashtbl.iter (AstTabSymbol.add_type pm.pm_body_stab pname) pg.pg_body.pk_type_table;
  Hashtbl.iter (AstTabSymbol.add_const pm.pm_body_stab pname) pg.pg_body.pk_const_table;
  Hashtbl.iter (AstTabSymbol.add_node pm.pm_body_stab)	 pg.pg_body.pk_node_table;
  (* ... dans le provide : *)
  match pg.pg_provides with
	 | None -> () 
	 | Some spflg -> (
	   let pptab = match pm.pm_provide_stab with
		    Some pt -> pt
	     | None -> assert false
	   in
	   let treat_prov x = 
	     let lxm = x.src in
	     let s = Lxm.id lxm in
		  match (x.it) with
		    | TypeInfo xti  -> AstTabSymbol.add_type pptab pname s (Lxm.flagit xti lxm) 
		    | ConstInfo xci -> AstTabSymbol.add_const pptab pname s (Lxm.flagit xci lxm)
		    | NodeInfo xoi  -> AstTabSymbol.add_node pptab s (Lxm.flagit xoi lxm)
	   in
	   List.iter treat_prov spflg
	 )
)


(****************************************************************************
Associations :
--------------
- Lv6Id.t -> Lv6Id.long * AstCore.xxxx_info

****************************************************************************)


(* exported *)
let (dump : t -> unit) =
  fun x -> 
    (* let p = Lv6Verbose.print_string ~level:3 in *)
    let p = prerr_string in
    p "*** � Syntax table dump:\n";
    
    p " \t - Package or model list:\n\t\t";
    (* st_list : AstV6.pack_or_model list ; *)
    List.iter 
	   (fun pm -> p (AstV6.pack_or_model_to_string pm); p "\n\t\t") 
	   x.st_list ;
    
    p "\n\t - Raw model table: ";
    (* st_raw_mod_tab  : (Lv6Id.t , model_info srcflagged) Hashtbl.t ; *)
    Hashtbl.iter 
	   (fun id _mi -> p ((Lv6Id.to_string id) ^ " "))
	   x.st_raw_mod_tab;
    
    p "\n\t - Raw Package table: ";
    (* st_raw_pack_tab : (Lv6Id.pack_name , pack_info srcflagged) Hashtbl.t ; *)
    Hashtbl.iter 
	   (fun pn _pi -> p ((Lv6Id.pack_name_to_string pn) ^ " ")) 
	   x.st_raw_pack_tab;
    
    p "\n\t - Package manager table: ";
    (* st_pack_mng_tab : (Lv6Id.pack_name , pack_mng)  Hashtbl.t;  *)
    Hashtbl.iter 
	   (fun pn _pm -> p ((Lv6Id.pack_name_to_string pn) ^ " ")) 
	   x.st_pack_mng_tab;
    
    p "\nEnd of Syntax table dump. �\n"