Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_ast_unparse.py1162 linesDownload Raw Back to Lib
1# This module contains ``ast.unparse()``, defined here2# to improve the import time for the ``ast`` module.3import sys4from _ast import *5from ast import NodeVisitor6from contextlib import contextmanager, nullcontext7from enum import IntEnum, auto, _simple_enum8 9# Large float and imaginary literals get turned into infinities in the AST.10# We unparse those infinities to INFSTR.11_INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1)12 13@_simple_enum(IntEnum)14class _Precedence:15    """Precedence table that originated from python grammar."""16 17    NAMED_EXPR = auto()      # <target> := <expr1>18    TUPLE = auto()           # <expr1>, <expr2>19    YIELD = auto()           # 'yield', 'yield from'20    TEST = auto()            # 'if'-'else', 'lambda'21    OR = auto()              # 'or'22    AND = auto()             # 'and'23    NOT = auto()             # 'not'24    CMP = auto()             # '<', '>', '==', '>=', '<=', '!=',25                             # 'in', 'not in', 'is', 'is not'26    EXPR = auto()27    BOR = EXPR               # '|'28    BXOR = auto()            # '^'29    BAND = auto()            # '&'30    SHIFT = auto()           # '<<', '>>'31    ARITH = auto()           # '+', '-'32    TERM = auto()            # '*', '@', '/', '%', '//'33    FACTOR = auto()          # unary '+', '-', '~'34    POWER = auto()           # '**'35    AWAIT = auto()           # 'await'36    ATOM = auto()37 38    def next(self):39        try:40            return self.__class__(self + 1)41        except ValueError:42            return self43 44 45_SINGLE_QUOTES = ("'", '"')46_MULTI_QUOTES = ('"""', "'''")47_ALL_QUOTES = (*_SINGLE_QUOTES, *_MULTI_QUOTES)48 49class Unparser(NodeVisitor):50    """Methods in this class recursively traverse an AST and51    output source code for the abstract syntax; original formatting52    is disregarded."""53 54    def __init__(self):55        self._source = []56        self._precedences = {}57        self._type_ignores = {}58        self._indent = 059        self._in_try_star = False60        self._in_interactive = False61 62    def interleave(self, inter, f, seq):63        """Call f on each item in seq, calling inter() in between."""64        seq = iter(seq)65        try:66            f(next(seq))67        except StopIteration:68            pass69        else:70            for x in seq:71                inter()72                f(x)73 74    def items_view(self, traverser, items):75        """Traverse and separate the given *items* with a comma and append it to76        the buffer. If *items* is a single item sequence, a trailing comma77        will be added."""78        if len(items) == 1:79            traverser(items[0])80            self.write(",")81        else:82            self.interleave(lambda: self.write(", "), traverser, items)83 84    def maybe_newline(self):85        """Adds a newline if it isn't the start of generated source"""86        if self._source:87            self.write("\n")88 89    def maybe_semicolon(self):90        """Adds a "; " delimiter if it isn't the start of generated source"""91        if self._source:92            self.write("; ")93 94    def fill(self, text="", *, allow_semicolon=True):95        """Indent a piece of text and append it, according to the current96        indentation level, or only delineate with semicolon if applicable"""97        if self._in_interactive and not self._indent and allow_semicolon:98            self.maybe_semicolon()99            self.write(text)100        else:101            self.maybe_newline()102            self.write("    " * self._indent + text)103 104    def write(self, *text):105        """Add new source parts"""106        self._source.extend(text)107 108    @contextmanager109    def buffered(self, buffer = None):110        if buffer is None:111            buffer = []112 113        original_source = self._source114        self._source = buffer115        yield buffer116        self._source = original_source117 118    @contextmanager119    def block(self, *, extra = None):120        """A context manager for preparing the source for blocks. It adds121        the character':', increases the indentation on enter and decreases122        the indentation on exit. If *extra* is given, it will be directly123        appended after the colon character.124        """125        self.write(":")126        if extra:127            self.write(extra)128        self._indent += 1129        yield130        self._indent -= 1131 132    @contextmanager133    def delimit(self, start, end):134        """A context manager for preparing the source for expressions. It adds135        *start* to the buffer and enters, after exit it adds *end*."""136 137        self.write(start)138        yield139        self.write(end)140 141    def delimit_if(self, start, end, condition):142        if condition:143            return self.delimit(start, end)144        else:145            return nullcontext()146 147    def require_parens(self, precedence, node):148        """Shortcut to adding precedence related parens"""149        return self.delimit_if("(", ")", self.get_precedence(node) > precedence)150 151    def get_precedence(self, node):152        return self._precedences.get(node, _Precedence.TEST)153 154    def set_precedence(self, precedence, *nodes):155        for node in nodes:156            self._precedences[node] = precedence157 158    def get_raw_docstring(self, node):159        """If a docstring node is found in the body of the *node* parameter,160        return that docstring node, None otherwise.161 162        Logic mirrored from ``_PyAST_GetDocString``."""163        if not isinstance(164            node, (AsyncFunctionDef, FunctionDef, ClassDef, Module)165        ) or len(node.body) < 1:166            return None167        node = node.body[0]168        if not isinstance(node, Expr):169            return None170        node = node.value171        if isinstance(node, Constant) and isinstance(node.value, str):172            return node173 174    def get_type_comment(self, node):175        comment = self._type_ignores.get(node.lineno) or node.type_comment176        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    # Note: as visit() resets the output text, do NOT rely on187    # NodeVisitor.generic_visit to handle any nodes (as it calls back in to188    # the subclass visit() method, which resets self._source to an empty list)189    def visit(self, node):190        """Outputs a source code string that, if converted back to an ast191        (using ast.parse) will generate an AST equivalent to *node*"""192        self._source = []193        self.traverse(node)194        return "".join(self._source)195 196    def _write_docstring_and_traverse_body(self, node):197        if (docstring := self.get_raw_docstring(node)):198            self._write_docstring(docstring)199            self.traverse(node.body[1:])200        else:201            self.traverse(node.body)202 203    def visit_Module(self, node):204        self._type_ignores = {205            ignore.lineno: f"ignore{ignore.tag}"206            for ignore in node.type_ignores207        }208        try:209            self._write_docstring_and_traverse_body(node)210        finally:211            self._type_ignores.clear()212 213    def visit_Interactive(self, node):214        self._in_interactive = True215        try:216            self._write_docstring_and_traverse_body(node)217        finally:218            self._in_interactive = False219 220    def visit_FunctionType(self, node):221        with self.delimit("(", ")"):222            self.interleave(223                lambda: self.write(", "), self.traverse, node.argtypes224            )225 226        self.write(" -> ")227        self.traverse(node.returns)228 229    def visit_Expr(self, node):230        self.fill()231        self.set_precedence(_Precedence.YIELD, node.value)232        self.traverse(node.value)233 234    def visit_NamedExpr(self, node):235        with self.require_parens(_Precedence.NAMED_EXPR, node):236            self.set_precedence(_Precedence.ATOM, node.target, node.value)237            self.traverse(node.target)238            self.write(" := ")239            self.traverse(node.value)240 241    def visit_Import(self, node):242        self.fill("import ")243        self.interleave(lambda: self.write(", "), self.traverse, node.names)244 245    def visit_ImportFrom(self, node):246        self.fill("from ")247        self.write("." * (node.level or 0))248        if node.module:249            self.write(node.module)250        self.write(" import ")251        self.interleave(lambda: self.write(", "), self.traverse, node.names)252 253    def visit_Assign(self, node):254        self.fill()255        for target in node.targets:256            self.set_precedence(_Precedence.TUPLE, target)257            self.traverse(target)258            self.write(" = ")259        self.traverse(node.value)260        if type_comment := self.get_type_comment(node):261            self.write(type_comment)262 263    def visit_AugAssign(self, node):264        self.fill()265        self.traverse(node.target)266        self.write(" " + self.binop[node.op.__class__.__name__] + "= ")267        self.traverse(node.value)268 269    def visit_AnnAssign(self, node):270        self.fill()271        with self.delimit_if("(", ")", not node.simple and isinstance(node.target, Name)):272            self.traverse(node.target)273        self.write(": ")274        self.traverse(node.annotation)275        if node.value:276            self.write(" = ")277            self.traverse(node.value)278 279    def visit_Return(self, node):280        self.fill("return")281        if node.value:282            self.write(" ")283            self.traverse(node.value)284 285    def visit_Pass(self, node):286        self.fill("pass")287 288    def visit_Break(self, node):289        self.fill("break")290 291    def visit_Continue(self, node):292        self.fill("continue")293 294    def visit_Delete(self, node):295        self.fill("del ")296        self.interleave(lambda: self.write(", "), self.traverse, node.targets)297 298    def visit_Assert(self, node):299        self.fill("assert ")300        self.traverse(node.test)301        if node.msg:302            self.write(", ")303            self.traverse(node.msg)304 305    def visit_Global(self, node):306        self.fill("global ")307        self.interleave(lambda: self.write(", "), self.write, node.names)308 309    def visit_Nonlocal(self, node):310        self.fill("nonlocal ")311        self.interleave(lambda: self.write(", "), self.write, node.names)312 313    def visit_Await(self, node):314        with self.require_parens(_Precedence.AWAIT, node):315            self.write("await")316            if node.value:317                self.write(" ")318                self.set_precedence(_Precedence.ATOM, node.value)319                self.traverse(node.value)320 321    def visit_Yield(self, node):322        with self.require_parens(_Precedence.YIELD, node):323            self.write("yield")324            if node.value:325                self.write(" ")326                self.set_precedence(_Precedence.ATOM, node.value)327                self.traverse(node.value)328 329    def visit_YieldFrom(self, node):330        with self.require_parens(_Precedence.YIELD, node):331            self.write("yield from ")332            if not node.value:333                raise ValueError("Node can't be used without a value attribute.")334            self.set_precedence(_Precedence.ATOM, node.value)335            self.traverse(node.value)336 337    def visit_Raise(self, node):338        self.fill("raise")339        if not node.exc:340            if node.cause:341                raise ValueError(f"Node can't use cause without an exception.")342            return343        self.write(" ")344        self.traverse(node.exc)345        if node.cause:346            self.write(" from ")347            self.traverse(node.cause)348 349    def do_visit_try(self, node):350        self.fill("try", allow_semicolon=False)351        with self.block():352            self.traverse(node.body)353        for ex in node.handlers:354            self.traverse(ex)355        if node.orelse:356            self.fill("else", allow_semicolon=False)357            with self.block():358                self.traverse(node.orelse)359        if node.finalbody:360            self.fill("finally", allow_semicolon=False)361            with self.block():362                self.traverse(node.finalbody)363 364    def visit_Try(self, node):365        prev_in_try_star = self._in_try_star366        try:367            self._in_try_star = False368            self.do_visit_try(node)369        finally:370            self._in_try_star = prev_in_try_star371 372    def visit_TryStar(self, node):373        prev_in_try_star = self._in_try_star374        try:375            self._in_try_star = True376            self.do_visit_try(node)377        finally:378            self._in_try_star = prev_in_try_star379 380    def visit_ExceptHandler(self, node):381        self.fill("except*" if self._in_try_star else "except", allow_semicolon=False)382        if node.type:383            self.write(" ")384            self.traverse(node.type)385        if node.name:386            self.write(" as ")387            self.write(node.name)388        with self.block():389            self.traverse(node.body)390 391    def visit_ClassDef(self, node):392        self.maybe_newline()393        for deco in node.decorator_list:394            self.fill("@", allow_semicolon=False)395            self.traverse(deco)396        self.fill("class " + node.name, allow_semicolon=False)397        if hasattr(node, "type_params"):398            self._type_params_helper(node.type_params)399        with self.delimit_if("(", ")", condition = node.bases or node.keywords):400            comma = False401            for e in node.bases:402                if comma:403                    self.write(", ")404                else:405                    comma = True406                self.traverse(e)407            for e in node.keywords:408                if comma:409                    self.write(", ")410                else:411                    comma = True412                self.traverse(e)413 414        with self.block():415            self._write_docstring_and_traverse_body(node)416 417    def visit_FunctionDef(self, node):418        self._function_helper(node, "def")419 420    def visit_AsyncFunctionDef(self, node):421        self._function_helper(node, "async def")422 423    def _function_helper(self, node, fill_suffix):424        self.maybe_newline()425        for deco in node.decorator_list:426            self.fill("@", allow_semicolon=False)427            self.traverse(deco)428        def_str = fill_suffix + " " + node.name429        self.fill(def_str, allow_semicolon=False)430        if hasattr(node, "type_params"):431            self._type_params_helper(node.type_params)432        with self.delimit("(", ")"):433            self.traverse(node.args)434        if node.returns:435            self.write(" -> ")436            self.traverse(node.returns)437        with self.block(extra=self.get_type_comment(node)):438            self._write_docstring_and_traverse_body(node)439 440    def _type_params_helper(self, type_params):441        if type_params is not None and len(type_params) > 0:442            with self.delimit("[", "]"):443                self.interleave(lambda: self.write(", "), self.traverse, type_params)444 445    def visit_TypeVar(self, node):446        self.write(node.name)447        if node.bound:448            self.write(": ")449            self.traverse(node.bound)450        if node.default_value:451            self.write(" = ")452            self.traverse(node.default_value)453 454    def visit_TypeVarTuple(self, node):455        self.write("*" + node.name)456        if node.default_value:457            self.write(" = ")458            self.traverse(node.default_value)459 460    def visit_ParamSpec(self, node):461        self.write("**" + node.name)462        if node.default_value:463            self.write(" = ")464            self.traverse(node.default_value)465 466    def visit_TypeAlias(self, node):467        self.fill("type ")468        self.traverse(node.name)469        self._type_params_helper(node.type_params)470        self.write(" = ")471        self.traverse(node.value)472 473    def visit_For(self, node):474        self._for_helper("for ", node)475 476    def visit_AsyncFor(self, node):477        self._for_helper("async for ", node)478 479    def _for_helper(self, fill, node):480        self.fill(fill, allow_semicolon=False)481        self.set_precedence(_Precedence.TUPLE, node.target)482        self.traverse(node.target)483        self.write(" in ")484        self.traverse(node.iter)485        with self.block(extra=self.get_type_comment(node)):486            self.traverse(node.body)487        if node.orelse:488            self.fill("else", allow_semicolon=False)489            with self.block():490                self.traverse(node.orelse)491 492    def visit_If(self, node):493        self.fill("if ", allow_semicolon=False)494        self.traverse(node.test)495        with self.block():496            self.traverse(node.body)497        # collapse nested ifs into equivalent elifs.498        while node.orelse and len(node.orelse) == 1 and isinstance(node.orelse[0], If):499            node = node.orelse[0]500            self.fill("elif ", allow_semicolon=False)501            self.traverse(node.test)502            with self.block():503                self.traverse(node.body)504        # final else505        if node.orelse:506            self.fill("else", allow_semicolon=False)507            with self.block():508                self.traverse(node.orelse)509 510    def visit_While(self, node):511        self.fill("while ", allow_semicolon=False)512        self.traverse(node.test)513        with self.block():514            self.traverse(node.body)515        if node.orelse:516            self.fill("else", allow_semicolon=False)517            with self.block():518                self.traverse(node.orelse)519 520    def visit_With(self, node):521        self.fill("with ", allow_semicolon=False)522        self.interleave(lambda: self.write(", "), self.traverse, node.items)523        with self.block(extra=self.get_type_comment(node)):524            self.traverse(node.body)525 526    def visit_AsyncWith(self, node):527        self.fill("async with ", allow_semicolon=False)528        self.interleave(lambda: self.write(", "), self.traverse, node.items)529        with self.block(extra=self.get_type_comment(node)):530            self.traverse(node.body)531 532    def _str_literal_helper(533        self, string, *, quote_types=_ALL_QUOTES, escape_special_whitespace=False534    ):535        """Helper for writing string literals, minimizing escapes.536        Returns the tuple (string literal to write, possible quote types).537        """538        def escape_char(c):539            # \n and \t are non-printable, but we only escape them if540            # escape_special_whitespace is True541            if not escape_special_whitespace and c in "\n\t":542                return c543            # Always escape backslashes and other non-printable characters544            if c == "\\" or not c.isprintable():545                return c.encode("unicode_escape").decode("ascii")546            return c547 548        escaped_string = "".join(map(escape_char, string))549        possible_quotes = quote_types550        if "\n" in escaped_string:551            possible_quotes = [q for q in possible_quotes if q in _MULTI_QUOTES]552        possible_quotes = [q for q in possible_quotes if q not in escaped_string]553        if not possible_quotes:554            # If there aren't any possible_quotes, fallback to using repr555            # on the original string. Try to use a quote from quote_types,556            # e.g., so that we use triple quotes for docstrings.557            string = repr(string)558            quote = next((q for q in quote_types if string[0] in q), string[0])559            return string[1:-1], [quote]560        if escaped_string:561            # Sort so that we prefer '''"''' over """\""""562            possible_quotes.sort(key=lambda q: q[0] == escaped_string[-1])563            # If we're using triple quotes and we'd need to escape a final564            # quote, escape it565            if possible_quotes[0][0] == escaped_string[-1]:566                assert len(possible_quotes[0]) == 3567                escaped_string = escaped_string[:-1] + "\\" + escaped_string[-1]568        return escaped_string, possible_quotes569 570    def _write_str_avoiding_backslashes(self, string, *, quote_types=_ALL_QUOTES):571        """Write string literal value with a best effort attempt to avoid backslashes."""572        string, quote_types = self._str_literal_helper(string, quote_types=quote_types)573        quote_type = quote_types[0]574        self.write(f"{quote_type}{string}{quote_type}")575 576    def _ftstring_helper(self, parts):577        new_parts = []578        quote_types = list(_ALL_QUOTES)579        fallback_to_repr = False580        for value, is_constant in parts:581            if is_constant:582                value, new_quote_types = self._str_literal_helper(583                    value,584                    quote_types=quote_types,585                    escape_special_whitespace=True,586                )587                if set(new_quote_types).isdisjoint(quote_types):588                    fallback_to_repr = True589                    break590                quote_types = new_quote_types591            else:592                if "\n" in value:593                    quote_types = [q for q in quote_types if q in _MULTI_QUOTES]594                    assert quote_types595 596                new_quote_types = [q for q in quote_types if q not in value]597                if new_quote_types:598                    quote_types = new_quote_types599            new_parts.append(value)600 601        if fallback_to_repr:602            # If we weren't able to find a quote type that works for all parts603            # of the JoinedStr, fallback to using repr and triple single quotes.604            quote_types = ["'''"]605            new_parts.clear()606            for value, is_constant in parts:607                if is_constant:608                    value = repr('"' + value)  # force repr to use single quotes609                    expected_prefix = "'\""610                    assert value.startswith(expected_prefix), repr(value)611                    value = value[len(expected_prefix):-1]612                new_parts.append(value)613 614        value = "".join(new_parts)615        quote_type = quote_types[0]616        self.write(f"{quote_type}{value}{quote_type}")617 618    def _write_ftstring(self, values, prefix):619        self.write(prefix)620        fstring_parts = []621        for value in values:622            with self.buffered() as buffer:623                self._write_ftstring_inner(value)624            fstring_parts.append(625                ("".join(buffer), isinstance(value, Constant))626            )627        self._ftstring_helper(fstring_parts)628 629    def visit_JoinedStr(self, node):630        self._write_ftstring(node.values, "f")631 632    def visit_TemplateStr(self, node):633        self._write_ftstring(node.values, "t")634 635    def _write_ftstring_inner(self, node, is_format_spec=False):636        if isinstance(node, JoinedStr):637            # for both the f-string itself, and format_spec638            for value in node.values:639                self._write_ftstring_inner(value, is_format_spec=is_format_spec)640        elif isinstance(node, Constant) and isinstance(node.value, str):641            value = node.value.replace("{", "{{").replace("}", "}}")642 643            if is_format_spec:644                value = value.replace("\\", "\\\\")645                value = value.replace("'", "\\'")646                value = value.replace('"', '\\"')647                value = value.replace("\n", "\\n")648            self.write(value)649        elif isinstance(node, FormattedValue):650            self.visit_FormattedValue(node)651        elif isinstance(node, Interpolation):652            self.visit_Interpolation(node)653        else:654            raise ValueError(f"Unexpected node inside JoinedStr, {node!r}")655 656    def _unparse_interpolation_value(self, inner):657        unparser = type(self)()658        unparser.set_precedence(_Precedence.TEST.next(), inner)659        return unparser.visit(inner)660 661    def _write_interpolation(self, node, use_str_attr=False):662        with self.delimit("{", "}"):663            if use_str_attr:664                expr = node.str665            else:666                expr = self._unparse_interpolation_value(node.value)667            if expr.startswith("{"):668                # Separate pair of opening brackets as "{ {"669                self.write(" ")670            self.write(expr)671            if node.conversion != -1:672                self.write(f"!{chr(node.conversion)}")673            if node.format_spec:674                self.write(":")675                self._write_ftstring_inner(node.format_spec, is_format_spec=True)676 677    def visit_FormattedValue(self, node):678        self._write_interpolation(node)679 680    def visit_Interpolation(self, node):681        # If `str` is set to `None`, use the `value` to generate the source code.682        self._write_interpolation(node, use_str_attr=node.str is not None)683 684    def visit_Name(self, node):685        self.write(node.id)686 687    def _write_docstring(self, node):688        self.fill(allow_semicolon=False)689        if node.kind == "u":690            self.write("u")691        self._write_str_avoiding_backslashes(node.value, quote_types=_MULTI_QUOTES)692 693    def _write_constant(self, value):694        if isinstance(value, (float, complex)):695            # Substitute overflowing decimal literal for AST infinities,696            # and inf - inf for NaNs.697            self.write(698                repr(value)699                .replace("inf", _INFSTR)700                .replace("nan", f"({_INFSTR}-{_INFSTR})")701            )702        else:703            self.write(repr(value))704 705    def visit_Constant(self, node):706        value = node.value707        if isinstance(value, tuple):708            with self.delimit("(", ")"):709                self.items_view(self._write_constant, value)710        elif value is ...:711            self.write("...")712        else:713            if node.kind == "u":714                self.write("u")715            self._write_constant(node.value)716 717    def visit_List(self, node):718        with self.delimit("[", "]"):719            self.interleave(lambda: self.write(", "), self.traverse, node.elts)720 721    def visit_ListComp(self, node):722        with self.delimit("[", "]"):723            self.traverse(node.elt)724            for gen in node.generators:725                self.traverse(gen)726 727    def visit_GeneratorExp(self, node):728        with self.delimit("(", ")"):729            self.traverse(node.elt)730            for gen in node.generators:731                self.traverse(gen)732 733    def visit_SetComp(self, node):734        with self.delimit("{", "}"):735            self.traverse(node.elt)736            for gen in node.generators:737                self.traverse(gen)738 739    def visit_DictComp(self, node):740        with self.delimit("{", "}"):741            self.traverse(node.key)742            self.write(": ")743            self.traverse(node.value)744            for gen in node.generators:745                self.traverse(gen)746 747    def visit_comprehension(self, node):748        if node.is_async:749            self.write(" async for ")750        else:751            self.write(" for ")752        self.set_precedence(_Precedence.TUPLE, node.target)753        self.traverse(node.target)754        self.write(" in ")755        self.set_precedence(_Precedence.TEST.next(), node.iter, *node.ifs)756        self.traverse(node.iter)757        for if_clause in node.ifs:758            self.write(" if ")759            self.traverse(if_clause)760 761    def visit_IfExp(self, node):762        with self.require_parens(_Precedence.TEST, node):763            self.set_precedence(_Precedence.TEST.next(), node.body, node.test)764            self.traverse(node.body)765            self.write(" if ")766            self.traverse(node.test)767            self.write(" else ")768            self.set_precedence(_Precedence.TEST, node.orelse)769            self.traverse(node.orelse)770 771    def visit_Set(self, node):772        if node.elts:773            with self.delimit("{", "}"):774                self.interleave(lambda: self.write(", "), self.traverse, node.elts)775        else:776            # `{}` would be interpreted as a dictionary literal, and777            # `set` might be shadowed. Thus:778            self.write('{*()}')779 780    def visit_Dict(self, node):781        def write_key_value_pair(k, v):782            self.traverse(k)783            self.write(": ")784            self.traverse(v)785 786        def write_item(item):787            k, v = item788            if k is None:789                # for dictionary unpacking operator in dicts {**{'y': 2}}790                # see PEP 448 for details791                self.write("**")792                self.set_precedence(_Precedence.EXPR, v)793                self.traverse(v)794            else:795                write_key_value_pair(k, v)796 797        with self.delimit("{", "}"):798            self.interleave(799                lambda: self.write(", "), write_item, zip(node.keys, node.values)800            )801 802    def visit_Tuple(self, node):803        with self.delimit_if(804            "(",805            ")",806            len(node.elts) == 0 or self.get_precedence(node) > _Precedence.TUPLE807        ):808            self.items_view(self.traverse, node.elts)809 810    unop = {"Invert": "~", "Not": "not", "UAdd": "+", "USub": "-"}811    unop_precedence = {812        "not": _Precedence.NOT,813        "~": _Precedence.FACTOR,814        "+": _Precedence.FACTOR,815        "-": _Precedence.FACTOR,816    }817 818    def visit_UnaryOp(self, node):819        operator = self.unop[node.op.__class__.__name__]820        operator_precedence = self.unop_precedence[operator]821        with self.require_parens(operator_precedence, node):822            self.write(operator)823            # factor prefixes (+, -, ~) shouldn't be separated824            # from the value they belong, (e.g: +1 instead of + 1)825            if operator_precedence is not _Precedence.FACTOR:826                self.write(" ")827            self.set_precedence(operator_precedence, node.operand)828            self.traverse(node.operand)829 830    binop = {831        "Add": "+",832        "Sub": "-",833        "Mult": "*",834        "MatMult": "@",835        "Div": "/",836        "Mod": "%",837        "LShift": "<<",838        "RShift": ">>",839        "BitOr": "|",840        "BitXor": "^",841        "BitAnd": "&",842        "FloorDiv": "//",843        "Pow": "**",844    }845 846    binop_precedence = {847        "+": _Precedence.ARITH,848        "-": _Precedence.ARITH,849        "*": _Precedence.TERM,850        "@": _Precedence.TERM,851        "/": _Precedence.TERM,852        "%": _Precedence.TERM,853        "<<": _Precedence.SHIFT,854        ">>": _Precedence.SHIFT,855        "|": _Precedence.BOR,856        "^": _Precedence.BXOR,857        "&": _Precedence.BAND,858        "//": _Precedence.TERM,859        "**": _Precedence.POWER,860    }861 862    binop_rassoc = frozenset(("**",))863    def visit_BinOp(self, node):864        operator = self.binop[node.op.__class__.__name__]865        operator_precedence = self.binop_precedence[operator]866        with self.require_parens(operator_precedence, node):867            if operator in self.binop_rassoc:868                left_precedence = operator_precedence.next()869                right_precedence = operator_precedence870            else:871                left_precedence = operator_precedence872                right_precedence = operator_precedence.next()873 874            self.set_precedence(left_precedence, node.left)875            self.traverse(node.left)876            self.write(f" {operator} ")877            self.set_precedence(right_precedence, node.right)878            self.traverse(node.right)879 880    cmpops = {881        "Eq": "==",882        "NotEq": "!=",883        "Lt": "<",884        "LtE": "<=",885        "Gt": ">",886        "GtE": ">=",887        "Is": "is",888        "IsNot": "is not",889        "In": "in",890        "NotIn": "not in",891    }892 893    def visit_Compare(self, node):894        with self.require_parens(_Precedence.CMP, node):895            self.set_precedence(_Precedence.CMP.next(), node.left, *node.comparators)896            self.traverse(node.left)897            for o, e in zip(node.ops, node.comparators):898                self.write(" " + self.cmpops[o.__class__.__name__] + " ")899                self.traverse(e)900 901    boolops = {"And": "and", "Or": "or"}902    boolop_precedence = {"and": _Precedence.AND, "or": _Precedence.OR}903 904    def visit_BoolOp(self, node):905        operator = self.boolops[node.op.__class__.__name__]906        operator_precedence = self.boolop_precedence[operator]907 908        def increasing_level_traverse(node):909            nonlocal operator_precedence910            operator_precedence = operator_precedence.next()911            self.set_precedence(operator_precedence, node)912            self.traverse(node)913 914        with self.require_parens(operator_precedence, node):915            s = f" {operator} "916            self.interleave(lambda: self.write(s), increasing_level_traverse, node.values)917 918    def visit_Attribute(self, node):919        self.set_precedence(_Precedence.ATOM, node.value)920        self.traverse(node.value)921        # Special case: 3.__abs__() is a syntax error, so if node.value922        # is an integer literal then we need to either parenthesize923        # it or add an extra space to get 3 .__abs__().924        if isinstance(node.value, Constant) and isinstance(node.value.value, int):925            self.write(" ")926        self.write(".")927        self.write(node.attr)928 929    def visit_Call(self, node):930        self.set_precedence(_Precedence.ATOM, node.func)931        self.traverse(node.func)932        with self.delimit("(", ")"):933            comma = False934            for e in node.args:935                if comma:936                    self.write(", ")937                else:938                    comma = True939                self.traverse(e)940            for e in node.keywords:941                if comma:942                    self.write(", ")943                else:944                    comma = True945                self.traverse(e)946 947    def visit_Subscript(self, node):948        def is_non_empty_tuple(slice_value):949            return (950                isinstance(slice_value, Tuple)951                and slice_value.elts952            )953 954        self.set_precedence(_Precedence.ATOM, node.value)955        self.traverse(node.value)956        with self.delimit("[", "]"):957            if is_non_empty_tuple(node.slice):958                # parentheses can be omitted if the tuple isn't empty959                self.items_view(self.traverse, node.slice.elts)960            else:961                self.traverse(node.slice)962 963    def visit_Starred(self, node):964        self.write("*")965        self.set_precedence(_Precedence.EXPR, node.value)966        self.traverse(node.value)967 968    def visit_Ellipsis(self, node):969        self.write("...")970 971    def visit_Slice(self, node):972        if node.lower:973            self.traverse(node.lower)974        self.write(":")975        if node.upper:976            self.traverse(node.upper)977        if node.step:978            self.write(":")979            self.traverse(node.step)980 981    def visit_Match(self, node):982        self.fill("match ", allow_semicolon=False)983        self.traverse(node.subject)984        with self.block():985            for case in node.cases:986                self.traverse(case)987 988    def visit_arg(self, node):989        self.write(node.arg)990        if node.annotation:991            self.write(": ")992            self.traverse(node.annotation)993 994    def visit_arguments(self, node):995        first = True996        # normal arguments997        all_args = node.posonlyargs + node.args998        defaults = [None] * (len(all_args) - len(node.defaults)) + node.defaults999        for index, elements in enumerate(zip(all_args, defaults), 1):1000            a, d = elements1001            if first:1002                first = False1003            else:1004                self.write(", ")1005            self.traverse(a)1006            if d:1007                self.write("=")1008                self.traverse(d)1009            if index == len(node.posonlyargs):1010                self.write(", /")1011 1012        # varargs, or bare '*' if no varargs but keyword-only arguments present1013        if node.vararg or node.kwonlyargs:1014            if first:1015                first = False1016            else:1017                self.write(", ")1018            self.write("*")1019            if node.vararg:1020                self.write(node.vararg.arg)1021                if node.vararg.annotation:1022                    self.write(": ")1023                    self.traverse(node.vararg.annotation)1024 1025        # keyword-only arguments1026        if node.kwonlyargs:1027            for a, d in zip(node.kwonlyargs, node.kw_defaults):1028                self.write(", ")1029                self.traverse(a)1030                if d:1031                    self.write("=")1032                    self.traverse(d)1033 1034        # kwargs1035        if node.kwarg:1036            if first:1037                first = False1038            else:1039                self.write(", ")1040            self.write("**" + node.kwarg.arg)1041            if node.kwarg.annotation:1042                self.write(": ")1043                self.traverse(node.kwarg.annotation)1044 1045    def visit_keyword(self, node):1046        if node.arg is None:1047            self.write("**")1048        else:1049            self.write(node.arg)1050            self.write("=")1051        self.traverse(node.value)1052 1053    def visit_Lambda(self, node):1054        with self.require_parens(_Precedence.TEST, node):1055            self.write("lambda")1056            with self.buffered() as buffer:1057                self.traverse(node.args)1058            if buffer:1059                self.write(" ", *buffer)1060            self.write(": ")1061            self.set_precedence(_Precedence.TEST, node.body)1062            self.traverse(node.body)1063 1064    def visit_alias(self, node):1065        self.write(node.name)1066        if node.asname:1067            self.write(" as " + node.asname)1068 1069    def visit_withitem(self, node):1070        self.traverse(node.context_expr)1071        if node.optional_vars:1072            self.write(" as ")1073            self.traverse(node.optional_vars)1074 1075    def visit_match_case(self, node):1076        self.fill("case ", allow_semicolon=False)1077        self.traverse(node.pattern)1078        if node.guard:1079            self.write(" if ")1080            self.traverse(node.guard)1081        with self.block():1082            self.traverse(node.body)1083 1084    def visit_MatchValue(self, node):1085        self.traverse(node.value)1086 1087    def visit_MatchSingleton(self, node):1088        self._write_constant(node.value)1089 1090    def visit_MatchSequence(self, node):1091        with self.delimit("[", "]"):1092            self.interleave(1093                lambda: self.write(", "), self.traverse, node.patterns1094            )1095 1096    def visit_MatchStar(self, node):1097        name = node.name1098        if name is None:1099            name = "_"1100        self.write(f"*{name}")1101 1102    def visit_MatchMapping(self, node):1103        def write_key_pattern_pair(pair):1104            k, p = pair1105            self.traverse(k)1106            self.write(": ")1107            self.traverse(p)1108 1109        with self.delimit("{", "}"):1110            keys = node.keys1111            self.interleave(1112                lambda: self.write(", "),1113                write_key_pattern_pair,1114                zip(keys, node.patterns, strict=True),1115            )1116            rest = node.rest1117            if rest is not None:1118                if keys:1119                    self.write(", ")1120                self.write(f"**{rest}")1121 1122    def visit_MatchClass(self, node):1123        self.set_precedence(_Precedence.ATOM, node.cls)1124        self.traverse(node.cls)1125        with self.delimit("(", ")"):1126            patterns = node.patterns1127            self.interleave(1128                lambda: self.write(", "), self.traverse, patterns1129            )1130            attrs = node.kwd_attrs1131            if attrs:1132                def write_attr_pattern(pair):1133                    attr, pattern = pair1134                    self.write(f"{attr}=")1135                    self.traverse(pattern)1136 1137                if patterns:1138                    self.write(", ")1139                self.interleave(1140                    lambda: self.write(", "),1141                    write_attr_pattern,1142                    zip(attrs, node.kwd_patterns, strict=True),1143                )1144 1145    def visit_MatchAs(self, node):1146        name = node.name1147        pattern = node.pattern1148        if name is None:1149            self.write("_")1150        elif pattern is None:1151            self.write(node.name)1152        else:1153            with self.require_parens(_Precedence.TEST, node):1154                self.set_precedence(_Precedence.BOR, node.pattern)1155                self.traverse(node.pattern)1156                self.write(f" as {node.name}")1157 1158    def visit_MatchOr(self, node):1159        with self.require_parens(_Precedence.BOR, node):1160            self.set_precedence(_Precedence.BOR.next(), *node.patterns)1161            self.interleave(lambda: self.write(" | "), self.traverse, node.patterns)1162 
codekingpro/portable-devtools · Team Ai