(* Parsing the command-line option for the Mini-ML compiler/interpreter *)

type options = {
  input     : string option;
  eval      : bool;
  compile   : string option;
  libs      : string list;
  verbose   : Utils.String.Set.t;
  raw_edits : bool
}

let abort msg =
  Utils.highlight (Printf.sprintf "Command-line error: %s" msg); exit 1

let printf  = Printf.printf
let sprintf = Printf.sprintf

(* Help *)

let help () =
  let file = Filename.basename Sys.argv.(0) in
  printf "Usage: %s [<option> ...] [<input>.ml | \"-\"]\n" file;
  print_endline "where <input>.mml is the Mini-ML source file (default: stdin),";
  print_endline "and each <option> (if any) is one of the following:";
  print_endline "  -I <paths>             Library paths (colon-separated)";
  print_endline "  -c [<file>.ml]         Translate to OCaml in <file>.ml";
  print_endline "                         (default: <input>.ml)";
  print_endline "  -e, --eval             Interpret <input>.mml or stdin";
  print_endline "      --raw-edits        Do not optimise translation edits";
  print_endline "      --verbose=<phases> Colon-separated phases: cmdline, lexer, parser";
  print_endline "      --version          Short commit hash on stdout";
  print_endline "  -h, --help             This help";
  exit 0

(* Version *)

let version () = printf "%s\n" Version.version; exit 0

(* Specifying the command-line options a la GNU *)

let input     = ref None
and eval      = ref false
and compile   = ref None
and verbose   = ref Utils.String.Set.empty
and libs      = ref []
and raw_edits = ref false

let verb_str  = ref ""

let set_opt var err =
  Some (fun x -> if !var = None then var := Some x else raise (Getopt.Error err))

let split_at_colon = Str.(split (regexp ":"))

let add_path p = libs := !libs @ split_at_colon p

let add_verbose d =
  verbose := List.fold_left (Utils.swap Utils.String.Set.add)
                            !verbose
                            (split_at_colon d)

let specs =
  let open! Getopt in [
    'I',     nolong,      None, Some add_path;
    'c',     nolong,      set compile (Some ""),
                          set_opt compile "Multiple OCaml outputs";
    'e',     "eval",      set eval true, None;
    noshort, "raw-edits", set raw_edits true, None;
    noshort, "verbose",   None, Some add_verbose;
    'h',     "help",      Some help, None;
    noshort, "version",   Some version, None
  ]
;;

(* Handler of anonymous arguments *)

let anonymous arg =
  match !input with
      None -> input := Some arg
  | Some _ -> abort (sprintf "Multiple inputs")

(* Checking options *)

let string_of convert = function
    None -> "None"
| Some s -> sprintf "Some %s" (convert s)

let string_of_path p =
  let apply s a = if a = "" then s else s ^ ":" ^ a
  in List.fold_right apply p ""

let quote s = Printf.sprintf "\"%s\"" s

let print_opt () =
  printf "COMMAND LINE\n";
  printf "input     = %s\n" (string_of quote !input);
  printf "compile   = %s\n" (string_of quote !compile);
  printf "eval      = %B\n" !eval;
  printf "raw_edits = %b\n" !raw_edits;
  printf "verbose   = %s\n" !verb_str;
  printf "libs      = %s\n" (string_of_path !libs)

let check () =
  let () =
    if Utils.String.Set.mem "cmdline" !verbose then print_opt () in

  let input =
    match !input with
      None | Some "-" ->
        if !compile <> None then
          abort "An input file is missing (for compilation)."
        else !input
    | Some file_path ->
        if   Filename.check_suffix file_path ".mml"
        then if   Sys.file_exists file_path
             then Some file_path
             else abort "Source file not found."
        else abort "Source file lacks the extension .mml." in

  let compile =
    match !compile with
      Some _ when !eval -> abort "Options -e and -c are mutually exclusive."
    | None | Some "-" -> !compile
    | Some "" ->
       (match input with
          None | Some "-" -> abort "The target OCaml filename is missing."
        | Some file -> Some (Filename.remove_extension file ^ ".ml"))
    | Some compile' ->
        if   Filename.check_suffix compile' ".ml"
        then !compile
        else abort "The extension of the target OCaml file is not .ml" in

  (* Exporting remaining options as non-mutable values *)

  let eval      = !eval
  and verbose   = !verbose
  and libs      = !libs
  and raw_edits = !raw_edits in

  let () =
  if Utils.String.Set.mem "cmdline" verbose then
    begin
      printf "\nEXPORTED COMMAND LINE\n";
      printf "input     = %s\n" (string_of quote input);
      printf "compile   = %s\n" (string_of quote compile);
      printf "eval      = %B\n" eval;
      printf "raw_edits = %B\n" raw_edits;
      printf "verbose   = %s\n" !verb_str;
      printf "I         = %s\n" (string_of_path libs)
    end

  in {input; eval; compile; libs; verbose; raw_edits}

(* Parsing the command-line options *)

let read () =
  try
    Getopt.parse_cmdline specs anonymous;
    (verb_str :=
       let apply e a =
         if a <> "" then Printf.sprintf "%s, %s" e a else e
       in Utils.String.Set.fold apply !verbose "");
    check ()
  with Getopt.Error msg -> abort msg
