commit bbec43d49e00f4958cb0de3bbc7b4bd33ff960df
parent 02a6abb8b245952640a4f8ec30502ff45eca371f
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date: Wed, 30 Sep 2026 11:31:49 +0200
Super calls implemented
Diffstat:
19 files changed, 356 insertions(+), 192 deletions(-)
diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig
@@ -59,13 +59,14 @@ pub fn Compiler(size: comptime_int) type {
const Class = struct {
enclosing: ?*Class,
+ hasSuperclass: bool = false,
};
const Self = @This();
pub const Stack = size;
pub const Upvalue = struct {
- pub const Type = enum(u8) { local = 0, remote = 1 };
+ pub const Type = enum(u8) { local = 0, remote = 1, empty = 2, immediate = 3 };
index: u8,
type: Type,
@@ -78,6 +79,8 @@ pub fn Compiler(size: comptime_int) type {
depth: ?usize = null,
con: bool = true,
captured: bool = false,
+ placeholder: bool = false,
+ immediate: bool = false,
};
const ParseFn = *const fn (*Self, bool) void;
@@ -101,34 +104,35 @@ pub fn Compiler(size: comptime_int) type {
const tok: Token = @enumFromInt(i);
v.* = switch (tok) {
// zig fmt: off
- T.LEFT_PAREN => R(S.grouping, S.call, P.CALL ),
- T.LEFT_BRACKET => R(S.listTable,S.index, P.CALL ),
- T.MINUS => R(S.unary, S.binary, P.TERM ),
- T.PLUS => R(null, S.binary, P.TERM ),
- T.SLASH => R(null, S.binary, P.FACTOR ),
- T.STAR => R(null, S.binary, P.FACTOR ),
- T.QUESTION => R(null, S.ternary, P.TERNARY ),
- T.BANG => R(S.unary, null, P.NONE ),
- T.BANG_EQUAL => R(null, S.binary, P.EQUALITY ),
- T.EQUAL_EQUAL => R(null, S.binary, P.EQUALITY ),
- T.GREATER => R(null, S.binary, P.COMPARISON ),
- T.GREATER_EQUAL => R(null, S.binary, P.COMPARISON ),
- T.LESS => R(null, S.binary, P.COMPARISON ),
- T.LESS_EQUAL => R(null, S.binary, P.COMPARISON ),
- T.IDENTIFIER => R(S.variable, null, P.NONE ),
- T.STRING => R(S.string, null, P.NONE ),
- T.CHAR => R(S.char, null, P.NONE ),
- T.NUMBER => R(S.number, null, P.NONE ),
- T.AND => R(null, S._and, P.AND ),
- T.FALSE => R(S.literal, null, P.NONE ),
- T.NIL => R(S.literal, null, P.NONE ),
- T.OR => R(null, S._or, P.OR ),
- T.TRUE => R(S.literal, null, P.NONE ),
- T.FUN => R(S.funExpression, null, P.NONE ),
- T.CLASS => R(S.class, null, P.NONE ),
- T.DOT => R(null, S.dot, P.CALL ),
- T.THIS => R(S.this, null, P.NONE ),
- else => R(null, null, P.NONE ),
+ T.LEFT_PAREN => R(S.grouping, S.call, P.CALL ),
+ T.LEFT_BRACKET => R(S.listTable, S.index, P.CALL ),
+ T.MINUS => R(S.unary, S.binary, P.TERM ),
+ T.PLUS => R(null, S.binary, P.TERM ),
+ T.SLASH => R(null, S.binary, P.FACTOR ),
+ T.STAR => R(null, S.binary, P.FACTOR ),
+ T.QUESTION => R(null, S.ternary, P.TERNARY ),
+ T.BANG => R(S.unary, null, P.NONE ),
+ T.BANG_EQUAL => R(null, S.binary, P.EQUALITY ),
+ T.EQUAL_EQUAL => R(null, S.binary, P.EQUALITY ),
+ T.GREATER => R(null, S.binary, P.COMPARISON ),
+ T.GREATER_EQUAL => R(null, S.binary, P.COMPARISON ),
+ T.LESS => R(null, S.binary, P.COMPARISON ),
+ T.LESS_EQUAL => R(null, S.binary, P.COMPARISON ),
+ T.IDENTIFIER => R(S.variable, null, P.NONE ),
+ T.STRING => R(S.string, null, P.NONE ),
+ T.CHAR => R(S.char, null, P.NONE ),
+ T.NUMBER => R(S.number, null, P.NONE ),
+ T.AND => R(null, S._and, P.AND ),
+ T.FALSE => R(S.literal, null, P.NONE ),
+ T.NIL => R(S.literal, null, P.NONE ),
+ T.OR => R(null, S._or, P.OR ),
+ T.TRUE => R(S.literal, null, P.NONE ),
+ T.FUN => R(S.funExpression, null, P.NONE ),
+ T.CLASS => R(S.classExpression, null, P.NONE ),
+ T.DOT => R(null, S.dot, P.CALL ),
+ T.THIS => R(S.this, null, P.NONE ),
+ T.SUPER => R(S.super, null, P.NONE ),
+ else => R(null, null, P.NONE ),
// zig fmt: on
};
}
@@ -185,7 +189,7 @@ pub fn Compiler(size: comptime_int) type {
fn emitReturn(self: *Self) void {
if (self.initializer)
- self.emit(OP.GET_LOCAL, 0)
+ self.namedVariable(makeIdentifier("this"), false)
else if (self.enclosing) |_|
self.emitOP(OP.NIL);
@@ -267,7 +271,7 @@ pub fn Compiler(size: comptime_int) type {
}
fn string(self: *Self, _: bool) void {
- self.emitObj(.String, &.{self.previous.lexeme[1 .. self.previous.lexeme.len - 1]}) catch return;
+ self.emitObj(.String, null, &.{self.previous.lexeme[1 .. self.previous.lexeme.len - 1]}) catch return;
}
fn char(self: *Self, _: bool) void {
@@ -308,16 +312,16 @@ pub fn Compiler(size: comptime_int) type {
return argCount;
}
- 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| {
+ fn makeObj(self: *Self, comptime tp: Obj.Type, name: ?[]const u8, arg: tp.get().Arg) !u8 {
+ return self.makeConstant(Value.init(self.objects.emplace_cast(tp, name, arg) catch |err| {
self.lastError = err;
self.errorAtPrevious("Unable to allocate obj");
return err;
}));
}
- fn emitObj(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) !void {
- self.emit(OP.CONSTANT, try self.makeObj(tp, arg));
+ fn emitObj(self: *Self, comptime tp: Obj.Type, name: ?[]const u8, arg: tp.get().Arg) !void {
+ self.emit(OP.CONSTANT, try self.makeObj(tp, name, arg));
}
fn listTable(self: *Self, _: bool) void {
@@ -350,11 +354,11 @@ pub fn Compiler(size: comptime_int) type {
}
}
if (isList) {
- self.chunk.code.ptr().set(offset, self.makeObj(.Native, .{
+ self.chunk.code.ptr().set(offset, self.makeObj(.Native, "L::list", .{
.fun = vm_native.list,
}) catch return) catch return;
} else {
- self.chunk.code.ptr().set(offset, self.makeObj(.Native, .{
+ self.chunk.code.ptr().set(offset, self.makeObj(.Native, "L::table", .{
.fun = vm_native.table,
}) catch return) catch return;
}
@@ -380,6 +384,23 @@ pub fn Compiler(size: comptime_int) type {
}
}
+ fn super(self: *Self, _: bool) void {
+ if (self.currentClass) |cls| {
+ if (cls.hasSuperclass) {
+ self.consume(Token.DOT, "Expect '.' after 'super'.");
+ self.consume(Token.IDENTIFIER, "Expect superclass method name.");
+ const name = self.identifierConstant(self.previous) catch return;
+ self.namedVariable(makeIdentifier("this"), false);
+ self.namedVariable(makeIdentifier("super"), false);
+ self.emit(OP.GET_SUPER, name);
+ } else {
+ self.errorAtPrevious("Can't use 'super' in a class with no parent.");
+ }
+ } else {
+ self.errorAtPrevious("Can't use 'super' outside of a class.");
+ }
+ }
+
fn variable(self: *Self, canAssign: bool) void {
self.namedVariable(self.previous, canAssign);
}
@@ -407,8 +428,12 @@ pub fn Compiler(size: comptime_int) type {
fn resolveUpvalue(self: *Self, name: scanner.Token) ?u8 {
if (self.enclosing) |enclosing| {
if (enclosing.resolveLocal(name)) |local| {
- enclosing.locals[local].captured = true;
- return self.addUpvalue(local, .local) catch null;
+ const loc = &enclosing.locals[local];
+ loc.captured = !loc.placeholder and !loc.immediate;
+ return self.addUpvalue(
+ local,
+ if (loc.placeholder) .empty else if (loc.immediate) .immediate else .local,
+ ) catch null;
} else if (enclosing.resolveUpvalue(name)) |upvalue| {
return self.addUpvalue(upvalue, .remote) catch null;
}
@@ -560,27 +585,60 @@ pub fn Compiler(size: comptime_int) type {
fn classDeclaration(self: *Self) void {
const global = self.parseVariable("Expect class name.", true) catch return;
self.markInitialized();
- self.class(false);
+ self.class(self.previous.lexeme);
self.defineVariable(global, true);
}
- fn class(self: *Self, _: bool) void {
- const cls = self.objects.emplace_cast(Obj.Type.Class, {}) catch |err| {
+ fn makeIdentifier(name: []const u8) scanner.Token {
+ return .{
+ .type = Token.IDENTIFIER,
+ .lexeme = name,
+ .line = -1,
+ .column = 0,
+ };
+ }
+
+ fn classExpression(self: *Self, _: bool) void {
+ self.class(null);
+ }
+
+ fn class(self: *Self, name: ?[]const u8) void {
+ const cls = self.objects.emplace_cast(Obj.Type.Class, name, {}) catch |err| {
self.errorAtPrevious("Couldn't allocate class");
self.lastError = err;
return;
};
+
+ self.beginScope();
+
self.emit(OP.CONSTANT, self.makeConstant(Value.init(cls)));
+ self.addLocal(makeIdentifier("this"), true);
+ self.markInitialized();
+ self.markPlaceholder();
+
+ const subclass = self.match(Token.LESS);
+
+ if (subclass) {
+ self.expression();
+ self.addLocal(makeIdentifier("super"), true);
+ self.markInitialized();
+ self.markImmediate();
+ self.emitOP(OP.INHERIT);
+ }
+
self.consume(Token.LEFT_BRACE, "Expect '{' before class body");
- var curcls = Class{ .enclosing = self.currentClass };
+ var curcls = Class{ .enclosing = self.currentClass, .hasSuperclass = subclass };
self.currentClass = &curcls;
+
while (!self.check(Token.RIGHT_BRACE) and !self.check(Token.EOF)) {
self.method();
}
+
self.currentClass = self.currentClass.?.enclosing;
self.consume(Token.RIGHT_BRACE, "Expect '}' after class body");
+ self.endScope();
}
fn method(self: *Self) void {
@@ -588,6 +646,7 @@ pub fn Compiler(size: comptime_int) type {
const constant = self.identifierConstant(self.previous) catch return;
self.function(true, self.previous.lexeme);
self.emit(OP.METHOD, constant);
+ self.emitByte(if (self.currentClass.?.hasSuperclass) 1 else 0);
}
fn funDeclaration(self: *Self) void {
@@ -598,11 +657,11 @@ pub fn Compiler(size: comptime_int) type {
}
fn funExpression(self: *Self, _: bool) void {
- self.function(false, "<anon>");
+ self.function(false, null);
}
- fn function(self: *Self, isMethod: bool, name: []const u8) void {
- const chunk = self.objects.emplace(.Chunk, {}) catch |err| {
+ fn function(self: *Self, isMethod: bool, name: ?[]const u8) void {
+ const chunk = self.objects.emplace(.Chunk, name, {}) catch |err| {
self.errorAtPrevious("Couldn't allocate chunk");
self.lastError = err;
return;
@@ -614,7 +673,7 @@ pub fn Compiler(size: comptime_int) type {
return;
};
- compiler.initializer = isMethod and std.mem.eql(u8, name, "init");
+ compiler.initializer = if (name) |n| isMethod and std.mem.eql(u8, n, "init") else false;
compiler.objects.push_callback(&gc_callback, &compiler) catch @panic("Couln't push callback");
defer compiler.objects.pop_callback();
@@ -635,26 +694,10 @@ pub fn Compiler(size: comptime_int) type {
}
compiler.consume(Token.RIGHT_PAREN, "Expect ')' after parameters");
- var offset: usize = 0;
-
- if (isMethod) {
- compiler.locals[0] = .{
- .name = scanner.Token{ .type = Token.THIS, .lexeme = "this", .line = -1, .column = 0 },
- .depth = compiler.scopeDepth,
- };
- compiler.emit(OP.GET_UPVALUE, 0);
- offset = compiler.chunk.code.ptr().len - 1;
- compiler.emit(OP.SET_LOCAL, 0);
- compiler.emitOP(OP.POP);
- }
-
compiler.consume(Token.LEFT_BRACE, "Expect '{' before function body");
compiler.block();
- if (isMethod)
- compiler.chunk.code.ptr().set(offset, compiler.upvaluesCount) catch unreachable;
-
self.current = compiler.current;
if (compiler.hadError) {
@@ -662,8 +705,7 @@ pub fn Compiler(size: comptime_int) type {
} else {
const endchunk = compiler.end() catch return;
if (compiler.upvaluesCount == 0) {
- const fun = self.objects.emplace(.Function, .{
- .type = .Function,
+ const fun = self.objects.emplace(.Function, name, .{
.chunk = endchunk,
.arity = arity,
}) catch |err| {
@@ -721,7 +763,7 @@ pub fn Compiler(size: comptime_int) type {
}
fn identifierConstant(self: *Self, tok: scanner.Token) !u8 {
- return self.makeConstant(Value.init(self.objects.emplace_cast(.String, &.{tok.lexeme}) catch |err| {
+ return self.makeConstant(Value.init(self.objects.emplace_cast(.String, null, &.{tok.lexeme}) catch |err| {
self.lastError = err;
self.errorAtPrevious("Couldn't allocate identifier");
return err;
@@ -764,6 +806,16 @@ pub fn Compiler(size: comptime_int) type {
self.locals[self.localCount - 1].depth = self.scopeDepth;
}
+ fn markPlaceholder(self: *Self) void {
+ if (self.scopeDepth == 0) return;
+ self.locals[self.localCount - 1].placeholder = true;
+ }
+
+ fn markImmediate(self: *Self) void {
+ if (self.scopeDepth == 0) return;
+ self.locals[self.localCount - 1].immediate = true;
+ }
+
fn defineVariable(self: *Self, global: u8, con: bool) void {
if (self.scopeDepth > 0) {
self.markInitialized();
@@ -833,7 +885,7 @@ pub fn Compiler(size: comptime_int) type {
self.consume(Token.LEFT_PAREN, "Expect '(' after 'switch'.");
- self.emitObj(.Native, Obj.Native.Arg{
+ self.emitObj(.Native, "L::table", Obj.Native.Arg{
.fun = vm_native.table,
}) catch return;
@@ -1026,14 +1078,17 @@ pub fn Compiler(size: comptime_int) type {
self.scopeDepth -= 1;
while (self.localCount > 0) {
- if (self.locals[self.localCount - 1].depth) |depth| {
+ const local = self.locals[self.localCount - 1];
+
+ if (local.depth) |depth| {
if (depth <= self.scopeDepth) break;
} else {
- self.errorAt(self.locals[self.localCount - 1].name, "Unitialized variable at scope end");
+ self.errorAt(local.name, "Unitialized variable at scope end");
}
- if (self.locals[self.localCount - 1].captured) {
+
+ if (local.captured) {
self.emitOP(OP.CLOSE_UPVALUE);
- } else {
+ } else if (!local.placeholder) {
self.emitOP(OP.POP);
}
self.localCount -= 1;
@@ -1104,7 +1159,7 @@ pub fn Compiler(size: comptime_int) type {
pub fn compile(source: []const u8, objects: *GC) CompilerError!*Obj.Chunk {
var scan = try scanner.Scanner.init(source);
- const chunk = try objects.emplace(.Chunk, {});
+ const chunk = try objects.emplace(.Chunk, "toplevel", {});
var self = try Self.init(&scan, objects, chunk);
try objects.push_callback(&gc_callback, &self);
diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig
@@ -55,7 +55,7 @@ fn _disassembleInstruction(ch: *const Obj.Chunk, offset: usize, print_fn: bool)
@intFromEnum(OP.NOT) => simpleInstruction(name, offset),
@intFromEnum(OP.CONSTANT) => try constantInstruction(name, ch, offset, print_fn),
@intFromEnum(OP.DEFINE_GLOBAL) => try constantInstruction(name, ch, offset, print_fn),
- @intFromEnum(OP.METHOD) => try constantInstruction(name, ch, offset, print_fn),
+ @intFromEnum(OP.METHOD) => try constantInstruction(name, ch, offset, print_fn) + 1,
@intFromEnum(OP.DEFINE_GLOBAL_CONSTANT) => try constantInstruction(name, ch, offset, print_fn),
@intFromEnum(OP.GET_GLOBAL) => try constantInstruction(name, ch, offset, print_fn),
@intFromEnum(OP.SET_GLOBAL) => try constantInstruction(name, ch, offset, print_fn),
@@ -67,6 +67,7 @@ fn _disassembleInstruction(ch: *const Obj.Chunk, offset: usize, print_fn: bool)
@intFromEnum(OP.SET_UPVALUE) => try byteInstruction(name, ch, offset),
@intFromEnum(OP.GET_PROPERTY) => try byteInstruction(name, ch, offset),
@intFromEnum(OP.SET_PROPERTY) => try byteInstruction(name, ch, offset),
+ @intFromEnum(OP.GET_SUPER) => try constantInstruction(name, ch, offset, print_fn),
@intFromEnum(OP.JUMP_IF_FALSE) => try jumpInstruction(name, true, ch, offset),
@intFromEnum(OP.JUMP_POP) => simpleInstruction(name, offset),
@intFromEnum(OP.JUMP) => try jumpInstruction(name, true, ch, offset),
@@ -76,6 +77,7 @@ fn _disassembleInstruction(ch: *const Obj.Chunk, offset: usize, print_fn: bool)
@intFromEnum(OP.CALL) => try byteInstruction(name, ch, offset),
@intFromEnum(OP.CLOSURE) => try closureInstruction(name, ch, offset),
@intFromEnum(OP.CLOSE_UPVALUE) => simpleInstruction(name, offset),
+ @intFromEnum(OP.INHERIT) => simpleInstruction(name, offset),
else => blk: {
print("Unknown opcode {d} {s}\n", .{ op, name });
break :blk offset + 1;
diff --git a/zlox/src/gc.zig b/zlox/src/gc.zig
@@ -5,6 +5,12 @@ const utils = @import("lib::utils.zig");
const Value = @import("value.zig").Value;
const VM = @import("vm.zig").VM;
+const Packed = @import("lib::packed.zig").Packed;
+
+const DBG_STRESS = false;
+const DBG_LOG = false;
+const DBG_NAME = true;
+const GC_HEAP_GROW_FACTOR = 2;
pub const GC = struct {
pub const Color = enum(u8) {
@@ -13,7 +19,24 @@ pub const GC = struct {
None,
};
- pub const Obj = @import("obj.zig").Obj(.{ .color = Color.White });
+ pub const Name = Packed(?[]const u8);
+
+ pub const Obj = if (DBG_NAME)
+ @import("obj.zig").Obj(packed struct {
+ color: Color = Color.White,
+ name: Name = Name.init(null),
+
+ pub fn format(self: anytype, writer: *std.Io.Writer) !void {
+ if (self.name.ptr()) |nam| {
+ _ = try writer.write(":");
+ _ = try writer.write(nam);
+ }
+ }
+ })
+ else
+ @import("obj.zig").Obj(packed struct {
+ color: Color = Color.White,
+ });
const Self = @This();
@@ -33,10 +56,6 @@ pub const GC = struct {
}
};
- const DBG_STRESS = false;
- const DBG_LOG = false;
- const GC_HEAP_GROW_FACTOR = 2;
-
allocator: std.mem.Allocator,
io: std.Io,
pool: Obj.String.Pool,
@@ -210,7 +229,12 @@ pub const GC = struct {
}
}
- pub fn emplace(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) (ObjList.Error || tp.get().Error || Obj.String.Pool.Error)!*tp.get() {
+ pub fn emplace(
+ self: *Self,
+ comptime tp: Obj.Type,
+ name: ?[]const u8,
+ arg: tp.get().Arg,
+ ) (ObjList.Error || tp.get().Error || Obj.String.Pool.Error)!*tp.get() {
if (tp == .String)
if (self.pool.find(arg)) |obj|
return obj;
@@ -228,6 +252,10 @@ pub const GC = struct {
try self.pool.put(chd);
const obj = chd.cast();
+
+ if (DBG_NAME)
+ obj.fields.name = Name.init(name);
+
dbg_obj("O", "new", obj, true);
try self.objs.push(0, obj);
@@ -245,12 +273,16 @@ pub const GC = struct {
}
}
+ pub fn name_of(obj: *Obj) ?[]const u8 {
+ return if (DBG_NAME) obj.fields.name.ptr() else null;
+ }
+
pub fn exclude(obj: *Obj) void {
obj.fields.color = .None;
}
- pub fn emplace_cast(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) !*Obj {
- return (try self.emplace(tp, arg)).cast();
+ pub fn emplace_cast(self: *Self, comptime tp: Obj.Type, name: ?[]const u8, arg: tp.get().Arg) !*Obj {
+ return (try self.emplace(tp, name, arg)).cast();
}
pub fn deinit(self: *Self) void {
diff --git a/zlox/src/lib::packed.zig b/zlox/src/lib::packed.zig
@@ -125,13 +125,16 @@ pub fn Slice(Type: type) type {
}
pub fn init(arg: Type) Self {
- return Self{
- ._ptr = Ptr.init(arg.ptr),
- ._len = if (utils.optional(arg)) |val|
- val.len
- else
- 0,
- };
+ return if (Ptr.optional)
+ Self{
+ ._ptr = Ptr.init(if (arg) |val| val.ptr else null),
+ ._len = if (arg) |val| val.len else 0,
+ }
+ else
+ Self{
+ ._ptr = Ptr.init(arg.ptr),
+ ._len = arg.len,
+ };
}
pub fn ptr(self: Self) Type {
@@ -154,7 +157,7 @@ pub fn Slice(Type: type) type {
return self._len;
}
- pub fn set(self: Self, val: Ptr.Val) if (Ptr.optional) Error.NullPointer!void else void {
+ pub fn set(self: Self, val: []Ptr.Val) if (Ptr.optional) Error.NullPointer!void else void {
if (utils.optional(self.ptr())) |pointer|
@memcpy(pointer, val)
else if (Ptr.optional)
diff --git a/zlox/src/lib::table.zig b/zlox/src/lib::table.zig
@@ -86,7 +86,7 @@ pub fn Table(K: type, V: type, hash_fn: fn (K) u32, cmp_fn: fn (K, K) bool) type
pub fn addAll(self: *Self, other: *const Self) Error!void {
for (other.entries) |entry| {
switch (entry) {
- .some => |some| self.set(some.key, some.value),
+ .some => |some| _ = try self.set(some.key, some.value),
else => {},
}
}
@@ -160,6 +160,12 @@ pub fn Table(K: type, V: type, hash_fn: fn (K) u32, cmp_fn: fn (K, K) bool) type
return self.set_(find(self.entries, key), key, val);
}
+ pub fn retset(self: *Self, key: K, val: V) Error!V {
+ try self.checkCapacity();
+ _ = self.set_(find(self.entries, key), key, val);
+ return val;
+ }
+
pub fn replace(self: *Self, key: K, val: V) Error!void {
if (self.entries.len == 0)
return Error.KeyError;
@@ -192,6 +198,16 @@ pub fn Table(K: type, V: type, hash_fn: fn (K) u32, cmp_fn: fn (K, K) bool) type
};
}
+ pub fn gorset(self: *Self, key: K, val: V) Error!V {
+ return self.get(key) catch |err| switch (err) {
+ Error.KeyError => blk: {
+ _ = try self.set(key, val);
+ break :blk val;
+ },
+ else => err,
+ };
+ }
+
pub fn delete(self: *Self, key: K) bool {
if (self.entries.len == 0) return false;
diff --git a/zlox/src/lib::utils.zig b/zlox/src/lib::utils.zig
@@ -112,6 +112,21 @@ pub fn pack_t(s: type) type {
);
}
+pub fn add_field(orig: type, name: []const u8, tp: type, default: ?tp) type {
+ const info = @typeInfo(orig).@"struct";
+ const Attributes = std.lang.Type.Struct.FieldAttributes;
+
+ return @Struct(
+ info.layout,
+ info.backing_integer,
+ info.field_names ++ &.{name},
+ info.field_types ++ &.{tp},
+ info.field_attrs ++ &.{Attributes{
+ .default_value_ptr = default,
+ }},
+ );
+}
+
pub fn pack(s: anytype) pack_t(@TypeOf(s)) {
const T = @TypeOf(s);
const fields = @typeInfo(T).@"struct".field_names;
diff --git a/zlox/src/obj.zig b/zlox/src/obj.zig
@@ -3,22 +3,22 @@ const std = @import("std");
const utils = @import("lib::utils.zig");
const Value = @import("value.zig").Value;
-pub fn Obj(fields: anytype) type {
+pub fn Obj(Fields: type) type {
return packed struct {
const Self = @This();
type: Type,
- fields: utils.pack_t(@TypeOf(fields)) = utils.pack(fields),
-
- pub const List = @import("obj::list.zig").List(fields);
- pub const String = @import("obj::string.zig").String(fields);
- pub const Table = @import("obj::table.zig").Table(fields);
- pub const Function = @import("obj::function.zig").Function(fields);
- pub const Native = @import("obj::native.zig").Native(fields);
- pub const Chunk = @import("obj::chunk.zig").Chunk(fields);
- pub const Upvalue = @import("obj::upvalue.zig").Upvalue(fields);
- pub const Class = @import("obj::class.zig").Class(fields);
- pub const Instance = @import("obj::instance.zig").Instance(fields);
+ fields: Fields = Fields{},
+
+ pub const List = @import("obj::list.zig").List(Fields);
+ pub const String = @import("obj::string.zig").String(Fields);
+ pub const Table = @import("obj::table.zig").Table(Fields);
+ pub const Function = @import("obj::function.zig").Function(Fields);
+ pub const Native = @import("obj::native.zig").Native(Fields);
+ pub const Chunk = @import("obj::chunk.zig").Chunk(Fields);
+ pub const Upvalue = @import("obj::upvalue.zig").Upvalue(Fields);
+ pub const Class = @import("obj::class.zig").Class(Fields);
+ pub const Instance = @import("obj::instance.zig").Instance(Fields);
pub const Error = error{IllegalCastError} //
|| List.Error //
@@ -75,16 +75,30 @@ pub fn Obj(fields: anytype) type {
}
pub fn format(self: anytype, writer: *std.Io.Writer) !void {
+ _ = try writer.write("<");
switch (self.type) {
- inline else => |tp| try self._cast(tp).format(writer),
+ inline else => |tp| {
+ _ = try writer.write(@tagName(tp)[0..2]);
+ if (@hasDecl(tp.get(), "format")) {
+ _ = try writer.write(":");
+ try self._cast(tp).format(writer);
+ }
+ },
+ }
+ if (@hasDecl(Fields, "format")) {
+ try self.fields.format(writer);
}
+ _ = try writer.write(">");
}
+
pub fn eql(self: *const Self, other: *const Self) bool {
if (!self.is(other.type)) return false;
+
return switch (self.type) {
- inline else => |tp| self._cast(tp).eql(other._cast(tp)),
+ inline else => |tp| if (@hasDecl(tp.get(), "eql")) self._cast(tp).eql(other._cast(tp)) else false,
};
}
+
pub fn free(obj: *Self, allocator: std.mem.Allocator) void {
return switch (obj.type) {
inline else => |tp| obj._cast(tp).free(allocator),
diff --git a/zlox/src/obj::chunk.zig b/zlox/src/obj::chunk.zig
@@ -42,11 +42,7 @@ pub fn Chunk(fields: anytype) type {
}
pub fn format(self: *const Self, writer: *std.Io.Writer) !void {
- _ = try writer.print("<chunk at {d}>", .{self.lines.ptr().get(0) orelse 0});
- }
-
- pub fn eql(_: *const Self, _: *const Self) bool {
- return false;
+ _ = try writer.print("{d}", .{self.lines.ptr().get(0) orelse 0});
}
pub fn free(self: *const Self, allocator: std.mem.Allocator) void {
diff --git a/zlox/src/obj::class.zig b/zlox/src/obj::class.zig
@@ -7,6 +7,7 @@ const hash = @import("hash.zig");
const Packed = @import("lib::packed.zig").Packed;
const Obj = @import("obj.zig").Obj;
const Value = @import("value.zig").Value;
+const GC = @import("gc.zig").GC;
pub fn Class(fields: anytype) type {
const Super = Obj(fields);
@@ -35,12 +36,42 @@ pub fn Class(fields: anytype) type {
return @ptrCast(self);
}
- pub fn format(_: *const Self, writer: *std.Io.Writer) !void {
- _ = try writer.write("<Class>");
- }
+ pub fn method(self: *Self, name: *Super.String) !union(enum) {
+ Static: *Super.Function,
+ Unbound: struct {
+ this: usize,
+ fun: *Super.Function,
+
+ pub fn bind(sel: *const @This(), gc: *GC, this: *Super.Instance) !*Super.Function {
+ var fun = try gc.emplace(.Function, GC.name_of(sel.fun.cast()), .{
+ .chunk = sel.fun.chunk.ptr(),
+ .arity = sel.fun.arity,
+ .upvalues = @intCast(sel.fun.upvalues.len()),
+ });
+
+ fun.upvalues.set(sel.fun.upvalues.ptr());
+
+ var thi = Value.init(this.cast());
+
+ fun.upvalues.ptr()[sel.this] = try gc.emplace(.Upvalue, null, .{
+ .val = &thi,
+ .slot = 0,
+ .closed = true,
+ });
+
+ return fun;
+ }
+ },
+ } {
+ const met = try self.methods.ptr().get(name);
+
+ for (met.upvalues.ptr(), 0..) |upvalue, idx| {
+ if (upvalue == null) {
+ return .{ .Unbound = .{ .fun = met, .this = idx } };
+ }
+ }
- pub fn eql(_: *const Self, _: *const Self) bool {
- return false;
+ return .{ .Static = met };
}
pub fn free(self: *const Self, allocator: std.mem.Allocator) void {
diff --git a/zlox/src/obj::function.zig b/zlox/src/obj::function.zig
@@ -13,13 +13,10 @@ pub fn Function(fields: anytype) type {
pub const Error = error{ OutOfMemory, InvalidArguments };
- pub const Type = enum(u8) { Function, Script, Closure, Method };
-
pub const Chunk = *Super.Chunk;
pub const Upvalue = ?*Super.Upvalue;
pub const Arg = struct {
- type: Type = .Function,
upvalues: u8 = 0,
chunk: Chunk,
arity: u8 = 0,
@@ -28,19 +25,14 @@ pub fn Function(fields: anytype) type {
obj: Super,
arity: u8,
chunk: Packed(*Super.Chunk),
- type: Type,
upvalues: Packed([]Upvalue),
pub fn init(arg: Arg, allocator: std.mem.Allocator) Error!*Self {
- if (if (arg.type == .Closure or arg.type == .Method) arg.upvalues == 0 else arg.upvalues > 0)
- return Error.InvalidArguments;
-
const self: *Self = try allocator.create(Self);
self.* = Self{
.obj = Super.make(Self),
.chunk = Packed(Chunk).init(arg.chunk),
.arity = arg.arity,
- .type = arg.type,
.upvalues = try Packed([]Upvalue).create(allocator, arg.upvalues),
};
@@ -53,16 +45,7 @@ pub fn Function(fields: anytype) type {
}
pub fn format(self: *const Self, writer: *std.Io.Writer) !void {
- _ = try writer.write(switch (self.type) {
- .Function => "<Function>",
- .Script => "<Script>",
- .Closure => "<Closure>",
- .Method => "<Method>",
- });
- }
-
- pub fn eql(_: *const Self, _: *const Self) bool {
- return false;
+ _ = try writer.print("{d}", .{self.upvalues.len()});
}
pub fn free(self: *const Self, allocator: std.mem.Allocator) void {
diff --git a/zlox/src/obj::instance.zig b/zlox/src/obj::instance.zig
@@ -37,40 +37,17 @@ pub fn Instance(fields: anytype) type {
}
pub fn method(self: *Self, gc: *GC, name: *Super.String) !*Super.Function {
- return self.bound.ptr().get(name) catch blk: {
- const met = try self.cls.ptr().methods.ptr().get(name);
- const fun = try gc.emplace(.Function, .{
- .type = .Method,
- .chunk = met.chunk.ptr(),
- .arity = met.arity,
- .upvalues = @intCast(met.upvalues.len() + 1),
- });
-
- var val = Value.init(self.cast());
- const len = fun.upvalues.len();
- if (len > 1)
- @memcpy(fun.upvalues.ptr()[0 .. len - 1], met.upvalues.ptr());
-
- fun.upvalues.ptr()[len - 1] = try gc.emplace(.Upvalue, .{
- .val = &val,
- .slot = 0,
- .closed = true,
- });
-
- _ = try self.bound.ptr().set(name, fun);
-
- break :blk fun;
- };
+ return self.bound.ptr().get(name) catch
+ switch (try self.cls.ptr().method(name)) {
+ .Static => |sta| sta,
+ .Unbound => |unb| try self.bound.ptr().retset(name, try unb.bind(gc, self)),
+ };
}
pub fn cast(self: anytype) utils.copy_const(@TypeOf(self), *Super) {
return @ptrCast(self);
}
- pub fn format(_: *const Self, writer: *std.Io.Writer) !void {
- _ = try writer.write("<Instance>");
- }
-
pub fn eql(_: *const Self, _: *const Self) bool {
return false;
}
diff --git a/zlox/src/obj::native.zig b/zlox/src/obj::native.zig
@@ -50,14 +50,6 @@ pub fn Native(fields: anytype) type {
return @ptrCast(self);
}
- pub fn format(_: *const Self, writer: *std.Io.Writer) !void {
- _ = try writer.write("<Builtin>");
- }
-
- pub fn eql(_: *const Self, _: *const Self) bool {
- return false;
- }
-
pub fn free(self: *const Self, allocator: std.mem.Allocator) void {
allocator.destroy(self);
}
diff --git a/zlox/src/obj::upvalue.zig b/zlox/src/obj::upvalue.zig
@@ -48,18 +48,13 @@ pub fn Upvalue(fields: anytype) type {
}
pub fn format(self: *const Self, writer: *std.Io.Writer) !void {
- try writer.print("<Upvalue{{{f} at 0x{x}, {any}, {d}}}>", .{
+ try writer.print("{{{f}:{s}:{d}}}", .{
self.location.get(),
- self.location._ptr,
- self.closed,
+ if (self.closed) "C" else "O",
self.slot,
});
}
- pub fn eql(_: *const Self, _: *const Self) bool {
- return false;
- }
-
pub fn free(self: *const Self, allocator: std.mem.Allocator) void {
self.location.destroy(allocator);
allocator.destroy(self);
diff --git a/zlox/src/op.zig b/zlox/src/op.zig
@@ -25,6 +25,7 @@ pub const OP = enum(u8) {
SET_PROPERTY,
GET_UPVALUE,
SET_UPVALUE,
+ GET_SUPER,
METHOD,
JUMP_IF_FALSE,
JUMP,
@@ -35,4 +36,5 @@ pub const OP = enum(u8) {
CALL,
CLOSURE,
CLOSE_UPVALUE,
+ INHERIT,
};
diff --git a/zlox/src/value.zig b/zlox/src/value.zig
@@ -21,7 +21,12 @@ pub const Value = union(enum) {
.char => |val| try writer.writeAll(&[_]u8{val}),
.bool => |val| try writer.writeAll(if (val) "true" else "false"),
.nil => try writer.writeAll("nil"),
- .obj => |o| try o.format(writer),
+ .obj => |o| switch (o.type) {
+ inline else => |tp| if (@hasDecl(tp.get(), "format"))
+ try (o.cast(tp) catch unreachable).format(writer)
+ else
+ try o.format(writer),
+ },
}
}
diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig
@@ -70,11 +70,11 @@ pub const VM = struct {
}
};
- 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});
+ fn defineNative(self: *@This(), comptime name: []const u8, arity_min: u8, arity_max: u8, fun: Obj.Native.Fn) !void {
+ const nameObj = try self.objects.emplace(.String, null, &.{name});
GC.exclude(nameObj.cast());
- const funObj = try self.objects.emplace_cast(.Native, Obj.Native.Arg{
+ const funObj = try self.objects.emplace_cast(.Native, "B::" ++ name, Obj.Native.Arg{
.fun = fun,
.arity_min = arity_min,
.arity_max = arity_max,
@@ -102,7 +102,7 @@ pub const VM = struct {
.initializer = undefined,
};
- self.initializer = try self.objects.emplace(.String, &.{"init"});
+ self.initializer = try self.objects.emplace(.String, null, &.{"init"});
GC.exclude(self.initializer.cast());
@@ -160,7 +160,7 @@ pub const VM = struct {
self.push(Value.init(chunk.cast()));
- const function = try vm.objects.emplace(.Function, .{ .type = .Script, .chunk = chunk });
+ const function = try vm.objects.emplace(.Function, null, .{ .chunk = chunk });
_ = self.pop();
self.push(Value.init(function.cast()));
@@ -255,7 +255,7 @@ pub const VM = struct {
}
fn callClass(self: *@This(), callee: *Obj.Class, argCount: u8) !void {
- const instance = try self.vm.objects.emplace(.Instance, callee);
+ const instance = try self.vm.objects.emplace(.Instance, GC.name_of(callee.cast()), callee);
const initializer = instance.method(&self.vm.objects, self.vm.initializer) catch
if (argCount != 0) {
@@ -291,7 +291,7 @@ pub const VM = struct {
return;
}
- fn captureUpvalue(self: *@This(), slot: u8) !*Obj.Upvalue {
+ fn captureUpvalue(self: *@This(), slot: u8, closed: bool) !*Obj.Upvalue {
var iter = self.upvalues.iter();
while (iter.next()) |val| {
@@ -302,7 +302,11 @@ pub const VM = struct {
}
_ = iter.next();
- const new = try self.vm.objects.emplace(.Upvalue, .{ .val = &self.frame().slots[slot], .slot = slot });
+ const new = try self.vm.objects.emplace(.Upvalue, null, .{
+ .val = &self.frame().slots[slot],
+ .slot = slot,
+ .closed = closed,
+ });
try iter.push(new);
return new;
}
@@ -519,8 +523,7 @@ pub const VM = struct {
const arity = self.read_byte();
const count = self.read_byte();
- const closure = try self.vm.objects.emplace(.Function, .{
- .type = .Closure,
+ const closure = try self.vm.objects.emplace(.Function, GC.name_of(chunk.cast()), .{
.chunk = chunk,
.arity = arity,
.upvalues = count,
@@ -533,18 +536,40 @@ pub const VM = struct {
const slot = self.read_byte();
const U = Compiler.Upvalue.Type;
upvalue.* = switch (tp) {
- @intFromEnum(U.local) => try self.captureUpvalue(slot),
+ @intFromEnum(U.local) => try self.captureUpvalue(slot, false),
+ @intFromEnum(U.immediate) => try self.captureUpvalue(slot, true),
@intFromEnum(U.remote) => self.frame().callee.upvalues.get(slot),
+ @intFromEnum(U.empty) => null,
else => return InterpreterError.RuntimeError,
};
}
},
@intFromEnum(OP.METHOD) => {
const name = self.read_string();
+ const offset = self.read_byte();
const method = try self.pop().obj.cast(.Function);
- const class = try self.peek(0).obj.cast(.Class);
+ const class = try self.peek(offset).obj.cast(.Class);
_ = try class.methods.ptr().set(name, method);
},
+ @intFromEnum(OP.INHERIT) => {
+ const val = self.peek(0);
+ if (val.cast_if(Obj.Type.Class)) |super| {
+ const sub = self.peek(1).obj.cast(.Class) catch unreachable;
+ try sub.methods.ptr().addAll(super.methods.ptr());
+ } else {
+ self.runtimeError("A class can only inherit from another class. Wrong type: {s}", .{val.typeName()});
+ }
+ },
+ @intFromEnum(OP.GET_SUPER) => {
+ const name = self.read_string();
+ const super = try self.pop().obj.cast(.Class);
+ const this = try self.pop().obj.cast(.Instance);
+
+ self.push(Value.init((switch (try super.method(name)) {
+ .Static => |sta| sta,
+ .Unbound => |unb| try unb.bind(&self.vm.objects, this),
+ }).cast()));
+ },
@intFromEnum(OP.DEFINE_GLOBAL) => _ = try self.vm.globals.set(self.read_string(), Global.make_var(self.pop())),
@intFromEnum(OP.DEFINE_GLOBAL_CONSTANT) => _ = try self.vm.globals.set(self.read_string(), Global.make_con(self.pop())),
@intFromEnum(OP.SUBTRACT) => try self.binary_op(Value.number, Value.number, callbacks.sub),
diff --git a/zlox/src/vm::callbacks.zig b/zlox/src/vm::callbacks.zig
@@ -28,7 +28,7 @@ pub fn concatenate(objects: *GC) Type(Obj.Type.String, Obj.Type.String) {
const Ret = Type(Obj.Type.String, Obj.Type.String);
const ret = Ret{ .objects = objects, ._call = struct {
pub fn concatenate(self: *const Ret, lhs: *Obj, rhs: *Obj) Error!*Obj {
- return try self.objects.emplace_cast(.String, &.{ (lhs.cast(.String) catch unreachable).slice(), (rhs.cast(.String) catch unreachable).slice() });
+ return try self.objects.emplace_cast(.String, null, &.{ (lhs.cast(.String) catch unreachable).slice(), (rhs.cast(.String) catch unreachable).slice() });
}
}.concatenate };
return ret;
diff --git a/zlox/src/vm::native.zig b/zlox/src/vm::native.zig
@@ -23,7 +23,7 @@ pub fn put(_: *GC, args: []const Value) Error!Value {
}
pub fn table(gc: *GC, args: []const Value) Error!Value {
- var tbl = gc.emplace(.Table, {}) catch return Error.Native;
+ var tbl = gc.emplace(.Table, null, {}) catch return Error.Native;
if (args.len % 2 != 0) return Error.Native;
var i: usize = 0;
while (i < args.len) : (i += 2) {
@@ -33,7 +33,7 @@ pub fn table(gc: *GC, args: []const Value) Error!Value {
}
pub fn list(gc: *GC, args: []const Value) Error!Value {
- var lis = gc.emplace(.List, {}) catch return Error.Native;
+ var lis = gc.emplace(.List, null, {}) catch return Error.Native;
for (args) |arg| {
lis.list.ptr().push(-1, arg) catch return Error.Native;
}
@@ -41,7 +41,7 @@ pub fn list(gc: *GC, args: []const Value) Error!Value {
}
pub fn typeof(gc: *GC, args: []const Value) Error!Value {
- return Value.init(gc.emplace_cast(.String, &.{args[0].typeName()}) catch return Error.Native);
+ return Value.init(gc.emplace_cast(.String, null, &.{args[0].typeName()}) catch return Error.Native);
}
pub fn rungc(gc: *GC, _: []const Value) Error!Value {
diff --git a/zlox/test/doughnut.lox b/zlox/test/doughnut.lox
@@ -0,0 +1,21 @@
+class Doughnut {
+ cook() {
+ print "Dunk in the fryer.";
+ this.finish("sprinkles");
+ }
+
+ finish(ingredient) {
+ print "Finish with " + ingredient;
+ }
+}
+
+class Cruller < Doughnut {
+ finish(ingredient) {
+ // No sprinkles, always icing.
+ super.finish("icing");
+ }
+}
+
+Doughnut().cook();
+Cruller().cook();
+