This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
|
|
@ -0,0 +1,158 @@
|
|||
Interfacing with C
|
||||
==================
|
||||
|
||||
In this chapter, we're going to extend our calculator with a new function.
|
||||
The difference with `sin` is that we're going to use a C implementation of the
|
||||
function because it is not available in `Stdlib`. To do so, we're going to use
|
||||
{doc}`(foreign_stubs) </reference/foreign-stubs>` to implement the function in
|
||||
C.
|
||||
|
||||
## Create a test
|
||||
|
||||
Add a new test in `test/calc.t`:
|
||||
|
||||
```console
|
||||
$ calc -e 'log10(123456)'
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Lexing
|
||||
|
||||
We have a tiny change to make in `lib/lexer.mll`: extend function names so that
|
||||
they can contain numbers (but not at the beginning).
|
||||
|
||||
```{code-block} ocaml
|
||||
let ident = letter (letter | digit)+
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
Let's extend our `eval` function in `lib/cli.ml` to handle a `log10` function.
|
||||
Instead of implementing the function in OCaml, we declare it as an `external`
|
||||
(with its type).
|
||||
|
||||
```{code-block} ocaml
|
||||
:emphasize-lines: 1,12
|
||||
external log10_c : float -> float = "calc_log10"
|
||||
|
||||
let rec eval = function
|
||||
| Ast.Int n -> VInt n
|
||||
| Float f -> VFloat f
|
||||
| Ident "pi" -> VFloat (2. *. Stdlib.acos 0.)
|
||||
| Ident _ -> failwith "unknown ident"
|
||||
| Op (Add, a, b) -> eval_number_op ( + ) ( +. ) (eval a) (eval b)
|
||||
| Op (Mul, a, b) -> eval_number_op ( * ) ( *. ) (eval a) (eval b)
|
||||
| Op (Div, a, b) -> eval_number_op ( / ) ( /. ) (eval a) (eval b)
|
||||
| Call ("sin", e) -> VFloat (Stdlib.sin (as_float (eval e)))
|
||||
| Call ("log10", e) -> VFloat (log10_c (as_float (eval e)))
|
||||
| Call _ -> failwith "unknown function"
|
||||
```
|
||||
|
||||
## Create Foreign Stubs
|
||||
|
||||
The final part is to implement `calc_log10` as a C function and link it with
|
||||
our library.
|
||||
|
||||
Let's create a file `lib/calc_stubs.c`:
|
||||
|
||||
:::{literalinclude} interfacing-with-c/lib/calc_stubs.c
|
||||
:language: c
|
||||
:::
|
||||
|
||||
:::{note}
|
||||
|
||||
The interface between C and OCaml code uses a C type called `value`.
|
||||
|
||||
Values of this type can be converted from and to `double` using `Double_val`
|
||||
and `caml_copy_double`.
|
||||
|
||||
They need to be registered with the garbage collector using the `CAMLparam1`
|
||||
and `CAMLreturn` macros.
|
||||
:::
|
||||
|
||||
And we finally we specify to Dune that this file is part of the library in
|
||||
`lib/dune`:
|
||||
|
||||
```{code-block} dune
|
||||
:emphasize-lines: 4-6
|
||||
(library
|
||||
(name calc)
|
||||
(libraries cmdliner)
|
||||
(foreign_stubs
|
||||
(language c)
|
||||
(names calc_stubs)))
|
||||
```
|
||||
|
||||
Run the tests again with `dune runtest`.
|
||||
At that point, the output should be correct.
|
||||
Call `dune promote` to update the expected output.
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this chapter, we've extended our library with some C code. The mechanism to
|
||||
do so is called {doc}`foreign stubs </reference/foreign-stubs>`.
|
||||
|
||||
::::{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} interfacing-with-c/lib/dune
|
||||
:caption: lib/dune
|
||||
:language: dune
|
||||
:::
|
||||
|
||||
:::{literalinclude} development-cycle/lib/ast.ml
|
||||
:caption: lib/ast.ml (unchanged)
|
||||
:language: ocaml
|
||||
:::
|
||||
|
||||
:::{literalinclude} interfacing-with-c/lib/calc_stubs.c
|
||||
:caption: lib/calc_stubs.c
|
||||
:language: c
|
||||
:::
|
||||
|
||||
:::{literalinclude} interfacing-with-c/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} interfacing-with-c/test/calc.t
|
||||
:caption: test/calc.t
|
||||
:language: cram
|
||||
:::
|
||||
|
||||
::::
|
||||
Loading…
Add table
Add a link
Reference in a new issue