opt2.zig (9252B) - raw
1 // opt1: use instructions 2 3 const std = @import("std"); 4 const common = @import("common.zig"); 5 const Allocator = std.mem.Allocator; 6 const ArrayList = std.ArrayList; 7 const JumpTable = ArrayList(usize); 8 9 // comptime known build option, -Dtrace and -Ddebug-opts 10 const build_options = @import("build_options"); 11 const TRACE = build_options.TRACE; 12 const DEBUG_OPS = build_options.DEBUG_OPS; 13 14 pub fn main() anyerror!void { 15 if (TRACE) { 16 std.debug.print("Building with TRACE enabled\n", .{}); 17 } 18 19 try common.runInterpreter(parse, interpret); 20 } 21 22 test "og: interpret hello world" { 23 try common.testHelloWorld(parse, interpret); 24 } 25 26 const Program = struct { 27 alloc: Allocator, 28 ops: []const Op, 29 30 pub fn deinit(self: Program) void { 31 self.alloc.free(self.ops); 32 } 33 }; 34 35 const Op = struct { 36 tag: Tag = .invalid, 37 value: usize = 0, 38 39 const Tag = enum { 40 invalid, 41 inc_ptr, 42 dec_ptr, 43 inc_data, 44 dec_data, 45 read_stdin, 46 write_stdout, 47 jump_if_data_zero, 48 jump_if_data_not_zero, 49 50 fn is_jump(self: Tag) bool { 51 return switch (self) { 52 .jump_if_data_zero, .jump_if_data_not_zero => true, 53 else => false, 54 }; 55 } 56 57 pub fn format(value: Tag, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 58 _ = fmt; 59 _ = options; 60 61 switch (value) { 62 .invalid => try writer.writeAll("INVALID"), 63 .inc_ptr => try writer.writeByte('>'), 64 .dec_ptr => try writer.writeByte('<'), 65 .inc_data => try writer.writeByte('+'), 66 .dec_data => try writer.writeByte('-'), 67 .read_stdin => try writer.writeByte(','), 68 .write_stdout => try writer.writeByte('.'), 69 .jump_if_data_zero => try writer.writeByte('['), 70 .jump_if_data_not_zero => try writer.writeByte(']'), 71 } 72 } 73 }; 74 }; 75 76 fn opsDebug(ops: []const Op) void { 77 std.debug.print("\n# Ops\n", .{}); 78 for (ops, 0..) |item, idx| { 79 std.debug.print("{d:0>2}: {any} {d}\n", .{ idx, item.tag, item.value }); 80 } 81 } 82 83 fn parse(src: []const u8, alloc: Allocator) !Program { 84 var ops = ArrayList(Op).init(alloc); 85 var jump_stack = ArrayList(usize).init(alloc); 86 var last_nonjump_tag: Op.Tag = .invalid; 87 var last_nonjump_tag_repeat: u8 = 0; 88 defer { 89 ops.deinit(); 90 jump_stack.deinit(); 91 } 92 93 for (src) |c| { 94 switch (c) { 95 '[', ']' => { 96 // jump Ops use a stack to hold state on opening/closing brackets 97 98 // First push the staged Op to `ops` 99 if (last_nonjump_tag != .invalid) { 100 try ops.append(Op{ 101 .tag = last_nonjump_tag, 102 .value = last_nonjump_tag_repeat, 103 }); 104 last_nonjump_tag = .invalid; 105 last_nonjump_tag_repeat = 0; 106 } 107 108 // Then handle jump-specific logic, including writing the jump Op to `ops` and 109 // pushing/popping stack 110 switch (c) { 111 '[' => { 112 // 0 will be rewritten once matching bracket found 113 try ops.append(Op{ .tag = .jump_if_data_zero, .value = 0 }); 114 // push the location of the LBracket 115 try jump_stack.append(ops.items.len - 1); 116 }, 117 ']' => { 118 if (jump_stack.popOrNull()) |l_bracket_pc| { 119 try ops.append(Op{ .tag = .jump_if_data_not_zero, .value = l_bracket_pc }); 120 ops.items[l_bracket_pc].value = ops.items.len - 1; 121 } else { 122 return error.UnmatchedBracket; 123 } 124 }, 125 else => unreachable, 126 } 127 }, 128 else => { 129 // all non-jump Ops can be repeated 130 131 const tag = switch (c) { 132 '>' => Op.Tag.inc_ptr, 133 '<' => Op.Tag.dec_ptr, 134 '+' => Op.Tag.inc_data, 135 '-' => Op.Tag.dec_data, 136 ',' => Op.Tag.read_stdin, 137 '.' => Op.Tag.write_stdout, 138 else => { 139 // Ignore comments 140 continue; 141 }, 142 }; 143 144 if (last_nonjump_tag == .invalid) { 145 last_nonjump_tag = tag; 146 last_nonjump_tag_repeat = 1; 147 } else if (tag == last_nonjump_tag) { 148 last_nonjump_tag_repeat += 1; 149 } else { 150 try ops.append(Op{ 151 .tag = last_nonjump_tag, 152 .value = last_nonjump_tag_repeat, 153 }); 154 last_nonjump_tag = tag; 155 last_nonjump_tag_repeat = 1; 156 } 157 }, 158 } 159 std.log.debug("\n{any}: {d}\n", .{ last_nonjump_tag, last_nonjump_tag_repeat }); 160 std.log.debug("{any}\n", .{ops.items}); 161 } 162 163 if (last_nonjump_tag != .invalid) { 164 try ops.append(Op{ 165 .tag = last_nonjump_tag, 166 .value = last_nonjump_tag_repeat, 167 }); 168 } 169 170 if (DEBUG_OPS) { 171 opsDebug(ops.items); 172 } 173 174 return .{ .ops = try ops.toOwnedSlice(), .alloc = alloc }; 175 } 176 177 test "opt2: parse basic 0" { 178 // checks both repeated and jump ops. 179 // - jump op ag beginning 180 // - nested jump ops 181 // - repeated run 182 // should also test w/out jump op at beginning. 183 184 const src = "[>>[>]>]>"; 185 const program = try parse(src, std.testing.allocator); 186 defer program.deinit(); 187 try std.testing.expectEqualSlices( 188 Op, 189 &.{ Op{ 190 .tag = .jump_if_data_zero, 191 .value = 6, 192 }, Op{ 193 .tag = .inc_ptr, 194 .value = 2, 195 }, Op{ 196 .tag = .jump_if_data_zero, 197 .value = 4, 198 }, Op{ 199 .tag = .inc_ptr, 200 .value = 1, 201 }, Op{ 202 .tag = .jump_if_data_not_zero, 203 .value = 2, 204 }, Op{ 205 .tag = .inc_ptr, 206 .value = 1, 207 }, Op{ 208 .tag = .jump_if_data_not_zero, 209 .value = 0, 210 }, Op{ 211 .tag = .inc_ptr, 212 .value = 1, 213 } }, 214 program.ops, 215 ); 216 } 217 218 test "opt2: parse basic 1" { 219 // checking for missing `-` 220 // Also for `[` not at beginning, and `]` at end 221 222 const src = ">[--]"; 223 const program = try parse(src, std.testing.allocator); 224 defer program.deinit(); 225 try std.testing.expectEqualSlices( 226 Op, 227 &.{ Op{ 228 .tag = .inc_ptr, 229 .value = 1, 230 }, Op{ 231 .tag = .jump_if_data_zero, 232 .value = 3, 233 }, Op{ 234 .tag = .dec_data, 235 .value = 2, 236 }, Op{ 237 .tag = .jump_if_data_not_zero, 238 .value = 1, 239 } }, 240 program.ops, 241 ); 242 } 243 244 fn interpret(program: Program, memory: []u8, rdr: anytype, wtr: anytype, alloc: Allocator) !void { 245 var instruction_count = if (TRACE) std.AutoHashMap(Op.Tag, usize).init(alloc); 246 if (TRACE) { 247 defer instruction_count.deinit(); 248 } 249 250 const ops = program.ops; 251 var pc: usize = 0; 252 var dataptr: usize = 0; 253 254 while (pc < ops.len) { 255 const op = ops[pc]; 256 257 if (TRACE) { 258 const entry = try instruction_count.getOrPut(op.tag); 259 if (entry.found_existing) { 260 entry.value_ptr.* += 1; 261 } else { 262 entry.value_ptr.* = 1; 263 } 264 } 265 266 switch (op.tag) { 267 .inc_ptr => dataptr += op.value, 268 .dec_ptr => dataptr -= op.value, 269 .inc_data => memory[dataptr] += @intCast(op.value), 270 .dec_data => memory[dataptr] -= @intCast(op.value), 271 .read_stdin => { 272 var i: usize = 0; 273 while (i < op.value) : (i += 1) { 274 memory[dataptr] = try rdr.readByte(); 275 } 276 }, 277 .write_stdout => { 278 var i: usize = 0; 279 while (i < op.value) : (i += 1) { 280 try wtr.writeByte(memory[dataptr]); 281 } 282 }, 283 // jumps to next matching ']' if curr_data == 0 284 .jump_if_data_zero => if (memory[dataptr] == 0) { 285 pc = op.value; 286 }, 287 // jumps to previous matching ']' if curr data != 0 288 .jump_if_data_not_zero => if (memory[dataptr] != 0) { 289 pc = op.value; 290 }, 291 else => { 292 return error.unreachableChar; 293 }, 294 } 295 296 pc += 1; 297 } 298 299 if (TRACE) { 300 var kv = instruction_count.iterator(); 301 std.debug.print("\n# Ops Count\n", .{}); 302 while (kv.next()) |entry| { 303 std.debug.print("{c}: {d}\n", .{ entry.key_ptr.*, entry.value_ptr.* }); 304 } 305 } 306 }