Spaces:
Runtime error
Runtime error
| # mypy: disable-error-code=no-untyped-def | |
| # Because Python >3.9 doesn't support ast.unparse, | |
| # we copied the unparse functionality from here: | |
| # https://github.com/python/cpython/blob/3.8/Tools/parser/unparse.py | |
| "Usage: unparse.py <path to source file>" | |
| import ast | |
| import io | |
| import sys | |
| import tokenize | |
| # Large float and imaginary literals get turned into infinities in the AST. | |
| # We unparse those infinities to INFSTR. | |
| INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1) | |
| def interleave(inter, f, seq): | |
| """Call f on each item in seq, calling inter() in between.""" | |
| seq = iter(seq) | |
| try: | |
| f(next(seq)) | |
| except StopIteration: | |
| pass | |
| else: | |
| for x in seq: | |
| inter() | |
| f(x) | |
| class Unparser: | |
| """Methods in this class recursively traverse an AST and | |
| output source code for the abstract syntax; original formatting | |
| is disregarded.""" | |
| def __init__(self, tree, file=sys.stdout): | |
| """Unparser(tree, file=sys.stdout) -> None. | |
| Print the source for tree to file.""" | |
| self.f = file | |
| self._indent = 0 | |
| self.dispatch(tree) | |
| self.f.flush() | |
| def fill(self, text=""): | |
| "Indent a piece of text, according to the current indentation level" | |
| self.f.write("\n" + " " * self._indent + text) | |
| def write(self, text): | |
| "Append a piece of text to the current line." | |
| self.f.write(text) | |
| def enter(self): | |
| "Print ':', and increase the indentation." | |
| self.write(":") | |
| self._indent += 1 | |
| def leave(self): | |
| "Decrease the indentation level." | |
| self._indent -= 1 | |
| def dispatch(self, tree): | |
| "Dispatcher function, dispatching tree type T to method _T." | |
| if isinstance(tree, list): | |
| for t in tree: | |
| self.dispatch(t) | |
| return | |
| meth = getattr(self, "_" + tree.__class__.__name__) | |
| meth(tree) | |
| ############### Unparsing methods ###################### | |
| # There should be one method per concrete grammar type # | |
| # Constructors should be grouped by sum type. Ideally, # | |
| # this would follow the order in the grammar, but # | |
| # currently doesn't. # | |
| ######################################################## | |
| def _Module(self, tree): | |
| for stmt in tree.body: | |
| self.dispatch(stmt) | |
| # stmt | |
| def _Expr(self, tree): | |
| self.fill() | |
| self.dispatch(tree.value) | |
| def _NamedExpr(self, tree): | |
| self.write("(") | |
| self.dispatch(tree.target) | |
| self.write(" := ") | |
| self.dispatch(tree.value) | |
| self.write(")") | |
| def _Import(self, t): | |
| self.fill("import ") | |
| interleave(lambda: self.write(", "), self.dispatch, t.names) | |
| def _ImportFrom(self, t): | |
| self.fill("from ") | |
| self.write("." * t.level) | |
| if t.module: | |
| self.write(t.module) | |
| self.write(" import ") | |
| interleave(lambda: self.write(", "), self.dispatch, t.names) | |
| def _Assign(self, t): | |
| self.fill() | |
| for target in t.targets: | |
| self.dispatch(target) | |
| self.write(" = ") | |
| self.dispatch(t.value) | |
| def _AugAssign(self, t): | |
| self.fill() | |
| self.dispatch(t.target) | |
| self.write(" " + self.binop[t.op.__class__.__name__] + "= ") | |
| self.dispatch(t.value) | |
| def _AnnAssign(self, t): | |
| self.fill() | |
| if not t.simple and isinstance(t.target, ast.Name): | |
| self.write("(") | |
| self.dispatch(t.target) | |
| if not t.simple and isinstance(t.target, ast.Name): | |
| self.write(")") | |
| self.write(": ") | |
| self.dispatch(t.annotation) | |
| if t.value: | |
| self.write(" = ") | |
| self.dispatch(t.value) | |
| def _Return(self, t): | |
| self.fill("return") | |
| if t.value: | |
| self.write(" ") | |
| self.dispatch(t.value) | |
| def _Pass(self, t): | |
| self.fill("pass") | |
| def _Break(self, t): | |
| self.fill("break") | |
| def _Continue(self, t): | |
| self.fill("continue") | |
| def _Delete(self, t): | |
| self.fill("del ") | |
| interleave(lambda: self.write(", "), self.dispatch, t.targets) | |
| def _Assert(self, t): | |
| self.fill("assert ") | |
| self.dispatch(t.test) | |
| if t.msg: | |
| self.write(", ") | |
| self.dispatch(t.msg) | |
| def _Global(self, t): | |
| self.fill("global ") | |
| interleave(lambda: self.write(", "), self.write, t.names) | |
| def _Nonlocal(self, t): | |
| self.fill("nonlocal ") | |
| interleave(lambda: self.write(", "), self.write, t.names) | |
| def _Await(self, t): | |
| self.write("(") | |
| self.write("await") | |
| if t.value: | |
| self.write(" ") | |
| self.dispatch(t.value) | |
| self.write(")") | |
| def _Yield(self, t): | |
| self.write("(") | |
| self.write("yield") | |
| if t.value: | |
| self.write(" ") | |
| self.dispatch(t.value) | |
| self.write(")") | |
| def _YieldFrom(self, t): | |
| self.write("(") | |
| self.write("yield from") | |
| if t.value: | |
| self.write(" ") | |
| self.dispatch(t.value) | |
| self.write(")") | |
| def _Raise(self, t): | |
| self.fill("raise") | |
| if not t.exc: | |
| assert not t.cause | |
| return | |
| self.write(" ") | |
| self.dispatch(t.exc) | |
| if t.cause: | |
| self.write(" from ") | |
| self.dispatch(t.cause) | |
| def _Try(self, t): | |
| self.fill("try") | |
| self.enter() | |
| self.dispatch(t.body) | |
| self.leave() | |
| for ex in t.handlers: | |
| self.dispatch(ex) | |
| if t.orelse: | |
| self.fill("else") | |
| self.enter() | |
| self.dispatch(t.orelse) | |
| self.leave() | |
| if t.finalbody: | |
| self.fill("finally") | |
| self.enter() | |
| self.dispatch(t.finalbody) | |
| self.leave() | |
| def _ExceptHandler(self, t): | |
| self.fill("except") | |
| if t.type: | |
| self.write(" ") | |
| self.dispatch(t.type) | |
| if t.name: | |
| self.write(" as ") | |
| self.write(t.name) | |
| self.enter() | |
| self.dispatch(t.body) | |
| self.leave() | |
| def _ClassDef(self, t): | |
| self.write("\n") | |
| for deco in t.decorator_list: | |
| self.fill("@") | |
| self.dispatch(deco) | |
| self.fill("class " + t.name) | |
| self.write("(") | |
| comma = False | |
| for e in t.bases: | |
| if comma: | |
| self.write(", ") | |
| else: | |
| comma = True | |
| self.dispatch(e) | |
| for e in t.keywords: | |
| if comma: | |
| self.write(", ") | |
| else: | |
| comma = True | |
| self.dispatch(e) | |
| self.write(")") | |
| self.enter() | |
| self.dispatch(t.body) | |
| self.leave() | |
| def _FunctionDef(self, t): | |
| self.__FunctionDef_helper(t, "def") | |
| def _AsyncFunctionDef(self, t): | |
| self.__FunctionDef_helper(t, "async def") | |
| def __FunctionDef_helper(self, t, fill_suffix): | |
| self.write("\n") | |
| for deco in t.decorator_list: | |
| self.fill("@") | |
| self.dispatch(deco) | |
| def_str = fill_suffix + " " + t.name + "(" | |
| self.fill(def_str) | |
| self.dispatch(t.args) | |
| self.write(")") | |
| if t.returns: | |
| self.write(" -> ") | |
| self.dispatch(t.returns) | |
| self.enter() | |
| self.dispatch(t.body) | |
| self.leave() | |
| def _For(self, t): | |
| self.__For_helper("for ", t) | |
| def _AsyncFor(self, t): | |
| self.__For_helper("async for ", t) | |
| def __For_helper(self, fill, t): | |
| self.fill(fill) | |
| self.dispatch(t.target) | |
| self.write(" in ") | |
| self.dispatch(t.iter) | |
| self.enter() | |
| self.dispatch(t.body) | |
| self.leave() | |
| if t.orelse: | |
| self.fill("else") | |
| self.enter() | |
| self.dispatch(t.orelse) | |
| self.leave() | |
| def _If(self, t): | |
| self.fill("if ") | |
| self.dispatch(t.test) | |
| self.enter() | |
| self.dispatch(t.body) | |
| self.leave() | |
| # collapse nested ifs into equivalent elifs. | |
| while t.orelse and len(t.orelse) == 1 and isinstance(t.orelse[0], ast.If): | |
| t = t.orelse[0] | |
| self.fill("elif ") | |
| self.dispatch(t.test) | |
| self.enter() | |
| self.dispatch(t.body) | |
| self.leave() | |
| # final else | |
| if t.orelse: | |
| self.fill("else") | |
| self.enter() | |
| self.dispatch(t.orelse) | |
| self.leave() | |
| def _While(self, t): | |
| self.fill("while ") | |
| self.dispatch(t.test) | |
| self.enter() | |
| self.dispatch(t.body) | |
| self.leave() | |
| if t.orelse: | |
| self.fill("else") | |
| self.enter() | |
| self.dispatch(t.orelse) | |
| self.leave() | |
| def _With(self, t): | |
| self.fill("with ") | |
| interleave(lambda: self.write(", "), self.dispatch, t.items) | |
| self.enter() | |
| self.dispatch(t.body) | |
| self.leave() | |
| def _AsyncWith(self, t): | |
| self.fill("async with ") | |
| interleave(lambda: self.write(", "), self.dispatch, t.items) | |
| self.enter() | |
| self.dispatch(t.body) | |
| self.leave() | |
| # expr | |
| def _JoinedStr(self, t): | |
| self.write("f") | |
| string = io.StringIO() | |
| self._fstring_JoinedStr(t, string.write) | |
| self.write(repr(string.getvalue())) | |
| def _FormattedValue(self, t): | |
| self.write("f") | |
| string = io.StringIO() | |
| self._fstring_FormattedValue(t, string.write) | |
| self.write(repr(string.getvalue())) | |
| def _fstring_JoinedStr(self, t, write): | |
| for value in t.values: | |
| meth = getattr(self, "_fstring_" + type(value).__name__) | |
| meth(value, write) | |
| def _fstring_Constant(self, t, write): | |
| assert isinstance(t.value, str) | |
| value = t.value.replace("{", "{{").replace("}", "}}") | |
| write(value) | |
| def _fstring_FormattedValue(self, t, write): | |
| write("{") | |
| expr = io.StringIO() | |
| Unparser(t.value, expr) | |
| expr = expr.getvalue().rstrip("\n") | |
| if expr.startswith("{"): | |
| write(" ") # Separate pair of opening brackets as "{ {" | |
| write(expr) | |
| if t.conversion != -1: | |
| conversion = chr(t.conversion) | |
| assert conversion in "sra" | |
| write(f"!{conversion}") | |
| if t.format_spec: | |
| write(":") | |
| meth = getattr(self, "_fstring_" + type(t.format_spec).__name__) | |
| meth(t.format_spec, write) | |
| write("}") | |
| def _Name(self, t): | |
| self.write(t.id) | |
| def _write_constant(self, value): | |
| if isinstance(value, (float, complex)): | |
| # Substitute overflowing decimal literal for AST infinities. | |
| self.write(repr(value).replace("inf", INFSTR)) | |
| else: | |
| self.write(repr(value)) | |
| def _Constant(self, t): | |
| value = t.value | |
| if isinstance(value, tuple): | |
| self.write("(") | |
| if len(value) == 1: | |
| self._write_constant(value[0]) | |
| self.write(",") | |
| else: | |
| interleave(lambda: self.write(", "), self._write_constant, value) | |
| self.write(")") | |
| elif value is ...: | |
| self.write("...") | |
| else: | |
| if t.kind == "u": | |
| self.write("u") | |
| self._write_constant(t.value) | |
| def _List(self, t): | |
| self.write("[") | |
| interleave(lambda: self.write(", "), self.dispatch, t.elts) | |
| self.write("]") | |
| def _ListComp(self, t): | |
| self.write("[") | |
| self.dispatch(t.elt) | |
| for gen in t.generators: | |
| self.dispatch(gen) | |
| self.write("]") | |
| def _GeneratorExp(self, t): | |
| self.write("(") | |
| self.dispatch(t.elt) | |
| for gen in t.generators: | |
| self.dispatch(gen) | |
| self.write(")") | |
| def _SetComp(self, t): | |
| self.write("{") | |
| self.dispatch(t.elt) | |
| for gen in t.generators: | |
| self.dispatch(gen) | |
| self.write("}") | |
| def _DictComp(self, t): | |
| self.write("{") | |
| self.dispatch(t.key) | |
| self.write(": ") | |
| self.dispatch(t.value) | |
| for gen in t.generators: | |
| self.dispatch(gen) | |
| self.write("}") | |
| def _comprehension(self, t): | |
| if t.is_async: | |
| self.write(" async for ") | |
| else: | |
| self.write(" for ") | |
| self.dispatch(t.target) | |
| self.write(" in ") | |
| self.dispatch(t.iter) | |
| for if_clause in t.ifs: | |
| self.write(" if ") | |
| self.dispatch(if_clause) | |
| def _IfExp(self, t): | |
| self.write("(") | |
| self.dispatch(t.body) | |
| self.write(" if ") | |
| self.dispatch(t.test) | |
| self.write(" else ") | |
| self.dispatch(t.orelse) | |
| self.write(")") | |
| def _Set(self, t): | |
| assert t.elts # should be at least one element | |
| self.write("{") | |
| interleave(lambda: self.write(", "), self.dispatch, t.elts) | |
| self.write("}") | |
| def _Dict(self, t): | |
| self.write("{") | |
| def write_key_value_pair(k, v): | |
| self.dispatch(k) | |
| self.write(": ") | |
| self.dispatch(v) | |
| def write_item(item): | |
| k, v = item | |
| if k is None: | |
| # for dictionary unpacking operator in dicts {**{'y': 2}} | |
| # see PEP 448 for details | |
| self.write("**") | |
| self.dispatch(v) | |
| else: | |
| write_key_value_pair(k, v) | |
| interleave(lambda: self.write(", "), write_item, zip(t.keys, t.values)) | |
| self.write("}") | |
| def _Tuple(self, t): | |
| self.write("(") | |
| if len(t.elts) == 1: | |
| elt = t.elts[0] | |
| self.dispatch(elt) | |
| self.write(",") | |
| else: | |
| interleave(lambda: self.write(", "), self.dispatch, t.elts) | |
| self.write(")") | |
| unop = {"Invert": "~", "Not": "not", "UAdd": "+", "USub": "-"} | |
| def _UnaryOp(self, t): | |
| self.write("(") | |
| self.write(self.unop[t.op.__class__.__name__]) | |
| self.write(" ") | |
| self.dispatch(t.operand) | |
| self.write(")") | |
| binop = { | |
| "Add": "+", | |
| "Sub": "-", | |
| "Mult": "*", | |
| "MatMult": "@", | |
| "Div": "/", | |
| "Mod": "%", | |
| "LShift": "<<", | |
| "RShift": ">>", | |
| "BitOr": "|", | |
| "BitXor": "^", | |
| "BitAnd": "&", | |
| "FloorDiv": "//", | |
| "Pow": "**", | |
| } | |
| def _BinOp(self, t): | |
| self.write("(") | |
| self.dispatch(t.left) | |
| self.write(" " + self.binop[t.op.__class__.__name__] + " ") | |
| self.dispatch(t.right) | |
| self.write(")") | |
| cmpops = { | |
| "Eq": "==", | |
| "NotEq": "!=", | |
| "Lt": "<", | |
| "LtE": "<=", | |
| "Gt": ">", | |
| "GtE": ">=", | |
| "Is": "is", | |
| "IsNot": "is not", | |
| "In": "in", | |
| "NotIn": "not in", | |
| } | |
| def _Compare(self, t): | |
| self.write("(") | |
| self.dispatch(t.left) | |
| for o, e in zip(t.ops, t.comparators): | |
| self.write(" " + self.cmpops[o.__class__.__name__] + " ") | |
| self.dispatch(e) | |
| self.write(")") | |
| boolops = {ast.And: "and", ast.Or: "or"} | |
| def _BoolOp(self, t): | |
| self.write("(") | |
| s = " %s " % self.boolops[t.op.__class__] | |
| interleave(lambda: self.write(s), self.dispatch, t.values) | |
| self.write(")") | |
| def _Attribute(self, t): | |
| self.dispatch(t.value) | |
| # Special case: 3.__abs__() is a syntax error, so if t.value | |
| # is an integer literal then we need to either parenthesize | |
| # it or add an extra space to get 3 .__abs__(). | |
| if isinstance(t.value, ast.Constant) and isinstance(t.value.value, int): | |
| self.write(" ") | |
| self.write(".") | |
| self.write(t.attr) | |
| def _Call(self, t): | |
| self.dispatch(t.func) | |
| self.write("(") | |
| comma = False | |
| for e in t.args: | |
| if comma: | |
| self.write(", ") | |
| else: | |
| comma = True | |
| self.dispatch(e) | |
| for e in t.keywords: | |
| if comma: | |
| self.write(", ") | |
| else: | |
| comma = True | |
| self.dispatch(e) | |
| self.write(")") | |
| def _Subscript(self, t): | |
| self.dispatch(t.value) | |
| self.write("[") | |
| if ( | |
| isinstance(t.slice, ast.Index) | |
| and isinstance(t.slice.value, ast.Tuple) | |
| and t.slice.value.elts | |
| ): | |
| if len(t.slice.value.elts) == 1: | |
| elt = t.slice.value.elts[0] | |
| self.dispatch(elt) | |
| self.write(",") | |
| else: | |
| interleave(lambda: self.write(", "), self.dispatch, t.slice.value.elts) | |
| else: | |
| self.dispatch(t.slice) | |
| self.write("]") | |
| def _Starred(self, t): | |
| self.write("*") | |
| self.dispatch(t.value) | |
| # slice | |
| def _Ellipsis(self, t): | |
| self.write("...") | |
| def _Index(self, t): | |
| self.dispatch(t.value) | |
| def _Slice(self, t): | |
| if t.lower: | |
| self.dispatch(t.lower) | |
| self.write(":") | |
| if t.upper: | |
| self.dispatch(t.upper) | |
| if t.step: | |
| self.write(":") | |
| self.dispatch(t.step) | |
| def _ExtSlice(self, t): | |
| if len(t.dims) == 1: | |
| elt = t.dims[0] | |
| self.dispatch(elt) | |
| self.write(",") | |
| else: | |
| interleave(lambda: self.write(", "), self.dispatch, t.dims) | |
| # argument | |
| def _arg(self, t): | |
| self.write(t.arg) | |
| if t.annotation: | |
| self.write(": ") | |
| self.dispatch(t.annotation) | |
| # others | |
| def _arguments(self, t): | |
| first = True | |
| # normal arguments | |
| all_args = t.posonlyargs + t.args | |
| defaults = [None] * (len(all_args) - len(t.defaults)) + t.defaults | |
| for index, elements in enumerate(zip(all_args, defaults), 1): | |
| a, d = elements | |
| if first: | |
| first = False | |
| else: | |
| self.write(", ") | |
| self.dispatch(a) | |
| if d: | |
| self.write("=") | |
| self.dispatch(d) | |
| if index == len(t.posonlyargs): | |
| self.write(", /") | |
| # varargs, or bare '*' if no varargs but keyword-only arguments present | |
| if t.vararg or t.kwonlyargs: | |
| if first: | |
| first = False | |
| else: | |
| self.write(", ") | |
| self.write("*") | |
| if t.vararg: | |
| self.write(t.vararg.arg) | |
| if t.vararg.annotation: | |
| self.write(": ") | |
| self.dispatch(t.vararg.annotation) | |
| # keyword-only arguments | |
| if t.kwonlyargs: | |
| for a, d in zip(t.kwonlyargs, t.kw_defaults): | |
| if first: | |
| first = False | |
| else: | |
| self.write(", ") | |
| self.dispatch(a) | |
| if d: | |
| self.write("=") | |
| self.dispatch(d) | |
| # kwargs | |
| if t.kwarg: | |
| if first: | |
| first = False | |
| else: | |
| self.write(", ") | |
| self.write("**" + t.kwarg.arg) | |
| if t.kwarg.annotation: | |
| self.write(": ") | |
| self.dispatch(t.kwarg.annotation) | |
| def _keyword(self, t): | |
| if t.arg is None: | |
| self.write("**") | |
| else: | |
| self.write(t.arg) | |
| self.write("=") | |
| self.dispatch(t.value) | |
| def _Lambda(self, t): | |
| self.write("(") | |
| self.write("lambda ") | |
| self.dispatch(t.args) | |
| self.write(": ") | |
| self.dispatch(t.body) | |
| self.write(")") | |
| def _alias(self, t): | |
| self.write(t.name) | |
| if t.asname: | |
| self.write(" as " + t.asname) | |
| def _withitem(self, t): | |
| self.dispatch(t.context_expr) | |
| if t.optional_vars: | |
| self.write(" as ") | |
| self.dispatch(t.optional_vars) | |
| def roundtrip(filename, output=sys.stdout): | |
| with open(filename, "rb") as pyfile: | |
| encoding = tokenize.detect_encoding(pyfile.readline)[0] | |
| with open(filename, "r", encoding=encoding) as pyfile: | |
| source = pyfile.read() | |
| tree = compile(source, filename, "exec", ast.PyCF_ONLY_AST) | |
| Unparser(tree, output) | |