Skip to content

Commit cdcc344

Browse files
Merge pull request #382 from talex5/toplevel-exit
Report exits from the toplevel correctly
2 parents 5708b6e + 098a644 commit cdcc344

File tree

7 files changed

+144
-4
lines changed

7 files changed

+144
-4
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#### Fixed
1010

1111
- Fixed compatibility with Cmdliner 1.1.0 (#371, @Leonidas-from-XIV)
12+
- Report errors and exit codes of toplevel directives (#382, @talex5,
13+
@Leonidas-from-XIV)
1214

1315
#### Removed
1416

lib/top/compat_top.ml

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,100 @@ let ctype_get_desc ty =
167167
#else
168168
(Ctype.repr ty).Types.desc
169169
#endif
170+
171+
exception Exit_with_status of int
172+
173+
174+
let execute_phrase print_outcome ppf phr =
175+
#if OCAML_VERSION >= (4, 12, 0)
176+
match Toploop.execute_phrase print_outcome ppf phr with
177+
| v -> v
178+
| exception Compenv.Exit_with_status status ->
179+
raise (Exit_with_status status)
180+
#else
181+
Toploop.execute_phrase print_outcome ppf phr
182+
#endif
183+
184+
#if OCAML_VERSION < (4, 14, 0)
185+
let std_err = Format.err_formatter
186+
187+
let patch_directive name directive =
188+
let patched_name = Format.asprintf "mdx_%s" name in
189+
let directive_info = Toploop.{ section = "MDX PATCHED"; doc = "Patched by MDX" } in
190+
Toploop.add_directive patched_name directive directive_info;
191+
patched_name
192+
193+
(* port of Topdirs.action_on_suberror *)
194+
let action_on_suberror b =
195+
if not b && not !Sys.interactive then
196+
raise (Exit_with_status 125)
197+
198+
let dir_use ppf name =
199+
action_on_suberror (Toploop.use_file ppf name)
200+
201+
let mdx_use = patch_directive "use" (Directive_string (dir_use std_err))
202+
203+
let mdx_install_printer = patch_directive "install_printer" (Directive_ident (Topdirs.dir_install_printer std_err))
204+
let mdx_remove_printer = patch_directive "remove_printer" (Directive_ident (Topdirs.dir_remove_printer std_err))
205+
#endif
206+
207+
#if OCAML_VERSION >= (4, 11, 0) && OCAML_VERSION < (4, 14, 0)
208+
209+
let dir_use_output ppf name =
210+
action_on_suberror (Toploop.use_output ppf name)
211+
212+
let mdx_use_output = patch_directive "use_output" (Directive_string (dir_use_output std_err))
213+
#endif
214+
215+
#if OCAML_VERSION < (4, 13, 0)
216+
let mdx_trace = patch_directive "trace" (Directive_ident (Topdirs.dir_trace std_err))
217+
let mdx_untrace = patch_directive "untrace" (Directive_ident (Topdirs.dir_untrace std_err))
218+
let mdx_untrace_all = patch_directive "untrace_all" (Directive_none (Topdirs.dir_untrace_all std_err))
219+
#endif
220+
221+
#if OCAML_VERSION < (4, 13, 0)
222+
(* [load] cannot be patched to return errors because the underlying code is not exposed:
223+
It would require [Topdirs.load_file] with the first argument to be [false] but the exposed
224+
version hardcodes it to [true].
225+
*)
226+
let mdx_load = patch_directive "load" (Directive_string (Topdirs.dir_load std_err))
227+
228+
(* On the other hand, [load_rec] can be patched because the curried [true] is the only
229+
difference between these directives *)
230+
let dir_load_rec ppf name =
231+
action_on_suberror (Topdirs.load_file ppf name)
232+
233+
let mdx_load_rec = patch_directive "load_rec" (Directive_string (dir_load_rec std_err))
234+
235+
#elif OCAML_VERSION >= (4, 13, 0) && OCAML_VERSION < (4, 14, 0)
236+
(* OCaml 4.13 exposes [Topeval.load_file] which allows us to patch [#load] too *)
237+
let dir_load ppf name =
238+
action_on_suberror (Topeval.load_file false ppf name)
239+
240+
let mdx_load = patch_directive "load" (Directive_string (dir_load std_err))
241+
242+
(* This uses [Topeval.load_file] because [Topdirs.load_file] is deprecated on 4.13 *)
243+
let dir_load_rec ppf name =
244+
action_on_suberror (Topeval.load_file true ppf name)
245+
246+
let mdx_load_rec = patch_directive "load_rec" (Directive_string (dir_load_rec std_err))
247+
#endif
248+
249+
let redirect_directive directive =
250+
match directive with
251+
#if OCAML_VERSION < (4, 14, 0)
252+
| "load" -> mdx_load
253+
| "load_rec" -> mdx_load_rec
254+
| "use" -> mdx_use
255+
| "install_printer" -> mdx_install_printer
256+
| "remove_printer" -> mdx_remove_printer
257+
#endif
258+
#if OCAML_VERSION >= (4, 11, 0) && OCAML_VERSION < (4, 14, 0)
259+
| "use_output" -> mdx_use_output
260+
#endif
261+
#if OCAML_VERSION < (4, 13, 0)
262+
| "trace" -> mdx_trace
263+
| "untrace" -> mdx_untrace
264+
| "untrace_all" -> mdx_untrace_all
265+
#endif
266+
| v -> v

lib/top/compat_top.mli

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,11 @@ val ctype_is_equal :
5959

6060
val ctype_expand_head_and_get_desc : Env.t -> Types.type_expr -> Types.type_desc
6161
val ctype_get_desc : Types.type_expr -> Types.type_desc
62+
63+
exception Exit_with_status of int
64+
65+
val execute_phrase :
66+
bool -> Format.formatter -> Parsetree.toplevel_phrase -> bool
67+
68+
(* If the directive has to be intercepted, this function will return the new name of the directive *)
69+
val redirect_directive : string -> string

lib/top/mdx_top.ml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ module Phrase = struct
161161

162162
let is_findlib_directive =
163163
let findlib_directive = function
164-
| "require" | "use" | "camlp4o" | "camlp4r" | "thread" -> true
164+
| "require" | "camlp4o" | "camlp4r" | "thread" -> true
165165
| _ -> false
166166
in
167167
function
@@ -290,6 +290,12 @@ module Rewrite = struct
290290
in
291291
Btype.backtrack snap;
292292
Ptop_def pstr)
293+
| Ptop_dir pdir ->
294+
let pdir_name = pdir.pdir_name in
295+
let pdir_name =
296+
{ pdir_name with txt = Compat_top.redirect_directive pdir_name.txt }
297+
in
298+
Ptop_dir { pdir with pdir_name }
293299
| _ -> phrase
294300

295301
(** [top_directive require "pkg"] builds the AST for [#require "pkg"] *)
@@ -305,7 +311,7 @@ module Rewrite = struct
305311
let preload verbose ppf =
306312
let require pkg =
307313
let p = top_directive_require pkg in
308-
let _ = Toploop.execute_phrase verbose ppf p in
314+
let _ = execute_phrase verbose ppf p in
309315
()
310316
in
311317
match active_rewriters () with
@@ -337,7 +343,7 @@ type t = {
337343
let toplevel_exec_phrase t ppf p =
338344
match Phrase.result p with
339345
| Error exn -> raise exn
340-
| Ok phrase ->
346+
| Ok phrase -> (
341347
Warnings.reset_fatal ();
342348
let mapper = Lexbuf.position_mapper (Phrase.start p) in
343349
let phrase =
@@ -356,7 +362,10 @@ let toplevel_exec_phrase t ppf p =
356362
if !Clflags.dump_parsetree then Printast.top_phrase ppf phrase;
357363
if !Clflags.dump_source then Pprintast.top_phrase ppf phrase;
358364
Env.reset_cache_toplevel ();
359-
Toploop.execute_phrase t.verbose ppf phrase
365+
try execute_phrase t.verbose ppf phrase
366+
with Exit_with_status code ->
367+
Format.fprintf ppf "[%d]@." code;
368+
false)
360369

361370
type var_and_value = V : 'a ref * 'a -> var_and_value
362371

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Exits from the toplevel are reported correctly:
2+
3+
```ocaml
4+
# #use "idontexist.ml";;
5+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Exits from the toplevel are reported correctly:
2+
3+
```ocaml
4+
# #use "idontexist.ml";;
5+
Cannot find file idontexist.ml.
6+
[125]
7+
```

test/bin/mdx-test/expect/dune.inc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@
3535
(alias runtest)
3636
(action (diff code/test-case.md code.actual)))
3737

38+
(rule
39+
(target compenv-exit.actual)
40+
(deps (package mdx) (source_tree compenv-exit))
41+
(action
42+
(with-stdout-to %{target}
43+
(chdir compenv-exit
44+
(run ocaml-mdx test --output - test-case.md)))))
45+
46+
(rule
47+
(alias runtest)
48+
(action (diff compenv-exit/test-case.md.expected compenv-exit.actual)))
49+
3850
(rule
3951
(target cram.actual)
4052
(deps (package mdx) (source_tree cram))

0 commit comments

Comments
 (0)