Ocamlyacc, the parser generator inspired by Yacc for OCaml, is not very flexible. It generates an interface file for your parser, which only exports the
token type and start rules. It is inconvenient if you want to document your parser with ocamldoc and/or export additional symbols.
We propose two solutions to this problem. The traditional but incomplete one, and a new solution instructing ocamlbuild to use a custom interface.
Let's say your parser definition is in
parser.mly. Ocamlyacc will generate
If you declare functions, types or exceptions in the parser header (or trailer) in
parser.mly, they won't appear in
parser.mli and thus be unavailable in other parts of your code. They also won't be documented by ocamldoc.
The usually proposed solution is to put these shared elements into their own module (e.g. Parser_utils), where they can be documented and reused. You can open Parser_utils in the header of your parser and in other parts of your code that needs it.
The problem with this approach is that the token type and parsing rules are still undocumented and that you need to use two different modules for parsing.
Thanks to ocamlbuild, we can keep our code in
parser.mly and selectively document and export it in a custom interface file. We can choose what to export or not at our own discretion and document it accordingly if we want to. The only drawback is some redundancy, as usual with OCaml interface files.
To respect ocamlbuild hygiene rules and to avoid ocamlyacc overwriting our interface file, we will name it
parser.override.mli. We put in this interface the same kind of things for the parser as we do in
lexer.mli for the lexer (ocamllex does not generate an interface file and will keep ours).
We must then instruct ocamlbuild to use our
parser.override.mli instead of ocamlyacc-generated
parser.mli. This is done through the following ocamlbuild plugin.
Ideally, we would like to extend the “ocamlyacc” rule to overwrite
parser.override.mli just after having called ocamlyacc. However, we could not found a way to do this without rewriting the full “ocamlyacc” rule and putting it before the predefined one.
Instead, we will generate
parser.mli by copying
parser.override.mli before calling ocamlyacc. By chance, default rule order of ocamlbuild is such that our custom
parser.mli file will be used for dependency analysis and interface compilation before being overwritten by ocamlyacc. The ocamlyacc
parser.mli will still overwrite our own at some point, but is never used.
(* Plugin for [ocamlbuild]. *) open Ocamlbuild_plugin (* Hook handler. This is where our extensions for the [ocamlbuild] system lie. * * It is registered automatically when [ocamlbuild] is called. *) let hook_handler = function After_rules -> (* Overrides ocamlyacc-generated parser.mli with parser.override.mli *) copy_rule "Overrides Parser interface" ~insert: (`before "ocamlyacc") "parser.override.mli" "parser.mli" | _ -> () (* Registers our hook handler when this module is loaded (by ocamlbuild). *) let _ = dispatch hook_handler
In example.tar.xz, you will find find a example of both approaches, complete with lexer, parser and driver. In each directory, you can generate the executable by calling
ocamlbuild driver.native and the documentation by calling