1
- ################ Lispy: Scheme Interpreter in Python 3.9
1
+ ################ Lispy: Scheme Interpreter in Python 3.10
2
2
3
3
## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html
4
4
## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021)
5
5
## by Luciano Ramalho, adding type hints and pattern matching.
6
6
7
- ################ Imports and Types
7
+ ################ imports and types
8
8
9
9
import math
10
10
import operator as op
11
11
from collections import ChainMap
12
- from collections .abc import MutableMapping
12
+ from collections .abc import MutableMapping , Iterator
13
+ from itertools import chain
13
14
from typing import Any , TypeAlias
14
15
15
16
Symbol : TypeAlias = str
@@ -23,41 +24,44 @@ class Procedure:
23
24
"A user-defined Scheme procedure."
24
25
25
26
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
27
30
28
31
def __call__ (self , * args : Expression ) -> Any :
29
32
local_env = dict (zip (self .parms , args ))
30
33
env : Environment = ChainMap (local_env , self .env )
31
34
return evaluate (self .body , env )
32
35
33
36
34
- ################ Global Environment
37
+ ################ global environment
35
38
36
39
37
40
def standard_env () -> Environment :
38
41
"An environment with some Scheme standard procedures."
39
42
env : Environment = {}
40
43
env .update (vars (math )) # sin, cos, sqrt, pi, ...
41
- env .update (
42
- {
44
+ env .update ({
43
45
'+' : op .add ,
44
46
'-' : op .sub ,
45
47
'*' : op .mul ,
46
48
'/' : op .truediv ,
49
+ '//' : op .floordiv ,
47
50
'>' : op .gt ,
48
51
'<' : op .lt ,
49
52
'>=' : op .ge ,
50
53
'<=' : op .le ,
51
54
'=' : op .eq ,
52
55
'abs' : abs ,
53
- 'append' : op . add ,
56
+ 'append' : lambda * args : list ( chain ( * args )) ,
54
57
'apply' : lambda proc , args : proc (* args ),
55
58
'begin' : lambda * x : x [- 1 ],
56
59
'car' : lambda x : x [0 ],
57
60
'cdr' : lambda x : x [1 :],
58
61
'cons' : lambda x , y : [x ] + y ,
59
62
'eq?' : op .is_ ,
60
63
'equal?' : op .eq ,
64
+ 'filter' : lambda * args : list (filter (* args )),
61
65
'length' : len ,
62
66
'list' : lambda * x : list (x ),
63
67
'list?' : lambda x : isinstance (x , list ),
@@ -70,12 +74,11 @@ def standard_env() -> Environment:
70
74
'procedure?' : callable ,
71
75
'round' : round ,
72
76
'symbol?' : lambda x : isinstance (x , Symbol ),
73
- }
74
- )
77
+ })
75
78
return env
76
79
77
80
78
- ################ Parsing: parse, tokenize, and read_from_tokens
81
+ ################ parse, tokenize, and read_from_tokens
79
82
80
83
81
84
def parse (program : str ) -> Expression :
@@ -94,11 +97,11 @@ def read_from_tokens(tokens: list[str]) -> Expression:
94
97
raise SyntaxError ('unexpected EOF while reading' )
95
98
token = tokens .pop (0 )
96
99
if '(' == token :
97
- L = []
100
+ exp = []
98
101
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
102
105
elif ')' == token :
103
106
raise SyntaxError ('unexpected )' )
104
107
else :
@@ -116,7 +119,7 @@ def parse_atom(token: str) -> Atom:
116
119
return Symbol (token )
117
120
118
121
119
- ################ Interaction: A REPL
122
+ ################ interaction: a REPL
120
123
121
124
122
125
def repl (prompt : str = 'lis.py> ' ) -> None :
@@ -138,29 +141,50 @@ def lispstr(exp: object) -> str:
138
141
139
142
################ eval
140
143
141
-
142
- def evaluate (x : Expression , env : Environment ) -> Any :
144
+ # tag::EVALUATE[]
145
+ def evaluate (exp : Expression , env : Environment ) -> Any :
143
146
"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 ):
146
151
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 ]:
150
155
return exp
151
- case ['if' , test , conseq , alt ]: # (if test conseq alt)
156
+ case ['if' , test , consequence , alternative ]:
152
157
if evaluate (test , env ):
153
- exp = conseq
158
+ return evaluate ( consequence , env )
154
159
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 ]:
162
164
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 ]:
164
168
proc = evaluate (op , env )
165
- values = ( evaluate (arg , env ) for arg in args )
169
+ values = [ evaluate (arg , env ) for arg in args ]
166
170
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