commit 1c61c9d487badce74c4ab873ccdc170b774411ab
parent 12d371f7a7dd8ea745a82a2dd028acec049f2d25
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date: Wed, 21 Aug 2024 22:50:15 +0200
No more parseLiteral
Diffstat:
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());
+}