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