git @ Cat's Eye Technologies Xoomonk / 8c8dc25
Move evaluator nearer to bottom of source; feels better that way. catseye 11 years ago
1 changed file(s) with 263 addition(s) and 263 deletion(s). Raw diff Collapse all Expand all
2424 if self.value is None:
2525 return 'AST(%r,%r)' % (self.type, self.children)
2626 return 'AST(%r,value=%r)' % (self.type, self.value)
27
28
29 class Scanner(object):
30 """A Scanner provides facilities for extracting successive
31 Xoomonk tokens from a string.
32
33 >>> a = Scanner(" {:= } foo ")
34 >>> a.token
35 '{'
36 >>> a.type
37 'operator'
38 >>> a.scan()
39 >>> a.on(":=")
40 True
41 >>> a.on_type('operator')
42 True
43 >>> a.check_type('identifier')
44 Traceback (most recent call last):
45 ...
46 SyntaxError: Expected identifier, but found operator (':=')
47 >>> a.scan()
48 >>> a.consume(".")
49 False
50 >>> a.consume("}")
51 True
52 >>> a.expect("foo")
53 >>> a.type
54 'EOF'
55 >>> a.expect("bar")
56 Traceback (most recent call last):
57 ...
58 SyntaxError: Expected 'bar', but found 'None'
59
60 """
61 def __init__(self, text):
62 self.text = text
63 self.token = None
64 self.type = None
65 self.scan()
66
67 def scan_pattern(self, pattern, type, token_group=1, rest_group=2):
68 pattern = r'^(' + pattern + r')(.*?)$'
69 match = re.match(pattern, self.text, re.DOTALL)
70 if not match:
71 return False
72 else:
73 self.type = type
74 self.token = match.group(token_group)
75 self.text = match.group(rest_group)
76 #print >>sys.stderr, "(%r/%s->%r)" % (self.token, self.type, self.text)
77 return True
78
79 def scan(self):
80 self.scan_pattern(r'[ \t\n\r]*', 'whitespace')
81 if not self.text:
82 self.token = None
83 self.type = 'EOF'
84 return
85 if self.scan_pattern(r':=|\;|\{|\}|\*|\.|\^|\$', 'operator'):
86 return
87 if self.scan_pattern(r'\d+', 'integer literal'):
88 return
89 if self.scan_pattern(r'\"(.*?)\"', 'string literal',
90 token_group=2, rest_group=3):
91 return
92 if self.scan_pattern(r'\w+', 'identifier'):
93 return
94 if self.scan_pattern(r'.', 'unknown character'):
95 return
96 else:
97 raise ValueError, "this should never happen, self.text=(%s)" % self.text
98
99 def expect(self, token):
100 if self.token == token:
101 self.scan()
102 else:
103 raise SyntaxError("Expected '%s', but found '%s'" %
104 (token, self.token))
105
106 def on(self, token):
107 return self.token == token
108
109 def on_type(self, type):
110 return self.type == type
111
112 def check_type(self, type):
113 if not self.type == type:
114 raise SyntaxError("Expected %s, but found %s ('%s')" %
115 (type, self.type, self.token))
116
117 def consume(self, token):
118 if self.token == token:
119 self.scan()
120 return True
121 else:
122 return False
123
124
125 # Parser
126
127 class Parser(object):
128 """A Parser provides facilities for recognizing various
129 parts of a Xoomonk program based on Xoomonk's grammar.
130
131 >>> a = Parser("123")
132 >>> a.expr()
133 AST('IntLit',value=123)
134 >>> a = Parser("{ a := 123 }")
135 >>> a.expr()
136 AST('Block',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('IntLit',value=123)])])
137
138 >>> a = Parser("a:=5 c:=4")
139 >>> a.program()
140 AST('Program',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('IntLit',value=5)]), AST('Assignment',[AST('Ref',[AST('Identifier',value='c')]), AST('IntLit',value=4)])])
141
142 >>> a = Parser("a := { b := 1 }")
143 >>> a.program()
144 AST('Program',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('Block',[AST('Assignment',[AST('Ref',[AST('Identifier',value='b')]), AST('IntLit',value=1)])])])])
145
146 """
147 def __init__(self, text):
148 self.scanner = Scanner(text)
149
150 def program(self):
151 p = AST('Program')
152 while self.scanner.type != 'EOF':
153 p.add_child(self.stmt())
154 return p
155
156 def stmt(self):
157 if self.scanner.on("print"):
158 return self.print_stmt()
159 else:
160 return self.assign()
161
162 def assign(self):
163 r = self.ref()
164 self.scanner.expect(":=")
165 e = self.expr()
166 return AST('Assignment', [r, e])
167
168 def print_stmt(self):
169 s = None
170 self.scanner.expect("print")
171 if self.scanner.consume("string"):
172 self.scanner.check_type("string literal")
173 st = self.scanner.token
174 self.scanner.scan()
175 s = AST('PrintString', value=st)
176 elif self.scanner.consume("char"):
177 e = self.expr()
178 s = AST('PrintChar', [e])
179 else:
180 e = self.expr()
181 s = AST('Print', [e])
182 newline = True
183 if self.scanner.consume(";"):
184 newline = False
185 if newline:
186 s = AST('Newline', [s])
187 return s
188
189 def expr(self):
190 v = None
191 if self.scanner.on("{"):
192 v = self.block()
193 elif self.scanner.on_type('integer literal'):
194 v = AST('IntLit', value=int(self.scanner.token))
195 self.scanner.scan()
196 else:
197 v = self.ref()
198 if self.scanner.consume("*"):
199 v = AST('CopyOf', [v])
200 return v
201
202 def block(self):
203 b = AST('Block')
204 self.scanner.expect("{")
205 while not self.scanner.on("}"):
206 b.add_child(self.stmt())
207 self.scanner.expect("}")
208 return b
209
210 def ref(self):
211 r = AST('Ref')
212 r.add_child(self.name())
213 while self.scanner.consume("."):
214 r.add_child(self.name())
215 return r
216
217 def name(self):
218 if self.scanner.consume("^"):
219 return AST('Upvalue')
220 elif self.scanner.consume("$"):
221 self.scanner.check_type("identifier")
222 id = self.scanner.token
223 self.scanner.scan()
224 return AST('Dollar', value=id)
225 else:
226 self.scanner.check_type("identifier")
227 id = self.scanner.token
228 self.scanner.scan()
229 return AST('Identifier', value=id)
230
231
232 # Runtime support for Xoomonk.
233
234 def demo(store):
235 print "demo!"
236
237 class MalingeringStore(object):
238 """
239 >>> a = MalingeringStore(['a','b'], [], demo)
240 demo!
241 >>> a['a'] = 7
242 >>> print a['a']
243 7
244 >>> a['c'] = 7
245 Traceback (most recent call last):
246 ...
247 ValueError: Attempt to assign undefined variable c
248 >>> a = MalingeringStore(['a','b'], ['a'], demo)
249 >>> a['a'] = 7
250 demo!
251 >>> a = MalingeringStore(['a','b'], ['a'], demo)
252 >>> a['b'] = 7
253 Traceback (most recent call last):
254 ...
255 ValueError: Attempt to assign unresolved variable b
256
257 """
258 def __init__(self, variables, unassigned, fun):
259 self.dict = {}
260 self.variables = variables
261 for variable in self.variables:
262 self.dict[variable] = 0
263 self.unassigned = unassigned
264 self.fun = fun
265 if not self.unassigned:
266 self.run()
267
268 def run(self):
269 self.fun(self)
270
271 def __getitem__(self, name):
272 if name not in self.variables:
273 raise ValueError("Attempt to access undefined variable %s" % name)
274 # check to see if it is unassigned or derived
275 return self.dict[name]
276
277 def __setitem__(self, name, value):
278 if name not in self.variables:
279 raise ValueError("Attempt to assign undefined variable %s" % name)
280 if name in self.unassigned:
281 self.dict[name] = value
282 self.unassigned.remove(name)
283 if not self.unassigned:
284 self.run()
285 elif self.unassigned:
286 raise ValueError("Attempt to assign unresolved variable %s" % name)
287 else:
288 # the store is saturated, do what you want
289 self.dict[name] = value
27290
28291
29292 def eval_xoomonk(ast, state):
63326 raise NotImplementedError, "not an AST type I know: %s" % type
64327
65328
66 class Scanner(object):
67 """A Scanner provides facilities for extracting successive
68 Xoomonk tokens from a string.
69
70 >>> a = Scanner(" {:= } foo ")
71 >>> a.token
72 '{'
73 >>> a.type
74 'operator'
75 >>> a.scan()
76 >>> a.on(":=")
77 True
78 >>> a.on_type('operator')
79 True
80 >>> a.check_type('identifier')
81 Traceback (most recent call last):
82 ...
83 SyntaxError: Expected identifier, but found operator (':=')
84 >>> a.scan()
85 >>> a.consume(".")
86 False
87 >>> a.consume("}")
88 True
89 >>> a.expect("foo")
90 >>> a.type
91 'EOF'
92 >>> a.expect("bar")
93 Traceback (most recent call last):
94 ...
95 SyntaxError: Expected 'bar', but found 'None'
96
97 """
98 def __init__(self, text):
99 self.text = text
100 self.token = None
101 self.type = None
102 self.scan()
103
104 def scan_pattern(self, pattern, type, token_group=1, rest_group=2):
105 pattern = r'^(' + pattern + r')(.*?)$'
106 match = re.match(pattern, self.text, re.DOTALL)
107 if not match:
108 return False
109 else:
110 self.type = type
111 self.token = match.group(token_group)
112 self.text = match.group(rest_group)
113 #print >>sys.stderr, "(%r/%s->%r)" % (self.token, self.type, self.text)
114 return True
115
116 def scan(self):
117 self.scan_pattern(r'[ \t\n\r]*', 'whitespace')
118 if not self.text:
119 self.token = None
120 self.type = 'EOF'
121 return
122 if self.scan_pattern(r':=|\;|\{|\}|\*|\.|\^|\$', 'operator'):
123 return
124 if self.scan_pattern(r'\d+', 'integer literal'):
125 return
126 if self.scan_pattern(r'\"(.*?)\"', 'string literal',
127 token_group=2, rest_group=3):
128 return
129 if self.scan_pattern(r'\w+', 'identifier'):
130 return
131 if self.scan_pattern(r'.', 'unknown character'):
132 return
133 else:
134 raise ValueError, "this should never happen, self.text=(%s)" % self.text
135
136 def expect(self, token):
137 if self.token == token:
138 self.scan()
139 else:
140 raise SyntaxError("Expected '%s', but found '%s'" %
141 (token, self.token))
142
143 def on(self, token):
144 return self.token == token
145
146 def on_type(self, type):
147 return self.type == type
148
149 def check_type(self, type):
150 if not self.type == type:
151 raise SyntaxError("Expected %s, but found %s ('%s')" %
152 (type, self.type, self.token))
153
154 def consume(self, token):
155 if self.token == token:
156 self.scan()
157 return True
158 else:
159 return False
160
161
162 # Parser
163
164 class Parser(object):
165 """A Parser provides facilities for recognizing various
166 parts of a Xoomonk program based on Xoomonk's grammar.
167
168 >>> a = Parser("123")
169 >>> a.expr()
170 AST('IntLit',value=123)
171 >>> a = Parser("{ a := 123 }")
172 >>> a.expr()
173 AST('Block',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('IntLit',value=123)])])
174
175 >>> a = Parser("a:=5 c:=4")
176 >>> a.program()
177 AST('Program',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('IntLit',value=5)]), AST('Assignment',[AST('Ref',[AST('Identifier',value='c')]), AST('IntLit',value=4)])])
178
179 >>> a = Parser("a := { b := 1 }")
180 >>> a.program()
181 AST('Program',[AST('Assignment',[AST('Ref',[AST('Identifier',value='a')]), AST('Block',[AST('Assignment',[AST('Ref',[AST('Identifier',value='b')]), AST('IntLit',value=1)])])])])
182
183 """
184 def __init__(self, text):
185 self.scanner = Scanner(text)
186
187 def program(self):
188 p = AST('Program')
189 while self.scanner.type != 'EOF':
190 p.add_child(self.stmt())
191 return p
192
193 def stmt(self):
194 if self.scanner.on("print"):
195 return self.print_stmt()
196 else:
197 return self.assign()
198
199 def assign(self):
200 r = self.ref()
201 self.scanner.expect(":=")
202 e = self.expr()
203 return AST('Assignment', [r, e])
204
205 def print_stmt(self):
206 s = None
207 self.scanner.expect("print")
208 if self.scanner.consume("string"):
209 self.scanner.check_type("string literal")
210 st = self.scanner.token
211 self.scanner.scan()
212 s = AST('PrintString', value=st)
213 elif self.scanner.consume("char"):
214 e = self.expr()
215 s = AST('PrintChar', [e])
216 else:
217 e = self.expr()
218 s = AST('Print', [e])
219 newline = True
220 if self.scanner.consume(";"):
221 newline = False
222 if newline:
223 s = AST('Newline', [s])
224 return s
225
226 def expr(self):
227 v = None
228 if self.scanner.on("{"):
229 v = self.block()
230 elif self.scanner.on_type('integer literal'):
231 v = AST('IntLit', value=int(self.scanner.token))
232 self.scanner.scan()
233 else:
234 v = self.ref()
235 if self.scanner.consume("*"):
236 v = AST('CopyOf', [v])
237 return v
238
239 def block(self):
240 b = AST('Block')
241 self.scanner.expect("{")
242 while not self.scanner.on("}"):
243 b.add_child(self.stmt())
244 self.scanner.expect("}")
245 return b
246
247 def ref(self):
248 r = AST('Ref')
249 r.add_child(self.name())
250 while self.scanner.consume("."):
251 r.add_child(self.name())
252 return r
253
254 def name(self):
255 if self.scanner.consume("^"):
256 return AST('Upvalue')
257 elif self.scanner.consume("$"):
258 self.scanner.check_type("identifier")
259 id = self.scanner.token
260 self.scanner.scan()
261 return AST('Dollar', value=id)
262 else:
263 self.scanner.check_type("identifier")
264 id = self.scanner.token
265 self.scanner.scan()
266 return AST('Identifier', value=id)
267
268
269 # Runtime support for Xoomonk.
270
271 def demo(store):
272 print "demo!"
273
274 class MalingeringStore(object):
275 """
276 >>> a = MalingeringStore(['a','b'], [], demo)
277 demo!
278 >>> a['a'] = 7
279 >>> print a['a']
280 7
281 >>> a['c'] = 7
282 Traceback (most recent call last):
283 ...
284 ValueError: Attempt to assign undefined variable c
285 >>> a = MalingeringStore(['a','b'], ['a'], demo)
286 >>> a['a'] = 7
287 demo!
288 >>> a = MalingeringStore(['a','b'], ['a'], demo)
289 >>> a['b'] = 7
290 Traceback (most recent call last):
291 ...
292 ValueError: Attempt to assign unresolved variable b
293
294 """
295 def __init__(self, variables, unassigned, fun):
296 self.dict = {}
297 self.variables = variables
298 for variable in self.variables:
299 self.dict[variable] = 0
300 self.unassigned = unassigned
301 self.fun = fun
302 if not self.unassigned:
303 self.run()
304
305 def run(self):
306 self.fun(self)
307
308 def __getitem__(self, name):
309 if name not in self.variables:
310 raise ValueError("Attempt to access undefined variable %s" % name)
311 # check to see if it is unassigned or derived
312 return self.dict[name]
313
314 def __setitem__(self, name, value):
315 if name not in self.variables:
316 raise ValueError("Attempt to assign undefined variable %s" % name)
317 if name in self.unassigned:
318 self.dict[name] = value
319 self.unassigned.remove(name)
320 if not self.unassigned:
321 self.run()
322 elif self.unassigned:
323 raise ValueError("Attempt to assign unresolved variable %s" % name)
324 else:
325 # the store is saturated, do what you want
326 self.dict[name] = value
327
328
329329 def main(argv):
330330 optparser = OptionParser(__doc__)
331331 optparser.add_option("-a", "--show-ast",