summaryrefslogtreecommitdiffstats
path: root/third_party/python/Mako/mako/ast.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/Mako/mako/ast.py')
-rw-r--r--third_party/python/Mako/mako/ast.py205
1 files changed, 205 insertions, 0 deletions
diff --git a/third_party/python/Mako/mako/ast.py b/third_party/python/Mako/mako/ast.py
new file mode 100644
index 0000000000..cfae28062c
--- /dev/null
+++ b/third_party/python/Mako/mako/ast.py
@@ -0,0 +1,205 @@
+# mako/ast.py
+# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file>
+#
+# This module is part of Mako and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+
+"""utilities for analyzing expressions and blocks of Python
+code, as well as generating Python from AST nodes"""
+
+import re
+
+from mako import compat
+from mako import exceptions
+from mako import pyparser
+
+
+class PythonCode(object):
+
+ """represents information about a string containing Python code"""
+
+ def __init__(self, code, **exception_kwargs):
+ self.code = code
+
+ # represents all identifiers which are assigned to at some point in
+ # the code
+ self.declared_identifiers = set()
+
+ # represents all identifiers which are referenced before their
+ # assignment, if any
+ self.undeclared_identifiers = set()
+
+ # note that an identifier can be in both the undeclared and declared
+ # lists.
+
+ # using AST to parse instead of using code.co_varnames,
+ # code.co_names has several advantages:
+ # - we can locate an identifier as "undeclared" even if
+ # its declared later in the same block of code
+ # - AST is less likely to break with version changes
+ # (for example, the behavior of co_names changed a little bit
+ # in python version 2.5)
+ if isinstance(code, compat.string_types):
+ expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs)
+ else:
+ expr = code
+
+ f = pyparser.FindIdentifiers(self, **exception_kwargs)
+ f.visit(expr)
+
+
+class ArgumentList(object):
+
+ """parses a fragment of code as a comma-separated list of expressions"""
+
+ def __init__(self, code, **exception_kwargs):
+ self.codeargs = []
+ self.args = []
+ self.declared_identifiers = set()
+ self.undeclared_identifiers = set()
+ if isinstance(code, compat.string_types):
+ if re.match(r"\S", code) and not re.match(r",\s*$", code):
+ # if theres text and no trailing comma, insure its parsed
+ # as a tuple by adding a trailing comma
+ code += ","
+ expr = pyparser.parse(code, "exec", **exception_kwargs)
+ else:
+ expr = code
+
+ f = pyparser.FindTuple(self, PythonCode, **exception_kwargs)
+ f.visit(expr)
+
+
+class PythonFragment(PythonCode):
+
+ """extends PythonCode to provide identifier lookups in partial control
+ statements
+
+ e.g.::
+
+ for x in 5:
+ elif y==9:
+ except (MyException, e):
+
+ """
+
+ def __init__(self, code, **exception_kwargs):
+ m = re.match(r"^(\w+)(?:\s+(.*?))?:\s*(#|$)", code.strip(), re.S)
+ if not m:
+ raise exceptions.CompileException(
+ "Fragment '%s' is not a partial control statement" % code,
+ **exception_kwargs
+ )
+ if m.group(3):
+ code = code[: m.start(3)]
+ (keyword, expr) = m.group(1, 2)
+ if keyword in ["for", "if", "while"]:
+ code = code + "pass"
+ elif keyword == "try":
+ code = code + "pass\nexcept:pass"
+ elif keyword == "elif" or keyword == "else":
+ code = "if False:pass\n" + code + "pass"
+ elif keyword == "except":
+ code = "try:pass\n" + code + "pass"
+ elif keyword == "with":
+ code = code + "pass"
+ else:
+ raise exceptions.CompileException(
+ "Unsupported control keyword: '%s'" % keyword,
+ **exception_kwargs
+ )
+ super(PythonFragment, self).__init__(code, **exception_kwargs)
+
+
+class FunctionDecl(object):
+
+ """function declaration"""
+
+ def __init__(self, code, allow_kwargs=True, **exception_kwargs):
+ self.code = code
+ expr = pyparser.parse(code, "exec", **exception_kwargs)
+
+ f = pyparser.ParseFunc(self, **exception_kwargs)
+ f.visit(expr)
+ if not hasattr(self, "funcname"):
+ raise exceptions.CompileException(
+ "Code '%s' is not a function declaration" % code,
+ **exception_kwargs
+ )
+ if not allow_kwargs and self.kwargs:
+ raise exceptions.CompileException(
+ "'**%s' keyword argument not allowed here"
+ % self.kwargnames[-1],
+ **exception_kwargs
+ )
+
+ def get_argument_expressions(self, as_call=False):
+ """Return the argument declarations of this FunctionDecl as a printable
+ list.
+
+ By default the return value is appropriate for writing in a ``def``;
+ set `as_call` to true to build arguments to be passed to the function
+ instead (assuming locals with the same names as the arguments exist).
+ """
+
+ namedecls = []
+
+ # Build in reverse order, since defaults and slurpy args come last
+ argnames = self.argnames[::-1]
+ kwargnames = self.kwargnames[::-1]
+ defaults = self.defaults[::-1]
+ kwdefaults = self.kwdefaults[::-1]
+
+ # Named arguments
+ if self.kwargs:
+ namedecls.append("**" + kwargnames.pop(0))
+
+ for name in kwargnames:
+ # Keyword-only arguments must always be used by name, so even if
+ # this is a call, print out `foo=foo`
+ if as_call:
+ namedecls.append("%s=%s" % (name, name))
+ elif kwdefaults:
+ default = kwdefaults.pop(0)
+ if default is None:
+ # The AST always gives kwargs a default, since you can do
+ # `def foo(*, a=1, b, c=3)`
+ namedecls.append(name)
+ else:
+ namedecls.append(
+ "%s=%s"
+ % (name, pyparser.ExpressionGenerator(default).value())
+ )
+ else:
+ namedecls.append(name)
+
+ # Positional arguments
+ if self.varargs:
+ namedecls.append("*" + argnames.pop(0))
+
+ for name in argnames:
+ if as_call or not defaults:
+ namedecls.append(name)
+ else:
+ default = defaults.pop(0)
+ namedecls.append(
+ "%s=%s"
+ % (name, pyparser.ExpressionGenerator(default).value())
+ )
+
+ namedecls.reverse()
+ return namedecls
+
+ @property
+ def allargnames(self):
+ return tuple(self.argnames) + tuple(self.kwargnames)
+
+
+class FunctionArgs(FunctionDecl):
+
+ """the argument portion of a function declaration"""
+
+ def __init__(self, code, **kwargs):
+ super(FunctionArgs, self).__init__(
+ "def ANON(%s):pass" % code, **kwargs
+ )