Write an interpreter
What you end up with: a small scripting language that runs real programs — variables, functions, closures, control flow, arrays — with error messages that point at the line that broke.

Writing an interpreter gives you a vocabulary that transfers everywhere: parsing is how you read config files and query languages, ASTs are how you transform code, and the evaluator is where Rust’s enums and pattern matching are at their most beautiful.
What you’ll build
Section titled “What you’ll build”- A scanner and a recursive-descent parser with proper error recovery
- An AST as Rust enums, with a printer for debugging
- A tree-walking evaluator with environments and scoping
- Functions, closures, recursion and a call stack with a depth limit
- Arrays, strings and a small standard library
- Error reporting with line numbers and a caret pointing at the offending token
- A REPL, plus an ahead-of-time resolver pass for variable lookup
Articles
Section titled “Articles”- What we’re building: a small scripting languagethe grammar, and why we chose it
- Scanning source into tokensa lexer with positions
- A Pratt parser for expressionsprecedence without a pile of rules
- Representing a program as a treean enum per node, and Box where needed
- Evaluating expressionsa tree-walking evaluator
- Variables and scopesan environment chain
- Control flow and functionsif, while, and calls
- Error reporting with line numberspointing at the token, not the file
- A REPL for the interpreterreading, evaluating, printing, looping
- Compiling instead of interpretingbytecode, and why it is faster
Before you start
Section titled “Before you start”Structs, enums, and pattern matching is required reading — this track is
essentially an extended exercise in match. Add
Smart pointers and memory before the closures article, where Rc becomes
unavoidable.