0% found this document useful (0 votes)
344 views

(How To Write A (Lisp) Interpreter (In Python) )

This document summarizes how to write a Lisp interpreter in Python. It describes parsing program text into an abstract syntax tree and evaluating that tree to produce a result. Specifically, it defines a simplified Lisp-like language called Lispy Calculator that can perform basic math operations. The interpreter works by parsing text into a list representation using functions tokenize and read_from_tokens, then evaluating that list using an eval function within an environment that maps variables to values.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
344 views

(How To Write A (Lisp) Interpreter (In Python) )

This document summarizes how to write a Lisp interpreter in Python. It describes parsing program text into an abstract syntax tree and evaluating that tree to produce a result. Specifically, it defines a simplified Lisp-like language called Lispy Calculator that can perform basic math operations. The interpreter works by parsing text into a list representation using functions tokenize and read_from_tokens, then evaluating that list using an eval function within an environment that maps variables to values.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

(HowtoWritea(Lisp)Interpreter(inPython))
Thispagehastwopurposes:todescribehowtoimplementcomputerlanguageinterpretersingeneral,andinparticular
toshowhowtoimplementasubsetoftheSchemedialectofLispusingPython.IcallmylanguageandinterpreterLispy
(lis.py).Yearsago,IshowedhowtowriteaSchemeinterpreterinJavaaswellasoneinCommonLisp.Thistime
aroundthegoalistodemonstrate,asconciselyandaccessiblyaspossible,whatAlanKaycalled"Maxwell'sEquations
ofSoftware."
Whydoesthismatter?AsSteveYeggesaid,"Ifyoudon'tknowhowcompilerswork,thenyoudon'tknowhow
computerswork."Yeggedescribes8problemsthatcanbesolvedwithcompilers(orequallywithinterpreters,orwith
Yegge'stypicalheavydosageofcynicism).

SyntaxandSemanticsofSchemePrograms
Thesyntaxofalanguageisthearrangementofcharacterstoformcorrectstatementsorexpressionsthesemanticsis
themeaningofthosestatementsorexpressions.Forexample,inthelanguageofmathematicalexpressions(andin
manyprogramminglanguages),thesyntaxforaddingoneplustwois"1+2"andthesemanticsistheapplicationofthe
additionoperatortothetwonumbers,yieldingthevalue3.Wesayweareevaluatinganexpressionwhenwedetermine
itsvaluewewouldsaythat"1+2"evaluatesto3,andwritethatas"1+2"3.
Schemesyntaxisdifferentfrommostotherlanguagesyoumaybefamiliarwith.Consider:
Java
if (x.val() > 0) {
fn(A[i] + 1,
return new String[] {"one", "two"});
}

