The VM
vm-exec-line

Write vm_exec_line(line) -- execute a single instruction. Start with arithmetic: i64.const N, i64.add, i64.sub, i64.mul, i64.div_s, and comparisons.

Returns true normally, false if we hit something that needs special handling (we'll use this later for control flow).

fn vm_exec_line(raw: []const u8) void {
    const line: []const u8 = vm_trim(raw);

    if (starts_with(line, "i64.const ")) {
        vm_push(parse_int(line, 10));
        return;
    }
    if (streq(line, "i64.add")) { const b: i64 = vm_pop(); const a: i64 = vm_pop(); vm_push(a + b); return; }
    if (streq(line, "i64.sub")) { const b: i64 = vm_pop(); const a: i64 = vm_pop(); vm_push(a - b); return; }
    if (streq(line, "i64.mul")) { const b: i64 = vm_pop(); const a: i64 = vm_pop(); vm_push(a * b); return; }
    if (streq(line, "i64.div_s")) { const b: i64 = vm_pop(); const a: i64 = vm_pop(); vm_push(@divTrunc(a, b)); return; }
    if (streq(line, "i64.gt_s")) { const b: i64 = vm_pop(); const a: i64 = vm_pop(); vm_push(if (a > b) @as(i64, 1) else 0); return; }
    if (streq(line, "i64.lt_s")) { const b: i64 = vm_pop(); const a: i64 = vm_pop(); vm_push(if (a < b) @as(i64, 1) else 0); return; }
    if (streq(line, "i64.eq")) { const b: i64 = vm_pop(); const a: i64 = vm_pop(); vm_push(if (a == b) @as(i64, 1) else 0); return; }
    if (streq(line, "i64.ne")) { const b: i64 = vm_pop(); const a: i64 = vm_pop(); vm_push(if (a != b) @as(i64, 1) else 0); return; }
    if (streq(line, "i64.eqz")) { const a: i64 = vm_pop(); vm_push(if (a == 0) @as(i64, 1) else 0); return; }

    if (starts_with(line, "local.get $")) {
        vm_push(vm_get_local(parse_name(line, 0)));
        return;
    }
    if (starts_with(line, "local.set $")) {
        vm_set_local(parse_name(line, 0), vm_pop());
        return;
    }
    if (starts_with(line, "(local $")) {
        // local declaration -- register the name, value starts at 0
        vm_set_local(parse_name(line, 0), 0);
        return;
    }

    // Lines we skip: (func, (param, (result, ), (export, etc.
    // Control flow (if, else, end, block, loop, br, call, return) handled by vm_run
}

Test it by feeding a few lines manually:

    vm_sp = 0;
    vm_exec_line("  i64.const 3");
    vm_exec_line("  i64.const 5");
    vm_exec_line("  i64.add");
    print("vm: {d}\n", .{vm_pop()});   // 8

That's the VM running 3 + 5 = 8 from WAT instructions. Same answer as the interpreter. We're getting somewhere.