Move script from script/ to src/
--HG--
rename : script/exanoke => src/exanoke.py
Chris Pressey
10 years ago
170 | 170 | -> Tests for functionality "Evaluate Exanoke program" |
171 | 171 | |
172 | 172 | -> Functionality "Evaluate Exanoke program" is implemented by |
173 | -> shell command "script/exanoke %(test-body-file)" | |
173 | -> shell command "src/exanoke.py %(test-body-file)" | |
174 | 174 | |
175 | 175 | `cons` can be used to make lists and trees and things. |
176 | 176 |
0 | #!/usr/bin/env python | |
1 | ||
2 | # Exanoke reference interpreter | |
3 | # ... implemented in a Questionable Manner | |
4 | ||
5 | from optparse import OptionParser | |
6 | import re | |
7 | import sys | |
8 | ||
9 | ||
10 | class AST(object): | |
11 | def __init__(self, type, children=None, value=None): | |
12 | self.type = type | |
13 | self.value = value | |
14 | if children is not None: | |
15 | self.children = children | |
16 | else: | |
17 | self.children = [] | |
18 | ||
19 | def add_child(self, item): | |
20 | self.children.append(item) | |
21 | ||
22 | def __repr__(self): | |
23 | if self.value is None: | |
24 | return 'AST(%r,%r)' % (self.type, self.children) | |
25 | if not self.children: | |
26 | return 'AST(%r,value=%r)' % (self.type, self.value) | |
27 | return 'AST(%r,%r,value=%r)' % (self.type, self.children, self.value) | |
28 | ||
29 | ||
30 | class Scanner(object): | |
31 | def __init__(self, text): | |
32 | self.text = text | |
33 | self.token = None | |
34 | self.type = None | |
35 | self.scan() | |
36 | ||
37 | def scan_pattern(self, pattern, type, token_group=1, rest_group=2): | |
38 | pattern = r'^(' + pattern + r')(.*?)$' | |
39 | match = re.match(pattern, self.text, re.DOTALL) | |
40 | if not match: | |
41 | return False | |
42 | else: | |
43 | self.type = type | |
44 | self.token = match.group(token_group) | |
45 | self.text = match.group(rest_group) | |
46 | #print >>sys.stderr, "(%r/%s->%r)" % (self.token, self.type, self.text) | |
47 | return True | |
48 | ||
49 | def scan(self): | |
50 | self.scan_pattern(r'[ \t\r\n]*', 'whitespace') | |
51 | if not self.text: | |
52 | self.token = None | |
53 | self.type = 'EOF' | |
54 | return | |
55 | if self.scan_pattern(r'\(|\)|\,|\#', 'goose egg'): | |
56 | return | |
57 | if self.scan_pattern(r':[a-zA-Z]+', 'atom'): | |
58 | return | |
59 | if self.scan_pattern(r'[a-zA-Z]+\??', 'identifier'): | |
60 | return | |
61 | if self.scan_pattern(r'\<[a-z]+', 'smallifier'): | |
62 | return | |
63 | if self.scan_pattern(r'.', 'unknown character'): | |
64 | return | |
65 | else: | |
66 | raise ValueError, "this should never happen, self.text=(%s)" % self.text | |
67 | ||
68 | def expect(self, token): | |
69 | if self.token == token: | |
70 | self.scan() | |
71 | else: | |
72 | raise SyntaxError("Expected '%s', but found '%s'" % | |
73 | (token, self.token)) | |
74 | ||
75 | def expect_type(self, type): | |
76 | self.check_type(type) | |
77 | self.scan() | |
78 | ||
79 | def on(self, token): | |
80 | return self.token == token | |
81 | ||
82 | def on_type(self, type): | |
83 | return self.type == type | |
84 | ||
85 | def check_type(self, type): | |
86 | if not self.type == type: | |
87 | raise SyntaxError("Expected %s, but found %s ('%s')" % | |
88 | (type, self.type, self.token)) | |
89 | ||
90 | def consume(self, token): | |
91 | if self.token == token: | |
92 | self.scan() | |
93 | return True | |
94 | else: | |
95 | return False | |
96 | ||
97 | ||
98 | class Parser(object): | |
99 | def __init__(self, text): | |
100 | self.scanner = Scanner(text) | |
101 | self.defining = None | |
102 | self.self_arity = None | |
103 | self.defined = {} | |
104 | self.formals = None | |
105 | ||
106 | def program(self): | |
107 | fundefs = [] | |
108 | while self.scanner.on('def'): | |
109 | fundefs.append(self.fundef()) | |
110 | self.defining = None | |
111 | expr = self.expr() | |
112 | return AST('Program', [expr] + fundefs) | |
113 | ||
114 | def ident(self): | |
115 | self.scanner.check_type('identifier') | |
116 | name = self.scanner.token | |
117 | self.scanner.scan() | |
118 | return name | |
119 | ||
120 | def fundef(self): | |
121 | self.scanner.expect('def') | |
122 | name = self.ident() | |
123 | if name in self.defined: | |
124 | raise SyntaxError('Function "%s" already defined' % name) | |
125 | args = [] | |
126 | self.scanner.expect('(') | |
127 | self.scanner.expect('#') | |
128 | self.formals = {} | |
129 | i = 1 | |
130 | while self.scanner.consume(','): | |
131 | ident = self.ident() | |
132 | args.append(ident) | |
133 | self.formals[ident] = i | |
134 | i += 1 | |
135 | self.scanner.expect(')') | |
136 | self.defining = name | |
137 | self.self_arity = len(args) + 1 | |
138 | expr = self.expr() | |
139 | self.defining = None | |
140 | self.self_arity = None | |
141 | self.formals = None | |
142 | self.defined[name] = len(args) + 1 | |
143 | return AST('FunDef', [expr] + args, value=name) | |
144 | ||
145 | def expr(self): | |
146 | if self.scanner.consume("cons"): | |
147 | self.scanner.expect("(") | |
148 | e1 = self.expr() | |
149 | self.scanner.expect(",") | |
150 | e2 = self.expr() | |
151 | self.scanner.expect(")") | |
152 | return AST('Cons', [e1, e2]) | |
153 | elif self.scanner.consume("head"): | |
154 | self.scanner.expect("(") | |
155 | e1 = self.expr() | |
156 | self.scanner.expect(")") | |
157 | return AST('Head', [e1]) | |
158 | elif self.scanner.consume("tail"): | |
159 | self.scanner.expect("(") | |
160 | e1 = self.expr() | |
161 | self.scanner.expect(")") | |
162 | return AST('Tail', [e1]) | |
163 | elif self.scanner.consume("if"): | |
164 | e1 = self.expr() | |
165 | self.scanner.expect("then") | |
166 | e2 = self.expr() | |
167 | self.scanner.expect("else") | |
168 | e3 = self.expr() | |
169 | return AST('If', [e1, e2, e3]) | |
170 | elif self.scanner.consume("self"): | |
171 | if self.defining is None: | |
172 | raise SyntaxError('Use of "self" outside of a function body') | |
173 | args = [] | |
174 | self.scanner.expect("(") | |
175 | e1 = self.smaller() | |
176 | args.append(e1) | |
177 | while self.scanner.consume(','): | |
178 | args.append(self.expr()) | |
179 | if len(args) != self.self_arity: | |
180 | raise SyntaxError('Arity mismatch on self (expected %d, got %d)' % | |
181 | (self.self_arity, len(args))) | |
182 | self.scanner.expect(")") | |
183 | return AST('Call', args, value=self.defining) | |
184 | elif self.scanner.consume("eq?"): | |
185 | self.scanner.expect("(") | |
186 | e1 = self.expr() | |
187 | self.scanner.expect(",") | |
188 | e2 = self.expr() | |
189 | self.scanner.expect(")") | |
190 | return AST('Eq?', [e1, e2]) | |
191 | elif self.scanner.consume("cons?"): | |
192 | self.scanner.expect("(") | |
193 | e1 = self.expr() | |
194 | self.scanner.expect(")") | |
195 | return AST('Cons?', [e1]) | |
196 | elif self.scanner.consume("not"): | |
197 | self.scanner.expect("(") | |
198 | e1 = self.expr() | |
199 | self.scanner.expect(")") | |
200 | return AST('Not', [e1]) | |
201 | elif self.scanner.consume("#"): | |
202 | if self.defining is None: | |
203 | raise SyntaxError('Use of "#" outside of a function body') | |
204 | return AST('ArgRef', value=0) | |
205 | elif self.scanner.on_type("atom"): | |
206 | atom = self.scanner.token | |
207 | self.scanner.scan() | |
208 | return AST('Atom', value=atom) | |
209 | elif self.scanner.on_type("identifier"): | |
210 | ident = self.scanner.token | |
211 | self.scanner.scan() | |
212 | if self.scanner.on('('): | |
213 | self.scanner.expect('(') | |
214 | if ident not in self.defined: | |
215 | raise SyntaxError('Undefined function "%s"' % ident) | |
216 | args = [self.expr()] | |
217 | while self.scanner.consume(','): | |
218 | args.append(self.expr()) | |
219 | if len(args) != self.defined[ident]: | |
220 | raise SyntaxError('Arity mismatch (expected %d, got %d)' % | |
221 | (self.defined[ident], len(args))) | |
222 | self.scanner.expect(')') | |
223 | return AST('Call', args, value=ident) | |
224 | else: | |
225 | if self.defining is None: | |
226 | raise SyntaxError('Reference to argument "%s" ' | |
227 | 'outside of a function body' % ident) | |
228 | if ident not in self.formals: | |
229 | raise SyntaxError('Undefined argument "%s"' % ident) | |
230 | return AST('ArgRef', value=self.formals[ident]) | |
231 | else: | |
232 | return self.smaller() | |
233 | ||
234 | def smaller(self): | |
235 | if self.scanner.consume("<head"): | |
236 | e1 = self.smallerterm() | |
237 | return AST('Head', [e1]) | |
238 | elif self.scanner.consume("<tail"): | |
239 | e1 = self.smallerterm() | |
240 | return AST('Tail', [e1]) | |
241 | elif self.scanner.consume("<if"): | |
242 | e1 = self.expr() | |
243 | self.scanner.expect("then") | |
244 | e2 = self.smallerterm() | |
245 | self.scanner.expect("else") | |
246 | e3 = self.smallerterm() | |
247 | return AST('If', [e1, e2, e3]) | |
248 | else: | |
249 | raise SyntaxError('Expected <smaller>, found "%s"' % | |
250 | self.scanner.token) | |
251 | ||
252 | def smallerterm(self): | |
253 | if self.scanner.consume("#"): | |
254 | return AST('ArgRef', value=0) | |
255 | else: | |
256 | return self.smaller() | |
257 | ||
258 | ||
259 | ### Runtime ### | |
260 | ||
261 | class Cons(object): | |
262 | def __init__(self, head, tail): | |
263 | self.head = head | |
264 | self.tail = tail | |
265 | ||
266 | def __str__(self): | |
267 | return "(%s %s)" % (self.head, self.tail) | |
268 | ||
269 | def __repr__(self): | |
270 | return "Cons(%r,%r)" % (self.head, self.tail) | |
271 | ||
272 | ||
273 | class Evaluator(object): | |
274 | def __init__(self, ast): | |
275 | self.fundefs = {} | |
276 | for fundef in ast.children[1:]: | |
277 | self.fundefs[fundef.value] = { | |
278 | 'expr': fundef.children[0], | |
279 | 'args': fundef.children[1:], | |
280 | } | |
281 | self.bindings = [] | |
282 | ||
283 | def eval(self, ast): | |
284 | if ast.type == 'Atom': | |
285 | return ast.value | |
286 | elif ast.type == 'Cons': | |
287 | v1 = self.eval(ast.children[0]) | |
288 | v2 = self.eval(ast.children[1]) | |
289 | return Cons(v1, v2) | |
290 | elif ast.type == 'Head': | |
291 | v1 = self.eval(ast.children[0]) | |
292 | try: | |
293 | return v1.head | |
294 | except AttributeError: | |
295 | raise TypeError("head: Not a cons cell") | |
296 | elif ast.type == 'Tail': | |
297 | v1 = self.eval(ast.children[0]) | |
298 | try: | |
299 | return v1.tail | |
300 | except AttributeError: | |
301 | raise TypeError("tail: Not a cons cell") | |
302 | elif ast.type == 'If': | |
303 | v1 = self.eval(ast.children[0]) | |
304 | if v1 == ':true': | |
305 | return self.eval(ast.children[1]) | |
306 | else: | |
307 | return self.eval(ast.children[2]) | |
308 | elif ast.type == 'Eq?': | |
309 | v1 = self.eval(ast.children[0]) | |
310 | v2 = self.eval(ast.children[1]) | |
311 | if v1 == v2: | |
312 | return ':true' | |
313 | else: | |
314 | return ':false' | |
315 | elif ast.type == 'Cons?': | |
316 | v1 = self.eval(ast.children[0]) | |
317 | if isinstance(v1, Cons): | |
318 | return ':true' | |
319 | else: | |
320 | return ':false' | |
321 | elif ast.type == 'Not': | |
322 | v1 = self.eval(ast.children[0]) | |
323 | if v1 == ':true': | |
324 | return ':false' | |
325 | else: | |
326 | return ':true' | |
327 | elif ast.type == 'Call': | |
328 | fun = self.fundefs[ast.value] | |
329 | bindings = self.bindings | |
330 | self.bindings = [self.eval(expr) for expr in ast.children] | |
331 | result = self.eval(fun['expr']) | |
332 | self.bindings = bindings | |
333 | return result | |
334 | elif ast.type == 'ArgRef': | |
335 | return self.bindings[ast.value] | |
336 | elif ast.type == 'Program': | |
337 | return self.eval(ast.children[0]) | |
338 | else: | |
339 | raise NotImplementedError("%s" % ast) | |
340 | ||
341 | ||
342 | def main(argv): | |
343 | optparser = OptionParser(__doc__) | |
344 | optparser.add_option("-a", "--show-ast", | |
345 | action="store_true", dest="show_ast", default=False, | |
346 | help="show parsed AST instead of evaluating") | |
347 | optparser.add_option("-t", "--test", | |
348 | action="store_true", dest="test", default=False, | |
349 | help="run test cases and exit") | |
350 | (options, args) = optparser.parse_args(argv[1:]) | |
351 | if options.test: | |
352 | import doctest | |
353 | (fails, something) = doctest.testmod() | |
354 | if fails == 0: | |
355 | print "All tests passed." | |
356 | sys.exit(0) | |
357 | else: | |
358 | sys.exit(1) | |
359 | file = open(args[0]) | |
360 | text = file.read() | |
361 | file.close() | |
362 | p = Parser(text) | |
363 | try: | |
364 | prog = p.program() | |
365 | except SyntaxError as e: | |
366 | print >>sys.stderr, str(e) | |
367 | sys.exit(1) | |
368 | if options.show_ast: | |
369 | from pprint import pprint | |
370 | pprint(prog) | |
371 | sys.exit(0) | |
372 | try: | |
373 | ev = Evaluator(prog) | |
374 | print str(ev.eval(prog)) | |
375 | except TypeError as e: | |
376 | print >>sys.stderr, str(e) | |
377 | sys.exit(1) | |
378 | sys.exit(0) | |
379 | ||
380 | ||
381 | if __name__ == "__main__": | |
382 | main(sys.argv) |
0 | #!/usr/bin/env python | |
1 | ||
2 | # Exanoke reference interpreter | |
3 | # ... implemented in a Questionable Manner | |
4 | ||
5 | from optparse import OptionParser | |
6 | import re | |
7 | import sys | |
8 | ||
9 | ||
10 | class AST(object): | |
11 | def __init__(self, type, children=None, value=None): | |
12 | self.type = type | |
13 | self.value = value | |
14 | if children is not None: | |
15 | self.children = children | |
16 | else: | |
17 | self.children = [] | |
18 | ||
19 | def add_child(self, item): | |
20 | self.children.append(item) | |
21 | ||
22 | def __repr__(self): | |
23 | if self.value is None: | |
24 | return 'AST(%r,%r)' % (self.type, self.children) | |
25 | if not self.children: | |
26 | return 'AST(%r,value=%r)' % (self.type, self.value) | |
27 | return 'AST(%r,%r,value=%r)' % (self.type, self.children, self.value) | |
28 | ||
29 | ||
30 | class Scanner(object): | |
31 | def __init__(self, text): | |
32 | self.text = text | |
33 | self.token = None | |
34 | self.type = None | |
35 | self.scan() | |
36 | ||
37 | def scan_pattern(self, pattern, type, token_group=1, rest_group=2): | |
38 | pattern = r'^(' + pattern + r')(.*?)$' | |
39 | match = re.match(pattern, self.text, re.DOTALL) | |
40 | if not match: | |
41 | return False | |
42 | else: | |
43 | self.type = type | |
44 | self.token = match.group(token_group) | |
45 | self.text = match.group(rest_group) | |
46 | #print >>sys.stderr, "(%r/%s->%r)" % (self.token, self.type, self.text) | |
47 | return True | |
48 | ||
49 | def scan(self): | |
50 | self.scan_pattern(r'[ \t\r\n]*', 'whitespace') | |
51 | if not self.text: | |
52 | self.token = None | |
53 | self.type = 'EOF' | |
54 | return | |
55 | if self.scan_pattern(r'\(|\)|\,|\#', 'goose egg'): | |
56 | return | |
57 | if self.scan_pattern(r':[a-zA-Z]+', 'atom'): | |
58 | return | |
59 | if self.scan_pattern(r'[a-zA-Z]+\??', 'identifier'): | |
60 | return | |
61 | if self.scan_pattern(r'\<[a-z]+', 'smallifier'): | |
62 | return | |
63 | if self.scan_pattern(r'.', 'unknown character'): | |
64 | return | |
65 | else: | |
66 | raise ValueError, "this should never happen, self.text=(%s)" % self.text | |
67 | ||
68 | def expect(self, token): | |
69 | if self.token == token: | |
70 | self.scan() | |
71 | else: | |
72 | raise SyntaxError("Expected '%s', but found '%s'" % | |
73 | (token, self.token)) | |
74 | ||
75 | def expect_type(self, type): | |
76 | self.check_type(type) | |
77 | self.scan() | |
78 | ||
79 | def on(self, token): | |
80 | return self.token == token | |
81 | ||
82 | def on_type(self, type): | |
83 | return self.type == type | |
84 | ||
85 | def check_type(self, type): | |
86 | if not self.type == type: | |
87 | raise SyntaxError("Expected %s, but found %s ('%s')" % | |
88 | (type, self.type, self.token)) | |
89 | ||
90 | def consume(self, token): | |
91 | if self.token == token: | |
92 | self.scan() | |
93 | return True | |
94 | else: | |
95 | return False | |
96 | ||
97 | ||
98 | class Parser(object): | |
99 | def __init__(self, text): | |
100 | self.scanner = Scanner(text) | |
101 | self.defining = None | |
102 | self.self_arity = None | |
103 | self.defined = {} | |
104 | self.formals = None | |
105 | ||
106 | def program(self): | |
107 | fundefs = [] | |
108 | while self.scanner.on('def'): | |
109 | fundefs.append(self.fundef()) | |
110 | self.defining = None | |
111 | expr = self.expr() | |
112 | return AST('Program', [expr] + fundefs) | |
113 | ||
114 | def ident(self): | |
115 | self.scanner.check_type('identifier') | |
116 | name = self.scanner.token | |
117 | self.scanner.scan() | |
118 | return name | |
119 | ||
120 | def fundef(self): | |
121 | self.scanner.expect('def') | |
122 | name = self.ident() | |
123 | if name in self.defined: | |
124 | raise SyntaxError('Function "%s" already defined' % name) | |
125 | args = [] | |
126 | self.scanner.expect('(') | |
127 | self.scanner.expect('#') | |
128 | self.formals = {} | |
129 | i = 1 | |
130 | while self.scanner.consume(','): | |
131 | ident = self.ident() | |
132 | args.append(ident) | |
133 | self.formals[ident] = i | |
134 | i += 1 | |
135 | self.scanner.expect(')') | |
136 | self.defining = name | |
137 | self.self_arity = len(args) + 1 | |
138 | expr = self.expr() | |
139 | self.defining = None | |
140 | self.self_arity = None | |
141 | self.formals = None | |
142 | self.defined[name] = len(args) + 1 | |
143 | return AST('FunDef', [expr] + args, value=name) | |
144 | ||
145 | def expr(self): | |
146 | if self.scanner.consume("cons"): | |
147 | self.scanner.expect("(") | |
148 | e1 = self.expr() | |
149 | self.scanner.expect(",") | |
150 | e2 = self.expr() | |
151 | self.scanner.expect(")") | |
152 | return AST('Cons', [e1, e2]) | |
153 | elif self.scanner.consume("head"): | |
154 | self.scanner.expect("(") | |
155 | e1 = self.expr() | |
156 | self.scanner.expect(")") | |
157 | return AST('Head', [e1]) | |
158 | elif self.scanner.consume("tail"): | |
159 | self.scanner.expect("(") | |
160 | e1 = self.expr() | |
161 | self.scanner.expect(")") | |
162 | return AST('Tail', [e1]) | |
163 | elif self.scanner.consume("if"): | |
164 | e1 = self.expr() | |
165 | self.scanner.expect("then") | |
166 | e2 = self.expr() | |
167 | self.scanner.expect("else") | |
168 | e3 = self.expr() | |
169 | return AST('If', [e1, e2, e3]) | |
170 | elif self.scanner.consume("self"): | |
171 | if self.defining is None: | |
172 | raise SyntaxError('Use of "self" outside of a function body') | |
173 | args = [] | |
174 | self.scanner.expect("(") | |
175 | e1 = self.smaller() | |
176 | args.append(e1) | |
177 | while self.scanner.consume(','): | |
178 | args.append(self.expr()) | |
179 | if len(args) != self.self_arity: | |
180 | raise SyntaxError('Arity mismatch on self (expected %d, got %d)' % | |
181 | (self.self_arity, len(args))) | |
182 | self.scanner.expect(")") | |
183 | return AST('Call', args, value=self.defining) | |
184 | elif self.scanner.consume("eq?"): | |
185 | self.scanner.expect("(") | |
186 | e1 = self.expr() | |
187 | self.scanner.expect(",") | |
188 | e2 = self.expr() | |
189 | self.scanner.expect(")") | |
190 | return AST('Eq?', [e1, e2]) | |
191 | elif self.scanner.consume("cons?"): | |
192 | self.scanner.expect("(") | |
193 | e1 = self.expr() | |
194 | self.scanner.expect(")") | |
195 | return AST('Cons?', [e1]) | |
196 | elif self.scanner.consume("not"): | |
197 | self.scanner.expect("(") | |
198 | e1 = self.expr() | |
199 | self.scanner.expect(")") | |
200 | return AST('Not', [e1]) | |
201 | elif self.scanner.consume("#"): | |
202 | if self.defining is None: | |
203 | raise SyntaxError('Use of "#" outside of a function body') | |
204 | return AST('ArgRef', value=0) | |
205 | elif self.scanner.on_type("atom"): | |
206 | atom = self.scanner.token | |
207 | self.scanner.scan() | |
208 | return AST('Atom', value=atom) | |
209 | elif self.scanner.on_type("identifier"): | |
210 | ident = self.scanner.token | |
211 | self.scanner.scan() | |
212 | if self.scanner.on('('): | |
213 | self.scanner.expect('(') | |
214 | if ident not in self.defined: | |
215 | raise SyntaxError('Undefined function "%s"' % ident) | |
216 | args = [self.expr()] | |
217 | while self.scanner.consume(','): | |
218 | args.append(self.expr()) | |
219 | if len(args) != self.defined[ident]: | |
220 | raise SyntaxError('Arity mismatch (expected %d, got %d)' % | |
221 | (self.defined[ident], len(args))) | |
222 | self.scanner.expect(')') | |
223 | return AST('Call', args, value=ident) | |
224 | else: | |
225 | if self.defining is None: | |
226 | raise SyntaxError('Reference to argument "%s" ' | |
227 | 'outside of a function body' % ident) | |
228 | if ident not in self.formals: | |
229 | raise SyntaxError('Undefined argument "%s"' % ident) | |
230 | return AST('ArgRef', value=self.formals[ident]) | |
231 | else: | |
232 | return self.smaller() | |
233 | ||
234 | def smaller(self): | |
235 | if self.scanner.consume("<head"): | |
236 | e1 = self.smallerterm() | |
237 | return AST('Head', [e1]) | |
238 | elif self.scanner.consume("<tail"): | |
239 | e1 = self.smallerterm() | |
240 | return AST('Tail', [e1]) | |
241 | elif self.scanner.consume("<if"): | |
242 | e1 = self.expr() | |
243 | self.scanner.expect("then") | |
244 | e2 = self.smallerterm() | |
245 | self.scanner.expect("else") | |
246 | e3 = self.smallerterm() | |
247 | return AST('If', [e1, e2, e3]) | |
248 | else: | |
249 | raise SyntaxError('Expected <smaller>, found "%s"' % | |
250 | self.scanner.token) | |
251 | ||
252 | def smallerterm(self): | |
253 | if self.scanner.consume("#"): | |
254 | return AST('ArgRef', value=0) | |
255 | else: | |
256 | return self.smaller() | |
257 | ||
258 | ||
259 | ### Runtime ### | |
260 | ||
261 | class Cons(object): | |
262 | def __init__(self, head, tail): | |
263 | self.head = head | |
264 | self.tail = tail | |
265 | ||
266 | def __str__(self): | |
267 | return "(%s %s)" % (self.head, self.tail) | |
268 | ||
269 | def __repr__(self): | |
270 | return "Cons(%r,%r)" % (self.head, self.tail) | |
271 | ||
272 | ||
273 | class Evaluator(object): | |
274 | def __init__(self, ast): | |
275 | self.fundefs = {} | |
276 | for fundef in ast.children[1:]: | |
277 | self.fundefs[fundef.value] = { | |
278 | 'expr': fundef.children[0], | |
279 | 'args': fundef.children[1:], | |
280 | } | |
281 | self.bindings = [] | |
282 | ||
283 | def eval(self, ast): | |
284 | if ast.type == 'Atom': | |
285 | return ast.value | |
286 | elif ast.type == 'Cons': | |
287 | v1 = self.eval(ast.children[0]) | |
288 | v2 = self.eval(ast.children[1]) | |
289 | return Cons(v1, v2) | |
290 | elif ast.type == 'Head': | |
291 | v1 = self.eval(ast.children[0]) | |
292 | try: | |
293 | return v1.head | |
294 | except AttributeError: | |
295 | raise TypeError("head: Not a cons cell") | |
296 | elif ast.type == 'Tail': | |
297 | v1 = self.eval(ast.children[0]) | |
298 | try: | |
299 | return v1.tail | |
300 | except AttributeError: | |
301 | raise TypeError("tail: Not a cons cell") | |
302 | elif ast.type == 'If': | |
303 | v1 = self.eval(ast.children[0]) | |
304 | if v1 == ':true': | |
305 | return self.eval(ast.children[1]) | |
306 | else: | |
307 | return self.eval(ast.children[2]) | |
308 | elif ast.type == 'Eq?': | |
309 | v1 = self.eval(ast.children[0]) | |
310 | v2 = self.eval(ast.children[1]) | |
311 | if v1 == v2: | |
312 | return ':true' | |
313 | else: | |
314 | return ':false' | |
315 | elif ast.type == 'Cons?': | |
316 | v1 = self.eval(ast.children[0]) | |
317 | if isinstance(v1, Cons): | |
318 | return ':true' | |
319 | else: | |
320 | return ':false' | |
321 | elif ast.type == 'Not': | |
322 | v1 = self.eval(ast.children[0]) | |
323 | if v1 == ':true': | |
324 | return ':false' | |
325 | else: | |
326 | return ':true' | |
327 | elif ast.type == 'Call': | |
328 | fun = self.fundefs[ast.value] | |
329 | bindings = self.bindings | |
330 | self.bindings = [self.eval(expr) for expr in ast.children] | |
331 | result = self.eval(fun['expr']) | |
332 | self.bindings = bindings | |
333 | return result | |
334 | elif ast.type == 'ArgRef': | |
335 | return self.bindings[ast.value] | |
336 | elif ast.type == 'Program': | |
337 | return self.eval(ast.children[0]) | |
338 | else: | |
339 | raise NotImplementedError("%s" % ast) | |
340 | ||
341 | ||
342 | def main(argv): | |
343 | optparser = OptionParser(__doc__) | |
344 | optparser.add_option("-a", "--show-ast", | |
345 | action="store_true", dest="show_ast", default=False, | |
346 | help="show parsed AST instead of evaluating") | |
347 | optparser.add_option("-t", "--test", | |
348 | action="store_true", dest="test", default=False, | |
349 | help="run test cases and exit") | |
350 | (options, args) = optparser.parse_args(argv[1:]) | |
351 | if options.test: | |
352 | import doctest | |
353 | (fails, something) = doctest.testmod() | |
354 | if fails == 0: | |
355 | print "All tests passed." | |
356 | sys.exit(0) | |
357 | else: | |
358 | sys.exit(1) | |
359 | file = open(args[0]) | |
360 | text = file.read() | |
361 | file.close() | |
362 | p = Parser(text) | |
363 | try: | |
364 | prog = p.program() | |
365 | except SyntaxError as e: | |
366 | print >>sys.stderr, str(e) | |
367 | sys.exit(1) | |
368 | if options.show_ast: | |
369 | from pprint import pprint | |
370 | pprint(prog) | |
371 | sys.exit(0) | |
372 | try: | |
373 | ev = Evaluator(prog) | |
374 | print str(ev.eval(prog)) | |
375 | except TypeError as e: | |
376 | print >>sys.stderr, str(e) | |
377 | sys.exit(1) | |
378 | sys.exit(0) | |
379 | ||
380 | ||
381 | if __name__ == "__main__": | |
382 | main(sys.argv) |