aboutsummaryrefslogtreecommitdiffstats
path: root/bin/repl_cmd.ml
diff options
context:
space:
mode:
Diffstat (limited to 'bin/repl_cmd.ml')
-rw-r--r--bin/repl_cmd.ml178
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 @@
1open Core
2open Option.Let_syntax
3
4let join_str_list ~sep = function
5 | [] -> ""
6 | s :: ss -> List.fold ss ~init:s ~f:(fun acc s -> acc ^ sep ^ s)
7
8type cmd = {
9 args : string;
10 opts : unit -> string list;
11 next : (string -> (string, cmd) Either.t) option;
12 call : string list -> bool;
13}
14
15let 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
29let 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
45let 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
60let 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
73let quit_cmd =
74 { args = ""; opts = (fun () -> []); next = None; call = (fun _ -> exit 0) }
75
76let 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
86let 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
105let is_space = Char.( = ) ' '
106let strip_space = String.strip ~drop:is_space
107let lstrip_space = String.lstrip ~drop:is_space
108
109let clean_str_list ss =
110 ss |> List.map ~f:strip_space
111 |> List.filter ~f:(fun s -> not (String.is_empty s))
112
113let words s = s |> String.split ~on:' ' |> clean_str_list
114let unwords ss = clean_str_list ss |> join_str_list ~sep:" "
115
116let 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
129let try_lsplit2_space s =
130 match String.lsplit2 s ~on:' ' with Some (l, r) -> (l, r) | None -> (s, "")
131
132let 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
140let 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
158let 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
176let invoke cmd = call root_cmd (words cmd)
177let complete cmd = completions root_cmd cmd
178let hint cmd = hints root_cmd cmd