1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
type path_kind =
  | File
  | Directory

type test_options = {
  verbose : bool;
  target_dir : string;
}

type mode_parse = {
  args : string list;
  mode : Typecheck.mode;
  mode_flag : bool;
  failfast : bool;
}

let validate_path ~kind path =
  let kind_name = function
    | File -> "File"
    | Directory -> "Directory"
  in
  try
    if path = "" then
      Error (Printf.sprintf "%s path must not be empty." (kind_name kind))
    else if not (Sys.file_exists path) then
      Error (Printf.sprintf "%s not found: %s" (kind_name kind) path)
    else
      let is_directory = Sys.is_directory path in
      match kind with
      | File when is_directory ->
          Error (Printf.sprintf "Expected a file path but received a directory: %s" path)
      | Directory when not is_directory ->
          Error (Printf.sprintf "Expected a directory path but received a file: %s" path)
      | _ -> Ok ()
  with
  | Sys_error msg -> Error msg

let parse_mode_args (args : string list) : (mode_parse, string) result =
  let rec extract acc mode seen failfast = function
    | [] ->
        Ok {
          args = List.rev acc;
          mode;
          mode_flag = seen;
          failfast;
        }
    | "--mode" :: [] ->
        Error "Missing value for --mode. Use --mode repl|strict"
    | "--mode" :: m :: rest ->
        if seen then
          Error "Duplicate --mode flag. Use --mode repl|strict only once."
        else
          (match Typecheck.mode_of_string m with
           | Some mode' -> extract acc mode' true failfast rest
           | None ->
               Error (Printf.sprintf "Invalid mode '%s'. Use --mode repl|strict" m))
    | "--failfast" :: rest -> extract acc mode seen true rest
    | x :: xs -> extract (x :: acc) mode seen failfast xs
  in
  extract [] Typecheck.Repl false false args

let validate_cli_flags ~mode_flag ~unsafe_flag ~failfast_flag (args : string list) : (unit, string) result =
  let commands = ["run"; "repl"; "test"; "explain"; "init"; "doc"; "doctor"; "docs"; "update"; "publish"; "--help"; "-h"; "--version"; "-v"] in
  let command =
    match args with
    | _ :: "run" :: _ -> Some "run"
    | _ :: "repl" :: _ -> Some "repl"
    | _ :: cmd :: _ when List.mem cmd commands -> Some cmd
    | _ :: file :: _ when String.ends_with ~suffix:".t" file -> Some "run"
    | _ :: ("help" | "--help" | "-h") :: _ -> Some "--help"
    | _ :: ("version" | "--version" | "-v") :: _ -> Some "--version"
    | _ -> None
  in
  let run_expr = (command = Some "run") && List.mem "--expr" args in
  let mode_allowed =
    match command with
    | None
    | Some "repl"
    | Some "run"
    | Some "explain"
    | Some "--help"
    | Some "--version"
    | Some "-h"
    | Some "-v" -> true
    | _ -> false
  in
  let unsafe_allowed =
    match command with
    | None | Some "run" | Some "repl" -> true
    | _ -> false
  in
  if unsafe_flag && not unsafe_allowed then
    Error "--unsafe is only valid with `t run <file.t>` or `t` (REPL)."
  else if unsafe_flag && run_expr then
    Error "--unsafe cannot be used with `t run --expr`."
  else if mode_flag && (not mode_allowed) then
    Error "--mode only applies to repl/run/explain."
  else if failfast_flag && (not mode_allowed) then
    Error "--failfast only applies to repl/run/explain."
  else
    Ok ()

let parse_test_args ~cwd (args : string list) : (test_options, string) result =
  let verbose = ref false in
  let target_dir = ref None in
  let rec parse = function
    | [] ->
        Ok {
          verbose = !verbose;
          target_dir = (match !target_dir with Some dir -> dir | None -> cwd);
        }
    | ("--verbose" | "-v") :: rest ->
        verbose := true;
        parse rest
    | arg :: _ when String.length arg > 0 && arg.[0] = '-' ->
        Error (Printf.sprintf "Unknown option: %s" arg)
    | arg :: rest ->
        (match !target_dir with
         | None ->
             target_dir := Some arg;
             parse rest
         | Some _ ->
             Error (Printf.sprintf "Unexpected argument: %s" arg))
  in
  parse args