Skip to content

Commit

Permalink
feat: add test chapter
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrobslisboa committed Dec 3, 2024
1 parent 8698080 commit 5069f73
Show file tree
Hide file tree
Showing 19 changed files with 303 additions and 46 deletions.
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ create-switch: ## Create opam switch
install:
$(DUNE) build @install
opam install . --deps-only --with-test
cd demo && yarn install

.PHONY: init
init: create-switch install
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ On my ppx learning journey, I'm having some difficulties to find examples and ex

So as I said, I'm learning, and I'm not an expert on the subject. If you find any mistake or have any suggestion, please open an issue or a pull request. I'll be glad to receive your feedback.

## Requirements

This repository assumes that you have the following tools installed on your machine:

- OPAM: To manage OCaml dependencies and versions.
- Make: To use the Makefile commands for building, cleaning, and running the project.

## Content

- [AST](./examples/1%20-%20AST/README.md)
Expand Down
1 change: 1 addition & 0 deletions dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
(depends
(ocaml
(>= 5.0.0))
alcotest
ocaml-lsp-server
ocamlformat
ppx_deriving
Expand Down
6 changes: 3 additions & 3 deletions examples/1 - AST/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The OCaml Platform officially supports a library for creating these preprocessor
- **Source Level**: Preprocessors work directly on the source code.
- **AST Level**: Preprocessors manipulate the AST, offering more powerful and flexible transformations. (Covered in this guide)

> **⚠️ Warning**
> [!WARNING]
> One of the key challenges with working with the Parsetree (the AST in OCaml) is that its API is not stable. For instance, in the OCaml 4.13 release, significant changes were made to the Parsetree type, which can impact the compatibility of your preprocessing tools. Read more about it in [The Future of PPX](https://discuss.ocaml.org/t/the-future-of-ppx/3766)
### AST Guide
Expand Down Expand Up @@ -187,7 +187,7 @@ A **structure** refers to the content within a module. It is composed of various

The structure represents the body of the module, where all these items are defined and implemented. Since each `.ml` file is implicitly a module, the entire content of a file can be viewed as the structure of that module.

> **:bulb: Tip**
> [!TIP]
> Every module in OCaml creates a new structure, and nested modules create nested structures.
Consider the following example:
Expand Down Expand Up @@ -263,7 +263,7 @@ end

As you can see, `Bar.ml` and `GameEnum` are modules, and their content is a **structure** that contain a list of **structure items**.

> **📝 Note**
> [!NOTE]
> A structure item can either represent a top-level expression, a type definition, a `let` definition, etc.
I'm not going to be able to cover all structure items, but you can find more about it in the [OCaml documentation](https://ocaml.org/learn/tutorials/modules.html). I strongly advise you to take a look at the [AST Explorer](https://astexplorer.net/) and play with it; it will help you a lot. Here is a [sample](https://astexplorer.net/#/gist/79e2c7cf04e26236bce5627e6d59a020/caa55456cfa6c30c37cc3a701979cf837c213b71).
Expand Down
35 changes: 19 additions & 16 deletions examples/1 - AST/a - Building AST/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@ make demo-building_ast

### Table of Contents

- [Description](#description)
- [Building ASTs with Pure OCaml](#building-asts-with-pure-ocaml)
- [Example: Building a Simple Integer AST Manually](#example-building-a-simple-integer-ast-manually)
- [Building ASTs with `AST_builder`](#building-asts-with-ast_builder)
- [Example 1: Using `pexp_constant` for Integer AST](#example-1-using-pexp_constant-for-integer-ast)
- [Example 2: Using `eint` for Simplified Integer AST](#example-2-using-eint-for-simplified-integer-ast)
- [Using Metaquot for AST Construction](#using-metaquot-for-ast-construction)
- [Example: Building an Integer AST with Metaquot](#example-building-an-integer-ast-with-metaquot)
- [Using Anti-Quotations in Metaquot](#using-anti-quotations-in-metaquot)
- [Example: Inserting Dynamic Expressions with Anti-Quotations](#example-inserting-dynamic-expressions-with-anti-quotations)
- [Building Complex Expressions](#building-complex-expressions)
- [Example 1: Constructing a Let Expression with `AST_builder`](#example-1-constructing-a-let-expression-with-ast_builder)
- [Example 2: Constructing a Let Expression with Metaquot](#example-2-constructing-a-let-expression-with-metaquot)
- [Conclusion](#conclusion)
- [Building AST](#building-ast)
- [Table of Contents](#table-of-contents)
- [Description](#description)
- [Building ASTs with Low-Level Builders](#building-asts-with-low-level-builders)
- [Example: Building a Simple Integer AST Manually](#example-building-a-simple-integer-ast-manually)
- [Building ASTs with `AST_builder`](#building-asts-with-ast_builder)
- [Example 1: Using `pexp_constant` for Integer AST](#example-1-using-pexp_constant-for-integer-ast)
- [Example 2: Using `eint` for Simplified Integer AST](#example-2-using-eint-for-simplified-integer-ast)
- [Using Metaquot for AST Construction](#using-metaquot-for-ast-construction)
- [Example: Building an Integer AST with Metaquot](#example-building-an-integer-ast-with-metaquot)
- [Using Anti-Quotations in Metaquot](#using-anti-quotations-in-metaquot)
- [Example: Inserting Dynamic Expressions with Anti-Quotations](#example-inserting-dynamic-expressions-with-anti-quotations)
- [Building Complex Expressions](#building-complex-expressions)
- [Example 1: Constructing a Let Expression with `AST_builder`](#example-1-constructing-a-let-expression-with-ast_builder)
- [Example 2: Constructing a Let Expression with Metaquot](#example-2-constructing-a-let-expression-with-metaquot)
- [Conclusion](#conclusion)
- [On the next section, we will learn how to destructure an AST.](#on-the-next-section-we-will-learn-how-to-destructure-an-ast)

## Description

Expand Down Expand Up @@ -93,7 +96,7 @@ For even more simplicity, use `eint`:
let two ~loc = Ast_builder.Default.eint ~loc 2
```

> **:bulb: Tip**
> [!TIP]
> `eint` is an abbreviation for expression (`e`) integer (`int`).
## Using Metaquot for AST Construction
Expand All @@ -110,7 +113,7 @@ With Metaquot, you can construct an integer AST like this:
let three ~loc = [%expr 3]
```

> **:bulb: Tip**
> [!TIP]
> Metaquot is highly readable and intuitive but is static. For dynamic values, use Anti-Quotations.
### Using Anti-Quotations in Metaquot
Expand Down
42 changes: 41 additions & 1 deletion examples/1 - AST/a - Building AST/building_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,41 @@ let zero ~loc : Ppxlib_ast.Ast.expression =
pexp_attributes = [];
}

let of_string_opt_expr ~loc fn name =
Ast_builder.Default.value_binding ~loc
~pat:(Ast_builder.Default.pvar ~loc name)
~expr:
(Ast_builder.Default.eapply ~loc
(Ast_builder.Default.evar ~loc fn)
[ Ast_builder.Default.evar ~loc name ])

let rec of_string_exp_let ~loc types =
match types with
| [] -> Ast_builder.Default.eunit ~loc
| (name, type_) :: rest ->
let of_string = of_string_opt_expr ~loc (type_ ^ "_of_string_opt") name in
let of_string =
Ast_builder.Default.pexp_let ~loc Nonrecursive [ of_string ]
(of_string_exp_let ~loc rest)
in
of_string

let of_path_stri ~loc types =
[%stri let of_path path = [%e of_string_exp_let ~loc types]]

let module_ =
Ast_builder.Default.module_binding ~loc
~name:{ txt = Some "Routes"; loc }
~expr:
(Ast_builder.Default.pmod_structure ~loc
([%stri type t] :: [ of_path_stri ~loc [ ("foo", "name") ] ]))

let _ =
print_endline
("\nAST with AST pure tree build: "
^ Astlib.Pprintast.string_of_structure
[ Ast_builder.Default.pstr_module ~loc module_ ])

let _ =
print_endline
("\nAST with AST pure tree build: "
Expand Down Expand Up @@ -69,9 +104,14 @@ let _ =
("\nLet expression with metaquot: "
^ Astlib.Pprintast.string_of_expression let_expression)

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

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

let _ =
print_endline
("\nAccess ./ast_playground.ml to play with ast"
^ Astlib.Pprintast.string_of_expression (anti_quotation_expr (one ~loc)))
22 changes: 13 additions & 9 deletions examples/1 - AST/b - Destructing AST/destructuring_ast.ml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
open Ppxlib

let loc = Location.none

let one ~loc = [%expr 1]

let structure_item loc =
Expand All @@ -28,8 +27,7 @@ let test_match_pstr_eval () =
let structure_item = structure_item loc in
let structure = [ structure_item ] in
match match_int_payload ~loc (PStr structure) with
| Ok _ ->
Printf.printf "\nMatched 1 using Ast_pattern"
| Ok _ -> Printf.printf "\nMatched 1 using Ast_pattern"
| Error _ -> Printf.printf "\nDid not match pstr_eval"

let _ = test_match_pstr_eval ()
Expand All @@ -41,7 +39,9 @@ let match_int_payload =
let test_match_pstr_eval () =
let structure_item = structure_item loc in
let structure = [ structure_item ] in
try Ast_pattern.parse match_int_payload loc (PStr structure) Printf.printf "\nMatched 1 using Ast_pattern"
try
Ast_pattern.parse match_int_payload loc (PStr structure) Printf.printf
"\nMatched 1 using Ast_pattern"
with _ -> Printf.printf "\nDid not match 1 payload using Ast_pattern"

let _ = test_match_pstr_eval ()
Expand All @@ -53,8 +53,11 @@ let match_int_payload =
let test_match_pstr_eval () =
let structure_item = structure_item loc in
let structure = [ structure_item ] in
try Ast_pattern.parse match_int_payload loc (PStr structure) Printf.printf "\nMatched 1 using Ast_patter with eint"
with _ -> Printf.printf "\nDid not match 1 payload using Ast_pattern with eint"
try
Ast_pattern.parse match_int_payload loc (PStr structure) Printf.printf
"\nMatched 1 using Ast_patter with eint"
with _ ->
Printf.printf "\nDid not match 1 payload using Ast_pattern with eint"

let _ = test_match_pstr_eval ()

Expand All @@ -69,8 +72,7 @@ let match_int_payload expr =
let test_match_pstr_eval () =
let expr = one ~loc in
match match_int_payload expr with
| Ok _ ->
Printf.printf "\nMatched 1 using metaquot"
| Ok _ -> Printf.printf "\nMatched 1 using metaquot"
| Error _ -> Printf.printf "\nDid not match 1 using metaquot"

let _ = test_match_pstr_eval ()
Expand All @@ -93,6 +95,8 @@ let test_match_pstr_eval () =
| Ok value ->
Printf.printf "\nMatched 1 + <int> using metaquot and anti-quotation: %s"
(value |> string_of_int)
| Error _ -> Printf.printf "\nDid not match matched 1 + <int> using metaquot and anti-quotation"
| Error _ ->
Printf.printf
"\nDid not match matched 1 + <int> using metaquot and anti-quotation"

let _ = test_match_pstr_eval ()
2 changes: 1 addition & 1 deletion examples/2 - Writing PPXs/a - Context Free/dune
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
(kind ppx_rewriter)
(libraries ppxlib yojson ppxlib.astlib)
(preprocess
(pps ppxlib.metaquot ppx_deriving.show ppx_deriving.ord)))
(pps ppxlib.metaquot ppx_deriving.show ppx_deriving.ord)))
2 changes: 1 addition & 1 deletion examples/2 - Writing PPXs/b - Global/demo/dune
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
(name global_demo)
(public_name global_demo)
(preprocess
(pps global)))
(pps global_context)))
22 changes: 12 additions & 10 deletions examples/2 - Writing PPXs/b - Global/demo/global_demo.ml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
let demo_name = "Global Demo"
let _ = demo_name
let name = "Global Demo"
let _ = name

(* Uncomment the code bellow to see the lint error *)
(* let name = "John Doe" *)
(* let demo_name = "John Doe" *)

(* module enum *)
let _ = print_endline "\n# Enum"
Expand Down Expand Up @@ -42,10 +42,12 @@ let _ =
| None -> Printf.printf "Stick is not a valid value\n"

(* Uncomment the code bellow to see the error *)
(* module GameEnumError = struct
type _t = Rock | Paper | Scissors
module GameEnum = struct
type t = Rock | Paper | Scissors
end [@enum]
end [@enum] *)
(*
module GameEnumError = struct
type _t = Rock | Paper | Scissors
module GameEnum = struct
type t = Rock | Paper | Scissors
end [@enum]
end [@enum]
*)
4 changes: 2 additions & 2 deletions examples/2 - Writing PPXs/b - Global/dune
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(library
(name global)
(name global_context)
(kind ppx_rewriter)
(libraries context_free ppxlib ppxlib.astlib)
(preprocess
(pps ppxlib.metaquot)))
(pps ppxlib.metaquot)))
4 changes: 2 additions & 2 deletions examples/2 - Writing PPXs/b - Global/global.ml
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ module Lint = struct
let loc = mb.pvb_loc in
match mb.pvb_pat.ppat_desc with
| Ppat_var { txt = name; _ } ->
if String.starts_with name ~prefix:"demo_" then acc
else
if String.ends_with name ~suffix:"demo_" then
Driver.Lint_error.of_string loc
"Ops, variable name must not start with demo_"
:: acc
else acc
| _ -> acc
end
end
Expand Down
Loading

0 comments on commit 5069f73

Please sign in to comment.