DzLox

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

commit 2de0bcd3e29b8ea0281bfae961c0aa46b5838046
parent 34885a21987d932cdd390709661470e9b4241b78
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Sun,  3 Sep 2023 18:26:03 +0200

Add scanner beginning

Diffstat:
Mzlox/build.zig | 8++++++++
Azlox/build.zig.zon | 10++++++++++
Mzlox/src/chunk.zig | 5+++++
Azlox/src/compiler.zig | 23+++++++++++++++++++++++
Mzlox/src/debug.zig | 14+++++++-------
Mzlox/src/main.zig | 53+++++++++++++++++++++++++++++++++--------------------
Azlox/src/scanner.zig | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Azlox/src/test.zig | 32++++++++++++++++++++++++++++++++
Mzlox/src/vm.zig | 26++++++++++++++++----------
9 files changed, 305 insertions(+), 37 deletions(-)

diff --git a/zlox/build.zig b/zlox/build.zig @@ -24,6 +24,14 @@ 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.addModule("linenoize", linenoize); // <== for zig project + // 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`). diff --git a/zlox/build.zig.zon b/zlox/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = "zlox", + .version = "0.1.0", + .dependencies = .{ + .linenoize = .{ + .url = "https://github.com/joachimschmidt557/linenoize/archive/180e8d1d8df4811d20ab7e216da9a6e2c35eb3a5.tar.gz", + .hash = "1220aa9878b4bfa6a70a10793ae8ad42ea7cb1bbd1394c78613c3a9480aae0694d79", + }, + }, +} diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig @@ -27,6 +27,11 @@ pub const Chunk = struct { try self.lines.add(line); } + pub fn writeOP(self: *@This(), op: OP, line: u32) !void { + try self.code.add(@intFromEnum(op)); + try self.lines.add(line); + } + pub fn addConstant(self: *@This(), val: value.Value) !u8 { try self.constants.add(val); return self.constants.len - 1; diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const scanner = @import("scanner.zig"); + +pub const CompilerError = scanner.ScannerError; + +pub fn compile(source: []const u8) CompilerError!void { + var Scanner = scanner.Scanner.init(source); + + var line: i32 = -1; + + while (true) { + const token = try Scanner.scanToken(); + if (token.line != line) { + std.debug.print("{d:4} ", .{token.line}); + line = token.line; + } else { + std.debug.print(" | ", .{}); + } + std.debug.print("{s:15} '{s}'\n", .{ @tagName(token.type), token.lexeme }); + + if (token.type == scanner.TokenType.EOF) break; + } +} diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig @@ -23,13 +23,13 @@ pub fn disassembleInstruction(ch: chunk.Chunk, offset: usize) !usize { } return switch (try ch.code.get(offset)) { - @enumToInt(OP.RETURN) => simpleInstruction("OP_RETURN", offset), - @enumToInt(OP.NEGATE) => simpleInstruction("OP_NEGATE", offset), - @enumToInt(OP.ADD) => simpleInstruction("OP_ADD", offset), - @enumToInt(OP.SUBTRACT) => simpleInstruction("OP_SUBTRACT", offset), - @enumToInt(OP.DIVIDE) => simpleInstruction("OP_DIVIDE", offset), - @enumToInt(OP.MULTIPLY) => simpleInstruction("OP_MULTIPLY", offset), - @enumToInt(OP.CONSTANT) => try constantInstruction("OP_CONSTANT", ch, offset), + @intFromEnum(OP.RETURN) => simpleInstruction("OP_RETURN", offset), + @intFromEnum(OP.NEGATE) => simpleInstruction("OP_NEGATE", offset), + @intFromEnum(OP.ADD) => simpleInstruction("OP_ADD", offset), + @intFromEnum(OP.SUBTRACT) => simpleInstruction("OP_SUBTRACT", offset), + @intFromEnum(OP.DIVIDE) => simpleInstruction("OP_DIVIDE", offset), + @intFromEnum(OP.MULTIPLY) => simpleInstruction("OP_MULTIPLY", offset), + @intFromEnum(OP.CONSTANT) => try constantInstruction("OP_CONSTANT", ch, offset), else => blk: { print("Unknown opcode {}\n", .{try ch.code.get(offset)}); break :blk offset + 1; diff --git a/zlox/src/main.zig b/zlox/src/main.zig @@ -1,33 +1,46 @@ const std = @import("std"); -const chunk = @import("chunk.zig"); -const debug = @import("debug.zig"); const vm = @import("vm.zig"); +const Linenoise = @import("linenoize").Linenoise; pub fn main() anyerror!u8 { - var allocator = std.heap.GeneralPurposeAllocator(.{}){}; - defer std.debug.assert(allocator.deinit() == std.heap.Check.ok); - var ch = try chunk.Chunk.init(allocator.allocator()); - defer ch.deinit(); - var VM = vm.VM.init(); - defer VM.deinit(); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer std.debug.assert(gpa.deinit() == std.heap.Check.ok); - try ch.write(@enumToInt(chunk.OP.CONSTANT), 123); - try ch.write(try ch.addConstant(1.2), 123); + const allocator = gpa.allocator(); - try ch.write(@enumToInt(chunk.OP.CONSTANT), 123); - try ch.write(try ch.addConstant(3.4), 123); + const args = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, args); - try ch.write(@enumToInt(chunk.OP.ADD), 123); + if (args.len == 1) { + try repl(allocator); + } else if (args.len == 2) { + try runFile(allocator, args[1]); + } else { + std.debug.print("Usage: {s} [path]\n", .{args[0]}); + return 64; + } - try ch.write(@enumToInt(chunk.OP.CONSTANT), 123); - try ch.write(try ch.addConstant(5.6), 123); + return 0; +} - try ch.write(@enumToInt(chunk.OP.DIVIDE), 123); +pub fn runFile(allocator: std.mem.Allocator, path: []const u8) anyerror!void { + const file = try std.fs.cwd().openFile(path, .{}); + defer file.close(); - try ch.write(@enumToInt(chunk.OP.NEGATE), 123); + var text = try file.reader().readAllAlloc(allocator, 999999); + defer allocator.free(text); +} - try ch.write(@enumToInt(chunk.OP.RETURN), 123); +pub fn repl(allocator: std.mem.Allocator) anyerror!void { + var ln = Linenoise.init(allocator); + defer ln.deinit(); - try VM.interpret(&ch); - return 0; + 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); + } } diff --git a/zlox/src/scanner.zig b/zlox/src/scanner.zig @@ -0,0 +1,171 @@ +const std = @import("std"); + +pub const TokenType = enum { + LEFT_PAREN, + RIGHT_PAREN, + LEFT_BRACE, + RIGHT_BRACE, + COMMA, + DOT, + MINUS, + PLUS, + SEMICOLON, + SLASH, + STAR, + // One or two character tokens. + BANG, + BANG_EQUAL, + EQUAL, + EQUAL_EQUAL, + GREATER, + GREATER_EQUAL, + LESS, + LESS_EQUAL, + // Literals. + IDENTIFIER, + STRING, + NUMBER, + // Keywords. + AND, + CLASS, + ELSE, + FALSE, + FOR, + FUN, + IF, + NIL, + OR, + PRINT, + RETURN, + SUPER, + THIS, + TRUE, + VAR, + WHILE, + + EOF, +}; + +pub const ScannerError = error{ UnexpectedCharacter, UnknownCharacter }; + +pub const Token = struct { + type: TokenType, + lexeme: []const u8, + line: i32, +}; + +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 scanToken(self: *@This()) ScannerError!Token { + self.start = self.current; + + if (self.isAtEnd()) return self.makeToken(TokenType.EOF); + + self.skipWhitespace(); + + switch (self.advance()) { + '(' => return self.makeToken(TokenType.LEFT_PAREN), + ')' => return self.makeToken(TokenType.RIGHT_PAREN), + '{' => return self.makeToken(TokenType.LEFT_BRACE), + '}' => return self.makeToken(TokenType.RIGHT_BRACE), + ';' => return self.makeToken(TokenType.SEMICOLON), + ',' => return self.makeToken(TokenType.COMMA), + '.' => return self.makeToken(TokenType.DOT), + '-' => return self.makeToken(TokenType.MINUS), + '+' => return self.makeToken(TokenType.PLUS), + '/' => return self.makeToken(TokenType.SLASH), + '*' => return self.makeToken(TokenType.STAR), + '!' => return self.makeToken(if (self.match('=')) TokenType.BANG_EQUAL else TokenType.BANG), + '=' => return self.makeToken(if (self.match('=')) TokenType.EQUAL_EQUAL else TokenType.EQUAL), + '<' => return self.makeToken(if (self.match('=')) TokenType.LESS_EQUAL else TokenType.LESS), + '>' => return self.makeToken(if (self.match('=')) TokenType.GREATER_EQUAL else TokenType.GREATER), + '0'...'9' => return self.number(), + 'a'...'z', 'A'...'Z', '_' => return self.identifier(), + else => return ScannerError.UnknownCharacter, + } + + return ScannerError.UnexpectedCharacter; + } + + fn skipWhitespace(self: *@This()) void { + while (true) { + switch (self.peek()) { + ' ', '\r', '\t' => _ = self.advance(), + '\n' => { + self.line += 1; + _ = self.advance(); + }, + '/' => { + if (self.peekNext() == '/') { + while (self.peek() != '\n' and !self.isAtEnd()) _ = self.advance(); + } else return; + }, + else => return, + } + } + } + + fn number(self: *@This()) Token { + while (isDigit(self.peek())) _ = self.advance(); + + if (self.peek() == '.' and isDigit(self.peekNext())) { + _ = self.advance(); + while (isDigit(self.peek())) _ = self.advance(); + } + + return self.makeToken(TokenType.NUMBER); + } + + fn identifier(self: *@This()) Token { + while (isAlpha(self.peek()) or isDigit(self.peek())) _ = self.advance(); + return self.makeToken(self.identifierType()); + } + + fn identifierType(_: *const @This()) TokenType { + return TokenType.IDENTIFIER; + } + + fn advance(self: *@This()) u8 { + self.current += 1; + return (self.current - 1)[0]; + } + + fn isDigit(c: u8) bool { + return c >= '0' and c <= '9'; + } + + fn isAlpha(c: u8) bool { + return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or c == '_'; + } + + fn peek(self: *const @This()) u8 { + return self.current[0]; + } + + fn peekNext(self: *const @This()) u8 { + return if (self.isAtEnd()) return 0 else self.current[1]; + } + + fn match(self: *@This(), expected: u8) bool { + if (self.isAtEnd()) return false; + if (self.current[0] != expected) return false; + self.current += 1; + return true; + } + + fn isAtEnd(self: *const @This()) bool { + return self.current == self.end; + } + + fn makeToken(self: *const @This(), tokentype: TokenType) Token { + return Token{ .type = tokentype, .lexeme = self.start[0..(@intFromPtr(self.current) - @intFromPtr(self.start))], .line = self.line }; + } + + start: [*]const u8, + current: [*]const u8, + end: [*]const u8, + line: i32, +}; diff --git a/zlox/src/test.zig b/zlox/src/test.zig @@ -0,0 +1,32 @@ +const std = @import("std"); +const chunk = @import("chunk.zig"); +const debug = @import("debug.zig"); +const vm = @import("vm.zig"); + +fn testChunk() anyerror!void { + var allocator = std.heap.GeneralPurposeAllocator(.{}){}; + defer std.debug.assert(allocator.deinit() == std.heap.Check.ok); + var ch = try chunk.Chunk.init(allocator.allocator()); + defer ch.deinit(); + var VM = vm.VM.init(); + defer VM.deinit(); + + try ch.writeOP(chunk.OP.CONSTANT, 123); + try ch.write(try ch.addConstant(1.2), 123); + + try ch.writeOP(chunk.OP.CONSTANT, 123); + try ch.write(try ch.addConstant(3.4), 123); + + try ch.writeOP(chunk.OP.ADD, 123); + + try ch.writeOP(chunk.OP.CONSTANT, 123); + try ch.write(try ch.addConstant(5.6), 123); + + try ch.writeOP(chunk.OP.DIVIDE, 123); + + try ch.writeOP(chunk.OP.NEGATE, 123); + + try ch.writeOP(chunk.OP.RETURN, 123); + + try VM.interpretChunk(&ch); +} diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -4,8 +4,9 @@ const value = @import("value.zig"); const std = @import("std"); const debug = @import("debug.zig"); const wrp = @import("wrap.zig"); +const compiler = @import("compiler.zig"); -pub const InterpreterError = error{ CompileError, RuntimeError, IndexOutOfBounds, Overflow, DivisionByZero }; +pub const InterpreterError = compiler.CompilerError || error{ CompileError, RuntimeError, IndexOutOfBounds, Overflow, DivisionByZero }; pub const VM = struct { ip: [*]const u8, @@ -27,13 +28,18 @@ pub const VM = struct { return ret; } - pub fn interpret(self: *@This(), chunk: *const Chunk) InterpreterError!void { + pub fn interpretChunk(self: *@This(), chunk: *const Chunk) InterpreterError!void { self.resetStack(); self.chunk = chunk; self.ip = chunk.code.data.ptr; try self.run(true); } + pub fn interpret(self: *@This(), source: []const u8) InterpreterError!void { + try compiler.compile(source); + self.resetStack(); + } + fn resetStack(self: *@This()) void { self.stackTop = &self.stack; } @@ -76,23 +82,23 @@ pub const VM = struct { } std.debug.print("\n", .{}); } - _ = try debug.disassembleInstruction(self.chunk.*, @ptrToInt(self.ip) - @ptrToInt(self.chunk.code.data.ptr)); + _ = try debug.disassembleInstruction(self.chunk.*, @intFromPtr(self.ip) - @intFromPtr(self.chunk.code.data.ptr)); const instruction: u8 = self.read_byte(); switch (instruction) { - @enumToInt(OP.RETURN) => { + @intFromEnum(OP.RETURN) => { value.printValue(self.pop()); std.debug.print("\n", .{}); return; }, - @enumToInt(OP.CONSTANT) => { + @intFromEnum(OP.CONSTANT) => { const constant = self.read_constant(); self.push(constant); }, - @enumToInt(OP.NEGATE) => self.push(-self.pop()), - @enumToInt(OP.ADD) => self.binary_op(wrp.add), - @enumToInt(OP.SUBTRACT) => self.binary_op(wrp.sub), - @enumToInt(OP.MULTIPLY) => self.binary_op(wrp.mul), - @enumToInt(OP.DIVIDE) => self.binary_op(wrp.div), + @intFromEnum(OP.NEGATE) => self.push(-self.pop()), + @intFromEnum(OP.ADD) => self.binary_op(wrp.add), + @intFromEnum(OP.SUBTRACT) => self.binary_op(wrp.sub), + @intFromEnum(OP.MULTIPLY) => self.binary_op(wrp.mul), + @intFromEnum(OP.DIVIDE) => self.binary_op(wrp.div), else => return InterpreterError.CompileError, } }