2
# Copyright (C) 2006, 2007 Michael Bayer mike_mp@zzzcomputing.com
2
# Copyright (C) 2006, 2007, 2008 Michael Bayer mike_mp@zzzcomputing.com
4
4
# This module is part of Mako and is released under
5
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
7
7
"""utilities for analyzing expressions and blocks of Python code, as well as generating Python from AST nodes"""
9
from compiler import ast, visitor
10
from compiler import parse as compiler_parse
11
from mako import util, exceptions
12
from StringIO import StringIO
9
from mako import exceptions, pyparser, util
15
def parse(code, mode, **exception_kwargs):
17
return compiler_parse(code, mode)
18
except SyntaxError, e:
19
raise exceptions.SyntaxException("(%s) %s (%s)" % (e.__class__.__name__, str(e), repr(code[0:50])), **exception_kwargs)
21
12
class PythonCode(object):
22
13
"""represents information about a string containing Python code"""
23
14
def __init__(self, code, **exception_kwargs):
36
27
# - AST is less likely to break with version changes (for example, the behavior of co_names changed a little bit
37
28
# in python version 2.5)
38
29
if isinstance(code, basestring):
39
expr = parse(code.lstrip(), "exec", **exception_kwargs)
30
expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs)
43
class FindIdentifiers(object):
45
self.in_function = False
46
self.local_ident_stack = {}
47
def _add_declared(s, name):
49
self.declared_identifiers.add(name)
50
def visitClass(self, node, *args):
51
self._add_declared(node.name)
52
def visitAssName(self, node, *args):
53
self._add_declared(node.name)
54
def visitAssign(self, node, *args):
55
# flip around the visiting of Assign so the expression gets evaluated first,
56
# in the case of a clause like "x=x+5" (x is undeclared)
57
self.visit(node.expr, *args)
60
def visitFunction(self,node, *args):
61
self._add_declared(node.name)
62
# push function state onto stack. dont log any
63
# more identifiers as "declared" until outside of the function,
64
# but keep logging identifiers as "undeclared".
65
# track argument names in each function header so they arent counted as "undeclared"
67
inf = self.in_function
68
self.in_function = True
69
for arg in node.argnames:
70
if arg in self.local_ident_stack:
73
self.local_ident_stack[arg] = True
74
for n in node.getChildNodes():
76
self.in_function = inf
77
for arg in node.argnames:
79
del self.local_ident_stack[arg]
80
def visitFor(self, node, *args):
82
self.visit(node.list, *args)
83
self.visit(node.assign, *args)
84
self.visit(node.body, *args)
85
def visitName(s, node, *args):
86
if node.name not in __builtins__ and node.name not in self.declared_identifiers and node.name not in s.local_ident_stack:
87
self.undeclared_identifiers.add(node.name)
88
def visitImport(self, node, *args):
89
for (mod, alias) in node.names:
91
self._add_declared(alias)
93
self._add_declared(mod.split('.')[0])
94
def visitFrom(self, node, *args):
95
for (mod, alias) in node.names:
97
self._add_declared(alias)
100
raise exceptions.CompileException("'import *' is not supported, since all identifier names must be explicitly declared. Please use the form 'from <modulename> import <name1>, <name2>, ...' instead.", **exception_kwargs)
101
self._add_declared(mod)
102
f = FindIdentifiers()
103
visitor.walk(expr, f) #, walker=walker())
34
f = pyparser.FindIdentifiers(self, **exception_kwargs)
105
37
class ArgumentList(object):
106
38
"""parses a fragment of code as a comma-separated list of expressions"""
110
42
self.declared_identifiers = util.Set()
111
43
self.undeclared_identifiers = util.Set()
112
class FindTuple(object):
113
def visitTuple(s, node, *args):
115
p = PythonCode(n, **exception_kwargs)
116
self.codeargs.append(p)
117
self.args.append(ExpressionGenerator(n).value())
118
self.declared_identifiers = self.declared_identifiers.union(p.declared_identifiers)
119
self.undeclared_identifiers = self.undeclared_identifiers.union(p.undeclared_identifiers)
120
44
if isinstance(code, basestring):
121
45
if re.match(r"\S", code) and not re.match(r",\s*$", code):
122
46
# if theres text and no trailing comma, insure its parsed
123
47
# as a tuple by adding a trailing comma
125
expr = parse(code, "exec", **exception_kwargs)
49
expr = pyparser.parse(code, "exec", **exception_kwargs)
130
visitor.walk(expr, f)
53
f = pyparser.FindTuple(self, PythonCode, **exception_kwargs)
132
56
class PythonFragment(PythonCode):
133
57
"""extends PythonCode to provide identifier lookups in partial control statements
157
81
raise exceptions.CompileException("Unsupported control keyword: '%s'" % keyword, **exception_kwargs)
158
82
super(PythonFragment, self).__init__(code, **exception_kwargs)
160
class walker(visitor.ASTVisitor):
161
def dispatch(self, node, *args):
162
print "Node:", str(node)
163
#print "dir:", dir(node)
164
return visitor.ASTVisitor.dispatch(self, node, *args)
166
85
class FunctionDecl(object):
167
86
"""function declaration"""
168
87
def __init__(self, code, allow_kwargs=True, **exception_kwargs):
170
expr = parse(code, "exec", **exception_kwargs)
171
class ParseFunc(object):
172
def visitFunction(s, node, *args):
173
self.funcname = node.name
174
self.argnames = node.argnames
175
self.defaults = node.defaults
176
self.varargs = node.varargs
177
self.kwargs = node.kwargs
89
expr = pyparser.parse(code, "exec", **exception_kwargs)
180
visitor.walk(expr, f)
91
f = pyparser.ParseFunc(self, **exception_kwargs)
181
93
if not hasattr(self, 'funcname'):
182
94
raise exceptions.CompileException("Code '%s' is not a function declaration" % code, **exception_kwargs)
183
95
if not allow_kwargs and self.kwargs:
211
123
"""the argument portion of a function declaration"""
212
124
def __init__(self, code, **kwargs):
213
125
super(FunctionArgs, self).__init__("def ANON(%s):pass" % code, **kwargs)
216
class ExpressionGenerator(object):
217
"""given an AST node, generates an equivalent literal Python expression."""
218
def __init__(self, astnode):
219
self.buf = StringIO()
220
visitor.walk(astnode, self) #, walker=walker())
222
return self.buf.getvalue()
223
def operator(self, op, node, *args):
225
self.visit(node.left, *args)
226
self.buf.write(" %s " % op)
227
self.visit(node.right, *args)
229
def booleanop(self, op, node, *args):
230
self.visit(node.nodes[0])
231
for n in node.nodes[1:]:
232
self.buf.write(" " + op + " ")
234
def visitConst(self, node, *args):
235
self.buf.write(repr(node.value))
236
def visitAssName(self, node, *args):
237
# TODO: figure out OP_ASSIGN, other OP_s
238
self.buf.write(node.name)
239
def visitName(self, node, *args):
240
self.buf.write(node.name)
241
def visitMul(self, node, *args):
242
self.operator("*", node, *args)
243
def visitAnd(self, node, *args):
244
self.booleanop("and", node, *args)
245
def visitOr(self, node, *args):
246
self.booleanop("or", node, *args)
247
def visitBitand(self, node, *args):
248
self.booleanop("&", node, *args)
249
def visitBitor(self, node, *args):
250
self.booleanop("|", node, *args)
251
def visitBitxor(self, node, *args):
252
self.booleanop("^", node, *args)
253
def visitAdd(self, node, *args):
254
self.operator("+", node, *args)
255
def visitGetattr(self, node, *args):
256
self.visit(node.expr, *args)
257
self.buf.write(".%s" % node.attrname)
258
def visitSub(self, node, *args):
259
self.operator("-", node, *args)
260
def visitNot(self, node, *args):
261
self.buf.write("not ")
262
self.visit(node.expr)
263
def visitDiv(self, node, *args):
264
self.operator("/", node, *args)
265
def visitFloorDiv(self, node, *args):
266
self.operator("//", node, *args)
267
def visitSubscript(self, node, *args):
268
self.visit(node.expr)
270
[self.visit(x) for x in node.subs]
272
def visitUnarySub(self, node, *args):
274
self.visit(node.expr)
275
def visitUnaryAdd(self, node, *args):
277
self.visit(node.expr)
278
def visitSlice(self, node, *args):
279
self.visit(node.expr)
281
if node.lower is not None:
282
self.visit(node.lower)
284
if node.upper is not None:
285
self.visit(node.upper)
287
def visitDict(self, node):
289
c = node.getChildren()
290
for i in range(0, len(c), 2):
297
def visitTuple(self, node):
299
c = node.getChildren()
300
for i in range(0, len(c)):
305
def visitList(self, node):
307
c = node.getChildren()
308
for i in range(0, len(c)):
313
def visitListComp(self, node):
315
self.visit(node.expr)
320
def visitListCompFor(self, node):
321
self.buf.write(" for ")
322
self.visit(node.assign)
323
self.buf.write(" in ")
324
self.visit(node.list)
327
def visitListCompIf(self, node):
328
self.buf.write(" if ")
329
self.visit(node.test)
330
def visitCompare(self, node):
331
self.visit(node.expr)
333
self.buf.write(tup[0])
335
def visitCallFunc(self, node, *args):
336
self.visit(node.node)
339
self.visit(node.args[0])
340
for a in node.args[1:]:
b'\\ No newline at end of file'