Skip to content

Commit 5e5a774

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

34 files changed

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

doc/example-ast copy.mld

Lines changed: 407 additions & 0 deletions
Large diffs are not rendered by default.

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 `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 `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 `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 `AST_builder`}
64+
65+
PPXLib provides the `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 `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 `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. {{:../b%20-%20Destructing%20AST/README.md} 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 `Ast_pattern` High-Level Destructors}
10+
{ul {- {{!section-"example-1-matching-integer-payload-with-ast_pattern"} Example 1: Matching Integer Payload with `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 `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+
{[:link: Sample Code](./destructuring_ast.ml#L11-L26)}
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 `Ast_pattern` High-Level Destructors}
86+
87+
To make AST destructuring more readable, PPXLib provides the `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 `Ast_pattern`}
90+
91+
{[:link: Sample Code](./destructuring_ast.ml#L37-L40)}
92+
93+
Let’s destructure the same integer `1` AST using `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+
{[:link: Sample Code](./destructuring_ast.ml#L40-L49)}
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+
{[:link: Sample Code](./destructuring_ast.ml#L51-L60)}
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+
{[:link: Sample Code](./destructuring_ast.ml#L79-L90)}
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 `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. {{:../../2%20-%20Writing%20PPXs/README.md} Read more}

0 commit comments

Comments
 (0)