3

tl;dr I'm trying to make a source transformation binary using AST_mapper and ppx_driver. I can't figure out how to get the example in the AST_mapper docs to be used by ppx_driver. Are there any good examples of how to use Ppx_driver.register_transformation_using_ocaml_current_ast?

I'm trying to port the example AST_mapper in the docs to be compatible with ppx_driver. Specifically, I'm looking to create a binary that takes source as input, transforms the source using this test mapper, and then outputs the transformed source. Unfortunately, the default main provided by Ast_mapper only accepts Ocaml AST as input (and presumably produces it as output). This is undesirable, because I don't want to have to run this through ocamlc with -dsource to get my output.

Here's my best stab at porting this:

test_mapper.ml

open Asttypes
open Parsetree
open Ast_mapper

let test_mapper argv =
  { default_mapper with
    expr = fun mapper expr ->
      Pprintast.expression Format.std_formatter expr;
      match expr with
      | { pexp_desc = Pexp_extension ({ txt = "test" }, PStr [])} ->
        Ast_helper.Exp.constant (Ast_helper.Const.int 42)
      | other -> default_mapper.expr mapper other; }

let test_transformation ast =
  let mapper = (test_mapper ast) in
    mapper.structure mapper ast

let () =
  Ppx_driver.register_transformation_using_ocaml_current_ast
    ~impl:test_transformation
    "test_transformation"

A few things to note:

  • The example from the docs didn't work out of the box (before introducing ppx_driver): Const_int 42 had to be replaced with Ast_helper.Const.int 42
  • For some reason test_mapper is Parsetree.structure -> mapper. (It is unclear to me why a recursive transformation needs the structure to create the mapper, but no matter.) But, this type isn't what Ppx_driver.register_transformation_using_ocaml_current_ast expects. So I wrote a sloppy wrapper test_transformation to make the typechecker happy (that is loosely based off how Ast_mapper.apply_lazy appears to apply a mapper to an AST so in theory it should work)

Unfortunately, after compiling this into a binary:

ocamlfind ocamlc -predicates ppx_driver -o test_mapper test_mapper.ml -linkpkg -package ppx_driver.runner

And running it on a sample file:

sample.ml

let x _ = [%test]

with the following:

./test_mapper sample.ml

I don't see any transformation occur (the sample file is regurgitated verbatim). What's more, the logging Pprintast.expression that I left in the code doesn't print anything, which suggests to me that my mapper never visits anything.

All of the examples I've been able to find in the wild are open sourced by Jane Street (who wrote ppx_*) and seem to either not register their transformation (perhaps there's some magic detection going on that's going over my head) or if they do they use Ppx_driver.register_transformation ~rules which uses Ppx_core.ContextFree (which doesn't seem to be complete and won't work for my real use case--but for the purposes of this question, I'm trying to keep things generally applicable).

Are there any good examples of how to do this properly? Why doesn't ppx_driver use my transformation?

Bailey Parker
  • 15,599
  • 5
  • 53
  • 91

1 Answers1

1

If you want to make a standalone rewriter with a single module, you need to add

let () = Ppx_driver.standalone ()

to run the rewriter and links with ppx_driver and not ppx_driver.runner: ppx_driver.runner runs the driver as soon as it is loaded, therefore before your transformation is registered. Note also that you should at least specify a specific Ast version and uses Ppx_driver.register_transformation rather than Ppx_driver.register_transformation_using_current_ocaml_ast otherwise there is little point in using ppx_driver rather than doing the parsing by hand with compiler-libs and the Pparse module.

octachron
  • 17,178
  • 2
  • 16
  • 23
  • Thanks so much! That did the trick! As for specifying an AST version and using `register_transformation`, can you expand on this? As I mentioned at the end of my question, I've seen both in my research but I'm unsure how to go about that (I couldn't find docs, just uncommented usage examples). `register_transformation` looks like it uses `~rules` which uses `Ppx_core.ContextFree`, which according to a comment in its source (linked in my answer) is incomplete. What is your recommendation? – Bailey Parker Jan 16 '18 at 00:37
  • Specifying the AST version can be done with opening `AST_406` (or any other version) before opening the AST-related modules and then converting back-and-forth to the AST version used by ppx_driver with the converter provided by OMP: `Migrate_parsetree_versions.Convert(AST_406)(AST_404).copy_mapper` . Then, it is possible to use the `impl` argument of `register_transformation`, the rules argument tend to be favored since rules give a precise description of the action of each rewriter making it easier to compose multiple rewriter. (Note also that OMP has a driver by itself). – octachron Jan 16 '18 at 10:35
  • Ah, that's convenient. I would definitely go that route, but my specific application doesn't fit well. `register_transformation` seems to only do `structure -> structure` and `interface -> interface` (which makes sense for source transformations). But, I'm looking to do `interface -> structure` (creating stubs for an interface), so it looks like I'll have to dig in pretty deep into the AST internals in a way that won't play nicely with the versioning. Thanks for you help! – Bailey Parker Jan 16 '18 at 14:45