-
Notifications
You must be signed in to change notification settings - Fork 0
/
scheme_reader.py
215 lines (183 loc) · 6.23 KB
/
scheme_reader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
"""This module implements the built-in data types of the Scheme language, along
with a parser for Scheme expressions.
In addition to the types defined in this file, some data types in Scheme are
represented by their corresponding type in Python:
number: int or float
symbol: string
boolean: bool
unspecified: None
The __repr__ method of a Scheme value will return a Python expression that
would be evaluated to the value, where possible.
The __str__ method of a Scheme value will return a Scheme expression that
would be read to the value, where possible.
"""
from ucb import main, trace, interact
from scheme_tokens import tokenize_lines, DELIMITERS
from buffer import Buffer, InputReader, LineReader
# Pairs and Scheme lists
class Pair:
"""A pair has two instance attributes: first and second. For a Pair to be
a well-formed list, second is either a well-formed list or nil. Some
methods only apply to well-formed lists.
>>> s = Pair(1, Pair(2, nil))
>>> s
Pair(1, Pair(2, nil))
>>> print(s)
(1 2)
>>> len(s)
2
>>> s[1]
2
>>> print(s.map(lambda x: x+4))
(5 6)
"""
def __init__(self, first, second):
self.first = first
self.second = second
def __repr__(self):
return "Pair({0}, {1})".format(repr(self.first), repr(self.second))
def __str__(self):
s = "(" + str(self.first)
second = self.second
while isinstance(second, Pair):
s += " " + str(second.first)
second = second.second
if second is not nil:
s += " . " + str(second)
return s + ")"
def __len__(self):
n, second = 1, self.second
while isinstance(second, Pair):
n += 1
second = second.second
if second is not nil:
raise TypeError("length attempted on improper list")
return n
def __getitem__(self, k):
if k < 0:
raise IndexError("negative index into list")
y = self
for _ in range(k):
if y.second is nil:
raise IndexError("list index out of bounds")
elif not isinstance(y.second, Pair):
raise TypeError("ill-formed list")
y = y.second
return y.first
def __eq__(self, p):
if not isinstance(p, Pair):
return False
return self.first == p.first and self.second == p.second
def map(self, fn):
"""Return a Scheme list after mapping Python function FN to SELF."""
mapped = fn(self.first)
if self.second is nil or isinstance(self.second, Pair):
return Pair(mapped, self.second.map(fn))
else:
raise TypeError("ill-formed list")
class nil:
"""The empty list"""
def __repr__(self):
return "nil"
def __str__(self):
return "()"
def __len__(self):
return 0
def __getitem__(self, k):
if k < 0:
raise IndexError("negative index into list")
raise IndexError("list index out of bounds")
def map(self, fn):
return self
nil = nil() # Assignment hides the nil class; there is only one instance
# Scheme list parser
def scheme_read(src):
"""Read the next expression from SRC, a Buffer of tokens.
>>> lines = ["(+ 1 ", "(+ 23 4)) ("]
>>> src = Buffer(tokenize_lines(lines))
>>> print(scheme_read(src))
(+ 1 (+ 23 4))
>>> read_line("'hello")
Pair('quote', Pair('hello', nil))
>>> print(read_line("(car '(1 2))"))
(car (quote (1 2)))
"""
if src.current() is None:
raise EOFError
val = src.pop()
if val == "nil":
return nil
elif val not in DELIMITERS:
return val
elif val == "'":
return Pair('quote', Pair(scheme_read(src), nil))
elif val == "(":
return read_tail(src)
else:
raise SyntaxError("unexpected token: {0}".format(val))
def read_tail(src):
"""Return the remainder of a list in SRC, starting before an element or ).
>>> read_tail(Buffer(tokenize_lines([")"])))
nil
>>> read_tail(Buffer(tokenize_lines(["2 3)"])))
Pair(2, Pair(3, nil))
>>> read_tail(Buffer(tokenize_lines(["2 (3 4))"])))
Pair(2, Pair(Pair(3, Pair(4, nil)), nil))
>>> read_line("(1 . 2)")
Pair(1, 2)
>>> read_line("(1 2 . 3)")
Pair(1, Pair(2, 3))
>>> read_line("(1 . 2 3)")
Traceback (most recent call last):
...
SyntaxError: Expected one element after .
>>> scheme_read(Buffer(tokenize_lines(["(1", "2 .", "'(3 4))", "4"])))
Pair(1, Pair(2, Pair('quote', Pair(Pair(3, Pair(4, nil)), nil))))
"""
try:
if src.current() is None:
raise SyntaxError("unexpected end of file")
if src.current() == ")":
src.pop()
return nil
if src.current() == ".":
src.pop()
next = scheme_read(src)
if src.current() == ")":
src.pop()
return next
else:
raise SyntaxError('unexpected token {0}'.format(src.current()))
first = scheme_read(src)
rest = read_tail(src)
return Pair(first, rest)
except EOFError:
raise SyntaxError("unexpected end of file")
# Convenience methods
def buffer_input(prompt="scm> "):
"""Return a Buffer instance containing interactive input."""
return Buffer(tokenize_lines(InputReader(prompt)))
def buffer_lines(lines, prompt="scm> ", show_prompt=False):
"""Return a Buffer instance iterating through LINES."""
if show_prompt:
input_lines = lines
else:
input_lines = LineReader(lines, prompt)
return Buffer(tokenize_lines(input_lines))
def read_line(line):
"""Read a single string LINE as a Scheme expression."""
return scheme_read(Buffer(tokenize_lines([line])))
# Interactive loop
@main
def read_print_loop():
"""Run a read-print loop for Scheme expressions."""
while True:
try:
src = buffer_input("read> ")
while src.more_on_line:
expression = scheme_read(src)
print(expression)
except (SyntaxError, ValueError) as err:
print(type(err).__name__ + ":", err)
except (KeyboardInterrupt, EOFError): # <Control>-D, etc.
return