Scheme
(if (> (val x) 0)
(fn (+ (aref A i) 1)
(quote (one two)))

Javahasawidevarietyofsyntacticconventions(keywords,infixoperators,threekindsofbrackets,operator
precedence,dotnotation,quotes,commas,semicolons),butSchemesyntaxismuchsimpler:
Schemeprogramsconsistsolelyofexpressions.Thereisnostatement/expressiondistinction.
Numbers(e.g.1)andsymbols(e.g.A)arecalledatomicexpressionstheycannotbebrokenintopieces.Theseare
similartotheirJavacounterparts,exceptthatinScheme,operatorssuchas+and>aresymbolstoo.
Everythingelseisalistexpression.Alistisa"(",followedbyzeroormoreexpressions,followedbya")".The
firstelementofthelistdetermineswhatitmeans:
Aliststartingwithakeyword,e.g.(if ...),isaspecialformthemeaningdependsonthekeyword.
Aliststartingwithanonkeyword,e.g.(fn ...),isafunctioncall.
ThebeautyofSchemeisthatthefulllanguageonlyneedseightsyntacticforms.(Incomparison,Pythonhas110and
Javahas133.)Usingsomanyparenthesesmayseemunfamiliar,butithasthevirtuesofsimplicityandconsistency.
(Somehavejokedthat"Lisp"standsfor"LotsofIrritatingSillyParentheses"Ithinkitstandfor"LispIsSyntactically
Pure".)
InthispagewewillcoveralltheimportantpointsofScheme(omittingsomeminordetails),butwewilltaketwosteps
togetthere,definingasimplifiedlanguagefirst,beforedefiningthenearfullSchemelanguage.

Language1:LispyCalculator
LispyCalculatorisasubsetofSchemeusingonlyfivesyntacticforms(twoatomic,twospecialforms,andthe
procedurecall).LispyCalculatorletsyoudoanycomputationyoucoulddoonatypicalcalculatoraslongasyouare
comfortablewithprefixnotation.Andyoucandotwothingsthatarenotofferedintypicalcalculatorlanguages:"if"
expressions,andthedefinitionofnewvariables.Here'sanexampleprogram,thatcomputestheareaofacircleofradius
10,usingtheformular2:
(begin
(define r 10)
(* pi (* r r)))

Hereisatableofalltheallowableexpressions:
Expression
http://norvig.com/lispy.html

Syntax

SemanticsandExample
1/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

variable
reference

var

Asymbolisinterpretedasavariablenameitsvalueisthevariable'svalue.

Example:r10(assumingrwaspreviouslydefinedtobe10)

constant
literal

number

Anumberevaluatestoitself.
Examples:12 12or-3.45e+6 -3.45e+6

conditional (iftestconseqalt)

Evaluatetestiftrue,evaluateandreturnconseqotherwisealt.
Example:(if (> 10 20) (+ 1 1) (+ 3 3)) 6

definition (definevarexp)

Defineanewvariableandgiveitthevalueofevaluatingtheexpressionexp.
Examples:(define r 10)

procedure
(procarg...)
call

Ifprocisanythingotherthanoneofthesymbolsif, define, orquotethen


itistreatedasaprocedure.Evaluateprocandalltheargs,andthenthe
procedureisappliedtothelistofargvalues.
Example:(sqrt (* 2 8)) 4.0

IntheSyntaxcolumnofthistable,varmustbeasymbol,numbermustbeanintegerorfloatingpointnumber,andthe
otheritalicizedwordscanbeanyexpression.Thenotationarg...meanszeroormorerepetitionsofarg.

WhatALanguageInterpreterDoes
Alanguageinterpreterhastwoparts:
1.Parsing:Theparsingcomponenttakesaninputprogramintheformofasequenceofcharacters,verifiesit
accordingtothesyntacticrulesofthelanguage,andtranslatestheprogramintoaninternalrepresentation.Ina
simpleinterpretertheinternalrepresentationisatreestructure(oftencalledanabstractsyntaxtree)thatclosely
mirrorsthenestedstructureofstatementsorexpressionsintheprogram.Inalanguagetranslatorcalleda
compilerthereisoftenaseriesofinternalrepresentations,startingwithanabstractsyntaxtree,andprogressingto
asequenceofinstructionsthatcanbedirectlyexecutedbythecomputer.TheLispyparserisimplementedwith
thefunctionparse.
2.Execution:Theinternalrepresentationisthenprocessedaccordingtothesemanticrulesofthelanguage,thereby
carryingoutthecomputation.Lispy'sexecutionfunctioniscalledeval(notethisshadowsPython'sbuiltin
functionofthesamename).
Hereisapictureoftheinterpretationprocess:
program(str) parse abstractsyntaxtree(list) eval result(object)
Andhereisashortexampleofwhatwewantparseandevaltobeabletodo:
>> program = "(begin (define r 10) (* pi (* r r)))"
>>> parse(program)
['begin', ['define', 'r', 10], ['*', 'pi', ['*', 'r', 'r']]]
>>> eval(parse(program))
314.1592653589793

Parsing:parse,tokenizeandread_from_tokens
Parsingistraditionallyseparatedintotwoparts:lexicalanalysis,inwhichtheinputcharacterstringisbrokenupintoa
sequenceoftokens,andsyntacticanalysis,inwhichthetokensareassembledintoanabstractsyntaxtree.TheLispy
tokensareparentheses,symbols,andnumbers.Therearemanytoolsforlexicalanalysis(suchasMikeLeskandEric
Schmidt'slex),butwe'lluseaverysimpletool:Python'sstr.split.Thefunctiontokenizetakesasinputastringof
charactersitaddsspacesaroundeachparen,andthencallsstr.splittogetalistoftokens:
def tokenize(chars):
"Convert a string of characters into a list of tokens."
return chars.replace('(', ' ( ').replace(')', ' ) ').split()
>>> program = "(begin (define r 10) (* pi (* r r)))"
>>> tokenize(program)
['(', 'begin', '(', 'define', 'r', '10', ')', '(', '*', 'pi', '(', '*', 'r', 'r', ')', ')', ')']

Ourfunctionparsewilltakeastringrepresentationofaprogramasinput,calltokenizetogetalistoftokens,and
thencallread_from_tokenstoassembleanabstractsyntaxtree.read_from_tokenslooksatthefirsttokenifitisa
http://norvig.com/lispy.html

2/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

')'that'sasyntaxerror.Ifitisa'(',thenwestartbuildingupalistofsubexpressionsuntilwehitamatching')'.

Anynonparenthesistokenmustbeasymbolornumber.We'llletPythonmakethedistinctionbetweenthem:foreach
nonparentoken,firsttrytointerpretitasanint,thenasafloat,andfinallyasasymbol.Hereistheparser:
def parse(program):
"Read a Scheme expression from a string."
return read_from_tokens(tokenize(program))
def read_from_tokens(tokens):
"Read an expression from a sequence of tokens."
if len(tokens) == 0:
raise SyntaxError('unexpected EOF while reading')
token = tokens.pop(0)
if '(' == token:
L = []
while tokens[0] != ')':
L.append(read_from_tokens(tokens))
tokens.pop(0) # pop off ')'
return L
elif ')' == token:
raise SyntaxError('unexpected )')
else:
return atom(token)
def atom(token):
"Numbers become numbers; every other token is a symbol."
try: return int(token)
except ValueError:
try: return float(token)
except ValueError:
return Symbol(token)
parseworkslikethis:
>>> program = "(begin (define r 10) (* pi (* r r)))"
>>> parse(program)
['begin', ['define', 'r', 10], ['*', 'pi', ['*', 'r', 'r']]]

WehavemadesomechoicesabouttherepresentationofSchemeobjects.Herewemakethechoicesexplicit:
Symbol = str # A Scheme Symbol is implemented as a Python str
List = list # A Scheme List is implemented as a Python list
Number = (int, float) # A Scheme Number is implemented as a Python int or float

We'realmostreadytodefineeval.Butweneedonemoreconceptfirst.

Environments
Thefunctionevaltakestwoarguments:anexpression,x,thatwewanttoevaluate,andanenvironment,env,inwhich
toevaluateit.Anenvironmentisamappingfromvariablenamestotheirvalues.Bydefault,evalwilluseaglobal
environentthatincludesthenamesforabunchofstandardfunctions(likesqrtandmax,andalsooperatorslike*).This
environmentcanbeaugmentedwithuserdefinedvariables,usingtheexpression(define variable value).Fornow,
wecanimplementanenvironmentasaPythondictof{variable:value}pairs.
import math
imoort operator as op
Env = dict # An environment is a mapping of {variable: value}
def standard_env():
"An environment with some Scheme standard procedures."
env = Env()
env.update(vars(math)) # sin, cos, sqrt, pi, ...
env.update({
'+':op.add, '-':op.sub, '*':op.mul, '/':op.div,
'>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq,
'abs': abs,
'append': op.add,
'apply': apply,
'begin': lambda *x: x[-1],
'car': lambda x: x[0],
'cdr': lambda x: x[1:],
'cons': lambda x,y: [x] + y,
http://norvig.com/lispy.html

3/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

'eq?': op.is_,
'equal?': op.eq,
'length': len,
'list': lambda *x: list(x),
'list?': lambda x: isinstance(x,list),
'map': map,
'max': max,
'min': min,
'not': op.not_,
'null?': lambda x: x == [],
'number?': lambda x: isinstance(x, Number),
'procedure?': callable,
'round': round,
'symbol?': lambda x: isinstance(x, Symbol),
})
return env
global_env = standard_env()

Evaluation:eval
Wearenowreadyfortheimplementationofeval.Asarefresher,werepeatthetableofSchemeforms:
Expression

Syntax

SemanticsandExample

variable
reference

var

Asymbolisinterpretedasavariablenameitsvalueisthevariable'svalue.
Example:r10(assumingrwaspreviouslydefinedtobe10)

constant
literal

number

Anumberevaluatestoitself.
Examples:12 12or-3.45e+6 -3.45e+6

conditional (iftestconseqalt)

Evaluatetestiftrue,evaluateandreturnconseqotherwisealt.
Example:(if (> 10 20) (+ 1 1) (+ 3 3)) 6

definition (definevarexp)

Defineanewvariableandgiveitthevalueofevaluatingtheexpressionexp.
Examples:(define r 10)

procedure
(procarg...)
call

Ifprocisanythingotherthanoneofthesymbolsif, define, orquotethen


itistreatedasaprocedure.Evaluateprocandalltheargs,andthenthe
procedureisappliedtothelistofargvalues.
Example:(sqrt (* 2 8)) 4.0

Noticehowcloselythecodeforevalfollowsthetable:
def eval(x, env=global_env):
"Evaluate an expression in an environment."
if isinstance(x, Symbol): # variable reference
return env[x]
elif not isinstance(x, List): # constant literal
return x
elif x[0] == 'if': # conditional
(_, test, conseq, alt) = x
exp = (conseq if eval(test, env) else alt)
return eval(exp, env)
elif x[0] == 'define': # definition
(_, var, exp) = x
env[var] = eval(exp, env)
else: # procedure call
proc = eval(x[0], env)
args = [eval(arg, env) for arg in x[1:]]
return proc(*args)

We'redone!Youcanseeitallinaction:
>>> eval(parse("(define r 10)"))
>>> eval(parse("(* pi (* r r))"))
314.1592653589793

Interaction:AREPL
Itistedioustohavetoenter"eval(parse(...))"allthetime.OneofLisp'sgreatlegaciesisthenotionofan
interactivereadevalprintloop:awayforaprogrammertoenteranexpression,andseeitimmediatelyread,evaluated,
http://norvig.com/lispy.html

4/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

andprinted,withouthavingtogothroughalengthybuild/compilecycle.Solet'sdefinethefunctionrepl(whichstands
forreadevalprintloop),andthefunctionschemestrwhichreturnsastringrepresentingaSchemeobject.
def repl(prompt='lis.py> '):
"A prompt-read-eval-print loop."
while True:
val = eval(parse(raw_input(prompt)))
if val is not None:
print(schemestr(val))
def schemestr(exp):
"Convert a Python object back into a Scheme-readable string."
if isinstance(exp, list):
return '(' + ' '.join(map(schemestr, exp)) + ')'
else:
return str(exp)

Hereisreplinaction:
>>> repl()
lis.py> (define r 10)
lis.py> (* pi (* r r))
314.159265359
lis.py> (if (> (* 11 11) 120) (* 7 6) oops)
42
lis.py>

Language2:FullLispy
Wewillnowextendourlanguagewiththreenewspecialforms,givingusamuchmorenearlycompleteScheme
subset:
Expression

Syntax

quotation (quote exp)

SemanticsandExample
Returntheexpliterallydonotevaluateit.
Example:(quote (+ 1 2)) (+ 1 2)

assignment (set!varexp)

Evaluateexpandassignthatvaluetovar,whichmusthavebeenpreviously
defined(withadefineorasaparametertoanenclosingprocedure).
Example:(set! r2 (* r r))

procedure (lambda (var...)exp)

Createaprocedurewithparameter(s)namedvar...andexpasthebody.
Example:(lambda (r) (* pi (* r r)))

Thelambdaspecialform(anobscurenomenclaturechoicethatreferstoAlonzoChurch'slambdacalculus)createsa
procedure.Wewantprocedurestoworklikethis:
lis.py> (define circle-area (lambda (r) (* pi (* r r)))
lis.py> (circle-area 10)
314.159265359

Theprocedurecall(circle-area 10)causesustoevaluatethebodyoftheprocedure,(* pi (* r r)),inan


environmentinwhichpiand*havethesameglobalvaluestheyalwaysdid,butnowrhasthevalue10.However,it
wouldn'tdotojustsetrtobe10intheglobalenvironment.Whatifwewereusingrforsomeotherpurpose?We
wouldn'twantacalltocircle-areatoalterthatvalue.Instead,wewanttoarrangefortheretobealocalvariable
namedrthatwecansetto10withoutworryingaboutinterferingwithanyothervariablethathappenstohavethesame
name.Wewillcreateanewkindofenvironment,onewhichallowsforbothlocalandglobalvariables.
Theideaisthatwhenweevaluate(circle-area 10),wewillfetchtheprocedurebody,(* pi (* r r)),and
evaluateitinanenvironmentthathasrasthesolelocalvariable,butalsohasaccesstotheglobalenvironment.Inother
words,wewantanenvironmentthatlookslikethis,withthelocalenvironmentnestedinsidethe"outer"global
environment:
pi: 3.141592653589793
*: <built-in function mul>
...
r: 10

Whenwelookupavariableinsuchanestedenvironment,welookfirstattheinnermostlevel,butifwedon'tfindthe
http://norvig.com/lispy.html

5/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

variablenamethere,wemovetothenextouterlevel.
Itisclearthatproceduresandenvironmentsareintertwined,solet'sdefinethemtogether:
class Procedure(object):
"A user-defined Scheme procedure."
def __init__(self, parms, body, env):
self.parms, self.body, self.env = parms, body, env
def __call__(self, *args):
return eval(self.body, Env(self.parms, args, self.env))
class Env(dict):
"An environment: a dict of {'var':val} pairs, with an outer Env."
def __init__(self, parms=(), args=(), outer=None):
self.update(zip(parms, args))
self.outer = outer
def find(self, var):
"Find the innermost Env where var appears."
return self if (var in self) else self.outer.find(var)
global_env = standard_env()

Weseethateveryprocedurehasthreecomponents:alistofparameternames,abodyexpression,andanenvironment
thattellsuswhatnonlocalvariablesareaccessiblefromthebody.
Anenvironmentisasubclassofdict,soithasallthemethodsthatdicthas.Inadditiontherearetwomethods:the
constructor__init__buildsanewenvironmentbytakingalistofparameternamesandacorrespondinglistof
argumentvalues,andcreatinganewenvironmentthathasthose{variable:value}pairsastheinnerpart,andalsorefers
tothegivenouterenvironment.Themethodfindisusedtofindtherightenvironmentforavariable:eithertheinner
oneoranouterone.
Toseehowtheseallgotogether,hereisthenewdefinitionofeval.Notethattheclauseforvariablereferencehas
changed:wenowhavetocallenv.find(x)tofindatwhatlevelthevariablexexiststhenwecanfetchthevalueofx
fromthatlevel.(Theclausefordefinehasnotchanged,becauseadefinealwaysaddsanewvariabletotheinnermost
environment.)Therearetwonewclauses:forset!,wefindtheenvironmentlevelwherethevariableexistsandsetitto
anewvalue.Withlambda,wecreateanewprocedureobjectwiththegivenparameterlist,body,andenvironment.
def eval(x, env=global_env):
"Evaluate an expression in an environment."
if isinstance(x, Symbol): # variable reference
return env.find(x)[x]
elif not isinstance(x, List): # constant literal
return x
elif x[0] == 'quote': # quotation
(_, exp) = x
return exp
elif x[0] == 'if': # conditional
(_, test, conseq, alt) = x
exp = (conseq if eval(test, env) else alt)
return eval(exp, env)
elif x[0] == 'define': # definition
(_, var, exp) = x
env[var] = eval(exp, env)
elif x[0] == 'set!': # assignment
(_, var, exp) = x
env.find(var)[var] = eval(exp, env)
elif x[0] == 'lambda': # procedure
(_, parms, body) = x
return Procedure(parms, body, env)
else: # procedure call
proc = eval(x[0], env)
args = [eval(arg, env) for arg in x[1:]]
return proc(*args)

Toappreciatehowproceduresandenvironmentsworktogether,considerthisprogramandtheenvironmentthatgets
formedwhenweevaluate(account1 -20.00):
+:<builtinoperatoradd>
make-account: <a Procedure>

(define make-account
(lambda (balance)
(lambda (amt)
(begin (set! balance (+ balance amt))
balance))))
http://norvig.com/lispy.html

balance: 100.00

amt: -20.00
6/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

(define account1 (make-account 100.00))


(account1 -20.00)

account1: <a Procedure>

Eachrectangularboxrepresentsanenvironment,andthecoloroftheboxmatchesthecolorofthevariablesthatare
newlydefinedintheenvironment.Inthelasttwolinesoftheprogramwedefineaccount1andcall(account1
-20.00)thisrepresentsthecreationofabankaccountwitha100dollaropeningbalance,followedbya20dollar
withdrawal.Intheprocessofevaluating(account1 -20.00),wewillevaltheexpressionhighlightedinyellow.There
arethreevariablesinthatexpression.amtcanbefoundimmediatelyintheinnermost(green)environment.But
balanceisnotdefinedthere:wehavetolookatthegreenenvironment'souterenv,theblueone.Andfinally,the
variable+isnotfoundineitherofthoseweneedtodoonemoreouterstep,totheglobal(red)environment.This
processoflookingfirstininnerenvironmentsandtheninouteronesiscalledlexicalscoping.Env.find(var)findsthe
rightenvironmentaccordingtolexicalscopingrules.
Let'sseewhatwecandonow:
>>> repl()
lis.py> (define circle-area (lambda (r) (* pi (* r r))))
lis.py> (circle-area 3)
28.274333877
lis.py> (define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))
lis.py> (fact 10)
3628800
lis.py> (fact 100)
9332621544394415268169923885626670049071596826438162146859296389521759999322991
5608941463976156518286253697920827223758251185210916864000000000000000000000000
lis.py> (circle-area (fact 10))
4.1369087198e+13
lis.py> (define first car)
lis.py> (define rest cdr)
lis.py> (define count (lambda (item L) (if L (+ (equal? item (first L)) (count item (rest L))) 0)))
lis.py> (count 0 (list 0 1 2 3 0 0))
3
lis.py> (count (quote the) (quote (the more the merrier the bigger the better)))
4
lis.py> (define twice (lambda (x) (* 2 x)))
lis.py> (twice 5)
10
lis.py> (define repeat (lambda (f) (lambda (x) (f (f x)))))
lis.py> ((repeat twice) 10)
40
lis.py> ((repeat (repeat twice)) 10)
160
lis.py> ((repeat (repeat (repeat twice))) 10)
2560
lis.py> ((repeat (repeat (repeat (repeat twice)))) 10)
655360
lis.py> (pow 2 16)
65536.0
lis.py> (define fib (lambda (n) (if (< n 2) 1 (+ (fib (- n 1)) (fib (- n 2))))))
lis.py> (define range (lambda (a b) (if (= a b) (quote ()) (cons a (range (+ a 1) b)))))
lis.py> (range 0 10)
(0 1 2 3 4 5 6 7 8 9)
lis.py> (map fib (range 0 10))
(1 1 2 3 5 8 13 21 34 55)
lis.py> (map fib (range 0 20))
(1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765)

Wenowhavealanguagewithprocedures,variables,conditionals(if),andsequentialexecution(thebeginprocedure).
Ifyouarefamiliarwithotherlanguages,youmightthinkthatawhileorforloopwouldbeneeded,butScheme
managestodowithoutthesejustfine.TheSchemereportsays"Schemedemonstratesthataverysmallnumberofrules
forformingexpressions,withnorestrictionsonhowtheyarecomposed,sufficetoformapracticalandefficient
programminglanguage."InSchemeyouiteratebydefiningrecursivefunctions.

HowSmall/Fast/Complete/GoodisLispy?
InwhichwejudgeLispyonseveralcriteria:
Small:Lispyisverysmall:117noncommentnonblanklines4Kofsourcecode.(Anearlierversionwasjust90
lines,buthadfewerstandardproceduresandwasperhapsabittooterse.)ThesmallestversionofmySchemein
http://norvig.com/lispy.html

7/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

Java,Jscheme,was1664linesand57Kofsource.JschemewasoriginallycalledSILK(SchemeinFifty
Kilobytes),butIonlykeptunderthatlimitbycountingbytecoderatherthansourcecode.Lispydoesmuchbetter
IthinkitmeetsAlanKay's1972claimthatyoucoulddefinethe"mostpowerfullanguageintheworld"in"a
pageofcode."(However,IthinkAlanmightdisagree,becausehewouldcountthePythoncompileraspartofthe
code,puttingmewelloverapage.)
bash$ grep "^\s*[^#\s]" lis.py | wc
117 497 4276

Fast:Lispycomputes(fact 100)in0.004seconds.That'sfastenoughforme(althoughfarslowerthanmost
otherwaysofcomputingit).
Complete:LispyisnotverycompletecomparedtotheSchemestandard.Somemajorshortcomings:
Syntax:Missingcomments,quoteandquasiquotenotation,#literals,thederivedexpressiontypes(suchas
cond,derivedfromif,orlet,derivedfromlambda),anddottedlistnotation.
Semantics:Missingcall/ccandtailrecursion.
DataTypes:Missingstrings,characters,booleans,ports,vectors,exact/inexactnumbers.Pythonlistsare
actuallyclosertoSchemevectorsthantotheSchemepairsandliststhatweimplementwiththem.
Procedures:Missingover100primitiveprocedures:alltheonesforthemissingdatatypes,plussome
otherslikeset-car!andset-cdr!,becausewecan'timplementset-cdr!completelyusingPythonlists.
Errorrecovery:Lispydoesnotattempttodetect,reasonablyreport,orrecoverfromerrors.Lispyexpects
theprogrammertobeperfect.
Good:That'suptothereaderstodecide.IfounditwasgoodformypurposeofexplainingLispinterpreters.

