diff options
Diffstat (limited to 'Demo/parser/unparse.py')
-rw-r--r-- | Demo/parser/unparse.py | 154 |
1 files changed, 116 insertions, 38 deletions
diff --git a/Demo/parser/unparse.py b/Demo/parser/unparse.py index 98aeb32..1b1ff29 100644 --- a/Demo/parser/unparse.py +++ b/Demo/parser/unparse.py @@ -1,15 +1,19 @@ "Usage: unparse.py <path to source file>" import sys -import _ast +import ast import cStringIO import os +# 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(seq.next()) + f(next(seq)) except StopIteration: pass else: @@ -20,15 +24,16 @@ def interleave(inter, f, seq): class Unparser: """Methods in this class recursively traverse an AST and output source code for the abstract syntax; original formatting - is disregarged. """ + 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.future_imports = [] self._indent = 0 self.dispatch(tree) - print >>self.f,"" + self.f.write("") self.f.flush() def fill(self, text = ""): @@ -79,11 +84,16 @@ class Unparser: interleave(lambda: self.write(", "), self.dispatch, t.names) def _ImportFrom(self, t): + # A from __future__ import may affect unparsing, so record it. + if t.module and t.module == '__future__': + self.future_imports.extend(n.name for n in t.names) + self.fill("from ") - self.write(t.module) + self.write("." * t.level) + if t.module: + self.write(t.module) self.write(" import ") interleave(lambda: self.write(", "), self.dispatch, t.names) - # XXX(jpe) what is level for? def _Assign(self, t): self.fill() @@ -186,10 +196,14 @@ class Unparser: self.leave() def _TryFinally(self, t): - self.fill("try") - self.enter() - self.dispatch(t.body) - self.leave() + if len(t.body) == 1 and isinstance(t.body[0], ast.TryExcept): + # try-except-finally + self.dispatch(t.body) + else: + self.fill("try") + self.enter() + self.dispatch(t.body) + self.leave() self.fill("finally") self.enter() @@ -202,7 +216,7 @@ class Unparser: self.write(" ") self.dispatch(t.type) if t.name: - self.write(", ") + self.write(" as ") self.dispatch(t.name) self.enter() self.dispatch(t.body) @@ -210,6 +224,9 @@ class Unparser: def _ClassDef(self, t): self.write("\n") + for deco in t.decorator_list: + self.fill("@") + self.dispatch(deco) self.fill("class "+t.name) if t.bases: self.write("(") @@ -251,9 +268,18 @@ class Unparser: self.fill("if ") self.dispatch(t.test) self.enter() - # XXX elif? 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() @@ -284,7 +310,17 @@ class Unparser: # expr def _Str(self, tree): - self.write(repr(tree.s)) + # if from __future__ import unicode_literals is in effect, + # then we want to output string literals using a 'b' prefix + # and unicode literals with no prefix. + if "unicode_literals" not in self.future_imports: + self.write(repr(tree.s)) + elif isinstance(tree.s, str): + self.write("b" + repr(tree.s)) + elif isinstance(tree.s, unicode): + self.write(repr(tree.s).lstrip("u")) + else: + assert False, "shouldn't get here" def _Name(self, t): self.write(t.id) @@ -295,16 +331,14 @@ class Unparser: self.write("`") def _Num(self, t): - # There are no negative numeric literals in Python; however, - # some optimizations produce a negative Num in the AST. Add - # parentheses to avoid turning (-1)**2 into -1**2. - strnum = repr(t.n) - if strnum.startswith("-"): + repr_n = repr(t.n) + # Parenthesize negative numbers, to avoid turning (-1)**2 into -1**2. + if repr_n.startswith("-"): self.write("(") - self.write(strnum) + # Substitute overflowing decimal literal for AST infinities. + self.write(repr_n.replace("inf", INFSTR)) + if repr_n.startswith("-"): self.write(")") - else: - self.write(repr(t.n)) def _List(self, t): self.write("[") @@ -325,6 +359,22 @@ class Unparser: 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): self.write(" for ") self.dispatch(t.target) @@ -343,13 +393,20 @@ class Unparser: 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 writem((k, v)): + def write_pair(pair): + (k, v) = pair self.dispatch(k) self.write(": ") self.dispatch(v) - interleave(lambda: self.write(", "), writem, zip(t.keys, t.values)) + interleave(lambda: self.write(", "), write_pair, zip(t.keys, t.values)) self.write("}") def _Tuple(self, t): @@ -367,7 +424,17 @@ class Unparser: self.write("(") self.write(self.unop[t.op.__class__.__name__]) self.write(" ") - self.dispatch(t.operand) + # If we're applying unary minus to a number, parenthesize the number. + # This is necessary: -2147483648 is different from -(2147483648) on + # a 32-bit machine (the first is an int, the second a long), and + # -7j is different from -(7j). (The first has real part 0.0, the second + # has real part -0.0.) + if isinstance(t.op, ast.USub) and isinstance(t.operand, ast.Num): + self.write("(") + self.dispatch(t.operand) + self.write(")") + else: + self.dispatch(t.operand) self.write(")") binop = { "Add":"+", "Sub":"-", "Mult":"*", "Div":"/", "Mod":"%", @@ -390,7 +457,7 @@ class Unparser: self.dispatch(e) self.write(")") - boolops = {_ast.And: 'and', _ast.Or: 'or'} + boolops = {ast.And: 'and', ast.Or: 'or'} def _BoolOp(self, t): self.write("(") s = " %s " % self.boolops[t.op.__class__] @@ -399,6 +466,11 @@ class Unparser: 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.Num) and isinstance(t.value.n, int): + self.write(" ") self.write(".") self.write(t.attr) @@ -455,21 +527,24 @@ class Unparser: # others def _arguments(self, t): first = True - nonDef = len(t.args)-len(t.defaults) - for a in t.args[0:nonDef]: - if first:first = False - else: self.write(", ") - self.dispatch(a) - for a,d in zip(t.args[nonDef:], t.defaults): + # normal arguments + defaults = [None] * (len(t.args) - len(t.defaults)) + t.defaults + for a,d in zip(t.args, defaults): if first:first = False else: self.write(", ") self.dispatch(a), - self.write("=") - self.dispatch(d) + if d: + self.write("=") + self.dispatch(d) + + # varargs if t.vararg: if first:first = False else: self.write(", ") - self.write("*"+t.vararg) + self.write("*") + self.write(t.vararg) + + # kwargs if t.kwarg: if first:first = False else: self.write(", ") @@ -481,10 +556,12 @@ class Unparser: 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) @@ -492,8 +569,9 @@ class Unparser: self.write(" as "+t.asname) def roundtrip(filename, output=sys.stdout): - source = open(filename).read() - tree = compile(source, filename, "exec", _ast.PyCF_ONLY_AST) + with open(filename, "r") as pyfile: + source = pyfile.read() + tree = compile(source, filename, "exec", ast.PyCF_ONLY_AST) Unparser(tree, output) @@ -502,7 +580,7 @@ def testdir(a): try: names = [n for n in os.listdir(a) if n.endswith('.py')] except OSError: - print >> sys.stderr, "Directory not readable: %s" % a + sys.stderr.write("Directory not readable: %s" % a) else: for n in names: fullname = os.path.join(a, n) @@ -511,7 +589,7 @@ def testdir(a): print 'Testing %s' % fullname try: roundtrip(fullname, output) - except Exception, e: + except Exception as e: print ' Failed to compile, exception is %s' % repr(e) elif os.path.isdir(fullname): testdir(fullname) |