OCaml specify path to ppx executable

268 Views Asked by At

I'm trying to figure out how to pass in the location of an executable to be run as a ppx filter for the OCaml compilers ocamlc/ocamlopt.

My questions are, basically

  • What format is a ppx filter expected to take as input?
  • What is it expected to produce?
  • In the case of cppo specifically, how do you configure it to accept the required format and emit the required format?
  • Why doesn't cat work as the "identity filter" and produce the same result using no filter at all?

For instance, here's a simple OCaml program.

(* foo.ml *)
let hi = ();;

Printf.printf "hi there\n"

Using an utterly trivial filter cat, we can see what sort of thing the filter is expected to handle as input.

$ ocamlopt -ppx cat foo.ml | cat -v
Caml1999M019M-^DM-^U??^@^@^@^G^@^@^@^A^@^@^@^C^@^@^@^B&foo.m
...
File "foo.ml", line 1:
Error: External preprocessor does not produce a valid file

It looks like some kind of binary format, maybe an AST representation?

cppo, with no options to configure it, processes textual OCaml source files when invoked with explicit files. I'm a little confused why it emits #line directives when invoked this way ... I believe these directives have meaning to a C compiler but not an OCaml compiler.

For example:

$ cppo foo.ml
# 1 "foo.ml"
let hi = ();;

Printf.printf "hi there\n"

And it works when invoked as a filter, supplying a file name of <stdin> to line directives.

$ cat foo.ml | cppo
# 1 "<stdin>"
let hi = ();;

Printf.printf "hi there\n"

Reading the --help for cppo, there's no mention of input and output formats or an argument like -ppx, which is somewhat disappointing.

What are you supposed to do to stitch all the pieces together?

2

There are 2 best solutions below

0
On BEST ANSWER

The Ocaml compiler supports two distinct families of preprocessor: textual preprocessors invoked with the -pp option and (binary) AST preprocessors invoked with the -ppx option.

Textual preprocessor are expected to take as an input the name of a textual source file and outputs on stdout either an OCaml source file or an OCaml binary AST. For those, cat does implement the identity map: ocamlc -pp catocamlc . An important limitation of those preprocessor is that they cannot be chained together.

Contrarily, -ppx AST preprocessor takes as an input the name of the input AST binary file and the name of the output binary AST file. In other words, in this case the identity map can be implemented with cp: ocamlc -ppx cpocamlc. Since both the input and output of these preprocessor are binary ASTs, multiple ppx preprocessor can be chained together.

The cppo preprocessor does belong to the first family and needs to be invoked with -pp

0
On

As it turns out, I mixed up the ppx argument, which covers extension points, with camlp4 (or possibly camlp5, the naming is weird there and I don't entirely understand it).

The correct incantation to put a camlp4 filter in front of the compiler appears to be with the -pp flag

The following commands all produce a.out

$ ocamlc foo.ml
$ ocamlc -pp cat foo.ml
$ ocamlc -pp cppo foo.ml

Somewhat surprisingly, this works too, even though the output is empty.

$ ocamlc -pp /usr/bin/true foo.ml

The following commands all do not emit valid OCaml and produce nothing

$ ocamlc -pp /usr/bin/false foo.ml
File "foo.ml", line 1:
Error: Error while running external preprocessor
$ ocamlc -pp /usr/bin/rev foo.ml
File "/var/.../ocamlpp27e608", line 1, characters 2-3:
Error: Syntax error