TrueStory
Tobackuptheideathatitcanbeveryhelpfultoknowhowinterpreterswork,here'sastory.Waybackin1984Iwas
writingaPh.D.thesis.ThiswasbeforeLaTeX,beforeMicrosoftWordforWindowsweusedtroff.Unfortunately,
troffhadnofacilityforforwardreferencestosymboliclabels:Iwantedtobeabletowrite"Aswewillseeonpage
@theoremx"andthenwritesomethinglike"@(settheoremx\n%)"intheappropriateplace(thetroffregister\n%
holdsthepagenumber).MyfellowgradstudentTonyDeRosefeltthesameneed,andtogetherwesketchedouta
simpleLispprogramthatwouldhandlethisasapreprocessor.However,itturnedoutthattheLispwehadatthetime
wasgoodatreadingLispexpressions,butsoslowatreadingcharacteratatimenonLispexpressionsthatourprogram
wasannoyingtouse.
FromthereTonyandIsplitpaths.HereasonedthatthehardpartwastheinterpreterforexpressionsheneededLispfor
that,butheknewhowtowriteatinyCroutineforreadingandechoingthenonLispcharactersandlinkitintotheLisp
program.Ididn'tknowhowtodothatlinking,butIreasonedthatwritinganinterpreterforthistriviallanguage(allit
hadwassetvariable,fetchvariable,andstringconcatenate)waseasy,soIwroteaninterpreterinC.So,ironically,Tony
wroteaLispprogram(withonesmallroutineinC)becausehewasaCprogrammer,andIwroteaCprogrambecauseI
wasaLispprogrammer.
Intheend,webothgotourthesesdone(Tony,Peter).

