|
1 | | -{%html: <div style="display: flex; justify-content:space-between"><div>%}{{!"good-practices"}< Good practices}{%html: </div><div>%}{%html: </div></div>%} |
| 1 | +{0 PPX by examples} |
2 | 2 |
|
3 | | -{0 Examples} |
| 3 | +This part of the documentation was made to help on understanding what are and how to write PPXs in OCaml with PPXLib. |
4 | 4 |
|
5 | | -This section is here to allow viewing complete examples of PPXs written using [ppxlib] directly in the documentation. However, they are not "complete" in the sense that the overall organization, such as the [dune] files, is not included. |
| 5 | +{1 Content} |
6 | 6 |
|
7 | | -In order to see a fully working complete example of a PPX written using [ppxlib], that you can compile, modify and test, go to the {{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples}examples} folder of ppxlib sources. |
| 7 | +- {{!page-"example-ast"} AST} |
| 8 | +{ul {- {{!page-"example-ast-building"} Building an AST}}} |
| 9 | +{ul {- {{!page-"example-ast-destructing"} Destructing an AST}}} |
8 | 10 |
|
9 | | -{1 [ppx_deriving_accesors]} |
| 11 | +- {{!page-"example-writing-ppxs"} Writing a PPX} |
| 12 | +{ul {- {{!page-"example-writing-ppxs-context-free"} Context-free transformations}}} |
| 13 | +{ul {- {{!page-"example-writing-ppxs-global"} Global transformations}}} |
10 | 14 |
|
11 | | -The fully complete, ready-to-compile [ppx_deriving_accesors] example is accessible in [ppxlib]'s {{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/simple-deriver}sources}. |
| 15 | +- {{!page-"example-testing-ppxs-index"} Testing a PPX} |
12 | 16 |
|
13 | | -This deriver will generate accessors for record fields, from the record type |
14 | | -definition. |
| 17 | +{1 References} |
15 | 18 |
|
16 | | -For example, this code: |
17 | | - |
18 | | -{@ocaml[ |
19 | | -type t = |
20 | | - { a : string |
21 | | - ; b : int |
22 | | - } |
23 | | - [@@deriving accessors] |
24 | | -]} |
25 | | - |
26 | | -will generate the following, appended after the type definition: |
27 | | - |
28 | | -{@ocaml[ |
29 | | -let a x = x.a |
30 | | -let b x = x.b |
31 | | -]} |
32 | | - |
33 | | -The entire code is: |
34 | | - |
35 | | -{@ocaml[ |
36 | | -open Ppxlib |
37 | | -module List = ListLabels |
38 | | -open Ast_builder.Default |
39 | | - |
40 | | -let accessor_impl (ld : label_declaration) = |
41 | | - let loc = ld.pld_loc in |
42 | | - pstr_value ~loc Nonrecursive |
43 | | - [ |
44 | | - { |
45 | | - pvb_pat = ppat_var ~loc ld.pld_name; |
46 | | - pvb_expr = |
47 | | - pexp_fun ~loc Nolabel None |
48 | | - (ppat_var ~loc { loc; txt = "x" }) |
49 | | - (pexp_field ~loc |
50 | | - (pexp_ident ~loc { loc; txt = lident "x" }) |
51 | | - { loc; txt = lident ld.pld_name.txt }); |
52 | | - pvb_attributes = []; |
53 | | - pvb_loc = loc; |
54 | | - }; |
55 | | - ] |
56 | | - |
57 | | -let accessor_intf ~ptype_name (ld : label_declaration) = |
58 | | - let loc = ld.pld_loc in |
59 | | - psig_value ~loc |
60 | | - { |
61 | | - pval_name = ld.pld_name; |
62 | | - pval_type = |
63 | | - ptyp_arrow ~loc Nolabel |
64 | | - (ptyp_constr ~loc { loc; txt = lident ptype_name.txt } []) |
65 | | - ld.pld_type; |
66 | | - pval_attributes = []; |
67 | | - pval_loc = loc; |
68 | | - pval_prim = []; |
69 | | - } |
70 | | - |
71 | | -let generate_impl ~ctxt (_rec_flag, type_declarations) = |
72 | | - let loc = Expansion_context.Deriver.derived_item_loc ctxt in |
73 | | - List.map type_declarations ~f:(fun (td : type_declaration) -> |
74 | | - match td with |
75 | | - | { |
76 | | - ptype_kind = Ptype_abstract | Ptype_variant _ | Ptype_open; |
77 | | - ptype_loc; |
78 | | - _; |
79 | | - } -> |
80 | | - let ext = |
81 | | - Location.error_extensionf ~loc:ptype_loc |
82 | | - "Cannot derive accessors for non record types" |
83 | | - in |
84 | | - [ Ast_builder.Default.pstr_extension ~loc ext [] ] |
85 | | - | { ptype_kind = Ptype_record fields; _ } -> |
86 | | - List.map fields ~f:accessor_impl) |
87 | | - |> List.concat |
88 | | - |
89 | | -let generate_intf ~ctxt (_rec_flag, type_declarations) = |
90 | | - let loc = Expansion_context.Deriver.derived_item_loc ctxt in |
91 | | - List.map type_declarations ~f:(fun (td : type_declaration) -> |
92 | | - match td with |
93 | | - | { |
94 | | - ptype_kind = Ptype_abstract | Ptype_variant _ | Ptype_open; |
95 | | - ptype_loc; |
96 | | - _; |
97 | | - } -> |
98 | | - let ext = |
99 | | - Location.error_extensionf ~loc:ptype_loc |
100 | | - "Cannot derive accessors for non record types" |
101 | | - in |
102 | | - [ Ast_builder.Default.psig_extension ~loc ext [] ] |
103 | | - | { ptype_kind = Ptype_record fields; ptype_name; _ } -> |
104 | | - List.map fields ~f:(accessor_intf ~ptype_name)) |
105 | | - |> List.concat |
106 | | - |
107 | | -let impl_generator = Deriving.Generator.V2.make_noarg generate_impl |
108 | | -let intf_generator = Deriving.Generator.V2.make_noarg generate_intf |
109 | | - |
110 | | -let my_deriver = |
111 | | - Deriving.add "accessors" ~str_type_decl:impl_generator |
112 | | - ~sig_type_decl:intf_generator |
113 | | -]} |
114 | | - |
115 | | -{1 [ppx_get_env]} |
116 | | - |
117 | | -The fully complete, ready-to-compile [ppx_get_env] example is accessible in [ppxlib]'s {{:https://github.com/ocaml-ppx/ppxlib/tree/main/examples/simple-extension-rewriter}sources}. |
118 | | - |
119 | | -A PPX rewriter that will expand [[%get_env "SOME_ENV_VAR"]] into the value of the |
120 | | -env variable [SOME_ENV_VAR] at compile time, as a string. |
121 | | - |
122 | | -E.g., assuming we set [MY_VAR="foo"], it will turn: |
123 | | - |
124 | | -{@ocaml[ |
125 | | -let () = print_string [%get_env "foo"] |
126 | | -]}``` |
127 | | - |
128 | | -into: |
129 | | - |
130 | | -{@ocaml[ |
131 | | -let () = print_string "foo" |
132 | | -]} |
133 | | - |
134 | | - |
135 | | -Note that this is just a toy example, and we actually advise against this |
136 | | -type of PPX that has side effects or relies heavily on the file system or [env] |
137 | | -variables, unless you absolutely you know what you're doing. |
138 | | - |
139 | | -In this case, it won't work well with Dune, since Dune won't know |
140 | | -about the dependency on the env variables specified in the extension's payload. |
141 | | - |
142 | | -The entire code is: |
143 | | - |
144 | | -{@ocaml[ |
145 | | -open Ppxlib |
146 | | - |
147 | | -let expand ~ctxt env_var = |
148 | | - let loc = Expansion_context.Extension.extension_point_loc ctxt in |
149 | | - match Sys.getenv env_var with |
150 | | - | value -> Ast_builder.Default.estring ~loc value |
151 | | - | exception Not_found -> |
152 | | - let ext = |
153 | | - Location.error_extensionf ~loc "The environment variable %s is unbound" |
154 | | - env_var |
155 | | - in |
156 | | - Ast_builder.Default.pexp_extension ~loc ext |
157 | | - |
158 | | -let my_extension = |
159 | | - Extension.V3.declare "get_env" Extension.Context.expression |
160 | | - Ast_pattern.(single_expr_payload (estring __)) |
161 | | - expand |
162 | | - |
163 | | -let rule = Ppxlib.Context_free.Rule.extension my_extension |
164 | | -let () = Driver.register_transformation ~rules:[ rule ] "get_env" |
165 | | -]} |
166 | | - |
167 | | -{%html: <div style="display: flex; justify-content:space-between"><div>%}{{!"good-practices"}< Good practices}{%html: </div><div>%}{%html: </div></div>%} |
| 19 | +- {{:https://ocaml.org/docs/metaprogramming} Ocaml Metaprogramming Docs} |
| 20 | +- {{:https://ocaml-ppx.github.io/ppxlib/ppxlib/index.html} PPXLib documentation} |
| 21 | +- {{:https://www.youtube.com/live/dMoRMqQ6GLs?feature=shared&t=4251} The needed introduction to writing a ppx} |
| 22 | +- {{:https://tarides.com/blog/2019-05-09-an-introduction-to-ocaml-ppx-ecosystem/} An introduction to OCaml PPX ecosystem} |
0 commit comments