(****************************************************************************)
(*                                                                          *)
(*                                   OCaml                                  *)
(*                                                                          *)
(*                            INRIA Rocquencourt                            *)
(*                                                                          *)
(*  Copyright  2006   Institut National de Recherche  en  Informatique et   *)
(*  en Automatique.  All rights reserved.  This file is distributed under   *)
(*  the terms of the GNU Library General Public License, with the special   *)
(*  exception on linking described in LICENSE at the top of the Camlp4      *)
(*  source tree.                                                            *)
(*                                                                          *)
(****************************************************************************)

(* Authors:
 * - Daniel de Rauglaudre: initial version
 * - Nicolas Pouillard: refactoring
 *)
type spec_list = list (string * Arg.spec * string);
open Format;

value rec action_arg s sl =
  fun
  [ Arg.Unit f -> if s = "" then do { f (); Some sl } else None
  | Arg.Bool f ->
      if s = "" then
        match sl with
        [ [s :: sl] ->
            try do { f (bool_of_string s); Some sl } with
            [ Invalid_argument "bool_of_string" -> None ]
        | [] -> None ]
      else
        try do { f (bool_of_string s); Some sl } with
        [ Invalid_argument "bool_of_string" -> None ]
  | Arg.Set r -> if s = "" then do { r.val := True; Some sl } else None
  | Arg.Clear r -> if s = "" then do { r.val := False; Some sl } else None
  | Arg.Rest f -> do { List.iter f [s :: sl]; Some [] }
  | Arg.String f ->
      if s = "" then
        match sl with
        [ [s :: sl] -> do { f s; Some sl }
        | [] -> None ]
      else do { f s; Some sl }
  | Arg.Set_string r ->
      if s = "" then
        match sl with
        [ [s :: sl] -> do { r.val := s; Some sl }
        | [] -> None ]
      else do { r.val := s; Some sl }
  | Arg.Int f ->
      if s = "" then
        match sl with
        [ [s :: sl] ->
            try do { f (int_of_string s); Some sl } with
            [ Failure "int_of_string" -> None ]
        | [] -> None ]
      else
        try do { f (int_of_string s); Some sl } with
        [ Failure "int_of_string" -> None ]
  | Arg.Set_int r ->
      if s = "" then
        match sl with
        [ [s :: sl] ->
            try do { r.val := (int_of_string s); Some sl } with
            [ Failure "int_of_string" -> None ]
        | [] -> None ]
      else
        try do { r.val := (int_of_string s); Some sl } with
        [ Failure "int_of_string" -> None ]
  | Arg.Float f ->
      if s = "" then
        match sl with
        [ [s :: sl] -> do { f (float_of_string s); Some sl }
        | [] -> None ]
      else do { f (float_of_string s); Some sl }
  | Arg.Set_float r ->
      if s = "" then
        match sl with
        [ [s :: sl] -> do { r.val := (float_of_string s); Some sl }
        | [] -> None ]
      else do { r.val := (float_of_string s); Some sl }
  | Arg.Tuple specs ->
      let rec action_args s sl =
        fun
        [ [] -> Some sl
        | [spec :: spec_list] ->
             match action_arg s sl spec with
             [ None -> action_args "" [] spec_list
             | Some [s :: sl] -> action_args s sl spec_list
             | Some sl -> action_args "" sl spec_list
             ]
        ] in
      action_args s sl specs
  | Arg.Symbol syms f ->
      match (if s = "" then sl else [s :: sl]) with
      [ [s :: sl] when List.mem s syms -> do { f s; Some sl }
      | _ -> None ]
  ];

value common_start s1 s2 =
  loop 0 where rec loop i =
    if i == String.length s1 || i == String.length s2 then i
    else if s1.[i] == s2.[i] then loop (i + 1)
    else i;

value parse_arg fold s sl =
  fold
    (fun (name, action, _) acu ->
      let i = common_start s name in
      if i == String.length name then
        try action_arg (String.sub s i (String.length s - i)) sl action with
        [ Arg.Bad _ -> acu ]
      else acu) None;

value rec parse_aux fold anon_fun =
  fun
  [ [] -> []
  | [s :: sl] ->
      if String.length s > 1 && s.[0] = '-' then
        match parse_arg fold s sl with
        [ Some sl -> parse_aux fold anon_fun sl
        | None -> [s :: parse_aux fold anon_fun sl] ]
      else do { (anon_fun s : unit); parse_aux fold anon_fun sl } ];

value align_doc key s =
  let s =
    loop 0 where rec loop i =
      if i = String.length s then ""
      else if s.[i] = ' ' then loop (i + 1)
     else String.sub s i (String.length s - i)
  in
  let (p, s) =
    if String.length s > 0 then
      if s.[0] = '<' then
        loop 0 where rec loop i =
          if i = String.length s then ("", s)
          else if s.[i] <> '>' then loop (i + 1)
          else
            let p = String.sub s 0 (i + 1) in
            loop (i + 1) where rec loop i =
              if i >= String.length s then (p, "")
              else if s.[i] = ' ' then loop (i + 1)
              else (p, String.sub s i (String.length s - i))
      else ("", s)
    else ("", "")
  in
  let tab =
    String.make (max 1 (16 - String.length key - String.length p)) ' '
  in
  p ^ tab  ^ s;

value make_symlist l =
  match l with
  [ [] -> "<none>"
  | [h::t] -> (List.fold_left (fun x y -> x ^ "|" ^ y) ("{" ^ h) t) ^ "}" ];

value print_usage_list l =
  List.iter
    (fun (key, spec, doc) ->
      match spec with
      [ Arg.Symbol symbs _ ->
          let s = make_symlist symbs in
          let synt = key ^ " " ^ s in
          eprintf "  %s %s\n" synt (align_doc synt doc)
      | _ -> eprintf "  %s %s\n" key (align_doc key doc) ] )
    l;

value remaining_args argv =
  let rec loop l i =
    if i == Array.length argv then l else loop [argv.(i) :: l] (i + 1)
  in
  List.rev (loop [] (Arg.current.val + 1));

value init_spec_list = ref [];
value ext_spec_list = ref [];

value init spec_list = init_spec_list.val := spec_list;

value add name spec descr =
  ext_spec_list.val := [(name, spec, descr) :: ext_spec_list.val];

value fold f init =
  let spec_list = init_spec_list.val @ ext_spec_list.val in
  let specs = Sort.list (fun (k1, _, _) (k2, _, _) -> k1 >= k2) spec_list in
  List.fold_right f specs init;

value parse anon_fun argv =
  let remaining_args = remaining_args argv in
  parse_aux fold anon_fun remaining_args;

value ext_spec_list () = ext_spec_list.val;
