I've been wanting to read this book, but I'm not sure how I should go about it. Are you supposed to read and type out the code examples? I'm just wondering if I will learn anything that way?
The way that worked better for me was doing the book in a different language (I did it in Crystal and Rust). I had to fully understand the intent being every code example so I could translate it correctly.
This adds another layer of difficulty, depending on how comfortable you are with Java and the language of your choice, but it was the way that worked better for me. I truly felt that I created a language, not just copied some code.
I also took this approach, and I think it has really helped me understand what I'm doing as opposed to merely transcribing the code.
In my case, I chose to use C++ (more specifically, C++20 under GCC 11) with minimal external dependencies (currently just Boost for memory mapped IO and fmt). I'm working on the second part of the book now (only just getting started, really). My tree-walking interpreter works, but does have some limitations (I currently leak memory in a few locations, such as closures, due to reference-counting cycles).
I've had a few gotchas following along this way, but it's been entirely due to my choices.
One thing I'd highly recommend doing: setup tests to validate your code immediately! I don't have anything fancy setup, but basically what I have is mechanisms to hand a script to my interpreter and capture stdout/stderr. It's not the best level of testing, for sure, but it does allow me to basically place the lox snippets from the book into a reproducible framework to make sure my interpreter is doing what's expected. I also added tracing/memory dump facilities to my tree-walker to facilitate debugging as well (the byte-code interpreter builds this in from the start, so I've not deviated in that aspect, yet).
I'm really enjoying the book. It's definitely created a spark in me I've not felt in quite a while.
Another vote for doing it like this. I chose to learn Haskell by working through this book and they were a perfect match. One thing that really helped was trying to build things using a few different language constructs and seeing which ones worked well and which ones didn't. Really helped me independently arrive at a lot of concepts as I searched around trying to figure out how to make things work (a big one being an understanding of the kinds of ways you need to structure your code for a typesystem like Haskell vs. Java)
I would also vouch for this approach, it's what I did and it helped force me to understand instead of just copying. Honestly, working through this book was the most fun I've had programming in years.
It also helps to have a practical application that you're working towards - I was in the process of building a game server in Node and wanted a way to modify the state at runtime, so I used this book to build an embedded language for debug builds.
Question for you on Rust for part 2. Did you skip the data structure implementations part, did you use unsafe, or did you do shenanigans like Ids instead of pointers with backing vecs to handle them?
I also used Rust, though I only went up to chapter 21 in Part 2. I completely skipped the basic data structure implementations. I had no interest in implementing basic stuff like vectors and hashmaps, because it's not relevant to the problem.
I did go the ID route instead of pointers, because I didn't like the idea of shotgunning the heap with my AST, so I put the actual statements/expressions in vectors and everything else uses indices inside opaque ID types.
For fun, I did also start on doing compiler for part 2 based on the AST from part 1, though as I say, I didn't get far. I got distracted with a static, stack-based, natively compiled language instead.
Well yes, the code is pretty much there 1:1. However the juice is in the straight forward explanations and excellent writing.
I used the book by translating the code into my favorite programming languages and heavily played around with different approaches, which is way more time consuming but I don't think it is necessary at all.
I think if you are already a seasoned programmer you might find it even useful to just read the book and the code without doing much of coding at all. Given that you are good at understanding code without executing it.
He does a great job of breaking down the code samples into chunks that are small enough to be meaningful. The longest chunk I remember seeing was around 10 lines. Each of those small blocks of code is surrounded by lots of very helpful and approachable explanatory text. The results was, at least for me, that I never felt like I was copying something I didn't understand.
I copy/pasted the code examples, reformatted them so they'd work in a different language (javascript), and refactored it to write my own language instead of Lox.
Then I did it again with a different destination language design, and had to reference the book less.
Whenever I end up writing an interpreter for a third time, I'll probably be able to do it without referencing Crafting Interpreters at all, if I had to. But I expect I'll still check the book before doing certain fiddly parts of the parser.
I read the beginning sections of each chapter until I understand conceptually what the chapter implements by the end. (I needed more assistance for the initial chapters not knowing what the output of scanning or parsing would be, or where to start, but by the later chapters, it becomes pretty clear what the output of implementing say, classes, is).
Then I would try to implement it on my own without reading, having Chapter 3, the language specs, open in a different tab for reference. I chose Java because (1) I wasn't very familiar with it, and (2) I wanted the option to use their code snippets.
My goal is to pass all of the edge test cases the book mentions. So finally I would read the chapter and take some notes on the different design choices and test cases I missed. The reading is quite fast once you've already implemented because you know what to look for.
the approach i took was to read it for understanding, and then go back and see if i could implement what i had just learned, in a different language, without reference to book. (so implementation would use same strategy, but not be exactly the same.)
it worked well for me.
i think this book along w/ spivak's calculus is probably tied for position #1 in technical book that i found most enjoyable and had the biggest influence on me. cannot recommend it enough.
edit: i should mention if i got stuck i went back to reading the book! the point was, "read to understand, try to implement, if understanding not there, repeat". rather than just typing directly out.
I read the chapters and looked at the samples. If there was a place where several followed a similar pattern then I’d only follow the first one from the book and then figure the others out myself (eg different types of statement or expression). There are also some exercises that don’t provide immediate answers which encourage you to think ahead.
This adds another layer of difficulty, depending on how comfortable you are with Java and the language of your choice, but it was the way that worked better for me. I truly felt that I created a language, not just copied some code.