DzLox

:)
git clone https://git.sr.ht/~ashymad/DzLox
Log | Files | Refs | Submodules | LICENSE

commit 89741115adeb2ccad04f08b4776b2fccec67584b
parent 7461681defecd82d22b351663ba98c759f1abdef
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date:   Mon, 22 Apr 2024 21:27:04 +0200

Defining global vars

Diffstat:
Mzlox/src/chunk.zig | 3+++
Mzlox/src/compiler.zig | 220++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mzlox/src/debug.zig | 3+++
Mzlox/src/main.zig | 4++--
Mzlox/src/obj.zig | 7+++----
Mzlox/src/table.zig | 2+-
Mzlox/src/vm.zig | 233+++++++++++++++++++++++++++++++++++++++----------------------------------------
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(); + } };