commit 3a280a553cf0189bda3c0546b94716be5b07e506
parent 05deccbb3b058e462dbb667bda3086e14f934ce2
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date: Tue, 13 Aug 2024 23:49:52 +0200
Lists
Diffstat:
12 files changed, 305 insertions(+), 56 deletions(-)
diff --git a/zlox/fib.lox b/zlox/fib.lox
@@ -0,0 +1,32 @@
+fun fib(n) {
+ if (n < 2) return n;
+ return fib(n - 2) + fib(n - 1);
+}
+
+fun printf(fmt, args) {
+ var args_i = 0;
+ for(var i = 0; fmt[i]; i = i + 1) {
+ if(fmt[i] == '{' and fmt[i+1] == '}') {
+ put(args[args_i]);
+ args_i = args_i + 1;
+ i = i + 1;
+ } else {
+ put(fmt[i]);
+ }
+ }
+ print "";
+}
+
+fun main() {
+ con num = 25;
+ con start = clock();
+
+ con args = [];
+ args[0] = num;
+ args[1] = fib(num);
+ args[2] = clock() - start;
+
+ printf("fib({}) == {}, time: {}", args);
+}
+
+main();
diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig
@@ -5,6 +5,7 @@ const OP = @import("chunk.zig").OP;
const Value = @import("value.zig").Value;
const ValueArray = @import("value.zig").ValueArray;
const Obj = @import("obj.zig").Obj;
+const GC = @import("gc.zig").GC;
const debug = @import("debug.zig");
const Token = scanner.TokenType;
@@ -42,7 +43,7 @@ pub fn Compiler(size: comptime_int) type {
hadError: bool,
panicMode: bool,
currentFunction: *Obj.Function,
- objects: *Obj.List,
+ objects: *GC,
locals: [size]Local,
localCount: usize,
scopeDepth: usize,
@@ -311,7 +312,7 @@ pub fn Compiler(size: comptime_int) type {
} else if (self.match(Token.NIL)) {
return Value.init({});
} else if (self.match(Token.LEFT_BRACKET)) {
- return self.parseLiteralTable();
+ return self.parseLiteralListOrTable();
} else {
self.errorAtCurrent("Not a literal value");
return error.UnexpectedToken;
@@ -330,11 +331,56 @@ pub fn Compiler(size: comptime_int) type {
return Value.init(try self.objects.emplace_cast(.String, &.{self.previous.lexeme[1 .. self.previous.lexeme.len - 1]}));
}
- fn parseLiteralTable(self: *Self) CompilerError!Value {
+ fn parseLiteralListOrTable(self: *Self) CompilerError!Value {
+ if (self.match(Token.RIGHT_BRACKET)) {
+ return Value.init(try self.objects.emplace_cast(.List, {}));
+ } else if (self.match(Token.COLON)) {
+ self.consume(Token.RIGHT_BRACKET, "Expect ']' in empty table literal");
+ return Value.init(try self.objects.emplace_cast(.Table, {}));
+ } else {
+ const firstVal = try self.parseLiteralValue();
+ if (self.match(Token.COLON)) {
+ return self.parseLiteralTable(firstVal);
+ } else {
+ return self.parseLiteralList(firstVal);
+ }
+ }
+ }
+
+ fn parseLiteralList(self: *Self, firstVal: Value) CompilerError!Value {
+ var first = true;
+ var list = try self.objects.emplace(.List, {});
+ while (first or !self.match(Token.RIGHT_BRACKET)) {
+ const val = if (!first)
+ try self.parseLiteralValue()
+ else blk: {
+ first = false;
+ break :blk firstVal;
+ };
+ if (val.is(Value.nil)) {
+ self.errorAtPrevious("Nil cannot be stored in a list");
+ return error.UnexpectedToken;
+ }
+ try list.push(val, self.objects.allocator);
+ if (self.match(Token.RIGHT_BRACKET))
+ break;
+ self.consume(Token.COMMA, "Expect ',' after value in list initalizer");
+ }
+ return Value.init(list.cast());
+ }
+
+ fn parseLiteralTable(self: *Self, firstVal: Value) CompilerError!Value {
+ var first = true;
var tabl = try self.objects.emplace(.Table, {});
- while (!self.match(Token.RIGHT_BRACKET)) {
- const key = try self.parseLiteralValue();
- self.consume(Token.COLON, "Expect ':' after key in table initalizer");
+ while (first or !self.match(Token.RIGHT_BRACKET)) {
+ const key = if (!first) blk: {
+ const key = try self.parseLiteralValue();
+ self.consume(Token.COLON, "Expect ':' after key in table initalizer");
+ break :blk key;
+ } else blk2: {
+ first = false;
+ break :blk2 firstVal;
+ };
const val = try self.parseLiteralValue();
if (val.is(Value.nil)) {
self.errorAtPrevious("Nil cannot be stored in a table");
@@ -352,7 +398,7 @@ pub fn Compiler(size: comptime_int) type {
}
fn table(self: *Self, _: bool) void {
- self.emitConstant(self.parseLiteralTable() catch |err| {
+ self.emitConstant(self.parseLiteralListOrTable() catch |err| {
self.lastError = err;
return;
});
@@ -397,12 +443,12 @@ pub fn Compiler(size: comptime_int) type {
fn resolveLocal(self: *Self, name: scanner.Token) !u8 {
var i = self.localCount;
while (i > 0) : (i -= 1) {
- if(self.locals[i-1].depth) |_| {
- if (identifiersEql(self.locals[i-1].name, name)) {
+ if (identifiersEql(self.locals[i-1].name, name)) {
+ if(self.locals[i-1].depth) |_| {
return @intCast(i-1);
+ } else {
+ self.errorAt(name, "Can't read local variable in it's own initializer");
}
- } else {
- self.errorAt(name, "Can't read local variable in it's own initializer");
}
}
return error.NotFound;
@@ -778,7 +824,9 @@ pub fn Compiler(size: comptime_int) type {
const exitJump = self.emitJump(OP.JUMP_IF_FALSE);
self.emitOP(OP.POP);
- self.statement();
+ if (!self.match(Token.SEMICOLON)) {
+ self.statement();
+ }
self.emitLoop(loopStart);
self.patchJump(exitJump);
@@ -821,8 +869,9 @@ pub fn Compiler(size: comptime_int) type {
loopStart = incrementStart;
self.patchJump(bodyJump);
}
-
- self.statement();
+ if (!self.match(Token.SEMICOLON)) {
+ self.statement();
+ }
self.emitLoop(loopStart);
if (exitJump) |jump| {
@@ -931,7 +980,7 @@ pub fn Compiler(size: comptime_int) type {
self.emitOP(OP.PRINT);
}
- fn init(scan: *scanner.Scanner, objects: *Obj.List, fun: *Obj.Function) Self {
+ fn init(scan: *scanner.Scanner, objects: *GC, fun: *Obj.Function) Self {
return Self{
.scanner = scan,
.current = scanner.Token.Empty,
@@ -947,7 +996,7 @@ pub fn Compiler(size: comptime_int) type {
};
}
- pub fn compile(source: []const u8, objects: *Obj.List) CompilerError!*Obj.Function {
+ pub fn compile(source: []const u8, objects: *GC) CompilerError!*Obj.Function {
var scan = try scanner.Scanner.init(source);
const fun = try objects.emplace(Obj.Type.Function, Obj.Function.Type.Script);
var self = Self.init(&scan, objects, fun);
diff --git a/zlox/src/gc.zig b/zlox/src/gc.zig
@@ -0,0 +1,42 @@
+const std = @import("std");
+
+const Obj = @import("obj.zig").Obj;
+const Value = @import("value.zig").Value;
+
+pub const GC = struct {
+ const Self = @This();
+
+ allocator: std.mem.Allocator,
+ table: Obj.String.Table,
+ list: *Obj.List,
+
+ pub fn init(allocator: std.mem.Allocator) !Self {
+ return Self{
+ .allocator = allocator,
+ .table = Obj.String.Table.init(allocator),
+ .list = try Obj.List.init({}, allocator),
+ };
+ }
+
+ pub fn emplace(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) Obj.Error!*tp.get() {
+ var newObj = true;
+ const obj = switch (tp) {
+ .String => try Obj.String.intern(arg, &self.table, &newObj, self.allocator),
+ else => try tp.get().init(arg, self.allocator),
+ };
+ if (newObj) try self.list.push(Value.init(obj.cast()), self.allocator);
+ return obj;
+ }
+
+ pub fn emplace_cast(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) Obj.Error!*Obj {
+ return (try self.emplace(tp, arg)).cast();
+ }
+
+ pub fn deinit(self: *Self) void {
+ while (self.list.pop(self.allocator)) |el| {
+ el.obj.free(self.allocator);
+ }
+ self.list.free(self.allocator);
+ self.table.deinit();
+ }
+};
diff --git a/zlox/src/main.zig b/zlox/src/main.zig
@@ -33,9 +33,15 @@ pub fn main() anyerror!u8 {
pub fn runFile(allocator: std.mem.Allocator, path: []const u8) anyerror!void {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
+ var VM = try vm.VM.init(allocator);
+ defer VM.deinit();
const text = try file.reader().readAllAlloc(allocator, 999999);
defer allocator.free(text);
+
+ VM.interpret(text, false) catch |err| {
+ std.debug.print("Error: {}\n", .{err});
+ };
}
pub fn repl(allocator: std.mem.Allocator, dbg: bool) anyerror!void {
diff --git a/zlox/src/obj.zig b/zlox/src/obj.zig
@@ -19,6 +19,7 @@ pub const Obj = packed struct {
Table,
Function,
Native,
+ List,
pub fn get(comptime self: @This()) type {
return @field(Super, @tagName(self));
diff --git a/zlox/src/obj/list.zig b/zlox/src/obj/list.zig
@@ -1,59 +1,97 @@
const std = @import("std");
+const Value = @import("../value.zig").Value;
const Super = @import("../obj.zig").Obj;
+const utils = @import("../comptime_utils.zig");
const Error = Super.Error;
const String = Super.String;
const Type = Super.Type;
-pub const List = struct {
+pub const List = packed struct {
const Self = @This();
+
const Element = struct {
- obj: *Super,
+ val: Value,
next: ?*@This(),
};
+ pub const Arg = void;
+
+ obj: Super,
+ len: usize = 0,
tip: ?*Element,
- allocator: std.mem.Allocator,
- table: String.Table,
- pub fn init(allocator: std.mem.Allocator) Self {
- return Self{ .tip = null, .allocator = allocator, .table = String.Table.init(allocator) };
+ pub fn init(_: Arg, allocator: std.mem.Allocator) Error!*Self {
+ const self: *Self = try allocator.create(Self);
+ self.* = Self{
+ .obj = Super{
+ .type = Super.Type.List,
+ },
+ .tip = null,
+ };
+ return self;
}
- pub fn push(self: *Self, val: *Super) Error!void {
- var new_tip = try self.allocator.create(Element);
- new_tip.next = self.tip;
- new_tip.obj = val;
- self.tip = new_tip;
+ pub fn cast(self: *Self) *Super {
+ return @ptrCast(self);
}
- pub fn emplace(self: *Self, comptime tp: Type, arg: tp.get().Arg) Error!*tp.get() {
- var newObj = true;
- const obj = switch (tp) {
- .String => try String.intern(arg, &self.table, &newObj, self.allocator),
- else => try tp.get().init(arg, self.allocator),
- };
- if (newObj) try self.push(obj.cast());
- return obj;
+ pub fn format(self: *const Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) utils.fn_error(@TypeOf(writer).write)!void {
+ _ = try writer.write("[");
+ var tip = self.tip;
+ while (tip) |el| : (tip = el.next) {
+ try el.val.format(fmt, options, writer);
+ _ = try writer.write(",");
+ }
+ _ = try writer.writeAll("]");
}
- pub fn emplace_cast(self: *Self, comptime tp: Type, arg: tp.get().Arg) Error!*Super {
- return (try self.emplace(tp, arg)).cast();
+ pub fn eql(self: *const Self, other: *const Self) bool {
+ if (self.len != other.len) return false;
+ if (self.len == 0) return true;
+ var tip1 = self.tip;
+ var tip2 = other.tip;
+ while (tip1) |el1| : (tip1 = el1.next) {
+ if (!el1.val.eql(tip2.?.val))
+ return false;
+ tip2 = tip2.?.next;
+ }
+ return true;
}
- pub fn pop(self: *Self) ?*Element {
+ pub fn free(self: *Self, allocator: std.mem.Allocator) void {
+ while (self.pop(allocator)) |_| {}
+ allocator.destroy(self);
+ }
+
+ pub fn get(self: *const Self, index: Value) !Value {
+ if (!index.is(Value.number) or index.number >= @as(Value.tagType(Value.number), @floatFromInt(self.len)) or index.number < 0) {
+ return error.KeyError;
+ }
+ var idx = self.len - @as(usize, @intFromFloat(index.number));
+ var tip = self.tip;
+ while(idx > 1) : (idx -= 1) {
+ tip = tip.?.next;
+ }
+ return tip.?.val;
+ }
+
+ pub fn pop(self: *Self, allocator: std.mem.Allocator) ?Value {
if (self.tip) |tip| {
self.tip = tip.next;
- tip.obj.free(self.allocator);
- return tip;
+ self.len -= 1;
+ const val = tip.val;
+ allocator.destroy(tip);
+ return val;
}
return null;
}
- pub fn deinit(self: *Self) void {
- while (self.pop()) |tip| {
- self.allocator.destroy(tip);
- }
- self.table.deinit();
+ pub fn push(self: *Self, val: Value, allocator: std.mem.Allocator) Error!void {
+ var new_tip = try allocator.create(Element);
+ new_tip.next = self.tip;
+ new_tip.val = val;
+ self.tip = new_tip;
+ self.len += 1;
}
};
diff --git a/zlox/src/obj/native.zig b/zlox/src/obj/native.zig
@@ -0,0 +1,59 @@
+const std = @import("std");
+
+const Value = @import("../value.zig").Value;
+const Super = @import("../obj.zig").Obj;
+const Error = Super.Error;
+
+pub const Native = packed struct {
+ const Self = @This();
+ pub const Fn = *const fn ([]const Value) Value;
+
+ pub const Arg = struct {
+ fun: Fn,
+ arity: u8,
+ name: []const u8
+ };
+
+ obj: Super,
+ fun: Fn,
+ arity: u8,
+ name: [*]const u8,
+ name_len: usize,
+
+ pub fn init(arg: Arg, allocator: std.mem.Allocator) Error!*Self {
+ const self: *Self = try allocator.create(Self);
+ self.* = Self{
+ .obj = Super{
+ .type = Super.Type.Native,
+ },
+ .fun = arg.fun,
+ .arity = arg.arity,
+ .name = arg.name.ptr,
+ .name_len = arg.name.len
+ };
+ return self;
+ }
+
+ pub fn call(self: *const Self, argCount: u8, args: [*]Value) Value {
+ return self.fun(args[0..argCount]);
+ }
+
+ pub fn cast(self: *Self) *Super {
+ return @ptrCast(self);
+ }
+
+ pub fn format(self: *const Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
+ _ = try writer.write("<N: ");
+ _ = try writer.write(self.name[0..self.name_len]);
+ _ = try writer.writeAll(">");
+ }
+
+ pub fn eql(_: *const Self, _: *const Self) bool {
+ return false;
+ }
+
+ pub fn free(self: *const Self, allocator: std.mem.Allocator) void {
+ allocator.destroy(self);
+ }
+};
+
diff --git a/zlox/src/obj/string.zig b/zlox/src/obj/string.zig
@@ -42,9 +42,7 @@ pub const String = packed struct {
return @ptrCast(self);
}
pub fn format(self: *const Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
- _ = try writer.write("\"");
- _ = try writer.write(self.slice());
- _ = try writer.writeAll("\"");
+ _ = try writer.writeAll(self.slice());
}
pub fn eql(self: *const Self, other: *const Self) bool {
return @intFromPtr(self) == @intFromPtr(other);
diff --git a/zlox/src/scanner.zig b/zlox/src/scanner.zig
@@ -73,7 +73,7 @@ pub const Token = struct {
line: i32,
column: usize,
- pub const Empty = @This(){ .type = ScannerError.EmptyToken, .lexeme = "", .line = -1, .column = 0 };
+ pub const Empty = @This(){ .type = ScannerError.EmptyToken, .lexeme = "", .line = -1, .column = 1 };
};
pub const Scanner = struct {
@@ -101,7 +101,7 @@ pub const Scanner = struct {
});
pub fn init(source: []const u8) !@This() {
- return @This(){ .start = source.ptr, .current = source.ptr, .end = source.ptr + source.len, .line_ptr = source.ptr, .line = 0 };
+ return @This(){ .start = source.ptr, .current = source.ptr, .end = source.ptr + source.len, .line_ptr = source.ptr, .line = 1 };
}
pub fn scanToken(self: *@This()) Token {
@@ -247,7 +247,7 @@ pub const Scanner = struct {
}
fn column(self: *const @This()) usize {
- return @intFromPtr(self.start) - @intFromPtr(self.line_ptr);
+ return 1 + @intFromPtr(self.start) - @intFromPtr(self.line_ptr);
}
fn lexeme(self: *const @This()) []const u8 {
diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig
@@ -5,6 +5,7 @@ const std = @import("std");
const debug = @import("debug.zig");
const compiler = @import("compiler.zig");
const Obj = @import("obj.zig").Obj;
+const GC = @import("gc.zig").GC;
const Callback = @import("vm/callbacks.zig");
const table = @import("table.zig");
const hash = @import("hash.zig");
@@ -14,7 +15,7 @@ const vm_native = @import("vm/native.zig");
pub const InterpreterError = compiler.CompilerError || Callback.Error || error{ CompileError, RuntimeError, StackOverflow, IndexOutOfBounds, Overflow, DivisionByZero };
pub const VM = struct {
- objects: Obj.List,
+ objects: GC,
globals: Globals,
allocator: std.mem.Allocator,
@@ -66,9 +67,13 @@ pub const VM = struct {
}
pub fn init(allocator: std.mem.Allocator) !@This() {
- var self = @This(){ .globals = Globals.init(allocator), .objects = Obj.List.init(allocator), .allocator = allocator };
+ var self = @This(){ .globals = Globals.init(allocator), .objects = try GC.init(allocator), .allocator = allocator };
+
try self.defineNative("clock", 0, vm_native.clock);
+ try self.defineNative("put", 1, vm_native.put);
+
try vm_native.set_start();
+
return self;
}
@@ -342,7 +347,7 @@ pub const VM = struct {
while (true) : (i -= 1) {
const fram = self.frames[i];
const idx = @intFromPtr(fram.ip) - @intFromPtr(fram.function.chunk.code.data.ptr);
- std.debug.print("[line {d}] in {s}\n", .{fram.function.chunk.lines.get(idx) catch 0, fram.function});
+ std.debug.print("[line {d}] in {s}\n", .{fram.function.chunk.lines.get(idx) catch 1, fram.function});
if (i == 0) break;
}
std.debug.print(fmt ++ "\n", args);
diff --git a/zlox/src/vm/callbacks.zig b/zlox/src/vm/callbacks.zig
@@ -1,5 +1,6 @@
const std = @import("std");
const Obj = @import("../obj.zig").Obj;
+const GC = @import("../gc.zig").GC;
const Value = @import("../value.zig").Value;
const Number = Value.tagType(.number);
@@ -10,7 +11,7 @@ pub const Error = Obj.Error;
pub fn Type(comptime in_tag: anytype, comptime out_tag: anytype) type {
if (@TypeOf(in_tag) == Obj.Type) {
return struct {
- objects: *Obj.List,
+ objects: *GC,
_call: *const fn (self: *const @This(), Value.tagType(in_tag), Value.tagType(in_tag)) Error!Value.tagType(out_tag),
pub fn call(self: *const @This(), a: Value.tagType(in_tag), b: Value.tagType(in_tag)) Error!Value.tagType(out_tag) {
return self._call(self, a, b);
@@ -23,7 +24,7 @@ pub fn Type(comptime in_tag: anytype, comptime out_tag: anytype) type {
}
}
-pub fn concatenate(objects: *Obj.List) Type(Obj.Type.String, Obj.Type.String) {
+pub fn concatenate(objects: *GC) Type(Obj.Type.String, Obj.Type.String) {
const Ret = Type(Obj.Type.String, Obj.Type.String);
const ret = Ret{ .objects = objects, ._call = struct {
pub fn concatenate(self: *const Ret, lhs: *Obj, rhs: *Obj) Error!*Obj {
diff --git a/zlox/src/vm/native.zig b/zlox/src/vm/native.zig
@@ -0,0 +1,18 @@
+const std = @import("std");
+const Value = @import("../value.zig").Value;
+var start: std.time.Instant = undefined;
+
+pub fn set_start() !void {
+ start = try std.time.Instant.now();
+}
+
+pub fn clock(_: [] const Value) Value {
+ const now = std.time.Instant.now() catch unreachable;
+ const elapsed: f64 = @floatFromInt(now.since(start));
+ return Value.init(elapsed / std.time.ns_per_s);
+}
+
+pub fn put(args: []const Value) Value {
+ std.debug.print("{s}", .{args[0]});
+ return Value.init({});
+}