Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 78 additions & 42 deletions Lib/ast.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
"""
ast
~~~

The `ast` module helps Python applications to process trees of the Python
abstract syntax grammar. The abstract syntax itself might change with
each Python release; this module helps to find out programmatically what
the current grammar looks like and allows modifications of it.

An abstract syntax tree can be generated by passing `ast.PyCF_ONLY_AST` as
a flag to the `compile()` builtin function or by using the `parse()`
function from this module. The result will be a tree of objects whose
classes all inherit from `ast.AST`.

A modified abstract syntax tree can be compiled into a Python code object
using the built-in `compile()` function.

Additionally various helper functions are provided that make working with
the trees simpler. The main intention of the helper functions and this
module in general is to provide an easy to use interface for libraries
that work tightly with the python syntax (template engines for example).


:copyright: Copyright 2008 by Armin Ronacher.
:license: Python License.
The `ast` module helps Python applications to process trees of the Python
abstract syntax grammar. The abstract syntax itself might change with
each Python release; this module helps to find out programmatically what
the current grammar looks like and allows modifications of it.

An abstract syntax tree can be generated by passing `ast.PyCF_ONLY_AST` as
a flag to the `compile()` builtin function or by using the `parse()`
function from this module. The result will be a tree of objects whose
classes all inherit from `ast.AST`.

A modified abstract syntax tree can be compiled into a Python code object
using the built-in `compile()` function.

Additionally various helper functions are provided that make working with
the trees simpler. The main intention of the helper functions and this
module in general is to provide an easy to use interface for libraries
that work tightly with the python syntax (template engines for example).

:copyright: Copyright 2008 by Armin Ronacher.
:license: Python License.
"""
import sys
import re
Expand All @@ -32,13 +28,15 @@


def parse(source, filename='<unknown>', mode='exec', *,
type_comments=False, feature_version=None):
type_comments=False, feature_version=None, optimize=-1):
"""
Parse the source into an AST node.
Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
Pass type_comments=True to get back type comments where the syntax allows.
"""
flags = PyCF_ONLY_AST
if optimize > 0:
flags |= PyCF_OPTIMIZED_AST
if type_comments:
flags |= PyCF_TYPE_COMMENTS
if feature_version is None:
Expand All @@ -50,7 +48,7 @@ def parse(source, filename='<unknown>', mode='exec', *,
feature_version = minor
# Else it should be an int giving the minor version for 3.x.
return compile(source, filename, mode, flags,
_feature_version=feature_version)
_feature_version=feature_version, optimize=optimize)


def literal_eval(node_or_string):
Expand Down Expand Up @@ -112,7 +110,11 @@ def _convert(node):
return _convert(node_or_string)


