commit 2de0bcd3e29b8ea0281bfae961c0aa46b5838046
parent 34885a21987d932cdd390709661470e9b4241b78
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date: Sun, 3 Sep 2023 18:26:03 +0200
Add scanner beginning
Diffstat:
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,
}
}