git @ Cat's Eye Technologies Exanoke / e004345
Move script from script/ to src/ --HG-- rename : script/exanoke => src/exanoke.py Chris Pressey 7 years ago
3 changed file(s) with 384 addition(s) and 384 deletion(s). Raw diff Collapse all Expand all
170170 -> Tests for functionality "Evaluate Exanoke program"
171171
172172 -> Functionality "Evaluate Exanoke program" is implemented by
173 -> shell command "script/exanoke %(test-body-file)"
173 -> shell command "src/exanoke.py %(test-body-file)"
174174
175175 `cons` can be used to make lists and trees and things.
176176
+0
-383
script/exanoke less more
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)