forked from timkchan/scheme
-
Notifications
You must be signed in to change notification settings - Fork 0
/
scheme_reader.py
200 lines (170 loc) · 5.84 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
"""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(object):
"""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)
>>> 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 __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(object):
"""The empty list"""
def __repr__(self):
return "nil"
def __str__(self):
return "()"
def __len__(self):
return 0
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 == "'":
# BEGIN Question 1
return Pair('quote', Pair(scheme_read(src),nil))
# END Question 1
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")
elif src.current() == ")":
src.pop()
return nil
elif src.current() == ".":
# BEGIN Question 2
src.pop()
temp = scheme_read(src)
if src.pop() == ")":
return temp
else:
raise SyntaxError("Expected one element after .")
# END Question 2
else:
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("str :", expression)
print("repr:", repr(expression))
except (SyntaxError, ValueError) as err:
print(type(err).__name__ + ":", err)
except (KeyboardInterrupt, EOFError): # <Control>-D, etc.
print()
return