File: using-ppx.md

package info (click to toggle)
ocaml-dune 3.20.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 33,564 kB
  • sloc: ml: 175,178; asm: 28,570; ansic: 5,251; sh: 1,096; lisp: 625; makefile: 148; python: 125; cpp: 48; javascript: 10
file content (180 lines) | stat: -rw-r--r-- 3,957 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
Using a PPX Preprocessor
========================

Our calculator is pretty much opaque: we feed it a string, and it displays a
result (on an error message), but we have now way to know what the internal expression looks like.

In this chapter, we're going to use a `ppx` deriver to generate a `pp_expr`
function that can display expressions.

## Prerequisites

Install [ppx_deriving](https://github.com/ocaml-ppx/ppx_deriving) by running:

```sh
opam install ppx_deriving.5.2.1
```

## Create a test

Add a new test in `test/calc.t`:

```console
$ calc --debug-ast -e '2 * sin (pi / 2)'
```

Run `dune runtest` and see the failure.
Run `dune promote` to add the failure to the test file.
Our goal in the rest of the chapter is to change the output of this test.

## Use `ppx_deriving.show`

Add an `[@@deriving show]` attribute on types in `lib/ast.ml`:

```{code-block} ocaml
:emphasize-lines: 5,13
type op =
  | Add
  | Mul
  | Div
[@@deriving show]

type expr =
  | Int of int
  | Float of float
  | Ident of string
  | Op of op * expr * expr
  | Call of string * expr
[@@deriving show]
```

This will generate `pp_op`, `show_op`, `pp_expr`, and `show_expr` functions.

To do do we need to instruct Dune to use `ppx_deriving.show` as a preprocessor by updating `lib/dune`:

```{code-block} dune
:emphasize-lines: 4-5
(library
 (name calc)
 (libraries cmdliner)
 (preprocess
  (pps ppx_deriving.show))
 (foreign_stubs
  (language c)
  (names calc_stubs)))
```

## Add a `--debug-ast` Flag

We have a few edits to make to `lib/cli.ml`.

First, add a new `cmdliner` flag to parse the command-line and pass it to
`repl` and `eval_lb`:

```{code-block} ocaml
:emphasize-lines: 6-8,11-12
let term =
  let open Cmdliner.Term.Syntax in
  let+ expr_opt =
    let open Cmdliner.Arg in
    value & opt (some string) None & info [ "e" ]
  and+ debug_ast =
    let open Cmdliner.Arg in
    value & flag & info [ "debug-ast" ]
  in
  match expr_opt with
  | Some s -> eval_lb ~debug_ast (Lexing.from_string s)
  | None -> repl ~debug_ast
```

Then, forward it from `repl` to `eval_lb`:

```{code-block} ocaml
:emphasize-lines: 1,5
let repl ~debug_ast =
  while true do
    Printf.printf ">> %!";
    let lb = Lexing.from_channel Stdlib.stdin in
    eval_lb ~debug_ast lb
  done
```

Finally, update `eval_lb` to use it:

```{code-block} ocaml
:emphasize-lines: 1,4
let eval_lb ~debug_ast lb =
  try
    let expr = Parser.main Lexer.token lb in
    if debug_ast then Format.eprintf "[debug] %a\n" Ast.pp_exp expr;
    let v = eval expr in
    Printf.printf "%s\n" (value_to_string v)
  with Parser.Error ->
    Printf.printf "parse error near character %d" lb.lex_curr_pos
```

Run the tests again with `dune runtest`.
At that point, the output should be correct.
Call `dune promote` to update the expected output.

::::{dropdown} Checkpoint
:icon: location

This is how the project looks like at the end of this chapter.

:::{literalinclude} introduction/dune-project
:caption: dune-project (unchanged)
:language: dune
:::

:::{literalinclude} structure/bin/dune
:caption: bin/dune (unchanged)
:language: dune
:::

:::{literalinclude} structure/bin/calc.ml
:caption: bin/calc.ml (unchanged)
:language: ocaml
:::

:::{literalinclude} using-ppx/lib/dune
:caption: lib/dune
:language: dune
:::

:::{literalinclude} using-ppx/lib/ast.ml
:caption: lib/ast.ml
:language: ocaml
:::

:::{literalinclude} interfacing-with-c/lib/calc_stubs.c
:caption: lib/calc_stubs.c (unchanged)
:language: c
:::

:::{literalinclude} using-ppx/lib/cli.ml
:caption: lib/cli.ml
:language: ocaml
:::

:::{literalinclude} interfacing-with-c/lib/lexer.mll
:caption: lib/lexer.mll
:language: ocaml
:::

:::{literalinclude} development-cycle/lib/parser.mly
:caption: lib/parser.mly (unchanged)
:language: ocaml
:::

:::{literalinclude} structure/test/dune
:caption: test/dune (unchanged)
:language: dune
:::

:::{literalinclude} using-ppx/test/calc.t
:caption: test/calc.t
:language: cram
:::

::::