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:
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);