1 | # ast.py |
---|
2 | # Copyright (C) 2006, 2007, 2008, 2009 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 mako import exceptions, pyparser, util |
---|
10 | import re |
---|
11 | |
---|
12 | class PythonCode(object): |
---|
13 | """represents information about a string containing Python code""" |
---|
14 | def __init__(self, code, **exception_kwargs): |
---|
15 | self.code = code |
---|
16 | |
---|
17 | # represents all identifiers which are assigned to at some point in the code |
---|
18 | self.declared_identifiers = util.Set() |
---|
19 | |
---|
20 | # represents all identifiers which are referenced before their assignment, if any |
---|
21 | self.undeclared_identifiers = util.Set() |
---|
22 | |
---|
23 | # note that an identifier can be in both the undeclared and declared lists. |
---|
24 | |
---|
25 | # using AST to parse instead of using code.co_varnames, code.co_names has several advantages: |
---|
26 | # - we can locate an identifier as "undeclared" even if its declared later in the same block of code |
---|
27 | # - AST is less likely to break with version changes (for example, the behavior of co_names changed a little bit |
---|
28 | # in python version 2.5) |
---|
29 | if isinstance(code, basestring): |
---|
30 | expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs) |
---|
31 | else: |
---|
32 | expr = code |
---|
33 | |
---|
34 | f = pyparser.FindIdentifiers(self, **exception_kwargs) |
---|
35 | f.visit(expr) |
---|
36 | |
---|
37 | class ArgumentList(object): |
---|
38 | """parses a fragment of code as a comma-separated list of expressions""" |
---|
39 | def __init__(self, code, **exception_kwargs): |
---|
40 | self.codeargs = [] |
---|
41 | self.args = [] |
---|
42 | self.declared_identifiers = util.Set() |
---|
43 | self.undeclared_identifiers = util.Set() |
---|
44 | if isinstance(code, basestring): |
---|
45 | if re.match(r"\S", code) and not re.match(r",\s*$", code): |
---|
46 | # if theres text and no trailing comma, insure its parsed |
---|
47 | # as a tuple by adding a trailing comma |
---|
48 | code += "," |
---|
49 | expr = pyparser.parse(code, "exec", **exception_kwargs) |
---|
50 | else: |
---|
51 | expr = code |
---|
52 | |
---|
53 | f = pyparser.FindTuple(self, PythonCode, **exception_kwargs) |
---|
54 | f.visit(expr) |
---|
55 | |
---|
56 | class PythonFragment(PythonCode): |
---|
57 | """extends PythonCode to provide identifier lookups in partial control statements |
---|
58 | |
---|
59 | e.g. |
---|
60 | for x in 5: |
---|
61 | elif y==9: |
---|
62 | except (MyException, e): |
---|
63 | etc. |
---|
64 | """ |
---|
65 | def __init__(self, code, **exception_kwargs): |
---|
66 | m = re.match(r'^(\w+)(?:\s+(.*?))?:\s*(#|$)', code.strip(), re.S) |
---|
67 | if not m: |
---|
68 | raise exceptions.CompileException("Fragment '%s' is not a partial control statement" % code, **exception_kwargs) |
---|
69 | if m.group(3): |
---|
70 | code = code[:m.start(3)] |
---|
71 | (keyword, expr) = m.group(1,2) |
---|
72 | if keyword in ['for','if', 'while']: |
---|
73 | code = code + "pass" |
---|
74 | elif keyword == 'try': |
---|
75 | code = code + "pass\nexcept:pass" |
---|
76 | elif keyword == 'elif' or keyword == 'else': |
---|
77 | code = "if False:pass\n" + code + "pass" |
---|
78 | elif keyword == 'except': |
---|
79 | code = "try:pass\n" + code + "pass" |
---|
80 | else: |
---|
81 | raise exceptions.CompileException("Unsupported control keyword: '%s'" % keyword, **exception_kwargs) |
---|
82 | super(PythonFragment, self).__init__(code, **exception_kwargs) |
---|
83 | |
---|
84 | |
---|
85 | class FunctionDecl(object): |
---|
86 | """function declaration""" |
---|
87 | def __init__(self, code, allow_kwargs=True, **exception_kwargs): |
---|
88 | self.code = code |
---|
89 | expr = pyparser.parse(code, "exec", **exception_kwargs) |
---|
90 | |
---|
91 | f = pyparser.ParseFunc(self, **exception_kwargs) |
---|
92 | f.visit(expr) |
---|
93 | if not hasattr(self, 'funcname'): |
---|
94 | raise exceptions.CompileException("Code '%s' is not a function declaration" % code, **exception_kwargs) |
---|
95 | if not allow_kwargs and self.kwargs: |
---|
96 | raise exceptions.CompileException("'**%s' keyword argument not allowed here" % self.argnames[-1], **exception_kwargs) |
---|
97 | |
---|
98 | def get_argument_expressions(self, include_defaults=True): |
---|
99 | """return the argument declarations of this FunctionDecl as a printable list.""" |
---|
100 | namedecls = [] |
---|
101 | defaults = [d for d in self.defaults] |
---|
102 | kwargs = self.kwargs |
---|
103 | varargs = self.varargs |
---|
104 | argnames = [f for f in self.argnames] |
---|
105 | argnames.reverse() |
---|
106 | for arg in argnames: |
---|
107 | default = None |
---|
108 | if kwargs: |
---|
109 | arg = "**" + arg |
---|
110 | kwargs = False |
---|
111 | elif varargs: |
---|
112 | arg = "*" + arg |
---|
113 | varargs = False |
---|
114 | else: |
---|
115 | default = len(defaults) and defaults.pop() or None |
---|
116 | if include_defaults and default: |
---|
117 | namedecls.insert(0, "%s=%s" % (arg, pyparser.ExpressionGenerator(default).value())) |
---|
118 | else: |
---|
119 | namedecls.insert(0, arg) |
---|
120 | return namedecls |
---|
121 | |
---|
122 | class FunctionArgs(FunctionDecl): |
---|
123 | """the argument portion of a function declaration""" |
---|
124 | def __init__(self, code, **kwargs): |
---|
125 | super(FunctionArgs, self).__init__("def ANON(%s):pass" % code, **kwargs) |
---|