%}{{!"driver"}< The Driver}{%html:
%}{{!"generating-code"}Generating AST nodes >}{%html:
%}
{0 Writing a Transformation}
This chapter covers the [ppxlib] procedure basics to define and register a
transformation, be it a global or a context-free transformation.
For the actual manipulation and generation of code, [ppxlib] provides many helpers
that are listed in {!generatingcode}.
{1 Defining a Transformation}
For [ppxlib], a transformation is a description of a way to modify a given AST
into another one. A transformation can be:
- A context-free transformation, which only acts on a portion of the AST. In the [ppxlib] framework, those transformations
are represented by values of type {{!Ppxlib.Context_free.Rule.t}[Context_free.Rule.t]} and are executed in the {{!driver."context-free-phase"}context-free phase}. This is the strongly recommended kind of transformation due to its {{!driver.advantages}important advantages}, such as good performance, well-defined composition semantics, and the safety and trustability that comes with well-isolated and strictly local modifications.
- A global transformation, which takes the simple form of a function of type
[structure -> structure] or [signature -> signature], that can sometimes take
extra information as additional arguments. Such a transformation is applied in
the {{!driver."global-transfo-phase"}global transformation phase}, unless it
has a good reason to have been registered in another phase. While global transformations are a flexible and powerful tool in the OCaml ecosystem, they come with many {{!global_transformation}drawbacks} and should only be used when really necessary.
In order to register a transformation to the [ppxlib] driver, one should use the
{{!Ppxlib.Driver.V2.register_transformation}[Driver.V2.register_transformation]}. This function is used to register all
rewriter types in every different phase, except derivers, which are abstracted
away in {{!Ppxlib.Deriving}[Deriving]}.
{1 Context-Free Transformation}
In [ppxlib], the type for context-free transformation is
{{!Ppxlib.Context_free.Rule.t}[Context_free.Rule.t]}. Rules will be applied during the AST's top-down traverse
of the context-free pass. A rule contains the information about
when it should be applied in the traversal, as well as the transformation to
apply.
Currently, rules can only be defined to apply in five different contexts:
- on extensions points, such as [\[%ext_point payload\]]
- on some structure or signature items with a deriving attribute, such as
[type t = Nil \[@@deriving show\]],
- on AST nodes with attributes, such as [let x = 42 [@@attr]],
- on
{{:https://v2.ocaml.org/manual/extensionsyntax.html#ss:extension-literals}
litterals with modifiers}, such as [41g] or [43.2x],
- on function application or identifiers, such as [meta_function "99"] and [meta_constant].
In order to define rules on extensions points, we will use the {{!Ppxlib.Extension}[Extension]}
module. In order to define deriving rules, we will use the {{!Ppxlib.Deriving}[Deriving]}
module. For the three other rules, we will directly use the
{{!Ppxlib.Context_free.Rule}[Context_free.Rule]} module.
{2 Extenders}
An {{!driver.def_extenders}extender} is characterised by several things:
{ul
{li The situation that triggers the rewriting, which consists of two things:
{ul
{li The extension points' name on which it is triggered. For instance,
an extender triggered on [[%name]] would not be triggered on [[%other_name]]}
{li The AST context on which it applies. Indeed, extension points can be used in
many different places: expression, pattern, core type, etc., and the extender
should be restricted to one context, as it produces code of a single type. So,
an extender triggered on expressions could be triggered on [let x = [%name]]
but not on [let [%name] = expr].}}}
{li The actual rewriting of the extension node:
{ul
{li A function, called "expander", taking arguments and outputting the generated AST}
{li How to extract from the payload the arguments to pass to the expander}}
}}
{3:ext_context The Extender Context}
The context is a value of type {{!Ppxlib.Extension.Context.t}[Extension.Context.t]}. For instance, to
define an extender for expression-extension points, the correct context is
{{!Ppxlib.Extension.Context.expression}[Extension.Context.expression]}. Consult the
{{!Ppxlib.Extension.Context}[Extension.Context]} module's API for the list of all contexts!
{@ocaml[
# let context = Extension.Context.expression;;
val context : expression Extension.Context.t =
Ppxlib.Extension.Context.Expression
]}
{3 The Extender Name}
The extension point name on which it applies is simply a string.
{@ocaml[
# let extender_name = "add_suffix" ;;
val extender_name : string = "add_suffix"
]}
See below for examples on when the above name and context will trigger rewriting:
{@ocaml[
(* will trigger rewriting: *)
let _ = [%add_suffix "payload"]
(* won't trigger rewriting: *)
let _ = [%other_name "payload"] (* wrong name *)
let _ = match () with [%add_suffix "payload"] -> () (* wrong context *)
]}
{3 The Payload Extraction}
An extension node contains a {{!Ppxlib.Parsetree.payload}[payload]}, which will be passed to the transformation function. However, while this payload contains all information, it is not always structured the best way for the transformation function. For instance, in [[%add_suffix "payload"]], the string ["payload"] is encoded as a structure item consisting of an expression’s evaluation, a constant that is a string.
[ppxlib] allows separating the transformation function from the extraction of the payload’s relevant information. As explained in depth in the {{!"matching-code"}Destructing AST nodes} chapter, this extraction is done by destructing the payload’s structure (which is therefore restricted: [[%add_suffix 12]] would be refused by the rewriter of the example below). The extraction is defined by a value of type
{{!Ppxlib.Ast_pattern.t}[Ast_pattern.t]}. The {{!Ppxlib.Ast_pattern}[Ast_pattern]} module provides some kind of pattern-matching on AST nodes: a way to structurally extract values from an AST
node in order to generate a value of another kind.
For instance, a value of type
[(payload, int -> float -> expression, expression) Ast_pattern.t] means that it
defines a way to extract an [int] and a [float] from a {{!Ppxlib.Parsetree.payload}[payload]},
which should be then combined to define a value of type {{!Ppxlib.Parsetree.expression}[expression]}.
In our case, the matched value will always be a {{!Ppxlib.Parsetree.payload}[payload]}, as that's the type for extension points' payloads. The type of the
produced node will have to match the {{!ext_context}type of extension node we rewrite}, {{!Ppxlib.Parsetree.expression}[expression]} in our example.
{@ocaml[
# let extracter () = Ast_pattern.(single_expr_payload (estring __)) ;;
val extracter : unit -> (payload, string -> 'a, 'a) Ast_pattern.t =