DzLox

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

commit 9c08125283c712015f7c4397fe30d70b8f3dc6f7
parent 18bdcb0101c47da2fb1a046c30e38eba48dbf5b6
Author: Ashymad <szymon.mikulicz@posteo.net>
Date:   Sun, 10 Mar 2024 22:19:31 +0100

Types 1

Diffstat:
Mzlox/src/chunk.zig | 4++++
Mzlox/src/compiler.zig | 28+++++++++++++++++++---------
Mzlox/src/debug.zig | 6+++++-
Mzlox/src/scanner.zig | 2+-
Mzlox/src/trie.zig | 33+++++++++++++++++++++------------
Mzlox/src/value.zig | 59+++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mzlox/src/vm.zig | 66++++++++++++++++++++++++++++++++++++++++++++++++------------------
7 files changed, 149 insertions(+), 49 deletions(-)

diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig @@ -5,12 +5,16 @@ const array = @import("array.zig"); pub const OP = enum(u8) { CONSTANT, + NIL, + TRUE, + FALSE, RETURN, NEGATE, ADD, SUBTRACT, MULTIPLY, DIVIDE, + NOT, }; pub const ChunkError = error{OutOfMemory}; diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -1,10 +1,10 @@ const std = @import("std"); const scanner = @import("scanner.zig"); const chunk = @import("chunk.zig"); -const value = @import("value.zig"); +const Value = @import("value.zig").Value; const debug = @import("debug.zig"); -pub const CompilerError = scanner.ScannerError || chunk.ChunkError || value.ParseValueError || error{ UnexpectedToken, NotAnExpression }; +pub const CompilerError = scanner.ScannerError || chunk.ChunkError || Value.ParseNumberError || error{ UnexpectedToken, NotAnExpression }; const Precedence = enum { NONE, @@ -72,7 +72,7 @@ pub const Compiler = struct { T.SLASH => R(null, S.binary, P.FACTOR ), T.STAR => R(null, S.binary, P.FACTOR ), T.QUESTION => R(null, S.ternary, P.TERNARY ), - T.BANG => R(null, null, P.NONE ), + T.BANG => R(S.unary, null, P.NONE ), T.BANG_EQUAL => R(null, null, P.NONE ), T.EQUAL => R(null, null, P.NONE ), T.EQUAL_EQUAL => R(null, null, P.NONE ), @@ -86,17 +86,17 @@ pub const Compiler = struct { T.AND => R(null, null, P.NONE ), T.CLASS => R(null, null, P.NONE ), T.ELSE => R(null, null, P.NONE ), - T.FALSE => R(null, null, P.NONE ), + T.FALSE => R(S.literal, null, P.NONE ), T.FOR => R(null, null, P.NONE ), T.FUN => R(null, null, P.NONE ), T.IF => R(null, null, P.NONE ), - T.NIL => R(null, null, P.NONE ), + T.NIL => R(S.literal, null, P.NONE ), T.OR => R(null, null, P.NONE ), T.PRINT => R(null, null, P.NONE ), T.RETURN => R(null, null, P.NONE ), T.SUPER => R(null, null, P.NONE ), T.THIS => R(null, null, P.NONE ), - T.TRUE => R(null, null, P.NONE ), + T.TRUE => R(S.literal, null, P.NONE ), T.VAR => R(null, null, P.NONE ), T.WHILE => R(null, null, P.NONE ), T.EOF => R(null, null, P.NONE ), @@ -222,18 +222,18 @@ pub const Compiler = struct { } fn number(self: *@This()) void { - self.emitConstant(value.parseValue(self.previous.lexeme) catch |err| { + self.emitConstant(Value.parseNumber(self.previous.lexeme) catch |err| { self.lastError = err; self.errorAtPrevious("Invalid numeric literal"); return; }); } - fn emitConstant(self: *@This(), val: value.Value) void { + fn emitConstant(self: *@This(), val: Value) void { self.emit(chunk.OP.CONSTANT, self.makeConstant(val)); } - fn makeConstant(self: *@This(), val: value.Value) u8 { + fn makeConstant(self: *@This(), val: Value) u8 { return self.currentChunk().addConstant(val) catch |err| { self.lastError = err; self.errorAtPrevious("Too many constants in one chunk"); @@ -253,6 +253,16 @@ pub const Compiler = struct { switch (operatorType) { scanner.TokenType.MINUS => self.emitOP(chunk.OP.NEGATE), + scanner.TokenType.BANG => self.emitOP(chunk.OP.NOT), + else => unreachable, + } + } + + fn literal(self: *@This()) void { + switch (self.previous.type catch unreachable) { + scanner.TokenType.FALSE => self.emitOP(chunk.OP.FALSE), + scanner.TokenType.TRUE => self.emitOP(chunk.OP.TRUE), + scanner.TokenType.NIL => self.emitOP(chunk.OP.NIL), else => unreachable, } } diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig @@ -29,6 +29,10 @@ pub fn disassembleInstruction(ch: chunk.Chunk, offset: usize) !usize { @intFromEnum(OP.SUBTRACT) => simpleInstruction("OP_SUBTRACT", offset), @intFromEnum(OP.DIVIDE) => simpleInstruction("OP_DIVIDE", offset), @intFromEnum(OP.MULTIPLY) => simpleInstruction("OP_MULTIPLY", offset), + @intFromEnum(OP.TRUE) => simpleInstruction("OP_TRUE", offset), + @intFromEnum(OP.FALSE) => simpleInstruction("OP_FALSE", offset), + @intFromEnum(OP.NIL) => simpleInstruction("OP_NIL", offset), + @intFromEnum(OP.NOT) => simpleInstruction("OP_NOT", offset), @intFromEnum(OP.CONSTANT) => try constantInstruction("OP_CONSTANT", ch, offset), else => blk: { print("Unknown opcode {}\n", .{try ch.code.get(offset)}); @@ -45,7 +49,7 @@ fn simpleInstruction(name: []const u8, offset: usize) usize { fn constantInstruction(name: []const u8, ch: chunk.Chunk, offset: usize) !usize { const constant = try ch.code.get(offset + 1); print("{s:<16} {d:4} '", .{ name, constant }); - value.printValue(try ch.constants.get(constant)); + (try ch.constants.get(constant)).print(); print("'\n", .{}); return offset + 2; } diff --git a/zlox/src/scanner.zig b/zlox/src/scanner.zig @@ -70,7 +70,7 @@ pub const Token = struct { }; pub const Scanner = struct { - const identifiers = trie.TrieTable(TokenType, .{ + const identifiers = trie.LowercaseTrieTable(TokenType, .{ .{ "and", TokenType.AND }, .{ "class", TokenType.CLASS }, .{ "else", TokenType.ELSE }, diff --git a/zlox/src/trie.zig b/zlox/src/trie.zig @@ -1,9 +1,9 @@ const std = @import("std"); -pub fn TrieTable(comptime T: type, comptime list: anytype) type { +pub fn TrieTable(comptime Key: type, comptime Value: type, size: comptime_int, get_idx: fn (Key) usize, comptime list: anytype) type { const TrieLeaf = struct { - value: ?T, - data: [26]?*@This(), + value: ?Value, + data: [size]?*@This(), }; const max_len = comptime blk: { @@ -15,21 +15,22 @@ pub fn TrieTable(comptime T: type, comptime list: anytype) type { }; const precomputed = comptime blk: { - var allocated = [_]TrieLeaf{TrieLeaf{ .value = null, .data = [_]?*TrieLeaf{null} ** 26 }} ** max_len; + var allocated = [_]TrieLeaf{TrieLeaf{ .value = null, .data = [_]?*TrieLeaf{null} ** size }} ** max_len; var allocated_i = 0; - var tip = TrieLeaf{ .value = null, .data = [_]?*TrieLeaf{null} ** 26 }; + var tip = TrieLeaf{ .value = null, .data = [_]?*TrieLeaf{null} ** size }; for (list) |el| { var leaf = &tip; - for (el.@"0") |ch| { - const idx = ch - 'a'; + for (el.@"0") |key| { + const idx = get_idx(key); + std.debug.assert(idx >= 0 and idx < size); if (leaf.data[idx]) |val| { leaf = val; } else { var new = &allocated[allocated_i]; allocated_i += 1; new.value = null; - new.data = [_]?*TrieLeaf{null} ** 26; + new.data = [_]?*TrieLeaf{null} ** size; leaf.data[idx] = new; leaf = new; } @@ -43,11 +44,11 @@ pub fn TrieTable(comptime T: type, comptime list: anytype) type { const allocated = precomputed.allocated; const tip = precomputed.tip; - pub fn get(word: []const u8) ?T { + pub fn get(word: []const Key) ?Value { var this = &tip; - for (word) |ch| { - if (ch < 'a' or ch > 'z') return null; - const idx = ch - 'a'; + for (word) |key| { + const idx = get_idx(key); + if (idx < 0 or idx >= size) return null; if (this.data[idx]) |val| { this = val; } else { @@ -58,3 +59,11 @@ pub fn TrieTable(comptime T: type, comptime list: anytype) type { } }; } + +pub fn LowercaseTrieTable(comptime Value: type, comptime list: anytype) type { + return TrieTable(u8, Value, 26, struct { + pub fn idx(c: u8) usize { + return c - 'a'; + } + }.idx, list); +} diff --git a/zlox/src/value.zig b/zlox/src/value.zig @@ -1,16 +1,59 @@ const std = @import("std"); const array = @import("array.zig"); -pub const Value = f64; +pub const Value = union(enum) { + number: f64, + bool: bool, + nil: void, -pub const ParseValueError = std.fmt.ParseFloatError; + pub const Tag = std.meta.Tag(@This()); -pub fn printValue(value: Value) void { - std.debug.print("{d}", .{value}); -} + pub fn print(self: @This()) void { + switch (self) { + .number => |val| std.debug.print("{d}", .{val}), + .bool => |val| std.debug.print("{s}", .{if (val) "true" else "false"}), + .nil => std.debug.print("nil", .{}), + } + } -pub fn parseValue(str: []const u8) ParseValueError!Value { - return std.fmt.parseFloat(Value, str); -} + pub fn is(self: @This(), comptime tag: Tag) bool { + return switch (self) { + tag => true, + else => false, + }; + } + + pub fn new(comptime tag: Tag, value: tagType(tag)) @This() { + var ret = @This(){ .number = undefined }; + ret.set(tag, value); + return ret; + } + + pub fn get(self: @This(), comptime tag: Tag) tagType(tag) { + return @field(self, @tagName(tag)); + } + + pub fn set(self: *@This(), comptime tag: Tag, value: tagType(tag)) void { + @field(self, @tagName(tag)) = value; + } + + pub fn tagType(comptime tag: Tag) type { + return @TypeOf(@field(@This(){ .number = undefined }, @tagName(tag))); + } + + pub const ParseNumberError = std.fmt.ParseFloatError; + + pub fn parseNumber(str: []const u8) ParseNumberError!@This() { + return @This(){ .number = try std.fmt.parseFloat(tagType(Value.number), str) }; + } + + pub fn isTruthy(self: @This()) bool { + return switch (self) { + .nil => false, + .bool => |val| val, + else => true, + }; + } +}; pub const ValueArray = array.Array(Value, u8, 8); diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -1,6 +1,6 @@ const Chunk = @import("chunk.zig").Chunk; const OP = @import("chunk.zig").OP; -const value = @import("value.zig"); +const Value = @import("value.zig").Value; const std = @import("std"); const debug = @import("debug.zig"); const wrp = @import("wrap.zig"); @@ -12,16 +12,16 @@ pub const VM = struct { ip: [*]const u8, chunk: *const Chunk, stack: stackType, - stackTop: [*]value.Value, + stackTop: [*]Value, const stackSize = 256; - const stackType = [stackSize]value.Value; + const stackType = [stackSize]Value; pub fn init() @This() { var ret = @This(){ .ip = undefined, .chunk = undefined, - .stack = std.mem.zeroes(@This().stackType), + .stack = [_]Value{Value{ .number = 0 }} ** stackSize, .stackTop = undefined, }; ret.stackTop = &ret.stack; @@ -55,58 +55,88 @@ pub const VM = struct { return out; } - fn read_constant(self: *@This()) value.Value { + fn read_constant(self: *@This()) Value { return self.chunk.constants.data[self.read_byte()]; } - fn push(self: *@This(), val: value.Value) void { + fn push(self: *@This(), val: Value) void { self.stackTop[0] = val; self.stackTop += 1; } - pub fn pop(self: *@This()) value.Value { + pub fn pop(self: *@This()) Value { self.stackTop -= 1; return self.stackTop[0]; } - fn binary_op(self: *@This(), comptime op: fn (comptime T: type, value.Value, value.Value) value.Value) void { + pub fn peek(self: *const @This(), distance: usize) Value { + return (self.stackTop - (1 + distance))[0]; + } + + fn binary_op(self: *@This(), comptime tag: Value.Tag, op: fn (type, Value.tagType(tag), Value.tagType(tag)) Value.tagType(tag)) !void { const b = self.pop(); const a = self.pop(); - self.push(op(value.Value, a, b)); + if (a.is(tag) and b.is(tag)) { + self.push(Value.new(tag, op(Value.tagType(tag), a.get(tag), b.get(tag)))); + } else { + self.runtimeError("Operands have invalid types, expected: {s}", .{@tagName(tag)}); + return InterpreterError.RuntimeError; + } + } + + fn instruction_idx(self: *const @This()) usize { + return @intFromPtr(self.ip) - @intFromPtr(self.chunk.code.data.ptr); } fn run(self: *@This(), comptime dbg: bool) !void { while (true) { if (dbg) { std.debug.print(" ", .{}); - var stackPtr: [*]value.Value = &self.stack; + var stackPtr: [*]Value = &self.stack; while (stackPtr != self.stackTop) : (stackPtr += 1) { std.debug.print("[ ", .{}); - value.printValue(stackPtr[0]); + stackPtr[0].print(); std.debug.print(" ]", .{}); } std.debug.print("\n", .{}); } - _ = try debug.disassembleInstruction(self.chunk.*, @intFromPtr(self.ip) - @intFromPtr(self.chunk.code.data.ptr)); + _ = try debug.disassembleInstruction(self.chunk.*, self.instruction_idx()); const instruction: u8 = self.read_byte(); switch (instruction) { @intFromEnum(OP.RETURN) => { - std.debug.print("{d}\n", .{self.pop()}); + self.pop().print(); + std.debug.print("\n", .{}); return; }, @intFromEnum(OP.CONSTANT) => { const constant = self.read_constant(); self.push(constant); }, - @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), + @intFromEnum(OP.NEGATE) => { + if (!self.peek(0).is(Value.number)) { + self.runtimeError("Operand must be a number.", .{}); + return InterpreterError.RuntimeError; + } + self.push(Value{ .number = -self.pop().number }); + }, + @intFromEnum(OP.ADD) => try self.binary_op(Value.number, wrp.add), + @intFromEnum(OP.SUBTRACT) => try self.binary_op(Value.number, wrp.sub), + @intFromEnum(OP.MULTIPLY) => try self.binary_op(Value.number, wrp.mul), + @intFromEnum(OP.DIVIDE) => try self.binary_op(Value.number, wrp.div), + @intFromEnum(OP.TRUE) => self.push(Value{ .bool = true }), + @intFromEnum(OP.FALSE) => self.push(Value{ .bool = false }), + @intFromEnum(OP.NIL) => self.push(Value{ .nil = undefined }), + @intFromEnum(OP.NOT) => self.push(Value{ .bool = !self.pop().isTruthy() }), else => return InterpreterError.CompileError, } } } + fn runtimeError(self: *@This(), comptime fmt: []const u8, args: anytype) void { + std.debug.print(fmt, args); + std.debug.print("\n[line {d}] in script\n", .{self.chunk.lines.get(self.instruction_idx()) catch 0}); + self.resetStack(); + } + pub fn deinit(_: *@This()) void {} };