Skip to content

Commit f633580

Browse files
committed
Add ast sample
Signed-off-by: Pedro B S Lisboa <[email protected]>
1 parent c1e73ea commit f633580

33 files changed

+899
-160
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ bench:
3636

3737
.PHONY: default install uninstall reinstall clean test doc bench
3838
.PHONY: all-supported-ocaml-versions opam-release
39+
40+
.PHONY: $(TARGET)
41+
example-%:
42+
DUNE_CONFIG__GLOBAL_LOCK=disabled opam exec -- dune exec $*-example

doc/dune

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
(documentation
22
(package ppxlib))
3+
4+
(rule
5+
(alias doc)
6+
(deps
7+
(glob_files ./images/*))
8+
(action
9+
(progn
10+
(system "mkdir -p %{project_root}/_doc/_html/ppxlib/assets/images")
11+
(system "cp -R ./images/ %{project_root}/_doc/_html/ppxlib/assets/images/"))))

doc/example-ast-building.mld

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
{0 Building AST}
2+
3+
{1 Table of Contents}
4+
5+
- {{!section-description} Description}
6+
- {{!section-"building-asts-with-pure-ocaml"} Building ASTs with Pure OCaml}
7+
{ul {- {{!section-"example-building-a-simple-integer-ast-manually"} Example: Building a Simple Integer AST Manually}}}
8+
9+
- {{!section-"building-asts-with-ast_builder"} Building ASTs with {!Ppxlib.Ast_builder}}
10+
{ul {- {{!section-"example-1-using-pexp_constant-for-integer-ast"} Example 1: Using [pexp_constant] for Integer AST}}}
11+
{ul {- {{!section-"example-2-using-eint-for-simplified-integer-ast"} Example 2: Using [eint] for Simplified Integer AST}}}
12+
13+
- {{!section-"using-metaquot-for-ast-construction"} Using Metaquot for AST Construction}
14+
{ul {- {{!section-"example-building-an-integer-ast-with-metaquot"} Example: Building an Integer AST with Metaquot}}}
15+
16+
- {{!section-"using-anti-quotations-in-metaquot"} Using Anti-Quotations in Metaquot}
17+
{ul {- {{!section-"example-inserting-dynamic-expressions-with-anti-quotations"} Example: Inserting Dynamic Expressions with Anti-Quotations}}}
18+
19+
- {{!section-"building-complex-expressions"} Building Complex Expressions}
20+
{ul {- {{!section-"example-1-constructing-a-let-expression-with-ast_builder"} Example 1: Constructing a Let Expression with [Ppxlib.Ast_builder]}}}
21+
{ul {- {{!section-"example-2-constructing-a-let-expression-with-metaquot"} Example 2: Constructing a Let Expression with Metaquot}}}
22+
23+
- {{!section-conclusion} Conclusion}
24+
25+
{1:description Description}
26+
27+
Building an AST (Abstract Syntax Tree) is a fundamental part of creating a PPX in OCaml. You'll need to construct an AST to represent the code you want to generate or transform.
28+
29+
For example, if you want to generate the following code:
30+
31+
{[
32+
let zero = [%int 0]
33+
]}
34+
35+
and replace the extension point [%int 0] with [0] to produce [let zero = 0], you’ll need to build an AST that represents this transformation.
36+
37+
There are several methods to build an AST. We’ll discuss three approaches:
38+
39+
- {b Building ASTs with Pure OCaml}
40+
- {b Building ASTs with {!Ppxlib.Ast_builder}}
41+
- {b Using Metaquot for AST Construction}
42+
43+
{1:building-asts-with-pure-ocaml Building ASTs with Low-Level Builders}
44+
45+
The most fundamental way to build an AST is to manually construct it using Low-Level Builders data structures.
46+
47+
{2:example-building-a-simple-integer-ast-manually Example: Building a Simple Integer AST Manually}
48+
49+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L5-L16} 🔗 Sample Code}
50+
51+
{[
52+
let zero ~loc : Ppxlib_ast.Ast.expression =
53+
{
54+
pexp_desc = Pexp_constant (Pconst_integer ("0", None));
55+
pexp_loc = loc;
56+
pexp_loc_stack = [];
57+
pexp_attributes = [];
58+
}
59+
]}
60+
61+
While this method provides full control over the AST, it is verbose and less maintainable.
62+
63+
{1:building-asts-with-ast_builder Building ASTs with {!Ppxlib.Ast_builder}}
64+
65+
PPXLib provides the {!Ppxlib.Ast_builder} module, which simplifies the process of building ASTs by providing helper functions.
66+
67+
{2:example-1-using-pexp_constant-for-integer-ast Example 1: Using [pexp_constant] for Integer AST}
68+
69+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L18-L24} 🔗 Sample Code}
70+
71+
{[
72+
let one ~loc =
73+
Ast_builder.Default.pexp_constant ~loc (Parsetree.Pconst_integer ("1", None))
74+
]}
75+
76+
This method is more readable and concise compared to the pure OCaml approach.
77+
78+
{2:example-2-using-eint-for-simplified-integer-ast Example 2: Using [eint] for Simplified Integer AST}
79+
80+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L26-L31} 🔗 Sample Code}
81+
82+
{[
83+
let two ~loc = Ast_builder.Default.eint ~loc 2
84+
]}
85+
86+
{b Tip:} [eint] is an abbreviation for expression ([e]) integer ([int]).
87+
88+
{1:using-metaquot-for-ast-construction Using Metaquot for AST Construction}
89+
90+
Metaquot is a syntax extension that allows you to write ASTs in a more natural and readable way.
91+
92+
{2:example-building-an-integer-ast-with-metaquot Example: Building an Integer AST with Metaquot}
93+
94+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L33-L38} 🔗 Sample Code}
95+
96+
{[
97+
let three ~loc = [%expr 3]
98+
]}
99+
100+
{b Tip:} Metaquot is highly readable and intuitive but is static. For dynamic values, use Anti-Quotations.
101+
102+
{2:using-anti-quotations-in-metaquot Using Anti-Quotations in Metaquot}
103+
104+
Anti-Quotations allow you to insert dynamic expressions into your Metaquot ASTs.
105+
106+
{3:example-inserting-dynamic-expressions-with-anti-quotations Example: Inserting Dynamic Expressions with Anti-Quotations}
107+
108+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L72-L77} 🔗 Sample Code}
109+
110+
{[
111+
let anti_quotation_expr expr = [%expr 1 + [%e expr]]
112+
]}
113+
114+
For example, to insert the AST for [1]:
115+
116+
{[
117+
let _ =
118+
print_endline
119+
("\nLet expression with metaquot and anti-quotation: "
120+
^ Astlib.Pprintast.string_of_expression (anti_quotation_expr (one ~loc)))
121+
]}
122+
123+
{1:building-complex-expressions Building Complex Expressions}
124+
125+
Beyond simple expressions, you may need to build more complex ASTs, such as [let] expressions.
126+
127+
{2:example-1-constructing-a-let-expression-with-ast_builder Example 1: Constructing a Let Expression with {!Ppxlib.Ast_builder}}
128+
129+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L40-L60} 🔗 Sample Code}
130+
131+
{[
132+
let let_expression =
133+
let expression =
134+
Ast_builder.Default.pexp_constant ~loc:Location.none
135+
(Pconst_integer ("3", None))
136+
in
137+
let pattern =
138+
Ast_builder.Default.ppat_var ~loc:Location.none
139+
(Ast_builder.Default.Located.mk ~loc:Location.none "foo")
140+
in
141+
let let_binding =
142+
Ast_builder.Default.value_binding ~loc:Location.none ~pat:pattern
143+
~expr:expression
144+
in
145+
Ast_builder.Default.pexp_let ~loc:Location.none Nonrecursive [ let_binding ]
146+
(Ast_builder.Default.eunit ~loc:Location.none)
147+
]}
148+
149+
{2:example-2-constructing-a-let-expression-with-metaquot Example 2: Constructing a Let Expression with Metaquot}
150+
151+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L62-L70} 🔗 Sample Code}
152+
153+
{[
154+
let let_expression =
155+
[%expr
156+
let foo = 3 in
157+
()]
158+
]}
159+
160+
This approach is shorter and easier to understand.
161+
162+
{1:conclusion Conclusion}
163+
164+
In this section, we explored three methods for building ASTs:
165+
166+
- {b Pure OCaml}: The most basic but verbose approach.
167+
- {b Using {!Ppxlib.Ast_builder}}: A more readable and maintainable option.
168+
- {b Using Metaquot}: The most intuitive method, especially when combined with Anti-Quotations for dynamic values.
169+
170+
Each method has its strengths, so choose the one that best fits your needs. Understanding all three will give you greater flexibility in creating effective and maintainable PPXs.
171+
172+
{2 Next Steps}
173+
On the next section, we will learn how to destructure an AST. {{!page-"example-ast-destructing"} Read more}

doc/example-ast-destructing.mld

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
{0 Destructuring AST}
2+
3+
{1:table-of-contents Table of Contents}
4+
5+
- {{!section-description} Description}
6+
- {{!section-"ast-structure-pattern-matching"} AST Structure Pattern Matching}
7+
{ul {- {{!section-"example-matching-integer-payload-manually"} Example: Matching Integer Payload Manually}}}
8+
9+
- {{!section-"using-ast_pattern-high-level-destructors"} Using {!Ppxlib.Ast_pattern} High-Level Destructors}
10+
{ul {- {{!section-"example-1-matching-integer-payload-with-ast_pattern"} Example 1: Matching Integer Payload with [Ppxlib.Ast_pattern]}}}
11+
{ul {- {{!section-"example-2-simplifying-matching-with-eint"} Example 2: Simplifying Matching with [eint]}}}
12+
13+
- {{!section-"using-metaquot"} Using Metaquot}
14+
{ul {- {{!section-"example-1-matching-integer-payload-with-metaquot"} Example 1: Matching Integer Payload with Metaquot}}}
15+
{ul {- {{!section-"example-2-matching-complex-expressions-with-metaquot-and-anti-quotations"} Example 2: Matching Complex Expressions with Metaquot and Anti-Quotations}}}
16+
17+
- {{!section-"conclusion"} Conclusion}
18+
19+
{1:description Description}
20+
21+
Destructuring an AST (Abstract Syntax Tree) is essential when creating a PPX (preprocessor extension) in OCaml. To generate or transform code, you must first break down the AST to understand and manipulate its structure.
22+
23+
For example, if you want to transform this code:
24+
25+
{[
26+
let one = [%one]
27+
]}
28+
29+
into:
30+
31+
{[
32+
let one = 1
33+
]}
34+
35+
You’ll need to destructure the AST representing the extension point ([%one]) to replace it with [1].
36+
There are several ways to destructure an AST. We’ll explore three methods:
37+
38+
- {b AST Structure Pattern Matching}
39+
- {b Using {!Ppxlib.Ast_pattern} High-Level Destructors}
40+
- {b Using Metaquot}
41+
42+
{1:ast-structure-pattern-matching AST Structure Pattern Matching}
43+
44+
The most fundamental method for destructuring an AST in PPXLib is by directly matching on the AST’s structure.
45+
46+
{2:example-matching-integer-payload-manually Example: Matching Integer Payload Manually}
47+
48+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/destructuring_ast.ml#L11-L26} 🔗 Sample Code}
49+
50+
Let’s say we want to destructure an AST representing the integer [1]:
51+
52+
{[
53+
let match_int_payload ~loc payload =
54+
match payload with
55+
| PStr
56+
[
57+
{
58+
pstr_desc =
59+
Pstr_eval
60+
({ pexp_desc = Pexp_constant (Pconst_integer (value, None)); _ }, _);
61+
_;
62+
};
63+
] -> (
64+
try Ok (value |> int_of_string)
65+
with Failure _ ->
66+
Error (Location.Error.createf ~loc "Value is not a valid integer"))
67+
| _ -> Error (Location.Error.createf ~loc "Wrong pattern")
68+
]}
69+
70+
1. {b Pattern Matching the Payload}:
71+
- Begins by matching the [payload] with the expected structure.
72+
- The pattern expects a structure ([PStr]) containing a single item.
73+
2. {b Destructuring the Structure Item}:
74+
- Matches the [pstr_desc] field, expecting an evaluated expression ([Pstr_eval]).
75+
- The expression should be a constant integer ([Pexp_constant] with [Pconst_integer]).
76+
- Captures the integer value as a string in [value].
77+
3. {b Handling the Matched Value}:
78+
- Converts the [value] to an integer and returns [Ok] if successful.
79+
- If conversion fails, returns an error message.
80+
4. {b Handling Mismatched Patterns}:
81+
- If the [payload] doesn’t match the expected structure, it returns an error.
82+
83+
While this method is powerful, it can be verbose and difficult to maintain as patterns become more complex.
84+
85+
{1:using-ast_pattern-high-level-destructors Using {!Ppxlib.Ast_pattern} High-Level Destructors}
86+
87+
To make AST destructuring more readable, PPXLib provides the {!Ppxlib.Ast_pattern} module, which offers high-level destructors.
88+
89+
{2:example-1-matching-integer-payload-with-ast_pattern Example 1: Matching Integer Payload with {!Ppxlib.Ast_pattern}}
90+
91+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/b%20-%20Destructing%20AST/destructuring_ast.ml#L37-L40} 🔗 Sample Code}
92+
93+
Let’s destructure the same integer [1] AST using {!Ppxlib.Ast_pattern}:
94+
95+
{[
96+
open Ppxlib
97+
98+
let match_int_payload =
99+
let open Ast_pattern in
100+
pstr (pstr_eval (pexp_constant (pconst_integer (string "1") none)) nil ^:: nil)
101+
]}
102+
103+
This code achieves the same result as the previous example but in a more concise and readable way.
104+
105+
- {b [PStr]} becomes [pstr]
106+
- {b [Pstr_eval]} becomes [pstr_eval]
107+
- {b [Pexp_constant]} becomes [pexp_constant]
108+
- {b [Pconst_integer]} becomes [pconst_integer]
109+
110+
{2:example-2-simplifying-matching-with-eint Example 2: Simplifying Matching with [eint]}
111+
112+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/b%20-%20Destructing%20AST/destructuring_ast.ml#L40-L49} 🔗 Sample Code}
113+
114+
You can further simplify it:
115+
116+
{[
117+
let match_int_payload =
118+
let open Ast_pattern in
119+
pstr (pstr_eval (eint (int 1)) nil ^:: nil)
120+
]}
121+
122+
Using [eint] instead of [pexp_constant] and [pconst_integer] provides better type safety. The [int] wildcard captures the integer value.
123+
124+
{1:using-metaquot Using Metaquot}
125+
126+
Metaquot is a syntax extension that allows you to write and destructure ASTs more intuitively.
127+
128+
{2:example-1-matching-integer-payload-with-metaquot Example 1: Matching Integer Payload with Metaquot}
129+
130+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/b%20-%20Destructing%20AST/destructuring_ast.ml#L51-L60} 🔗 Sample Code}
131+
132+
Let’s destructure the same integer [1] AST with Metaquot:
133+
134+
{[
135+
let match_int_payload expr =
136+
match expr with
137+
| [%expr 1] -> Ok 1
138+
| _ -> Error (Location.Error.createf ~loc:expr.pexp_loc "Wrong pattern")
139+
]}
140+
141+
{2:example-2-matching-complex-expressions-with-metaquot-and-anti-quotations Example 2: Matching Complex Expressions with Metaquot and Anti-Quotations}
142+
143+
{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/b%20-%20Destructing%20AST/destructuring_ast.ml#L79-L90} 🔗 Sample Code}
144+
145+
For example, to match any expression of the form [1 + <int>]:
146+
147+
{[
148+
let match_int_payload expr =
149+
match expr with
150+
| [%expr 1 + [%e? e]] -> (
151+
match e with
152+
| { pexp_desc = Pexp_constant (Pconst_integer (value, None)); _ } ->
153+
Ok (1 + int_of_string value)
154+
| _ -> Error (Location.Error.createf ~loc:e.pexp_loc "Invalid integer"))
155+
| _ -> Error (Location.Error.createf ~loc:expr.pexp_loc "Wrong pattern")
156+
]}
157+
158+
Metaquot simplifies the process, making the AST patterns more readable, especially for complex structures.
159+
160+
{1:conclusion Conclusion}
161+
162+
In this section, we explored different methods to destructure an AST using PPXLib:
163+
164+
- {b AST Structure Pattern Matching}: Powerful but verbose.
165+
- {b Using {!Ppxlib.Ast_pattern} High-Level Destructors}: More readable and maintainable.
166+
- {b Using Metaquot}: Intuitive and effective for both simple and complex patterns.
167+
168+
There’s no right way to destructure an AST, choose the approach that best fits your use case. Understanding all these methods is valuable for creating robust and maintainable PPXs.
169+
170+
{1:next-steps Next Steps}
171+
On the next section, we will learn how to write a PPX. {{!page-"example-writing-ppxs"} Read more}

0 commit comments

Comments
 (0)