DzLox

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

commit 4cc7e5f654996bb0fdd4e5834ad7cb93857334de
parent 6a04bf164625fc290aad19d49c88f28c9d44a408
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Tue,  1 Sep 2026 18:54:54 +0200

Objects && Instances

Diffstat:
Mzlox/src/chunk.zig | 2++
Mzlox/src/compiler.zig | 38+++++++++++++++++++++++++++++++++++++-
Mzlox/src/debug.zig | 2++
Mzlox/src/gc.zig | 13++++++++++---
Mzlox/src/obj.zig | 18++++++++++++++++--
Azlox/src/obj::class.zig | 42++++++++++++++++++++++++++++++++++++++++++
Azlox/src/obj::instance.zig | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mzlox/src/obj::table.zig | 7++-----
Mzlox/src/vm.zig | 83++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
9 files changed, 229 insertions(+), 31 deletions(-)

diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig @@ -27,6 +27,8 @@ pub const OP = enum(u8) { SET_GLOBAL, GET_LOCAL, SET_LOCAL, + GET_PROPERTY, + SET_PROPERTY, GET_UPVALUE, SET_UPVALUE, JUMP_IF_FALSE, diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -116,6 +116,8 @@ pub fn Compiler(size: comptime_int) type { 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.CLASS => R(S.class, null, P.NONE ), + T.DOT => R(null, S.dot, P.CALL ), else => R(null, null, P.NONE ), // zig fmt: on }; @@ -269,6 +271,18 @@ pub fn Compiler(size: comptime_int) type { self.emit(OP.CALL, argCount); } + fn dot(self: *Self, canAssign: bool) void { + self.consume(Token.IDENTIFIER, "Expect property name after '.'"); + const name = self.identifierConstant(self.previous) catch return; + + if (canAssign and self.match(Token.EQUAL)) { + self.expression(); + self.emit(OP.SET_PROPERTY, name); + } else { + self.emit(OP.GET_PROPERTY, name); + } + } + fn argumentList(self: *Self) u8 { var argCount: u8 = 0; if (!self.check(Token.RIGHT_PAREN)) { @@ -514,7 +528,9 @@ pub fn Compiler(size: comptime_int) type { } fn declaration(self: *Self) void { - if (self.match(Token.FUN)) { + if (self.match(Token.CLASS)) { + self.classDeclaration(); + } else if (self.match(Token.FUN)) { self.funDeclaration(); } else if (self.match(Token.VAR)) { self.varDeclaration(); @@ -527,6 +543,26 @@ pub fn Compiler(size: comptime_int) type { if (self.panicMode) self.synchronize(); } + fn classDeclaration(self: *Self) void { + const global = self.parseVariable("Expect class name.", true) catch return; + self.markInitialized(); + self.class(false); + self.defineVariable(global, true); + } + + fn class(self: *Self, _: bool) void { + self.consume(Token.LEFT_BRACE, "Expect '{' before class body"); + self.consume(Token.RIGHT_BRACE, "Expect '}' after class body"); + + const cls = self.objects.emplace_cast(Obj.Type.Class, {}) catch |err| { + self.errorAtPrevious("Couldn't allocate class"); + self.lastError = err; + return; + }; + + self.emit(OP.CONSTANT, self.makeConstant(Value.init(cls))); + } + fn funDeclaration(self: *Self) void { const global = self.parseVariable("Expect function name.", true) catch return; self.markInitialized(); diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig @@ -63,6 +63,8 @@ fn _disassembleInstruction(ch: *const chunk.Chunk, offset: usize, print_fn: bool @intFromEnum(OP.SET_LOCAL) => try byteInstruction(name, ch, offset), @intFromEnum(OP.GET_UPVALUE) => try byteInstruction(name, ch, offset), @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.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), diff --git a/zlox/src/gc.zig b/zlox/src/gc.zig @@ -88,8 +88,6 @@ pub const GC = struct { const T = @TypeOf(obj); switch (T) { - *Obj.String => {}, - *Obj.Native => {}, *Obj.Table => { obj.table.ptr().for_each(self, struct { pub fn fun(s: *Self, key: Obj.Table.Table.Key, val: Obj.Table.Table.Value) void { @@ -118,7 +116,16 @@ pub const GC = struct { if (obj.closed) self.mark("u", obj.location.get()); }, - else => @compileError("Invalid type: " ++ @typeName(T)), + *Obj.Instance => { + self.mark("i", obj.cls.ptr()); + obj.fields.ptr().for_each(self, struct { + pub fn fun(s: *Self, key: Obj.Instance.Fields.Key, val: Obj.Instance.Fields.Value) void { + s.mark("i", key); + s.mark("i", val); + } + }.fun); + }, + else => {}, } } diff --git a/zlox/src/obj.zig b/zlox/src/obj.zig @@ -17,8 +17,20 @@ pub fn Obj(fields: anytype) type { pub const Native = @import("obj::native.zig").Native(fields); pub const Closure = @import("obj::closure.zig").Closure(fields); pub const Upvalue = @import("obj::upvalue.zig").Upvalue(fields); - - pub const Error = error{IllegalCastError} || List.Error || String.Error || Table.Error || Function.Error || Native.Error || List.Error || Closure.Error || Upvalue.Error; + 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 // + || String.Error // + || Table.Error // + || Function.Error // + || Native.Error // + || List.Error // + || Closure.Error // + || Upvalue.Error // + || Class.Error // + || Instance.Error; pub const Type = enum(u8) { String, @@ -28,6 +40,8 @@ pub fn Obj(fields: anytype) type { List, Closure, Upvalue, + Class, + Instance, pub fn get(self: @This()) type { return @field(Self, @tagName(self)); diff --git a/zlox/src/obj::class.zig b/zlox/src/obj::class.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +const utils = @import("lib::utils.zig"); + +const Obj = @import("obj.zig").Obj; + +pub fn Class(fields: anytype) type { + const Super = Obj(fields); + + return packed struct { + const Self = @This(); + + pub const Arg = void; + pub const Error = error{OutOfMemory}; + + obj: Super, + + pub fn init(_: Arg, allocator: std.mem.Allocator) Error!*Self { + const self: *Self = try allocator.create(Self); + self.* = Self{ + .obj = Super.make(Self), + }; + return 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("<Class>"); + } + + 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::instance.zig b/zlox/src/obj::instance.zig @@ -0,0 +1,55 @@ +const std = @import("std"); + +const utils = @import("lib::utils.zig"); +const table = @import("lib::table.zig"); +const hash = @import("hash.zig"); + +const Packed = @import("lib::packed.zig").Packed; +const Value = @import("value.zig").Value; +const Obj = @import("obj.zig").Obj; + +pub fn Instance(fields: anytype) type { + const Super = Obj(fields); + + return packed struct { + const Self = @This(); + + pub const Arg = *Super.Class; + pub const Error = error{OutOfMemory}; + + pub const Fields = table.Table(*Super.String, Value, hash.hash_t(*Super.String), Super.String.eql); + + obj: Super, + cls: Packed(*Super.Class), + fields: Packed(*Fields), + + pub fn init(cls: Arg, allocator: std.mem.Allocator) Error!*Self { + const self: *Self = try allocator.create(Self); + self.* = Self{ + .obj = Super.make(Self), + .cls = Packed(*Super.Class).init(cls), + .fields = try Packed(*Self.Fields).create(allocator), + }; + self.fields.set(Self.Fields.init(allocator)); + return 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; + } + + pub fn free(self: *const Self, allocator: std.mem.Allocator) void { + self.fields.ptr().deinit(); + self.fields.destroy(allocator); + allocator.destroy(self); + } + }; +} diff --git a/zlox/src/obj::table.zig b/zlox/src/obj::table.zig @@ -19,14 +19,12 @@ pub fn Table(fields: anytype) type { obj: Super, table: Packed(*Self.Table), - len: usize, pub fn init(_: Arg, allocator: std.mem.Allocator) Error!*Self { const self: *Self = try allocator.create(Self); self.* = Self{ .obj = Super.make(Self), .table = try Packed(*Self.Table).create(allocator), - .len = 0, }; self.table.set(Self.Table.init(allocator)); return self; @@ -37,7 +35,6 @@ pub fn Table(fields: anytype) type { } pub fn set(self: *Self, key: Value, val: Value) Error!bool { - self.len += 1; return self.table.ptr().set(key, val); } @@ -46,7 +43,7 @@ pub fn Table(fields: anytype) type { } pub fn delete(self: *Self, key: Value) void { - if (self.table.ptr().delete(key)) self.len -= 1; + _ = self.table.ptr().delete(key); } pub fn format(self: *const Self, writer: *std.Io.Writer) !void { @@ -64,7 +61,7 @@ pub fn Table(fields: anytype) type { } }; - var printer = Printer{ .writer = writer, .count = self.len }; + var printer = Printer{ .writer = writer, .count = self.table.ptr().count }; _ = try writer.write("["); if (self.table.ptr().count > 0) { try self.table.ptr().for_each_try(&printer, Printer.print); diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -225,19 +225,34 @@ pub const VM = struct { return (self.stackTop - (1 + distance))[0]; } + fn pook(self: *@This(), distance: usize, val: Value) void { + (self.stackTop - (1 + distance))[0] = val; + } + fn callValue(self: *@This(), callee: Value, argCount: u8) !void { - if (callee.is(Obj.Type.Function)) { - try self.callFunction(callee.obj.cast(.Function) catch unreachable, argCount); - } else if (callee.is(Obj.Type.Closure)) { - try self.callClosure(callee.obj.cast(.Closure) catch unreachable, argCount); - } else if (callee.is(Obj.Type.Native)) { - try self.callNative(callee.obj.cast(.Native) catch unreachable, argCount); + if (callee.cast_if(Obj.Type.Function)) |fun| { + try self.callFunction(fun, argCount); + } else if (callee.cast_if(Obj.Type.Closure)) |clo| { + try self.callClosure(clo, argCount); + } else if (callee.cast_if(Obj.Type.Native)) |nat| { + try self.callNative(nat, argCount); + } else if (callee.cast_if(Obj.Type.Class)) |cls| { + try self.callClass(cls, argCount); } else { self.runtimeError("Can only call functions and classes", .{}); return InterpreterError.RuntimeError; } } + fn callClass(self: *@This(), callee: *Obj.Class, argCount: u8) !void { + if (argCount != 0) { + self.runtimeError("Expected {d} arguments but got {d}", .{ 0, argCount }); + return InterpreterError.RuntimeError; + } + + self.pook(argCount, Value.init(try self.vm.objects.emplace_cast(.Instance, callee))); + } + fn callClosure(self: *@This(), callee: *Obj.Closure, argCount: u8) !void { if (argCount != callee.function.ptr().arity) { self.runtimeError("Expected {d} arguments but got {d}", .{ callee.function.ptr().arity, argCount }); @@ -384,6 +399,31 @@ pub const VM = struct { @intFromEnum(OP.SET_LOCAL) => { self.frame().slots[self.read_byte()] = self.peek(0); }, + @intFromEnum(OP.GET_PROPERTY) => { + if (self.peek(0).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; + }; + _ = self.pop(); + self.push(prop); + } else { + self.runtimeError("Only instances have properties, found: {s}", .{self.peek(0).typeName()}); + return InterpreterError.RuntimeError; + } + }, + @intFromEnum(OP.SET_PROPERTY) => { + if (self.peek(1).cast_if(Obj.Type.Instance)) |instance| { + _ = try instance.fields.ptr().set(self.read_string(), self.peek(0)); + const val = self.pop(); + _ = self.pop(); + self.push(val); + } else { + self.runtimeError("Only instances have properties, found: {s}", .{self.peek(0).typeName()}); + return InterpreterError.RuntimeError; + } + }, @intFromEnum(OP.GET_GLOBAL) => { const name = self.read_string(); const global = self.vm.globals.get(name) catch { @@ -419,32 +459,34 @@ pub const VM = struct { }, @intFromEnum(OP.GET_INDEX) => { const key = self.pop(); - const obj = self.pop(); + const col = self.pop(); var pushed = false; - if (obj.is(Value.obj)) { - switch (obj.obj.type) { - .Function, .Native, .Closure, .Upvalue => {}, - inline else => |tp| { - self.push((obj.obj.cast(tp) catch unreachable).get(key) catch Value.init({})); + + if (col.cast_if(Value.obj)) |obj| { + switch (obj.type) { + inline .Table, .String, .List => |tp| { + self.push((obj.cast(tp) catch unreachable).get(key) catch Value.init({})); pushed = true; }, + else => {}, } } + if (!pushed) { - self.runtimeError("Cannot index a value of type {s}", .{obj.typeName()}); + self.runtimeError("Cannot index a value of type {s}", .{col.typeName()}); return InterpreterError.RuntimeError; } }, @intFromEnum(OP.SET_INDEX) => { const val = self.pop(); const key = self.pop(); - const obj = self.pop(); + const col = self.pop(); var pushed = false; - if (obj.is(Value.obj)) { - switch (obj.obj.type) { - .Function, .Native, .Closure, .Upvalue, .String => {}, - inline else => |tp| { - var m = obj.obj.cast(tp) catch unreachable; + + if (col.cast_if(Value.obj)) |obj| { + switch (obj.type) { + inline .Table, .List => |tp| { + var m = obj.cast(tp) catch unreachable; if (val.is(Value.nil)) { m.delete(key); } else { @@ -452,10 +494,11 @@ pub const VM = struct { } pushed = true; }, + else => {}, } } if (!pushed) { - self.runtimeError("Cannot index a value of type {s}", .{obj.typeName()}); + self.runtimeError("Cannot index a value of type {s}", .{col.typeName()}); return InterpreterError.RuntimeError; } self.push(val);