2 # Copyright (C) 2006-2015 the Mako authors and contributors <see AUTHORS file>
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
8 code, as well as generating Python from AST nodes"""
10 from mako
import exceptions
, pyparser
, compat
13 class PythonCode(object):
14 """represents information about a string containing Python code"""
15 def __init__(self
, code
, **exception_kwargs
):
18 # represents all identifiers which are assigned to at some point in
20 self
.declared_identifiers
= set()
22 # represents all identifiers which are referenced before their
24 self
.undeclared_identifiers
= set()
26 # note that an identifier can be in both the undeclared and declared
29 # using AST to parse instead of using code.co_varnames,
30 # code.co_names has several advantages:
31 # - we can locate an identifier as "undeclared" even if
32 # its declared later in the same block of code
33 # - AST is less likely to break with version changes
34 # (for example, the behavior of co_names changed a little bit
35 # in python version 2.5)
36 if isinstance(code
, compat
.string_types
):
37 expr
= pyparser
.parse(code
.lstrip(), "exec", **exception_kwargs
)
41 f
= pyparser
.FindIdentifiers(self
, **exception_kwargs
)
44 class ArgumentList(object):
45 """parses a fragment of code as a comma-separated list of expressions"""
46 def __init__(self
, code
, **exception_kwargs
):
49 self
.declared_identifiers
= set()
50 self
.undeclared_identifiers
= set()
51 if isinstance(code
, compat
.string_types
):
52 if re
.match(r
"\S", code
) and not re
.match(r
",\s*$", code
):
53 # if theres text and no trailing comma, insure its parsed
54 # as a tuple by adding a trailing comma
56 expr
= pyparser
.parse(code
, "exec", **exception_kwargs
)
60 f
= pyparser
.FindTuple(self
, PythonCode
, **exception_kwargs
)
63 class PythonFragment(PythonCode
):
64 """extends PythonCode to provide identifier lookups in partial control
70 except (MyException, e):
73 def __init__(self
, code
, **exception_kwargs
):
74 m
= re
.match(r
'^(\w+)(?:\s+(.*?))?:\s*(#|$)', code
.strip(), re
.S
)
76 raise exceptions
.CompileException(
77 "Fragment '%s' is not a partial control statement" %
78 code
, **exception_kwargs
)
80 code
= code
[:m
.start(3)]
81 (keyword
, expr
) = m
.group(1,2)
82 if keyword
in ['for','if', 'while']:
84 elif keyword
== 'try':
85 code
= code
+ "pass\nexcept:pass"
86 elif keyword
== 'elif' or keyword
== 'else':
87 code
= "if False:pass\n" + code
+ "pass"
88 elif keyword
== 'except':
89 code
= "try:pass\n" + code
+ "pass"
90 elif keyword
== 'with':
93 raise exceptions
.CompileException(
94 "Unsupported control keyword: '%s'" %
95 keyword
, **exception_kwargs
)
96 super(PythonFragment
, self
).__init
__(code
, **exception_kwargs
)
99 class FunctionDecl(object):
100 """function declaration"""
101 def __init__(self
, code
, allow_kwargs
=True, **exception_kwargs
):
103 expr
= pyparser
.parse(code
, "exec", **exception_kwargs
)
105 f
= pyparser
.ParseFunc(self
, **exception_kwargs
)
107 if not hasattr(self
, 'funcname'):
108 raise exceptions
.CompileException(
109 "Code '%s' is not a function declaration" % code
,
111 if not allow_kwargs
and self
.kwargs
:
112 raise exceptions
.CompileException(
113 "'**%s' keyword argument not allowed here" %
114 self
.kwargnames
[-1], **exception_kwargs
)
116 def get_argument_expressions(self
, as_call
=False):
117 """Return the argument declarations of this FunctionDecl as a printable
120 By default the return value is appropriate for writing in a ``def``;
121 set `as_call` to true to build arguments to be passed to the function
122 instead (assuming locals with the same names as the arguments exist).
127 # Build in reverse order, since defaults and slurpy args come last
128 argnames
= self
.argnames
[::-1]
129 kwargnames
= self
.kwargnames
[::-1]
130 defaults
= self
.defaults
[::-1]
131 kwdefaults
= self
.kwdefaults
[::-1]
135 namedecls
.append("**" + kwargnames
.pop(0))
137 for name
in kwargnames
:
138 # Keyword-only arguments must always be used by name, so even if
139 # this is a call, print out `foo=foo`
141 namedecls
.append("%s=%s" % (name
, name
))
143 default
= kwdefaults
.pop(0)
145 # The AST always gives kwargs a default, since you can do
146 # `def foo(*, a=1, b, c=3)`
147 namedecls
.append(name
)
149 namedecls
.append("%s=%s" % (
150 name
, pyparser
.ExpressionGenerator(default
).value()))
152 namedecls
.append(name
)
154 # Positional arguments
156 namedecls
.append("*" + argnames
.pop(0))
158 for name
in argnames
:
159 if as_call
or not defaults
:
160 namedecls
.append(name
)
162 default
= defaults
.pop(0)
163 namedecls
.append("%s=%s" % (
164 name
, pyparser
.ExpressionGenerator(default
).value()))
170 def allargnames(self
):
171 return tuple(self
.argnames
) + tuple(self
.kwargnames
)
173 class FunctionArgs(FunctionDecl
):
174 """the argument portion of a function declaration"""
176 def __init__(self
, code
, **kwargs
):
177 super(FunctionArgs
, self
).__init
__("def ANON(%s):pass" % code
,