diff options
Diffstat (limited to 'bin/repl_cmd.ml')
-rw-r--r-- | bin/repl_cmd.ml | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/bin/repl_cmd.ml b/bin/repl_cmd.ml new file mode 100644 index 0000000..9ebeae7 --- /dev/null +++ b/bin/repl_cmd.ml | |||
@@ -0,0 +1,178 @@ | |||
1 | open Core | ||
2 | open Option.Let_syntax | ||
3 | |||
4 | let join_str_list ~sep = function | ||
5 | | [] -> "" | ||
6 | | s :: ss -> List.fold ss ~init:s ~f:(fun acc s -> acc ^ sep ^ s) | ||
7 | |||
8 | type cmd = { | ||
9 | args : string; | ||
10 | opts : unit -> string list; | ||
11 | next : (string -> (string, cmd) Either.t) option; | ||
12 | call : string list -> bool; | ||
13 | } | ||
14 | |||
15 | let set_opt_cmd opt setting = | ||
16 | { | ||
17 | args = "<option value>"; | ||
18 | opts = (fun () -> Settings.allowed_values setting); | ||
19 | next = None; | ||
20 | call = | ||
21 | (fun args -> | ||
22 | match Settings.set_to setting args with | ||
23 | | None -> true | ||
24 | | Some msg -> | ||
25 | printf "Failed to set option %s: %s\n%!" opt msg; | ||
26 | false); | ||
27 | } | ||
28 | |||
29 | let set_cmd = | ||
30 | { | ||
31 | args = "<option name> <option value>"; | ||
32 | opts = (fun () -> Map.keys Settings.settings); | ||
33 | next = | ||
34 | Some | ||
35 | (fun opt -> | ||
36 | match Map.find Settings.settings opt with | ||
37 | | Some setting -> Second (set_opt_cmd opt setting) | ||
38 | | None -> First (sprintf "Unknown option '%s'" opt)); | ||
39 | call = | ||
40 | (fun _ -> | ||
41 | printf "Missing option argument value\n%!"; | ||
42 | false); | ||
43 | } | ||
44 | |||
45 | let settings_cmd = | ||
46 | { | ||
47 | args = ""; | ||
48 | opts = (fun () -> []); | ||
49 | next = None; | ||
50 | call = | ||
51 | (function | ||
52 | | [] -> | ||
53 | Settings.print (); | ||
54 | true | ||
55 | | _ -> | ||
56 | printf "Expected no arguments\n%!"; | ||
57 | false); | ||
58 | } | ||
59 | |||
60 | let run_cmd = | ||
61 | { | ||
62 | args = "<filename>"; | ||
63 | opts = (fun () -> []); | ||
64 | next = None; | ||
65 | call = | ||
66 | (function | ||
67 | | [ filename ] -> Run.eval_file filename | ||
68 | | _ -> | ||
69 | printf "Expected one argument (the filename)\n%!"; | ||
70 | false); | ||
71 | } | ||
72 | |||
73 | let quit_cmd = | ||
74 | { args = ""; opts = (fun () -> []); next = None; call = (fun _ -> exit 0) } | ||
75 | |||
76 | let commands = | ||
77 | Map.of_alist_exn | ||
78 | (module String) | ||
79 | [ | ||
80 | ("quit", quit_cmd); | ||
81 | ("set", set_cmd); | ||
82 | ("settings", settings_cmd); | ||
83 | ("run", run_cmd); | ||
84 | ] | ||
85 | |||
86 | let root_cmd = | ||
87 | { | ||
88 | args = "<command>"; | ||
89 | opts = (fun () -> Map.keys commands); | ||
90 | next = | ||
91 | Some | ||
92 | (fun cmd_name -> | ||
93 | match Map.find commands cmd_name with | ||
94 | | Some cmd -> Second cmd | ||
95 | | None -> | ||
96 | First | ||
97 | (sprintf "Unknown command '%s' (expected one of {%s})" cmd_name | ||
98 | (Map.keys commands |> join_str_list ~sep:", "))); | ||
99 | call = | ||
100 | (fun _ -> | ||
101 | printf "Missing command!\n%!"; | ||
102 | false); | ||
103 | } | ||
104 | |||
105 | let is_space = Char.( = ) ' ' | ||
106 | let strip_space = String.strip ~drop:is_space | ||
107 | let lstrip_space = String.lstrip ~drop:is_space | ||
108 | |||
109 | let clean_str_list ss = | ||
110 | ss |> List.map ~f:strip_space | ||
111 | |> List.filter ~f:(fun s -> not (String.is_empty s)) | ||
112 | |||
113 | let words s = s |> String.split ~on:' ' |> clean_str_list | ||
114 | let unwords ss = clean_str_list ss |> join_str_list ~sep:" " | ||
115 | |||
116 | let rec call cmd args = | ||
117 | match args with | ||
118 | | [] -> cmd.call [] | ||
119 | | arg0 :: argn -> ( | ||
120 | match cmd.next with | ||
121 | | None -> cmd.call args | ||
122 | | Some next -> ( | ||
123 | match next arg0 with | ||
124 | | First msg -> | ||
125 | printf "%s\n%!" msg; | ||
126 | false | ||
127 | | Second cmd' -> call cmd' argn)) | ||
128 | |||
129 | let try_lsplit2_space s = | ||
130 | match String.lsplit2 s ~on:' ' with Some (l, r) -> (l, r) | None -> (s, "") | ||
131 | |||
132 | let lsplit2_space' s = | ||
133 | let%bind l, r = String.lsplit2 s ~on:' ' in | ||
134 | (* s = l ^ " " ^ r *) | ||
135 | let r' = lstrip_space r in | ||
136 | let space = " " ^ String.chop_suffix_exn r ~suffix:r' in | ||
137 | (* s = l ^ space ^ r' *) | ||
138 | return (l, space, r') | ||
139 | |||
140 | let rec completions cmd args = | ||
141 | (* cmd|<TAB> -> options | ||
142 | cmd|abc<TAB> -> options with prefix 'abc' | ||
143 | cmd|abc .*<TAB> -> subcommand 'abc' options, pass .* *) | ||
144 | if String.(args = "") then cmd.opts () | ||
145 | else | ||
146 | match lsplit2_space' args with | ||
147 | | None -> cmd.opts () |> List.filter ~f:(String.is_prefix ~prefix:args) | ||
148 | | Some (arg0, space, argn) -> ( | ||
149 | match cmd.next with | ||
150 | | None -> [] | ||
151 | | Some next -> ( | ||
152 | match next arg0 with | ||
153 | | First _ -> [] | ||
154 | | Second cmd' -> | ||
155 | completions cmd' argn | ||
156 | |> List.map ~f:(String.append (arg0 ^ space)))) | ||
157 | |||
158 | let rec hints cmd args = | ||
159 | (* cmd: "" -> " <args>" | ||
160 | cmd: "<space>+" -> "<args>" | ||
161 | cmd: "<space>+<subcmd>" -> "<hints for subcmd>" | ||
162 | cmd: "<space>+<subcmd> .*" -> "<hints for subcmd with .*>" *) | ||
163 | if String.(args = "") then Some (" " ^ cmd.args) | ||
164 | else if String.(strip_space args = "") then Some cmd.args | ||
165 | else | ||
166 | let args = lstrip_space args in | ||
167 | let%bind next = cmd.next in | ||
168 | match lsplit2_space' args with | ||
169 | | None -> | ||
170 | let%bind cmd' = next args |> Either.Second.to_option in | ||
171 | hints cmd' "" | ||
172 | | Some (arg0, space, argn) -> | ||
173 | let%bind cmd' = next arg0 |> Either.Second.to_option in | ||
174 | hints cmd' (space ^ argn) | ||
175 | |||
176 | let invoke cmd = call root_cmd (words cmd) | ||
177 | let complete cmd = completions root_cmd cmd | ||
178 | let hint cmd = hints root_cmd cmd | ||