Update Demo/parser directory; backport unparse fixes from py3k.

This commit is contained in:
Mark Dickinson 2010-06-30 14:19:56 +00:00
parent 4aa5f6f1f2
commit 2037913507
5 changed files with 297 additions and 170 deletions

View file

@ -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)