commit 543eaa45c9a95066b86c94fe159571bcd3532d7b
parent c3997b2ea200e7bcc8ad818ee841a9cb641a950f
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date: Sun, 4 Aug 2024 19:20:56 +0200
hash-based switch()
Diffstat:
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())),