realworldocaml / mdx

Execute code blocks inside your documentation
ISC License
268 stars 45 forks source link

fix: make directives usable in the `ocaml` mode #443

Open sorawee opened 11 months ago

sorawee commented 11 months ago

Prior this PR, the following code fails:

{@ocaml ocaml[
  #require "astring";;
  let x = Astring.strf;;
]}

because MDX incorrectly infers that the code block is a toplevel interaction (from the fact that the block starts with #), resulting in an error:

incomplete toplevel entry: unexpected character '#'.
Did you forget a space after the '#' at the start of the line

It is possible to workaround this issue as suggested in #421 by adding a comment as a first line.

{@ocaml ocaml[
  (* This works! *)
  #require "astring";;
  let x = Astring.strf;;
]}

but ideally the workaround should not be needed.

One may wonder why the inference is needed, since the above code block is already specified to be in the ocaml mode. The answer appears to be that we are expected to use the inference heuristics for a light sanity check, as the existing tests ("invalid ocaml" and "invalid toplevel" in test_block.ml) require:

"let x = 2;;" in the toplevel mode should error with
invalid toplevel syntax in toplevel blocks

"# let x = 2;;" in the ocaml mode should error with
toplevel syntax is not allowed in OCaml blocks

As a result, this PR keeps the light sanity check intact, but adjusts the inference heuristics to be more conservative. A block is now considered a toplevel interaction when it starts with # followed by a space. This fixes the issue, making it possible to use directives. As a bonus, directives will now also work even when the mode is not specified at all. But one disadvantage is that this kind of code will no longer be considered invalid.

#1+1;;
...
#2+2;;
...
sorawee commented 11 months ago

@Julow Here's the actual result of #1+1;; after this PR:

{@ocaml[
  #1+1;;
][
{err@mdx-error[
Line 1, characters 4-5:
Error: Syntax error
]err}]}

This is because #1+1;; makes the block considered the ocaml block type due to the absence of space after #.