Introduction

The goal of this first chapter is to get to a place where you have a working Dune project with a skeleton of a calculator.

This is is a tutorial: it is meant to be followed in order, but you can stop at any point. You can also restart from any chapter, using these sections that are present at the end of the previous chapter.

Checkpoint

This will contain the project at the end of each chapter.

Installing Packages

First, you’ll need to have a working Opam installation. This is described in How to Install Dune.

Then, create an empty directory somewhere, say ~/dune-calc. In this tutorial, we will only create files in this directory.

Let’s first make sure you have a working opam installation. Run this command:

opam --version

It should display something like “2.2.0”. Anything greater than 2.0.0 is fine.

Important

When asked to type a command, you can click to copy the full command to your clipboard.

In this tutorial, all commands will be typed at the root of your project, like ~/dune-calc.

Let’s create a local switch: cd to this directory and run the following command. This can take a few minutes.

opam switch create ./ 5.1.1

This command has created a directory named _opam in the current directory. Now, let’s install some packages by running:

opam install dune.3.15.3 menhir.20231231

You can confirm that opam is correctly setup by typing dune --version, which should display 3.15.3. Otherwise, please refer to How to Install Dune.

Note

The instructions use precise version numbers in opam install command. This is to ensure that the error messages will exactly map what you’re seeing, but it is very likely to work with any version.

The Calculator Skeleton

Now that we have an opam switch and some packages installed, let’s create the various files.

For each file, click to reveal the file contents and click to copy the contents to your clipboard. Open file.txt in a text editor and paste the contents there.

dune-project
(lang dune 3.0)
(using menhir 2.1)
(package (name calc))

This file contains metadata about the project:

  • the version of the dune language we’re using

  • the extensions we’re using

  • the package we’re defining

See also

dune-project Reference documentation about dune-project files

dune
(executable
 (public_name calc))

(ocamllex lexer)

(menhir
 (modules parser))

This file contains a description of what’s in our project:

  • an executable stanza defining our calculator binary

  • an ocamllex stanza, setting up rules to compile lexer.mll to a Lexer module

  • a menhir stanza, to similarly use parser.mly as a Parser module

parser.mly
%token Eof
%token<int> Int
%token Plus
%start<Ast.exp> main

%left Plus

%{ open Ast %}

%%

main: expr Eof { $1 }

expr:
| Int { Int $1 }
| expr Plus expr { Add ($1, $3) }

%%

This contains definitions of our tokens and grammar rules, using Menhir.

lexer.mll
let space = [' ']+

let digit = ['0'-'9']

rule token = parse
    | eof { Parser.Eof }
    | space { token lexbuf }
    | '\n' { Parser.Eof }
    | '+' { Parser.Plus }
    | digit+ { Parser.Int (int_of_string (Lexing.lexeme lexbuf)) }

This is our lexer, using ocamllex.

ast.ml
type exp =
  | Int of int
  | Add of exp * exp

This contains a definition of the arithmetic expressions manipulated by the calculator.

Note

This is in a separate file from calc.ml to avoid module cycles, since Calc depends on Parser, which depends on the expression type.

calc.ml
let rec eval = function Ast.Int n -> n | Add (a, b) -> eval a + eval b

let () =
  while true do
    Printf.printf ">> %!";
    let lb = Lexing.from_channel Stdlib.stdin in
    let e = Parser.main Lexer.token lb in
    Printf.printf "%d\n" (eval e)
  done

This is the “business logic” of our app, in which we:

  • display a prompt

  • call the lexer and parser to get an expression

  • evaluate the expression

  • display the result

At this stage, we have the skeleton of a calculator.

Run the following command to build and execute the calculator:

dune exec calc

You can enter additions, such as 1+2 followed by Enter. Exit with Ctrl+C.

Initially, only addition is supported and anything else triggers an exception terminating the execution.

Note that a _build directory is now present. This is where Dune will store all compiled artifacts.

You can safely remove this directory - that’s actually what the dune clean command does. But that’s not usually necessary since Dune will keep track of dependencies and what is up to date.

The _opam directory is where your dependencies are located. It is managed by opam. If it gets removed by accident or something is corrupted in there, it is safe to remove it and recreate it by running opam switch and opam install as described above.