DzLox

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

commit 6ebfe7eb920247e8c0215ca568dec9634a388f65
parent f9e90b557359a18f30ebeed065d556edd34e6b57
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Thu,  3 Sep 2026 18:54:51 +0200

Methods and This

Diffstat:
Mzlox/src/compiler.zig | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mzlox/src/debug.zig | 34+++++++++++++++++++---------------
Mzlox/src/gc.zig | 2+-
Mzlox/src/obj::upvalue.zig | 9++++++---
Mzlox/src/vm.zig | 72++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Azlox/test/nested.lox | 11+++++++++++
Azlox/test/scone.lox | 8++++++++
7 files changed, 167 insertions(+), 71 deletions(-)

diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -55,13 +55,26 @@ pub fn Compiler(size: comptime_int) type { enclosing: ?*Self, upvalues: [upvalues_size]Upvalue, upvaluesCount: u8, + currentClass: ?*Class, + + const Class = struct { + enclosing: ?*Class, + }; const Self = @This(); + pub const Stack = size; pub const Upvalue = struct { + pub const Type = enum(u8) { + local = 0, + remote = 1, + empty = 2, + }; + index: u8, - isLocal: bool, + type: Type, }; + const upvalues_size = std.math.maxInt(u8); const Local = struct { @@ -115,9 +128,10 @@ pub fn Compiler(size: comptime_int) type { 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.function, 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 ), // zig fmt: on }; @@ -362,6 +376,14 @@ pub fn Compiler(size: comptime_int) type { } } + fn this(self: *Self, _: bool) void { + if (self.currentClass) |_| { + self.variable(false); + } else { + self.errorAtPrevious("Can't use 'this' outside of class."); + } + } + fn variable(self: *Self, canAssign: bool) void { self.namedVariable(self.previous, canAssign); } @@ -390,20 +412,22 @@ pub fn Compiler(size: comptime_int) type { if (self.enclosing) |enclosing| { if (enclosing.resolveLocal(name)) |local| { enclosing.locals[local].captured = true; - return self.addUpvalue(local, true) catch null; + return self.addUpvalue(local, .local) catch null; } else if (enclosing.resolveUpvalue(name)) |upvalue| { - return self.addUpvalue(upvalue, false) catch null; + return self.addUpvalue(upvalue, .remote) catch null; } } return null; } - fn addUpvalue(self: *Self, idx: u8, isLocal: bool) !u8 { + fn addUpvalue(self: *Self, idx: u8, tp: Upvalue.Type) !u8 { const count = self.upvaluesCount; - for (self.upvalues[0..count], 0..) |upvalue, i| { - if (upvalue.index == idx and upvalue.isLocal == isLocal) { - return @intCast(i); + if (tp != .empty) { + for (self.upvalues[0..count], 0..) |upvalue, i| { + if (upvalue.index == idx and upvalue.type == tp) { + return @intCast(i); + } } } @@ -413,7 +437,7 @@ pub fn Compiler(size: comptime_int) type { return self.lastError; } - self.upvalues[count] = .{ .index = idx, .isLocal = isLocal }; + self.upvalues[count] = .{ .index = idx, .type = tp }; self.upvaluesCount += 1; return count; } @@ -554,16 +578,21 @@ pub fn Compiler(size: comptime_int) type { }; self.emit(OP.CONSTANT, self.makeConstant(Value.init(cls))); self.consume(Token.LEFT_BRACE, "Expect '{' before class body"); + + var curcls = Class{ .enclosing = self.currentClass }; + 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"); } fn method(self: *Self) void { self.consume(Token.IDENTIFIER, "Expect method name"); const constant = self.identifierConstant(self.previous) catch return; - self.function(false); + self.function(true); self.emit(OP.METHOD, constant); } @@ -574,7 +603,11 @@ pub fn Compiler(size: comptime_int) type { self.defineVariable(global, true); } - fn function(self: *Self, _: bool) void { + fn funExpression(self: *Self, _: bool) void { + self.function(false); + } + + fn function(self: *Self, isMethod: bool) void { const chunk = self.objects.emplace(.Chunk, {}) catch |err| { self.errorAtPrevious("Couldn't allocate chunk"); self.lastError = err; @@ -605,6 +638,18 @@ pub fn Compiler(size: comptime_int) type { } } compiler.consume(Token.RIGHT_PAREN, "Expect ')' after parameters"); + + if (isMethod) { + _ = compiler.addUpvalue(0, .empty) catch return; + compiler.locals[0] = .{ + .name = scanner.Token{ .type = Token.THIS, .lexeme = "this", .line = -1, .column = 0 }, + .depth = compiler.scopeDepth, + }; + compiler.emit(OP.GET_UPVALUE, 0); + compiler.emit(OP.SET_LOCAL, 0); + compiler.emitOP(OP.POP); + } + compiler.consume(Token.LEFT_BRACE, "Expect '{' before function body"); compiler.block(); @@ -614,22 +659,25 @@ pub fn Compiler(size: comptime_int) type { if (compiler.hadError) { self.lastError = compiler.lastError; } else { - const fun = self.objects.emplace(.Function, .{ - .type = .Function, - .chunk = compiler.end() catch return, - .arity = arity, - }) catch |err| { - self.errorAtPrevious("Couldn't allocate function"); - self.lastError = err; - return; - }; - self.emit(OP.CONSTANT, self.makeConstant(Value.init(fun.cast()))); - - if (compiler.upvaluesCount != 0) { - self.emit(OP.CLOSURE, compiler.upvaluesCount); + const endchunk = compiler.end() catch return; + if (compiler.upvaluesCount == 0) { + const fun = self.objects.emplace(.Function, .{ + .type = .Function, + .chunk = endchunk, + .arity = arity, + }) catch |err| { + self.errorAtPrevious("Couldn't allocate function"); + self.lastError = err; + return; + }; + self.emit(OP.CONSTANT, self.makeConstant(Value.init(fun.cast()))); + } else { + self.emit(OP.CONSTANT, self.makeConstant(Value.init(endchunk.cast()))); + self.emit(OP.CLOSURE, arity); + self.emitByte(compiler.upvaluesCount); for (compiler.upvalues[0..compiler.upvaluesCount]) |upvalue| { - self.emitByte(if (upvalue.isLocal) 1 else 0); + self.emitByte(@intFromEnum(upvalue.type)); self.emitByte(upvalue.index); } } @@ -1026,8 +1074,9 @@ pub fn Compiler(size: comptime_int) type { .localCount = 1, .scopeDepth = 0, .enclosing = null, - .upvalues = @splat(Upvalue{ .index = 0, .isLocal = false }), + .upvalues = @splat(Upvalue{ .index = 0, .type = .empty }), .upvaluesCount = 0, + .currentClass = null, }; self.locals[0].depth = 0; @@ -1038,6 +1087,7 @@ pub fn Compiler(size: comptime_int) type { var enclosed = try Self.init(enclosing.scanner, enclosing.objects, chunk); enclosed.current = enclosing.current; enclosed.enclosing = enclosing; + enclosed.currentClass = enclosing.currentClass; enclosed.beginScope(); return enclosed; diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig @@ -5,6 +5,7 @@ const value = @import("value.zig"); const Obj = @import("gc.zig").GC.Obj; const Error = Obj.Error; const OP = @import("op.zig").OP; +const Compiler = @import("vm.zig").VM.Compiler; const print = std.debug.print; pub fn disassembleChunk(ch: *const Obj.Chunk) Error!void { @@ -73,7 +74,7 @@ fn _disassembleInstruction(ch: *const Obj.Chunk, offset: usize, print_fn: bool) @intFromEnum(OP.SET_INDEX) => simpleInstruction(name, offset), @intFromEnum(OP.GET_INDEX) => simpleInstruction(name, offset), @intFromEnum(OP.CALL) => try byteInstruction(name, ch, offset), - @intFromEnum(OP.CLOSURE) => try byteInstruction(name, ch, offset), + @intFromEnum(OP.CLOSURE) => try closureInstruction(name, ch, offset), @intFromEnum(OP.CLOSE_UPVALUE) => simpleInstruction(name, offset), else => blk: { print("Unknown opcode {d} {s}\n", .{ op, name }); @@ -91,9 +92,13 @@ fn constantInstruction(name: []const u8, ch: *const Obj.Chunk, offset: usize, pr const constant = try ch.code.ptr().get(offset + 1); const constval = try ch.constants.ptr().get(constant); print("{s:<32} {d:4} '{f}'\n", .{ name, constant, constval }); - if (print_fn) if (constval.cast_if(Obj.Type.Function)) |function| { - try disassembleChunk(function.chunk.ptr()); - }; + if (print_fn) { + if (constval.cast_if(Obj.Type.Function)) |function| { + try disassembleChunk(function.chunk.ptr()); + } else if (constval.cast_if(Obj.Type.Chunk)) |chunk| { + try disassembleChunk(chunk); + } + } return offset + 2; } @@ -111,21 +116,20 @@ fn jumpInstruction(name: []const u8, sign: bool, ch: *const Obj.Chunk, offset: u return offset + 3; } -fn closureInstruction(name: []const u8, ch: *const Obj.Chunk, offset: usize, print_fn: bool) Error!usize { +fn closureInstruction(name: []const u8, ch: *const Obj.Chunk, offset: usize) Error!usize { var off = offset + 1; - const constant = try ch.code.ptr().get(off); - const val = try ch.constants.ptr().get(constant); - const function = try val.obj.cast(.Function); - print("{s:<32} {d:4} '{f}'\n", .{ name, constant, function }); - for (0..function.upvalue_count) |_| { - const isLocal = try ch.code.ptr().get(off + 1); - const idx = try ch.code.ptr().get(off + 2); + const arity = try ch.code.ptr().get(off); + const count = try ch.code.ptr().get(off + 1); + off += 2; + + print("{s:<32} {d:4} {d}\n", .{ name, arity, count }); + for (0..count) |_| { + const tp = try ch.code.ptr().get(off); + const idx = try ch.code.ptr().get(off + 1); try print_offset(ch, off + 1); - print("{s:<38}|-> {s} {d}\n", .{ "", if (isLocal == 1) "local" else "upvalue", idx }); + print("{s:<38}|-> {s} {d}\n", .{ "", @tagName(@as(Compiler.Upvalue.Type, @enumFromInt(tp))), idx }); off += 2; } - if (print_fn) - try disassembleChunk(function.chunk.ptr()); return off + 1; } diff --git a/zlox/src/gc.zig b/zlox/src/gc.zig @@ -101,7 +101,7 @@ pub const GC = struct { if (obj.upvalues.ptr()) |upvalues| for (upvalues) |upvalue_ptr| if (upvalue_ptr) |upvalue| - self.mark("c", upvalue); + self.mark("f", upvalue); }, *Obj.Chunk => { for (obj.constants.ptr().slice()) |constant| diff --git a/zlox/src/obj::upvalue.zig b/zlox/src/obj::upvalue.zig @@ -12,7 +12,7 @@ pub fn Upvalue(fields: anytype) type { return packed struct { const Self = @This(); - pub const Arg = struct { val: *Value, slot: u8 }; + pub const Arg = struct { val: *Value, slot: u8, closed: bool = false }; pub const Error = error{OutOfMemory}; obj: Super, @@ -24,8 +24,11 @@ pub fn Upvalue(fields: anytype) type { const self: *Self = try allocator.create(Self); self.* = Self{ .obj = Super.make(Self), - .location = Packed(*Value).init(arg.val), - .closed = false, + .location = if (arg.closed) + try Packed(*Value).create2(allocator, arg.val.*) + else + Packed(*Value).init(arg.val), + .closed = arg.closed, .slot = arg.slot, }; return self; diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -22,6 +22,10 @@ pub const VM = struct { globals: Globals, allocator: std.mem.Allocator, + pub const CALLSTACK = 64; + pub const STACK = 256; + pub const Compiler = compiler.Compiler(STACK); + pub const Global = struct { val: Value, con: bool, @@ -110,17 +114,14 @@ pub const VM = struct { } pub fn interpret(self: *@This(), source: []const u8, dbg: bool) InterpreterError!void { - const callstack_size = 64; - const stack_size = 256; - try self.objects.push_callback(&VM.gc_callback, self); defer self.objects.pop_callback(); - const chunk = try compiler.Compiler(stack_size).compile(source, &self.objects); + const chunk = try Compiler.compile(source, &self.objects); if (dbg) try debug.disassembleChunk(chunk); - try Interpreter(callstack_size, stack_size).run(self, chunk, dbg); + try Interpreter(CALLSTACK, Compiler.Stack).run(self, chunk, dbg); } fn Interpreter(callstack_size: comptime_int, stack_size: comptime_int) type { @@ -133,7 +134,7 @@ pub const VM = struct { stackTop: [*]Value, stack: [stack_size]Value, vm: *VM, - open_upvalues: List, + upvalues: List, pub fn run(vm: *VM, chunk: *Obj.Chunk, dbg: bool) InterpreterError!void { var self = @This(){ @@ -142,14 +143,14 @@ pub const VM = struct { .stack = @splat(Value.init({})), .stackTop = undefined, .vm = vm, - .open_upvalues = List.init(vm.allocator), + .upvalues = List.init(vm.allocator), }; self.stackTop = &self.stack; try vm.objects.push_callback(&Self.gc_callback, &self); defer vm.objects.pop_callback(); - defer self.open_upvalues.free(); + defer self.upvalues.free(); self.push(Value.init(chunk.cast())); @@ -174,7 +175,7 @@ pub const VM = struct { self.vm.objects.mark("F", self.frames[frame_idx].callee); } - var iter = self.open_upvalues.iter(); + var iter = self.upvalues.iter(); while (iter.next()) |upval| { self.vm.objects.mark("U", upval); } @@ -279,7 +280,7 @@ pub const VM = struct { } fn captureUpvalue(self: *@This(), slot: u8) !*Obj.Upvalue { - var iter = self.open_upvalues.iter(); + var iter = self.upvalues.iter(); while (iter.next()) |val| { if (val.slot == slot) @@ -295,7 +296,7 @@ pub const VM = struct { } fn closeUpvalues(self: *@This(), slot: u8) !void { - var iter = self.open_upvalues.iter(); + var iter = self.upvalues.iter(); while (iter.next()) |upval| { if (upval.slot < slot) break; @@ -392,11 +393,25 @@ pub const VM = struct { self.frame().slots[self.read_byte()] = self.peek(0); }, @intFromEnum(OP.GET_PROPERTY) => { - if (self.peek(0).cast_if(Obj.Type.Instance)) |instance| { + var val = self.peek(0); + if (val.cast_if(Obj.Type.Instance)) |instance| { const field = self.read_string(); - const prop = instance.fields.ptr().get(field) catch { - self.runtimeError("Undefined property '{f}'", .{field}); - return InterpreterError.RuntimeError; + const prop = instance.fields.ptr().get(field) catch blk: { + const method = instance.cls.ptr().methods.ptr().get(field) catch { + self.runtimeError("Undefined property '{f}'", .{field}); + return InterpreterError.RuntimeError; + }; + if (method.upvalues.ptr()) |upvalues| { + if (upvalues[0] == null) { + upvalues[0] = try self.vm.objects.emplace(.Upvalue, .{ + .val = &val, + .slot = 0, + .closed = true, + }); + method.type = .Method; + } + } + break :blk Value.init(method.cast()); }; _ = self.pop(); self.push(prop); @@ -438,12 +453,12 @@ pub const VM = struct { @intFromEnum(OP.GET_UPVALUE) => { const closure = self.frame().callee; const index = self.read_byte(); - self.push(closure.upvalues.at(index).?.location.ptr().*); + self.push(closure.upvalues.at(index).?.location.get()); }, @intFromEnum(OP.SET_UPVALUE) => { const closure = self.frame().callee; const index = self.read_byte(); - closure.upvalues.at(index).?.location.ptr().* = self.peek(0); + closure.upvalues.at(index).?.location.set(self.peek(0)); }, @intFromEnum(OP.CLOSE_UPVALUE) => { try self.closeUpvalues(self.current_slot()); @@ -500,24 +515,29 @@ pub const VM = struct { try self.callValue(self.peek(argCount), argCount); }, @intFromEnum(OP.CLOSURE) => { + const arity = self.read_byte(); const count = self.read_byte(); - const function = try self.pop().obj.cast(.Function); + const chunk = try self.pop().obj.cast(.Chunk); + const closure = try self.vm.objects.emplace(.Function, .{ .type = .Closure, - .chunk = function.chunk.ptr(), - .arity = function.arity, + .chunk = chunk, + .arity = arity, .upvalues = count, }); + self.push(Value.init(closure.cast())); for (closure.upvalues.ptr().?) |*upvalue| { - const isLocal = self.read_byte(); + const tp = self.read_byte(); const slot = self.read_byte(); - if (isLocal == 1) { - upvalue.* = try self.captureUpvalue(slot); - } else { - upvalue.* = self.frame().callee.upvalues.at(slot); - } + const U = Compiler.Upvalue.Type; + upvalue.* = switch (tp) { + @intFromEnum(U.local) => try self.captureUpvalue(slot), + @intFromEnum(U.remote) => self.frame().callee.upvalues.at(slot), + @intFromEnum(U.empty) => null, + else => return InterpreterError.RuntimeError, + }; } }, @intFromEnum(OP.METHOD) => { diff --git a/zlox/test/nested.lox b/zlox/test/nested.lox @@ -0,0 +1,11 @@ +class Nested { + method() { + fun function() { + print this; + } + + function(); + } +} + +Nested().method(); diff --git a/zlox/test/scone.lox b/zlox/test/scone.lox @@ -0,0 +1,8 @@ +class Scone { + topping(first, second) { + print "scone with " + first + " and " + second; + } +} + +var scone = Scone(); +scone.topping("berries", "cream");