Move evaluator nearer to bottom of source; feels better that way.
catseye
11 years ago
24 | 24 | if self.value is None: |
25 | 25 | return 'AST(%r,%r)' % (self.type, self.children) |
26 | 26 | 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 | |
27 | 290 | |
28 | 291 | |
29 | 292 | def eval_xoomonk(ast, state): |
63 | 326 | raise NotImplementedError, "not an AST type I know: %s" % type |
64 | 327 | |
65 | 328 | |
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 | ||
329 | 329 | def main(argv): |
330 | 330 | optparser = OptionParser(__doc__) |
331 | 331 | optparser.add_option("-a", "--show-ast", |