Build a Lisp Interpreter in Python
Inspired by Peter Norvig's classic *lis.py*. About 100 lines give you a working Scheme-like language.
What you just built: a reader (tokenize + parse), environments (lexical scope with outer links), closures (Procedure captures env), special forms (quote, if, define, set!, lambda, begin), and function application. That's the essential core of every Lisp.
# lisp.py
import math
import operator as op
def tokenize(source: str) -> list[str]:
"""Split source text into tokens: parentheses and atoms."""
return source.replace('(', ' ( ').replace(')', ' ) ').split()
def parse(tokens: list[str]):
"""Read one expression from a token list (consuming tokens)."""
if not tokens:
raise SyntaxError('unexpected EOF')
token = tokens.pop(0)
if token == '(':
lst = []
while tokens and tokens[0] != ')':
lst.append(parse(tokens))
if not tokens:
raise SyntaxError('missing )')
tokens.pop(0) # discard ')'
return lst
if token == ')':
raise SyntaxError('unexpected )')
return atom(token)
def atom(token: str):
"""Numbers become int/float; everything else is a symbol (str)."""
try:
return int(token)
except ValueError:
try:
return float(token)
except ValueError:
return token
class Env(dict):
"""An environment: a dict of {symbol: value} with an outer environment."""
def __init__(self, params=(), args=(), outer=None):
super().__init__(zip(params, args))
…lisp> (define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))
lisp> (fact 20)
2432902008176640000
lisp> (define make-adder (lambda (n) (lambda (x) (+ x n))))
lisp> ((make-adder 10) 5)
15
lisp> (map (lambda (x) (* x x)) (list 1 2 3 4))
(1 4 9 16)
lisp> (cons 0 (quote (1 2 3)))
(0 1 2 3)