DzLox

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

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:
Mzlox/build.zig | 53+++++++++++++++++++++++++++++++++++++++++++----------
Mzlox/src/array.zig | 33+++++++++++++++------------------
Mzlox/src/chunk.zig | 9+++++++--
Mzlox/src/debug.zig | 11++++++++---
Mzlox/src/main.zig | 20+++++++++++++++++---
Azlox/src/vm.zig | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Azlox/src/wrap.zig | 12++++++++++++
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; +}