DzLox

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

commit 1c61c9d487badce74c4ab873ccdc170b774411ab
parent 12d371f7a7dd8ea745a82a2dd028acec049f2d25
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date:   Wed, 21 Aug 2024 22:50:15 +0200

No more parseLiteral

Diffstat:
Mzlox/src/compiler.zig | 177++++++++++++++++++++++++++-----------------------------------------------------
Mzlox/src/debug.zig | 10+++++-----
Mzlox/src/obj/native.zig | 23++++++++++++++++-------
Mzlox/src/vm.zig | 24+++++++++++++-----------
Mzlox/src/vm/native.zig | 44++++++++++++++++++++++++++++++++++----------
5 files changed, 125 insertions(+), 153 deletions(-)

diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -8,6 +8,7 @@ const Obj = @import("obj.zig").Obj; const GC = @import("gc.zig").GC; const debug = @import("debug.zig"); const Token = scanner.TokenType; +const vm_native = @import("vm/native.zig"); pub const CompilerError = Obj.Error || scanner.ScannerError || Chunk.Error || Value.ParseNumberError || error{ UnexpectedToken, NotAnExpression }; @@ -81,7 +82,7 @@ pub fn Compiler(size: comptime_int) type { T.RIGHT_PAREN => R(null, null, P.NONE ), T.LEFT_BRACE => R(null, null, P.NONE ), T.RIGHT_BRACE => R(null, null, P.NONE ), - T.LEFT_BRACKET => R(S.table, S.index, P.CALL ), + T.LEFT_BRACKET => R(S.listTable,S.index, P.CALL ), T.RIGHT_BRACKET => R(null, null, P.NONE ), T.COMMA => R(null, null, P.NONE ), T.DOT => R(null, null, P.NONE ), @@ -265,15 +266,11 @@ pub fn Compiler(size: comptime_int) type { } fn string(self: *Self, _: bool) void { - self.emitConstant(self.parseLiteralString() catch |err| { - self.lastError = err; - self.errorAtPrevious("Couldn't allocate object"); - return; - }); + self.emitObj(.String, &.{self.previous.lexeme[1 .. self.previous.lexeme.len - 1]}) catch return; } fn char(self: *Self, _: bool) void { - self.emitConstant(self.parseLiteralChar()); + self.emitConstant(Value.init(self.previous.lexeme[1])); } fn call(self: *Self, _: bool) void { @@ -298,110 +295,52 @@ pub fn Compiler(size: comptime_int) type { return argCount; } - fn parseLiteralValue(self: *Self) CompilerError!Value { - if (self.match(Token.STRING)) { - return self.parseLiteralString(); - } else if (self.match(Token.CHAR)) { - return self.parseLiteralChar(); - } else if (self.match(Token.NUMBER)) { - return self.parseLiteralNumber(); - } else if (self.match(Token.FALSE)) { - return Value.init(false); - } else if (self.match(Token.TRUE)) { - return Value.init(true); - } else if (self.match(Token.NIL)) { - return Value.init({}); - } else if (self.match(Token.LEFT_BRACKET)) { - return self.parseLiteralListOrTable(); - } else { - self.errorAtCurrent("Not a literal value"); - return error.UnexpectedToken; - } - } - - fn parseLiteralNumber(self: *Self) !Value { - return Value.parseNumber(self.previous.lexeme); + fn makeObj(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) !u8 { + return self.makeConstant(Value.init(self.objects.emplace_cast(tp, arg) catch |err| { + self.lastError = err; + self.errorAtPrevious("Unable to allocate obj"); + return err; + })); } - - fn parseLiteralChar(self: *Self) Value { - return Value.init(self.previous.lexeme[1]); + + fn emitObj(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) !void { + self.emit(OP.CONSTANT, try self.makeObj(tp, arg)); } - fn parseLiteralString(self: *Self) !Value { - return Value.init(try self.objects.emplace_cast(.String, &.{self.previous.lexeme[1 .. self.previous.lexeme.len - 1]})); - } + fn listTable(self: *Self, _: bool) void { + self.emit(OP.CONSTANT, 0xff); + const offset = self.currentChunk().code.len - 1; + var argCount: u8 = 0; + var isList = true; - fn parseLiteralListOrTable(self: *Self) CompilerError!Value { if (self.match(Token.RIGHT_BRACKET)) { - return Value.init(try self.objects.emplace_cast(.List, {})); } else if (self.match(Token.COLON)) { - self.consume(Token.RIGHT_BRACKET, "Expect ']' in empty table literal"); - return Value.init(try self.objects.emplace_cast(.Table, {})); + isList = false; } else { - const firstVal = try self.parseLiteralValue(); + self.expression(); + argCount += 1; if (self.match(Token.COLON)) { - return self.parseLiteralTable(firstVal); - } else { - return self.parseLiteralList(firstVal); - } - } - } - - fn parseLiteralList(self: *Self, firstVal: Value) CompilerError!Value { - var first = true; - var list = try self.objects.emplace(.List, {}); - while (first or !self.match(Token.RIGHT_BRACKET)) { - const val = if (!first) - try self.parseLiteralValue() - else blk: { - first = false; - break :blk firstVal; - }; - if (val.is(Value.nil)) { - self.errorAtPrevious("Nil cannot be stored in a list"); - return error.UnexpectedToken; - } - try list.push(val, self.objects.allocator); - if (self.match(Token.RIGHT_BRACKET)) - break; - self.consume(Token.COMMA, "Expect ',' after value in list initalizer"); - } - return Value.init(list.cast()); - } - - fn parseLiteralTable(self: *Self, firstVal: Value) CompilerError!Value { - var first = true; - var tabl = try self.objects.emplace(.Table, {}); - while (first or !self.match(Token.RIGHT_BRACKET)) { - const key = if (!first) blk: { - const key = try self.parseLiteralValue(); - self.consume(Token.COLON, "Expect ':' after key in table initalizer"); - break :blk key; - } else blk2: { - first = false; - break :blk2 firstVal; - }; - const val = try self.parseLiteralValue(); - if (val.is(Value.nil)) { - self.errorAtPrevious("Nil cannot be stored in a table"); - return error.UnexpectedToken; + isList = false; + self.expression(); + argCount += 1; } - if (!try tabl.set(key, val)) { - self.errorAtPrevious("Duplicate key in table literal"); - return error.UnexpectedToken; + while(!self.match(Token.RIGHT_BRACKET)) { + self.consume(Token.COMMA, "Expect ',' between expressions"); + self.expression(); + argCount += 1; + if(!isList) { + self.consume(Token.COLON, "Expect ':' between key and value"); + self.expression(); + argCount += 1; + } } - if (self.match(Token.RIGHT_BRACKET)) - break; - self.consume(Token.COMMA, "Expect ',' after value in table initalizer"); } - return Value.init(tabl.cast()); - } - - fn table(self: *Self, _: bool) void { - self.emitConstant(self.parseLiteralListOrTable() catch |err| { - self.lastError = err; - return; - }); + if (isList) { + self.currentChunk().code.set(offset, self.makeObj(.Native, Obj.Native.Arg{.name = "internal::list", .fun = vm_native.list}) catch return) catch return; + } else { + self.currentChunk().code.set(offset, self.makeObj(.Native, Obj.Native.Arg{.name = "internal::table", .fun = vm_native.table}) catch return) catch return; + } + self.emit(OP.CALL, argCount); } fn index(self: *Self, canAssign: bool) void { @@ -749,51 +688,46 @@ pub fn Compiler(size: comptime_int) type { } fn switchStatement(self: *Self) void { - self.consume(Token.LEFT_PAREN, "Expect '(' after 'switch'."); + var defaultPresent = false; + var argCount: u8 = 0; - var tabl = self.objects.emplace(.Table, {}) catch |err| { - self.lastError = err; - return; - }; - self.emitConstant(Value.init(tabl.cast())); + self.consume(Token.LEFT_PAREN, "Expect '(' after 'switch'."); + self.emitObj(.Native, Obj.Native.Arg{.name = "switch", .fun = vm_native.table}) catch return; + + var jumpOver = self.emitJump(OP.JUMP); + const switchExpression = self.currentChunk().code.len; self.expression(); self.consume(Token.RIGHT_PAREN, "Expect ')' after expression"); 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.patchJump(jumpOver); + 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; + self.expression(); + argCount += 2; + const distance = self.currentChunk().code.len - switchJump + 5; if (distance > std.math.maxInt(u52)) { self.errorAtCurrent("Switch body too large"); return; } - const isNew = tabl.set(case, Value.init(@as(Value.tagType(.number), @floatFromInt(distance)))) catch |err| { - self.lastError = err; - return; - }; - if (!isNew) { - self.errorAtCurrent("Duplicate case"); - return; - } + self.emitConstant(Value.init(@as(Value.tagType(.number), @floatFromInt(distance)))); + jumpOver = self.emitJump(OP.JUMP); } else if (self.match(Token.DEFAULT)) { if (defaultPresent) { self.errorAtCurrent("Duplicate default"); return; } + jumpOver = self.emitJump(OP.JUMP); self.patchJump(defaultJump); self.emitOP(OP.POP); defaultPresent = true; @@ -804,7 +738,10 @@ pub fn Compiler(size: comptime_int) type { self.consume(Token.COLON, "Expect ':' after case"); self.statement(); self.emitLoop(switchJump); + self.patchJump(jumpOver); } + self.emit(OP.CALL, argCount); + self.emitLoop(switchExpression); if (!defaultPresent) { self.patchJump(defaultJump); diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig @@ -3,7 +3,7 @@ const chunk = @import("chunk.zig"); const value = @import("value.zig"); const print = std.debug.print; -pub fn disassembleChunk(ch: chunk.Chunk, name: []const u8) !void { +pub fn disassembleChunk(ch: *const chunk.Chunk, name: []const u8) !void { print("== {s} ==\n", .{name}); var offset: usize = 0; @@ -13,7 +13,7 @@ pub fn disassembleChunk(ch: chunk.Chunk, name: []const u8) !void { } } -pub fn disassembleInstruction(ch: chunk.Chunk, offset: usize) !usize { +pub fn disassembleInstruction(ch: *const chunk.Chunk, offset: usize) !usize { const OP = chunk.OP; print("{d:0>4} ", .{offset}); if (offset > 0 and (try ch.lines.get(offset)) == (try ch.lines.get(offset - 1))) { @@ -67,18 +67,18 @@ fn simpleInstruction(name: []const u8, offset: usize) usize { return offset + 1; } -fn constantInstruction(name: []const u8, ch: chunk.Chunk, offset: usize) !usize { +fn constantInstruction(name: []const u8, ch: *const chunk.Chunk, offset: usize) !usize { const constant = try ch.code.get(offset + 1); print("{s:<32} {d:4} '{s}'\n", .{ name, constant, try ch.constants.get(constant)}); return offset + 2; } -fn byteInstruction(name: []const u8, ch: chunk.Chunk, offset: usize) !usize { +fn byteInstruction(name: []const u8, ch:*const chunk.Chunk, offset: usize) !usize { print("{s:<32} {d:4}\n", .{name, try ch.code.get(offset+1)}); return offset + 2; } -fn jumpInstruction(name: []const u8, sign: bool, ch: chunk.Chunk, offset: usize) !usize { +fn jumpInstruction(name: []const u8, sign: bool, ch: *const chunk.Chunk, offset: usize) !usize { const msb: u16 = try ch.code.get(offset + 1); const lsb: u16 = try ch.code.get(offset + 2); const jump = (msb << 8) | lsb; diff --git a/zlox/src/obj/native.zig b/zlox/src/obj/native.zig @@ -1,22 +1,30 @@ const std = @import("std"); +const GC = @import("../gc.zig").GC; const Value = @import("../value.zig").Value; const Super = @import("../obj.zig").Obj; const Error = Super.Error; +pub const NativeError = error { NativeError }; + pub const Native = packed struct { const Self = @This(); - pub const Fn = *const fn ([]const Value) Value; + pub const Fn = *const fn (*GC, []const Value) NativeError!Value; + + pub const ArityMin = 0; + pub const ArityMax = std.math.maxInt(u8); pub const Arg = struct { fun: Fn, - arity: u8, - name: []const u8 + arity_min: u8 = ArityMin, + arity_max: u8 = ArityMax, + name: []const u8 = "" }; obj: Super, fun: Fn, - arity: u8, + arity_min: u8, + arity_max: u8, name: [*]const u8, name_len: usize, @@ -27,15 +35,16 @@ pub const Native = packed struct { .type = Super.Type.Native, }, .fun = arg.fun, - .arity = arg.arity, + .arity_min = arg.arity_min, + .arity_max = arg.arity_max, .name = arg.name.ptr, .name_len = arg.name.len }; return self; } - pub fn call(self: *const Self, argCount: u8, args: [*]Value) Value { - return self.fun(args[0..argCount]); + pub fn call(self: *const Self, gc: *GC, argCount: u8, args: [*]Value) NativeError!Value { + return self.fun(gc, args[0..argCount]); } pub fn cast(self: *Self) *Super { diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -12,7 +12,7 @@ const hash = @import("hash.zig"); const utils = @import("comptime_utils.zig"); const vm_native = @import("vm/native.zig"); -pub const InterpreterError = compiler.CompilerError || Callback.Error || error{ CompileError, RuntimeError, StackOverflow, IndexOutOfBounds, Overflow, DivisionByZero }; +pub const InterpreterError = vm_native.NativeError || compiler.CompilerError || Callback.Error || error{ CompileError, RuntimeError, StackOverflow, IndexOutOfBounds, Overflow, DivisionByZero }; pub const VM = struct { objects: GC, @@ -60,19 +60,21 @@ pub const VM = struct { } }; - fn defineNative(self: *@This(), name: []const u8, arity: u8, fun: Obj.Native.Fn) !void { + fn defineNative(self: *@This(), name: []const u8, arity_min: u8, arity_max: u8, fun: Obj.Native.Fn) !void { const nameObj = try self.objects.emplace(.String, &.{name}); - const funObj = try self.objects.emplace_cast(.Native, Obj.Native.Arg{.fun = fun, .name = name, .arity = arity}); + const funObj = try self.objects.emplace_cast(.Native, Obj.Native.Arg{.fun = fun, .name = name, .arity_min = arity_min, .arity_max = arity_max}); _ = try self.globals.set(nameObj, Global.make_con(Value.init(funObj))); } pub fn init(allocator: std.mem.Allocator) !@This() { var self = @This(){ .globals = Globals.init(allocator), .objects = try GC.init(allocator), .allocator = allocator }; - try self.defineNative("clock", 0, vm_native.clock); - try self.defineNative("put", 1, vm_native.put); + try self.defineNative("clock", 0, 0, vm_native.Clock.clock); + try self.defineNative("put", 1, 1, vm_native.put); + try self.defineNative("table", 0, Obj.Native.ArityMax, vm_native.table); + try self.defineNative("list", 0, Obj.Native.ArityMax, vm_native.list); - try vm_native.set_start(); + try vm_native.Clock.set_start(); return self; } @@ -83,7 +85,7 @@ pub const VM = struct { const function = try compiler.Compiler(stack_size).compile(source, &self.objects); - //try debug.disassembleChunk(chunk, "Main"); + try debug.disassembleChunk(function.chunk, "Main"); try Interpreter(callstack_size, stack_size).run(self, function, dbg); } @@ -165,11 +167,11 @@ pub const VM = struct { try self.call(callee.obj.cast(.Function) catch unreachable, argCount); } else if(callee.is(Obj.Type.Native)) { const native = callee.obj.cast(.Native) catch unreachable; - if (argCount != native.arity) { - self.runtimeError("Expected {d} arguments but got {d}", .{native.arity, argCount}); + if (argCount < native.arity_min or argCount > native.arity_max) { + self.runtimeError("Expected from {d} to {d} arguments but got {d}", .{native.arity_min, native.arity_max, argCount}); return InterpreterError.RuntimeError; } - const result = native.call(argCount, self.stackTop - argCount); + const result = try native.call(&self.vm.objects, argCount, self.stackTop - argCount); self.stackTop -= argCount + 1; self.push(result); return; @@ -214,7 +216,7 @@ pub const VM = struct { std.debug.print("[{s}]", .{stackPtr[0]}); } std.debug.print("\n", .{}); - _ = try debug.disassembleInstruction(self.frame().function.chunk.*, self.instruction_idx()); + _ = try debug.disassembleInstruction(self.frame().function.chunk, self.instruction_idx()); } const instruction: u8 = self.read_byte(); switch (instruction) { diff --git a/zlox/src/vm/native.zig b/zlox/src/vm/native.zig @@ -1,18 +1,42 @@ const std = @import("std"); +const GC = @import("../gc.zig").GC; const Value = @import("../value.zig").Value; -var start: std.time.Instant = undefined; +pub const NativeError = @import("../obj/native.zig").NativeError; -pub fn set_start() !void { - start = try std.time.Instant.now(); -} +pub const Clock = struct { + var start: std.time.Instant = undefined; + + pub fn set_start() !void { + start = try std.time.Instant.now(); + } + + pub fn clock(_: *GC, _: [] const Value) NativeError!Value { + const now = std.time.Instant.now() catch return NativeError.NativeError; + const elapsed: f64 = @floatFromInt(now.since(start)); + return Value.init(elapsed / std.time.ns_per_s); + } +}; -pub fn clock(_: [] const Value) Value { - const now = std.time.Instant.now() catch unreachable; - const elapsed: f64 = @floatFromInt(now.since(start)); - return Value.init(elapsed / std.time.ns_per_s); -} -pub fn put(args: []const Value) Value { +pub fn put(_: *GC, args: []const Value) NativeError!Value { std.debug.print("{s}", .{args[0]}); return Value.init({}); } + +pub fn table(gc: *GC, args: []const Value) NativeError!Value { + var tbl = gc.emplace(.Table, {}) catch return NativeError.NativeError; + if (args.len % 2 != 0) return NativeError.NativeError; + var i: usize = 0; + while(i < args.len) : (i += 2) { + _ = tbl.set(args[i], args[i+1]) catch return NativeError.NativeError; + } + return Value.init(tbl.cast()); +} + +pub fn list(gc: *GC, args: []const Value) NativeError!Value { + var lis = gc.emplace(.List, {}) catch return NativeError.NativeError; + for(args) |arg| { + lis.push(arg, gc.allocator) catch return NativeError.NativeError; + } + return Value.init(lis.cast()); +}