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 }