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_desc =
Ppat_constant (Pconst_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.make loc in
let open B in
Parsetree.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 () =
let open Ast_pattern in
pstr
(pstr_value nonrecursive
(value_binding ~pat:(pstring __) ~expr:__ ^:: nil)
^:: nil)
Notice that the place-holders for name
and expr
have been replaced by __
. An extra unit argument appears because of value restriction. The function above would create a pattern with 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 __
.
An empty payload (e.g. for an attribute that has no payload) is matched by Ast_pattern.(pstr nil)
. A payload with exactly one expression (e.g. to specify a custom function in a deriver) is matched by Ast_pattern.(single_expr_payload __)
.
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.
val parse :
('a, 'b, 'c) t ->
Location.t ->
?on_error:(unit -> 'c) ->
'a ->
'b ->
'c
Matches a value against a pattern. Raise a located error in case of failure.
Matches a value against a pattern and return a result.
val __ : ('a, 'a -> 'b, 'b) t
Pattern that captures its input.
val __' : ('a, 'a Loc.t -> 'b, 'b) t
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.
val drop : ('a, 'b, 'b) t
Useful when some part of the AST is irrelevant. With __
, the captured value is passed to the continuation, with drop
it is ignored. In higher-level pattern matching, it is called wildcard pattern.
val as__ : ('a, 'b, 'c) t -> ('a, 'a -> 'b, 'c) t
As-pattern. Passes the current node to the continuation.
Pitfall. In general, the continuation is called step by step by being applied partially to every next captured node in the pattern. That means that the node captured by as__
is passed to the continuation before checking if the pattern is matched.
val alt : ('a, 'b, 'c) t -> ('a, 'b, 'c) t -> ('a, 'b, 'c) t
alt
stands for `alternatives'. It matches either the first pattern or the second one.
val alt_option :
('a, 'v -> 'b, 'c) t ->
('a, 'b, 'c) t ->
('a, 'v option -> 'b, 'c) t
Same as alt
, for the common case where the left-hand-side captures a value but not the right-hand-side.
val (|||) : ('a, 'b, 'c) t -> ('a, 'b, 'c) t -> ('a, 'b, 'c) t
val map : ('a, 'b, 'c) t -> f:('d -> 'b) -> ('a, 'd, 'c) t
val map' : ('a, 'b, 'c) t -> f:(Location.t -> 'd -> 'b) -> ('a, 'd, 'c) t
val map_result : ('a, 'b, 'c) t -> f:('c -> 'd) -> ('a, 'b, 'd) t
val (>>|) : ('a, 'b, 'c) t -> ('d -> 'b) -> ('a, 'd, 'c) t
val map0 : ('a, 'b, 'c) t -> f:'v -> ('a, 'v -> 'b, 'c) t
val map1 : ('a, 'v1 -> 'b, 'c) t -> f:('v1 -> 'v) -> ('a, 'v -> 'b, 'c) t
val map2 :
('a, 'v1 -> 'v2 -> 'b, 'c) t ->
f:('v1 -> 'v2 -> 'v) ->
('a, 'v -> 'b, 'c) t
val map0' : ('a, 'b, 'c) t -> f:(Location.t -> 'v) -> ('a, 'v -> 'b, 'c) t
val map1' :
('a, 'v1 -> 'b, 'c) t ->
f:(Location.t -> 'v1 -> 'v) ->
('a, 'v -> 'b, 'c) t
val map2' :
('a, 'v1 -> 'v2 -> 'b, 'c) t ->
f:(Location.t -> 'v1 -> 'v2 -> 'v) ->
('a, 'v -> 'b, 'c) t
val nil : (_ list, 'a, 'a) t
val (^::) : ('a, 'b, 'c) t -> ('a list, 'c, 'd) t -> ('a list, 'b, 'd) t
val many : ('a, 'b -> 'c, 'c) t -> ('a list, 'b list -> 'c, 'c) t
val int : int -> (int, 'a, 'a) t
val char : char -> (char, 'a, 'a) t
val string : string -> (string, 'a, 'a) t
val float : float -> (float, 'a, 'a) t
val int32 : int32 -> (int32, 'a, 'a) t
val int64 : int64 -> (int64, 'a, 'a) t
val nativeint : nativeint -> (nativeint, 'a, 'a) t
val bool : bool -> (bool, 'a, 'a) t
val cst :
to_string:('a -> string) ->
?equal:('a -> 'a -> bool) ->
'a ->
('a, 'b, 'b) t
val none : (_ option, 'a, 'a) t
val some : ('a, 'b, 'c) t -> ('a option, 'b, 'c) t
val pair : ('a1, 'b, 'c) t -> ('a2, 'c, 'd) t -> ('a1 * 'a2, 'b, 'd) t
val (**) : ('a1, 'b, 'c) t -> ('a2, 'c, 'd) t -> ('a1 * 'a2, 'b, 'd) t
val triple :
('a1, 'b, 'c) t ->
('a2, 'c, 'd) t ->
('a3, 'd, 'e) t ->
('a1 * 'a2 * 'a3, 'b, 'e) t
val loc : ('a, 'b, 'c) t -> ('a Loc.t, 'b, 'c) t
val pack0 : ('a, 'b, 'c) t -> ('a, unit -> 'b, 'c) t
val pack2 : ('a, 'b -> 'c -> 'd, 'e) t -> ('a, ('b * 'c) -> 'd, 'e) t
val pack3 :
('a, 'b -> 'c -> 'd -> 'e, 'f) t ->
('a, ('b * 'c * 'd) -> 'e, 'f) t
AST patterns for each constructor/record of the parsetree are generated in the same way AST builders are generated. In addition, for every wrapper we generate a pattern to match the loc
and attributes
fields. For instance for the expression
type:
val pexp_loc :
(Location.t, 'a, 'b) t ->
(expression, 'b, 'c) t ->
(expression, 'a, 'c) t
val pexp_attributes :
(attributes, 'a, 'b) t ->
(expression, 'b, 'c) t ->
(expression, 'a, 'c) t
val pcl_fun :
(Astlib.Ast_500.Asttypes.arg_label, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.expression option, 'c, 'd) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.pattern, 'e, 'f) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.class_expr, 'g, 'h) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.class_expr, 'i, 'j) Ppxlib__.Ast_pattern0.t
val class_infos :
virt:(Astlib.Ast_500.Asttypes.virtual_flag, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
params:
((Astlib.Ast_500.Parsetree.core_type
* (Astlib.Ast_500.Asttypes.variance * Astlib.Ast_500.Asttypes.injectivity))
list,
'c,
'd)
Ppxlib__.Ast_pattern0.t ->
name:(string, 'e, 'f) Ppxlib__.Ast_pattern0.t ->
expr:('g, 'h, 'i) Ppxlib__.Ast_pattern0.t ->
('j Astlib.Ast_500.Parsetree.class_infos, 'k, 'l) Ppxlib__.Ast_pattern0.t
val pconst_integer :
(string, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
(char option, 'c, 'd) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.constant, 'e, 'f) Ppxlib__.Ast_pattern0.t
val pconst_float :
(string, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
(char option, 'c, 'd) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.constant, 'e, 'f) Ppxlib__.Ast_pattern0.t
val constructor_declaration :
name:(string, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
vars:(string Astlib.Location.loc list, 'c, 'd) Ppxlib__.Ast_pattern0.t ->
args:
(Astlib.Ast_500.Parsetree.constructor_arguments, 'e, 'f)
Ppxlib__.Ast_pattern0.t ->
res:
(Astlib.Ast_500.Parsetree.core_type option, 'g, 'h) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.constructor_declaration, 'i, 'j)
Ppxlib__.Ast_pattern0.t
val pexp_fun :
(Astlib.Ast_500.Asttypes.arg_label, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.expression option, 'c, 'd) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.pattern, 'e, 'f) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.expression, 'g, 'h) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.expression, 'i, 'j) Ppxlib__.Ast_pattern0.t
val pexp_for :
(Astlib.Ast_500.Parsetree.pattern, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.expression, 'c, 'd) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.expression, 'e, 'f) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Asttypes.direction_flag, 'g, 'h) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.expression, 'i, 'j) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.expression, 'k, 'l) Ppxlib__.Ast_pattern0.t
val lident :
(string, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
(Astlib.Longident.t, 'c, 'd) Ppxlib__.Ast_pattern0.t
val position :
fname:(string, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
lnum:(int, 'c, 'd) Ppxlib__.Ast_pattern0.t ->
bol:(int, 'e, 'f) Ppxlib__.Ast_pattern0.t ->
cnum:(int, 'g, 'h) Ppxlib__.Ast_pattern0.t ->
(Lexing.position, 'i, 'j) Ppxlib__.Ast_pattern0.t
val type_declaration :
name:(string, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
params:
((Astlib.Ast_500.Parsetree.core_type
* (Astlib.Ast_500.Asttypes.variance * Astlib.Ast_500.Asttypes.injectivity))
list,
'c,
'd)
Ppxlib__.Ast_pattern0.t ->
cstrs:
((Astlib.Ast_500.Parsetree.core_type
* Astlib.Ast_500.Parsetree.core_type
* Astlib.Location.t)
list,
'e,
'f)
Ppxlib__.Ast_pattern0.t ->
kind:(Astlib.Ast_500.Parsetree.type_kind, 'g, 'h) Ppxlib__.Ast_pattern0.t ->
private_:
(Astlib.Ast_500.Asttypes.private_flag, 'i, 'j) Ppxlib__.Ast_pattern0.t ->
manifest:
(Astlib.Ast_500.Parsetree.core_type option, 'k, 'l) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.type_declaration, 'm, 'n) Ppxlib__.Ast_pattern0.t
val type_extension :
path:(Astlib.Longident.t, 'a, 'b) Ppxlib__.Ast_pattern0.t ->
params:
((Astlib.Ast_500.Parsetree.core_type
* (Astlib.Ast_500.Asttypes.variance * Astlib.Ast_500.Asttypes.injectivity))
list,
'c,
'd)
Ppxlib__.Ast_pattern0.t ->
constructors:
(Astlib.Ast_500.Parsetree.extension_constructor list, 'e, 'f)
Ppxlib__.Ast_pattern0.t ->
private_:
(Astlib.Ast_500.Asttypes.private_flag, 'g, 'h) Ppxlib__.Ast_pattern0.t ->
(Astlib.Ast_500.Parsetree.type_extension, 'i, 'j) Ppxlib__.Ast_pattern0.t
val true_ : (bool, 'a, 'a) t
val false_ : (bool, 'a, 'a) t