Skip to content

Commit 22513c3

Browse files
committed
Add print_ast
1 parent 35ff3a9 commit 22513c3

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed

src/python_minifier/ast_printer.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"""
2+
Print a representation of an AST
3+
4+
This prints a human readable representation of the nodes in the AST.
5+
The goal is to make it easy to see what the AST looks like, and to
6+
make it easy to compare two ASTs.
7+
8+
This is not intended to be a complete representation of the AST, some
9+
fields or field names may be omitted for clarity. It should still be precise and unambiguous.
10+
11+
"""
12+
13+
import ast
14+
15+
from python_minifier.util import is_ast_node
16+
17+
INDENT = ' '
18+
19+
# The field name that can be omitted for each node
20+
# Either it's the only field or would otherwise be obvious
21+
default_fields = {
22+
'Constant': 'value',
23+
'Num': 'n',
24+
'Str': 's',
25+
'Bytes': 's',
26+
'NameConstant': 'value',
27+
'FormattedValue': 'value',
28+
'JoinedStr': 'values',
29+
'List': 'elts',
30+
'Tuple': 'elts',
31+
'Set': 'elts',
32+
'Name': 'id',
33+
'Expr': 'value',
34+
'UnaryOp': 'op',
35+
'BinOp': 'op',
36+
'BoolOp': 'op',
37+
'Call': 'func',
38+
'Index': 'value',
39+
'ExtSlice': 'dims',
40+
'Assert': 'test',
41+
'Delete': 'targets',
42+
'Import': 'names',
43+
'If': 'test',
44+
'While': 'test',
45+
'Try': 'handlers',
46+
'TryExcept': 'handlers',
47+
'With': 'items',
48+
'withitem': 'context_expr',
49+
'FunctionDef': 'name',
50+
'arg': 'arg',
51+
'Return': 'value',
52+
'Yield': 'value',
53+
'YieldFrom': 'value',
54+
'Global': 'names',
55+
'Nonlocal': 'names',
56+
'ClassDef': 'name',
57+
'AsyncFunctionDef': 'name',
58+
'Await': 'value',
59+
'AsyncWith': 'items',
60+
'Raise': 'exc',
61+
'Subscript': 'value',
62+
'Attribute': 'value',
63+
'AugAssign': 'op',
64+
}
65+
66+
def is_literal(node, field):
67+
if hasattr(ast, 'Constant') and isinstance(node, ast.Constant) and field == 'value':
68+
return True
69+
70+
if isinstance(node, ast.Num) and field == 'n':
71+
return True
72+
73+
if isinstance(node, ast.Str) and field == 's':
74+
return True
75+
76+
if is_ast_node(node, 'Bytes') and field == 's':
77+
return True
78+
79+
if is_ast_node(node, 'NameConstant') and field == 'value':
80+
return True
81+
82+
return False
83+
84+
def print_ast(node):
85+
if not isinstance(node, ast.AST):
86+
return repr(node)
87+
88+
s = ''
89+
90+
node_name = node.__class__.__name__
91+
s += node_name
92+
s += '('
93+
94+
first = True
95+
for field, value in ast.iter_fields(node):
96+
if not value and not is_literal(node, field):
97+
# Don't bother printing fields that are empty, except for literals
98+
continue
99+
100+
if field == 'ctx':
101+
# Don't print the ctx, it's always apparent from context
102+
continue
103+
104+
if first:
105+
first = False
106+
else:
107+
s += ', '
108+
109+
if default_fields.get(node_name) != field:
110+
s += field + '='
111+
112+
if isinstance(value, ast.AST):
113+
s += print_ast(value)
114+
elif isinstance(value, list):
115+
s += '['
116+
first_list = True
117+
for item in value:
118+
if first_list:
119+
first_list = False
120+
else:
121+
s += ','
122+
123+
for line in print_ast(item).splitlines():
124+
s += '\n' + INDENT + line
125+
s += '\n]'
126+
else:
127+
s += repr(value)
128+
129+
s += ')'
130+
return s

0 commit comments

Comments
 (0)