(** Time-stamp: <modified the 05/02/2009 (at 15:30) by Erwan Jahier> *)

(** Here follows a description of the different modules used by this lus2lic compiler.

(1) First of all, the Lustre files are parsed,

   lexer.mll  
   parser.mly
   parserUtils.ml
   lxm.mli/ml

which results into a parse tree containing raw source expressions.

   syntaxTree.ml  -> should rather be called rawSyntaxTab.ml ?

(2) Then, we perform reference checking at module level + model expansion.

   syntaxTab.mli/ml
      syntaxTabUtil.ml/mli
      instanciateModel.mli/ml
      symbolTab.mli/ml (type/const/node)

syntaxTab is a kind of layer above syntaxTree to make things easier afterwards.


(3) Finally, the compilation (type checking+const/type evaluation) is performed.

   compile.ml
      lazyCompiler.mli/ml
        evalConst.mli/ml
        evalType.mli/ml
   compiledData.ml
    

Some misc (eponymous) modules are used along the way.
   errors.ml 
   verbose.mli/ml
   version.ml
   ident.ml
*)


open Verbose
open SyntaxTree
open SyntaxTreeCore
open Lxm
open Errors
open Parsing
open Format


(*---------------------------------------------------------
Les args sont des variables GLOBALES
---------------------------------------------------------*)

let print_version = function (x: unit) -> (
  print_string (Version.str ^ "\n")
)

let usage_msg =  
  "usage: "^(Version.tool)^" [options] <lustre files>\nwhere [options] can be:" 
   
let rec arg_list = [
  ( "--node", Arg.String(fun x -> Global.main_node := x; Global.compile_all_items := false),
    "<node>"
  );
  ( "-n", Arg.String(fun x -> Global.main_node := x; Global.compile_all_items := false),
    "<node>\n\t Set the main node (all items are compiled if unset)"
  );

  ( "--output-file", Arg.String(fun x -> Global.outfile := x), "<file>"
  );
  ( "-o", Arg.String(fun x -> Global.outfile := x),
    "<file>\n\t Set the output file name."
  );

  ( "--keep-nested-calls", Arg.Unit (fun _ -> Global.one_op_per_equation := false),
    ""
  );
  ( "-knc", Arg.Unit (fun _ -> Global.one_op_per_equation := false),
    "\n\t Keep nested calls. By default, only one node per equation is generated."
  );

  ( "--expand-iterators", Arg.Unit (fun _ -> Global.inline_iterator := true),
    ""
  );
  ( "-ei", Arg.Unit (fun _ -> Global.inline_iterator := true),
    "\n\t Expand array iterators."
  );

  ( "--expand-enums", Arg.Unit (fun _ -> Global.expand_enums := true),
    ""
  );
  ( "-ee", Arg.Unit (fun _ -> Global.expand_enums := true),
    "\n\t Translate enums into integers."
  );

  ( "--expand-structs-and-arrays", Arg.Unit
      (fun _ -> Global.expand_structs := true;Global.inline_iterator := true),
    ""
  );
  ( "-esa", Arg.Unit
      (fun _ -> Global.expand_structs := true;Global.inline_iterator := true),
    "\n\t Expand structures and arrays using as many variables as necessary (automatically impose '-ei')."
  );

  ( "--expand-nodes", Arg.Unit (fun _ -> Global.expand_nodes := true),
    ""
  );
  ( "-en", Arg.Unit (fun _ -> Global.expand_nodes := true),
    "\n\t Expand the main node (use the first node if no one is specified)."
  );

  ( "--lustre-v4", Arg.Unit
      (fun _ -> set_v4_options ()),
    "\t"
  );

  ( "-lv4", Arg.Unit
      (fun _ -> set_v4_options ()),
    "\n\t Use Lustre V4 syntax (automatically impose '-ei -ee -esa')."
  );

  ( "--expanded-code", Arg.Unit
      (fun _ -> set_ec_options ()),
    ""
  );
  ( "-ec", Arg.Unit
      (fun _ -> set_ec_options ()),
    "\n\t Generate ec (actually just an alias for '-en -lv4')."
  );

  ( "-unit", Arg.Unit (fun x -> Global.run_unit_test := true),
    "\n\t Run some (internal) unit tests"
  );

  ( "--verbose-level", Arg.Int(fun vl -> Verbose.set_level vl ), "<int>"
  );
  ( "-vl", Arg.Int(fun vl -> Verbose.set_level vl ),
    "<int>\n\t Set the verbose level."
  );

  ( "--verbose", Arg.Unit (fun vl -> Verbose.set_level 1 ),
    ""
  );
  ( "-v", Arg.Unit (fun vl -> Verbose.set_level 1 ),
    "\n\t Set the verbose level to 1."
  );

  ( "--version", Arg.Unit(fun x -> print_version () ; exit 0),
    ""
  );
  ( "-version", Arg.Unit(fun x -> print_version () ; exit 0),
    "\n\t Display the current version of the tool."
  );


  ("-h", Arg.Unit (fun _ -> (Arg.usage arg_list usage_msg; exit 0)), "" );
  ("-help", Arg.Unit (fun _ -> (Arg.usage arg_list usage_msg; exit 0)),"" );
  ("--help", Arg.Unit (fun _ -> (Arg.usage arg_list usage_msg; exit 0)),
   "\n\t Display this message." )
]
and set_v4_options () =
  Global.lv4 := true;
  Global.inline_iterator := true;
  Global.expand_enums := true;
  Global.expand_structs := true
