DzLox

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

commit 543eaa45c9a95066b86c94fe159571bcd3532d7b
parent c3997b2ea200e7bcc8ad818ee841a9cb641a950f
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date:   Sun,  4 Aug 2024 19:20:56 +0200

hash-based switch()

Diffstat:
Mzlox/src/chunk.zig | 1+
Mzlox/src/compiler.zig | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mzlox/src/debug.zig | 1+
Mzlox/src/obj.zig | 10++++++++--
Mzlox/src/scanner.zig | 4++++
Mzlox/src/table.zig | 6+++---
Mzlox/src/vm.zig | 16++++++++++------
7 files changed, 100 insertions(+), 15 deletions(-)

diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig @@ -29,6 +29,7 @@ pub const OP = enum(u8) { SET_LOCAL, JUMP_IF_FALSE, JUMP, + JUMP_POP, LOOP, SET_INDEX, GET_INDEX, diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -43,7 +43,6 @@ pub fn Compiler(size: comptime_int) type { hadError: bool, panicMode: bool, compilingChunk: Chunk, - allocator: std.mem.Allocator, objects: *Obj.List, locals: [size]Local, localCount: usize, @@ -123,6 +122,8 @@ pub fn Compiler(size: comptime_int) type { T.CON => R(null, null, P.NONE ), T.WHILE => R(null, null, P.NONE ), T.SWITCH => R(null, null, P.NONE ), + T.CASE => R(null, null, P.NONE ), + T.DEFAULT => R(null, null, P.NONE ), T.EOF => R(null, null, P.NONE ), // zig fmt: on }; @@ -310,7 +311,15 @@ pub fn Compiler(size: comptime_int) type { while (!self.match(Token.RIGHT_BRACKET)) { const key = try self.parseLiteralValue(); self.consume(Token.COLON, "Expect ':' after key in map initalizer"); - try retmap.set(key, try self.parseLiteralValue()); + const val = try self.parseLiteralValue(); + if (val.is(Value.nil)) { + self.errorAtPrevious("Nil cannot be stored in a map"); + return error.UnexpectedToken; + } + if (!try retmap.set(key, val)) { + self.errorAtPrevious("Duplicate key in map literal"); + return error.UnexpectedToken; + } if (self.match(Token.RIGHT_BRACKET)) break; self.consume(Token.COMMA, "Expect ',' after value in map initalizer"); @@ -603,9 +612,70 @@ pub fn Compiler(size: comptime_int) type { fn switchStatement(self: *Self) void { self.consume(Token.LEFT_PAREN, "Expect '(' after 'switch'."); + + var ret = self.objects.emplace(.Map, {}) catch |err| { + self.lastError = err; + return; + }; + var retmap = ret.cast(.Map) catch unreachable; + self.emitConstant(Value.init(ret)); + self.expression(); + self.consume(Token.RIGHT_PAREN, "Expect ')' after expression"); - //var array = ValueArray.init(self.allocator); + + self.emitOP(OP.GET_INDEX); + const defaultJump = self.emitJump(OP.JUMP_IF_FALSE); + var defaultPresent = false; + self.emitOP(OP.JUMP_POP); + const switchJump = self.currentChunk().code.len; + const exitJump = self.emitJump(OP.JUMP); + + self.consume(Token.LEFT_BRACE, "Expect '{' after switch()"); + + while(!self.match(Token.RIGHT_BRACE)) { + if (self.match(Token.CASE)) { + const case = self.parseLiteralValue() catch |err| { + self.lastError = err; + return; + }; + const distance = self.currentChunk().code.len - switchJump; + if (distance > std.math.maxInt(u52)) { + self.errorAtCurrent("Switch body too large"); + return; + } + const isNew = retmap.set(case, Value.init(@as(Value.tagType(.number), @floatFromInt(distance)))) catch |err| { + self.lastError = err; + return; + }; + if (!isNew) { + self.errorAtCurrent("Duplicate case"); + return; + } + } else if (self.match(Token.DEFAULT)) { + if (defaultPresent) { + self.errorAtCurrent("Duplicate default"); + return; + } + self.patchJump(defaultJump); + self.emitOP(OP.POP); + defaultPresent = true; + } else { + self.errorAtCurrent("Expect 'case' or 'default'"); + return; + } + self.consume(Token.COLON, "Expect ':' after case"); + self.statement(); + self.emitLoop(switchJump); + } + + if (!defaultPresent) { + self.patchJump(defaultJump); + self.emitOP(OP.POP); + self.emitLoop(switchJump); + } + + self.patchJump(exitJump); } fn whileStatement(self: *Self) void { @@ -780,7 +850,6 @@ pub fn Compiler(size: comptime_int) type { .hadError = false, .lastError = scanner.ScannerError.EmptyToken, .compilingChunk = try Chunk.init(allocator), - .allocator = allocator, .objects = objects, .locals = [_]Local{Local{.name = scanner.Token.Empty, .depth = 0, .con = true}} ** size, .localCount = 0, diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig @@ -45,6 +45,7 @@ pub fn disassembleInstruction(ch: chunk.Chunk, offset: usize) !usize { @intFromEnum(OP.GET_LOCAL) => try byteInstruction("OP_GET_LOCAL", ch, offset), @intFromEnum(OP.SET_LOCAL) => try byteInstruction("OP_SET_LOCAL", ch, offset), @intFromEnum(OP.JUMP_IF_FALSE) => try jumpInstruction("OP_JUMP_IF_FALSE", true, ch, offset), + @intFromEnum(OP.JUMP_POP) => simpleInstruction("OP_JUMP_POP", offset), @intFromEnum(OP.JUMP) => try jumpInstruction("OP_JUMP", true, ch, offset), @intFromEnum(OP.LOOP) => try jumpInstruction("OP_LOOP", false, ch, offset), @intFromEnum(OP.SET_INDEX) => simpleInstruction("SET_INDEX", offset), diff --git a/zlox/src/obj.zig b/zlox/src/obj.zig @@ -184,15 +184,21 @@ pub const Obj = packed struct { return @ptrCast(self); } - pub fn set(self: *Self, key: value.Value, val: value.Value) !void { + pub fn set(self: *Self, key: value.Value, val: value.Value) !bool { self.hash +%= hash.hash(key) +% hash.hash(val); - _ = try self.map.set(key, val); + return self.map.set(key, val); } pub fn get(self: *Self, key: value.Value) !value.Value { return self.map.get(key); } + pub fn delete(self: *Self, key: value.Value) void { + const val = self.map.get(key) catch return; + self.hash -%= hash.hash(key) -% hash.hash(val); + _ = self.map.delete(key); + } + fn print_element(key: value.Value, val: value.Value) void { key.print(); std.debug.print(":", .{}); diff --git a/zlox/src/scanner.zig b/zlox/src/scanner.zig @@ -49,6 +49,8 @@ pub const TokenType = enum { CON, WHILE, SWITCH, + CASE, + DEFAULT, EOF, }; @@ -94,6 +96,8 @@ pub const Scanner = struct { .{ "con", TokenType.CON }, .{ "while", TokenType.WHILE }, .{ "switch", TokenType.SWITCH }, + .{ "case", TokenType.CASE }, + .{ "default", TokenType.DEFAULT }, }); pub fn init(source: []const u8) !@This() { diff --git a/zlox/src/table.zig b/zlox/src/table.zig @@ -175,9 +175,9 @@ pub fn Table(K: type, V: type, hash_fn: fn (K) u32, cmp_fn: fn (K, K) bool) type pub fn delete(self: *Self, key: K) bool { if (self.entries.len == 0) return false; - var entry = find(self.entries, key); - switch (entry) { - .some => entry = .tomb, + const entry = find(self.entries, key); + switch (entry.*) { + .some => entry.* = .tomb, else => return false, } diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -174,6 +174,9 @@ pub const VM = struct { @intFromEnum(OP.JUMP) => { self.ip_add(self.read_short()); }, + @intFromEnum(OP.JUMP_POP) => { + self.ip_add(@intFromFloat(self.pop().number)); + }, @intFromEnum(OP.LOOP) => { self.ip_sub(self.read_short()); }, @@ -207,11 +210,7 @@ pub const VM = struct { const map = self.pop(); if (map.is(Obj.Type.Map) or map.is(Obj.Type.String)) { switch(map.obj.type) { - inline else => |tp| self.push((map.obj.cast(tp) catch unreachable).get(idx) catch { - idx.print(); - self.runtimeError(" index invalid", .{}); - return InterpreterError.RuntimeError; - }) + inline else => |tp| self.push((map.obj.cast(tp) catch unreachable).get(idx) catch Value.init({})), } } else { self.runtimeError("Cannot index a non-map value", .{}); @@ -226,7 +225,12 @@ pub const VM = struct { self.runtimeError("Cannot index a non-map value", .{}); return InterpreterError.RuntimeError; } - _ = try (map.obj.cast(.Map) catch unreachable).set(idx, val); + var m = map.obj.cast(.Map) catch unreachable; + if (val.is(Value.nil)) { + m.delete(idx); + } else { + _ = try m.set(idx, val); + } self.push(val); }, @intFromEnum(OP.DEFINE_GLOBAL) => _ = try self.vm.globals.set(self.read_string(), Global.make_var(self.pop())),