(** Time-stamp: <modified the 29/08/2008 (at 10:21) by Erwan Jahier> *)


open Printf 
open Lxm
open Errors
open SyntaxTree
open Eff
open SyntaxTreeCore
open Predef
open PredefEvalConst
open PredefEvalType

(*----------------------------------------------------
EvalArray_error :
        - lev�e par les fonctions d�di�es aux tableaux
----------------------------------------------------*)
exception EvalArray_error of string

(*----------------------------------------------------
EvalConst_error :
  - lev�e localement dans les sous-fonctions,
  - capt�e dans EvalConst.f et tranform�e en Compile_error.
----------------------------------------------------*)

let finish_me msg = print_string ("\n\tXXX evalConst.ml:"^msg^" ->  finish me!\n")
      
let not_evaluable_construct str =
  raise (EvalConst_error(
           Printf.sprintf "The construct %s is not allowed in static expression" 
             str))

(*----------------------------------------------------
Utilitaire :
extraire une tranche de tableau

N.B. first_ix  last_ix step et width sont suppos�s 
venir de eva et donc �tre corrects

N.B. Puisque correct, last_ix est inutile, mais bon ...
-----------------------------------------------------*)
let (make_slice_const : 
       Eff.const array -> Eff.type_ -> Eff.slice_info -> Eff.const list) =
  fun ctab ctype slice ->
    let get_res (ix : int) = Array.get ctab (slice.se_first + ix*slice.se_step) in
      [Array_const_eff (Array.init slice.se_width get_res, ctype)]


(** Utilitaire : fabriquer si possible une constante tableau *)
let (make_array_const : Eff.const list array -> Eff.const) =
  fun ops -> 
    let expected_type = ref None in
    let treat_arg (op : Eff.const list) =
      match op with
        | [x] -> (
            (* non tuple *)
            let xtyp = Eff.type_of_const x in
              match (!expected_type) with
                | None -> expected_type := Some xtyp; x
                | Some t -> (
                    if (t = xtyp) then x else 
                      raise (EvalConst_error(
                               "type error in array, "^
                                 (LicDump.string_of_type_eff xtyp)^
                                 " mixed with " ^ LicDump.string_of_type_eff t
                             ))
                  )
          )
        | _  ->  (* tuple *)
            raise (EvalConst_error("array of tuple not allowed"))
    in 
    let res = Array.map treat_arg ops in
      match (!expected_type) with
        | None -> raise (EvalConst_error("empty array"))
        | Some t -> Array_const_eff(res, t)



