Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/mako/ast.py : 19%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# mako/ast.py
2# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file>
3#
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
8code, as well as generating Python from AST nodes"""
10import re
12from mako import compat
13from mako import exceptions
14from mako import pyparser
17class PythonCode(object):
19 """represents information about a string containing Python code"""
21 def __init__(self, code, **exception_kwargs):
22 self.code = code
24 # represents all identifiers which are assigned to at some point in
25 # the code
26 self.declared_identifiers = set()
28 # represents all identifiers which are referenced before their
29 # assignment, if any
30 self.undeclared_identifiers = set()
32 # note that an identifier can be in both the undeclared and declared
33 # lists.
35 # using AST to parse instead of using code.co_varnames,
36 # code.co_names has several advantages:
37 # - we can locate an identifier as "undeclared" even if
38 # its declared later in the same block of code
39 # - AST is less likely to break with version changes
40 # (for example, the behavior of co_names changed a little bit
41 # in python version 2.5)
42 if isinstance(code, compat.string_types):
43 expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs)
44 else:
45 expr = code
47 f = pyparser.FindIdentifiers(self, **exception_kwargs)
48 f.visit(expr)
51class ArgumentList(object):
53 """parses a fragment of code as a comma-separated list of expressions"""
55 def __init__(self, code, **exception_kwargs):
56 self.codeargs = []
57 self.args = []
58 self.declared_identifiers = set()
59 self.undeclared_identifiers = set()
60 if isinstance(code, compat.string_types):
61 if re.match(r"\S", code) and not re.match(r",\s*$", code):
62 # if theres text and no trailing comma, insure its parsed
63 # as a tuple by adding a trailing comma
64 code += ","
65 expr = pyparser.parse(code, "exec", **exception_kwargs)
66 else:
67 expr = code
69 f = pyparser.FindTuple(self, PythonCode, **exception_kwargs)
70 f.visit(expr)
73class PythonFragment(PythonCode):
75 """extends PythonCode to provide identifier lookups in partial control
76 statements
78 e.g.::
80 for x in 5:
81 elif y==9:
82 except (MyException, e):
84 """
86 def __init__(self, code, **exception_kwargs):
87 m = re.match(r"^(\w+)(?:\s+(.*?))?:\s*(#|$)", code.strip(), re.S)
88 if not m:
89 raise exceptions.CompileException(
90 "Fragment '%s' is not a partial control statement" % code,
91 **exception_kwargs
92 )
93 if m.group(3):
94 code = code[: m.start(3)]
95 (keyword, expr) = m.group(1, 2)
96 if keyword in ["for", "if", "while"]:
97 code = code + "pass"
98 elif keyword == "try":
99 code = code + "pass\nexcept:pass"
100 elif keyword == "elif" or keyword == "else":
101 code = "if False:pass\n" + code + "pass"
102 elif keyword == "except":
103 code = "try:pass\n" + code + "pass"
104 elif keyword == "with":
105 code = code + "pass"
106 else:
107 raise exceptions.CompileException(
108 "Unsupported control keyword: '%s'" % keyword,
109 **exception_kwargs
110 )
111 super(PythonFragment, self).__init__(code, **exception_kwargs)
114class FunctionDecl(object):
116 """function declaration"""
118 def __init__(self, code, allow_kwargs=True, **exception_kwargs):
119 self.code = code
120 expr = pyparser.parse(code, "exec", **exception_kwargs)
122 f = pyparser.ParseFunc(self, **exception_kwargs)
123 f.visit(expr)
124 if not hasattr(self, "funcname"):
125 raise exceptions.CompileException(
126 "Code '%s' is not a function declaration" % code,
127 **exception_kwargs
128 )
129 if not allow_kwargs and self.kwargs:
130 raise exceptions.CompileException(
131 "'**%s' keyword argument not allowed here"
132 % self.kwargnames[-1],
133 **exception_kwargs
134 )
136 def get_argument_expressions(self, as_call=False):
137 """Return the argument declarations of this FunctionDecl as a printable
138 list.
140 By default the return value is appropriate for writing in a ``def``;
141 set `as_call` to true to build arguments to be passed to the function
142 instead (assuming locals with the same names as the arguments exist).
143 """
145 namedecls = []
147 # Build in reverse order, since defaults and slurpy args come last
148 argnames = self.argnames[::-1]
149 kwargnames = self.kwargnames[::-1]
150 defaults = self.defaults[::-1]
151 kwdefaults = self.kwdefaults[::-1]
153 # Named arguments
154 if self.kwargs:
155 namedecls.append("**" + kwargnames.pop(0))
157 for name in kwargnames:
158 # Keyword-only arguments must always be used by name, so even if
159 # this is a call, print out `foo=foo`
160 if as_call:
161 namedecls.append("%s=%s" % (name, name))
162 elif kwdefaults:
163 default = kwdefaults.pop(0)
164 if default is None:
165 # The AST always gives kwargs a default, since you can do
166 # `def foo(*, a=1, b, c=3)`
167 namedecls.append(name)
168 else:
169 namedecls.append(
170 "%s=%s"
171 % (name, pyparser.ExpressionGenerator(default).value())
172 )
173 else:
174 namedecls.append(name)
176 # Positional arguments
177 if self.varargs:
178 namedecls.append("*" + argnames.pop(0))
180 for name in argnames:
181 if as_call or not defaults:
182 namedecls.append(name)
183 else:
184 default = defaults.pop(0)
185 namedecls.append(
186 "%s=%s"
187 % (name, pyparser.ExpressionGenerator(default).value())
188 )
190 namedecls.reverse()
191 return namedecls
193 @property
194 def allargnames(self):
195 return tuple(self.argnames) + tuple(self.kwargnames)
198class FunctionArgs(FunctionDecl):
200 """the argument portion of a function declaration"""
202 def __init__(self, code, **kwargs):
203 super(FunctionArgs, self).__init__(
204 "def ANON(%s):pass" % code, **kwargs
205 )