관리-도구
편집 파일: ast.py
# mako/ast.py # Copyright 2006-2019 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 )