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.