DzLox

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

commit 3a280a553cf0189bda3c0546b94716be5b07e506
parent 05deccbb3b058e462dbb667bda3086e14f934ce2
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date:   Tue, 13 Aug 2024 23:49:52 +0200

Lists

Diffstat:
Azlox/fib.lox | 32++++++++++++++++++++++++++++++++
Mzlox/src/compiler.zig | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Azlox/src/gc.zig | 42++++++++++++++++++++++++++++++++++++++++++
Mzlox/src/main.zig | 6++++++
Mzlox/src/obj.zig | 1+
Mzlox/src/obj/list.zig | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Azlox/src/obj/native.zig | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mzlox/src/obj/string.zig | 4+---
Mzlox/src/scanner.zig | 6+++---
Mzlox/src/vm.zig | 11++++++++---
Mzlox/src/vm/callbacks.zig | 5+++--
Azlox/src/vm/native.zig | 18++++++++++++++++++
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({}); +}