A Practical Path From AST Walker to Bytecode VM
Crafting Interpreters takes a single, compelling idea—write two complete interpreters for one language, end to end—and executes it with unusual rigor. The target language, Lox, is a small dynamically typed, object-oriented language, but the book makes no cuts on the pipeline. You build a tree-walking interpreter in Java and then a bytecode VM in C, complete with a garbage collector.
The pedagogical payoff is that the text itself contains the entire codebase for both interpreters. No external packages, no companion repository, no setup steps beyond an editor. Read along and type the code in, and it runs. That makes the book feel like a workshop rather than a treatise, and it is far and away the most practical compiler text I have encountered. I did not find a single error, even a typo; the explanations, diagrams, and code are all carefully thought through.
The approach is not without costs. The most obvious is bulk: the physical book is enormous, and reading it comfortably means leaving it flat on a table. Less obvious but more substantive is what the author had to leave out to keep that bulk in check. There is very little actual Lox code in the book and no real testing strategy, which is a shame because writing two interpreters for the same language is exactly the scenario where a shared conformance test suite pays off handsomely. Space constraints are understandable, but the omission is felt.
A more subtle criticism concerns the single-pass compiler design. Sticking to one pass forces some needless complexity into the interpreter's architecture. A two-pass approach would have been cleaner, though it would have made an already large book even longer.
The book's sequential code presentation also has a hidden trap for anyone who implements along in a different language. Because the author wants every chapter's code to keep working, the early code sometimes carries provisions that only make sense pages or chapters later—fields whose purpose you cannot see yet, decisions that look superfluous until they click into place. The author calls these out in the text, but they are easy to miss when you are actively porting the code to another language and are tempted to "simplify" ahead of time. Reading a couple of chapters ahead before writing any code is the safest way through.
On balance, these are minor complaints against a genuinely valuable book. It fills a real gap in the compiler literature: the practical middle ground between theory and production engineering. For someone with a solid grounding in the theory of compilers, but who has not yet had the experience of bringing a language to life, it should be an outstanding companion.
Notice the design decisions that look arbitrary now—they are likely fences with reasons behind them that will become clear only later.



