Front end
A compiler is a program that turns source code into instructions a machine can run. Ours handles a tiny language: whole numbers, names, + and *. Line 1 says x is an int (a whole number) holding 4. Follow line 2 through every stage.
The lexer reads the line one character at a time and groups the characters into tokens, the words of the language. x + 2 * 3 becomes five tokens: a name, a plus, a number, a star and a number. The spaces are dropped.
The parser has one function per rule: expr handles +, term handles *, factor reads a number or a name. expr calls term for x, sees +, then calls term again, which builds 2 * 3 as one branch. Then + joins x to it.
Parsing gives an abstract syntax tree (AST). Abstract means the spelling is gone: no spaces, and x + (2 * 3) with brackets gives the same tree. The shape keeps the order: 2 * 3 sits below +, so it is worked out first.
The type checker walks the tree from the leaves up. 2 and 3 are ints, and line 1 declared x an int. int * int gives an int, and so does int + int. A mismatch, like text plus a number, stops the compiler here.
Next the tree is flattened into an intermediate representation (IR): simple steps with at most one operator each, called three-address code. t1 = 2 * 3 does the low branch, then t2 = x + t1. t1 and t2 are temporary names the compiler invents.
Back end
The optimiser makes the IR faster without changing its answer. 2 * 3 has no unknowns, so it is worked out now, at compile time: t1 = 6. That is constant folding. Then the 6 is copied into the next line, and t1 is dropped.
The code generator writes instructions for a stack machine, which keeps its working values on a stack: a pile where the last value in is the first out. t2 = x + 6 becomes three instructions: LOAD x, PUSH 6, ADD.
Now the code runs. LOAD x copies x's value, 4, onto the stack. PUSH 6 puts 6 on top. ADD takes the top two values off, adds them and pushes 10. Without folding, it would also have needed PUSH 2, PUSH 3 and MUL.
Front and back
The front end (lexer, parser, type checker) knows the language and hands IR to the back end. The back end (optimiser, code generator) knows the machine. In LLVM, C and Rust front ends write the same IR; back ends make x86-64 or Arm code.
What happens
;; step 1/10 · source source code plain text 1 x: int = 4 2 x + 2 * 3 the stages, in order: lexer text -> tokens parser tokens -> syntax tree type checker tree -> tree with types IR tree -> three-address code optimiser IR -> faster IR code gen IR -> stack-machine code
Constant folding, constant propagation (copying the 6 into the next line) and dead-code removal (dropping t1) are three separate optimisations; the figure shows them together. CPython folds this expression too: dis shows the constant 6 and no multiply. Many compilers, LLVM among them, count the optimiser as a third, middle phase between the front end and the back end. Real back ends also choose registers and machine instructions instead of a stack. The IR is the hand-off between the two ends: the front end writes it and the back end reads it, so the figure sets the IR card at the start of the back-end plate, where the front end passes it over.