commit 9c08125283c712015f7c4397fe30d70b8f3dc6f7
parent 18bdcb0101c47da2fb1a046c30e38eba48dbf5b6
Author: Ashymad <szymon.mikulicz@posteo.net>
Date: Sun, 10 Mar 2024 22:19:31 +0100
Types 1
Diffstat:
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 {}
};