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