Compiler Visualizations
A compiler does not copy your source string. It classifies character spans, gives the resulting tokens a hierarchy, walks that tree, and writes a program a machine can run. These three lessons follow that chain: source to AST, AST to bytecode, then bytecode through a stack VM. Play them in that order; each default hands the same 1 + 2 * 3 idea to the next stage, with 12 + 3 * 4 used first to make multi-digit lexing impossible to miss.
The machine at the end of this pipeline is not a CPU and it is not a hypervisor. It is a language VM: a loop over compact opcodes, fetch then tiny stack surgery. A + in the source is tree shape. ADD is two pops and a push. Mixing those two up is how you emit the operator first and watch the VM underflow.
The boundaries matter as much as the happy path. 12 + @ 4 never becomes a complete token tape. 12 + * 4 does, then fails in primary(). ADD before its operands gets farther still: it is valid bytecode, but the VM underflows. Three different failures, three different owners.
Compare the lessons
| Lesson | Main question | What changes | Where it breaks |
|---|---|---|---|
| Lexer & Parser | Where does precedence come from? | character spans become flat tokens; grammar calls build hierarchy | @ stops lexing; * in operand position stops parsing |
| AST → Code | Why does 1 + 2 * 3 write MUL before ADD? | post-order walk; Fold rewrites literals first | a name blocks the parent, not the cousins |
| Virtual Machine | Why must that listing be in that order? | each opcode is a stack surgery; a hot JUMP compiles | operator-first bytecode underflows; a cold loop never JITs |
The parser's AST and codegen's input tree share the same operator hierarchy on purpose. Codegen's right-hand panel is titled IR because it is the program being written. The VM's right-hand tape is titled Stack because it is the runtime plates that program uses. If you catch yourself reading one as the other, that is the misconception the latter two docs name.
A recommended order
Start with Lexer & Parser on 12 + 3 * 4. Watch 1 and 2 stay inside one lexeme, then ignore the source row and follow the parser calls: expr holds + while term finishes *. Load Parentheses next. The parser code is unchanged, but Add becomes the inner node and Mul becomes the root. Finish with one error from each stage so "tokenized" never becomes shorthand for "valid."
Then open AST → Code. Default is Naive on 1 + 2 * 3. Watch MUL land while + is still waiting. Then flip to Fold on the same tree: five instructions become LOAD_CONST 7. Load the x + 2 * 3 scenario before you leave — * still folds, + cannot — so you do not walk away thinking constant folding is magic.
Then Virtual Machine, which runs the listing you just wrote. Default is Interpreter on that same 1 + 2 * 3: three pushes, a MUL that makes 2 * 3 → 6, an ADD that consumes 1 + 6. The plates are why codegen used post-order. Load ADD first immediately afterwards. It is the counterfactual the first lesson's quiz already named, and here it actually raises Underflow.
The JIT tab is optional and later. A countdown interprets two trips, compiles, and the listing goes quiet. The cold-loop scenario is the breaking point: hotness stalls below the threshold and nothing compiles. Do not start there. The stack-discipline claim has to land first.
What to watch
- Keep source order, token order, tree hierarchy, and instruction order apart. The string and tape say
+first; the tree nests*; the listing writesMULfirst; the VM believes the listing. - A token can be valid while its position is not.
STARis a real token in12 + * 4, just not one that can beginprimary(). - Read stack height, not just the top value. Peak height 3 on the default is the three leaves waiting for the first operator.
- An empty stack is not a crash until an operator asks for operands. Init is empty on purpose.
ADD firstis empty by mistake. - After JIT compile, the remaining arithmetic still happens. One native beat is "no more fetches," not O(1) work and not "skip to the answer."
All compiler visualizations
Pick a lesson below, change the expression or the engine, and step through the trace beside its highlighted Python code. Lexer beats intentionally leave that code dark: the panel owns the parser proof, while the character-to-token evidence stays on the canvas.