DzLox

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

commit bbec43d49e00f4958cb0de3bbc7b4bd33ff960df
parent 02a6abb8b245952640a4f8ec30502ff45eca371f
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Wed, 30 Sep 2026 11:31:49 +0200

Super calls implemented

Diffstat:
Mzlox/src/compiler.zig | 199++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mzlox/src/debug.zig | 4+++-
Mzlox/src/gc.zig | 48++++++++++++++++++++++++++++++++++++++++--------
Mzlox/src/lib::packed.zig | 19+++++++++++--------
Mzlox/src/lib::table.zig | 18+++++++++++++++++-
Mzlox/src/lib::utils.zig | 15+++++++++++++++
Mzlox/src/obj.zig | 42++++++++++++++++++++++++++++--------------
Mzlox/src/obj::chunk.zig | 6+-----
Mzlox/src/obj::class.zig | 41++++++++++++++++++++++++++++++++++++-----
Mzlox/src/obj::function.zig | 19+------------------
Mzlox/src/obj::instance.zig | 33+++++----------------------------
Mzlox/src/obj::native.zig | 8--------
Mzlox/src/obj::upvalue.zig | 9++-------
Mzlox/src/op.zig | 2++
Mzlox/src/value.zig | 7++++++-
Mzlox/src/vm.zig | 49+++++++++++++++++++++++++++++++++++++------------
Mzlox/src/vm::callbacks.zig | 2+-
Mzlox/src/vm::native.zig | 6+++---
Azlox/test/doughnut.lox | 21+++++++++++++++++++++
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(); +