Skip to content

Commit

Permalink
feat: add ast sample
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrobslisboa committed Dec 15, 2024
1 parent c1e73ea commit 3f6a703
Show file tree
Hide file tree
Showing 34 changed files with 1,307 additions and 160 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ bench:

.PHONY: default install uninstall reinstall clean test doc bench
.PHONY: all-supported-ocaml-versions opam-release

.PHONY: $(TARGET)
example-%:
DUNE_CONFIG__GLOBAL_LOCK=disabled opam exec -- dune exec $*-example
10 changes: 10 additions & 0 deletions doc/dune
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
(documentation
(package ppxlib))

(rule
(alias doc)
(deps
(glob_files ./images/*))
(action
(progn
(system "echo 'hello'")
(system "mkdir -p %{project_root}/_doc/_html/ppxlib/assets/images")
(system "cp -R ./images/ %{project_root}/_doc/_html/ppxlib/assets/images/"))))
407 changes: 407 additions & 0 deletions doc/example-ast copy.mld

Large diffs are not rendered by default.

173 changes: 173 additions & 0 deletions doc/example-ast-building.mld
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
{0 Building AST}

{1 Table of Contents}

- {{!section-description} Description}
- {{!section-"building-asts-with-pure-ocaml"} Building ASTs with Pure OCaml}
{ul {- {{!section-"example-building-a-simple-integer-ast-manually"} Example: Building a Simple Integer AST Manually}}}

- {{!section-"building-asts-with-ast_builder"} Building ASTs with `AST_builder`}
{ul {- {{!section-"example-1-using-pexp_constant-for-integer-ast"} Example 1: Using `pexp_constant` for Integer AST}}}
{ul {- {{!section-"example-2-using-eint-for-simplified-integer-ast"} Example 2: Using `eint` for Simplified Integer AST}}}

- {{!section-"using-metaquot-for-ast-construction"} Using Metaquot for AST Construction}
{ul {- {{!section-"example-building-an-integer-ast-with-metaquot"} Example: Building an Integer AST with Metaquot}}}

- {{!section-"using-anti-quotations-in-metaquot"} Using Anti-Quotations in Metaquot}
{ul {- {{!section-"example-inserting-dynamic-expressions-with-anti-quotations"} Example: Inserting Dynamic Expressions with Anti-Quotations}}}

- {{!section-"building-complex-expressions"} Building Complex Expressions}
{ul {- {{!section-"example-1-constructing-a-let-expression-with-ast_builder"} Example 1: Constructing a Let Expression with `AST_builder`}}}
{ul {- {{!section-"example-2-constructing-a-let-expression-with-metaquot"} Example 2: Constructing a Let Expression with Metaquot}}}

- {{!section-conclusion} Conclusion}

{1:description Description}

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.

For example, if you want to generate the following code:

{[
let zero = [%int 0]
]}

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.

There are several methods to build an AST. We’ll discuss three approaches:

- {b Building ASTs with Pure OCaml}
- {b Building ASTs with `AST_builder`}
- {b Using Metaquot for AST Construction}

{1:building-asts-with-pure-ocaml Building ASTs with Low-Level Builders}

The most fundamental way to build an AST is to manually construct it using Low-Level Builders data structures.

{2:example-building-a-simple-integer-ast-manually Example: Building a Simple Integer AST Manually}

{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L5-L16} 🔗 Sample Code}

{[
let zero ~loc : Ppxlib_ast.Ast.expression =
{
pexp_desc = Pexp_constant (Pconst_integer ("0", None));
pexp_loc = loc;
pexp_loc_stack = [];
pexp_attributes = [];
}
]}

While this method provides full control over the AST, it is verbose and less maintainable.

{1:building-asts-with-ast_builder Building ASTs with `AST_builder`}

PPXLib provides the `AST_builder` module, which simplifies the process of building ASTs by providing helper functions.

{2:example-1-using-pexp_constant-for-integer-ast Example 1: Using `pexp_constant` for Integer AST}

{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L18-L24} 🔗 Sample Code}

{[
let one ~loc =
Ast_builder.Default.pexp_constant ~loc (Parsetree.Pconst_integer ("1", None))
]}

This method is more readable and concise compared to the pure OCaml approach.

{2:example-2-using-eint-for-simplified-integer-ast Example 2: Using `eint` for Simplified Integer AST}

{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L26-L31} 🔗 Sample Code}

{[
let two ~loc = Ast_builder.Default.eint ~loc 2
]}

{b Tip:} `eint` is an abbreviation for expression (`e`) integer (`int`).

{1:using-metaquot-for-ast-construction Using Metaquot for AST Construction}

Metaquot is a syntax extension that allows you to write ASTs in a more natural and readable way.

{2:example-building-an-integer-ast-with-metaquot Example: Building an Integer AST with Metaquot}

{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L33-L38} 🔗 Sample Code}

{[
let three ~loc = [%expr 3]
]}

{b Tip:} Metaquot is highly readable and intuitive but is static. For dynamic values, use Anti-Quotations.

{2:using-anti-quotations-in-metaquot Using Anti-Quotations in Metaquot}

Anti-Quotations allow you to insert dynamic expressions into your Metaquot ASTs.

{3:example-inserting-dynamic-expressions-with-anti-quotations Example: Inserting Dynamic Expressions with Anti-Quotations}

{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L72-L77} 🔗 Sample Code}

{[
let anti_quotation_expr expr = [%expr 1 + [%e expr]]
]}

For example, to insert the AST for `1`:

{[
let _ =
print_endline
("\nLet expression with metaquot and anti-quotation: "
^ Astlib.Pprintast.string_of_expression (anti_quotation_expr (one ~loc)))
]}

{1:building-complex-expressions Building Complex Expressions}

Beyond simple expressions, you may need to build more complex ASTs, such as `let` expressions.

{2:example-1-constructing-a-let-expression-with-ast_builder Example 1: Constructing a Let Expression with `AST_builder`}

{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L40-L60} 🔗 Sample Code}

{[
let let_expression =
let expression =
Ast_builder.Default.pexp_constant ~loc:Location.none
(Pconst_integer ("3", None))
in
let pattern =
Ast_builder.Default.ppat_var ~loc:Location.none
(Ast_builder.Default.Located.mk ~loc:Location.none "foo")
in
let let_binding =
Ast_builder.Default.value_binding ~loc:Location.none ~pat:pattern
~expr:expression
in
Ast_builder.Default.pexp_let ~loc:Location.none Nonrecursive [ let_binding ]
(Ast_builder.Default.eunit ~loc:Location.none)
]}

{2:example-2-constructing-a-let-expression-with-metaquot Example 2: Constructing a Let Expression with Metaquot}

{{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/1-AST/a%20-%20Building%20AST/building_ast.ml#L62-L70} 🔗 Sample Code}

{[
let let_expression =
[%expr
let foo = 3 in
()]
]}

This approach is shorter and easier to understand.

{1:conclusion Conclusion}

In this section, we explored three methods for building ASTs:

- {b Pure OCaml}: The most basic but verbose approach.
- {b Using `AST_builder`}: A more readable and maintainable option.
- {b Using Metaquot}: The most intuitive method, especially when combined with Anti-Quotations for dynamic values.

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.

{2 Next Steps}
On the next section, we will learn how to destructure an AST. {{:../b%20-%20Destructing%20AST/README.md} Read more}
171 changes: 171 additions & 0 deletions doc/example-ast-destructing.mld
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
{0 Destructuring AST}

{1:table-of-contents Table of Contents}

- {{!section-description} Description}
- {{!section-"ast-structure-pattern-matching"} AST Structure Pattern Matching}
{ul {- {{!section-"example-matching-integer-payload-manually"} Example: Matching Integer Payload Manually}}}

- {{!section-"using-ast_pattern-high-level-destructors"} Using `Ast_pattern` High-Level Destructors}
{ul {- {{!section-"example-1-matching-integer-payload-with-ast_pattern"} Example 1: Matching Integer Payload with `Ast_pattern`}}}
{ul {- {{!section-"example-2-simplifying-matching-with-eint"} Example 2: Simplifying Matching with `eint`}}}

- {{!section-"using-metaquot"} Using Metaquot}
{ul {- {{!section-"example-1-matching-integer-payload-with-metaquot"} Example 1: Matching Integer Payload with Metaquot}}}
{ul {- {{!section-"example-2-matching-complex-expressions-with-metaquot-and-anti-quotations"} Example 2: Matching Complex Expressions with Metaquot and Anti-Quotations}}}

- {{!section-"conclusion"} Conclusion}

{1:description Description}

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.

For example, if you want to transform this code:

{[
let one = [%one]
]}

into:

{[
let one = 1
]}

You’ll need to destructure the AST representing the extension point (`[%one]`) to replace it with `1`.
There are several ways to destructure an AST. We’ll explore three methods:

- {b AST Structure Pattern Matching}
- {b Using `Ast_pattern` High-Level Destructors}
- {b Using Metaquot}

{1:ast-structure-pattern-matching AST Structure Pattern Matching}

The most fundamental method for destructuring an AST in PPXLib is by directly matching on the AST’s structure.

{2:example-matching-integer-payload-manually Example: Matching Integer Payload Manually}

{[:link: Sample Code](./destructuring_ast.ml#L11-L26)}

Let’s say we want to destructure an AST representing the integer `1`:

{[
let match_int_payload ~loc payload =
match payload with
| PStr
[
{
pstr_desc =
Pstr_eval
({ pexp_desc = Pexp_constant (Pconst_integer (value, None)); _ }, _);
_;
};
] -> (
try Ok (value |> int_of_string)
with Failure _ ->
Error (Location.Error.createf ~loc "Value is not a valid integer"))
| _ -> Error (Location.Error.createf ~loc "Wrong pattern")
]}

1. {b Pattern Matching the Payload}:
- Begins by matching the `payload` with the expected structure.
- The pattern expects a structure (`PStr`) containing a single item.
2. {b Destructuring the Structure Item}:
- Matches the `pstr_desc` field, expecting an evaluated expression (`Pstr_eval`).
- The expression should be a constant integer (`Pexp_constant` with `Pconst_integer`).
- Captures the integer value as a string in `value`.
3. {b Handling the Matched Value}:
- Converts the `value` to an integer and returns `Ok` if successful.
- If conversion fails, returns an error message.
4. {b Handling Mismatched Patterns}:
- If the `payload` doesn’t match the expected structure, it returns an error.

While this method is powerful, it can be verbose and difficult to maintain as patterns become more complex.

{1:using-ast_pattern-high-level-destructors Using `Ast_pattern` High-Level Destructors}

To make AST destructuring more readable, PPXLib provides the `Ast_pattern` module, which offers high-level destructors.

{2:example-1-matching-integer-payload-with-ast_pattern Example 1: Matching Integer Payload with `Ast_pattern`}

{[:link: Sample Code](./destructuring_ast.ml#L37-L40)}

Let’s destructure the same integer `1` AST using `Ast_pattern`:

{[
open Ppxlib

let match_int_payload =
let open Ast_pattern in
pstr (pstr_eval (pexp_constant (pconst_integer (string "1") none)) nil ^:: nil)
]}

This code achieves the same result as the previous example but in a more concise and readable way.

- {b `PStr`} becomes `pstr`
- {b `Pstr_eval`} becomes `pstr_eval`
- {b `Pexp_constant`} becomes `pexp_constant`
- {b `Pconst_integer`} becomes `pconst_integer`

{2:example-2-simplifying-matching-with-eint Example 2: Simplifying Matching with `eint`}

{[:link: Sample Code](./destructuring_ast.ml#L40-L49)}

You can further simplify it:

{[
let match_int_payload =
let open Ast_pattern in
pstr (pstr_eval (eint (int 1)) nil ^:: nil)
]}

Using `eint` instead of `pexp_constant` and `pconst_integer` provides better type safety. The `int` wildcard captures the integer value.

{1:using-metaquot Using Metaquot}

Metaquot is a syntax extension that allows you to write and destructure ASTs more intuitively.

{2:example-1-matching-integer-payload-with-metaquot Example 1: Matching Integer Payload with Metaquot}

{[:link: Sample Code](./destructuring_ast.ml#L51-L60)}

Let’s destructure the same integer `1` AST with Metaquot:

{[
let match_int_payload expr =
match expr with
| [%expr 1] -> Ok 1
| _ -> Error (Location.Error.createf ~loc:expr.pexp_loc "Wrong pattern")
]}

{2:example-2-matching-complex-expressions-with-metaquot-and-anti-quotations Example 2: Matching Complex Expressions with Metaquot and Anti-Quotations}

{[:link: Sample Code](./destructuring_ast.ml#L79-L90)}

For example, to match any expression of the form `1 + <int>`:

{[
let match_int_payload expr =
match expr with
| [%expr 1 + [%e? e]] -> (
match e with
| { pexp_desc = Pexp_constant (Pconst_integer (value, None)); _ } ->
Ok (1 + int_of_string value)
| _ -> Error (Location.Error.createf ~loc:e.pexp_loc "Invalid integer"))
| _ -> Error (Location.Error.createf ~loc:expr.pexp_loc "Wrong pattern")
]}

Metaquot simplifies the process, making the AST patterns more readable, especially for complex structures.

{1:conclusion Conclusion}

In this section, we explored different methods to destructure an AST using PPXLib:

- {b AST Structure Pattern Matching}: Powerful but verbose.
- {b Using `Ast_pattern` High-Level Destructors}: More readable and maintainable.
- {b Using Metaquot}: Intuitive and effective for both simple and complex patterns.

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.

{1:next-steps Next Steps}
On the next section, we will learn how to write a PPX. {{:../../2%20-%20Writing%20PPXs/README.md} Read more}
Loading

0 comments on commit 3f6a703

Please sign in to comment.