(** Utilitaire : fabriquer si possible une constante structure 

N.B. Par construction on sait que nops n'a pas de doublons
*)
let make_struct_const 
    (teff : Eff.type_)
    (arg_tab : (Ident.t, Lxm.t * Eff.const) Hashtbl.t) =
  (
    (* on verifie qu'on a bien un type struct *)
    match teff with
        Struct_type_eff (tnm, flst) -> (
          (* on construit la liste dans le BON ordre *)
          let make_eff_field ((fn: Ident.t),((ft:Eff.type_),(fv:Eff.const option))) = (
            try (
              (* on prend en priorit� dans arg_tab *)
              match (Hashtbl.find arg_tab fn) with
                  (lxm, v) -> (
                    (* effet de bord : on vire la valeur de arg_tab *)
                    Hashtbl.remove arg_tab fn ;
                    let vt = Eff.type_of_const v in
                      if (vt = ft) then (fn, v) (*ok*)
                      else raise (Compile_error(
                                    lxm , 
                                    sprintf
                                      "\n*** type error in struct %s, %s instead of %s"
                                      (Ident.string_of_long tnm)
                                      (LicDump.string_of_type_eff vt)
                                      (LicDump.string_of_type_eff ft) 
                                  )) 
                  )
            ) with Not_found -> (
              (* sinon la valeur par d�faut *)
              match fv with
                  Some v -> (fn, v) (* ok : v correcte par construction *)
                | None -> 
                    raise (EvalConst_error(
                             sprintf
                               "bad struct expression, no value given for field %s" 
                                             (Ident.to_string fn)
                           ))
            )
          ) in
            (* on mappe flst pour avoir la liste dans le bon ordre *)
          let eff_fields = List.map make_eff_field flst in
            (* si arg_tab n'est pas vide, erreur sur le premier *) 
          let raise_error (id : Ident.t) ((lxm : Lxm.t), (veff : Eff.const)) 
              = raise(Compile_error(
                        lxm, 
                        sprintf
                          "\n*** %s is not a field of struct %s" 
                          (Ident.to_string id) 
                          (LicDump.string_of_type_eff(teff))
                      )) 
          in 
            Hashtbl.iter raise_error arg_tab ;
            (* ok : tout s'est bien pass� ! *)
            Struct_const_eff (eff_fields, teff)
        ) 
      | _ -> raise (EvalConst_error(
                          sprintf
                            "struct type expected instead of %s" 
                            (LicDump.string_of_type_eff teff)
                        ))
  )


let l2ll l = if l = [] then [] else [l]

(*----------------------------------------------------
        Evaluation r�cursive des expressions constantes
------------------------------------------------------
f :
        - entr�es :  Eff.id_solver et val_exp
        - sortie :        Eff.const list
        - Effet de bord : Compile_error 
R�le :
        -> r�soud les r�f�rences aux idents
        -> g�re les appels r�cursifs (�valuation des arguments) 
----------------------------------------------------*)
let rec f
    (env : Eff.id_solver) 
    (vexp : val_exp)
    = (
      (*-----------------------------------
        fonction r�cursive principale
        -> capte les nv
        -> r�cup�re les EvalConst_error 
        -----------------------------------*)
      let rec rec_eval_const (vexp : SyntaxTreeCore.val_exp) = (
        match vexp with
          | SyntaxTreeCore.CallByPos ({it=posop; src=lxm}, Oper args) -> (
              try eval_by_pos_const posop lxm args
              with
                | EvalType_error msg -> 
                    raise (Compile_error(lxm, "type error: "^msg))
                | EvalConst_error msg ->
                    raise (Compile_error(lxm, "\n*** can't eval constant: "^msg))
            )
          | SyntaxTreeCore.CallByName ({it=nmop; src=lxm}, nmargs ) -> (
              try eval_by_name_const nmop lxm nmargs
              with EvalConst_error msg ->
                raise (Compile_error(lxm, "\n*** can't eval constant: "^msg))
            )
      )
        (*-----------------------------------
          fonction r�cursive secondaire
          eval. exp classique (by pos)
          N.B. On distingue les op�rations 
          classiques (avec extention tableau
          implicie) des autres. Ici, on traite
          toutes les op�rations non classiques. 
          -----------------------------------*)
      and eval_by_pos_const
          (posop : by_pos_op)   (* l'operateur *)
          (lxm : Lxm.t)      (* source de l'op�rateur *)
          (args : val_exp list) (* arguments *)
          = (
            match (posop) with 
                (* capte les idents de constantes *)
                IDENT_n  id  -> (
                  (* 2007-07 on interdit les externes *)
                  match (env.id2const id lxm) with
                    | Extern_const_eff(_,_, Some const_eff) -> [const_eff]
                    | Extern_const_eff(_,_,None) -> 
                        raise (EvalConst_error(
                                 sprintf "\n*** cannot access this abstract constant value"))
                    | x -> [ x ]
                )
                  (* op�rateur lazzy *)
              | WITH_n(a0,a1,a2) -> (
                  match (rec_eval_const a0) with
                      [ Bool_const_eff true] -> rec_eval_const a1
                    | [ Bool_const_eff false] -> rec_eval_const a2
                    | x -> type_error_const x "bool"
                )
                  (* mettre � plat la liste des args *)
              | TUPLE_n -> ( List.flatten (List.map rec_eval_const args))
                  (* les tableaux de tuples sont interdits *)
              | HAT_n -> (
                  match args with
                    | [cexp; szexp] -> (
                        try
                          let sz = eval_array_size env szexp in
                            match rec_eval_const cexp with
                              | [cst] ->
                                  let atab = Array.make sz cst in       
                                    [ Array_const_eff (atab, Eff.type_of_const cst) ]
                              | x -> 
                                  raise (EvalConst_error("array of tuple not allowed"))
                        with 
                            EvalArray_error msg -> raise(EvalConst_error msg)
                      ) 
                    | _ -> raise(EvalConst_error
                                   (sprintf "arity error: 2 expected instead of %d" 
                                      (List.length args)))
                )
              | CONCAT_n -> (
                  let ops = (List.map rec_eval_const args) in
                    match ops with
                      | [[Array_const_eff (v0, t0)];
                         [Array_const_eff (v1, t1)]] -> (
                          if(t0 = t1) then 
                            [Array_const_eff (Array.append v0 v1, t0)]  
                          else 
                            raise(EvalConst_error(
                                    sprintf 
                                      "\n*** type combination error, can't concat %s with %s"
                                      (LicDump.string_of_type_eff(t0)) 
                                      (LicDump.string_of_type_eff(t1)) 
                                  ))
                        )
                      | [_;_] -> 
                          raise(EvalConst_error(
                                  "type combination error, array type expected"))
                      | _ -> raise(EvalConst_error
                                     (sprintf "arity error: 2 expected instead of %d" 
                                        (List.length ops)))
                )
              | ARRAY_n -> (
                  let ops = (List.map rec_eval_const args) in
                    [make_array_const (Array.of_list ops)]
                )
              | ARRAY_ACCES_n ix -> (
                  let effargs = List.flatten (List.map rec_eval_const args) in
                    match effargs with
                      | [Array_const_eff (elts, typelts)] -> (
                          try
                            let sz = Array.length elts in
                            let effix = eval_array_index env ix sz lxm in
                              [Array.get elts effix ]
                          with EvalArray_error msg -> raise(EvalConst_error msg)
                        )
                      |  _  -> type_error_const effargs "some array"
                )
              | ARRAY_SLICE_n sl -> (
                  let (elts, typelts) =
                    match List.flatten (List.map rec_eval_const args) with
                      | [Array_const_eff (l, t)] -> (l, t) 
                      | x -> type_error_const x "some array"
                  in
                    (* on en d�duit la taille du tableau *) 
                  let sz = Array.length elts in
                    (* �value la slice *)
                    try
                      let sliceff = eval_array_slice env sl sz lxm in
                        make_slice_const elts typelts sliceff
                    with
                        EvalArray_error msg -> raise(EvalConst_error msg)
                ) 

              | STRUCT_ACCESS_n fid -> 
                  let ceff_list = List.flatten (List.map rec_eval_const args) in
                    (match ceff_list with 
                       | [Struct_const_eff (flst, typ)] -> (
                           try [(List.assoc fid flst)]
                           with Not_found -> 
                             raise (EvalConst_error
                                      (Printf.sprintf "%s is not a field of struct %s" 
                                         (Ident.to_string fid) 
                                         (LicDump.string_of_type_eff(typ))))
                         )
                       | [x] -> type_error_const [x] "struct type"
                       | x -> arity_error_const x "1"
                    )

              | CALL_n _ -> not_evaluable_construct "node call"
              | MERGE_n _ -> not_evaluable_construct "merge"
              | WHEN_n -> not_evaluable_construct "when"
              | FBY_n -> not_evaluable_construct "fby"
              | ARROW_n -> not_evaluable_construct "->"
              | CURRENT_n -> not_evaluable_construct "current"
              | PRE_n -> not_evaluable_construct "pre"

              | Predef_n(op,sargs)
                -> 
                  if sargs = [] then
                    let effargs =  (List.map rec_eval_const args) in
                      PredefEvalConst.f op lxm [] effargs
                  else
                    (* Well, it migth be possible after all... TODO *)
                    not_evaluable_construct (op2string op)
                    
                      
          ) (* FIN DE : eval_by_pos_const *)
        (*-------------------------------------*)
        (* Fonction r�cursive secondaire       *)
        (*-------------------------------------*)
        (* -> Eval. d'une expression sp�ciale  *)
        (* "par nom"                           *)
        (*-------------------------------------*)
      and eval_by_name_const
          (namop : by_name_op)   (* l'operateur *)
          (lxm : Lxm.t)      (* source de l'op�rateur *)
          (namargs : (Ident.t srcflagged * val_exp) list) (* arguments *)
          = (
            match namop with
              | STRUCT_anonymous_n -> 
                  finish_me "anonymous struct"; 
                  assert false
                    
              | STRUCT_n opid -> (
                  (* effet de bord : on tabule les param effectif *)
                  let arg_tab = Hashtbl.create 50 in 
                  let treat_one_arg ((pid:Ident.t srcflagged), (pexp:val_exp)) =
                    if 
                      (Hashtbl.mem arg_tab pid.it) 
                    then
                      raise(EvalConst_error(
                              sprintf
                                "multiple definition of param %s in %s call"
                                (Ident.to_string pid.it)
                                (Ident.string_of_idref opid)))
                    else 
                      let v = rec_eval_const pexp in
                        match v with
                          | [x] -> Hashtbl.add arg_tab pid.it (pid.src, x)
                          | _ -> 
                              raise(
                                EvalConst_error(
                                  sprintf
                                    "unexpected tuple value for param %s in %s call"
                                    (Ident.to_string pid.it)
                                    (Ident.string_of_idref opid)
                                ))
                  in 
                    List.iter treat_one_arg namargs ;
                    (* pour l'instant, on ne traite que les constructions de struct *)
                    try let teff = env.id2type opid lxm in
                      [make_struct_const teff arg_tab]
                    with _ -> 
                      raise(EvalConst_error(
                              sprintf "struct type expected instead of %s"
                                (Ident.string_of_idref opid)))
                )
          ) (* FIN DE : eval_by_name_const *)
        (*-------------------------------------*)
        (* Corps de la fonction principale      *)
        (*-------------------------------------*)
      in 
        rec_eval_const vexp 
    ) (* fin de f *)

(*---------------------------------------------------------------------
  eval_array_size
  -----------------------------------------------------------------------
  R�le : calcule une taille de tableau 

  Entr�es: 

  Sorties :
  int (strictement positif)

  Effets de bord :
  EvalArray_error "bad array size, type int expected but get <t>" si t pas int
  EvalArray_error "bad array size <n>" si n <= 0
  ----------------------------------------------------------------------*)
and (eval_array_size: Eff.id_solver -> val_exp -> int) =
  fun id_solver szexp -> 
    match (f id_solver szexp) with
      | [Int_const_eff sz] -> 
          if (sz > 0) then sz else
            raise(EvalArray_error(sprintf "bad array size %d" sz))
      | [x] -> 
          raise(EvalArray_error(sprintf  "bad array size, int expected but get %s"
                        (LicDump.string_of_type_eff(Eff.type_of_const x)))) 
      | _ -> 
          raise(EvalArray_error(sprintf "bad array size, int expected, not a tuple"))
            
(*---------------------------------------------------------------------
  eval_array_index
  -----------------------------------------------------------------------
  R�le :

  Entr�es :
  id_solver, val_exp, taille du tableau

  Sorties :
  int (entre 0 et taille du tableau -1
  
  Effets de bord :
  EvalArray_error msg si pas bon
  ----------------------------------------------------------------------*)
and eval_array_index
    (env : Eff.id_solver)
    (ixexp : val_exp)
    (sz : int)
    (lxm : Lxm.t)
    = 
  try 
    (
      match (f env ixexp) with
        | [Int_const_eff i] 
        | [Extern_const_eff(_,_, Some (Int_const_eff i))] -> check_int i sz
        | [Extern_const_eff(id,_,None)] ->
            raise(EvalArray_error("The extern const " ^ (Ident.string_of_long id) ^ 
                                    " is abstract"))
        | [Extern_const_eff(_,_, Some x)] 
        | [x] -> raise(EvalArray_error(sprintf 
                        "bad array index, int expected but get %s"
                        (LicDump.string_of_type_eff(Eff.type_of_const x)))
                      ) 
        | _ -> raise(EvalArray_error(
                       sprintf "bad array index, int expected but get a tuple"))

    )
  with 
      EvalArray_error msg -> 
        raise (Compile_error(lxm, "\n*** can't eval constant: "^msg))
          
 and check_int i sz =
    if ((i >= 0) && (i < sz)) then i
    else raise(EvalArray_error(
                 sprintf "array index %d out of bounds 0..%d" i (sz-1)))

(*---------------------------------------------------------------------
  eval_array_slice
  -----------------------------------------------------------------------
  R�le :

  Entr�es :
  Eff.id_solver, slice_info, size du tableau,
  lxm (source de l'op�ration slice pour warning)
  Eff.Sor :
  slice_info_eff, i.e.
  (fisrt,last,step,width) tels que step <> 0 et
  - si step > 0 alors 0<=first<=last<=sz
  - si step < 0 alors 0<=last<=first<=sz
  - 1<=width<=sz 
  Effets de bord :
  EvalArray_error msg si pas bon
  ----------------------------------------------------------------------*)
and eval_array_slice (env : Eff.id_solver) (sl : slice_info) (sz : int) (lxm : Lxm.t) =
  try
    let first_ix = eval_array_index env sl.si_first sz lxm in 
    let last_ix = eval_array_index env sl.si_last sz lxm in
    let step =
      match sl.si_step with
        | Some stepexp -> (
            match (f env stepexp) with
              | [Int_const_eff s] ->  s (* ok *)                    
              | [x] -> raise(EvalArray_error(
                      sprintf  "bad array step, int expected but get %s"
                        (LicDump.string_of_type_eff (Eff.type_of_const x)))) 
              | _ -> raise(EvalArray_error(
                             sprintf "bad array step, int expected but get a tuple"))
          )
        | None -> if (first_ix <= last_ix) then 1 else -1
    in
      if
        (step = 0) 
        || ((step > 0) && (first_ix > last_ix))
        || ((step < 0) && (first_ix < last_ix))
      then
        let msg = sprintf "bad array slice [%d..%d] step %d" first_ix last_ix step in
          raise (EvalArray_error msg)
      else
        (* index relatif du dernier *)
        let last_rel = abs (last_ix-first_ix) in
        let abs_step = abs step in
          (* le dernier est-il pris dans la tranche ? *)
          if ((last_rel mod abs_step) <> 0) then
            warning lxm (sprintf "last index out of slice [%d..%d step %d]" 
                           first_ix last_ix step);
          let width = 1 + last_rel/abs_step in
            (* on force le dernier a �tre dans la tranche *)
          let real_last_ix = first_ix + (width-1) * step in
            (* (first_ix,last_ix,step,width) *)
            {
              se_first = first_ix;
              se_last = real_last_ix;
              se_step = step;
              se_width = width
            }
  with 
      EvalArray_error msg -> 
        raise (Compile_error(lxm, "\n*** can't eval constant: "^msg))