TheWholeThing
Thewholeprogramishere:lis.py.

FurtherReading
TolearnmoreaboutSchemeconsultsomeofthefinebooks(byFriedmanandFellesein,Dybvig,Queinnec,Harvey
andWrightorSussmanandAbelson),videos(byAbelsonandSussman),tutorials(byDorai,PLT,orNeller),orthe
referencemanual.
IalsohaveanotherpagedescribingamoreadvancedversionofLispy.
PeterNorvig
96Comments

norvig.com

Recommend 134
http://norvig.com/lispy.html

Share

Login

SortbyBest
8/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

Jointhediscussion
thedeemon6yearsago

Here'saRubyanalogin60lineswithaddedsupportofcontinuations(call/cc,famousSchemefeature):
http://stuff.thedeemon.com/ris...
RequiresRuby1.8.x.
22

Reply Share

PeterNorvig

Mod >thedeemon6yearsago

Verynice!
9

Reply Share

tarpsocks>thedeemon5yearsago

60linesbutnotanounceoftheclarityMr.Norvigsupplied.
3

Reply Share

PeterNorvig>tarpsocks5yearsago

tarpsocks,Ithinkitisonlylackingclarityifyoudon'tknowtheRubyidioms.Onceyoudo,itisclear.
OnethingIwouldobjectto:call/ccshouldbearegularfunction,notaspecialform.
8

