First class AST patterns
PPX rewriters often need to recognize fragments the OCaml AST, for instance to parse the payload of an attribute/expression. You can do that with a pattern matching and manual error reporting when the input is not what you expect but this has proven to quickly become extremely verbose and unreadable.
This module aims to help with that by providing first class AST patterns.
To understand how to use it, let's consider the example of ppx_inline_test. We want to recognize patterns of the form:
let%test "name" = expr
Which is a syntactic sugar for:
[%%test let "name" = expr]
If we wanted to write a function that recognizes the payload of %%test
using normal
pattern matching we would write:
let match_payload = function
| Pstr [ { pstr_desc = Pstr_value (Nonrecursive,
[ { pvb_pat = Ppat_constant (Constant_string
(name, None))
; pvb_expr = e
; _ } ])
; _ } ] ->
(name, e)
| _ -> Location.raise_errorf ...
This is quite cumbersome, and this is still not right: this function drops all attributes without notice.
Now let's imagine we wanted to construct the payload instead, using Ast_builder
one
would write:
let build_payload ~loc name expr =
let (module B) = Ast_builder.with_loc loc in
let open B in
pstr [ pstr_value Nonrecursive (value_binding ~pat:(pstring name) ~expr) ]
Constructing a first class pattern is almost as simple as replacing Ast_builder
by
Ast_pattern
:
let payload_pattern name expr =
let open Ast_pattern in
pstr (pstr_value nonrecursive (value_binding ~pat:(pstring __) ~expr:__) ^:: nil)
Notice that the place-holders for name
and expr
have been replaced by __
. The
following pattern with have type:
(payload, string -> expression -> 'a, 'a) Ast_pattern.t
which means that it matches values of type payload
and captures a string and
expression from it. The two captured elements comes from the use of __
.
Type of a pattern:
'a
is the type of value matched by the pattern'b
is the continuation, for instance for a pattern that captures an int
and a
string
, 'b
will be int -> string -> _
'c
is the result of the continuation.Matches a value against a pattern.
Pattern that captures its input.
Same as __
but also captures the location.
Note: this should only be used for types that do not embed a location. For instance you can use it to capture a string constant:
estring __'
but using it to capture an expression would not yield the expected result:
pair (eint (int 42)) __'
In the latter case you should use the pexp_loc
field of the captured expression
instead.