|
1 """ |
|
2 ast_unparse |
|
3 ~~~~~~~~~~~ |
|
4 |
|
5 This module is a backport of the unparse function of the Python 3.9 |
|
6 ast module. |
|
7 |
|
8 Original ast module code is |
|
9 :copyright: Copyright 2008 by Armin Ronacher. |
|
10 :license: Python License. |
|
11 """ |
|
12 |
|
13 import ast |
|
14 import sys |
|
15 from enum import IntEnum, auto |
|
16 from contextlib import contextmanager, nullcontext |
|
17 |
|
18 import AstUtilities |
|
19 |
|
20 # Large float and imaginary literals get turned into infinities in the AST. |
|
21 # We unparse those infinities to INFSTR. |
|
22 _INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1) |
|
23 |
|
24 class _Precedence(IntEnum): |
|
25 """Precedence table that originated from python grammar.""" |
|
26 |
|
27 TUPLE = auto() |
|
28 YIELD = auto() # 'yield', 'yield from' |
|
29 TEST = auto() # 'if'-'else', 'lambda' |
|
30 OR = auto() # 'or' |
|
31 AND = auto() # 'and' |
|
32 NOT = auto() # 'not' |
|
33 CMP = auto() # '<', '>', '==', '>=', '<=', '!=', |
|
34 # 'in', 'not in', 'is', 'is not' |
|
35 EXPR = auto() |
|
36 BOR = EXPR # '|' |
|
37 BXOR = auto() # '^' |
|
38 BAND = auto() # '&' |
|
39 SHIFT = auto() # '<<', '>>' |
|
40 ARITH = auto() # '+', '-' |
|
41 TERM = auto() # '*', '@', '/', '%', '//' |
|
42 FACTOR = auto() # unary '+', '-', '~' |
|
43 POWER = auto() # '**' |
|
44 AWAIT = auto() # 'await' |
|
45 ATOM = auto() |
|
46 |
|
47 def next(self): |
|
48 try: |
|
49 return self.__class__(self + 1) |
|
50 except ValueError: |
|
51 return self |
|
52 |
|
53 |
|
54 _SINGLE_QUOTES = ("'", '"') |
|
55 _MULTI_QUOTES = ('"""', "'''") |
|
56 _ALL_QUOTES = (*_SINGLE_QUOTES, *_MULTI_QUOTES) |
|
57 |
|
58 class _Unparser(ast.NodeVisitor): |
|
59 """Methods in this class recursively traverse an AST and |
|
60 output source code for the abstract syntax; original formatting |
|
61 is disregarded.""" |
|
62 |
|
63 def __init__(self, *, _avoid_backslashes=False): |
|
64 self._source = [] |
|
65 self._buffer = [] |
|
66 self._precedences = {} |
|
67 self._type_ignores = {} |
|
68 self._indent = 0 |
|
69 self._avoid_backslashes = _avoid_backslashes |
|
70 |
|
71 def interleave(self, inter, f, seq): |
|
72 """Call f on each item in seq, calling inter() in between.""" |
|
73 seq = iter(seq) |
|
74 try: |
|
75 f(next(seq)) |
|
76 except StopIteration: |
|
77 pass |
|
78 else: |
|
79 for x in seq: |
|
80 inter() |
|
81 f(x) |
|
82 |
|
83 def items_view(self, traverser, items): |
|
84 """Traverse and separate the given *items* with a comma and append it to |
|
85 the buffer. If *items* is a single item sequence, a trailing comma |
|
86 will be added.""" |
|
87 if len(items) == 1: |
|
88 traverser(items[0]) |
|
89 self.write(",") |
|
90 else: |
|
91 self.interleave(lambda: self.write(", "), traverser, items) |
|
92 |
|
93 def maybe_newline(self): |
|
94 """Adds a newline if it isn't the start of generated source""" |
|
95 if self._source: |
|
96 self.write("\n") |
|
97 |
|
98 def fill(self, text=""): |
|
99 """Indent a piece of text and append it, according to the current |
|
100 indentation level""" |
|
101 self.maybe_newline() |
|
102 self.write(" " * self._indent + text) |
|
103 |
|
104 def write(self, text): |
|
105 """Append a piece of text""" |
|
106 self._source.append(text) |
|
107 |
|
108 def buffer_writer(self, text): |
|
109 self._buffer.append(text) |
|
110 |
|
111 @property |
|
112 def buffer(self): |
|
113 value = "".join(self._buffer) |
|
114 self._buffer.clear() |
|
115 return value |
|
116 |
|
117 @contextmanager |
|
118 def block(self, *, extra = None): |
|
119 """A context manager for preparing the source for blocks. It adds |
|
120 the character':', increases the indentation on enter and decreases |
|
121 the indentation on exit. If *extra* is given, it will be directly |
|
122 appended after the colon character. |
|
123 """ |
|
124 self.write(":") |
|
125 if extra: |
|
126 self.write(extra) |
|
127 self._indent += 1 |
|
128 yield |
|
129 self._indent -= 1 |
|
130 |
|
131 @contextmanager |
|
132 def delimit(self, start, end): |
|
133 """A context manager for preparing the source for expressions. It adds |
|
134 *start* to the buffer and enters, after exit it adds *end*.""" |
|
135 |
|
136 self.write(start) |
|
137 yield |
|
138 self.write(end) |
|
139 |
|
140 def delimit_if(self, start, end, condition): |
|
141 if condition: |
|
142 return self.delimit(start, end) |
|
143 else: |
|
144 return nullcontext() |
|
145 |
|
146 def require_parens(self, precedence, node): |
|
147 """Shortcut to adding precedence related parens""" |
|
148 return self.delimit_if("(", ")", self.get_precedence(node) > precedence) |
|
149 |
|
150 def get_precedence(self, node): |
|
151 return self._precedences.get(node, _Precedence.TEST) |
|
152 |
|
153 def set_precedence(self, precedence, *nodes): |
|
154 for node in nodes: |
|
155 self._precedences[node] = precedence |
|
156 |
|
157 def get_raw_docstring(self, node): |
|
158 """If a docstring node is found in the body of the *node* parameter, |
|
159 return that docstring node, None otherwise. |
|
160 |
|
161 Logic mirrored from ``_PyAST_GetDocString``.""" |
|
162 if not isinstance( |
|
163 node, (ast.AsyncFunctionDef, ast.FunctionDef, ast.ClassDef, |
|
164 ast.Module) |
|
165 ) or len(node.body) < 1: |
|
166 return None |
|
167 node = node.body[0] |
|
168 if not isinstance(node, ast.Expr): |
|
169 return None |
|
170 node = node.value |
|
171 if isinstance(node, ast.Constant) and isinstance(node.value, str): |
|
172 return node |
|
173 |
|
174 def get_type_comment(self, node): |
|
175 comment = self._type_ignores.get(node.lineno) or node.type_comment |
|
176 if comment is not None: |
|
177 return f" # type: {comment}" |
|
178 |
|
179 def traverse(self, node): |
|
180 if isinstance(node, list): |
|
181 for item in node: |
|
182 self.traverse(item) |
|
183 else: |
|
184 super().visit(node) |
|
185 |
|
186 def visit(self, node): |
|
187 """Outputs a source code string that, if converted back to an ast |
|
188 (using ast.parse) will generate an AST equivalent to *node*""" |
|
189 self._source = [] |
|
190 self.traverse(node) |
|
191 return "".join(self._source) |
|
192 |
|
193 def _write_docstring_and_traverse_body(self, node): |
|
194 docstring = self.get_raw_docstring(node) |
|
195 if (docstring): |
|
196 self._write_docstring(docstring) |
|
197 self.traverse(node.body[1:]) |
|
198 else: |
|
199 self.traverse(node.body) |
|
200 |
|
201 def visit_Module(self, node): |
|
202 self._type_ignores = { |
|
203 ignore.lineno: f"ignore{ignore.tag}" |
|
204 for ignore in node.type_ignores |
|
205 } |
|
206 self._write_docstring_and_traverse_body(node) |
|
207 self._type_ignores.clear() |
|
208 |
|
209 def visit_FunctionType(self, node): |
|
210 with self.delimit("(", ")"): |
|
211 self.interleave( |
|
212 lambda: self.write(", "), self.traverse, node.argtypes |
|
213 ) |
|
214 |
|
215 self.write(" -> ") |
|
216 self.traverse(node.returns) |
|
217 |
|
218 def visit_Expr(self, node): |
|
219 self.fill() |
|
220 self.set_precedence(_Precedence.YIELD, node.value) |
|
221 self.traverse(node.value) |
|
222 |
|
223 def visit_NamedExpr(self, node): |
|
224 with self.require_parens(_Precedence.TUPLE, node): |
|
225 self.set_precedence(_Precedence.ATOM, node.target, node.value) |
|
226 self.traverse(node.target) |
|
227 self.write(" := ") |
|
228 self.traverse(node.value) |
|
229 |
|
230 def visit_Import(self, node): |
|
231 self.fill("import ") |
|
232 self.interleave(lambda: self.write(", "), self.traverse, node.names) |
|
233 |
|
234 def visit_ImportFrom(self, node): |
|
235 self.fill("from ") |
|
236 self.write("." * node.level) |
|
237 if node.module: |
|
238 self.write(node.module) |
|
239 self.write(" import ") |
|
240 self.interleave(lambda: self.write(", "), self.traverse, node.names) |
|
241 |
|
242 def visit_Assign(self, node): |
|
243 self.fill() |
|
244 for target in node.targets: |
|
245 self.traverse(target) |
|
246 self.write(" = ") |
|
247 self.traverse(node.value) |
|
248 type_comment = self.get_type_comment(node) |
|
249 if type_comment: |
|
250 self.write(type_comment) |
|
251 |
|
252 def visit_AugAssign(self, node): |
|
253 self.fill() |
|
254 self.traverse(node.target) |
|
255 self.write(" " + self.binop[node.op.__class__.__name__] + "= ") |
|
256 self.traverse(node.value) |
|
257 |
|
258 def visit_AnnAssign(self, node): |
|
259 self.fill() |
|
260 with self.delimit_if("(", ")", not node.simple and isinstance(node.target, ast.Name)): |
|
261 self.traverse(node.target) |
|
262 self.write(": ") |
|
263 self.traverse(node.annotation) |
|
264 if node.value: |
|
265 self.write(" = ") |
|
266 self.traverse(node.value) |
|
267 |
|
268 def visit_Return(self, node): |
|
269 self.fill("return") |
|
270 if node.value: |
|
271 self.write(" ") |
|
272 self.traverse(node.value) |
|
273 |
|
274 def visit_Pass(self, node): |
|
275 self.fill("pass") |
|
276 |
|
277 def visit_Break(self, node): |
|
278 self.fill("break") |
|
279 |
|
280 def visit_Continue(self, node): |
|
281 self.fill("continue") |
|
282 |
|
283 def visit_Delete(self, node): |
|
284 self.fill("del ") |
|
285 self.interleave(lambda: self.write(", "), self.traverse, node.targets) |
|
286 |
|
287 def visit_Assert(self, node): |
|
288 self.fill("assert ") |
|
289 self.traverse(node.test) |
|
290 if node.msg: |
|
291 self.write(", ") |
|
292 self.traverse(node.msg) |
|
293 |
|
294 def visit_Global(self, node): |
|
295 self.fill("global ") |
|
296 self.interleave(lambda: self.write(", "), self.write, node.names) |
|
297 |
|
298 def visit_Nonlocal(self, node): |
|
299 self.fill("nonlocal ") |
|
300 self.interleave(lambda: self.write(", "), self.write, node.names) |
|
301 |
|
302 def visit_Await(self, node): |
|
303 with self.require_parens(_Precedence.AWAIT, node): |
|
304 self.write("await") |
|
305 if node.value: |
|
306 self.write(" ") |
|
307 self.set_precedence(_Precedence.ATOM, node.value) |
|
308 self.traverse(node.value) |
|
309 |
|
310 def visit_Yield(self, node): |
|
311 with self.require_parens(_Precedence.YIELD, node): |
|
312 self.write("yield") |
|
313 if node.value: |
|
314 self.write(" ") |
|
315 self.set_precedence(_Precedence.ATOM, node.value) |
|
316 self.traverse(node.value) |
|
317 |
|
318 def visit_YieldFrom(self, node): |
|
319 with self.require_parens(_Precedence.YIELD, node): |
|
320 self.write("yield from ") |
|
321 if not node.value: |
|
322 raise ValueError("Node can't be used without a value attribute.") |
|
323 self.set_precedence(_Precedence.ATOM, node.value) |
|
324 self.traverse(node.value) |
|
325 |
|
326 def visit_Raise(self, node): |
|
327 self.fill("raise") |
|
328 if not node.exc: |
|
329 if node.cause: |
|
330 raise ValueError("Node can't use cause without an exception.") |
|
331 return |
|
332 self.write(" ") |
|
333 self.traverse(node.exc) |
|
334 if node.cause: |
|
335 self.write(" from ") |
|
336 self.traverse(node.cause) |
|
337 |
|
338 def visit_Try(self, node): |
|
339 self.fill("try") |
|
340 with self.block(): |
|
341 self.traverse(node.body) |
|
342 for ex in node.handlers: |
|
343 self.traverse(ex) |
|
344 if node.orelse: |
|
345 self.fill("else") |
|
346 with self.block(): |
|
347 self.traverse(node.orelse) |
|
348 if node.finalbody: |
|
349 self.fill("finally") |
|
350 with self.block(): |
|
351 self.traverse(node.finalbody) |
|
352 |
|
353 def visit_ExceptHandler(self, node): |
|
354 self.fill("except") |
|
355 if node.type: |
|
356 self.write(" ") |
|
357 self.traverse(node.type) |
|
358 if node.name: |
|
359 self.write(" as ") |
|
360 self.write(node.name) |
|
361 with self.block(): |
|
362 self.traverse(node.body) |
|
363 |
|
364 def visit_ClassDef(self, node): |
|
365 self.maybe_newline() |
|
366 for deco in node.decorator_list: |
|
367 self.fill("@") |
|
368 self.traverse(deco) |
|
369 self.fill("class " + node.name) |
|
370 with self.delimit_if("(", ")", condition = node.bases or node.keywords): |
|
371 comma = False |
|
372 for e in node.bases: |
|
373 if comma: |
|
374 self.write(", ") |
|
375 else: |
|
376 comma = True |
|
377 self.traverse(e) |
|
378 for e in node.keywords: |
|
379 if comma: |
|
380 self.write(", ") |
|
381 else: |
|
382 comma = True |
|
383 self.traverse(e) |
|
384 |
|
385 with self.block(): |
|
386 self._write_docstring_and_traverse_body(node) |
|
387 |
|
388 def visit_FunctionDef(self, node): |
|
389 self._function_helper(node, "def") |
|
390 |
|
391 def visit_AsyncFunctionDef(self, node): |
|
392 self._function_helper(node, "async def") |
|
393 |
|
394 def _function_helper(self, node, fill_suffix): |
|
395 self.maybe_newline() |
|
396 for deco in node.decorator_list: |
|
397 self.fill("@") |
|
398 self.traverse(deco) |
|
399 def_str = fill_suffix + " " + node.name |
|
400 self.fill(def_str) |
|
401 with self.delimit("(", ")"): |
|
402 self.traverse(node.args) |
|
403 if node.returns: |
|
404 self.write(" -> ") |
|
405 self.traverse(node.returns) |
|
406 with self.block(extra=self.get_type_comment(node)): |
|
407 self._write_docstring_and_traverse_body(node) |
|
408 |
|
409 def visit_For(self, node): |
|
410 self._for_helper("for ", node) |
|
411 |
|
412 def visit_AsyncFor(self, node): |
|
413 self._for_helper("async for ", node) |
|
414 |
|
415 def _for_helper(self, fill, node): |
|
416 self.fill(fill) |
|
417 self.traverse(node.target) |
|
418 self.write(" in ") |
|
419 self.traverse(node.iter) |
|
420 with self.block(extra=self.get_type_comment(node)): |
|
421 self.traverse(node.body) |
|
422 if node.orelse: |
|
423 self.fill("else") |
|
424 with self.block(): |
|
425 self.traverse(node.orelse) |
|
426 |
|
427 def visit_If(self, node): |
|
428 self.fill("if ") |
|
429 self.traverse(node.test) |
|
430 with self.block(): |
|
431 self.traverse(node.body) |
|
432 # collapse nested ifs into equivalent elifs. |
|
433 while node.orelse and len(node.orelse) == 1 and isinstance(node.orelse[0], ast.If): |
|
434 node = node.orelse[0] |
|
435 self.fill("elif ") |
|
436 self.traverse(node.test) |
|
437 with self.block(): |
|
438 self.traverse(node.body) |
|
439 # final else |
|
440 if node.orelse: |
|
441 self.fill("else") |
|
442 with self.block(): |
|
443 self.traverse(node.orelse) |
|
444 |
|
445 def visit_While(self, node): |
|
446 self.fill("while ") |
|
447 self.traverse(node.test) |
|
448 with self.block(): |
|
449 self.traverse(node.body) |
|
450 if node.orelse: |
|
451 self.fill("else") |
|
452 with self.block(): |
|
453 self.traverse(node.orelse) |
|
454 |
|
455 def visit_With(self, node): |
|
456 self.fill("with ") |
|
457 self.interleave(lambda: self.write(", "), self.traverse, node.items) |
|
458 with self.block(extra=self.get_type_comment(node)): |
|
459 self.traverse(node.body) |
|
460 |
|
461 def visit_AsyncWith(self, node): |
|
462 self.fill("async with ") |
|
463 self.interleave(lambda: self.write(", "), self.traverse, node.items) |
|
464 with self.block(extra=self.get_type_comment(node)): |
|
465 self.traverse(node.body) |
|
466 |
|
467 def _str_literal_helper( |
|
468 self, string, *, quote_types=_ALL_QUOTES, escape_special_whitespace=False |
|
469 ): |
|
470 """Helper for writing string literals, minimizing escapes. |
|
471 Returns the tuple (string literal to write, possible quote types). |
|
472 """ |
|
473 def escape_char(c): |
|
474 # \n and \t are non-printable, but we only escape them if |
|
475 # escape_special_whitespace is True |
|
476 if not escape_special_whitespace and c in "\n\t": |
|
477 return c |
|
478 # Always escape backslashes and other non-printable characters |
|
479 if c == "\\" or not c.isprintable(): |
|
480 return c.encode("unicode_escape").decode("ascii") |
|
481 return c |
|
482 |
|
483 escaped_string = "".join(map(escape_char, string)) |
|
484 possible_quotes = quote_types |
|
485 if "\n" in escaped_string: |
|
486 possible_quotes = [q for q in possible_quotes if q in _MULTI_QUOTES] |
|
487 possible_quotes = [q for q in possible_quotes if q not in escaped_string] |
|
488 if not possible_quotes: |
|
489 # If there aren't any possible_quotes, fallback to using repr |
|
490 # on the original string. Try to use a quote from quote_types, |
|
491 # e.g., so that we use triple quotes for docstrings. |
|
492 string = repr(string) |
|
493 quote = next((q for q in quote_types if string[0] in q), string[0]) |
|
494 return string[1:-1], [quote] |
|
495 if escaped_string: |
|
496 # Sort so that we prefer '''"''' over """\"""" |
|
497 possible_quotes.sort(key=lambda q: q[0] == escaped_string[-1]) |
|
498 # If we're using triple quotes and we'd need to escape a final |
|
499 # quote, escape it |
|
500 if possible_quotes[0][0] == escaped_string[-1]: |
|
501 assert len(possible_quotes[0]) == 3 |
|
502 escaped_string = escaped_string[:-1] + "\\" + escaped_string[-1] |
|
503 return escaped_string, possible_quotes |
|
504 |
|
505 def _write_str_avoiding_backslashes(self, string, *, quote_types=_ALL_QUOTES): |
|
506 """Write string literal value with a best effort attempt to avoid backslashes.""" |
|
507 string, quote_types = self._str_literal_helper(string, quote_types=quote_types) |
|
508 quote_type = quote_types[0] |
|
509 self.write(f"{quote_type}{string}{quote_type}") |
|
510 |
|
511 def visit_JoinedStr(self, node): |
|
512 self.write("f") |
|
513 if self._avoid_backslashes: |
|
514 self._fstring_JoinedStr(node, self.buffer_writer) |
|
515 self._write_str_avoiding_backslashes(self.buffer) |
|
516 return |
|
517 |
|
518 # If we don't need to avoid backslashes globally (i.e., we only need |
|
519 # to avoid them inside FormattedValues), it's cosmetically preferred |
|
520 # to use escaped whitespace. That is, it's preferred to use backslashes |
|
521 # for cases like: f"{x}\n". To accomplish this, we keep track of what |
|
522 # in our buffer corresponds to FormattedValues and what corresponds to |
|
523 # Constant parts of the f-string, and allow escapes accordingly. |
|
524 buffer = [] |
|
525 for value in node.values: |
|
526 meth = getattr(self, "_fstring_" + type(value).__name__) |
|
527 meth(value, self.buffer_writer) |
|
528 buffer.append((self.buffer, isinstance(value, ast.Constant))) |
|
529 new_buffer = [] |
|
530 quote_types = _ALL_QUOTES |
|
531 for value, is_constant in buffer: |
|
532 # Repeatedly narrow down the list of possible quote_types |
|
533 value, quote_types = self._str_literal_helper( |
|
534 value, quote_types=quote_types, |
|
535 escape_special_whitespace=is_constant |
|
536 ) |
|
537 new_buffer.append(value) |
|
538 value = "".join(new_buffer) |
|
539 quote_type = quote_types[0] |
|
540 self.write(f"{quote_type}{value}{quote_type}") |
|
541 |
|
542 def visit_FormattedValue(self, node): |
|
543 self.write("f") |
|
544 self._fstring_FormattedValue(node, self.buffer_writer) |
|
545 self._write_str_avoiding_backslashes(self.buffer) |
|
546 |
|
547 def _fstring_JoinedStr(self, node, write): |
|
548 for value in node.values: |
|
549 meth = getattr(self, "_fstring_" + type(value).__name__) |
|
550 meth(value, write) |
|
551 |
|
552 def _fstring_Constant(self, node, write): |
|
553 if not isinstance(node.value, str): |
|
554 raise ValueError("Constants inside JoinedStr should be a string.") |
|
555 value = node.value.replace("{", "{{").replace("}", "}}") |
|
556 write(value) |
|
557 |
|
558 def _fstring_FormattedValue(self, node, write): |
|
559 write("{") |
|
560 unparser = type(self)(_avoid_backslashes=True) |
|
561 unparser.set_precedence(_Precedence.TEST.next(), node.value) |
|
562 expr = unparser.visit(node.value) |
|
563 if expr.startswith("{"): |
|
564 write(" ") # Separate pair of opening brackets as "{ {" |
|
565 if "\\" in expr: |
|
566 raise ValueError("Unable to avoid backslash in f-string expression part") |
|
567 write(expr) |
|
568 if node.conversion != -1: |
|
569 conversion = chr(node.conversion) |
|
570 if conversion not in "sra": |
|
571 raise ValueError("Unknown f-string conversion.") |
|
572 write(f"!{conversion}") |
|
573 if node.format_spec: |
|
574 write(":") |
|
575 meth = getattr(self, "_fstring_" + type(node.format_spec).__name__) |
|
576 meth(node.format_spec, write) |
|
577 write("}") |
|
578 |
|
579 def visit_Name(self, node): |
|
580 self.write(node.id) |
|
581 |
|
582 def _write_docstring(self, node): |
|
583 self.fill() |
|
584 if node.kind == "u": |
|
585 self.write("u") |
|
586 self._write_str_avoiding_backslashes(node.value, quote_types=_MULTI_QUOTES) |
|
587 |
|
588 def _write_constant(self, value): |
|
589 if isinstance(value, (float, complex)): |
|
590 # Substitute overflowing decimal literal for AST infinities, |
|
591 # and inf - inf for NaNs. |
|
592 self.write( |
|
593 repr(value) |
|
594 .replace("inf", _INFSTR) |
|
595 .replace("nan", f"({_INFSTR}-{_INFSTR})") |
|
596 ) |
|
597 elif self._avoid_backslashes and isinstance(value, str): |
|
598 self._write_str_avoiding_backslashes(value) |
|
599 else: |
|
600 self.write(repr(value)) |
|
601 |
|
602 def visit_Constant(self, node): |
|
603 value = AstUtilities.getValue(node) |
|
604 if isinstance(value, tuple): |
|
605 with self.delimit("(", ")"): |
|
606 self.items_view(self._write_constant, value) |
|
607 elif value is ...: |
|
608 self.write("...") |
|
609 else: |
|
610 if hasattr(node, "kind") and node.kind == "u": |
|
611 self.write("u") |
|
612 self._write_constant(value) |
|
613 |
|
614 visit_NameConstant = visit_Constant |
|
615 visit_Num = visit_Constant |
|
616 visit_Str = visit_Constant |
|
617 visit_Bytes = visit_Constant |
|
618 visit_Ellipsis = visit_Constant |
|
619 |
|
620 def visit_List(self, node): |
|
621 with self.delimit("[", "]"): |
|
622 self.interleave(lambda: self.write(", "), self.traverse, node.elts) |
|
623 |
|
624 def visit_ListComp(self, node): |
|
625 with self.delimit("[", "]"): |
|
626 self.traverse(node.elt) |
|
627 for gen in node.generators: |
|
628 self.traverse(gen) |
|
629 |
|
630 def visit_GeneratorExp(self, node): |
|
631 with self.delimit("(", ")"): |
|
632 self.traverse(node.elt) |
|
633 for gen in node.generators: |
|
634 self.traverse(gen) |
|
635 |
|
636 def visit_SetComp(self, node): |
|
637 with self.delimit("{", "}"): |
|
638 self.traverse(node.elt) |
|
639 for gen in node.generators: |
|
640 self.traverse(gen) |
|
641 |
|
642 def visit_DictComp(self, node): |
|
643 with self.delimit("{", "}"): |
|
644 self.traverse(node.key) |
|
645 self.write(": ") |
|
646 self.traverse(node.value) |
|
647 for gen in node.generators: |
|
648 self.traverse(gen) |
|
649 |
|
650 def visit_comprehension(self, node): |
|
651 if node.is_async: |
|
652 self.write(" async for ") |
|
653 else: |
|
654 self.write(" for ") |
|
655 self.set_precedence(_Precedence.TUPLE, node.target) |
|
656 self.traverse(node.target) |
|
657 self.write(" in ") |
|
658 self.set_precedence(_Precedence.TEST.next(), node.iter, *node.ifs) |
|
659 self.traverse(node.iter) |
|
660 for if_clause in node.ifs: |
|
661 self.write(" if ") |
|
662 self.traverse(if_clause) |
|
663 |
|
664 def visit_IfExp(self, node): |
|
665 with self.require_parens(_Precedence.TEST, node): |
|
666 self.set_precedence(_Precedence.TEST.next(), node.body, node.test) |
|
667 self.traverse(node.body) |
|
668 self.write(" if ") |
|
669 self.traverse(node.test) |
|
670 self.write(" else ") |
|
671 self.set_precedence(_Precedence.TEST, node.orelse) |
|
672 self.traverse(node.orelse) |
|
673 |
|
674 def visit_Set(self, node): |
|
675 if node.elts: |
|
676 with self.delimit("{", "}"): |
|
677 self.interleave(lambda: self.write(", "), self.traverse, node.elts) |
|
678 else: |
|
679 # `{}` would be interpreted as a dictionary literal, and |
|
680 # `set` might be shadowed. Thus: |
|
681 self.write('{*()}') |
|
682 |
|
683 def visit_Dict(self, node): |
|
684 def write_key_value_pair(k, v): |
|
685 self.traverse(k) |
|
686 self.write(": ") |
|
687 self.traverse(v) |
|
688 |
|
689 def write_item(item): |
|
690 k, v = item |
|
691 if k is None: |
|
692 # for dictionary unpacking operator in dicts {**{'y': 2}} |
|
693 # see PEP 448 for details |
|
694 self.write("**") |
|
695 self.set_precedence(_Precedence.EXPR, v) |
|
696 self.traverse(v) |
|
697 else: |
|
698 write_key_value_pair(k, v) |
|
699 |
|
700 with self.delimit("{", "}"): |
|
701 self.interleave( |
|
702 lambda: self.write(", "), write_item, zip(node.keys, node.values) |
|
703 ) |
|
704 |
|
705 def visit_Tuple(self, node): |
|
706 with self.delimit("(", ")"): |
|
707 self.items_view(self.traverse, node.elts) |
|
708 |
|
709 unop = {"Invert": "~", "Not": "not", "UAdd": "+", "USub": "-"} |
|
710 unop_precedence = { |
|
711 "not": _Precedence.NOT, |
|
712 "~": _Precedence.FACTOR, |
|
713 "+": _Precedence.FACTOR, |
|
714 "-": _Precedence.FACTOR, |
|
715 } |
|
716 |
|
717 def visit_UnaryOp(self, node): |
|
718 operator = self.unop[node.op.__class__.__name__] |
|
719 operator_precedence = self.unop_precedence[operator] |
|
720 with self.require_parens(operator_precedence, node): |
|
721 self.write(operator) |
|
722 # factor prefixes (+, -, ~) shouldn't be seperated |
|
723 # from the value they belong, (e.g: +1 instead of + 1) |
|
724 if operator_precedence is not _Precedence.FACTOR: |
|
725 self.write(" ") |
|
726 self.set_precedence(operator_precedence, node.operand) |
|
727 self.traverse(node.operand) |
|
728 |
|
729 binop = { |
|
730 "Add": "+", |
|
731 "Sub": "-", |
|
732 "Mult": "*", |
|
733 "MatMult": "@", |
|
734 "Div": "/", |
|
735 "Mod": "%", |
|
736 "LShift": "<<", |
|
737 "RShift": ">>", |
|
738 "BitOr": "|", |
|
739 "BitXor": "^", |
|
740 "BitAnd": "&", |
|
741 "FloorDiv": "//", |
|
742 "Pow": "**", |
|
743 } |
|
744 |
|
745 binop_precedence = { |
|
746 "+": _Precedence.ARITH, |
|
747 "-": _Precedence.ARITH, |
|
748 "*": _Precedence.TERM, |
|
749 "@": _Precedence.TERM, |
|
750 "/": _Precedence.TERM, |
|
751 "%": _Precedence.TERM, |
|
752 "<<": _Precedence.SHIFT, |
|
753 ">>": _Precedence.SHIFT, |
|
754 "|": _Precedence.BOR, |
|
755 "^": _Precedence.BXOR, |
|
756 "&": _Precedence.BAND, |
|
757 "//": _Precedence.TERM, |
|
758 "**": _Precedence.POWER, |
|
759 } |
|
760 |
|
761 binop_rassoc = frozenset(("**",)) |
|
762 def visit_BinOp(self, node): |
|
763 operator = self.binop[node.op.__class__.__name__] |
|
764 operator_precedence = self.binop_precedence[operator] |
|
765 with self.require_parens(operator_precedence, node): |
|
766 if operator in self.binop_rassoc: |
|
767 left_precedence = operator_precedence.next() |
|
768 right_precedence = operator_precedence |
|
769 else: |
|
770 left_precedence = operator_precedence |
|
771 right_precedence = operator_precedence.next() |
|
772 |
|
773 self.set_precedence(left_precedence, node.left) |
|
774 self.traverse(node.left) |
|
775 self.write(f" {operator} ") |
|
776 self.set_precedence(right_precedence, node.right) |
|
777 self.traverse(node.right) |
|
778 |
|
779 cmpops = { |
|
780 "Eq": "==", |
|
781 "NotEq": "!=", |
|
782 "Lt": "<", |
|
783 "LtE": "<=", |
|
784 "Gt": ">", |
|
785 "GtE": ">=", |
|
786 "Is": "is", |
|
787 "IsNot": "is not", |
|
788 "In": "in", |
|
789 "NotIn": "not in", |
|
790 } |
|
791 |
|
792 def visit_Compare(self, node): |
|
793 with self.require_parens(_Precedence.CMP, node): |
|
794 self.set_precedence(_Precedence.CMP.next(), node.left, *node.comparators) |
|
795 self.traverse(node.left) |
|
796 for o, e in zip(node.ops, node.comparators): |
|
797 self.write(" " + self.cmpops[o.__class__.__name__] + " ") |
|
798 self.traverse(e) |
|
799 |
|
800 boolops = {"And": "and", "Or": "or"} |
|
801 boolop_precedence = {"and": _Precedence.AND, "or": _Precedence.OR} |
|
802 |
|
803 def visit_BoolOp(self, node): |
|
804 operator = self.boolops[node.op.__class__.__name__] |
|
805 operator_precedence = self.boolop_precedence[operator] |
|
806 |
|
807 def increasing_level_traverse(node): |
|
808 nonlocal operator_precedence |
|
809 operator_precedence = operator_precedence.next() |
|
810 self.set_precedence(operator_precedence, node) |
|
811 self.traverse(node) |
|
812 |
|
813 with self.require_parens(operator_precedence, node): |
|
814 s = f" {operator} " |
|
815 self.interleave(lambda: self.write(s), increasing_level_traverse, node.values) |
|
816 |
|
817 def visit_Attribute(self, node): |
|
818 self.set_precedence(_Precedence.ATOM, node.value) |
|
819 self.traverse(node.value) |
|
820 # Special case: 3.__abs__() is a syntax error, so if node.value |
|
821 # is an integer literal then we need to either parenthesize |
|
822 # it or add an extra space to get 3 .__abs__(). |
|
823 if isinstance(node.value, ast.Constant) and isinstance(node.value.value, int): |
|
824 self.write(" ") |
|
825 self.write(".") |
|
826 self.write(node.attr) |
|
827 |
|
828 def visit_Call(self, node): |
|
829 self.set_precedence(_Precedence.ATOM, node.func) |
|
830 self.traverse(node.func) |
|
831 with self.delimit("(", ")"): |
|
832 comma = False |
|
833 for e in node.args: |
|
834 if comma: |
|
835 self.write(", ") |
|
836 else: |
|
837 comma = True |
|
838 self.traverse(e) |
|
839 for e in node.keywords: |
|
840 if comma: |
|
841 self.write(", ") |
|
842 else: |
|
843 comma = True |
|
844 self.traverse(e) |
|
845 |
|
846 def visit_Subscript(self, node): |
|
847 def is_simple_tuple(slice_value): |
|
848 # when unparsing a non-empty tuple, the parantheses can be safely |
|
849 # omitted if there aren't any elements that explicitly requires |
|
850 # parantheses (such as starred expressions). |
|
851 return ( |
|
852 isinstance(slice_value, ast.Tuple) |
|
853 and slice_value.elts |
|
854 and not any(isinstance(elt, ast.Starred) for elt in slice_value.elts) |
|
855 ) |
|
856 |
|
857 self.set_precedence(_Precedence.ATOM, node.value) |
|
858 self.traverse(node.value) |
|
859 with self.delimit("[", "]"): |
|
860 if is_simple_tuple(node.slice): |
|
861 self.items_view(self.traverse, node.slice.elts) |
|
862 else: |
|
863 if isinstance(node.slice, ast.Index): |
|
864 self.traverse(node.slice.value) |
|
865 else: |
|
866 self.traverse(node.slice) |
|
867 |
|
868 def visit_Starred(self, node): |
|
869 self.write("*") |
|
870 self.set_precedence(_Precedence.EXPR, node.value) |
|
871 self.traverse(node.value) |
|
872 |
|
873 def visit_Ellipsis(self, node): |
|
874 self.write("...") |
|
875 |
|
876 def visit_Slice(self, node): |
|
877 if node.lower: |
|
878 self.traverse(node.lower) |
|
879 self.write(":") |
|
880 if node.upper: |
|
881 self.traverse(node.upper) |
|
882 if node.step: |
|
883 self.write(":") |
|
884 self.traverse(node.step) |
|
885 |
|
886 def visit_arg(self, node): |
|
887 self.write(node.arg) |
|
888 if node.annotation: |
|
889 self.write(": ") |
|
890 self.traverse(node.annotation) |
|
891 |
|
892 def visit_arguments(self, node): |
|
893 first = True |
|
894 # normal arguments |
|
895 all_args = node.posonlyargs + node.args |
|
896 defaults = [None] * (len(all_args) - len(node.defaults)) + node.defaults |
|
897 for index, elements in enumerate(zip(all_args, defaults), 1): |
|
898 a, d = elements |
|
899 if first: |
|
900 first = False |
|
901 else: |
|
902 self.write(", ") |
|
903 self.traverse(a) |
|
904 if d: |
|
905 self.write("=") |
|
906 self.traverse(d) |
|
907 if index == len(node.posonlyargs): |
|
908 self.write(", /") |
|
909 |
|
910 # varargs, or bare '*' if no varargs but keyword-only arguments present |
|
911 if node.vararg or node.kwonlyargs: |
|
912 if first: |
|
913 first = False |
|
914 else: |
|
915 self.write(", ") |
|
916 self.write("*") |
|
917 if node.vararg: |
|
918 self.write(node.vararg.arg) |
|
919 if node.vararg.annotation: |
|
920 self.write(": ") |
|
921 self.traverse(node.vararg.annotation) |
|
922 |
|
923 # keyword-only arguments |
|
924 if node.kwonlyargs: |
|
925 for a, d in zip(node.kwonlyargs, node.kw_defaults): |
|
926 self.write(", ") |
|
927 self.traverse(a) |
|
928 if d: |
|
929 self.write("=") |
|
930 self.traverse(d) |
|
931 |
|
932 # kwargs |
|
933 if node.kwarg: |
|
934 if first: |
|
935 first = False |
|
936 else: |
|
937 self.write(", ") |
|
938 self.write("**" + node.kwarg.arg) |
|
939 if node.kwarg.annotation: |
|
940 self.write(": ") |
|
941 self.traverse(node.kwarg.annotation) |
|
942 |
|
943 def visit_keyword(self, node): |
|
944 if node.arg is None: |
|
945 self.write("**") |
|
946 else: |
|
947 self.write(node.arg) |
|
948 self.write("=") |
|
949 self.traverse(node.value) |
|
950 |
|
951 def visit_Lambda(self, node): |
|
952 with self.require_parens(_Precedence.TEST, node): |
|
953 self.write("lambda ") |
|
954 self.traverse(node.args) |
|
955 self.write(": ") |
|
956 self.set_precedence(_Precedence.TEST, node.body) |
|
957 self.traverse(node.body) |
|
958 |
|
959 def visit_alias(self, node): |
|
960 self.write(node.name) |
|
961 if node.asname: |
|
962 self.write(" as " + node.asname) |
|
963 |
|
964 def visit_withitem(self, node): |
|
965 self.traverse(node.context_expr) |
|
966 if node.optional_vars: |
|
967 self.write(" as ") |
|
968 self.traverse(node.optional_vars) |
|
969 |
|
970 def unparse(ast_obj): |
|
971 unparser = _Unparser() |
|
972 return unparser.visit(ast_obj) |