Reply Share

m00nlight223>thedeemon2yearsago

It'strickytosaytheinterpretersupportcall/ccsincetheimplementationusecallccoftheRubylanguage

Reply Share

LostProtocol6yearsago

OhMyGawd.Isearchedfor'howtowritesimplelanguageinterpretersinpython'andreachedthispage(thankyou
google).Thisarticlehadtwoprofoundimpactsonme.1)Iunderstoodhowtowritelispinterpretersin
python(obvious)and2)IunderstoodLISPforthefirsttime.ForatleasttwoyearsIhavegoneonandoffaboutLisp,
neverreally'getting'it.Alwaysthoughtitwasalousylanguagenomatterhowmuchpeoplelovedit.Thatthought
changedafterthisarticle.Ithasembarkedmeontowritingmyownlispdialect(notagain)andalsolearningLispwith
anewattitude.NowIthinkofonlyonething,IwishPythonhadequivalentof(defmacro)oflisp.:)ThanksMr.
NorvigIwillbuyyourbookonArtificialIntelligenceto(somehow)supportthiswebpage:D.
15

Reply Share

Dave5yearsago

Chapter11ofthisbookhttp://www.computingbook.org/[http://www.computingbook.org/I...]isallaboutaPython
implementationofaminiSchemeinterpreter.(Thisproblemset,http://www.cs.virginia.edu/~ev...,isaboutextending
thatinterpreter).
7

