[wg-camlp4] Matching on concrete syntax (was: Re: Camlp4 uses)
Alain Frisch
alain.frisch at lexifi.com
Fri Mar 29 14:35:03 GMT 2013
On 03/29/2013 02:46 PM, Gabriel Scherer wrote:
> Would it be possible to use the extension mechanism itself for
> lightweight quasiquotations?
>
> I did a small experiment with -ppx recently (
> http://gallium.inria.fr/blog/a-library-to-record-ocaml-backtraces/smartcatch_ppx.ml,
> as a contribution to Jacques-Henri Jourdan's work on
> http://gallium.inria.fr/blog/a-library-to-record-ocaml-backtraces/ ),
> and frankly the absence of quasiquotations felt like a pain in the ass.
> Compare the camlp4 part of the relevant code:
>
> let add_debug_expr _loc patvar e =
> <:expr<
> let _ = ExceptionHandling.register $lid:patvar$ in
> $e$
> >>
>
> with the Ast_rewriter equivalent (the recommended solution alongside
> -ppx right now):
>
> let add_register patvar body =
> let open Ast_mapper in
> let register_fun = Location.mknoloc (Longident.parse "ExceptionHandling.register") in
> (* let _ = <register_fun> <patvar> in <body> *)
> E.(let_ Nonrecursive
> [P.any (), apply (ident register_fun) ["", ident patvar]]
> body)
I'd write it as:
let add_register e body =
let_in [pany, app (evar "ExceptionHandling.register") [e]] body
which does not look so bad. This relies on the following definitions,
which could go e.g. in a Ast_helper.Convenience module:
let evar s = E.ident (mknoloc (Longident.parse s))
let let_in l body = E.let_ Nonrecursive l b
(* maybe with an optional argument for the recursive case *)
let pany = P.any ()
let app f args = apply f (List.map (fun a -> "", a) args)
The rest of the code is interesting as well:
Camlp4:
value rec map_handler =
let patvar = "__exn" in
fun
[ <:match_case at _loc< $m1$ | $m2$ >> ->
<:match_case< $map_handler m1$ | $map_handler m2$ >>
| <:match_case at _loc< $p$ when $w$ -> $e$ >> ->
<:match_case at _loc<
($p$ as $lid:patvar$) when $w$ -> $add_debug_expr _loc patvar
e$ >>
| m -> m ];
value filter = object
inherit Ast.map as super;
method expr = fun
[ <:expr at _loc< try $e$ with [ $h$ ] >> ->
<:expr< try $e$ with [ $map_handler h$ ] >>
| x -> super#expr x ];
end;
PPX:
method expr e =
let e = super#expr e in
{ e with pexp_desc =
match e.pexp_desc with
| Pexp_try (body, handler) ->
let instrument_case (pat, body) =
let patvar_str = "__exn" in
let patvar = Location.mknoloc (Longident.parse patvar_str) in
let pat = { pat with ppat_desc =
Ppat_alias (pat, Location.mknoloc patvar_str) } in
(pat, add_register patvar body) in
Pexp_try (body, List.map instrument_case handler)
| other -> other
}
This might be a matter of taste, but I prefer the PPX version, which I
can read only by knowing about the Parsetree (which is required anyway
to write any such code), a normal OCaml library. The quotation and
anti-quotations in the Camlp4 version look very noisy, and it relies on
a syntax I'm not familiar with (revised syntax) and syntactic extensions
(quotations/antiquotations), with their own conventions
("<:match_case<", $lid:$). Since I'm not writing extensions every day,
I really prefer having to learn how to use simple OCaml data types and
libraries (Parsetree, Ast_helper) rather than to learn new syntax and
new concepts. Also, I'd write the code above as:
(* --> in Ast_helper.Convenience *)
let palias p x = P.alias p (mknoloc x)
let evar s = E.ident (mknoloc (Longident.parse s))
...
method expr e =
let e = super#expr e in
match e.pexp_desc with
| Pexp_try (body, handler) ->
let instrument_case (pat, body) =
(palias pat "__exn", add_register (evar "__exn") body)
in
{e with pexp_desc = Pexp_try (body, List.map instrument_case
handler)}
| e -> e
> But maybe we
> can have quasiquotations with the current extension mechanism?
>
> let add_register patvar body =
> [%quote
> let _ = ExceptionHandling.register [%anti patvar] in [%anti body]
> ]
Implementing this "quote" expander is not very difficult, just a little
bit tedious (and this can be automated by parsing the definition of the
Parsetree). Note that nothing forces to use [%anti x] for
antiquotations. We could very well decide on a more lightweight
convention, like prefixing identifiers, or using a dedicated operator:
[%quote
let _ = ExceptionHandling.register __patvar in __body
]
[%quote
let _ = ExceptionHandling.register !!patvar in !!body
]
But this approach would work nicely only for writing expressions or
patterns on OCaml expressions, not on other syntactic categories
(because the content of an extension node is an expression). The
problem is that AST-manipulating code tend to require to work a lot with
many different categories (like "match cases") even to build expressions.
Quasi-quotations would be useful if the expanders had to generate big
fragments of mostly static code, with only a few "dynamic" placeholders.
In my experience, this is rarely the case: you assemble the resulting
OCaml code by combining many small fragments generated programmatically.
For these cases, a nice library of "AST constructors" seems better to me.
-- Alain
More information about the wg-camlp4
mailing list