2
# Copyright (C) 2006, 2007 Michael Bayer mike_mp@zzzcomputing.com
4
# This module is part of Mako and is released under
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
7
"""utilities for analyzing expressions and blocks of Python code, as well as generating Python from AST nodes"""
9
from compiler import visitor
10
from compiler import parse as compiler_parse
11
from mako import util, exceptions
12
from StringIO import StringIO
19
def parse(code, mode, lineno, pos, filename):
21
return compiler_parse(code, mode)
22
except SyntaxError, e:
23
raise exceptions.SyntaxException("(%s) %s (%s)" % (e.__class__.__name__, str(e), repr(code[0:50])), lineno, pos, filename)
25
class PythonCode(object):
26
"""represents information about a string containing Python code"""
27
def __init__(self, code, lineno, pos, filename):
30
# represents all identifiers which are assigned to at some point in the code
31
self.declared_identifiers = util.Set()
33
# represents all identifiers which are referenced before their assignment, if any
34
self.undeclared_identifiers = util.Set()
36
# note that an identifier can be in both the undeclared and declared lists.
38
# using AST to parse instead of using code.co_varnames, code.co_names has several advantages:
39
# - we can locate an identifier as "undeclared" even if its declared later in the same block of code
40
# - AST is less likely to break with version changes (for example, the behavior of co_names changed a little bit
41
# in python version 2.5)
42
if isinstance(code, basestring):
43
expr = parse(code.lstrip(), "exec", lineno, pos, filename)
47
class FindIdentifiers(object):
49
self.in_function = False
50
self.local_ident_stack = {}
51
def _add_declared(s, name):
53
self.declared_identifiers.add(name)
54
def visitClass(self, node, *args):
55
self._add_declared(node.name)
56
def visitAssName(self, node, *args):
57
self._add_declared(node.name)
58
def visitAssign(self, node, *args):
59
# flip around the visiting of Assign so the expression gets evaluated first,
60
# in the case of a clause like "x=x+5" (x is undeclared)
61
self.visit(node.expr, *args)
64
def visitFunction(self,node, *args):
65
self._add_declared(node.name)
66
# push function state onto stack. dont log any
67
# more identifiers as "declared" until outside of the function,
68
# but keep logging identifiers as "undeclared".
69
# track argument names in each function header so they arent counted as "undeclared"
71
inf = self.in_function
72
self.in_function = True
73
for arg in node.argnames:
74
if arg in self.local_ident_stack:
77
self.local_ident_stack[arg] = True
78
for n in node.getChildNodes():
80
self.in_function = inf
81
for arg in node.argnames:
83
del self.local_ident_stack[arg]
84
def visitFor(self, node, *args):
86
self.visit(node.list, *args)
87
self.visit(node.assign, *args)
88
self.visit(node.body, *args)
89
def visitName(s, node, *args):
90
if node.name not in __builtins__ and node.name not in self.declared_identifiers and node.name not in s.local_ident_stack:
91
self.undeclared_identifiers.add(node.name)
92
def visitImport(self, node, *args):
93
for (mod, alias) in node.names:
95
self._add_declared(alias)
97
self._add_declared(mod.split('.')[0])
98
def visitFrom(self, node, *args):
99
for (mod, alias) in node.names:
100
if alias is not None:
101
self._add_declared(alias)
104
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.", lineno, pos, filename)
105
self._add_declared(mod)
106
f = FindIdentifiers()
107
visitor.walk(expr, f) #, walker=walker())
109
class ArgumentList(object):
110
"""parses a fragment of code as a comma-separated list of expressions"""
111
def __init__(self, code, lineno, pos, filename):
114
self.declared_identifiers = util.Set()
115
self.undeclared_identifiers = util.Set()
116
class FindTuple(object):
117
def visitTuple(s, node, *args):
119
p = PythonCode(n, lineno, pos, filename)
120
self.codeargs.append(p)
121
self.args.append(ExpressionGenerator(n).value())
122
self.declared_identifiers = self.declared_identifiers.union(p.declared_identifiers)
123
self.undeclared_identifiers = self.undeclared_identifiers.union(p.undeclared_identifiers)
124
if isinstance(code, basestring):
125
if re.match(r"\S", code) and not re.match(r",\s*$", code):
126
# if theres text and no trailing comma, insure its parsed
127
# as a tuple by adding a trailing comma
129
expr = parse(code, "exec", lineno, pos, filename)
134
visitor.walk(expr, f)
136
class PythonFragment(PythonCode):
137
"""extends PythonCode to provide identifier lookups in partial control statements
142
except (MyException, e):
145
def __init__(self, code, lineno, pos, filename):
146
m = re.match(r'^(\w+)(?:\s+(.*?))?:$', code.strip())
148
raise exceptions.CompileException("Fragment '%s' is not a partial control statement" % code, lineno, pos, filename)
149
(keyword, expr) = m.group(1,2)
150
if keyword in ['for','if', 'while']:
152
elif keyword == 'try':
153
code = code + "pass\nexcept:pass"
154
elif keyword == 'elif' or keyword == 'else':
155
code = "if False:pass\n" + code + "pass"
156
elif keyword == 'except':
157
code = "try:pass\n" + code + "pass"
159
raise exceptions.CompileException("Unsupported control keyword: '%s'" % keyword, lineno, pos, filename)
160
super(PythonFragment, self).__init__(code, lineno, pos, filename)
162
class walker(visitor.ASTVisitor):
163
def dispatch(self, node, *args):
164
print "Node:", str(node)
165
#print "dir:", dir(node)
166
return visitor.ASTVisitor.dispatch(self, node, *args)
168
class FunctionDecl(object):
169
"""function declaration"""
170
def __init__(self, code, lineno, pos, filename, allow_kwargs=True):
172
expr = parse(code, "exec", lineno, pos, filename)
173
class ParseFunc(object):
174
def visitFunction(s, node, *args):
175
self.funcname = node.name
176
self.argnames = node.argnames
177
self.defaults = node.defaults
178
self.varargs = node.varargs
179
self.kwargs = node.kwargs
182
visitor.walk(expr, f)
183
if not hasattr(self, 'funcname'):
184
raise exceptions.CompileException("Code '%s' is not a function declaration" % code, lineno, pos, filename)
185
if not allow_kwargs and self.kwargs:
186
raise exceptions.CompileException("'**%s' keyword argument not allowed here" % self.argnames[-1], lineno, pos, filename)
188
def get_argument_expressions(self, include_defaults=True):
189
"""return the argument declarations of this FunctionDecl as a printable list."""
191
defaults = [d for d in self.defaults]
193
varargs = self.varargs
194
argnames = [f for f in self.argnames]
205
default = len(defaults) and defaults.pop() or None
206
if include_defaults and default:
207
namedecls.insert(0, "%s=%s" % (arg, ExpressionGenerator(default).value()))
209
namedecls.insert(0, arg)
212
class FunctionArgs(FunctionDecl):
213
"""the argument portion of a function declaration"""
214
def __init__(self, code, lineno, pos, filename, **kwargs):
215
super(FunctionArgs, self).__init__("def ANON(%s):pass" % code, lineno, pos, filename, **kwargs)
218
class ExpressionGenerator(object):
219
"""given an AST node, generates an equivalent literal Python expression."""
220
def __init__(self, astnode):
221
self.buf = StringIO()
222
visitor.walk(astnode, self) #, walker=walker())
224
return self.buf.getvalue()
225
def operator(self, op, node, *args):
227
self.visit(node.left, *args)
228
self.buf.write(" %s " % op)
229
self.visit(node.right, *args)
231
def booleanop(self, op, node, *args):
232
self.visit(node.nodes[0])
233
for n in node.nodes[1:]:
234
self.buf.write(" " + op + " ")
236
def visitConst(self, node, *args):
237
self.buf.write(repr(node.value))
238
def visitAssName(self, node, *args):
239
# TODO: figure out OP_ASSIGN, other OP_s
240
self.buf.write(node.name)
241
def visitName(self, node, *args):
242
self.buf.write(node.name)
243
def visitMul(self, node, *args):
244
self.operator("*", node, *args)
245
def visitAnd(self, node, *args):
246
self.booleanop("and", node, *args)
247
def visitOr(self, node, *args):
248
self.booleanop("or", node, *args)
249
def visitBitand(self, node, *args):
250
self.booleanop("&", node, *args)
251
def visitBitor(self, node, *args):
252
self.booleanop("|", node, *args)
253
def visitBitxor(self, node, *args):
254
self.booleanop("^", node, *args)
255
def visitAdd(self, node, *args):
256
self.operator("+", node, *args)
257
def visitGetattr(self, node, *args):
258
self.visit(node.expr, *args)
259
self.buf.write(".%s" % node.attrname)
260
def visitSub(self, node, *args):
261
self.operator("-", node, *args)
262
def visitNot(self, node, *args):
263
self.buf.write("not ")
264
self.visit(node.expr)
265
def visitDiv(self, node, *args):
266
self.operator("/", node, *args)
267
def visitFloorDiv(self, node, *args):
268
self.operator("//", node, *args)
269
def visitSubscript(self, node, *args):
270
self.visit(node.expr)
272
[self.visit(x) for x in node.subs]
274
def visitUnarySub(self, node, *args):
276
self.visit(node.expr)
277
def visitUnaryAdd(self, node, *args):
279
self.visit(node.expr)
280
def visitSlice(self, node, *args):
281
self.visit(node.expr)
283
if node.lower is not None:
284
self.visit(node.lower)
286
if node.upper is not None:
287
self.visit(node.upper)
289
def visitDict(self, node):
291
c = node.getChildren()
292
for i in range(0, len(c), 2):
299
def visitTuple(self, node):
301
c = node.getChildren()
302
for i in range(0, len(c)):
307
def visitList(self, node):
309
c = node.getChildren()
310
for i in range(0, len(c)):
315
def visitListComp(self, node):
317
self.visit(node.expr)
322
def visitListCompFor(self, node):
323
self.buf.write(" for ")
324
self.visit(node.assign)
325
self.buf.write(" in ")
326
self.visit(node.list)
329
def visitListCompIf(self, node):
330
self.buf.write(" if ")
331
self.visit(node.test)
332
def visitCompare(self, node):
333
self.visit(node.expr)
335
self.buf.write(tup[0])
337
def visitCallFunc(self, node, *args):
338
self.visit(node.node)
341
self.visit(node.args[0])
342
for a in node.args[1:]: