commit ec40277f547e2695d542b6e1134b4e238d81718e
parent f3684e186858adfbf2faa44afe6728ae78234433
Author: Walther Chen <walther.chen@gmail.com>
Date:   Sat, 12 Nov 2022 15:08:35 -0500

opt2

Diffstat:
Mbuild.zig | 1+
Msrc/common.zig | 1+
Asrc/opt2.zig | 291++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 293 insertions(+), 0 deletions(-)

diff --git a/build.zig b/build.zig @@ -19,6 +19,7 @@ pub fn build(b: *std.build.Builder) void { const stages = [_][]const u8{ "og", "opt1", + "opt2", }; const test_step = b.step("test", "Run unit tests"); diff --git a/src/common.zig b/src/common.zig @@ -70,5 +70,6 @@ pub fn testHelloWorld(parse: anytype, interpret: anytype) anyerror!void { defer program.deinit(); try interpret(program, &memory, rdr, wtr, std.testing.allocator); + std.debug.print("{s}", .{list.items}); try expectEqualSlices(u8, "Hello World!\n", list.items); } diff --git a/src/opt2.zig b/src/opt2.zig @@ -0,0 +1,291 @@ +// opt1: use instructions + +const std = @import("std"); +const common = @import("common.zig"); +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; +const JumpTable = ArrayList(usize); + +// comptime known, from build option -Dtrace +const TRACE = @import("build_with_trace").TRACE; + +pub fn main() anyerror!void { + if (TRACE) { + std.debug.print("Building with TRACE enabled\n", .{}); + } + + try common.runInterpreter(parse, interpret); +} + +test "og: interpret hello world" { + try common.testHelloWorld(parse, interpret); +} + +const Program = struct { + alloc: Allocator, + ops: []const Op, + + pub fn deinit(self: Program) void { + self.alloc.free(self.ops); + } +}; + +const Op = struct { + tag: Tag = .invalid, + value: usize = 0, + + const Tag = enum { + invalid, + inc_ptr, + dec_ptr, + inc_data, + dec_data, + read_stdin, + write_stdout, + jump_if_data_zero, + jump_if_data_not_zero, + + fn is_jump(self: Tag) bool { + return switch (self) { + .jump_if_data_zero, .jump_if_data_not_zero => true, + else => false, + }; + } + + pub fn format(value: Tag, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + + switch (value) { + .invalid => try writer.writeAll("INVALID"), + .inc_ptr => try writer.writeByte('>'), + .dec_ptr => try writer.writeByte('<'), + .inc_data => try writer.writeByte('+'), + .dec_data => try writer.writeByte('-'), + .read_stdin => try writer.writeByte(','), + .write_stdout => try writer.writeByte('.'), + .jump_if_data_zero => try writer.writeByte('['), + .jump_if_data_not_zero => try writer.writeByte(']'), + } + } + }; +}; + +fn ops_debug(ops: []const Op) void { + std.debug.print("\n", .{}); + for (ops) |item, idx| { + std.debug.print("{d:0>2}: {any} {d}\n", .{ idx, item.tag, item.value }); + } +} + +fn parse(src: []const u8, alloc: Allocator) !Program { + var ops = ArrayList(Op).init(alloc); + var jump_stack = ArrayList(usize).init(alloc); + var last_nonjump_tag: Op.Tag = .invalid; + var last_nonjump_tag_repeat: u8 = 0; + defer { + ops.deinit(); + jump_stack.deinit(); + } + + for (src) |c| { + switch (c) { + '[', ']' => { + // jump Ops use a stack to hold state on opening/closing brackets + + // First push the staged Op to `ops` + if (last_nonjump_tag != .invalid) { + try ops.append(Op{ + .tag = last_nonjump_tag, + .value = last_nonjump_tag_repeat, + }); + last_nonjump_tag = .invalid; + last_nonjump_tag_repeat = 0; + } + + // Then handle jump-specific logic, including writing the jump Op to `ops` and + // pushing/popping stack + switch (c) { + '[' => { + // 0 will be rewritten once matching bracket found + try ops.append(Op{ .tag = .jump_if_data_zero, .value = 0 }); + // push the location of the LBracket + try jump_stack.append(ops.items.len - 1); + }, + ']' => { + if (jump_stack.popOrNull()) |l_bracket_pc| { + try ops.append(Op{ .tag = .jump_if_data_not_zero, .value = l_bracket_pc }); + ops.items[l_bracket_pc].value = ops.items.len - 1; + } else { + return error.UnmatchedBracket; + } + }, + else => unreachable, + } + }, + else => { + // all non-jump Ops can be repeated + + const tag = switch (c) { + '>' => Op.Tag.inc_ptr, + '<' => Op.Tag.dec_ptr, + '+' => Op.Tag.inc_data, + '-' => Op.Tag.dec_data, + ',' => Op.Tag.read_stdin, + '.' => Op.Tag.write_stdout, + else => { + // Ignore comments + continue; + }, + }; + + if (last_nonjump_tag == .invalid) { + last_nonjump_tag = tag; + last_nonjump_tag_repeat = 1; + } else if (tag == last_nonjump_tag) { + last_nonjump_tag_repeat += 1; + } else { + try ops.append(Op{ + .tag = last_nonjump_tag, + .value = last_nonjump_tag_repeat, + }); + last_nonjump_tag = tag; + last_nonjump_tag_repeat = 1; + } + }, + } + std.log.debug("\n{any}: {d}\n", .{ last_nonjump_tag, last_nonjump_tag_repeat }); + std.log.debug("{any}\n", .{ops.items}); + } + + if (last_nonjump_tag != .invalid) { + try ops.append(Op{ + .tag = last_nonjump_tag, + .value = last_nonjump_tag_repeat, + }); + } + + ops_debug(ops.items); + + return .{ .ops = ops.toOwnedSlice(), .alloc = alloc }; +} + +test "opt2: parse basic 0" { + // checks both repeated and jump ops. + // - jump op ag beginning + // - nested jump ops + // - repeated run + // should also test w/out jump op at beginning. + + const src = "[>>[>]>]>"; + const program = try parse(src, std.testing.allocator); + defer program.deinit(); + try std.testing.expectEqualSlices( + Op, + &.{ Op{ + .tag = .jump_if_data_zero, + .value = 6, + }, Op{ + .tag = .inc_ptr, + .value = 2, + }, Op{ + .tag = .jump_if_data_zero, + .value = 4, + }, Op{ + .tag = .inc_ptr, + .value = 1, + }, Op{ + .tag = .jump_if_data_not_zero, + .value = 2, + }, Op{ + .tag = .inc_ptr, + .value = 1, + }, Op{ + .tag = .jump_if_data_not_zero, + .value = 0, + }, Op{ + .tag = .inc_ptr, + .value = 1, + } }, + program.ops, + ); +} + +test "opt2: parse basic 1" { + // checking for missing `-` + // Also for `[` not at beginning, and `]` at end + + const src = ">[<<--]"; + const program = try parse(src, std.testing.allocator); + defer program.deinit(); + try std.testing.expectEqualSlices( + Op, + &.{ Op{ + .tag = .inc_ptr, + .value = 1, + }, Op{ + .tag = .jump_if_data_zero, + .value = 3, + }, Op{ + .tag = .dec_data, + .value = 2, + }, Op{ + .tag = .jump_if_data_not_zero, + .value = 1, + } }, + program.ops, + ); +} + +fn interpret(program: Program, memory: []u8, rdr: anytype, wtr: anytype, alloc: Allocator) !void { + comptime var instruction_count = if (TRACE) std.AutoHashMap(Op.OpKind, usize).init(alloc) else undefined; + if (TRACE) { + defer instruction_count.deinit(); + } + + const ops = program.ops; + var pc: usize = 0; + var dataptr: usize = 0; + + while (pc < ops.len) { + const op = ops[pc]; + + if (TRACE) { + var entry = try instruction_count.getOrPut(op); + if (entry.found_existing) { + entry.value_ptr.* += 1; + } else { + entry.value_ptr.* = 1; + } + } + + switch (op.tag) { + .inc_ptr => dataptr += op.value, + .dec_ptr => dataptr -= op.value, + .inc_data => memory[dataptr] += @intCast(u8, op.value), + .dec_data => memory[dataptr] -= @intCast(u8, op.value), + .read_stdin => memory[dataptr] = try rdr.readByte(), + .write_stdout => try wtr.writeByte(memory[dataptr]), + // jumps to next matching ']' if curr_data == 0 + .jump_if_data_zero => if (memory[dataptr] == 0) { + pc = op.value; + }, + // jumps to previous matching ']' if curr data != 0 + .jump_if_data_not_zero => if (memory[dataptr] != 0) { + pc = op.value; + }, + else => { + return error.unreachableChar; + }, + } + + pc += 1; + } + + if (TRACE) { + var kv = instruction_count.iterator(); + while (kv.next()) |entry| { + std.debug.print("{c}: {d}\n", .{ entry.key_ptr.*, entry.value_ptr.* }); + } + } +}