commit 89741115adeb2ccad04f08b4776b2fccec67584b
parent 7461681defecd82d22b351663ba98c759f1abdef
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date: Mon, 22 Apr 2024 21:27:04 +0200
Defining global vars
Diffstat:
7 files changed, 278 insertions(+), 194 deletions(-)
diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig
@@ -19,6 +19,9 @@ pub const OP = enum(u8) {
MULTIPLY,
DIVIDE,
NOT,
+ PRINT,
+ POP,
+ DEFINE_GLOBAL,
};
pub const Chunk = struct {
diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig
@@ -5,6 +5,7 @@ const OP = @import("chunk.zig").OP;
const Value = @import("value.zig").Value;
const Obj = @import("obj.zig").Obj;
const debug = @import("debug.zig");
+const Token = scanner.TokenType;
pub const CompilerError = Obj.Error || scanner.ScannerError || Chunk.Error || Value.ParseNumberError || error{ UnexpectedToken, NotAnExpression };
@@ -40,18 +41,11 @@ pub const Compiler = struct {
panicMode: bool,
compilingChunk: Chunk,
allocator: std.mem.Allocator,
- objects: Obj.List,
-
- pub const Result = struct {
- chunk: Chunk,
- objects: Obj.List,
- pub fn deinit(self: *@This()) void {
- self.objects.deinit();
- self.chunk.deinit();
- }
- };
+ objects: *Obj.List,
- const ParseFn = *const fn (*@This()) void;
+ const Self = @This();
+
+ const ParseFn = *const fn (*Self) void;
const ParseRule = struct {
prefix: ?ParseFn,
@@ -63,13 +57,13 @@ pub const Compiler = struct {
};
const rules = init: {
- var new: [@typeInfo(scanner.TokenType).Enum.fields.len]ParseRule = undefined;
+ var new: [@typeInfo(Token).Enum.fields.len]ParseRule = undefined;
for (&new, 0..) |*v, i| {
- const T = scanner.TokenType;
- const S = @This();
+ const T = Token;
+ const S = Self;
const R = ParseRule.init;
const P = Precedence;
- const tok: T = @enumFromInt(i);
+ const tok: Token = @enumFromInt(i);
v.* = switch (tok) {
// zig fmt: off
T.LEFT_PAREN => R(S.grouping, null, P.NONE ),
@@ -119,11 +113,11 @@ pub const Compiler = struct {
break :init new;
};
- fn getRule(tok: scanner.TokenType) *const ParseRule {
+ fn getRule(tok: Token) *const ParseRule {
return &Compiler.rules[@intFromEnum(tok)];
}
- fn advance(self: *@This()) void {
+ fn advance(self: *Self) void {
self.previous = self.current;
while (true) {
@@ -138,60 +132,55 @@ pub const Compiler = struct {
}
}
- fn currentChunk(self: *@This()) *Chunk {
+ fn currentChunk(self: *Self) *Chunk {
return &self.compilingChunk;
}
- fn emitByte(self: *@This(), byte: u8) void {
+ fn emitByte(self: *Self, byte: u8) void {
self.currentChunk().write(byte, self.previous.line) catch |err| {
self.lastError = err;
self.errorAtCurrent("Out of Memory");
};
}
- fn emitOP(self: *@This(), op: OP) void {
+ fn emitOP(self: *Self, op: OP) void {
self.currentChunk().writeOP(op, self.previous.line) catch |err| {
self.lastError = err;
self.errorAtCurrent("Out of Memory");
};
}
- fn emit(self: *@This(), op: OP, byte: u8) void {
+ fn emit(self: *Self, op: OP, byte: u8) void {
self.emitOP(op);
self.emitByte(byte);
}
- fn emit2OP(self: *@This(), op: OP, op2: OP) void {
+ fn emit2OP(self: *Self, op: OP, op2: OP) void {
self.emitOP(op);
self.emitOP(op2);
}
- fn endCompiler(self: *@This()) void {
+ fn endCompiler(self: *Self) void {
self.emitReturn();
- if (!self.hadError) {
- debug.disassembleChunk(self.currentChunk().*, "code") catch {
- std.debug.print("Unable to disassemble chunk\n", .{});
- };
- }
}
- fn emitReturn(self: *@This()) void {
+ fn emitReturn(self: *Self) void {
self.emitOP(OP.RETURN);
}
- fn errorAtCurrent(self: *@This(), message: []const u8) void {
+ fn errorAtCurrent(self: *Self, message: []const u8) void {
self.errorAt(self.current, message);
}
- fn errorAtPrevious(self: *@This(), message: []const u8) void {
+ fn errorAtPrevious(self: *Self, message: []const u8) void {
self.errorAt(self.previous, message);
}
- fn expression(self: *@This()) void {
+ fn expression(self: *Self) void {
self.parsePrecedence(Precedence.ASSIGNMENT);
}
- fn parsePrecedence(self: *@This(), precedence: Precedence) void {
+ fn parsePrecedence(self: *Self, precedence: Precedence) void {
self.advance();
if (getRule(self.previous.type catch unreachable).prefix) |prefixRule| {
prefixRule(self);
@@ -213,12 +202,12 @@ pub const Compiler = struct {
}
}
- fn errorAt(self: *@This(), token: scanner.Token, message: []const u8) void {
+ fn errorAt(self: *Self, token: scanner.Token, message: []const u8) void {
if (self.panicMode) return;
self.panicMode = true;
std.debug.print("[{d}:{d}] Error", .{ token.line, token.column });
if (token.type) |tpe| {
- if (tpe == scanner.TokenType.EOF) {
+ if (tpe == Token.EOF) {
std.debug.print(" at end", .{});
} else {
std.debug.print(" at {s}", .{token.lexeme});
@@ -228,7 +217,7 @@ pub const Compiler = struct {
self.hadError = true;
}
- fn consume(self: *@This(), tok: scanner.TokenType, message: []const u8) void {
+ fn consume(self: *Self, tok: Token, message: []const u8) void {
if (self.current.type) |tpe| {
if (tpe == tok) {
self.advance();
@@ -239,7 +228,7 @@ pub const Compiler = struct {
self.errorAtCurrent(message);
}
- fn number(self: *@This()) void {
+ fn number(self: *Self) void {
self.emitConstant(Value.parseNumber(self.previous.lexeme) catch |err| {
self.lastError = err;
self.errorAtPrevious("Invalid numeric literal");
@@ -247,7 +236,7 @@ pub const Compiler = struct {
});
}
- fn string(self: *@This()) void {
+ fn string(self: *Self) void {
self.emitConstant(Value.init(self.objects.emplace(.String, &.{self.previous.lexeme[1 .. self.previous.lexeme.len - 1]}) catch |err| {
self.lastError = err;
self.errorAtPrevious("Couldn't allocate object");
@@ -255,11 +244,11 @@ pub const Compiler = struct {
}));
}
- fn emitConstant(self: *@This(), val: Value) void {
+ fn emitConstant(self: *Self, val: Value) void {
self.emit(OP.CONSTANT, self.makeConstant(val));
}
- fn makeConstant(self: *@This(), val: Value) u8 {
+ fn makeConstant(self: *Self, val: Value) u8 {
return self.currentChunk().addConstant(val) catch |err| {
self.lastError = err;
self.errorAtPrevious("Too many constants in one chunk");
@@ -267,74 +256,169 @@ pub const Compiler = struct {
};
}
- fn grouping(self: *@This()) void {
+ fn grouping(self: *Self) void {
self.expression();
- self.consume(scanner.TokenType.RIGHT_PAREN, "Expected ')' after expression");
+ self.consume(Token.RIGHT_PAREN, "Expected ')' after expression");
}
- fn unary(self: *@This()) void {
+ fn unary(self: *Self) void {
const operatorType = self.previous.type catch unreachable;
self.parsePrecedence(Precedence.UNARY);
switch (operatorType) {
- scanner.TokenType.MINUS => self.emitOP(OP.NEGATE),
- scanner.TokenType.BANG => self.emitOP(OP.NOT),
+ Token.MINUS => self.emitOP(OP.NEGATE),
+ Token.BANG => self.emitOP(OP.NOT),
else => unreachable,
}
}
- fn literal(self: *@This()) void {
+ fn literal(self: *Self) void {
switch (self.previous.type catch unreachable) {
- scanner.TokenType.FALSE => self.emitOP(OP.FALSE),
- scanner.TokenType.TRUE => self.emitOP(OP.TRUE),
- scanner.TokenType.NIL => self.emitOP(OP.NIL),
+ Token.FALSE => self.emitOP(OP.FALSE),
+ Token.TRUE => self.emitOP(OP.TRUE),
+ Token.NIL => self.emitOP(OP.NIL),
else => unreachable,
}
}
- fn binary(self: *@This()) void {
+ fn binary(self: *Self) void {
const operatorType = self.previous.type catch unreachable;
self.parsePrecedence(getRule(operatorType).precedence.inc());
switch (operatorType) {
- scanner.TokenType.PLUS => self.emitOP(OP.ADD),
- scanner.TokenType.MINUS => self.emitOP(OP.SUBTRACT),
- scanner.TokenType.STAR => self.emitOP(OP.MULTIPLY),
- scanner.TokenType.SLASH => self.emitOP(OP.DIVIDE),
- scanner.TokenType.BANG_EQUAL => self.emit2OP(OP.EQUAL, OP.NOT),
- scanner.TokenType.EQUAL_EQUAL => self.emitOP(OP.EQUAL),
- scanner.TokenType.GREATER => self.emitOP(OP.GREATER),
- scanner.TokenType.GREATER_EQUAL => self.emit2OP(OP.LESS, OP.NOT),
- scanner.TokenType.LESS => self.emitOP(OP.LESS),
- scanner.TokenType.LESS_EQUAL => self.emit2OP(OP.GREATER, OP.NOT),
+ Token.PLUS => self.emitOP(OP.ADD),
+ Token.MINUS => self.emitOP(OP.SUBTRACT),
+ Token.STAR => self.emitOP(OP.MULTIPLY),
+ Token.SLASH => self.emitOP(OP.DIVIDE),
+ Token.BANG_EQUAL => self.emit2OP(OP.EQUAL, OP.NOT),
+ Token.EQUAL_EQUAL => self.emitOP(OP.EQUAL),
+ Token.GREATER => self.emitOP(OP.GREATER),
+ Token.GREATER_EQUAL => self.emit2OP(OP.LESS, OP.NOT),
+ Token.LESS => self.emitOP(OP.LESS),
+ Token.LESS_EQUAL => self.emit2OP(OP.GREATER, OP.NOT),
else => unreachable,
}
}
- fn ternary(self: *@This()) void {
+ fn ternary(self: *Self) void {
const operatorType = self.previous.type catch unreachable;
self.parsePrecedence(getRule(operatorType).precedence.inc());
// emit bytecode
- self.consume(scanner.TokenType.COLON, "Expected ':' in ternary expression.");
+ self.consume(Token.COLON, "Expected ':' in ternary expression.");
self.parsePrecedence(getRule(operatorType).precedence.inc());
// emit bytecode
}
- pub fn compile(source: []const u8, allocator: std.mem.Allocator) CompilerError!Result {
- var self = @This(){ .scanner = try scanner.Scanner.init(source), .current = scanner.Token.Empty, .previous = scanner.Token.Empty, .panicMode = false, .hadError = false, .lastError = scanner.ScannerError.EmptyToken, .compilingChunk = try Chunk.init(allocator), .allocator = allocator, .objects = Obj.List.init(allocator) };
- errdefer self.compilingChunk.deinit();
- errdefer self.objects.deinit();
+ fn declaration(self: *Self) void {
+ if (self.match(Token.VAR)) {
+ self.varDeclaration();
+ } else {
+ self.statement();
+ }
+
+ if (self.panicMode) self.synchronize();
+ }
+ fn varDeclaration(self: *Self) void {
+ const global = self.parseVariable("Expect variable name.") catch |err| {
+ self.lastError = err;
+ self.errorAtPrevious("Couldn't create variable");
+ return;
+ };
+
+ if (self.match(Token.EQUAL)) {
+ self.expression();
+ } else {
+ self.emitOP(OP.NIL);
+ }
+
+ self.consume(Token.SEMICOLON, "Expect ';' after variable declaration.");
+
+ self.defineVariable(global);
+ }
+
+ fn parseVariable(self: *Self, errorMessage: []const u8) !u8 {
+ self.consume(Token.IDENTIFIER, errorMessage);
+ return try self.identifierConstant(self.previous);
+ }
+
+ fn identifierConstant(self: *Self, tok: scanner.Token) !u8 {
+ return self.makeConstant(Value.init(try self.objects.emplace(.String, &.{tok.lexeme})));
+ }
+
+ fn defineVariable(self: *Self, global: u8) void {
+ self.emit(OP.DEFINE_GLOBAL, global);
+ }
+
+ fn synchronize(self: *Self) void {
+ self.panicMode = false;
+
+ while ((self.current.type catch Token.NIL) != Token.EOF) {
+ if ((self.previous.type catch Token.NIL) == Token.SEMICOLON) return;
+ switch (self.current.type catch Token.NIL) {
+ Token.CLASS, Token.FUN, Token.VAR, Token.IF, Token.FOR, Token.WHILE, Token.PRINT, Token.RETURN => return,
+ else => self.advance(),
+ }
+ }
+ }
+
+ fn statement(self: *Self) void {
+ if (self.match(Token.PRINT)) {
+ self.printStatement();
+ } else {
+ self.expressionStatement();
+ }
+ }
+
+ fn expressionStatement(self: *Self) void {
+ self.expression();
+ self.consume(Token.SEMICOLON, "Expect ';' after expression.");
+ self.emitOP(OP.POP);
+ }
+
+ fn match(self: *Self, token: Token) bool {
+ if (!self.check(token)) return false;
self.advance();
+ return true;
+ }
+
+ fn check(self: *const Self, token: Token) bool {
+ return if (self.current.type) |tp| tp == token else |_| false;
+ }
+
+ fn printStatement(self: *Self) void {
self.expression();
- self.consume(scanner.TokenType.EOF, "Expected end of expression.");
+ self.consume(Token.SEMICOLON, "Expect ';' after value.");
+ self.emitOP(OP.PRINT);
+ }
+
+ pub fn compile(source: []const u8, objects: *Obj.List, allocator: std.mem.Allocator) CompilerError!Chunk {
+ // zig fmt: off
+ var self = Self{
+ .scanner = try scanner.Scanner.init(source),
+ .current = scanner.Token.Empty,
+ .previous = scanner.Token.Empty,
+ .panicMode = false,
+ .hadError = false,
+ .lastError = scanner.ScannerError.EmptyToken,
+ .compilingChunk = try Chunk.init(allocator),
+ .allocator = allocator,
+ .objects = objects
+ };
+ // zig fmt: on
+ errdefer self.compilingChunk.deinit();
+
+ self.advance();
+ while (!self.match(Token.EOF)) {
+ self.declaration();
+ }
self.endCompiler();
- return if (self.hadError) self.lastError else Result{ .chunk = self.compilingChunk, .objects = self.objects };
+ return if (self.hadError) self.lastError else self.compilingChunk;
}
};
diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig
@@ -37,6 +37,9 @@ pub fn disassembleInstruction(ch: chunk.Chunk, offset: usize) !usize {
@intFromEnum(OP.NIL) => simpleInstruction("OP_NIL", offset),
@intFromEnum(OP.NOT) => simpleInstruction("OP_NOT", offset),
@intFromEnum(OP.CONSTANT) => try constantInstruction("OP_CONSTANT", ch, offset),
+ @intFromEnum(OP.DEFINE_GLOBAL) => try constantInstruction("OP_DEFINE_GLOBAL", ch, offset),
+ @intFromEnum(OP.PRINT) => simpleInstruction("OP_PRINT", offset),
+ @intFromEnum(OP.POP) => simpleInstruction("OP_POP", offset),
else => blk: {
print("Unknown opcode {}\n", .{try ch.code.get(offset)});
break :blk offset + 1;
diff --git a/zlox/src/main.zig b/zlox/src/main.zig
@@ -35,14 +35,14 @@ pub fn runFile(allocator: std.mem.Allocator, path: []const u8) anyerror!void {
}
pub fn repl(allocator: std.mem.Allocator) anyerror!void {
- var VM = vm.VM.init();
+ var VM = vm.VM.init(allocator);
defer VM.deinit();
_ = Linenoise.linenoiseHistorySetMaxLen(100);
while (Linenoise.linenoise("lox> ")) |line| {
defer Linenoise.linenoiseFree(line);
- VM.interpret(std.mem.span(line), allocator) catch |err| {
+ VM.interpret(std.mem.span(line)) catch |err| {
std.debug.print("Error: {}\n", .{err});
};
_ = Linenoise.linenoiseHistoryAdd(line);
diff --git a/zlox/src/obj.zig b/zlox/src/obj.zig
@@ -63,7 +63,7 @@ pub const Obj = packed struct {
pub const Arg = []const []const u8;
obj: Super,
- len: usize,
+ len: usize = 0,
hash: u32,
fn data(self: anytype) utils.copy_const(@TypeOf(self), [*]u8) {
@@ -76,7 +76,6 @@ pub const Obj = packed struct {
.obj = Super{
.type = Super.Type.String,
},
- .len = 0,
.hash = params.hash,
};
for (arg) |el| {
@@ -142,8 +141,8 @@ pub const Obj = packed struct {
return entry.some.key;
}
- pub fn init(arg: Arg, allocator: std.mem.Allocator) Error!*Self {
- return try new(arg, arg_params(arg), allocator);
+ pub fn init(_: Arg, _: std.mem.Allocator) Error!*Self {
+ @compileError("The String Obj has to be interned");
}
fn free(self: *const Self, allocator: std.mem.Allocator) void {
diff --git a/zlox/src/table.zig b/zlox/src/table.zig
@@ -111,7 +111,7 @@ pub fn Table(K: type, V: type, hash_fn: fn (K) u32, cmp_fn: fn (K, K) bool) type
pub fn set(self: *Self, key: K, val: V) TableError!bool {
try self.checkCapacity();
- return set_(find(self.entries, key), key, val);
+ return self.set_(find(self.entries, key), key, val);
}
pub fn get(self: *const Self, key: K) TableError!V {
diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig
@@ -6,148 +6,143 @@ const debug = @import("debug.zig");
const compiler = @import("compiler.zig");
const Obj = @import("obj.zig").Obj;
const Callback = @import("vm_callbacks.zig");
+const table = @import("table.zig");
+const hash = @import("hash.zig");
pub const InterpreterError = compiler.CompilerError || Callback.Error || error{ CompileError, RuntimeError, IndexOutOfBounds, Overflow, DivisionByZero };
pub const VM = struct {
- ip: [*]const u8,
- chunk: *Chunk,
- objects: *Obj.List,
- stack: stackType,
- stackTop: [*]Value,
-
- const stackSize = 256;
- const stackType = [stackSize]Value;
-
- pub fn init() @This() {
- var ret = @This(){
- .ip = undefined,
- .chunk = undefined,
- .objects = undefined,
- .stack = [_]Value{Value.init({})} ** stackSize,
- .stackTop = undefined,
- };
- ret.stackTop = &ret.stack;
- return ret;
- }
-
- pub fn interpretChunk(self: *@This(), chunk: *Chunk) InterpreterError!void {
- self.resetStack();
- self.chunk = chunk;
- self.ip = chunk.code.data.ptr;
- try self.run(true);
- }
+ objects: Obj.List,
+ globals: Globals,
+ allocator: std.mem.Allocator,
- pub fn interpret(self: *@This(), source: []const u8, allocator: std.mem.Allocator) InterpreterError!void {
- var result = try compiler.Compiler.compile(source, allocator);
- defer result.deinit();
- self.objects = &result.objects;
+ const Globals = table.Table(*const Obj.String, Value, hash.hash_t(*const Obj.String), Obj.String.eql);
- try self.interpretChunk(&result.chunk);
- self.resetStack();
+ pub fn init(allocator: std.mem.Allocator) @This() {
+ return @This(){ .globals = Globals.init(allocator), .objects = Obj.List.init(allocator), .allocator = allocator };
}
- fn resetStack(self: *@This()) void {
- self.stackTop = &self.stack;
- }
+ pub fn interpret(self: *@This(), source: []const u8) InterpreterError!void {
+ var chunk = try compiler.Compiler.compile(source, &self.objects, self.allocator);
+ defer chunk.deinit();
- fn read_byte(self: *@This()) u8 {
- const out: u8 = self.ip[0];
- self.ip += 1;
- return out;
+ try Interpreter(256).run(self, &chunk);
}
- fn read_constant(self: *@This()) Value {
- return self.chunk.constants.data[self.read_byte()];
- }
+ fn Interpreter(size: comptime_int) type {
+ return struct {
+ ip: [*]const u8,
+ chunk: *Chunk,
+ stackTop: [*]Value,
+ stack: [size]Value,
+ vm: *VM,
+
+ pub fn run(vm: *VM, chunk: *Chunk) InterpreterError!void {
+ var self = @This(){ .ip = chunk.code.data.ptr, .chunk = chunk, .stack = [_]Value{Value.init({})} ** size, .stackTop = undefined, .vm = vm };
+ self.stackTop = &self.stack;
+ try self.execute(false);
+ }
- fn push(self: *@This(), val: Value) void {
- self.stackTop[0] = val;
- self.stackTop += 1;
- }
+ fn read_byte(self: *@This()) u8 {
+ const out: u8 = self.ip[0];
+ self.ip += 1;
+ return out;
+ }
- pub fn pop(self: *@This()) Value {
- self.stackTop -= 1;
- return self.stackTop[0];
- }
+ fn read_constant(self: *@This()) Value {
+ return self.chunk.constants.data[self.read_byte()];
+ }
- pub fn peek(self: *@This(), distance: usize) Value {
- return (self.stackTop - (1 + distance))[0];
- }
+ fn push(self: *@This(), val: Value) void {
+ self.stackTop[0] = val;
+ self.stackTop += 1;
+ }
- fn binary_op(self: *@This(), comptime in_tag: anytype, comptime out_tag: anytype, op: Callback.Type(in_tag, out_tag)) InterpreterError!void {
- const b = self.pop();
- const a = self.pop();
- if (a.is(in_tag) and b.is(in_tag)) {
- self.push(Value.init(try op.call(a.get(in_tag), b.get(in_tag))));
- } else {
- self.runtimeError("Operands have invalid types, expected: {s}", .{@tagName(in_tag)});
- return InterpreterError.RuntimeError;
- }
- }
+ fn pop(self: *@This()) Value {
+ self.stackTop -= 1;
+ return self.stackTop[0];
+ }
- fn instruction_idx(self: *const @This()) usize {
- return @intFromPtr(self.ip) - @intFromPtr(self.chunk.code.data.ptr);
- }
+ fn peek(self: *@This(), distance: usize) Value {
+ return (self.stackTop - (1 + distance))[0];
+ }
- fn run(self: *@This(), comptime dbg: bool) !void {
- while (true) {
- if (dbg) {
- std.debug.print(" ", .{});
- var stackPtr: [*]Value = &self.stack;
- while (stackPtr != self.stackTop) : (stackPtr += 1) {
- std.debug.print("[ ", .{});
- stackPtr[0].print();
- std.debug.print(" ]", .{});
+ fn binary_op(self: *@This(), comptime in_tag: anytype, comptime out_tag: anytype, op: Callback.Type(in_tag, out_tag)) InterpreterError!void {
+ const b = self.pop();
+ const a = self.pop();
+ if (a.is(in_tag) and b.is(in_tag)) {
+ self.push(Value.init(try op.call(a.get(in_tag), b.get(in_tag))));
+ } else {
+ self.runtimeError("Operands have invalid types, expected: {s}", .{@tagName(in_tag)});
+ return InterpreterError.RuntimeError;
}
- std.debug.print("\n", .{});
}
- _ = try debug.disassembleInstruction(self.chunk.*, self.instruction_idx());
- const instruction: u8 = self.read_byte();
- switch (instruction) {
- @intFromEnum(OP.RETURN) => {
- self.pop().print();
- std.debug.print("\n", .{});
- return;
- },
- @intFromEnum(OP.CONSTANT) => {
- const constant = self.read_constant();
- self.push(constant);
- },
- @intFromEnum(OP.NEGATE) => {
- if (!self.peek(0).is(Value.number)) {
- self.runtimeError("Operand must be a number.", .{});
- return InterpreterError.RuntimeError;
+
+ fn instruction_idx(self: *const @This()) usize {
+ return @intFromPtr(self.ip) - @intFromPtr(self.chunk.code.data.ptr);
+ }
+
+ fn execute(self: *@This(), comptime dbg: bool) !void {
+ while (true) {
+ if (dbg) {
+ std.debug.print(" ", .{});
+ var stackPtr: [*]Value = &self.stack;
+ while (stackPtr != self.stackTop) : (stackPtr += 1) {
+ std.debug.print("[ ", .{});
+ stackPtr[0].print();
+ std.debug.print(" ]", .{});
+ }
+ std.debug.print("\n", .{});
+ _ = try debug.disassembleInstruction(self.chunk.*, self.instruction_idx());
}
- self.push(Value.init(-self.pop().number));
- },
- @intFromEnum(OP.ADD) => {
- if (self.peek(0).is(Obj.Type.String)) {
- try self.binary_op(Obj.Type.String, Obj.Type.String, Callback.concatenate(self.objects));
- } else {
- try self.binary_op(Value.number, Value.number, Callback.add);
+ const instruction: u8 = self.read_byte();
+ switch (instruction) {
+ @intFromEnum(OP.PRINT) => {
+ self.pop().print();
+ std.debug.print("\n", .{});
+ },
+ @intFromEnum(OP.RETURN) => return,
+ @intFromEnum(OP.POP) => _ = self.pop(),
+ @intFromEnum(OP.CONSTANT) => self.push(self.read_constant()),
+ @intFromEnum(OP.NEGATE) => {
+ if (!self.peek(0).is(Value.number)) {
+ self.runtimeError("Operand must be a number.", .{});
+ return InterpreterError.RuntimeError;
+ }
+ self.push(Value.init(-self.pop().number));
+ },
+ @intFromEnum(OP.ADD) => {
+ if (self.peek(0).is(Obj.Type.String)) {
+ try self.binary_op(Obj.Type.String, Obj.Type.String, Callback.concatenate(&self.vm.objects));
+ } else {
+ try self.binary_op(Value.number, Value.number, Callback.add);
+ }
+ },
+ @intFromEnum(OP.DEFINE_GLOBAL) => _ = try self.vm.globals.set(self.read_constant().obj.cast(.String) catch unreachable, self.pop()),
+ @intFromEnum(OP.SUBTRACT) => try self.binary_op(Value.number, Value.number, Callback.sub),
+ @intFromEnum(OP.MULTIPLY) => try self.binary_op(Value.number, Value.number, Callback.mul),
+ @intFromEnum(OP.DIVIDE) => try self.binary_op(Value.number, Value.number, Callback.div),
+ @intFromEnum(OP.TRUE) => self.push(Value.init(true)),
+ @intFromEnum(OP.FALSE) => self.push(Value.init(false)),
+ @intFromEnum(OP.EQUAL) => self.push(Value.init(self.pop().eql(self.pop()))),
+ @intFromEnum(OP.LESS) => try self.binary_op(Value.number, Value.bool, Callback.less),
+ @intFromEnum(OP.GREATER) => try self.binary_op(Value.number, Value.bool, Callback.more),
+ @intFromEnum(OP.NIL) => self.push(Value.init({})),
+ @intFromEnum(OP.NOT) => self.push(Value.init(!self.pop().isTruthy())),
+ else => return InterpreterError.CompileError,
}
- },
- @intFromEnum(OP.SUBTRACT) => try self.binary_op(Value.number, Value.number, Callback.sub),
- @intFromEnum(OP.MULTIPLY) => try self.binary_op(Value.number, Value.number, Callback.mul),
- @intFromEnum(OP.DIVIDE) => try self.binary_op(Value.number, Value.number, Callback.div),
- @intFromEnum(OP.TRUE) => self.push(Value.init(true)),
- @intFromEnum(OP.FALSE) => self.push(Value.init(false)),
- @intFromEnum(OP.EQUAL) => self.push(Value.init(self.pop().eql(self.pop()))),
- @intFromEnum(OP.LESS) => try self.binary_op(Value.number, Value.bool, Callback.less),
- @intFromEnum(OP.GREATER) => try self.binary_op(Value.number, Value.bool, Callback.more),
- @intFromEnum(OP.NIL) => self.push(Value.init({})),
- @intFromEnum(OP.NOT) => self.push(Value.init(!self.pop().isTruthy())),
- else => return InterpreterError.CompileError,
+ }
}
- }
- }
- fn runtimeError(self: *@This(), comptime fmt: []const u8, args: anytype) void {
- std.debug.print(fmt, args);
- std.debug.print("\n[line {d}] in script\n", .{self.chunk.lines.get(self.instruction_idx()) catch 0});
- self.resetStack();
+ fn runtimeError(self: *@This(), comptime fmt: []const u8, args: anytype) void {
+ std.debug.print(fmt, args);
+ std.debug.print("\n[line {d}] in script\n", .{self.chunk.lines.get(self.instruction_idx()) catch 0});
+ }
+ };
}
- pub fn deinit(_: *@This()) void {}
+ pub fn deinit(self: *@This()) void {
+ self.objects.deinit();
+ self.globals.deinit();
+ }
};