~stoq-dev/stoqnfe/master

« back to all changes in this revision

Viewing changes to external/mako/ast.py

  • Committer: georgeyk
  • Date: 2009-10-28 16:25:15 UTC
  • mfrom: (12.1.13 stoqnfe-danfe2)
  • Revision ID: georgeyk.dev@gmail.com-20091028162515-8ajv4cujuqnqa9k2
Initial implementation of the DANF-e report.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# ast.py
 
2
# Copyright (C) 2006, 2007 Michael Bayer mike_mp@zzzcomputing.com
 
3
#
 
4
# This module is part of Mako and is released under
 
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
 
6
 
 
7
"""utilities for analyzing expressions and blocks of Python code, as well as generating Python from AST nodes"""
 
8
 
 
9
from compiler import visitor
 
10
from compiler import parse as compiler_parse
 
11
from mako import util, exceptions
 
12
from StringIO import StringIO
 
13
import re
 
14
 
 
15
# pyflakes
 
16
if 0:
 
17
    __builtins__ = []
 
18
 
 
19
def parse(code, mode, lineno, pos, filename):
 
20
    try:
 
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)
 
24
 
 
25
class PythonCode(object):
 
26
    """represents information about a string containing Python code"""
 
27
    def __init__(self, code, lineno, pos, filename):
 
28
        self.code = code
 
29
 
 
30
        # represents all identifiers which are assigned to at some point in the code
 
31
        self.declared_identifiers = util.Set()
 
32
 
 
33
        # represents all identifiers which are referenced before their assignment, if any
 
34
        self.undeclared_identifiers = util.Set()
 
35
 
 
36
        # note that an identifier can be in both the undeclared and declared lists.
 
37
 
 
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)
 
44
        else:
 
45
            expr = code
 
46
 
 
47
        class FindIdentifiers(object):
 
48
            def __init__(self):
 
49
                self.in_function = False
 
50
                self.local_ident_stack = {}
 
51
            def _add_declared(s, name):
 
52
                if not s.in_function:
 
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)
 
62
                for n in node.nodes:
 
63
                    self.visit(n, *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"
 
70
                saved = {}
 
71
                inf = self.in_function
 
72
                self.in_function = True
 
73
                for arg in node.argnames:
 
74
                    if arg in self.local_ident_stack:
 
75
                        saved[arg] = True
 
76
                    else:
 
77
                        self.local_ident_stack[arg] = True
 
78
                for n in node.getChildNodes():
 
79
                    self.visit(n, *args)
 
80
                self.in_function = inf
 
81
                for arg in node.argnames:
 
82
                    if arg not in saved:
 
83
                        del self.local_ident_stack[arg]
 
84
            def visitFor(self, node, *args):
 
85
                # flip around visit
 
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:
 
94
                    if alias is not None:
 
95
                        self._add_declared(alias)
 
96
                    else:
 
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)
 
102
                    else:
 
103
                        if mod == '*':
 
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())
 
108
 
 
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):
 
112
        self.codeargs = []
 
113
        self.args = []
 
114
        self.declared_identifiers = util.Set()
 
115
        self.undeclared_identifiers = util.Set()
 
116
        class FindTuple(object):
 
117
            def visitTuple(s, node, *args):
 
118
                for n in node.nodes:
 
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
 
128
                code  += ","
 
129
            expr = parse(code, "exec", lineno, pos, filename)
 
130
        else:
 
131
            expr = code
 
132
 
 
133
        f = FindTuple()
 
134
        visitor.walk(expr, f)
 
135
 
 
136
class PythonFragment(PythonCode):
 
137
    """extends PythonCode to provide identifier lookups in partial control statements
 
138
 
 
139
    e.g.
 
140
        for x in 5:
 
141
        elif y==9:
 
142
        except (MyException, e):
 
143
    etc.
 
144
    """
 
145
    def __init__(self, code, lineno, pos, filename):
 
146
        m = re.match(r'^(\w+)(?:\s+(.*?))?:$', code.strip())
 
147
        if not m:
 
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']:
 
151
            code = code + "pass"
 
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"
 
158
        else:
 
159
            raise exceptions.CompileException("Unsupported control keyword: '%s'" % keyword, lineno, pos, filename)
 
160
        super(PythonFragment, self).__init__(code, lineno, pos, filename)
 
161
 
 
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)
 
167
 
 
168
class FunctionDecl(object):
 
169
    """function declaration"""
 
170
    def __init__(self, code, lineno, pos, filename, allow_kwargs=True):
 
171
        self.code = code
 
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
 
180
 
 
181
        f = ParseFunc()
 
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)
 
187
 
 
188
    def get_argument_expressions(self, include_defaults=True):
 
189
        """return the argument declarations of this FunctionDecl as a printable list."""
 
190
        namedecls = []
 
191
        defaults = [d for d in self.defaults]
 
192
        kwargs = self.kwargs
 
193
        varargs = self.varargs
 
194
        argnames = [f for f in self.argnames]
 
195
        argnames.reverse()
 
196
        for arg in argnames:
 
197
            default = None
 
198
            if kwargs:
 
199
                arg = "**" + arg
 
200
                kwargs = False
 
201
            elif varargs:
 
202
                arg = "*" + arg
 
203
                varargs = False
 
204
            else:
 
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()))
 
208
            else:
 
209
                namedecls.insert(0, arg)
 
210
        return namedecls
 
211
 
 
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)
 
216
 
 
217
 
 
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())
 
223
    def value(self):
 
224
        return self.buf.getvalue()
 
225
    def operator(self, op, node, *args):
 
226
        self.buf.write("(")
 
227
        self.visit(node.left, *args)
 
228
        self.buf.write(" %s " % op)
 
229
        self.visit(node.right, *args)
 
230
        self.buf.write(")")
 
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 + " ")
 
235
            self.visit(n, *args)
 
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)
 
271
        self.buf.write("[")
 
272
        [self.visit(x) for x in node.subs]
 
273
        self.buf.write("]")
 
274
    def visitUnarySub(self, node, *args):
 
275
        self.buf.write("-")
 
276
        self.visit(node.expr)
 
277
    def visitUnaryAdd(self, node, *args):
 
278
        self.buf.write("-")
 
279
        self.visit(node.expr)
 
280
    def visitSlice(self, node, *args):
 
281
        self.visit(node.expr)
 
282
        self.buf.write("[")
 
283
        if node.lower is not None:
 
284
            self.visit(node.lower)
 
285
        self.buf.write(":")
 
286
        if node.upper is not None:
 
287
            self.visit(node.upper)
 
288
        self.buf.write("]")
 
289
    def visitDict(self, node):
 
290
        self.buf.write("{")
 
291
        c = node.getChildren()
 
292
        for i in range(0, len(c), 2):
 
293
            self.visit(c[i])
 
294
            self.buf.write(": ")
 
295
            self.visit(c[i+1])
 
296
            if i<len(c) -2:
 
297
                self.buf.write(", ")
 
298
        self.buf.write("}")
 
299
    def visitTuple(self, node):
 
300
        self.buf.write("(")
 
301
        c = node.getChildren()
 
302
        for i in range(0, len(c)):
 
303
            self.visit(c[i])
 
304
            if i<len(c) - 1:
 
305
                self.buf.write(", ")
 
306
        self.buf.write(")")
 
307
    def visitList(self, node):
 
308
        self.buf.write("[")
 
309
        c = node.getChildren()
 
310
        for i in range(0, len(c)):
 
311
            self.visit(c[i])
 
312
            if i<len(c) - 1:
 
313
                self.buf.write(", ")
 
314
        self.buf.write("]")
 
315
    def visitListComp(self, node):
 
316
        self.buf.write("[")
 
317
        self.visit(node.expr)
 
318
        self.buf.write(" ")
 
319
        for n in node.quals:
 
320
            self.visit(n)
 
321
        self.buf.write("]")
 
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)
 
327
        for n in node.ifs:
 
328
            self.visit(n)
 
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)
 
334
        for tup in node.ops:
 
335
            self.buf.write(tup[0])
 
336
            self.visit(tup[1])
 
337
    def visitCallFunc(self, node, *args):
 
338
        self.visit(node.node)
 
339
        self.buf.write("(")
 
340
        if len(node.args):
 
341
            self.visit(node.args[0])
 
342
            for a in node.args[1:]:
 
343
                self.buf.write(", ")
 
344
                self.visit(a)
 
345
        self.buf.write(")")
 
346