1- ################ Lispy: Scheme Interpreter in Python 3.9
1+ ################ Lispy: Scheme Interpreter in Python 3.10
22
33## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html
44## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021)
55## by Luciano Ramalho, adding type hints and pattern matching.
66
7- ################ Imports and Types
7+ ################ imports and types
88
99import math
1010import operator as op
1111from collections import ChainMap
12- from collections .abc import MutableMapping
12+ from collections .abc import MutableMapping , Iterator
13+ from itertools import chain
1314from typing import Any , TypeAlias
1415
1516Symbol : TypeAlias = str
@@ -23,41 +24,44 @@ class Procedure:
2324 "A user-defined Scheme procedure."
2425
2526 def __init__ (self , parms : list [Symbol ], body : Expression , env : Environment ):
26- self .parms , self .body , self .env = parms , body , env
27+ self .parms = parms
28+ self .body = body
29+ self .env = env
2730
2831 def __call__ (self , * args : Expression ) -> Any :
2932 local_env = dict (zip (self .parms , args ))
3033 env : Environment = ChainMap (local_env , self .env )
3134 return evaluate (self .body , env )
3235
3336
34- ################ Global Environment
37+ ################ global environment
3538
3639
3740def standard_env () -> Environment :
3841 "An environment with some Scheme standard procedures."
3942 env : Environment = {}
4043 env .update (vars (math )) # sin, cos, sqrt, pi, ...
41- env .update (
42- {
44+ env .update ({
4345 '+' : op .add ,
4446 '-' : op .sub ,
4547 '*' : op .mul ,
4648 '/' : op .truediv ,
49+ '//' : op .floordiv ,
4750 '>' : op .gt ,
4851 '<' : op .lt ,
4952 '>=' : op .ge ,
5053 '<=' : op .le ,
5154 '=' : op .eq ,
5255 'abs' : abs ,
53- 'append' : op . add ,
56+ 'append' : lambda * args : list ( chain ( * args )) ,
5457 'apply' : lambda proc , args : proc (* args ),
5558 'begin' : lambda * x : x [- 1 ],
5659 'car' : lambda x : x [0 ],
5760 'cdr' : lambda x : x [1 :],
5861 'cons' : lambda x , y : [x ] + y ,
5962 'eq?' : op .is_ ,
6063 'equal?' : op .eq ,
64+ 'filter' : lambda * args : list (filter (* args )),
6165 'length' : len ,
6266 'list' : lambda * x : list (x ),
6367 'list?' : lambda x : isinstance (x , list ),
@@ -70,12 +74,11 @@ def standard_env() -> Environment:
7074 'procedure?' : callable ,
7175 'round' : round ,
7276 'symbol?' : lambda x : isinstance (x , Symbol ),
73- }
74- )
77+ })
7578 return env
7679
7780
78- ################ Parsing: parse, tokenize, and read_from_tokens
81+ ################ parse, tokenize, and read_from_tokens
7982
8083
8184def parse (program : str ) -> Expression :
@@ -94,11 +97,11 @@ def read_from_tokens(tokens: list[str]) -> Expression:
9497 raise SyntaxError ('unexpected EOF while reading' )
9598 token = tokens .pop (0 )
9699 if '(' == token :
97- L = []
100+ exp = []
98101 while tokens [0 ] != ')' :
99- L .append (read_from_tokens (tokens ))
100- tokens .pop (0 ) # pop off ')'
101- return L
102+ exp .append (read_from_tokens (tokens ))
103+ tokens .pop (0 ) # discard ')'
104+ return exp
102105 elif ')' == token :
103106 raise SyntaxError ('unexpected )' )
104107 else :
@@ -116,7 +119,7 @@ def parse_atom(token: str) -> Atom:
116119 return Symbol (token )
117120
118121
119- ################ Interaction: A REPL
122+ ################ interaction: a REPL
120123
121124
122125def repl (prompt : str = 'lis.py> ' ) -> None :
@@ -138,29 +141,50 @@ def lispstr(exp: object) -> str:
138141
139142################ eval
140143
141-
142- def evaluate (x : Expression , env : Environment ) -> Any :
144+ # tag::EVALUATE[]
145+ def evaluate (exp : Expression , env : Environment ) -> Any :
143146 "Evaluate an expression in an environment."
144- match x :
145- case Symbol (var ): # variable reference
147+ match exp :
148+ case int (x ) | float (x ):
149+ return x
150+ case Symbol (var ):
146151 return env [var ]
147- case literal if not isinstance ( x , list ): # constant literal
148- return literal
149- case ['quote' , exp ]: # (quote exp)
152+ case []:
153+ return []
154+ case ['quote' , exp ]:
150155 return exp
151- case ['if' , test , conseq , alt ]: # (if test conseq alt)
156+ case ['if' , test , consequence , alternative ]:
152157 if evaluate (test , env ):
153- exp = conseq
158+ return evaluate ( consequence , env )
154159 else :
155- exp = alt
156- return evaluate (exp , env )
157- case ['lambda' , parms , body ]: # (lambda (parm...) body)
158- return Procedure (parms , body , env )
159- case ['define' , Symbol (var ), exp ]: # (define var exp)
160- env [var ] = evaluate (exp , env )
161- case ['define' , [name , * parms ], body ]: # (define (fun parm...) body)
160+ return evaluate (alternative , env )
161+ case ['define' , Symbol (var ), value_exp ]:
162+ env [var ] = evaluate (value_exp , env )
163+ case ['define' , [Symbol (name ), * parms ], body ]:
162164 env [name ] = Procedure (parms , body , env )
163- case [op , * args ]: # (proc arg...)
165+ case ['lambda' , [* parms ], body ]:
166+ return Procedure (parms , body , env )
167+ case [op , * args ]:
164168 proc = evaluate (op , env )
165- values = ( evaluate (arg , env ) for arg in args )
169+ values = [ evaluate (arg , env ) for arg in args ]
166170 return proc (* values )
171+ case _:
172+ raise SyntaxError (repr (exp ))
173+ # end::EVALUATE[]
174+
175+
176+ ################ non-interactive execution
177+
178+
179+ def run_lines (source : str ) -> Iterator [Any ]:
180+ global_env : Environment = standard_env ()
181+ tokens = tokenize (source )
182+ while tokens :
183+ exp = read_from_tokens (tokens )
184+ yield evaluate (exp , global_env )
185+
186+
187+ def run (source : str ) -> Any :
188+ for result in run_lines (source ):
189+ pass
190+ return result
0 commit comments