DzLox

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

commit f9e90b557359a18f30ebeed065d556edd34e6b57
parent 00cf1d7e9a45844c1e2ab7a856f148326349dee7
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Wed,  2 Sep 2026 22:43:18 +0200

Remove Closure, covert Chunk to Obj

Diffstat:
Dzlox/src/chunk.zig | 84-------------------------------------------------------------------------------
Mzlox/src/compiler.zig | 138+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mzlox/src/debug.zig | 49+++++++++++++++++++++++++------------------------
Mzlox/src/gc.zig | 27++++++++++++++++++---------
Mzlox/src/lib::packed.zig | 27+++++++++++++++++++++------
Mzlox/src/main.zig | 12+++++++++---
Mzlox/src/obj.zig | 6+++---
Azlox/src/obj::chunk.zig | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mzlox/src/obj::class.zig | 3++-
Dzlox/src/obj::closure.zig | 48------------------------------------------------
Mzlox/src/obj::function.zig | 38+++++++++++++++++++++++++-------------
Mzlox/src/obj::instance.zig | 1+
Azlox/src/op.zig | 38++++++++++++++++++++++++++++++++++++++
Mzlox/src/vm.zig | 88+++++++++++++++++++++++++++++++++++++++++--------------------------------------
14 files changed, 347 insertions(+), 293 deletions(-)

diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig @@ -1,84 +0,0 @@ -const std = @import("std"); - -const array = @import("lib::array.zig"); - -const Value = @import("value.zig").Value; - -pub const OP = enum(u8) { - CONSTANT, - NIL, - TRUE, - FALSE, - EQUAL, - GREATER, - LESS, - RETURN, - NEGATE, - ADD, - SUBTRACT, - MULTIPLY, - DIVIDE, - NOT, - PRINT, - POP, - DEFINE_GLOBAL, - DEFINE_GLOBAL_CONSTANT, - GET_GLOBAL, - SET_GLOBAL, - GET_LOCAL, - SET_LOCAL, - GET_PROPERTY, - SET_PROPERTY, - GET_UPVALUE, - SET_UPVALUE, - JUMP_IF_FALSE, - JUMP, - JUMP_POP, - LOOP, - SET_INDEX, - GET_INDEX, - CALL, - CLOSURE, - CLOSE_UPVALUE, -}; - -pub const Chunk = struct { - pub const Error = error{OutOfMemory}; - - pub fn init(allocator: std.mem.Allocator) Error!@This() { - return @This(){ - .code = try array.Array(u8, usize, 8).init(allocator), - .constants = try Value.Array.init(allocator), - .lines = try array.RLEArray(i32, 8).init(allocator), - }; - } - - pub fn write(self: *@This(), byte: u8, line: i32) Error!void { - try self.code.add(byte); - try self.lines.add(line); - } - - pub fn writeOP(self: *@This(), op: OP, line: i32) Error!void { - try self.write(@intFromEnum(op), line); - } - - pub fn addConstant(self: *@This(), val: Value) Error!u8 { - for (self.constants.slice(), 0..) |el, i| { - if (el.eql(val)) { - return @intCast(i); - } - } - try self.constants.add(val); - return self.constants.len - 1; - } - - pub fn deinit(self: *@This()) void { - self.constants.deinit(); - self.lines.deinit(); - self.code.deinit(); - } - - code: array.Array(u8, usize, 8), - constants: Value.Array, - lines: array.RLEArray(i32, 8), -}; diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -4,16 +4,15 @@ const vm_native = @import("vm::native.zig"); const utils = @import("lib::utils.zig"); const scanner = @import("scanner.zig"); const debug = @import("debug.zig"); -const chunk = @import("chunk.zig"); const value = @import("value.zig"); const Token = scanner.TokenType; -const Chunk = chunk.Chunk; -const OP = chunk.OP; +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; pub const CompilerError = Obj.Error || scanner.ScannerError || Chunk.Error || Value.ParseNumberError || error{ UnexpectedToken, NotAnExpression }; @@ -48,13 +47,14 @@ pub fn Compiler(size: comptime_int) type { lastError: CompilerError, hadError: bool, panicMode: bool, - currentFunction: *Obj.Function, + chunk: *Obj.Chunk, objects: *GC, locals: [size]Local, localCount: usize, scopeDepth: usize, enclosing: ?*Self, upvalues: [upvalues_size]Upvalue, + upvaluesCount: u8, const Self = @This(); @@ -144,19 +144,15 @@ pub fn Compiler(size: comptime_int) type { } } - fn currentChunk(self: *Self) *Chunk { - return self.currentFunction.chunk.ptr(); - } - fn emitByte(self: *Self, byte: u8) void { - self.currentChunk().write(byte, self.previous.line) catch |err| { + self.chunk.write(byte, self.previous.line) catch |err| { self.lastError = err; self.errorAtCurrent("Out of Memory"); }; } fn emitOP(self: *Self, op: OP) void { - self.currentChunk().writeOP(op, self.previous.line) catch |err| { + self.chunk.writeOP(op, self.previous.line) catch |err| { self.lastError = err; self.errorAtCurrent("Out of Memory"); }; @@ -172,15 +168,15 @@ pub fn Compiler(size: comptime_int) type { self.emitOP(op2); } - fn end(self: *Self) *Obj.Function { + fn end(self: *Self) !*Obj.Chunk { self.emitReturn(); - return self.currentFunction; + return self.chunk; } fn emitReturn(self: *Self) void { - if (self.currentFunction.type != Obj.Function.Type.Script) { + if (self.enclosing) |_| self.emitOP(OP.NIL); - } + self.emitOP(OP.RETURN); } @@ -314,7 +310,7 @@ pub fn Compiler(size: comptime_int) type { fn listTable(self: *Self, _: bool) void { self.emit(OP.CONSTANT, 0xff); - const offset = self.currentChunk().code.len - 1; + const offset = self.chunk.code.ptr().len - 1; var argCount: u8 = 0; var isList = true; @@ -342,12 +338,12 @@ pub fn Compiler(size: comptime_int) type { } } if (isList) { - self.currentChunk().code.set(offset, self.makeObj(.Native, Obj.Native.Arg{ + self.chunk.code.ptr().set(offset, self.makeObj(.Native, .{ .fun = vm_native.list, .type = .Literal, }) catch return) catch return; } else { - self.currentChunk().code.set(offset, self.makeObj(.Native, Obj.Native.Arg{ + self.chunk.code.ptr().set(offset, self.makeObj(.Native, .{ .fun = vm_native.table, .type = .Literal, }) catch return) catch return; @@ -403,7 +399,7 @@ pub fn Compiler(size: comptime_int) type { } fn addUpvalue(self: *Self, idx: u8, isLocal: bool) !u8 { - const count = self.currentFunction.upvalue_count; + const count = self.upvaluesCount; for (self.upvalues[0..count], 0..) |upvalue, i| { if (upvalue.index == idx and upvalue.isLocal == isLocal) { @@ -418,7 +414,7 @@ pub fn Compiler(size: comptime_int) type { } self.upvalues[count] = .{ .index = idx, .isLocal = isLocal }; - self.currentFunction.upvalue_count += 1; + self.upvaluesCount += 1; return count; } @@ -441,7 +437,7 @@ pub fn Compiler(size: comptime_int) type { } fn makeConstant(self: *Self, val: Value) u8 { - return self.currentChunk().addConstant(val) catch |err| { + return self.chunk.addConstant(val) catch |err| { self.lastError = err; self.errorAtPrevious("Too many constants in one chunk"); return 0; @@ -551,16 +547,24 @@ pub fn Compiler(size: comptime_int) type { } 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))); + self.consume(Token.LEFT_BRACE, "Expect '{' before class body"); + while (!self.check(Token.RIGHT_BRACE) and !self.check(Token.EOF)) { + self.method(); + } + 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.emit(OP.METHOD, constant); } fn funDeclaration(self: *Self) void { @@ -571,26 +575,31 @@ pub fn Compiler(size: comptime_int) type { } fn function(self: *Self, _: bool) void { - var fun = self.objects.emplace(Obj.Type.Function, .Function) catch |err| { - self.errorAtPrevious("Couldn't allocate function"); + const chunk = self.objects.emplace(.Chunk, {}) catch |err| { + self.errorAtPrevious("Couldn't allocate chunk"); self.lastError = err; return; }; - var compiler = Self.init_enclosed(self, fun) catch |err| { + var compiler = Self.init_enclosed(self, chunk) catch |err| { self.errorAtPrevious("Couldn't init enclosed function"); self.lastError = err; return; }; + compiler.objects.push_callback(&gc_callback, &compiler) catch @panic("Couln't push callback"); + defer compiler.objects.pop_callback(); + + var arity: u8 = 0; + compiler.consume(Token.LEFT_PAREN, "Expect '(' in function definition"); if (!compiler.check(Token.RIGHT_PAREN)) { while (true) { - if (fun.arity == std.math.maxInt(@TypeOf(fun.arity))) { + if (arity == std.math.maxInt(@TypeOf(arity))) { self.errorAtCurrent("Too many arguments to a function"); return; } - fun.arity += 1; + arity += 1; compiler.defineVariable(compiler.parseVariable("Expect parameter name.", true) catch return, true); if (!compiler.match(Token.COMMA)) break; } @@ -604,14 +613,25 @@ pub fn Compiler(size: comptime_int) type { if (compiler.hadError) { self.lastError = compiler.lastError; - } else if (compiler.currentFunction.upvalue_count == 0) { - self.emit(OP.CONSTANT, self.makeConstant(Value.init(compiler.end().cast()))); } else { - self.emit(OP.CLOSURE, self.makeConstant(Value.init(compiler.end().cast()))); + 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); - for (compiler.upvalues[0..compiler.currentFunction.upvalue_count]) |upvalue| { - self.emitByte(if (upvalue.isLocal) 1 else 0); - self.emitByte(upvalue.index); + for (compiler.upvalues[0..compiler.upvaluesCount]) |upvalue| { + self.emitByte(if (upvalue.isLocal) 1 else 0); + self.emitByte(upvalue.index); + } } } } @@ -743,7 +763,7 @@ pub fn Compiler(size: comptime_int) type { } fn returnStatement(self: *Self) void { - if (self.currentFunction.type == Obj.Function.Type.Script) { + if (self.enclosing == null) { self.errorAtPrevious("Can't return from top-level code"); return; } @@ -768,7 +788,7 @@ pub fn Compiler(size: comptime_int) type { }) catch return; var jumpOver = self.emitJump(OP.JUMP); - const switchExpression = self.currentChunk().code.len; + const switchExpression = self.chunk.code.ptr().len; self.expression(); self.consume(Token.RIGHT_PAREN, "Expect ')' after expression"); @@ -776,7 +796,7 @@ pub fn Compiler(size: comptime_int) type { self.emitOP(OP.GET_INDEX); const defaultJump = self.emitJump(OP.JUMP_IF_FALSE); self.emitOP(OP.JUMP_POP); - const switchJump = self.currentChunk().code.len; + const switchJump = self.chunk.code.ptr().len; const exitJump = self.emitJump(OP.JUMP); self.patchJump(jumpOver); @@ -787,7 +807,7 @@ pub fn Compiler(size: comptime_int) type { if (self.match(Token.CASE)) { self.expression(); argCount += 2; - const distance = self.currentChunk().code.len - switchJump + 5; + const distance = self.chunk.code.ptr().len - switchJump + 5; if (distance > std.math.maxInt(u52)) { self.errorAtCurrent("Switch body too large"); return; @@ -825,7 +845,7 @@ pub fn Compiler(size: comptime_int) type { } fn whileStatement(self: *Self) void { - const loopStart = self.currentChunk().code.len; + const loopStart = self.chunk.code.ptr().len; self.consume(Token.LEFT_PAREN, "Expect '(' after 'while'."); self.expression(); @@ -856,7 +876,7 @@ pub fn Compiler(size: comptime_int) type { self.expressionStatement(); } - var loopStart = self.currentChunk().code.len; + var loopStart = self.chunk.code.ptr().len; var exitJump: ?usize = null; if (!self.match(Token.SEMICOLON)) { @@ -869,7 +889,7 @@ pub fn Compiler(size: comptime_int) type { if (!self.match(Token.RIGHT_PAREN)) { const bodyJump = self.emitJump(OP.JUMP); - const incrementStart = self.currentChunk().code.len; + const incrementStart = self.chunk.code.ptr().len; self.expression(); self.emitOP(OP.POP); self.consume(Token.RIGHT_PAREN, "Expect ')' after increment clause"); @@ -892,7 +912,7 @@ pub fn Compiler(size: comptime_int) type { fn emitLoop(self: *Self, start: usize) void { self.emitOP(OP.LOOP); - const offset = self.currentChunk().code.len - start + 2; + const offset = self.chunk.code.ptr().len - start + 2; if (offset > std.math.maxInt(u16)) { self.errorAtPrevious("Loop body too large"); @@ -922,20 +942,20 @@ pub fn Compiler(size: comptime_int) type { self.emitOP(instruction); self.emitByte(0xff); self.emitByte(0xff); - return self.currentChunk().code.len - 2; + return self.chunk.code.ptr().len - 2; } fn patchJump(self: *Self, offset: usize) void { - const jump = self.currentChunk().code.len - offset - 2; + const jump = self.chunk.code.ptr().len - offset - 2; if (jump > std.math.maxInt(u16)) { self.errorAtPrevious("Jump too large"); return; } - self.currentChunk().code.set(offset, @intCast((jump >> 8) & 0xff)) catch { + self.chunk.code.ptr().set(offset, @intCast((jump >> 8) & 0xff)) catch { self.errorAtPrevious("Invalid jump offset"); }; - self.currentChunk().code.set(offset + 1, @intCast(jump & 0xff)) catch { + self.chunk.code.ptr().set(offset + 1, @intCast(jump & 0xff)) catch { self.errorAtPrevious("Invalid jump offset"); }; } @@ -992,7 +1012,7 @@ pub fn Compiler(size: comptime_int) type { self.emitOP(OP.PRINT); } - fn init(scan: *scanner.Scanner, objects: *GC, fun: *Obj.Function) Self { + fn init(scan: *scanner.Scanner, objects: *GC, chunk: *Obj.Chunk) !Self { var self = Self{ .scanner = scan, .current = scanner.Token.Empty, @@ -1000,41 +1020,39 @@ pub fn Compiler(size: comptime_int) type { .panicMode = false, .hadError = false, .lastError = scanner.ScannerError.EmptyToken, - .currentFunction = fun, + .chunk = chunk, .objects = objects, .locals = @splat(Local{}), .localCount = 1, .scopeDepth = 0, .enclosing = null, .upvalues = @splat(Upvalue{ .index = 0, .isLocal = false }), + .upvaluesCount = 0, }; self.locals[0].depth = 0; + return self; } - fn init_enclosed(enclosing: *Self, fun: *Obj.Function) !Self { - var enclosed = Self.init(enclosing.scanner, enclosing.objects, fun); + fn init_enclosed(enclosing: *Self, chunk: *Obj.Chunk) !Self { + var enclosed = try Self.init(enclosing.scanner, enclosing.objects, chunk); enclosed.current = enclosing.current; enclosed.enclosing = enclosing; enclosed.beginScope(); - try enclosing.objects.swap_callback(&gc_callback, &enclosed); return enclosed; } fn gc_callback(self_ptr: *anyopaque) void { var self: *@This() = @ptrCast(@alignCast(self_ptr)); - self.objects.mark("C", self.currentFunction); - while (self.enclosing) |enclosed| : (self = enclosed) { - self.objects.mark("C", enclosed.currentFunction); - } + self.objects.mark("C", self.chunk); } - pub fn compile(source: []const u8, objects: *GC) CompilerError!*Obj.Function { + pub fn compile(source: []const u8, objects: *GC) CompilerError!*Obj.Chunk { var scan = try scanner.Scanner.init(source); - const fun = try objects.emplace(Obj.Type.Function, .Script); - var self = Self.init(&scan, objects, fun); + const chunk = try objects.emplace(.Chunk, {}); + var self = try Self.init(&scan, objects, chunk); try objects.push_callback(&gc_callback, &self); defer objects.pop_callback(); @@ -1045,7 +1063,7 @@ pub fn Compiler(size: comptime_int) type { self.declaration(); } - return if (self.hadError) self.lastError else self.end(); + return if (self.hadError) self.lastError else try self.end(); } }; } diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig @@ -1,41 +1,41 @@ const std = @import("std"); -const chunk = @import("chunk.zig"); const value = @import("value.zig"); const Obj = @import("gc.zig").GC.Obj; const Error = Obj.Error; +const OP = @import("op.zig").OP; const print = std.debug.print; -pub fn disassembleChunk(ch: *const chunk.Chunk) Error!void { +pub fn disassembleChunk(ch: *const Obj.Chunk) Error!void { print("/=======\\\n", .{}); var offset: usize = 0; - while (offset < ch.code.len) { + while (offset < ch.code.ptr().len) { offset = try _disassembleInstruction(ch, offset, true); } print("\\=======/\n", .{}); } -pub fn print_offset(ch: *const chunk.Chunk, offset: usize) !void { +pub fn print_offset(ch: *const Obj.Chunk, offset: usize) !void { print("{d:0>4} ", .{offset}); - if (offset > 0 and (try ch.lines.get(offset)) == (try ch.lines.get(offset - 1))) { + const line = try ch.lines.ptr().get(offset); + if (offset > 0 and line == (try ch.lines.ptr().get(offset - 1))) { print(" | ", .{}); } else { - print("{d:4} ", .{try ch.lines.get(offset)}); + print("{d:4} ", .{line}); } } -pub fn disassembleInstruction(ch: *const chunk.Chunk, offset: usize) Error!usize { +pub fn disassembleInstruction(ch: *const Obj.Chunk, offset: usize) Error!usize { return _disassembleInstruction(ch, offset, false); } -fn _disassembleInstruction(ch: *const chunk.Chunk, offset: usize, print_fn: bool) Error!usize { +fn _disassembleInstruction(ch: *const Obj.Chunk, offset: usize, print_fn: bool) Error!usize { try print_offset(ch, offset); - const OP = chunk.OP; - const op = try ch.code.get(offset); + const op = try ch.code.ptr().get(offset); const name = @tagName(@as(OP, @enumFromInt(op))); return switch (op) { @@ -54,6 +54,7 @@ fn _disassembleInstruction(ch: *const chunk.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.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), @@ -72,7 +73,7 @@ fn _disassembleInstruction(ch: *const chunk.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 closureInstruction(name, ch, offset, print_fn), + @intFromEnum(OP.CLOSURE) => try byteInstruction(name, ch, offset), @intFromEnum(OP.CLOSE_UPVALUE) => simpleInstruction(name, offset), else => blk: { print("Unknown opcode {d} {s}\n", .{ op, name }); @@ -86,9 +87,9 @@ fn simpleInstruction(name: []const u8, offset: usize) usize { return offset + 1; } -fn constantInstruction(name: []const u8, ch: *const chunk.Chunk, offset: usize, print_fn: bool) Error!usize { - const constant = try ch.code.get(offset + 1); - const constval = try ch.constants.get(constant); +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); 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()); @@ -96,29 +97,29 @@ fn constantInstruction(name: []const u8, ch: *const chunk.Chunk, offset: usize, return offset + 2; } -fn byteInstruction(name: []const u8, ch: *const chunk.Chunk, offset: usize) Error!usize { - print("{s:<32} {d:4}\n", .{ name, try ch.code.get(offset + 1) }); +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) }); return offset + 2; } -fn jumpInstruction(name: []const u8, sign: bool, ch: *const chunk.Chunk, offset: usize) !usize { - const msb: u16 = try ch.code.get(offset + 1); - const lsb: u16 = try ch.code.get(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 jump = (msb << 8) | lsb; print("{s:<32} {d:4} -> {d}\n", .{ name, offset, if (sign) offset + 3 + jump else offset + 3 - jump }); return offset + 3; } -fn closureInstruction(name: []const u8, ch: *const chunk.Chunk, offset: usize, print_fn: bool) Error!usize { +fn closureInstruction(name: []const u8, ch: *const Obj.Chunk, offset: usize, print_fn: bool) Error!usize { var off = offset + 1; - const constant = try ch.code.get(off); - const val = try ch.constants.get(constant); + 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.get(off + 1); - const idx = try ch.code.get(off + 2); + const isLocal = try ch.code.ptr().get(off + 1); + const idx = try ch.code.ptr().get(off + 2); try print_offset(ch, off + 1); print("{s:<38}|-> {s} {d}\n", .{ "", if (isLocal == 1) "local" else "upvalue", idx }); off += 2; diff --git a/zlox/src/gc.zig b/zlox/src/gc.zig @@ -97,21 +97,22 @@ pub const GC = struct { }.fun); }, *Obj.Function => { - for (obj.chunk.ptr().constants.slice()) |constant| - self.mark("f", constant); + self.mark("f", obj.chunk.ptr()); + if (obj.upvalues.ptr()) |upvalues| + for (upvalues) |upvalue_ptr| + if (upvalue_ptr) |upvalue| + self.mark("c", upvalue); + }, + *Obj.Chunk => { + for (obj.constants.ptr().slice()) |constant| + self.mark("h", constant); }, *Obj.List => { var iter = obj.list.ptr().iter(); while (iter.next()) |val| { - self.mark("t", val); + self.mark("l", val); } }, - *Obj.Closure => { - self.mark("c", obj.function.ptr()); - for (obj.upvalues.ptr()) |upvalue_ptr| - if (upvalue_ptr) |upvalue| - self.mark("c", upvalue); - }, *Obj.Upvalue => { if (obj.closed) self.mark("u", obj.location.get()); @@ -125,6 +126,14 @@ pub const GC = struct { } }.fun); }, + *Obj.Class => { + obj.methods.ptr().for_each(self, struct { + pub fn fun(s: *Self, key: Obj.Class.Methods.Key, val: Obj.Class.Methods.Value) void { + s.mark("k", key); + s.mark("k", val); + } + }.fun); + }, else => {}, } } diff --git a/zlox/src/lib::packed.zig b/zlox/src/lib::packed.zig @@ -35,7 +35,11 @@ pub fn Packed(Type: type) type { } pub fn alloc(allocator: std.mem.Allocator, count: usize) !Self { - return if (slice) + 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) @@ -47,9 +51,17 @@ pub fn Packed(Type: type) type { pub fn alloc2(allocator: std.mem.Allocator, count: usize, val: Child) !Self { const self = try Self.alloc(allocator, count); - const p = self.ptr(); - for (0..count) |i| + var p = if (optional) + if (self.ptr()) |p| + p + else + return self + else + self.ptr(); + + for (0..count) |i| { p[i] = val; + } return self; } @@ -92,11 +104,14 @@ pub fn Packed(Type: type) type { @as(Ptr, @ptrFromInt(self._ptr)).*; } - pub fn at(self: Self, idx: usize) if (optional) ?Child else Child { + 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 and self._ptr == 0) - null + else if (optional) + if (self.ptr()) |val| + val[idx] + else + null else self.ptr()[idx]; } diff --git a/zlox/src/main.zig b/zlox/src/main.zig @@ -15,7 +15,13 @@ pub fn main(init: std.process.Init) anyerror!u8 { if (std.mem.eql(u8, args[1], "-d")) { try repl(allocator, io, true); } else { - try runFile(allocator, io, args[1]); + try runFile(allocator, io, args[1], false); + } + } else if (args.len == 3) { + if (std.mem.eql(u8, args[1], "-d")) { + try runFile(allocator, io, args[2], true); + } else { + try runFile(allocator, io, args[1], true); } } else { std.debug.print("Usage: {s} [path]\n", .{args[0]}); @@ -25,13 +31,13 @@ pub fn main(init: std.process.Init) anyerror!u8 { return 0; } -pub fn runFile(allocator: std.mem.Allocator, io: std.Io, path: []const u8) anyerror!void { +pub fn runFile(allocator: std.mem.Allocator, io: std.Io, path: []const u8, dbg: bool) anyerror!void { const text = try std.Io.Dir.cwd().readFileAlloc(io, path, allocator, std.Io.Limit.unlimited); defer allocator.free(text); var VM = try vm.VM.init(allocator, io); defer VM.deinit(); - try VM.interpret(text, false); + try VM.interpret(text, dbg); } pub fn repl(allocator: std.mem.Allocator, io: std.Io, dbg: bool) anyerror!void { diff --git a/zlox/src/obj.zig b/zlox/src/obj.zig @@ -15,7 +15,7 @@ pub fn Obj(fields: anytype) type { 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 Closure = @import("obj::closure.zig").Closure(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); @@ -27,7 +27,7 @@ pub fn Obj(fields: anytype) type { || Function.Error // || Native.Error // || List.Error // - || Closure.Error // + || Chunk.Error // || Upvalue.Error // || Class.Error // || Instance.Error; @@ -38,7 +38,7 @@ pub fn Obj(fields: anytype) type { Function, Native, List, - Closure, + Chunk, Upvalue, Class, Instance, diff --git a/zlox/src/obj::chunk.zig b/zlox/src/obj::chunk.zig @@ -0,0 +1,81 @@ +const std = @import("std"); + +const utils = @import("lib::utils.zig"); +const array = @import("lib::array.zig"); + +const Value = @import("value.zig").Value; +const Obj = @import("obj.zig").Obj; +const OP = @import("op.zig").OP; +const Packed = @import("lib::packed.zig").Packed; + +pub fn Chunk(fields: anytype) type { + const Super = Obj(fields); + + return packed struct { + const Self = @This(); + + 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); + + obj: Super, + code: Packed(*Code), + constants: Packed(*Constants), + lines: Packed(*Lines), + + pub fn init(_: Arg, allocator: std.mem.Allocator) Error!*Self { + 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)), + }; + return self; + } + + pub fn cast(self: anytype) utils.copy_const(@TypeOf(self), *Super) { + return @ptrCast(self); + } + + pub fn format(self: *const Self, writer: *std.Io.Writer) !void { + _ = try writer.print("<chunk at {d}>", .{self.lines.ptr().get(0) catch 0}); + } + + pub fn eql(_: *const Self, _: *const Self) bool { + return false; + } + + 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); + } + + pub fn write(self: *@This(), byte: u8, line: i32) Error!void { + try self.code.ptr().add(byte); + try self.lines.ptr().add(line); + } + + pub fn writeOP(self: *@This(), op: OP, line: i32) Error!void { + try self.write(@intFromEnum(op), line); + } + + pub fn addConstant(self: *@This(), val: Value) Error!u8 { + for (self.constants.ptr().slice(), 0..) |el, i| { + if (el.eql(val)) { + return @intCast(i); + } + } + try self.constants.ptr().add(val); + return self.constants.ptr().len - 1; + } + }; +} diff --git a/zlox/src/obj::class.zig b/zlox/src/obj::class.zig @@ -6,6 +6,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; pub fn Class(fields: anytype) type { const Super = Obj(fields); @@ -16,7 +17,7 @@ pub fn Class(fields: anytype) type { pub const Arg = void; pub const Error = error{OutOfMemory}; - pub const Methods = table.Table(*Super.String, *Super.Closure, hash.hash_t(*Super.String), Super.String.eql); + pub const Methods = table.Table(*Super.String, *Super.Function, hash.hash_t(*Super.String), Super.String.eql); obj: Super, methods: Packed(*Methods), diff --git a/zlox/src/obj::closure.zig b/zlox/src/obj::closure.zig @@ -1,48 +0,0 @@ -const std = @import("std"); - -const utils = @import("lib::utils.zig"); - -const Packed = @import("lib::packed.zig").Packed; -const Obj = @import("obj.zig").Obj; - -pub fn Closure(fields: anytype) type { - const Super = Obj(fields); - - return packed struct { - const Self = @This(); - - pub const Arg = *Super.Function; - pub const Error = error{OutOfMemory}; - - obj: Super, - function: Packed(*Super.Function), - upvalues: Packed([]?*Super.Upvalue), - - pub fn init(arg: Arg, allocator: std.mem.Allocator) Error!*Self { - const self: *Self = try allocator.create(Self); - self.* = Self{ - .obj = Super.make(Self), - .upvalues = try Packed([]?*Super.Upvalue).alloc2(allocator, arg.upvalue_count, null), - .function = Packed(*Super.Function).init(arg), - }; - 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("<Closure>"); - } - - pub fn eql(_: *const Self, _: *const Self) bool { - return false; - } - - pub fn free(self: *const Self, allocator: std.mem.Allocator) void { - self.upvalues.destroy(allocator); - allocator.destroy(self); - } - }; -} diff --git a/zlox/src/obj::function.zig b/zlox/src/obj::function.zig @@ -1,7 +1,6 @@ const std = @import("std"); const utils = @import("lib::utils.zig"); -const chunk = @import("chunk.zig"); const Packed = @import("lib::packed.zig").Packed; const Obj = @import("obj.zig").Obj; @@ -12,25 +11,37 @@ pub fn Function(fields: anytype) type { return packed struct { const Self = @This(); - pub const Arg = Type; - pub const Error = error{OutOfMemory}; + pub const Error = error{ OutOfMemory, InvalidArguments }; - pub const Type = enum(u8) { Function, Script }; + 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, + }; obj: Super, arity: u8, - chunk: Packed(*chunk.Chunk), + chunk: Packed(*Super.Chunk), type: Type, - upvalue_count: u8, + 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) + return Error.InvalidArguments; - pub fn init(tpe: Arg, allocator: std.mem.Allocator) Error!*Self { const self: *Self = try allocator.create(Self); self.* = Self{ .obj = Super.make(Self), - .chunk = try Packed(*chunk.Chunk).create2(allocator, try chunk.Chunk.init(allocator)), - .arity = 0, - .type = tpe, - .upvalue_count = 0, + .chunk = Packed(Chunk).init(arg.chunk), + .arity = arg.arity, + .type = arg.type, + .upvalues = try Packed(?[]Upvalue).alloc2(allocator, arg.upvalues, null), }; return self; } @@ -43,6 +54,8 @@ pub fn Function(fields: anytype) type { switch (self.type) { .Function => _ = try writer.write("<Function>"), .Script => _ = try writer.write("<Script>"), + .Closure => _ = try writer.write("<Closure>"), + .Method => _ = try writer.write("<Method>"), } } @@ -51,8 +64,7 @@ pub fn Function(fields: anytype) type { } pub fn free(self: *const Self, allocator: std.mem.Allocator) void { - self.chunk.ptr().deinit(); - self.chunk.destroy(allocator); + self.upvalues.destroy(allocator); allocator.destroy(self); } }; diff --git a/zlox/src/obj::instance.zig b/zlox/src/obj::instance.zig @@ -46,6 +46,7 @@ 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/op.zig b/zlox/src/op.zig @@ -0,0 +1,38 @@ +pub const OP = enum(u8) { + CONSTANT, + NIL, + TRUE, + FALSE, + EQUAL, + GREATER, + LESS, + RETURN, + NEGATE, + ADD, + SUBTRACT, + MULTIPLY, + DIVIDE, + NOT, + PRINT, + POP, + DEFINE_GLOBAL, + DEFINE_GLOBAL_CONSTANT, + GET_GLOBAL, + SET_GLOBAL, + GET_LOCAL, + SET_LOCAL, + GET_PROPERTY, + SET_PROPERTY, + GET_UPVALUE, + SET_UPVALUE, + METHOD, + JUMP_IF_FALSE, + JUMP, + JUMP_POP, + LOOP, + SET_INDEX, + GET_INDEX, + CALL, + CLOSURE, + CLOSE_UPVALUE, +}; diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -8,13 +8,12 @@ const native = @import("vm::native.zig"); const debug = @import("debug.zig"); const compiler = @import("compiler.zig"); const hash = @import("hash.zig"); -const chunk = @import("chunk.zig"); -const Chunk = chunk.Chunk; -const OP = chunk.OP; +const OP = @import("op.zig").OP; const Value = @import("value.zig").Value; const GC = @import("gc.zig").GC; const Obj = GC.Obj; +const Chunk = Obj.Chunk; const InterpreterError = Obj.Error || compiler.CompilerError || callbacks.Error || error{ CompileError, RuntimeError, StackOverflow, IndexOutOfBounds, Overflow, DivisionByZero }; @@ -51,16 +50,17 @@ pub const VM = struct { const Globals = table.Table(*Obj.String, Global, hash.hash_t(*Obj.String), Obj.String.eql); const CallFrame = struct { - callee: *Obj, + callee: *Obj.Function, ip: [*]const u8, slots: [*]Value, chunk: *const Chunk, - pub fn init(comptime tp: Obj.Type, callee: *tp.get(), slots: [*]Value) @This() { - return switch (tp) { - .Function => @This(){ .callee = callee.cast(), .ip = callee.chunk.ptr().code.data.ptr, .chunk = callee.chunk.ptr(), .slots = slots }, - .Closure => @This(){ .callee = callee.cast(), .ip = callee.function.ptr().chunk.ptr().code.data.ptr, .chunk = callee.function.ptr().chunk.ptr(), .slots = slots }, - else => @compileError("Invalid type"), + pub fn init(callee: *Obj.Function, slots: [*]Value) @This() { + return @This(){ + .callee = callee, + .ip = callee.chunk.ptr().code.ptr().data.ptr, + .chunk = callee.chunk.ptr(), + .slots = slots, }; } }; @@ -116,11 +116,11 @@ pub const VM = struct { try self.objects.push_callback(&VM.gc_callback, self); defer self.objects.pop_callback(); - const function = try compiler.Compiler(stack_size).compile(source, &self.objects); + const chunk = try compiler.Compiler(stack_size).compile(source, &self.objects); - if (dbg) try debug.disassembleChunk(function.chunk.ptr()); + if (dbg) try debug.disassembleChunk(chunk); - try Interpreter(callstack_size, stack_size).run(self, function, dbg); + try Interpreter(callstack_size, stack_size).run(self, chunk, dbg); } fn Interpreter(callstack_size: comptime_int, stack_size: comptime_int) type { @@ -135,24 +135,29 @@ pub const VM = struct { vm: *VM, open_upvalues: List, - pub fn run(vm: *VM, function: *Obj.Function, dbg: bool) InterpreterError!void { + pub fn run(vm: *VM, chunk: *Obj.Chunk, dbg: bool) InterpreterError!void { var self = @This(){ .frames = @splat(undefined), - .frameCount = 1, + .frameCount = 0, .stack = @splat(Value.init({})), .stackTop = undefined, .vm = vm, .open_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(); - self.stackTop = &self.stack; - self.frames[0] = CallFrame.init(.Function, function, self.stackTop); + self.push(Value.init(chunk.cast())); + + const function = try vm.objects.emplace(.Function, .{ .type = .Script, .chunk = chunk }); + _ = self.pop(); self.push(Value.init(function.cast())); + + try self.callFunction(function, 0); try self.execute(dbg); } @@ -204,7 +209,7 @@ pub const VM = struct { } fn read_constant(self: *@This()) Value { - return self.frame().chunk.constants.get(self.read_byte()) catch unreachable; + return self.frame().chunk.constants.ptr().get(self.read_byte()) catch unreachable; } fn read_string(self: *@This()) *Obj.String { @@ -232,8 +237,6 @@ pub const VM = struct { fn callValue(self: *@This(), callee: Value, argCount: u8) !void { 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| { @@ -253,17 +256,6 @@ pub const VM = struct { 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 }); - return InterpreterError.RuntimeError; - } - if (self.frameCount == callstack_size - 1) - return InterpreterError.StackOverflow; - self.frameCount += 1; - self.frames[self.frameCount - 1] = CallFrame.init(.Closure, callee, self.stackTop - argCount - 1); - } - fn callFunction(self: *@This(), callee: *Obj.Function, argCount: u8) !void { if (argCount != callee.arity) { self.runtimeError("Expected {d} arguments but got {d}", .{ callee.arity, argCount }); @@ -272,7 +264,7 @@ pub const VM = struct { if (self.frameCount == callstack_size - 1) return InterpreterError.StackOverflow; self.frameCount += 1; - self.frames[self.frameCount - 1] = CallFrame.init(.Function, callee, self.stackTop - argCount - 1); + self.frames[self.frameCount - 1] = CallFrame.init(callee, self.stackTop - argCount - 1); } fn callNative(self: *@This(), obj: *Obj.Native, argCount: u8) !void { @@ -328,7 +320,7 @@ pub const VM = struct { } fn instruction_idx(self: *const @This()) usize { - return @intFromPtr(self.ip()) - @intFromPtr(self.frame().chunk.code.data.ptr); + return @intFromPtr(self.ip()) - @intFromPtr(self.frame().chunk.code.ptr().data.ptr); } fn current_slot(self: *const @This()) u8 { @@ -444,12 +436,12 @@ pub const VM = struct { } }, @intFromEnum(OP.GET_UPVALUE) => { - const closure = try self.frame().callee.cast(.Closure); + const closure = self.frame().callee; const index = self.read_byte(); self.push(closure.upvalues.at(index).?.location.ptr().*); }, @intFromEnum(OP.SET_UPVALUE) => { - const closure = try self.frame().callee.cast(.Closure); + const closure = self.frame().callee; const index = self.read_byte(); closure.upvalues.at(index).?.location.ptr().* = self.peek(0); }, @@ -508,19 +500,31 @@ pub const VM = struct { try self.callValue(self.peek(argCount), argCount); }, @intFromEnum(OP.CLOSURE) => { - const function = try self.read_constant().obj.cast(.Function); - const closure = try self.vm.objects.emplace(.Closure, function); - for (closure.upvalues.ptr()) |*upvalue| { + const count = self.read_byte(); + const function = try self.pop().obj.cast(.Function); + const closure = try self.vm.objects.emplace(.Function, .{ + .type = .Closure, + .chunk = function.chunk.ptr(), + .arity = function.arity, + .upvalues = count, + }); + self.push(Value.init(closure.cast())); + + for (closure.upvalues.ptr().?) |*upvalue| { const isLocal = self.read_byte(); const slot = self.read_byte(); if (isLocal == 1) { upvalue.* = try self.captureUpvalue(slot); } else { - const callee = try self.frame().callee.cast(.Closure); - upvalue.* = callee.upvalues.at(slot); + upvalue.* = self.frame().callee.upvalues.at(slot); } } - self.push(Value.init(closure.cast())); + }, + @intFromEnum(OP.METHOD) => { + const name = self.read_string(); + const method = try self.pop().obj.cast(.Function); + const class = try self.peek(0).obj.cast(.Class); + _ = try class.methods.ptr().set(name, method); }, @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())), @@ -543,8 +547,8 @@ pub const VM = struct { var i = self.frameCount - 1; while (true) : (i -= 1) { const fram = self.frames[i]; - const idx = @intFromPtr(fram.ip) - @intFromPtr(fram.chunk.code.data.ptr); - std.debug.print("[line {d}] in {f}\n", .{ fram.chunk.lines.get(idx) catch 1, fram.callee }); + 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 }); if (i == 0) break; } std.debug.print(fmt ++ "\n", args);