Explore Lambda Calculus - Part II
This is the second part that shows the OCaml port of F# code that implements a stack-based Virtual Machine. The functors turned out to be very different from the structure of F# code.
I believe a VM like this can be enhanced further. I am on the look out for good resources to build a better Virtual Machine.
open Scanner__Lang1.Language
type int_binary_op =
|Add|Sub|Mul|Div|Less|Greater|Equal
[@@deriving show]
type int_unary_op = Neg
[@@deriving show]
type instr = Halt | IntConst of int | IntBinaryOp of int_binary_op
| IntUnaryOp of int_unary_op
[@@deriving show]
(* TODO Unused *)
module type IntBinaryOp = sig
type t
val fromArithmeticFn : Scanner__Lang1.arithmetic_fn -> t
val fromComparisonFn : Scanner__Lang1.comparison_fn -> t
end
module type ByteCodeGen =
sig
val generate : expr -> instr list -> instr list
val emit_instr: instr -> instr list -> instr list
end
module Op = struct
let fromArithmeticFn (fn : Scanner__Lang1.arithmetic_fn) =
match fn with
| Add -> IntBinaryOp Add
| Sub -> IntBinaryOp Sub
| Div -> IntBinaryOp Div
| Mul -> IntBinaryOp Mul
let fromComparisonFn (fn : Scanner__Lang1.comparison_fn) =
match fn with
| Less -> IntBinaryOp Less
| Greater -> IntBinaryOp Greater
| Equal -> IntBinaryOp Equal
end
module Value = struct
type value =
| VInt of int
| BlackHole
[@@deriving show]
type eval_error = WrongType of value * string
exception EvalException of eval_error
let asInt = function
| VInt i-> i
| other -> raise( EvalException(WrongType( other, "int" )) )
end
module type Operation =
sig
val execute : instr list -> unit
val current_value : Value.value option
end
module VM = struct
let bc_stack : Value.value Stack.t = Stack.create()
let instructions = []
module VMOperation
( Byte : ByteCodeGen )
( Oper : Operation)= struct
let eval expr =
let instr_list = Byte.generate expr instructions in
(* List.iter( fun instr -> Printf.printf "%s\n" (Format.asprintf "%a" pp_instr instr )) instr_list; *)
Oper.execute instr_list;
Printf.printf "%s" (Format.asprintf "%a\n" Value.pp_value
(Stack.top bc_stack));
end
module VMOp =
VMOperation(struct
let emit_instr instr instructions : instr list=
(* instructions @ [(Format.asprintf "%a" pp_instr (instr))] *)
instructions @ [instr]
let rec generate expr instructions =
(*Unsure how the missing cases are handled*)
match expr with
| Lit (BInt i) -> emit_instr ( IntConst i ) instructions
| Builtin ( Arithmetic( fn, opA, opB ) ) ->
let instructions = (generate opA instructions) in
let instructions = (generate opB instructions) in
let op_type = Op.fromArithmeticFn fn in
emit_instr op_type instructions
| Builtin ( Comparison( fn, lhs, rhs) ) ->
let instructions = (generate lhs instructions) in
let instructions =(generate rhs instructions) in
let op_type = Op.fromComparisonFn fn in
emit_instr op_type instructions
| Builtin ( UnaryArithmetic( fn, expr) ) ->
let instructions = (generate expr instructions) in
match fn with
| Neg -> emit_instr (IntUnaryOp Neg) instructions
| _ -> failwith "TO Investigate"
end)
(struct
let execute instructions : unit=
let halt = ref false in
let rec loop cp =
if (cp < (List.length instructions) && (!halt <> true)) then
let instr = List.nth instructions cp in
(* Printf.printf "Pointer is %d Length of instructions %d \n" cp (List.length instructions); *)
(* List.iter( fun instr -> Printf.printf "Instruction %s\n" (Format.asprintf "%a" pp_instr instr )) instructions; *)
match instr with
| Halt -> halt := true
| IntConst i -> Stack.push (Value.VInt i) bc_stack;
loop (cp + 1);
| IntBinaryOp op ->
let arg2 = Stack.pop bc_stack |> Value.asInt in
let arg1 = Stack.pop bc_stack |> Value.asInt in
let result =
(match op with
| Add -> arg1 + arg2
| Sub -> arg1 - arg2
| Div -> arg1 / arg2
| Mul -> arg1 * arg2
| Less -> if arg1 < arg2 then 1 else 0
| Greater -> if arg1 > arg2 then 1 else 0
| Equal -> if arg1 == arg2 then 1 else 0)
in
Stack.push (Value.VInt result) bc_stack;
loop (cp + 1);
| IntUnaryOp op ->
let arg = Stack.pop bc_stack |> Value.asInt in
Printf.printf "Popping %d\n" arg;
let result =
(match op with
| Neg -> -arg ) in
Stack.push (Value.VInt result) bc_stack;
loop (cp + 1);
in
loop 0
let current_value =
match (Stack.is_empty bc_stack) with
| false -> Some (Stack.top bc_stack);
| true -> None
end)
end
Test
The second test shows the Add operator in action.
let%expect_test _=
eval (Lit (BInt 5));
[%expect {| (ByteCode.Value.VInt 5) |}]
let%expect_test _=
eval (Builtin (Arithmetic (Add, (Lit( BInt( 1 ))), Lit( BInt 55))));
[%expect {| (ByteCode.Value.VInt 56) |}]
Written on May 23, 2025