def dump(node, annotate_fields=True, include_attributes=False, *, indent=None):
def dump(
node, annotate_fields=True, include_attributes=False,
*,
indent=None, show_empty=False,
):
"""
Return a formatted dump of the tree in node. This is mainly useful for
debugging purposes. If annotate_fields is true (by default),
Expand All @@ -123,6 +125,8 @@ def dump(node, annotate_fields=True, include_attributes=False, *, indent=None):
include_attributes can be set to true. If indent is a non-negative
integer or string, then the tree will be pretty-printed with that indent
level. None (the default) selects the single line representation.
If show_empty is False, then empty lists and fields that are None
will be omitted from the output for better readability.
"""
def _format(node, level=0):
if indent is not None:
Expand All @@ -135,6 +139,7 @@ def _format(node, level=0):
if isinstance(node, AST):
cls = type(node)
args = []
args_buffer = []
allsimple = True
keywords = annotate_fields
for name in node._fields:
Expand All @@ -146,6 +151,16 @@ def _format(node, level=0):
if value is None and getattr(cls, name, ...) is None:
keywords = True
continue
if not show_empty:
if value == []:
field_type = cls._field_types.get(name, object)
if getattr(field_type, '__origin__', ...) is list:
if not keywords:
args_buffer.append(repr(value))
continue
if not keywords:
args.extend(args_buffer)
args_buffer = []
value, simple = _format(value, level)
allsimple = allsimple and simple
if keywords:
Expand Down Expand Up @@ -726,12 +741,11 @@ class _Unparser(NodeVisitor):
output source code for the abstract syntax; original formatting
is disregarded."""

def __init__(self, *, _avoid_backslashes=False):
def __init__(self):
self._source = []
self._precedences = {}
self._type_ignores = {}
self._indent = 0
self._avoid_backslashes = _avoid_backslashes
self._in_try_star = False

def interleave(self, inter, f, seq):
Expand Down Expand Up @@ -1104,12 +1118,21 @@ def visit_TypeVar(self, node):
if node.bound:
self.write(": ")
self.traverse(node.bound)
if node.default_value:
self.write(" = ")
self.traverse(node.default_value)

def visit_TypeVarTuple(self, node):
self.write("*" + node.name)
if node.default_value:
self.write(" = ")
self.traverse(node.default_value)

def visit_ParamSpec(self, node):
self.write("**" + node.name)
if node.default_value:
self.write(" = ")
self.traverse(node.default_value)

def visit_TypeAlias(self, node):
self.fill("type ")
Expand Down Expand Up @@ -1246,9 +1269,14 @@ def visit_JoinedStr(self, node):
fallback_to_repr = True
break
quote_types = new_quote_types
elif "\n" in value:
quote_types = [q for q in quote_types if q in _MULTI_QUOTES]
assert quote_types
else:
if "\n" in value:
quote_types = [q for q in quote_types if q in _MULTI_QUOTES]
assert quote_types

new_quote_types = [q for q in quote_types if q not in value]
if new_quote_types:
quote_types = new_quote_types
new_fstring_parts.append(value)

if fallback_to_repr:
Expand All @@ -1268,13 +1296,19 @@ def visit_JoinedStr(self, node):
quote_type = quote_types[0]
self.write(f"{quote_type}{value}{quote_type}")

def _write_fstring_inner(self, node):
def _write_fstring_inner(self, node, is_format_spec=False):
if isinstance(node, JoinedStr):
# for both the f-string itself, and format_spec
for value in node.values:
self._write_fstring_inner(value)
self._write_fstring_inner(value, is_format_spec=is_format_spec)
elif isinstance(node, Constant) and isinstance(node.value, str):
value = node.value.replace("{", "{{").replace("}", "}}")

if is_format_spec:
value = value.replace("\\", "\\\\")
value = value.replace("'", "\\'")
value = value.replace('"', '\\"')
value = value.replace("\n", "\\n")
self.write(value)
elif isinstance(node, FormattedValue):
self.visit_FormattedValue(node)
Expand All @@ -1297,7 +1331,7 @@ def unparse_inner(inner):
self.write(f"!{chr(node.conversion)}")
if node.format_spec:
self.write(":")
self._write_fstring_inner(node.format_spec)
self._write_fstring_inner(node.format_spec, is_format_spec=True)

def visit_Name(self, node):
self.write(node.id)
Expand All @@ -1317,8 +1351,6 @@ def _write_constant(self, value):
.replace("inf", _INFSTR)
.replace("nan", f"({_INFSTR}-{_INFSTR})")
)
elif self._avoid_backslashes and isinstance(value, str):
self._write_str_avoiding_backslashes(value)
else:
self.write(repr(value))

Expand Down Expand Up @@ -1805,8 +1837,7 @@ def main():
import argparse

parser = argparse.ArgumentParser(prog='python -m ast')
parser.add_argument('infile', type=argparse.FileType(mode='rb'), nargs='?',
default='-',
parser.add_argument('infile', nargs='?', default='-',
help='the file to parse; defaults to stdin')
parser.add_argument('-m', '--mode', default='exec',
choices=('exec', 'single', 'eval', 'func_type'),
Expand All @@ -1820,9 +1851,14 @@ def main():
help='indentation of nodes (number of spaces)')
args = parser.parse_args()

with args.infile as infile:
source = infile.read()
tree = parse(source, args.infile.name, args.mode, type_comments=args.no_type_comments)
if args.infile == '-':
name = '<stdin>'
source = sys.stdin.buffer.read()
else:
name = args.infile
with open(args.infile, 'rb') as infile:
source = infile.read()
tree = parse(source, name, args.mode, type_comments=args.no_type_comments)
print(dump(tree, include_attributes=args.include_attributes, indent=args.indent))

if __name__ == '__main__':
Expand Down
Loading
Loading