commit a8a432282ba4031a28124ab78715d6e6ab9fcf3f
parent 1bdf43f7670fca732061f69181b5f1281f0ee1ea
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date: Thu, 11 May 2023 20:02:09 +0200
Smth smth new things
Diffstat:
7 files changed, 204 insertions(+), 36 deletions(-)
diff --git a/zlox/build.zig b/zlox/build.zig
@@ -1,34 +1,67 @@
const std = @import("std");
-pub fn build(b: *std.build.Builder) void {
+// Although this function looks imperative, note that its job is to
+// declaratively construct a build graph that will be executed by an external
+// runner.
+pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
- // Standard release options allow the person running `zig build` to select
- // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
- const mode = b.standardReleaseOptions();
+ // Standard optimization options allow the person running `zig build` to select
+ // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
+ // set a preferred release mode, allowing the user to decide how to optimize.
+ const optimize = b.standardOptimizeOption(.{});
- const exe = b.addExecutable("zlox", "src/main.zig");
- exe.setTarget(target);
- exe.setBuildMode(mode);
+ const exe = b.addExecutable(.{
+ .name = "zlox",
+ // In this case the main source file is merely a path, however, in more
+ // complicated build scripts, this could be a generated file.
+ .root_source_file = .{ .path = "src/main.zig" },
+ .target = target,
+ .optimize = optimize,
+ });
+
+ // 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`).
exe.install();
+ // This *creates* a RunStep in the build graph, to be executed when another
+ // step is evaluated that depends on it. The next line below will establish
+ // such a dependency.
const run_cmd = exe.run();
+
+ // By making the run step depend on the install step, it will be run from the
+ // installation directory rather than directly from within the cache directory.
+ // This is not necessary, however, if the application depends on other installed
+ // files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());
+
+ // This allows the user to pass arguments to the application in the build
+ // command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}
+ // This creates a build step. It will be visible in the `zig build --help` menu,
+ // and can be selected like this: `zig build run`
+ // This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
- const exe_tests = b.addTest("src/main.zig");
- exe_tests.setTarget(target);
- exe_tests.setBuildMode(mode);
+ // Creates a step for unit testing.
+ const exe_tests = b.addTest(.{
+ .root_source_file = .{ .path = "src/main.zig" },
+ .target = target,
+ .optimize = optimize,
+ });
+ // Similar to creating the run step earlier, this exposes a `test` step to
+ // the `zig build --help` menu, providing a way for the user to request
+ // running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&exe_tests.step);
}
diff --git a/zlox/src/array.zig b/zlox/src/array.zig
@@ -2,43 +2,40 @@ const std = @import("std");
pub fn Array(comptime T: type, comptime S: type, comptime size: S) type {
return struct {
- count: S,
- capacity: S,
- elements: []T,
+ len: S,
+ data: []T,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) !@This() {
return @This() {
- .count = 0,
- .capacity = size,
- .elements = try allocator.alloc(T, size),
+ .len = 0,
+ .data = try allocator.alloc(T, size),
.allocator = allocator
};
}
pub fn add(self: *@This(), val: T) !void {
- if (self.capacity < self.count + 1) {
- self.capacity = 2 * self.count;
- self.elements = try self.allocator.realloc(self.elements, self.capacity);
+ if (self.data.len <= self.len) {
+ self.data = try self.allocator.realloc(self.data, 2 * self.data.len);
}
- self.elements[self.count] = val;
- self.count += 1;
+ self.data[self.len] = val;
+ self.len += 1;
}
pub fn get(self: *const @This(), idx: S) !T {
- if (idx >= self.count)
+ if (idx >= self.len)
return error.IndexOutOfBounds;
- return self.elements[idx];
+ return self.data[idx];
}
pub fn last(self: *const @This()) !T {
- if (self.count == 0)
+ if (self.len == 0)
return error.IndexOutOfBounds;
- return self.get(self.count - 1);
+ return self.data[self.len - 1];
}
pub fn deinit(self: *@This()) void {
- self.allocator.free(self.elements);
+ self.allocator.free(self.data);
}
};
}
@@ -61,7 +58,7 @@ pub fn RLEArray(comptime T: type, comptime size: usize) type {
return;
};
if (val == last.val) {
- self.array.elements[self.array.count-1].run += 1;
+ self.array.data[self.array.len-1].run += 1;
} else {
try self.array.add(.{.val = val, .run = 1});
}
@@ -73,7 +70,7 @@ pub fn RLEArray(comptime T: type, comptime size: usize) type {
while(idx >= sum) : (i += 1) {
sum += (try self.array.get(i)).run;
}
- if (i > 0) return self.array.elements[i-1].val
+ if (i > 0) return self.array.data[i-1].val
else return (try self.array.get(i)).val;
}
diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig
@@ -6,11 +6,16 @@ const array = @import("array.zig");
pub const OP = enum(u8) {
CONSTANT,
RETURN,
+ NEGATE,
+ ADD,
+ SUBTRACT,
+ MULTIPLY,
+ DIVIDE,
};
pub const Chunk = struct {
pub fn init(allocator: std.mem.Allocator) !@This() {
- return @This() {
+ return @This(){
.code = try array.Array(u8, usize, 8).init(allocator),
.constants = try ValueArray.init(allocator),
.lines = try array.RLEArray(u32, 8).init(allocator),
@@ -24,7 +29,7 @@ pub const Chunk = struct {
pub fn addConstant(self: *@This(), val: value.Value) !u8 {
try self.constants.add(val);
- return self.constants.count - 1;
+ return self.constants.len - 1;
}
pub fn deinit(self: *@This()) void {
diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig
@@ -8,7 +8,7 @@ pub fn disassembleChunk(ch: chunk.Chunk, name: []const u8) !void {
var offset: usize = 0;
- while (offset < ch.code.count) {
+ while (offset < ch.code.len) {
offset = try disassembleInstruction(ch, offset);
}
}
@@ -16,7 +16,7 @@ pub fn disassembleChunk(ch: chunk.Chunk, name: []const u8) !void {
pub fn disassembleInstruction(ch: chunk.Chunk, offset: usize) !usize {
const OP = chunk.OP;
print("{d:0>4} ", .{offset});
- if (offset > 0 and (try ch.lines.get(offset)) == (try ch.lines.get(offset-1))) {
+ if (offset > 0 and (try ch.lines.get(offset)) == (try ch.lines.get(offset - 1))) {
print(" | ", .{});
} else {
print("{d:4} ", .{try ch.lines.get(offset)});
@@ -24,6 +24,11 @@ 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),
else => blk: {
print("Unknown opcode {}\n", .{try ch.code.get(offset)});
@@ -39,7 +44,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});
+ print("{s:<16} {d:4} '", .{ name, constant });
value.printValue(try ch.constants.get(constant));
print("'\n", .{});
return offset + 2;
diff --git a/zlox/src/main.zig b/zlox/src/main.zig
@@ -1,19 +1,33 @@
const std = @import("std");
const chunk = @import("chunk.zig");
const debug = @import("debug.zig");
+const vm = @import("vm.zig");
pub fn main() anyerror!u8 {
var allocator = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(!allocator.deinit());
var ch = try chunk.Chunk.init(allocator.allocator());
defer ch.deinit();
+ var VM = vm.VM.init();
+ defer VM.deinit();
- const constant = try ch.addConstant(1.2);
try ch.write(@enumToInt(chunk.OP.CONSTANT), 123);
- try ch.write(constant, 123);
+ try ch.write(try ch.addConstant(1.2), 123);
+
+ try ch.write(@enumToInt(chunk.OP.CONSTANT), 123);
+ try ch.write(try ch.addConstant(3.4), 123);
+
+ try ch.write(@enumToInt(chunk.OP.ADD), 123);
+
+ try ch.write(@enumToInt(chunk.OP.CONSTANT), 123);
+ try ch.write(try ch.addConstant(5.6), 123);
+
+ try ch.write(@enumToInt(chunk.OP.DIVIDE), 123);
+
+ try ch.write(@enumToInt(chunk.OP.NEGATE), 123);
try ch.write(@enumToInt(chunk.OP.RETURN), 123);
- try debug.disassembleChunk(ch, "test chunk");
+ try VM.interpret(&ch);
return 0;
}
diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig
@@ -0,0 +1,102 @@
+const Chunk = @import("chunk.zig").Chunk;
+const OP = @import("chunk.zig").OP;
+const value = @import("value.zig");
+const std = @import("std");
+const debug = @import("debug.zig");
+const wrp = @import("wrap.zig");
+
+pub const InterpreterError = error{ CompileError, RuntimeError, IndexOutOfBounds, Overflow, DivisionByZero };
+
+pub const VM = struct {
+ ip: [*]const u8,
+ chunk: *const Chunk,
+ stack: stackType,
+ stackTop: [*]value.Value,
+
+ const stackSize = 256;
+ const stackType = [stackSize]value.Value;
+
+ pub fn init() @This() {
+ var ret = @This(){
+ .ip = undefined,
+ .chunk = undefined,
+ .stack = std.mem.zeroes(@This().stackType),
+ .stackTop = undefined,
+ };
+ ret.stackTop = &ret.stack;
+ return ret;
+ }
+
+ pub fn interpret(self: *@This(), chunk: *const Chunk) InterpreterError!void {
+ self.resetStack();
+ self.chunk = chunk;
+ self.ip = chunk.code.data.ptr;
+ try self.run(true);
+ }
+
+ fn resetStack(self: *@This()) void {
+ self.stackTop = &self.stack;
+ }
+
+ fn read_byte(self: *@This()) u8 {
+ const out: u8 = self.ip[0];
+ self.ip += 1;
+ return out;
+ }
+
+ fn read_constant(self: *@This()) value.Value {
+ return self.chunk.constants.data[self.read_byte()];
+ }
+
+ fn push(self: *@This(), val: value.Value) void {
+ self.stackTop[0] = val;
+ self.stackTop += 1;
+ }
+
+ fn pop(self: *@This()) value.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 {
+ const b = self.pop();
+ const a = self.pop();
+ self.push(op(value.Value, a, b));
+ }
+
+ fn run(self: *@This(), comptime dbg: bool) !void {
+ while (true) {
+ if (dbg) {
+ std.debug.print(" ", .{});
+ var stackPtr: [*]value.Value = &self.stack;
+ while (stackPtr != self.stackTop) : (stackPtr += 1) {
+ std.debug.print("[ ", .{});
+ value.printValue(stackPtr[0]);
+ std.debug.print(" ]", .{});
+ }
+ std.debug.print("\n", .{});
+ }
+ _ = try debug.disassembleInstruction(self.chunk.*, @ptrToInt(self.ip) - @ptrToInt(self.chunk.code.data.ptr));
+ const instruction: u8 = self.read_byte();
+ switch (instruction) {
+ @enumToInt(OP.RETURN) => {
+ value.printValue(self.pop());
+ std.debug.print("\n", .{});
+ return;
+ },
+ @enumToInt(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),
+ else => return InterpreterError.CompileError,
+ }
+ }
+ }
+
+ pub fn deinit(_: *@This()) void {}
+};
diff --git a/zlox/src/wrap.zig b/zlox/src/wrap.zig
@@ -0,0 +1,12 @@
+pub fn add(comptime T: type, a: T, b: T) T {
+ return a + b;
+}
+pub fn mul(comptime T: type, a: T, b: T) T {
+ return a * b;
+}
+pub fn sub(comptime T: type, a: T, b: T) T {
+ return a - b;
+}
+pub fn div(comptime T: type, a: T, b: T) T {
+ return a / b;
+}