DzLox

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

commit 372a5c516991a81012d7b7031dc39f4b88b81db1
parent 06108bc2948c70884037f64dd333fae93f07df2c
Author: Ashymad <szymon.mikulicz@posteo.net>
Date:   Tue, 27 Feb 2024 21:56:32 +0100

Implement a trie for identifiers

Diffstat:
Mzlox/build.zig | 12+++---------
Dzlox/build.zig.zon | 13-------------
Mzlox/src/compiler.zig | 5+++--
Mzlox/src/main.zig | 18++++++++++--------
Mzlox/src/scanner.zig | 46++++++++++++++++++++++++++++++----------------
Mzlox/src/test.zig | 18+++++++++++++++++-
Mzlox/src/trie.zig | 53+++++++++++++++++++++++++++++++++++++++++++++++++----
Mzlox/src/vm.zig | 8+++-----
8 files changed, 115 insertions(+), 58 deletions(-)

diff --git a/zlox/build.zig b/zlox/build.zig @@ -24,14 +24,6 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); - const linenoize = b.dependency("linenoize", .{ // <== as declared in build.zig.zon - .target = target, // the same as passing `-Dtarget=<...>` to the library's build.zig script - .optimize = optimize, // ditto for `-Doptimize=<...>` - }).module("linenoise"); - - // your executable config - exe.root_module.addImport("linenoize", linenoize); - // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default // step when running `zig build`). @@ -60,10 +52,12 @@ pub fn build(b: *std.Build) void { const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); + exe.linkLibC(); + exe.linkSystemLibrary("linenoise"); // Creates a step for unit testing. This only builds the test executable // but does not run it. const unit_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = .{ .path = "src/test.zig" }, .target = target, .optimize = optimize, }); diff --git a/zlox/build.zig.zon b/zlox/build.zig.zon @@ -1,13 +0,0 @@ -.{ - .name = "zlox", - .version = "0.1.0", - .paths = .{ - "./src", - }, - .dependencies = .{ - .linenoize = .{ - .url = "https://github.com/joachimschmidt557/linenoize/archive/refs/heads/master.tar.gz", - .hash = "12201240f2e3bd999a7846b8d408c2b59243f2ea427efe5cdb99091d28bb9ebc9287", - }, - }, -} diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -3,8 +3,9 @@ const scanner = @import("scanner.zig"); pub const CompilerError = scanner.ScannerError; -pub fn compile(source: []const u8) CompilerError!void { - var Scanner = scanner.Scanner.init(source); +pub fn compile(source: []const u8, allocator: std.mem.Allocator) !void { + var Scanner = try scanner.Scanner.init(source, allocator); + defer Scanner.deinit(); var line: i32 = -1; diff --git a/zlox/src/main.zig b/zlox/src/main.zig @@ -1,6 +1,9 @@ const std = @import("std"); const vm = @import("vm.zig"); -const Linenoise = @import("linenoize").Linenoise; +const Linenoise = @cImport({ + @cInclude("stddef.h"); + @cInclude("linenoise.h"); +}); pub fn main() anyerror!u8 { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; @@ -32,15 +35,14 @@ pub fn runFile(allocator: std.mem.Allocator, path: []const u8) anyerror!void { } pub fn repl(allocator: std.mem.Allocator) anyerror!void { - var ln = Linenoise.init(allocator); - defer ln.deinit(); - var VM = vm.VM.init(); defer VM.deinit(); - while (try ln.linenoise("lox> ")) |input| { - defer allocator.free(input); - try VM.interpret(input); - try ln.history.add(input); + _ = Linenoise.linenoiseHistorySetMaxLen(100); + + while (Linenoise.linenoise("lox> ")) |line| { + defer Linenoise.linenoiseFree(line); + try VM.interpret(std.mem.span(line), allocator); + _ = Linenoise.linenoiseHistoryAdd(line); } } diff --git a/zlox/src/scanner.zig b/zlox/src/scanner.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const trie = @import("trie.zig"); pub const TokenType = enum { LEFT_PAREN, @@ -54,11 +55,30 @@ pub const Token = struct { line: i32, }; -const keywords: [][]const u8 = [_][]const u8{"while"}; - 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 = 0 }; + pub fn init(source: []const u8, allocator: std.mem.Allocator) !@This() { + var this = @This(){ .start = source.ptr, .current = source.ptr, .end = source.ptr + source.len, .line = 0, .identifiers = try trie.TrieTable(TokenType).init(allocator) }; + try this.identifiers.put("and", TokenType.AND); + try this.identifiers.put("class", TokenType.CLASS); + try this.identifiers.put("else", TokenType.ELSE); + try this.identifiers.put("false", TokenType.FALSE); + try this.identifiers.put("for", TokenType.FOR); + try this.identifiers.put("fun", TokenType.FUN); + try this.identifiers.put("if", TokenType.IF); + try this.identifiers.put("nil", TokenType.NIL); + try this.identifiers.put("or", TokenType.OR); + try this.identifiers.put("print", TokenType.PRINT); + try this.identifiers.put("return", TokenType.RETURN); + try this.identifiers.put("super", TokenType.SUPER); + try this.identifiers.put("this", TokenType.THIS); + try this.identifiers.put("true", TokenType.TRUE); + try this.identifiers.put("var", TokenType.VAR); + try this.identifiers.put("while", TokenType.WHILE); + return this; + } + + pub fn deinit(self: *@This()) void { + self.identifiers.deinit(); } pub fn scanToken(self: *@This()) ScannerError!Token { @@ -141,18 +161,11 @@ pub const Scanner = struct { } fn identifierType(self: *const @This()) TokenType { - return switch (self.start[0]) { - 'a' => self.checkKeyword(1, "nd", TokenType.AND), - else => TokenType.IDENTIFIER, - }; - } - - fn checkKeyword(self: *const @This(), start: u8, rest: []const u8, token: TokenType) TokenType { - return if (@intFromPtr(self.current) - @intFromPtr(self.start) == start + rest.len and - std.mem.eql(u8, self.start[start .. start + rest.len], rest)) - token - else - TokenType.IDENTIFIER; + if (self.identifiers.get(self.start[0..(@intFromPtr(self.current) - @intFromPtr(self.start))])) |tok| { + return tok; + } else { + return TokenType.IDENTIFIER; + } } fn advance(self: *@This()) u8 { @@ -191,6 +204,7 @@ pub const Scanner = struct { return Token{ .type = tokentype, .lexeme = self.start[0..(@intFromPtr(self.current) - @intFromPtr(self.start))], .line = self.line }; } + identifiers: trie.TrieTable(TokenType), start: [*]const u8, current: [*]const u8, end: [*]const u8, diff --git a/zlox/src/test.zig b/zlox/src/test.zig @@ -2,8 +2,9 @@ const std = @import("std"); const chunk = @import("chunk.zig"); const debug = @import("debug.zig"); const vm = @import("vm.zig"); +const Trie = @import("trie.zig").TrieTable; -fn testChunk() anyerror!void { +test "testChunk" { var allocator = std.heap.GeneralPurposeAllocator(.{}){}; defer std.debug.assert(allocator.deinit() == std.heap.Check.ok); var ch = try chunk.Chunk.init(allocator.allocator()); @@ -29,4 +30,19 @@ fn testChunk() anyerror!void { try ch.writeOP(chunk.OP.RETURN, 123); try VM.interpretChunk(&ch); + + try std.testing.expect(VM.pop() == -((1.2 + 3.4) / 5.6)); +} + +test "testTrie" { + var allocator = std.heap.GeneralPurposeAllocator(.{}){}; + defer std.debug.assert(allocator.deinit() == std.heap.Check.ok); + + var tr = try Trie(u8).init(allocator.allocator()); + defer tr.deinit(); + + try tr.put("test", 16); + + try std.testing.expectEqual(tr.get("test").?, 16); + try std.testing.expectEqual(tr.get("tes"), null); } diff --git a/zlox/src/trie.zig b/zlox/src/trie.zig @@ -1,5 +1,50 @@ -pub fn Trie(comptime T: type) type { +const std = @import("std"); + +pub fn TrieTable(comptime T: type) type { return struct { - len: S, - data: []T, - allocator: std.mem.Allocator, + value: ?T, + data: [26]?*@This(), + allocator: ?std.heap.ArenaAllocator, + + pub fn init(allocator: std.mem.Allocator) !@This() { + return @This(){ .value = null, .data = [_]?*@This(){null} ** 26, .allocator = std.heap.ArenaAllocator.init(allocator) }; + } + + pub fn put(self: *@This(), word: []const u8, value: T) !void { + var this = self; + const allo = self.allocator.?.allocator(); + for (word) |ch| { + const idx = ch - 'a'; + if (this.data[idx]) |val| { + this = val; + } else { + var new = try allo.create(@This()); + new.value = null; + new.data = [_]?*@This(){null} ** 26; + new.allocator = null; + this.data[idx] = new; + this = new; + } + } + this.value = value; + } + + pub fn get(self: *const @This(), word: []const u8) ?T { + var this = self; + for (word) |ch| { + if (ch < 'a' or ch > 'z') return null; + const idx = ch - 'a'; + if (this.data[idx]) |val| { + this = val; + } else { + return null; + } + } + return this.value; + } + + pub fn deinit(self: *@This()) void { + self.allocator.?.deinit(); + } + }; +} diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -35,8 +35,8 @@ pub const VM = struct { try self.run(true); } - pub fn interpret(self: *@This(), source: []const u8) InterpreterError!void { - try compiler.compile(source); + pub fn interpret(self: *@This(), source: []const u8, allocator: std.mem.Allocator) !void { + try compiler.compile(source, allocator); self.resetStack(); } @@ -59,7 +59,7 @@ pub const VM = struct { self.stackTop += 1; } - fn pop(self: *@This()) value.Value { + pub fn pop(self: *@This()) value.Value { self.stackTop -= 1; return self.stackTop[0]; } @@ -86,8 +86,6 @@ pub const VM = struct { const instruction: u8 = self.read_byte(); switch (instruction) { @intFromEnum(OP.RETURN) => { - value.printValue(self.pop()); - std.debug.print("\n", .{}); return; }, @intFromEnum(OP.CONSTANT) => {