Most people meet compiler construction as a stack of concepts: lexical analysis, grammars, symbol tables, intermediate representations, register allocation. Each concept is explained, and then the course ends, and you have never seen a whole compiler work.

This series takes the other road. It starts from a complete, working compiler that is small enough to read in an afternoon, and takes it apart a piece at a time. Every chapter points at real source code you can open, change, and run. By the end you will have compiled a program written in a small language down to native machine code, linked it, and executed it — and you will know what each part of that pipeline did.

The compiler is PL/0, published by Niklaus Wirth in 1975. It is the smallest complete compiler anyone has written down, and it has taught compiler construction for fifty years. What is new here is what happens after the parse: instead of stopping at an interpreter, this PL/0 hands its program to Dragon — the backend that compiles the Mica language — and gets real optimized machine code back.

Two compilers, one lesson

PL/0 appears here twice: once in C23 and once in Mica, seven source files each, in the same order, doing the same things. That is deliberate.

Reading one of them teaches you how a compiler works. Reading both teaches you something you cannot learn from one: which parts of a compiler are essential and which are your language’s opinion. The scanner is the same idea in both. The seam between the parser and the code emitter is not — C spells an interface as a table of function pointers, and Mica, which has no function pointers at all, spells it as a dispatch. Same seam, two honest shapes.

Pick whichever language you are comfortable in. The chapters show both, side by side, and never assume you know the other.

The chapters

#ChapterWhat you get out of it
1Why PL/0what a compiler actually is, the whole PL/0 language on one page, and why small is the right teacher
2Get it runningboth compilers built and running on your machine, from nothing, including the backend dependency
3The shape of a compilerthe seven parts, why they are ordered the way they are, and what “front end” and “back end” really mean
4The scannerturning characters into words — and why a compiler tracks positions from the very first line
5The symbol tablehow a compiler answers “what does this name mean here?” — scope, nesting, and shadowing in forty lines
6The parserrecursive descent, where operator precedence actually lives, and how to report five errors instead of fifty
7The seamone parse driving two different back ends, and what an interface costs in two languages
8The oracleWirth’s stack machine — eight instructions, activation records, and the static link that makes nesting work
9The Dragon roademitting a real intermediate language, and getting optimized native code back
10Proving ithow the two compilers certify each other, and how anyone tests a compiler honestly
11Exercisesextend the language yourself — with the hard parts marked
AAppendix: Wirth’s original PL/0the 1975 compiler in Pascal, complete, with a guide to what to look at

What you need

A Linux machine on x86-64 or ARM64 — or Docker, which gives you one. Chapter 2 walks the whole setup, and nothing in this series needs a machine you do not already have.

You should be able to read C or Pascal-family code. You do not need prior compiler knowledge, and you do not need to know Mica: the language is introduced as it appears, and every construct that matters is explained where it is used.

A note on the source

The two compilers are not written for this article. They are the Mica project’s own worked example for its backend SDK, they are compiled and run on every change to the compiler, and their outputs are checked byte for byte against each other. When this series quotes a line, that line is the line that runs.

You can read the sources here: sdk/pl0/c/ and sdk/pl0/mica/.

Found a gap, or something that did not work as written? We would like to know: info@mica-dev.com.