Skip to content

Instantly share code, notes, and snippets.

@srijan-paul
Last active August 5, 2024 16:05
Show Gist options
  • Save srijan-paul/da9367aa4ae067af8b9a3d3c34ccc532 to your computer and use it in GitHub Desktop.
Save srijan-paul/da9367aa4ae067af8b9a3d3c34ccc532 to your computer and use it in GitHub Desktop.
Simple stack based interpreter.
const std = @import("std");
const Opcode = enum(u8) {
push,
add,
print,
exit,
eq, // pushes 1 if stack[-1] == stack[-2], 0 otherwise
jumpif_1,
jump,
load_var,
store_var,
};
const CodeBlock = struct {
instructions: []const u8,
constants: []const i64,
};
const Interpreter = struct {
stack: [32_000]i64 = undefined,
stack_pos: u64 = 0,
blocks: []const CodeBlock,
current_block: *const CodeBlock = undefined,
pc: usize = 0,
pub fn init(blocks: []const CodeBlock) Interpreter {
return .{
.blocks = blocks,
.current_block = &blocks[0],
};
}
inline fn loadConst(self: *Interpreter) i64 {
const index = self.current_block.instructions[self.pc];
const constant = self.current_block.constants[index];
self.pc += 1;
return constant;
}
inline fn top(self: *Interpreter) i64 {
return self.stack[self.stack_pos - 1];
}
inline fn pop(self: *Interpreter) i64 {
self.stack_pos -= 1;
return self.stack[self.stack_pos];
}
inline fn push(self: *Interpreter, value: i64) void {
self.stack[self.stack_pos] = value;
self.stack_pos += 1;
}
inline fn nextOp(self: *Interpreter) u8 {
const op = self.current_block.instructions[self.pc];
self.pc += 1;
return op;
}
pub fn run(self: *Interpreter) !void {
while (self.pc < self.current_block.instructions.len) {
const instr: Opcode = @enumFromInt(self.nextOp());
switch (instr) {
.push => self.push(self.loadConst()),
.add => {
const a = self.pop();
const b = self.pop();
self.push(a + b);
},
.print => {
std.debug.print("{d}\n", .{self.pop()});
},
.exit => {
const exit_code: u8 = @intCast(self.pop());
std.process.exit(exit_code);
},
.eq => {
const a = self.pop();
const b = self.pop();
self.push(if (a == b) 1 else 0);
},
.jump => self.jump(),
.jumpif_1 => {
if (self.pop() == 1) {
self.jump();
} else {
self.pc += 1;
}
},
.load_var => {
const index = self.nextOp();
self.push(self.stack[index]);
},
.store_var => {
// [value, index]
const index = self.nextOp();
self.stack[index] = self.pop();
},
}
}
}
fn jump(self: *Interpreter) void {
const block_idx = self.nextOp();
self.current_block = &self.blocks[block_idx];
self.pc = 0;
}
};
pub inline fn Op(o: Opcode) u8 {
return @intFromEnum(o);
}
pub fn main() !void {
// 0 is x, 1 is i
const b0 = CodeBlock{
.instructions = &[_]u8{
Op(.push), 0, // x = 0
Op(.push), 0, // i = 0
Op(.jump), 1, // jump to while loop (block 1
},
.constants = &[_]i64{0},
};
const b1 = CodeBlock{
.instructions = &[_]u8{
// while i < 1_000_000
Op(.load_var), 1, // load i
Op(.push), 0, // load 1_000_000
Op(.eq),
Op(.jumpif_1), 2, // jump to exit if i == 1_000_000
// x = x + 1
Op(.load_var), 0, // load x
Op(.load_var), 1, // load i
Op(.add),
Op(.store_var), 0, // x = x + i
// i += 1
Op(.load_var), 1, // load i
Op(.push), 1, // load 1
Op(.add),
Op(.store_var), 1, // i = i + 1
Op(.jump), 1, // jump to start
},
.constants = &[_]i64{ 1_00_0001, 1 },
};
const b2 = CodeBlock{
.instructions = &[_]u8{
Op(.load_var), 0, // load x
Op(.print),
Op(.push), 0, // load 0
Op(.exit),
},
.constants = &[_]i64{0},
};
const program = [_]CodeBlock{ b0, b1, b2 };
var interpreter = Interpreter.init(&program);
try interpreter.run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment