A comprehensive introduction to writing an interpreter for a dynamically typed, object-oriented toy programming language (Lox). Throughout the book the author develops two complete interpreters for the language from start to finish, including all the front-end and backend parts. The first is a simple AST walking interpreter implemented in Java, and the second is a moderately optimized VM written in C, including a garbage collector.
Overall, this is a great teaching approach and it mirrors the one I took in my Bob project almost exactly [1].
The book follows an interesting plan - all the code for both interpreters is fully included in the text and presented in pieces. You don't need anything additional - no external packages, no external repositories to look at. If you use the same language(s) the author chose, just read along and copy the code into your editor. It will work. In this approach lies the book's strength and also its weakness, as I will explain below.
If this is the first interpreter you ever write, I do recommend following this approach (using the same languages the book uses). Otherwise, you may be tempted to follow along but implement your interpreter in another language. This is much more challenging, though, especially with the second (C-based) interpreter which uses a lot of C idiosyncrasies. I personally reimplemented the complete first interpreter in Go as I went along with the book - this was very easy. For the second one I picked Rust and reimplemented about 2/3rds of clox in it before running out of steam. Since I already wrote Bob in the past I felt like I wasn't learning much new and I wasn't particularly interested in Lox's OOP features anyway.
I'll state the conclusion of my review here, before moving on: this book is really great. Especially if you haven't had too much exposure to writing compilers in the past. Among the many compiler books I've seen/read, this is by far the most practical. It's hard for me to judge how beginner-friendly it is since I have quite a bit of background on this subject, but coupled with a more theoretical compilation book or course, it should be absolutely terrific to get anyone started. The attention to detail is nothing short of amazing - I don't think I found a single mistake in this book, even a typo. The code is well thought out, the explanations are solid and the diagrams are delightful.
Now on to some (minor) criticism: remember how I said that placing all the code in the book is somewhat of a weakness? Well, this is for a few reasons. First, the book is enormous. Long, large, heavy. It's hard to read the physical book in any other way than laid flat on top of a table. Second, because it's so large and packed already, the author left quite a few things out, particularly testing. There's precious little actual Lox code in the book, and no testing strategy [2]. Testing interpreters well is relatively easy and very powerful, especially when implementing multiple ones for the same language. I do understand the space constraints, however.
Also, I fell like the sticking to single-pass compilation needlessly complicates the interpreter, and is unwarranted in this day and age. But doing a two-pass compiler would likely have made the book even longer.
A more subtle issue with writing all the code down in sequence is that the author tries very hard to keep it working at all times without refactoring too much, and this means that early code sometimes has subtle provisions for the future. Especially when re-implementing in another language, these are a constant source of grief - "oh I don't need this field in my language", and 50 pages later "oops, now I understand why it was there". It's full of Chesterton's fences! These are excusable for an old and dusty code base, but not for new code being developed on the fly. So my advice to you, if you're reading the book and implementing along in another language - beware of these! Read a chapter or two ahead before going back and actually implementing.
As I said, these criticisms are minor, and I understand the constraints the author was acting under - making the book even larger would have been difficult. Overall, it's a very good book; it fills an important niche in the compiler literature, and someone just had to write it. I'm happy Bob Nystrom did.
[1] | Bob is a Scheme implementation with a simpl-ish interpreter in Python, as well as a compiler written in Python targeting a VM bytecode; the other implementation is a VM for this bytecode implemented in C++ that includes a garbage collector. |
[2] | The author does have a GitHub repository with extensive testing, but I did not find it particularly easy to hook up to in the process of developing partial versions of the interpreter. It's doable, but it's not as easy as following the rest of the book. |