File: ppx-for-plugin-authors.rst

package info (click to toggle)
ppxlib 0.15.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, sid
  • size: 1,284 kB
  • sloc: ml: 17,184; sh: 149; makefile: 36; python: 36
file content (154 lines) | stat: -rw-r--r-- 6,375 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
**********************
PPX for plugin authors
**********************

This section describes how to use ``ppxlib`` for PPX plugin authors.

Metaquot
--------

``metaquot`` is a PPX plugin that helps you write PPX plugins. It lets you write AST node values
using the actual corresponding OCaml syntax instead of building them with the more verbose AST types
or ``Ast_builder``.

To use ``metaquot`` you need to add it to the list of preprocessor for your PPX plugin:

.. code:: scheme

          (library
           (name my_plugin_lib)
           (preprocess (pps ppxlib.metaquot)))

``metaquot`` can be used both to write expressions of some of the AST types or to write patterns to
match over those same types. The various extensions it exposes can be used in both contexts,
expressions or patterns.

The extension you should use depends on the type of AST node you're trying to write or to
pattern-match over. You can use the following extensions with the following syntax:

- ``expr`` for ``Parsetree.expression``: ``[%expr 1 + 1]``
- ``pat`` for ``Parsetree.pattern``: ``[%pat? ("", _)]``
- ``type`` for ``Parsetree.core_type``: ``[%type: int -> string]``
- ``stri`` for ``Parsetree.structure_item``: ``[%stri let a = 1]``
- ``sigi`` for ``Parsetree.signature_item``: ``[%sigi: val i : int]``
- ``str`` and ``sig`` respectively for ``Parsetree.structure`` and ``Parsetree.signature``. They use
  similar syntax to the ``_item`` extensions above as they are just a list of such items.

If you consider the first example ``[%expr 1 + 1]``, in an expression context, ``metaquot`` will
actually expand it into:

.. code:: ocaml

          {
            pexp_desc =
              (Pexp_apply
                 ({
                    pexp_desc = (Pexp_ident { txt = (Lident "+"); loc });
                    pexp_loc = loc;
                    pexp_attributes = []
                  },
                   [(Nolabel,
                      {
                        pexp_desc = (Pexp_constant (Pconst_integer ("1", None)));
                        pexp_loc = loc;
                        pexp_attributes = []
                      });
                   (Nolabel,
                     {
                       pexp_desc = (Pexp_constant (Pconst_integer ("1", None)));
                       pexp_loc = loc;
                       pexp_attributes = []
                     })]));
            pexp_loc = loc;
            pexp_attributes = []
          }

For this to compile you need the AST types to be in the scope so you should always use ``metaquot``
where ``Ppxlib`` is opened.
You'll also note that the generated node expects a ``loc : Location.t`` value to be available. The
produced AST node value and every other nodes within it will be located to ``loc``. You should make
sure ``loc`` is the location you want for your generated code when using ``metaquot``.

When using the pattern extension, it will produce a pattern that matches no matter what the
location and attributes are. For the previous example for instance, it will produce the following
pattern:

.. code:: ocaml

          {
            pexp_desc =
              (Pexp_apply
                 ({
                    pexp_desc = (Pexp_ident { txt = (Lident "+"); loc = _ });
                    pexp_loc = _;
                    pexp_attributes = _
                  },
                   [(Nolabel,
                      {
                        pexp_desc = (Pexp_constant (Pconst_integer ("1", None)));
                        pexp_loc = _;
                        pexp_attributes = _
                      });
                   (Nolabel,
                     {
                       pexp_desc = (Pexp_constant (Pconst_integer ("1", None)));
                       pexp_loc = _;
                       pexp_attributes = _
                     })]));
            pexp_loc = _;
            pexp_attributes = _
          }

Using these extensions alone, you can only produce constant/static AST nodes. You can't bind
variables in the generated patterns either.
``metaquot`` has a solution for that as well: anti-quotation.
You can use anti-quotation to insert any expression or pattern representing an AST node.
That way you can include dynamically generated nodes inside a ``metaquot`` expression extension point
or use a wildcard or variable pattern in a pattern extension.

Consider the following example:

.. code:: ocaml

          let with_suffix_expr ~loc s =
            let dynamic_node = Ast_builder.Default.estring ~loc s in
            [%expr [%e dynamic_node] ^ "some_fixed_suffix"]

The ``with_suffix_expr`` function will create an ``expression`` which is the concatenation of the
``s`` argument and the fixed suffix. I.e. ``with_suffix_expr "some_dynamic_stem"`` is equivalent to
``[%expr "some_dynamic_steme" ^ "some_fixed_suffix"]``.

Similarly if you want to ignore some parts of AST nodes and extract some others when
pattern-matching over them, you can use anti-quotation:

.. code:: ocaml

          match some_expr_node with
          | [%expr 1 + [%e? _] + [%e? third]] -> do_something_with third

The syntax for anti-quotation depends on the type of the node you wish to insert:

- ``e`` to anti-quote values of type ``Parsetree.expression``: ``[%expr 1 + [%e some_expr_node]]``
- ``p`` to anti-quote values of type ``Parsetree.pattern``:
  ``[%pat? (1, [%p some_pat_node]]``
- ``t`` to anti-quote values of type ``Parsetree.core_type``:
  ``[%type: int -> [%t some_core_type_node]]``
- ``m`` to anti-quote values of type ``Parsetree.module_expr`` or ``module_type``:
  ``[%expr let module M = [%m some_module_expr_node]]`` or
  ``[%sigi: module M : [%m some_module_type_node]]``
- ``i`` to anti-quote values of type ``Parsetree.structure_item`` or ``signature_item``:
  ``[%str let a = 1 [%%i some_structure_item_node]]`` or
  ``[%sig: val a : int [%%i some_signature_item_node]]``

Note that when anti-quoting in a pattern context you must always use the ``?`` in the anti-quotation
extension as its payload should always be a pattern the same way it must always be an expression
in an expression context.

As you may have noticed, you can anti-quote expressions which type differs from the type of the
whole ``metaquot`` extension point. E.g. you can write:

.. code:: ocaml

          let structure_item =
            [%stri let [%p some_pat] : [%t some_type] = [%e some_expr]]