---
title: "Compilers overview"
description: "Watch a lexer cut source into tokens, a parser build precedence into an AST, a compiler emit stack bytecode, and a virtual machine run it — including the exact errors at each boundary."
source: https://vizlearn.app/cs/compilers
---

# 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 writes `MUL` first; the VM believes the listing.
- A token can be valid while its position is not. `STAR` is a real token in `12 + * 4`, just not one that can begin `primary()`.
- 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 first` is 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.
