User Tools

Site Tools


blog:2011:01:12:custom_interface_with_ocamlyacc

Custom Interface with Ocamlyacc

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.

The Traditional Solution

Let's say your parser definition is in parser.mly. Ocamlyacc will generate parser.ml and parser.mli.

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.

Using Ocamlbuild

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.mli with 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.

myocamlbuild.ml
(* 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 ocamlbuild driver.docdir/index.html.

Discussion

Enter your comment. Wiki syntax is allowed:
 
blog/2011/01/12/custom_interface_with_ocamlyacc.txt · Last modified: 2011/02/20 20:28 (external edit)