%}{{!"generating-code"}< Generating AST nodes}{%html:
%}{{!"ast-traversal"}Traversing the AST >}{%html:
%}
{0 Destructing AST Nodes}
In the previous chapter, we have seen how to generate code. However, the
transformation function should depend on its input (the payload and maybe the
derived item), which we have to be able to inspect.
Once again, directly inspecting the {{!Ppxlib.Parsetree}[Parsetree]} value that
we get as input is not a good option because it is very big to manipulate and can
break at every new OCaml release. For instance, let's consider the case of
{{:https://github.com/janestreet/ppx_inline_test}[ppx_inline_test]}. We want to
recognize and extract the name and expression only from the form patterns:
{[
[%%test let "name" = expr]
]}
If we wrote a function accepting the payload of [[%%test]], and extracting the
name and expression from it, using normal pattern matching we would have:
{[
# let match_payload ~loc payload =
match payload with
| PStr
[
{
pstr_desc =
Pstr_value
( Nonrecursive,
[
{
pvb_pat =
{
ppat_desc =
Ppat_constant (Pconst_string (name, _, None));
_;
};
pvb_expr = expr;
_;
};
] );
_;
};
] ->
Ok (name, expr)
| _ -> Error (Location.Error.createf ~loc "Wrong pattern") ;;
val match_payload :
loc:location -> payload -> (string * expression, Location.Error.t) result =
]}
[ppxlib]'s solution to the verbosity and stability problem is to provide helpers
to {e match} the AST, in a very similar way to what it does for generating AST
nodes.
{1 The Different Options}
In this chapter, we will often mention the similarities between matching code
and generating code (from the {{!"generating-code"}previous chapter}). Indeed, the
options provided by [ppxlib] to match AST nodes mirror the ones for generating
nodes:
- {{!Ppxlib.Ast_pattern}[Ast_pattern]}, the {{!Ppxlib.Ast_builder}[Ast_builder]} sibling,
- {{!Ppxlib_metaquot}[Metaquot]} again.
{{!Ppxlib.Ast_pattern}[Ast_pattern]} is used in {{!Ppxlib.Extension.V3.declare}[Extension.V3.declare]}, so you will need it to write
extenders. {!Ppxlib_metaquot} is, as for generating nodes, more natural to use but also
restricted to some cases.
{1:ast_pattern_intro The [Ast_pattern] Module}
A match is a "structural destruction" of a value into multiple subvalues to
continue the computation. For instance, in the example above from the single
variable [payload], we structurally extract two variables: [name] and [expr].
Destruction is very similar to construction, but in reverse. Instead of using
several values to build a bigger one, we use one big value to define smaller
ones. As an illustration, note how in OCaml the following construction and
destruction are close:
{[
let big = { x ; y } (** Construction from [x] and [y] *)
let { x ; y } = big (** Destruction recovering [x] and [y] *)
]}
For the same reason, building AST nodes using {{!Ppxlib.Ast_builder}[Ast_builder]} and destructing AST
nodes using {{!Ppxlib.Ast_pattern}[Ast_pattern]} look very similar. The difference is that in the construction "leaf," {{!Ppxlib.Ast_builder}[Ast_builder]} uses actual values, while {{!Ppxlib.Ast_pattern}[Ast_pattern]} has
"wildcards" at the leafs.
Consider the example in the introduction matching [[%%test let "name" = expr]].
Building such an expression with {{!Ppxlib.Ast_builder}[Ast_builder]} could look like:
{[
# let build_payload_test ~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 :: [])
:: []) ;;
val build_payload_test :
loc:location -> string -> expression -> payload =