Reply Share

TimFinin6yearsago

Iwasgoingoverthistopresentitinaclassandnoticedthatthereisnovariableboundtotheemptylist.Wecanuse
(quote())forthetheemptylist,asin(definex(cons1(cons2(quote())))),butthewaylistsworkinPython,(eq?
(quote())(quote()))isFalse.SoIthinkitwouldbegoodtopredefineaglobalvariablenullinitiallyboundto[]:
global_env['null']=[]
6

Reply Share

Sainamdar5yearsago

HereisaversionofthisinJavascript.IcreatedthissothatIcouldrunthroughtheexamples/exercisesinSICPwhile
commuting.
https://bitbucket.org/sainamda...
5

Reply Share

http://norvig.com/lispy.html

9/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

Sainamdar>Sainamdar5yearsago

HereisanothervariationinJavascriptthatseparatessyntacticanalysisfromexecution.
https://bitbucket.org/sainamda...
2

Reply Share

affiszervmention>Sainamdar3yearsago

stringsdon'tseemtoworkonyourinterpreter
(="str""str")givesTypeError:outer.findisnotafunction

Reply Share

ShantanuInamdar>affiszervmention3yearsago

Thanksforthefeedbackaffiszervmention.Thisis"bydesign".AsPeterNorvigwroteinthe
completenessevaluationofLispy,itismissingmanydatatypeslike"Missingstrings,characters,
booleans,ports,vectors,exact/inexactnumbers".

