if True: ... if n < 42: ... else: ...
x = 42 y += 1
while i < 6: print(i) i += 1
i = 1 r = 1 while i <= 6: r = i * r i += 1 print("Computing factorial of 6 =", r)Note that indentation is significant in case of conditionals and while loop (just like in Python).
Once uncompressed (with tar zxvf while.tar.gz), you get a directory while/ with the following files:
ast.ml | abstract syntax of While language (focus of this demo) |
interp.ml | interpreter (focus of this demo) |
while.ml | main file (produced the interpreter that can be run on the test file) |
lexer.mll | lexical analysis (used by the main file to analize the source text of the input) |
parser.mly | parsing (used by the main file to construct the AST of the input) |
Makefile/dune | to automate the build |
test.wh | to test the interpreter (input file for this demo) |
The project compiles (run make, that launches dune build).
The executable takes a While file on the command line, with suffix .wh. When it is absent, file test.wh is used. When running make, the interpreter is run on file test.wh.
type location = Lexing.position * Lexing.position
type ident = { loc: location; id: string; }
(* Unary operators. *)
type unop =
| Uneg (* -e *)
| Unot (* not e *)
(* Binary operators. *)
type binop =
| Badd | Bsub | Bmul | Bdiv | Bmod (* + - * // % *)
| Beq | Bneq | Blt | Ble | Bgt | Bge (* == != < <= > >= *)
| Band | Bor (* and or *)
(* Constants. *)
type constant =
| Cbool of bool
| Cstring of string
| Cint of int
(* Expressions. *)
type expr =
| Ecst of constant (* constant *)
| Eunop of unop * expr (* unary operation *)
| Ebinop of binop * expr * expr (* binary operation *)
| Eident of ident (* variable *)
(* Statements. *)
type stmt =
| Sif of expr * stmt * stmt (* conditional *)
| Sassign of ident * expr (* modifying a variable *)
| Sblock of stmt list (* a sequence of statements *)
| Sprint of expr list (* printing a list of expressions *)
| Swhile of expr * stmt (* while loop *)
(* a program is simply a statement. *)
type file = stmt
# let l = 1 :: 2 :: 3 :: [];; val l : int list = [1; 2; 3]As seen in this display, OCaml offers a more concise syntax to directly construct a list by extension, by specifying all its elements:
# let l = [1; 2; 3];; val l : int list = [1; 2; 3]Many functions on lists are predefined: to access the first element, all the others, calculate the length, etc.
# let rec sum l = match l with | [] -> 0 | x :: r -> x + sum r;; val sum : int list -> int =The match with construction consists of an expression to consider (between the keywords match and with, here the list l) and one or more filtering cases separated by a vertical bar |. A filtering case consists of a pattern and an expression separated by an arrow. The pattern is a constructor (here [] or ::) and its arguments can be named (here the two arguments of :: are named x and r).
# sum [1;2;3];; - : int = 6It becomes clear now where the power of pattern matching comes from: it acts like a series of tests and local variable definitions at the same time, all with an extremely concise syntax. There's even a syntactic shortcut for pattern matching on the last argument of a function, using the function keyword. Thus, we can rewrite the sum function as simply as:
let rec sum = function | [] -> 0 | x :: r -> x + sum r
open Ast
open Format
exception Error of string
let error s = raise (Error s)
Next, we need a (semantic) notion of values, i.e. to what the interpreter evaluates the expressions to:
(* Values. *)
type value =
| Vbool of bool
| Vint of int
| Vstring of string
(* Print a value on standard output *)
let print_value = function
| Vbool true -> printf "True"
| Vbool false -> printf "False"
| Vint n -> printf "%d" n
| Vstring s -> printf "%s" s
(* Printing a list of values. *)
let rec print_values vl = match vl with
| [] -> printf "@."
| [v] ->
print_value v;
printf "@."
| v :: vtl ->
print_value v;
print_string " ";
print_values vtl
type memory = (string, value) Hashtbl.t
let rec interp_expr ctx = function
| Ecst c -> interp_const c
| Eunop (op, e1) -> interp_unop ctx op e1
| Ebinop (op, e1, e2) -> interp_binop ctx op e1 e2
| Eident {id} -> try Hashtbl.find ctx id with _ -> error "not found"
and interp_const = function
| Cbool b -> Vbool b
| Cstring s -> Vstring s
| Cint n -> Vint n
(* Interpreting unary operations. *)
and interp_unop ctx op e1 =
let v1 = interp_expr ctx e1 in
match op with
| Uneg ->
let v1 = interp_expr ctx e1 in
begin match v1 with
| Vint n1 -> Vint (-n1)
| _ -> error "wring unary operand type: argument must be of integer type!"
end
| Unot ->
begin match v1 with
| Vbool b1 -> Vbool (not b1)
| _ -> error "wring unary operand type: argument must be of Boolean type!"
end
and interp_binop ctx op e1 e2 =
match op with
| Badd | Bsub | Bmul | Bdiv | Bmod -> interp_binop_arith ctx op e1 e2
| _ (* all other cases *) -> interp_binop_bool ctx op e1 e2
and interp_binop_arith ctx op e1 e2 =
let v1 = interp_expr ctx e1 in
let v2 = interp_expr ctx e2 in
match v1, v2 with
| Vint n1, Vint n2 ->
begin match op with
| Badd -> Vint (n1 + n2)
| Bsub -> Vint (n1 - n2)
| Bmul -> Vint (n1 * n2)
| Bmod -> Vint (n1 mod n2)
| Bdiv -> if n2 = 0 then error "division by zero!" else Vint (n1 / n2)
| _ -> assert false (* other operations excluded by asssumption. *)
end
| _ -> error "wring operand type: arguments must be of integer type!"
and interp_binop_bool ctx op e1 e2 =
(* We first treat cases where `op` is a logical operation `Band` or `Bor`
separately for efficiency. *)
if op = Band then
begin match interp_expr ctx e1 with
| Vbool b1 ->
if b1 then begin match interp_expr ctx e2 with
| Vbool b2 -> Vbool b2
| _ -> error "unsupported operand types"
end
else Vbool false
| _ -> error "unsupported operand types"
end
else if op = Bor then
match interp_expr ctx e1 with
| Vbool b1 ->
if b1 then Vbool true
else begin match interp_expr ctx e1 with
| Vbool b2 -> Vbool b2
| _ -> error "unsupported operand types"
end
| _ -> error "unsupported operand types"
else
(* In all other binary comparison operations, we can evaluate both
arguments first: *)
let v1 = interp_expr ctx e1 in
let v2 = interp_expr ctx e2 in
match op with
| Beq -> Vbool (v1 = v2)
| Bneq -> Vbool (not (v1 = v2))
| Blt -> Vbool (v1 < v2)
| Ble -> Vbool (v1 <= v2)
| Bgt -> Vbool (v1 > v2)
| Bge -> Vbool (v1 >= v2)
| _ -> assert false (* other operations excluded by asssumption. *)
(* Interpreting a statement *)
let rec interp_stmt ctx = function
| Sif (e, s1, s2) ->
begin match (interp_expr ctx e) with
| Vbool b1 -> if b1 then interp_stmt ctx s1 else interp_stmt ctx s2
| _ -> error "wrong type : bool expected"
end
| Sassign ({id}, e1) -> Hashtbl.replace ctx id (interp_expr ctx e1)
| Sblock bl -> block ctx bl
| Sprint el -> print_values (List.map (fun e -> interp_expr ctx e) el)
| Swhile (e, s) ->
match interp_expr ctx e with
| Vbool b -> if b then (interp_stmt ctx s; interp_stmt ctx (Swhile (e, s))) else printf ""
| _ -> error "wrong type : bool expected"
and block ctx = function
| [] -> ()
| s :: sl -> interp_stmt ctx s; block ctx sl
let file s = interp_stmt (Hashtbl.create 16) s
| Sif (e, s1, s2) -> begin match (interp_expr ctx e) with | Vbool b1 -> if b1 then interp_stmt ctx s1 else interp_stmt ctx s2 | _ -> error "wrong type : bool expected" endwe interpret the expression to a Boolean value and then evaluate the corresponding branche of the conditional recursively calling interp_stmt.
| Sassign ({id}, e1) -> Hashtbl.replace ctx id (interp_expr ctx e1)we use the Hashtbl function replace that stores the result of the evaluation of expression interp_expr ctx e1 in the memory ctx at the variable id
| Sblock bl -> block ctx blwe using mutual recursion, calling the block function. If the sequence is empty, it returns the unit value ().
| Sprint el -> print_values (List.map (fun e -> interp_expr ctx e) el)we use the defined Utility function print_values. However, that function is defined on the list of values, but here we have a list el of expressions!
let rec map f = function | [] -> [] | x :: l -> (f x) :: map f land it is higher-order function, because its parameter f is a function itself.
| Swhile (e, s) -> match interp_expr ctx e with | Vbool b -> if b then (interp_stmt ctx s; interp_stmt ctx (Swhile (e, s))) else printf "" | _ -> error "wrong type : bool expected"we first interpret the guard expression e. If the result of the evaluation is true (Vbool b, with b equal true),