208 lines
5.2 KiB
OCaml
208 lines
5.2 KiB
OCaml
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* The type of lambda-terms, in de Bruijn's representation. *)
|
|
|
|
type var = int (* a de Bruijn index *)
|
|
type term =
|
|
| Var of var
|
|
| Lam of (* bind: *) term
|
|
| App of term * term
|
|
| Let of (* bind: *) term * term
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* An environment-based big-step interpreter. This is the same interpreter
|
|
that we programmed in Coq, except here, in OCaml, fuel is not needed. *)
|
|
|
|
type cvalue =
|
|
| Clo of (* bind: *) term * cenv
|
|
|
|
and cenv =
|
|
cvalue list
|
|
|
|
let empty : cenv =
|
|
[]
|
|
|
|
exception RuntimeError
|
|
|
|
let lookup (e : cenv) (x : var) : cvalue =
|
|
try
|
|
List.nth e x
|
|
with Failure _ ->
|
|
raise RuntimeError
|
|
|
|
let rec eval (e : cenv) (t : term) : cvalue =
|
|
match t with
|
|
| Var x ->
|
|
lookup e x
|
|
| Lam t ->
|
|
Clo (t, e)
|
|
| App (t1, t2) ->
|
|
let cv1 = eval e t1 in
|
|
let cv2 = eval e t2 in
|
|
let Clo (u1, e') = cv1 in
|
|
eval (cv2 :: e') u1
|
|
| Let (t1, t2) ->
|
|
eval (eval e t1 :: e) t2
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Term/value/environment printers. *)
|
|
|
|
open Printf
|
|
|
|
let rec print_term f = function
|
|
| Var x ->
|
|
fprintf f "(Var %d)" x
|
|
| Lam t ->
|
|
fprintf f "(Lam %a)" print_term t
|
|
| App (t1, t2) ->
|
|
fprintf f "(App %a %a)" print_term t1 print_term t2
|
|
| Let (t1, t2) ->
|
|
fprintf f "(Let %a %a)" print_term t1 print_term t2
|
|
|
|
let rec print_cvalue f = function
|
|
| Clo (t, e) ->
|
|
fprintf f "< %a | %a >" print_term t print_cenv e
|
|
|
|
and print_cenv f = function
|
|
| [] ->
|
|
fprintf f "[]"
|
|
| cv :: e ->
|
|
fprintf f "%a :: %a" print_cvalue cv print_cenv e
|
|
|
|
let print_cvalue cv =
|
|
fprintf stdout "%a\n" print_cvalue cv
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* A tiny test suite. *)
|
|
|
|
let id =
|
|
Lam (Var 0)
|
|
|
|
let idid =
|
|
App (id, id)
|
|
|
|
let apply =
|
|
Lam (Lam (App (Var 1, Var 0)))
|
|
|
|
let test1 eval t =
|
|
print_cvalue (eval empty t)
|
|
|
|
let test name eval =
|
|
printf "Testing %s...\n%!" name;
|
|
test1 eval idid;
|
|
test1 eval (App (apply, id));
|
|
test1 eval (App (App (apply, id), id));
|
|
()
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Test. *)
|
|
|
|
let () =
|
|
test "the direct-style evaluator" eval
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* A CPS-transformed, environment-based big-step interpreter. *)
|
|
|
|
(* In this code, every recursive call to [evalk] is a tail call. Thus,
|
|
it is compiled by the OCaml compiler to a loop, and requires only O(1)
|
|
space on the OCaml stack. *)
|
|
|
|
let rec evalk (e : cenv) (t : term) (k : cvalue -> 'a) : 'a =
|
|
match t with
|
|
| Var x ->
|
|
k (lookup e x)
|
|
| Lam t ->
|
|
k (Clo (t, e))
|
|
| App (t1, t2) ->
|
|
evalk e t1 (fun cv1 ->
|
|
evalk e t2 (fun cv2 ->
|
|
let Clo (u1, e') = cv1 in
|
|
evalk (cv2 :: e') u1 k))
|
|
| Let (t1, t2) ->
|
|
evalk e t1 (fun cv1 ->
|
|
evalk (cv1 :: e) t2 k)
|
|
|
|
(* It is possible to explicitly assert that these calls are tail calls.
|
|
The compiler would tell us if we were wrong. *)
|
|
|
|
let rec evalk (e : cenv) (t : term) (k : cvalue -> 'a) : 'a =
|
|
match t with
|
|
| Var x ->
|
|
(k[@tailcall]) (lookup e x)
|
|
| Lam t ->
|
|
(k[@tailcall]) (Clo (t, e))
|
|
| App (t1, t2) ->
|
|
(evalk[@tailcall]) e t1 (fun cv1 ->
|
|
(evalk[@tailcall]) e t2 (fun cv2 ->
|
|
let Clo (u1, e') = cv1 in
|
|
(evalk[@tailcall]) (cv2 :: e') u1 k))
|
|
| Let (t1, t2) ->
|
|
(evalk[@tailcall]) e t1 (fun cv1 ->
|
|
(evalk[@tailcall]) (cv1 :: e) t2 k)
|
|
|
|
let eval (e : cenv) (t : term) : cvalue =
|
|
evalk e t (fun cv -> cv)
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Test. *)
|
|
|
|
let () =
|
|
test "the CPS evaluator" eval
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* The above code uses heap-allocated closures, which form a linked list in the
|
|
heap. In fact, the interpreter's "stack" is now heap-allocated. To see this
|
|
more clearly, let us defunctionalize the CPS-transformed interpreter. *)
|
|
|
|
(* There are four places in the above code where an anonymous continuation is
|
|
built, so defunctionalization yields a data type of four possible kinds of
|
|
continuations. This data type describes a linked list of stack frames! *)
|
|
|
|
type kont =
|
|
| AppL of { e: cenv; t2: term; k: kont }
|
|
| AppR of { cv1: cvalue; k: kont }
|
|
| LetL of { e: cenv; t2: term; k: kont }
|
|
| Init
|
|
|
|
let rec evalkd (e : cenv) (t : term) (k : kont) : cvalue =
|
|
match t with
|
|
| Var x ->
|
|
apply k (lookup e x)
|
|
| Lam t ->
|
|
apply k (Clo (t, e))
|
|
| App (t1, t2) ->
|
|
evalkd e t1 (AppL { e; t2; k })
|
|
| Let (t1, t2) ->
|
|
evalkd e t1 (LetL { e; t2; k })
|
|
|
|
and apply (k : kont) (cv : cvalue) : cvalue =
|
|
match k with
|
|
| AppL { e; t2; k } ->
|
|
let cv1 = cv in
|
|
evalkd e t2 (AppR { cv1; k })
|
|
| AppR { cv1; k } ->
|
|
let cv2 = cv in
|
|
let Clo (u1, e') = cv1 in
|
|
evalkd (cv2 :: e') u1 k
|
|
| LetL { e; t2; k } ->
|
|
let cv1 = cv in
|
|
evalkd (cv1 :: e) t2 k
|
|
| Init ->
|
|
cv
|
|
|
|
let eval e t =
|
|
evalkd e t Init
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Test. *)
|
|
|
|
let () =
|
|
test "the defunctionalized CPS evaluator" eval
|