Reply Share

PatHayes6yearsago

Verysweet.Butyouknow,whatwouldreallybeinterestingwouldbetoseehowmanylinesofcodeyouneedtodo
thisinsomethingthatdoesn'thavebuiltinrecursionandinwhichyouhavetodescribegarbagecollection.
Somethinglikeanassemblylanguage...
4

Reply Share

TheoD'Hondt>PatHayes6yearsago

Havealookathttp://soft.vub.ac.be/francquiit's"Slip"insteadof"Lispy"andCinsteadofPython,hence
trampolinesandgarbagecollection...Shouldmakeyouhappy.
4

Reply Share

an691>TheoD'Hondt5yearsago

HelloTheo,aretheslidesandpossiblyvideosoftheselecturesavailable?


PeterNorvig

Reply Share

Mod >PatHayes6yearsago

Exactly,Pat!Thatisadifferentchallenge,andaverygoodone.IgotanicenotefromAlanKayreminding
methatthe"onepage"didthat,ratherthanrelyingonahostlanguagetodoit.(Ontheotherhand,the"one
page"didnotincludethedefinitionsofindividualprocedures,includingsyntacticprocedures.)
2

Reply Share

MikeParr>PatHayes4yearsago

Pat:LispKitlisp(originallybyHenderson),hasanimplementationinPascal,butitusesarraysforlist
storage,ratherthanrecords/structsquiteclosetoassembler.
1

Reply Share

Frakturfreund5yearsago

Thiscodelooksverynice,butithinkthatimplementingaLispInterpreterinPythonissomekindofcheating.Python
isahighlevellanguage,soyougetverymuchforfree.Foranantipode,isuggesttohavealookintoZozotez,aLisp
InterpeterinBrainfuck(whichisaridiculouslylowleveltoylanguage):
https://code.google.com/p/zozo...
3

Reply Share

PeterNorvig

Mod >Frakturfreund5yearsago

YouarerightwearerelyingonmanyfeaturesofPython:callstack,datatypes,garbagecollection,etc.The
nextstepwouldbetoshowacompilertosomesortofassemblylanguage.IthinkeithertheJavaJVMorthe
Pythonbytecodewouldbegoodtargets.We'dalsoneedaruntimesystemwithGC.Ishowthecompilerin
myPAIPbook.
http://norvig.com/lispy.html

10/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

myPAIPbook.
3

Reply Share

Frakturfreund>PeterNorvig5yearsago

Thanksforthehint!Illputthebookonmychristmaslist:).

Reply Share

espin5yearsago

Veryniceandinspiring!
FYIthereisanicesmalllispimplementationinCfromPiumatra(aresearcherwithAlanKayatVPRI)
athttp://piumarta.com/software/l...
3

Reply Share

missingparen3yearsago

there'samissingclosingpareninbelowexample
>>program="(definearea(lambda(r)(*3.141592653(*rr)))"
itshouldbe
>>program="(definearea(lambda(r)(*3.141592653(*rr))))"
2

Reply Share

fishyrecondite3yearsago

Fabulous...!!
2

Reply Share

Reborn22665yearsago

HiPeter
Iamstudyinginterpretersandcompilersrecently.Readingthisarticlehelpsmealot.Ithinkthismightalsohelpother
CSstudentsinTaiwansothatItranslateitintoTraditionalChinese.Ifyoumind,pleaseletmeknowandIwill
removethepost.
http://reborn2266.blogspot.com...
Thanks.
2

Reply Share

PeterNorvig

Mod >Reborn22665yearsago

Thankyoufordoingthetranslation.Iappreciateit.
4

Reply Share

Reborn2266>PeterNorvig5yearsago

Thankyouverymuch.Iwilldomybesttomakesurethetranslationaccurate.:)
2

Reply Share

JukkaVlimaa6yearsago

Beautiful.Shouldn'tconsbelambdax,y:[x]+[y]?
2

Reply Share

PeterNorvig

Mod >JukkaVlimaa6yearsago

Shouldn'tconsbelambdax,y:[x]+[y]?Mostlyno.I'mtryingtomakeitworkforthecasewhereyisalist
(possiblyempty)andxisanitem.Forexample,(cons1'(23))shouldbe(123),andifx=1andy=[2,3],then
[1]+[2,3]is[1,2,3],sothat's[x]+y.(Imakeitlist(y)ratherthanyjustincaseyisatuple,notalist.)
Istillcan'tdotheequivalentof(cons12),whichshouldyield(1.2)thatcan'tberepresentedinmy
approachtolists.
2
http://norvig.com/lispy.html

Reply Share

11/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

RalphCorderoy6yearsago

Thanksforanothernicearticle.Wouldthe"tokens.pop(0)#popoff')'"bemoreclearasthefaster,smallerbytecode,
"deltokens[0]"sincetokens[0],returnedbypop(),isn'twanted.
2

Reply Share

PeterNorvig

Mod 6yearsago

Thanksforallthecomments.EricCooper:IlikeyourideaofeliminatingtheProcedureclass.Ihaditbecauseitis
necessarytohaveaccesstotheexptodotailrecursionelimination,butIdon'tneedthatinthisversion.bsagert:you
arerightthataproperreplneedstocatchExceptions.Italsoshouldbeabletoreadmultilineinput,whichcan'teasily
bedonewiththeinterfaceIhaveprovided.Sainamdar:IdidthisbecauseIstillgetalotofinterestinJScheme,and
severalpeopleaskedifIcoulddoitinPython.Nowtheansweris:YesIcan!Massaro:youarerightthatamutable
paircanbeimplementedasaprocedurewithtwovariables.Oravectoroftwoelements.ButthepointisthatifIdid
that,theywouldbedifferentfromPython'slists,andIcouldn'tusePython'smap,etc.
2

Reply Share

EricCooper6yearsago

Hereisamoremetacircularimplementationofprocedures:
defprocedure(parms,exp,env):
"AuserdefinedSchemeprocedure."
returnlambda*args:eval(exp,Env(parms,args,env))
2

Reply Share

PeterNorvig

Mod >EricCooper6yearsago

Thanks,Eric.Iadoptedyoursolutionhere.Ididn'tuseitoriginallybecauseIwasthinkingaheadtoLispy2,
whereIcouldn'tuseit.Butforthisversion,yoursuggestionisrighton.

Reply Share

KisituAugustine2yearsago

maybeammissingsomethinghere,whatdoes(_,vars,exp)=xmean??
1

Reply Share

steni>KisituAugustine2yearsago

Itmeansthatwhateverisinx,is"explodedinto"threevariables,ofwhichthefirstisdiscarded(becauseitis
notneeded).
Let'ssayxisanarray:x=[1,2,3].
Then(_,vars,exp)=xwilldiscardthe1,andnowvars=2,andexp=3.

Reply Share

KisituAugustine>steni2yearsago

thankssteni

Reply Share

cym133yearsago

GreatinterpretationofthatmagicalpartofSICP,happytoseethatthisbookstillinspirepeople!
1

Reply Share

Thiscommentisawaitingmoderation.Showcomment.

Konstantinos>Lolol3yearsago

What/prog/,aboardaboutprogramming,hastodowithLISP,aprogramminglanguage?
And/prog/asamatteroffactlikedLISPthoughnotmanyareusingit.
#Yes,fuckingrule14well,Ineverlikedfollowingtherulesanyway.
http://norvig.com/lispy.html

12/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

Reply Share

Lulzy4yearsago

inallhonestythisiscrap
1

Reply Share

wang8monthsago

>(definecirclearea(lambda(r)(*pi(*rr)))
shouldbe
(definecirclearea(lambda(r)(*pi(*rr))))

Reply Share

wang8monthsago

nicejob.helpmealot.

Reply Share

FloydLeeayearago

Ithinkschemegenerallyallowsmultipleexpressionsinlambdas,e.g.,localdefinitions.
Sointheevalfunction,thelambdacaseshouldbemorelike:
elif(ast[0]=='lambda'):
params=ast[1]
body=ast[2:]
returnProcedure(params,body,env)
andinProcedure:
def__call__(self,*args):
e=Env(self.params,args,self.env)
foridx,exprinenumerate(self.body):
ifidx==len(self.body)1:
returnmyeval(expr,e)
else:
myeval(expr,e)
Sorry,can'tseemtogetwhitespacetowork.

Reply Share

alexzhouayearago

verygood

Reply Share

Caridorcayearago

Ithasbeennicetoreadthis,clearlywritten,thanks.ButEnvandProcedureleftmeabitpuzzled.

Reply Share

marnout2yearsago

Brillant!

Reply Share

Eashan2yearsago

awesome

Reply Share

DivyanshPrakash3yearsago

Conciseandenlightening.Beautiful.

Reply Share

http://norvig.com/lispy.html

13/14

23/07/2016

(How to Write a (Lisp) Interpreter (in Python))

Guest3yearsago

Howtotestaboveexamplesinpython2.7.5

Reply Share

victoracid3yearsago

Thatsremarkable...

Reply Share

affiszervmention3yearsago

howtodotailcalloptimization?

Reply Share

Loadmorecomments

Subscribe d AddDisqustoyoursiteAddDisqusAdd

http://norvig.com/lispy.html

Privacy

14/14

You might also like