and set_ec_options () =
  set_v4_options ();
  Global.ec := true;
  Global.expand_nodes := true

and
    parse_args () = (
      Arg.parse arg_list  (* liste des options *)
        Global.add_infile (* arg par defaut = fichier d'entree *)
        usage_msg         (* message d'erreur *)
      ;
      ()
    )


(* Retourne un parse_tree *)
let lus_load lexbuf = (
  SolveIdent.recognize_predef_op 
    (Parser.sxLusFile Lexer.lexer lexbuf)
) 


  (*
    Lance le parser et renvoie la liste name-spaces d'entr�e.   
    Dans le cas d'un fichier sans package, on lui donne
    comme nom le basename de infile.
  *)
type maybe_packed = 
  | Packed of SyntaxTree.pack_or_model list
  | Unpacked of SyntaxTree.pack_or_model

let (get_source_list : string list -> SyntaxTree.pack_or_model list) =
  fun infile_list -> 
    let (get_one_source : string -> string list * maybe_packed) = 
      fun infile -> 
        let lexbuf = Global.lexbuf_of_file_name infile in
          match (lus_load lexbuf) with
            | PRPackBody(incl_files, pbdy) ->
                let nme = 
                  try Filename.chop_extension (Filename.basename infile) 
                  with _ -> print_string ("*** '"^infile^"': bad file name.\n"); exit 1
                in
                let pi = 
                  SyntaxTree.give_pack_this_name (Ident.pack_name_of_string nme) pbdy 
                in
                  incl_files, Unpacked (NSPack (Lxm.flagit pi (Lxm.dummy nme)))
            | PRPack_or_models(incl_files, nsl) -> incl_files, Packed nsl
    in
    let rec (get_remaining_source_list : maybe_packed * string list * string list -> 
           maybe_packed * string list * string list) =
      fun (maybe_pack, compiled, to_be_compiled) -> 
        match to_be_compiled with
          | [] -> (maybe_pack, compiled, [])
          | infile::tail ->
              if List.mem infile compiled then
                get_remaining_source_list (maybe_pack, compiled, tail)
              else
                let included_files, pack = get_one_source infile in
                let new_maybe_pack =
                  match maybe_pack, pack with
                    | Unpacked _, _ 
                    | _, Unpacked _ -> 
                        print_string ("old-style (un-packaged) lustre files can " ^
                                        " not be mixed with packages, nor be " ^
                                        " defined in more than 1 file.");
                        exit 1
                    | Packed l1, Packed l2 -> Packed (l1@l2)
                in
                  get_remaining_source_list(
                    new_maybe_pack, 
                    infile::compiled, 
                    tail@included_files)
    in
    let first_file = assert (infile_list <> []); List.hd infile_list in
    let included_files, first_pack = get_one_source first_file in
    let (pack_list,_,_) = get_remaining_source_list 
      (first_pack, [first_file], (List.tl infile_list) @ included_files)
    in
      match pack_list with 
        | Packed l -> l
        | Unpacked pack -> [pack]
    

let dump_entete oc = 
  let time = Unix.localtime (Unix.time ()) in
  let sys_call, _ = Array.fold_left 
    (fun (acc,i) x -> 
       if 70 < i + (String.length x) then 
	 acc ^ "\n--\t\t" ^ x, String.length ("\n--\t\t" ^ x)
       else 
	 acc ^ " " ^ x , (i+1+(String.length x))
    )
    ("",0) 
    Sys.argv
  and 
      date = ( 
	(string_of_int time.Unix.tm_mday) ^ "/" ^
	  (string_of_int (time.Unix.tm_mon+1)) ^  "/" ^
	  (string_of_int (1900+time.Unix.tm_year))
      )
  and time_str = (
    (string_of_int time.Unix.tm_hour) ^  ":" ^
      (if time.Unix.tm_min < 10 then "0" else "") ^
      (string_of_int time.Unix.tm_min) ^   ":" ^
      (if time.Unix.tm_sec < 10 then "0" else "") ^
      (string_of_int time.Unix.tm_sec) 
  )
    (*   and user = Unix.getlogin () *)
  and hostname = Unix.gethostname ()
  in
    output_string oc
      ("-- This file was generated by lus2lic version " ^ Version.str ^ 
         ".\n--\t" ^ sys_call ^ "
-- on " ^ hostname ^ 
         (* 	 "by "^ user ^ *)
         " the " ^ date ^ " at " ^ time_str ^ "\n\n");
    flush oc


let my_exit i =
  close_out !Global.oc;
  if Sys.file_exists !Global.outfile then Sys.remove !Global.outfile;
  exit i

let main = (
  (* Compile.init_appli () ; *)
  parse_args ();
  if !Global.run_unit_test then (
    UnifyType.unit_test ();
    exit 0
  );
  if (!Global.infiles = []) then (
    Arg.usage arg_list usage_msg ;
    exit 1
  );
  try (
    let nsl = get_source_list !Global.infiles in
    let main_node = 
      if !Global.main_node = "" then None else 
        Some (Ident.idref_of_string !Global.main_node)
    in
      if !Global.outfile <> "" then Global.oc := open_out !Global.outfile;
      dump_entete !Global.oc;
      Compile.doit nsl main_node;
      LicDump.dump_type_alias !Global.oc;
      close_out !Global.oc
  ) with
      Sys_error(s) ->
        prerr_string (s^"\n");
        my_exit 1
    | Global_error s ->
        print_global_error s ;
        my_exit 1
    | Parse_error ->
        print_compile_error (Lxm.last_made ()) "syntax error";
        my_exit 1
    | Compile_error(lxm,msg) -> 
        print_compile_error lxm msg ;
        my_exit 1
    | Assert_failure (file, line, col)  -> 
        prerr_string (
          "\n*** oops: an internal error (lus2lic) occurred in file "^ file ^ 
            ", line " ^ (string_of_int line) ^ ", column " ^
            (string_of_int col) ^ "\n*** when compiling lustre program" ^
            (if List.length !Global.infiles > 1 then "s " else " ") ^
            (String.concat ", " !Global.infiles) ^ "\n"^
            "\n*** You migth want to sent a bug report to jahier@imag.fr\n") ;
        my_exit 2

        (* | Compile_node_error(nkey,lxm,msg) -> ( *)
        (* print_compile_node_error nkey lxm msg ; *)
        (* exit 1 *)
        (* ) *)
        (* | Global_node_error(nkey,msg) -> ( *)
        (* print_global_node_error nkey msg ; *)
        (* exit 1 *)
        (* ) *)

)