Walk through the WAT for 2.0 + 3.0 * 4.0:
| Instruction | Stack (as f64) |
|-------------|----------------|
| f64.const 2.0 | [2.0] |
| f64.const 3.0 | [2.0, 3.0] |
| f64.const 4.0 | [2.0, 3.0, 4.0] |
| f64.mul | [2.0, 12.0] |
| f64.add | [14.0] |
Identical structure to the i64 trace from Problem 44. Precedence works the same way -- c_term grabs the 3.0 * 4.0 first. The only difference is the instruction prefix: f64 instead of i64. Same parser, same recursion, same stack machine. Two types, one architecture.
What we added: f64 as a second type. Float literals with decimal points. Float arithmetic, comparisons, and conversion. Type tracking in the compiler to emit the right WAT instructions. The VM handling floats via bitcast.
What we learned: adding a second type touches everything. The parser (literal detection), the interpreter (dispatch on type), the compiler (instruction selection, local/global declarations), the VM (new instruction handlers), and the test harness (float comparison). One type was simple. Two types is real.
But the architecture didn't change. The parser is still recursive descent. The compiler is still syntax-directed. The VM is still a line-by-line stack machine. Types added a dimension to every decision, but they didn't change the decisions. f64.add is just i64.add wearing different shoes -- the same metaphor we started with.
## Summary
| Part | What it adds | Problems |
|------|-------------|----------|
| 1 | Parse and eval "3+5" | 1–5 |
| 2 | Cursor, chains, multi-digit, whitespace, precedence | 6–14 |
| 3 | Typed variables, streq, assignment | 15–19 |
| 4 | If/else (mandatory braces), while | 20–23 |
| 5 | Typed functions, return, fib(10) | 24–29 |
| 6 | Compiler: parse -> WAT (syntax-directed, stack-clean) | 30–47 |
| 7 | VM: execute WAT, agreement testing | 48–64 |
| 8 | Memory (load8/store8/load64/store64), strings | 65–79 |
| 9 | Global variables | 80–93 |
| 10 | Completing the language (Zig subset) | 94–111 |
| 11 | The compiler in its own language | 112–126 |
| 12 | The bootstrap, the diff, the proof | 127–131 |
| A | A simple allocator | A1–A9 |
| B | Floating point (f64) | B1–B11 |
You started by making "3+5" give 8.
You ended with a compiler that compiles itself.
The proof is an empty diff -- two engines, same output, on every input, including the compiler's own source code.
Not bad for one file.