The Compiler
output-buffer

Write the output buffer. We need somewhere to put the WAT instructions. A big byte array and a position, just like source and pos but for output.

var out: [1 << 20]u8 = [_]u8{0} ** (1 << 20);
var out_len: usize = 0;

Write emit_byte(b), emit_str(s), and emit_num(n) (which prints a number as decimal digits into the buffer):

fn emit_byte(b: u8) void {
    if (out_len < out.len) {
        out[out_len] = b;
        out_len += 1;
    }
}

fn emit_str(s: []const u8) void {
    for (s) |c| {
        emit_byte(c);
    }
}

fn emit_num(n: i64) void {
    if (n < 0) {
        emit_byte('-');
        emit_num(-n);
        return;
    }
    if (n >= 10) {
        emit_num(@divTrunc(n, 10));
    }
    emit_byte(@intCast(@as(u8, @intCast(@rem(n, 10))) + '0'));
}

Test it:

    out_len = 0;
    emit_str("hello ");
    emit_num(42);
    emit_byte('\n');
    print("{s}", .{out[0..out_len]});   // hello 42

This is the compiler's pen and paper. Everything it produces goes through these three functions.

Self-hosting note: when we write the compiler in its own language, these exact functions will exist there too. emit_byte, emit_s, emit_num. Simple enough to express in the language they compile.