DzLox

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

commit e4a1d0de6da1c2569240e32806514596d6b78c70
parent 6ebfe7eb920247e8c0215ca568dec9634a388f65
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Wed,  9 Sep 2026 00:32:06 +0200

Rewrite packed and array

Diffstat:
Mzlox/src/compiler.zig | 31+++++++++++++------------------
Mzlox/src/debug.zig | 32+++++++++++++++++++-------------
Mzlox/src/gc.zig | 13++++++-------
Mzlox/src/lib::array.zig | 109+++++++++++++++++++++++++++++++------------------------------------------------
Mzlox/src/lib::list.zig | 2+-
Mzlox/src/lib::packed.zig | 249+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mzlox/src/lib::utils.zig | 22++++++++++++++++++++--
Mzlox/src/obj::chunk.zig | 19++++++++-----------
Mzlox/src/obj::class.zig | 3+--
Mzlox/src/obj::function.zig | 8+++++---
Mzlox/src/obj::instance.zig | 27+++++++++++++++++++++++++--
Mzlox/src/obj::list.zig | 3+--
Mzlox/src/obj::native.zig | 12++----------
Mzlox/src/obj::table.zig | 3+--
Mzlox/src/obj::upvalue.zig | 17+++++++++++------
Mzlox/src/value.zig | 2--
Mzlox/src/vm.zig | 59+++++++++++++++++++++++++++++------------------------------
Azlox/test/brunch.lox | 5+++++
Azlox/test/this.lox | 16++++++++++++++++
19 files changed, 341 insertions(+), 291 deletions(-)

diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -9,7 +9,6 @@ const value = @import("value.zig"); const Token = scanner.TokenType; const OP = @import("op.zig").OP; const Value = value.Value; -const ValueArray = value.ValueArray; const GC = @import("gc.zig").GC; const Obj = GC.Obj; const Chunk = Obj.Chunk; @@ -65,11 +64,7 @@ pub fn Compiler(size: comptime_int) type { pub const Stack = size; pub const Upvalue = struct { - pub const Type = enum(u8) { - local = 0, - remote = 1, - empty = 2, - }; + pub const Type = enum(u8) { local = 0, remote = 1 }; index: u8, type: Type, @@ -354,12 +349,10 @@ pub fn Compiler(size: comptime_int) type { if (isList) { self.chunk.code.ptr().set(offset, self.makeObj(.Native, .{ .fun = vm_native.list, - .type = .Literal, }) catch return) catch return; } else { self.chunk.code.ptr().set(offset, self.makeObj(.Native, .{ .fun = vm_native.table, - .type = .Literal, }) catch return) catch return; } self.emit(OP.CALL, argCount); @@ -423,11 +416,9 @@ pub fn Compiler(size: comptime_int) type { fn addUpvalue(self: *Self, idx: u8, tp: Upvalue.Type) !u8 { const count = self.upvaluesCount; - if (tp != .empty) { - for (self.upvalues[0..count], 0..) |upvalue, i| { - if (upvalue.index == idx and upvalue.type == tp) { - return @intCast(i); - } + for (self.upvalues[0..count], 0..) |upvalue, i| { + if (upvalue.index == idx and upvalue.type == tp) { + return @intCast(i); } } @@ -639,13 +630,15 @@ pub fn Compiler(size: comptime_int) type { } compiler.consume(Token.RIGHT_PAREN, "Expect ')' after parameters"); + var offset: usize = 0; + 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); + offset = compiler.chunk.code.ptr().len - 1; compiler.emit(OP.SET_LOCAL, 0); compiler.emitOP(OP.POP); } @@ -654,6 +647,9 @@ pub fn Compiler(size: comptime_int) type { compiler.block(); + if (isMethod) + compiler.chunk.code.ptr().set(offset, compiler.upvaluesCount) catch unreachable; + self.current = compiler.current; if (compiler.hadError) { @@ -672,9 +668,9 @@ pub fn Compiler(size: comptime_int) type { }; 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.emit(OP.CLOSURE, self.makeConstant(Value.init(endchunk.cast()))); + self.emitByte(arity); self.emitByte(compiler.upvaluesCount); for (compiler.upvalues[0..compiler.upvaluesCount]) |upvalue| { self.emitByte(@intFromEnum(upvalue.type)); @@ -832,7 +828,6 @@ pub fn Compiler(size: comptime_int) type { self.emitObj(.Native, Obj.Native.Arg{ .fun = vm_native.table, - .type = .Literal, }) catch return; var jumpOver = self.emitJump(OP.JUMP); @@ -1074,7 +1069,7 @@ pub fn Compiler(size: comptime_int) type { .localCount = 1, .scopeDepth = 0, .enclosing = null, - .upvalues = @splat(Upvalue{ .index = 0, .type = .empty }), + .upvalues = @splat(Upvalue{ .index = 0, .type = .local }), .upvaluesCount = 0, .currentClass = null, }; diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig @@ -21,8 +21,8 @@ pub fn disassembleChunk(ch: *const Obj.Chunk) Error!void { pub fn print_offset(ch: *const Obj.Chunk, offset: usize) !void { print("{d:0>4} ", .{offset}); - const line = try ch.lines.ptr().get(offset); - if (offset > 0 and line == (try ch.lines.ptr().get(offset - 1))) { + const line = ch.lines.ptr().get(offset).?; + if (offset > 0 and line == ch.lines.ptr().get(offset - 1).?) { print(" | ", .{}); } else { print("{d:4} ", .{line}); @@ -36,7 +36,7 @@ pub fn disassembleInstruction(ch: *const Obj.Chunk, offset: usize) Error!usize { fn _disassembleInstruction(ch: *const Obj.Chunk, offset: usize, print_fn: bool) Error!usize { try print_offset(ch, offset); - const op = try ch.code.ptr().get(offset); + const op = ch.code.ptr().get(offset).?; const name = @tagName(@as(OP, @enumFromInt(op))); return switch (op) { @@ -89,8 +89,8 @@ fn simpleInstruction(name: []const u8, offset: usize) usize { } fn constantInstruction(name: []const u8, ch: *const Obj.Chunk, offset: usize, print_fn: bool) Error!usize { - const constant = try ch.code.ptr().get(offset + 1); - const constval = try ch.constants.ptr().get(constant); + const constant = ch.code.ptr().get(offset + 1).?; + const constval = 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| { @@ -103,13 +103,13 @@ fn constantInstruction(name: []const u8, ch: *const Obj.Chunk, offset: usize, pr } fn byteInstruction(name: []const u8, ch: *const Obj.Chunk, offset: usize) Error!usize { - print("{s:<32} {d:4}\n", .{ name, try ch.code.ptr().get(offset + 1) }); + print("{s:<32} {d:4}\n", .{ name, ch.code.ptr().get(offset + 1).? }); return offset + 2; } fn jumpInstruction(name: []const u8, sign: bool, ch: *const Obj.Chunk, offset: usize) !usize { - const msb: u16 = try ch.code.ptr().get(offset + 1); - const lsb: u16 = try ch.code.ptr().get(offset + 2); + const msb: u16 = ch.code.ptr().get(offset + 1).?; + const lsb: u16 = ch.code.ptr().get(offset + 2).?; const jump = (msb << 8) | lsb; print("{s:<32} {d:4} -> {d}\n", .{ name, offset, if (sign) offset + 3 + jump else offset + 3 - jump }); @@ -118,18 +118,24 @@ fn jumpInstruction(name: []const u8, sign: bool, ch: *const Obj.Chunk, offset: u fn closureInstruction(name: []const u8, ch: *const Obj.Chunk, offset: usize) Error!usize { var off = offset + 1; - const arity = try ch.code.ptr().get(off); - const count = try ch.code.ptr().get(off + 1); - off += 2; + const constant = ch.code.ptr().get(off).?; + const arity = ch.code.ptr().get(off + 1).?; + const count = ch.code.ptr().get(off + 2).?; + off += 3; + + const constval = ch.constants.ptr().get(constant).?; + const chunk = try constval.obj.cast(.Chunk); 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); + const tp = ch.code.ptr().get(off).?; + const idx = ch.code.ptr().get(off + 1).?; try print_offset(ch, off + 1); print("{s:<38}|-> {s} {d}\n", .{ "", @tagName(@as(Compiler.Upvalue.Type, @enumFromInt(tp))), idx }); off += 2; } + try disassembleChunk(chunk); + return off + 1; } diff --git a/zlox/src/gc.zig b/zlox/src/gc.zig @@ -98,10 +98,9 @@ pub const GC = struct { }, *Obj.Function => { self.mark("f", obj.chunk.ptr()); - if (obj.upvalues.ptr()) |upvalues| - for (upvalues) |upvalue_ptr| - if (upvalue_ptr) |upvalue| - self.mark("f", upvalue); + for (obj.upvalues.ptr()) |upvalue_ptr| + if (upvalue_ptr) |upvalue| + self.mark("f", upvalue); }, *Obj.Chunk => { for (obj.constants.ptr().slice()) |constant| @@ -249,14 +248,14 @@ pub const GC = struct { } pub fn deinit(self: *Self) void { - self.callbacks.free(); - self.greys.free(); + self.callbacks.deinit(); + self.greys.deinit(); while (true) { const el = self.objs.pop(0) catch break; dbg_obj("O", "free", el, false); el.free(self.allocator); } - self.objs.free(); + self.objs.deinit(); self.pool.free(); } }; diff --git a/zlox/src/lib::array.zig b/zlox/src/lib::array.zig @@ -1,16 +1,46 @@ const std = @import("std"); -pub fn Array(comptime T: type, comptime S: type, comptime size: S) type { +pub fn Array(comptime T: type) type { return struct { - len: S, + const Self = @This(); + const Init = 8; + + len: usize, data: []T, allocator: std.mem.Allocator, - pub fn init(allocator: std.mem.Allocator) !@This() { - return @This(){ .len = 0, .data = try allocator.alloc(T, size), .allocator = allocator }; + pub const Iterator = struct { + cur: ?[*]T = null, + end: ?[*]T = null, + + pub fn next(self: *Iterator) ?*const T { + if (self.cur) |cur| { + self.cur = if (cur == self.end) + null + else + cur + 1; + return cur[0]; + } + return null; + } + }; + + pub fn init(allocator: std.mem.Allocator) !Self { + return Self{ + .len = 0, + .data = try allocator.alloc(T, Init), + .allocator = allocator, + }; } - pub fn add(self: *@This(), val: T) !void { + pub fn iter(self: *Self) Iterator { + return if (self.len == 0) Iterator{} else Iterator{ + .cur = &self.data[0], + .end = &self.data[self.len - 1], + }; + } + + pub fn add(self: *Self, val: T) !void { if (self.data.len <= self.len) { self.data = try self.allocator.realloc(self.data, 2 * self.data.len); } @@ -18,81 +48,26 @@ pub fn Array(comptime T: type, comptime S: type, comptime size: S) type { self.len += 1; } - pub fn slice(self: *const @This()) []const T { + pub fn slice(self: *const Self) []const T { return self.data[0..self.len]; } - pub fn get(self: *const @This(), idx: S) !T { - if (idx >= self.len) - return error.IndexOutOfBounds; - return self.data[idx]; - } - - pub fn if_get(self: *const @This(), idx: S) ?T { - if (idx >= self.len) - return null; - return self.data[idx]; + pub fn get(self: *const Self, idx: usize) ?T { + return if (idx >= self.len) null else self.data[idx]; } - pub fn set(self: *const @This(), idx: S, val: T) !void { + pub fn set(self: *const Self, idx: usize, val: T) !void { if (idx >= self.len) return error.IndexOutOfBounds; self.data[idx] = val; } - pub fn last(self: *const @This()) !T { - if (self.len == 0) - return error.IndexOutOfBounds; - return self.data[self.len - 1]; - } - - pub fn if_last(self: *const @This()) ?T { - if (self.len == 0) - return null; - return self.data[self.len - 1]; + pub fn last(self: *const Self) ?T { + return if (self.len == 0) null else self.data[self.len - 1]; } - pub fn deinit(self: *@This()) void { + pub fn deinit(self: *Self) void { self.allocator.free(self.data); } }; } - -pub fn RLEArray(comptime T: type, comptime size: usize) type { - return struct { - array: Array(element, usize, size), - - const element = struct { val: T, run: usize }; - - pub fn init(allocator: std.mem.Allocator) !@This() { - return @This(){ - .array = try Array(element, usize, size).init(allocator), - }; - } - - pub fn add(self: *@This(), val: T) !void { - const last = self.array.last() catch { - try self.array.add(.{ .val = val, .run = 1 }); - return; - }; - if (val == last.val) { - self.array.data[self.array.len - 1].run += 1; - } else { - try self.array.add(.{ .val = val, .run = 1 }); - } - } - - pub fn get(self: *const @This(), idx: usize) !T { - var i: usize = 0; - var sum: usize = 0; - while (idx >= sum) : (i += 1) { - sum += (try self.array.get(i)).run; - } - if (i > 0) return self.array.data[i - 1].val else return (try self.array.get(i)).val; - } - - pub fn deinit(self: *@This()) void { - self.array.deinit(); - } - }; -} diff --git a/zlox/src/lib::list.zig b/zlox/src/lib::list.zig @@ -142,7 +142,7 @@ pub fn List(T: type) type { return true; } - pub fn free(self: *Self) void { + pub fn deinit(self: *Self) void { while (true) { _ = self.pop(-1) catch break; } diff --git a/zlox/src/lib::packed.zig b/zlox/src/lib::packed.zig @@ -2,163 +2,186 @@ const std = @import("std"); const utils = @import("lib::utils.zig"); -pub fn Packed(Type: type) type { - return packed struct { - const optional = utils.is_type(Type, "optional"); - - const Ptr = if (optional) @typeInfo(Type).optional.child else Type; +const Error = error{NullPointer}; - const Child = if (utils.is_type(Ptr, "pointer")) - @typeInfo(Ptr).pointer.child - else - @compileError("Expected pointer type, got " ++ @typeName(Ptr)); +pub fn Pointer(Type: type) type { + return packed struct { + const Self = @This(); - const slice = @typeInfo(Ptr).pointer.size == .slice; - const many = @typeInfo(Ptr).pointer.size == .many; + pub const optional = utils.is_type(Type, "optional"); - const Self = @This(); + pub const Ptr = if (optional) @typeInfo(Type).optional.child else Type; + pub const Val = @typeInfo(Ptr).pointer.child; + pub const Opt = if (optional) ?Val else Val; _ptr: usize, - _len: if (slice) usize else void, pub fn create(allocator: std.mem.Allocator) !Self { - return if (slice or many) - @compileError("Cannot create() a slice or many pointer, use alloc() instead") + return Self.init(try allocator.create(Val)); + } + + pub fn init(arg: Type) Self { + return Self{ + ._ptr = if (utils.optional(arg)) |val| + @intFromPtr(val) + else + 0, + }; + } + + pub fn ptr(self: Self) Type { + return if (optional and self._ptr == 0) + null else - Self.init(try allocator.create(Child)); + @ptrFromInt(self._ptr); } - pub fn create2(allocator: std.mem.Allocator, val: Child) !Self { - const self = try Self.create(allocator); - self.set(val); - return self; + pub fn get(self: Self) Opt { + return if (utils.optional(self.ptr())) |pointer| + pointer.* + else if (optional) + null + else + unreachable; } - pub fn alloc(allocator: std.mem.Allocator, count: usize) !Self { - return if (count == 0 and optional) - Self.init(null) - else if (count == 0) - @panic("Unable to create a non-optional 0 size pointer") - else if (slice) - Self.init(try allocator.alloc(Child, count)) - else if (many) - Self.init((try allocator.alloc(Child, count)).ptr) - else if (count == 1) - Self.create(allocator) + pub fn set(self: Self, val: Val) if (optional) Error.NullPointer!void else void { + if (utils.optional(self.ptr())) |pointer| + pointer.* = val + else if (optional) + return Error.NullPointer else - @panic("Cannot alloc() a single-item pointer with a count of more than one"); + unreachable; } - pub fn alloc2(allocator: std.mem.Allocator, count: usize, val: Child) !Self { - const self = try Self.alloc(allocator, count); - var p = if (optional) - if (self.ptr()) |p| - p - else - return self + pub fn destroy(self: Self, allocator: std.mem.Allocator) void { + if (utils.optional(self.ptr())) |pointer| + allocator.destroy(pointer); + } + }; +} + +pub fn Object(Type: type) type { + return packed struct { + const Self = @This(); + + const Ptr = Pointer(Type); + + _ptr: Ptr, + _own: bool, + + pub fn create(allocator: std.mem.Allocator) !Self { + var self = Self.init(try allocator.create(Ptr.Val)); + if (utils.fn_error(Ptr.Val.init)) |_| + self._ptr.set(try Ptr.Val.init(allocator)) else - self.ptr(); + self._ptr.set(Ptr.Val.init(allocator)); - for (0..count) |i| { - p[i] = val; - } + self._own = true; return self; } pub fn init(arg: Type) Self { return Self{ - ._ptr = if (optional) - if (arg) |val| - @intFromPtr(if (slice) val.ptr else val) - else - 0 - else - @intFromPtr(if (slice) arg.ptr else arg), - - ._len = if (slice) - if (optional) - if (arg) |val| - val.len - else - 0 - else - arg.len, + ._ptr = Ptr.init(arg), + ._own = false, }; } + pub fn own(self: Self) bool { + return self._own; + } + pub fn ptr(self: Self) Type { - return if (optional and self._ptr == 0) - null - else if (slice) - @as(utils.with_size(Ptr, .many), @ptrFromInt(self._ptr))[0..self._len] - else - @ptrFromInt(self._ptr); + return self._ptr.ptr(); } - pub fn get(self: Self) if (optional) ?Child else Child { - return if (slice or many) - @compileError("Cannot call get() on a slice or many pointer") - else if (optional and self._ptr == 0) - null - else - @as(Ptr, @ptrFromInt(self._ptr)).*; + pub fn get(self: Self) Ptr.Opt { + return self._ptr.get(); } - pub fn at(self: Self, idx: usize) if (optional and !utils.is_type(Child, "optional")) ?Child else Child { - return if (!slice and !many) - @compileError("Cannot call at() on a single-item pointer") - else if (optional) - if (self.ptr()) |val| - val[idx] - else - null - else - self.ptr()[idx]; + pub fn set(self: Self, val: Ptr.Val) if (Ptr.optional) Error.NullPointer!void else void { + self._ptr.set(val); } - pub fn len(self: Self) usize { - return if (slice) - self._len - else - @compileError("Cannot call len() on a non-slice pointer"); + pub fn destroy(self: Self, allocator: std.mem.Allocator) void { + if (self._own) + self._ptr.ptr().deinit(); + self._ptr.destroy(allocator); + } + }; +} + +pub fn Slice(Type: type) type { + return packed struct { + const Self = @This(); + const Ptr = Pointer(utils.with_size(Type, .many)); + + _ptr: Ptr, + _len: usize, + + pub fn create(allocator: std.mem.Allocator, count: usize) !Self { + return Self.init(try allocator.alloc(Ptr.Val, count)); } - pub fn free(self: Self, allocator: std.mem.Allocator, count: usize) void { - const pointer = if (optional) - if (self.ptr()) |_ptr| - _ptr + pub fn init(arg: Type) Self { + return Self{ + ._ptr = Ptr.init(arg.ptr), + ._len = if (utils.optional(arg)) |val| + val.len else - return - else - self.ptr(); + 0, + }; + } - if (many) { - allocator.free(pointer[0..count]); - } else if (slice) { - if (count != self._len) - @panic("Count has to be equal to the length of the slice"); + pub fn ptr(self: Self) Type { + return if (utils.optional(self._ptr.ptr())) |pointer| + pointer[0..self._len] + else if (Ptr.optional) + null + else + unreachable; + } - allocator.free(pointer); - } else { - if (count != 1) - @panic("Count has to be equal 1 for a single-item pointer"); + pub fn get(self: Self, idx: usize) Ptr.Opt { + return if (utils.optional(self.ptr())) |pointer| + pointer[idx] + else + null; + } - allocator.destroy(pointer); - } + pub fn len(self: Self) usize { + return self._len; } - pub fn set(self: Self, val: if (many or slice) utils.mod_ptr_t(ptr, "const", true) else Child) void { - if (many or slice) - @memcpy(self.ptr(), val) + 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) + return Error.NullPointer else - self.ptr().* = val; + unreachable; } pub fn destroy(self: Self, allocator: std.mem.Allocator) void { - if (many) - @compileError("Cannot use destroy() on a many-pointer, call free() instead"); - - self.free(allocator, if (slice) self._len else 1); + if (utils.optional(self.ptr())) |pointer| + allocator.free(pointer); } }; } + +pub fn Packed(Type: type) type { + const Ptr = if (utils.is_type(Type, "optional")) @typeInfo(Type).optional.child else Type; + const Child = @typeInfo(Ptr).pointer.child; + + return if (@typeInfo(Ptr).pointer.size == .slice) + Slice(Type) + else if ((utils.is_type(Child, "union") or + utils.is_type(Child, "enum") or + utils.is_type(Child, "struct")) and + @hasDecl(Child, "init") and + @hasDecl(Child, "deinit")) + Object(Type) + else + Pointer(Type); +} diff --git a/zlox/src/lib::utils.zig b/zlox/src/lib::utils.zig @@ -12,8 +12,24 @@ pub fn copy_const(T: type, U: type) type { return mod_ptr_t(U, "const", is_const(T)); } +pub fn optional(val: anytype) if (is_type(@TypeOf(val), "optional")) @TypeOf(val) else ?@TypeOf(val) { + return val; +} + +pub fn log_return(val: anytype) @TypeOf(val) { + if (@TypeOf(val) == type) + @compileLog("Returning: " ++ @typeName(val)) + else + @compileLog("Returning: " ++ @typeName(@TypeOf(val))); + return val; +} + pub fn mod_ptr_t(T: type, comptime field: []const u8, comptime val: anytype) type { - comptime var new = @typeInfo(T).pointer; + const is_optional = is_type(T, "optional"); + + const old = if (is_optional) @typeInfo(T).optional.child else T; + + comptime var new = @typeInfo(old).pointer; if (@hasField(std.lang.Type.Pointer.Attributes, field)) { @field(new.attrs, field) = val; @@ -21,12 +37,14 @@ pub fn mod_ptr_t(T: type, comptime field: []const u8, comptime val: anytype) typ @field(new, field) = val; } - return @Pointer( + const ret = @Pointer( new.size, new.attrs, new.child, std.lang.Type.Pointer.sentinel(new), ); + + return if (is_optional) ?ret else ret; } pub fn enum_len(T: type) usize { diff --git a/zlox/src/obj::chunk.zig b/zlox/src/obj::chunk.zig @@ -17,9 +17,9 @@ pub fn Chunk(fields: anytype) type { pub const Arg = void; pub const Error = error{OutOfMemory}; - pub const Code = array.Array(u8, usize, 8); - pub const Constants = Value.Array; - pub const Lines = array.RLEArray(i32, 8); + pub const Code = array.Array(u8); + pub const Constants = array.Array(Value); + pub const Lines = array.Array(i32); obj: Super, code: Packed(*Code), @@ -30,9 +30,9 @@ pub fn Chunk(fields: anytype) type { const self: *Self = try allocator.create(Self); self.* = Self{ .obj = Super.make(Self), - .code = try Packed(*Code).create2(allocator, try Code.init(allocator)), - .constants = try Packed(*Constants).create2(allocator, try Constants.init(allocator)), - .lines = try Packed(*Lines).create2(allocator, try Lines.init(allocator)), + .code = try Packed(*Code).create(allocator), + .constants = try Packed(*Constants).create(allocator), + .lines = try Packed(*Lines).create(allocator), }; return self; } @@ -42,7 +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) catch 0}); + _ = try writer.print("<chunk at {d}>", .{self.lines.ptr().get(0) orelse 0}); } pub fn eql(_: *const Self, _: *const Self) bool { @@ -50,11 +50,8 @@ pub fn Chunk(fields: anytype) type { } pub fn free(self: *const Self, allocator: std.mem.Allocator) void { - self.constants.ptr().deinit(); self.constants.destroy(allocator); - self.lines.ptr().deinit(); self.lines.destroy(allocator); - self.code.ptr().deinit(); self.code.destroy(allocator); allocator.destroy(self); } @@ -75,7 +72,7 @@ pub fn Chunk(fields: anytype) type { } } try self.constants.ptr().add(val); - return self.constants.ptr().len - 1; + return @intCast(self.constants.ptr().len - 1); } }; } diff --git a/zlox/src/obj::class.zig b/zlox/src/obj::class.zig @@ -26,7 +26,7 @@ pub fn Class(fields: anytype) type { const self: *Self = try allocator.create(Self); self.* = Self{ .obj = Super.make(Self), - .methods = try Packed(*Methods).create2(allocator, Methods.init(allocator)), + .methods = try Packed(*Methods).create(allocator), }; return self; } @@ -44,7 +44,6 @@ pub fn Class(fields: anytype) type { } pub fn free(self: *const Self, allocator: std.mem.Allocator) void { - self.methods.ptr().deinit(); self.methods.destroy(allocator); allocator.destroy(self); } diff --git a/zlox/src/obj::function.zig b/zlox/src/obj::function.zig @@ -29,10 +29,10 @@ pub fn Function(fields: anytype) type { arity: u8, chunk: Packed(*Super.Chunk), type: Type, - upvalues: Packed(?[]Upvalue), + upvalues: Packed([]Upvalue), pub fn init(arg: Arg, allocator: std.mem.Allocator) Error!*Self { - if (if (arg.type == .Closure) arg.upvalues == 0 else arg.upvalues > 0) + 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); @@ -41,8 +41,10 @@ pub fn Function(fields: anytype) type { .chunk = Packed(Chunk).init(arg.chunk), .arity = arg.arity, .type = arg.type, - .upvalues = try Packed(?[]Upvalue).alloc2(allocator, arg.upvalues, null), + .upvalues = try Packed([]Upvalue).create(allocator, arg.upvalues), }; + + for (self.upvalues.ptr()) |*upvalue| upvalue.* = null; return self; } diff --git a/zlox/src/obj::instance.zig b/zlox/src/obj::instance.zig @@ -7,6 +7,7 @@ const hash = @import("hash.zig"); const Packed = @import("lib::packed.zig").Packed; const Value = @import("value.zig").Value; const Obj = @import("obj.zig").Obj; +const GC = @import("gc.zig").GC; pub fn Instance(fields: anytype) type { const Super = Obj(fields); @@ -28,11 +29,34 @@ pub fn Instance(fields: anytype) type { self.* = Self{ .obj = Super.make(Self), .cls = Packed(*Super.Class).init(cls), - .fields = try Packed(*Self.Fields).create2(allocator, Fields.init(allocator)), + .fields = try Packed(*Self.Fields).create(allocator), }; return self; } + pub fn method(self: *Self, gc: *GC, name: *Super.String) !*Super.Function { + 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 - 2], met.upvalues.ptr()); + + fun.upvalues.ptr()[len - 1] = try gc.emplace(.Upvalue, .{ + .val = &val, + .slot = 0, + .closed = true, + }); + + return fun; + } + pub fn cast(self: anytype) utils.copy_const(@TypeOf(self), *Super) { return @ptrCast(self); } @@ -46,7 +70,6 @@ pub fn Instance(fields: anytype) type { } 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::list.zig b/zlox/src/obj::list.zig @@ -24,7 +24,7 @@ pub fn List(fields: anytype) type { const self: *Self = try allocator.create(Self); self.* = Self{ .obj = Super.make(Self), - .list = try Packed(*Self.List).create2(allocator, Self.List.init(allocator)), + .list = try Packed(*Self.List).create(allocator), }; return self; } @@ -52,7 +52,6 @@ pub fn List(fields: anytype) type { } pub fn free(self: *Self, allocator: std.mem.Allocator) void { - self.list.ptr().free(); self.list.destroy(allocator); allocator.destroy(self); } diff --git a/zlox/src/obj::native.zig b/zlox/src/obj::native.zig @@ -19,20 +19,16 @@ pub fn Native(fields: anytype) type { pub const ArityMin = 0; pub const ArityMax = std.math.maxInt(u8); - pub const Type = enum(u8) { Builtin, Literal }; - pub const Arg = struct { fun: Fn, arity_min: u8 = ArityMin, arity_max: u8 = ArityMax, - type: Type = .Builtin, }; obj: Super, fun: Packed(Fn), arity_min: u8, arity_max: u8, - type: Type, pub fn init(arg: Arg, allocator: std.mem.Allocator) Error!*Self { const self: *Self = try allocator.create(Self); @@ -41,7 +37,6 @@ pub fn Native(fields: anytype) type { .fun = Packed(Fn).init(arg.fun), .arity_min = arg.arity_min, .arity_max = arg.arity_max, - .type = arg.type, }; return self; } @@ -55,11 +50,8 @@ pub fn Native(fields: anytype) type { return @ptrCast(self); } - pub fn format(self: *const Self, writer: *std.Io.Writer) !void { - switch (self.type) { - .Builtin => _ = try writer.write("<Builtin>"), - .Literal => _ = try writer.write("<Literal> "), - } + pub fn format(_: *const Self, writer: *std.Io.Writer) !void { + _ = try writer.write("<Builtin>"); } pub fn eql(_: *const Self, _: *const Self) bool { diff --git a/zlox/src/obj::table.zig b/zlox/src/obj::table.zig @@ -24,7 +24,7 @@ pub fn Table(fields: anytype) type { const self: *Self = try allocator.create(Self); self.* = Self{ .obj = Super.make(Self), - .table = try Packed(*Self.Table).create2(allocator, Self.Table.init(allocator)), + .table = try Packed(*Self.Table).create(allocator), }; return self; } @@ -75,7 +75,6 @@ pub fn Table(fields: anytype) type { } pub fn free(self: *const Self, allocator: std.mem.Allocator) void { - self.table.ptr().deinit(); self.table.destroy(allocator); allocator.destroy(self); } diff --git a/zlox/src/obj::upvalue.zig b/zlox/src/obj::upvalue.zig @@ -25,20 +25,21 @@ pub fn Upvalue(fields: anytype) type { self.* = Self{ .obj = Super.make(Self), .location = if (arg.closed) - try Packed(*Value).create2(allocator, arg.val.*) + try Packed(*Value).create(allocator) else Packed(*Value).init(arg.val), .closed = arg.closed, .slot = arg.slot, }; + if (arg.closed) self.location.set(arg.val.*); return self; } pub fn close(self: *Self, allocator: std.mem.Allocator) Error!void { if (!self.closed) { const old = self.location.get(); - self.location = try Packed(*Value).create2(allocator, old); - self.closed = true; + self.location = try Packed(*Value).create(allocator); + self.location.set(old); } } @@ -47,7 +48,12 @@ 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}}}>", .{ self.location.get(), self.location._ptr, self.closed, self.slot }); + try writer.print("<Upvalue{{{f} at 0x{x}, {any}, {d}}}>", .{ + self.location.get(), + self.location._ptr, + self.closed, + self.slot, + }); } pub fn eql(_: *const Self, _: *const Self) bool { @@ -55,8 +61,7 @@ pub fn Upvalue(fields: anytype) type { } pub fn free(self: *const Self, allocator: std.mem.Allocator) void { - if (self.closed) - self.location.destroy(allocator); + self.location.destroy(allocator); allocator.destroy(self); } }; diff --git a/zlox/src/value.zig b/zlox/src/value.zig @@ -15,8 +15,6 @@ pub const Value = union(enum) { const Self = @This(); pub const Tag = std.meta.Tag(Self); - pub const Array = array.Array(Value, u8, 8); - pub fn format(self: Self, writer: *std.Io.Writer) !void { switch (self) { .number => |val| try writer.print("{d}", .{val}), diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -21,6 +21,7 @@ pub const VM = struct { objects: GC, globals: Globals, allocator: std.mem.Allocator, + initializer: *Obj.String, pub const CALLSTACK = 64; pub const STACK = 256; @@ -98,8 +99,13 @@ pub const VM = struct { .globals = Globals.init(allocator), .objects = try GC.init(allocator, io), .allocator = allocator, + .initializer = undefined, }; + self.initializer = try self.objects.emplace(.String, &.{"init"}); + + GC.exclude(self.initializer.cast()); + try self.defineNative("clock", 0, 0, native.Clock.clock); try self.defineNative("put", 1, 1, native.put); try self.defineNative("typeof", 1, 1, native.typeof); @@ -150,7 +156,7 @@ pub const VM = struct { try vm.objects.push_callback(&Self.gc_callback, &self); defer vm.objects.pop_callback(); - defer self.upvalues.free(); + defer self.upvalues.deinit(); self.push(Value.init(chunk.cast())); @@ -210,7 +216,7 @@ pub const VM = struct { } fn read_constant(self: *@This()) Value { - return self.frame().chunk.constants.ptr().get(self.read_byte()) catch unreachable; + return self.frame().chunk.constants.ptr().get(self.read_byte()).?; } fn read_string(self: *@This()) *Obj.String { @@ -249,12 +255,18 @@ pub const VM = struct { } 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))); + const instance = try self.vm.objects.emplace(.Instance, callee); + + const initializer = instance.method(&self.vm.objects, self.vm.initializer) catch + if (argCount != 0) { + self.runtimeError("Expected 0 arguments but got {d}", .{argCount}); + return InterpreterError.RuntimeError; + } else { + self.pook(argCount, Value.init(instance.cast())); + return; + }; + + try self.callFunction(initializer, argCount); } fn callFunction(self: *@This(), callee: *Obj.Function, argCount: u8) !void { @@ -396,23 +408,11 @@ pub const VM = struct { 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 blk: { - const method = instance.cls.ptr().methods.ptr().get(field) catch { + const prop = instance.fields.ptr().get(field) catch + Value.init((instance.method(&self.vm.objects, 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()); - }; + }).cast()); _ = self.pop(); self.push(prop); } else { @@ -453,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.get()); + self.push(closure.upvalues.get(index).?.location.get()); }, @intFromEnum(OP.SET_UPVALUE) => { const closure = self.frame().callee; const index = self.read_byte(); - closure.upvalues.at(index).?.location.set(self.peek(0)); + closure.upvalues.get(index).?.location.set(self.peek(0)); }, @intFromEnum(OP.CLOSE_UPVALUE) => { try self.closeUpvalues(self.current_slot()); @@ -515,9 +515,9 @@ pub const VM = struct { try self.callValue(self.peek(argCount), argCount); }, @intFromEnum(OP.CLOSURE) => { + const chunk = try self.read_constant().obj.cast(.Chunk); const arity = self.read_byte(); const count = self.read_byte(); - const chunk = try self.pop().obj.cast(.Chunk); const closure = try self.vm.objects.emplace(.Function, .{ .type = .Closure, @@ -528,14 +528,13 @@ pub const VM = struct { self.push(Value.init(closure.cast())); - for (closure.upvalues.ptr().?) |*upvalue| { + for (closure.upvalues.ptr()) |*upvalue| { const tp = self.read_byte(); const slot = self.read_byte(); 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, + @intFromEnum(U.remote) => self.frame().callee.upvalues.get(slot), else => return InterpreterError.RuntimeError, }; } @@ -568,7 +567,7 @@ pub const VM = struct { while (true) : (i -= 1) { const fram = self.frames[i]; const idx = @intFromPtr(fram.ip) - @intFromPtr(fram.chunk.code.ptr().data.ptr); - std.debug.print("[line {d}] in {f}\n", .{ fram.chunk.lines.ptr().get(idx) catch 1, fram.callee }); + std.debug.print("[line {d}] in {f}\n", .{ fram.chunk.lines.ptr().get(idx) orelse 1, fram.callee }); if (i == 0) break; } std.debug.print(fmt ++ "\n", args); diff --git a/zlox/test/brunch.lox b/zlox/test/brunch.lox @@ -0,0 +1,5 @@ +class Brunch { + init(food, drink) {} +} + +Brunch("eggs", "coffee"); diff --git a/zlox/test/this.lox b/zlox/test/this.lox @@ -0,0 +1,16 @@ +class P { + prin() { + print this.msg; + } +} + +var A = P(); +A.msg = "I am A"; + +con prin = A.prin; + +var B = P(); +B.msg = "I am B"; + +B.prin(); +prin();