Technology

Peter Norvig's Lisp Interpreter Tutorial: A Modern Look at Language Implementation

Martin HollowayPublished 2month ago4 min readBased on 3 sources
Reading level
Peter Norvig's Lisp Interpreter Tutorial: A Modern Look at Language Implementation

Peter Norvig published "How to Write a (Lisp) Interpreter (in Python)" on 21 June 2026, offering a hands-on walkthrough of building a working Scheme interpreter—called Lispy—in Python. The tutorial covers the three core pieces of any interpreter: lexing (tokenization), parsing, and evaluation, enough of the Lisp language to teach interpreter design without aiming for a complete R7RS standard implementation.

Norvig brings serious credentials to the work. He co-authored Artificial Intelligence: A Modern Approach, the standard undergraduate AI textbook, and spent years as Director of Research at Google. His tutorials carry weight because they come from someone who has built systems at scale, not merely described them in theory.

The pedagogical strategy is straightforward: strip the language down to its essentials, get evaluation working, then add complexity. Lispy handles the fundamental building blocks—define, lambda, if, quote, begin—along with arithmetic operations and a base environment seeded with Python's math functions. The parser uses recursive descent, a standard technique; the evaluator is a compact eval function that dispatches based on what kind of expression it receives. The entire implementation fits in under 100 lines of Python.

That compactness is deliberate. Before LLVM toolchains and parser generators became the default way to build languages, Lisp was the go-to teaching vehicle for explaining how interpreters actually work—from McCarthy's original 1960 eval, through the metacircular evaluator in SICP during the 1980s, down through decades of tutorial implementations. Norvig's version sits squarely in that tradition, updating it for Python programmers who may never have written Lisp before.

A second article, "An ((Even Better) Lisp) Interpreter (in Python)", builds on the first by adding tail-call optimization (a technique to avoid running out of memory during recursive calls), proper tail recursion, a new data type, and macro support—the features that move something from a learning tool to code you could actually use. Together, the two articles show a recurring pattern in language implementation: get the core semantics right first, then optimize for the performance and ease-of-use that real-world code demands.

Norvig also published the interpreter as lis.py, a complete, runnable Python file with a copyright notice spanning 2010–2016, so readers get a working artifact rather than just narrative and code snippets.

The choice of Lisp as the teaching vehicle is worth examining. Lisp has a property called homoiconicity—code and data use the same representation—which makes the evaluation loop unusual transparent. You can write an eval for Lisp in a recursive way that mirrors Lisp's own structure, which is why the metacircular evaluator has been a teaching standard for so long. Python is a natural fit as the host language: you can represent a Lisp environment as a dictionary, a procedure as a Python function, and an expression as a nested list, without the type system pushing back at every step.

For working engineers, the practical insight extends beyond Lisp itself. Anyone who has built a configuration language, a rules engine, or a query layer on top of an existing runtime has essentially built a restricted evaluator. Seeing the full pipeline—tokenizer, parser, environment-aware eval—compressed into under 100 lines is a useful reality check against the complexity that builds up in production systems.

The timing relative to growing interest in domain-specific languages is coincidental. Norvig's tutorial is structured as durable reference material rather than commentary on any particular industry moment. The longevity of lis.py—whose copyright predates the tutorial's current publication by more than a decade—suggests the code has quietly served as a resource for years.

For those wanting to deepen their understanding, the path from Lispy to something closer to production runs through proper tail-call elimination, garbage collection strategy, and compiler backends if you want native code. Norvig's second article covers some of that. Beyond it, Christian Queinnec's Lisp in Small Pieces and the Nystrom Crafting Interpreters project offer the next levels of depth. Norvig's pair of tutorials earns its place at the start of that reading list.