Input file
โ
Compiler
โ
Output file
Source Preprocessor
PreProcessor eXtension
#define PI 3
return PI;
โ
return 3;
#if OCAML_VERSION >= (4,13)
| Type_variant (tll,_) ->
#else
| Type_variant tll ->
#endif
do_something
โ
# 764 "src/loader/cmi.ml"
| Type_variant (tll,_) ->
# 768 "src/loader/cmi.ml"
do_something
module Pretty = struct
#include "prettyprint.ml"
end
โ
module Pretty = struct
# 1 "prettyprint.ml"
< content of the file >
# 1 "prettyprint.ml"
end
Breaks most developper tools.
Good for "completely new syntax".
Difficult to do anything "OCaml-specific".
Handled by build system or ocamlopt
's -pp
option.
Don't use source preprocessing. But let's do a quick exercise anyway ๐คจ
Start with 0_source_preprocessing_exercise/README.md
(10min)
In OCaml: a Parse
tree
.
(x + 0) * (2 + 3)
โ
List.map
(fun x -> expr)
(1 :: [])
โ
float -> int option
โ
module X = struct
let n : float -> int option =
expr
let n2 : t = expr
end
โ
Parsetree
The API in the manual (unique source of truth),
utop -dparsetree
,
The ast explorer website,
Editor support (?).
Try anything you are curious about!
Paperwork
val transform : Parsetree.t -> Parsetree.t
Paperwork
Don't write PPX by hand. But let's do a quick exercise anyway ๐คจ
Start with 1_handmade_ppx_exercise/README.md
(20min)
Everything. Here is a small list:
We have a solution: ppxlib
!
dune
Don't write global transformation by hand. But let's do a quick exercise anyway ๐คจ
Start with 4_global_transformations/README.md
(10min)
Also, record your ideas for an interesting PPX.
We have seen that dune
and ppxlib
coordinate to solve the boilerplate/build
complexity.
How about opaqueness and composability?
We need two Jokers ๐๐.
Attributes are extra named Parsetree
nodes that can be attached to a Parsetree
node.
g[@inlined] x
โ
val f : int -> int
[@@ocaml.deprecated
"Please use function g instead"]
โ
[@@@ocaml.warnings "-42"]
โ
Extension nodes are named Parsetree
nodes that can replace a Parsetree
node.
1 + [%hello "world"]
โ
module M = struct
[%%rewrite_me let f x = x]
let%rewrite_me f x = x
end
โ
We are going to restrict the transformation in:
Their input: No full Parsetree
as input. Context-free.
Their effect: No full Parsetree
as output. Local.
_ | Deriver | Extender |
---|---|---|
Input | Attributed node (right name) | Extension node (right name) |
Output | Nodes to append | Node to replace |
let before = 1
type foo = Bar of int | Baz [@@deriving show]
let after = 1
rewritten into
let before = 1
type foo =
| Bar of int
| Baz [@@deriving show]
let rec pp_foo : Format.formatter -> foo -> unit =
fun fmt -> function
| Bar a0 ->
Format.fprintf fmt "(@[<2>Bar@ ";
Format.fprintf fmt "%d" a0;
Format.fprintf fmt "@])"
| Baz -> Format.pp_print_string fmt "Baz"
and show_foo : foo -> string =
fun x -> Format.asprintf "%a" pp_foo x
let after = 1
let before = 1
let to_ocaml = [%html "<a href='ocaml.org'>OCaml!</a>"]
let after = 1
rewritten into
let before = 1
let to_ocaml =
Html.a ~a:[Html.a_href (Html.Xml.W.return "ocaml.org")]
(Html.Xml.W.cons
(Html.Xml.W.return (Html.txt (Html.Xml.W.return "OCaml!")))
(Html.Xml.W.nil ()))
let after = 1
Less opaqueness
More guarantees
More composability
Many new ideas for transformations!
Derivers:
ppx_deriving.show
for deriving pretty printers.ppx_deriving.eq
for deriving equalityppx_deriving.ord
for deriving comparisonppx_deriving.enum
for deriving enumeratorsppx_deriving.iter
for deriving itersppx_deriving.map
for deriving mapsppx_deriving.fold
for deriving foldsppx_deriving.make
for deriving constructorsppx_show
for deriving pretty-printersppx_yojson_conv
for deriving json convertersppx_deriving_yaml
for deriving yaml convertersppx_sexp_conv
for deriving sexp convertersppx_accessor
for deriving record accessorsExtenders:
ppx_expect
for expect testsppx_inline_test
for inline testsppx_lwt
for monadic Lwtppx_tyxml
for writing HTML naturallyppx_rapper
for writing SQL naturallyLet's use some of them:
Feel free to reuse in everyday life!
Start with 2_use_deriver_exercise/README.md
Then, 3_use_extender_exercise/README.md
(15 min total)
Feel free to reuse in everyday life!
Start with write_deriver_exercise/README.md
(150 min total)
Some comments:
ppxlib
has a documentation.
Remembering how ppxlib
and dune
cooperate can help.
The Parsetree
is a big and unstable type.
Ppxlib
provides some stability (at the expense of a little complexity,
especially for pattern-matching).
Be careful with locations. Having them wrong can harm the user experience, with Merlin being confused.
Be careful with shadowing. If you access a module, do not expect anymodule to be open, qualify everything completely, and do not shadow existing definition.