ast.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # mako/ast.py
  2. # Copyright 2006-2025 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
  6. """utilities for analyzing expressions and blocks of Python
  7. code, as well as generating Python from AST nodes"""
  8. import re
  9. from mako import exceptions
  10. from mako import pyparser
  11. class PythonCode:
  12. """represents information about a string containing Python code"""
  13. def __init__(self, code, **exception_kwargs):
  14. self.code = code
  15. # represents all identifiers which are assigned to at some point in
  16. # the code
  17. self.declared_identifiers = set()
  18. # represents all identifiers which are referenced before their
  19. # assignment, if any
  20. self.undeclared_identifiers = set()
  21. # note that an identifier can be in both the undeclared and declared
  22. # lists.
  23. # using AST to parse instead of using code.co_varnames,
  24. # code.co_names has several advantages:
  25. # - we can locate an identifier as "undeclared" even if
  26. # its declared later in the same block of code
  27. # - AST is less likely to break with version changes
  28. # (for example, the behavior of co_names changed a little bit
  29. # in python version 2.5)
  30. if isinstance(code, str):
  31. expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs)
  32. else:
  33. expr = code
  34. f = pyparser.FindIdentifiers(self, **exception_kwargs)
  35. f.visit(expr)
  36. class ArgumentList:
  37. """parses a fragment of code as a comma-separated list of expressions"""
  38. def __init__(self, code, **exception_kwargs):
  39. self.codeargs = []
  40. self.args = []
  41. self.declared_identifiers = set()
  42. self.undeclared_identifiers = set()
  43. if isinstance(code, str):
  44. if re.match(r"\S", code) and not re.match(r",\s*$", code):
  45. # if theres text and no trailing comma, insure its parsed
  46. # as a tuple by adding a trailing comma
  47. code += ","
  48. expr = pyparser.parse(code, "exec", **exception_kwargs)
  49. else:
  50. expr = code
  51. f = pyparser.FindTuple(self, PythonCode, **exception_kwargs)
  52. f.visit(expr)
  53. class PythonFragment(PythonCode):
  54. """extends PythonCode to provide identifier lookups in partial control
  55. statements
  56. e.g.::
  57. for x in 5:
  58. elif y==9:
  59. except (MyException, e):
  60. """
  61. def __init__(self, code, **exception_kwargs):
  62. m = re.match(r"^(\w+)(?:\s+(.*?))?:\s*(#|$)", code.strip(), re.S)
  63. if not m:
  64. raise exceptions.CompileException(
  65. "Fragment '%s' is not a partial control statement" % code,
  66. **exception_kwargs,
  67. )
  68. if m.group(3):
  69. code = code[: m.start(3)]
  70. (keyword, expr) = m.group(1, 2)
  71. if keyword in ["for", "if", "while"]:
  72. code = code + "pass"
  73. elif keyword == "try":
  74. code = code + "pass\nexcept:pass"
  75. elif keyword in ["elif", "else"]:
  76. code = "if False:pass\n" + code + "pass"
  77. elif keyword == "except":
  78. code = "try:pass\n" + code + "pass"
  79. elif keyword == "with":
  80. code = code + "pass"
  81. else:
  82. raise exceptions.CompileException(
  83. "Unsupported control keyword: '%s'" % keyword,
  84. **exception_kwargs,
  85. )
  86. super().__init__(code, **exception_kwargs)
  87. class FunctionDecl:
  88. """function declaration"""
  89. def __init__(self, code, allow_kwargs=True, **exception_kwargs):
  90. self.code = code
  91. expr = pyparser.parse(code, "exec", **exception_kwargs)
  92. f = pyparser.ParseFunc(self, **exception_kwargs)
  93. f.visit(expr)
  94. if not hasattr(self, "funcname"):
  95. raise exceptions.CompileException(
  96. "Code '%s' is not a function declaration" % code,
  97. **exception_kwargs,
  98. )
  99. if not allow_kwargs and self.kwargs:
  100. raise exceptions.CompileException(
  101. "'**%s' keyword argument not allowed here"
  102. % self.kwargnames[-1],
  103. **exception_kwargs,
  104. )
  105. def get_argument_expressions(self, as_call=False):
  106. """Return the argument declarations of this FunctionDecl as a printable
  107. list.
  108. By default the return value is appropriate for writing in a ``def``;
  109. set `as_call` to true to build arguments to be passed to the function
  110. instead (assuming locals with the same names as the arguments exist).
  111. """
  112. namedecls = []
  113. # Build in reverse order, since defaults and slurpy args come last
  114. argnames = self.argnames[::-1]
  115. kwargnames = self.kwargnames[::-1]
  116. defaults = self.defaults[::-1]
  117. kwdefaults = self.kwdefaults[::-1]
  118. # Named arguments
  119. if self.kwargs:
  120. namedecls.append("**" + kwargnames.pop(0))
  121. for name in kwargnames:
  122. # Keyword-only arguments must always be used by name, so even if
  123. # this is a call, print out `foo=foo`
  124. if as_call:
  125. namedecls.append("%s=%s" % (name, name))
  126. elif kwdefaults:
  127. default = kwdefaults.pop(0)
  128. if default is None:
  129. # The AST always gives kwargs a default, since you can do
  130. # `def foo(*, a=1, b, c=3)`
  131. namedecls.append(name)
  132. else:
  133. namedecls.append(
  134. "%s=%s"
  135. % (name, pyparser.ExpressionGenerator(default).value())
  136. )
  137. else:
  138. namedecls.append(name)
  139. # Positional arguments
  140. if self.varargs:
  141. namedecls.append("*" + argnames.pop(0))
  142. for name in argnames:
  143. if as_call or not defaults:
  144. namedecls.append(name)
  145. else:
  146. default = defaults.pop(0)
  147. namedecls.append(
  148. "%s=%s"
  149. % (name, pyparser.ExpressionGenerator(default).value())
  150. )
  151. namedecls.reverse()
  152. return namedecls
  153. @property
  154. def allargnames(self):
  155. return tuple(self.argnames) + tuple(self.kwargnames)
  156. class FunctionArgs(FunctionDecl):
  157. """the argument portion of a function declaration"""
  158. def __init__(self, code, **kwargs):
  159. super().__init__("def ANON(%s):pass" % code, **kwargs)