mirror of
https://github.com/python/cpython.git
synced 2025-11-03 03:22:27 +00:00
The compiler package is now part of the standard library.
Remove all these files. All except astgen.py are moved to Lib/compiler.
This commit is contained in:
parent
f6cc07cffe
commit
b3c569ce82
12 changed files with 0 additions and 5811 deletions
|
|
@ -1,27 +0,0 @@
|
||||||
"""Package for parsing and compiling Python source code
|
|
||||||
|
|
||||||
There are several functions defined at the top level that are imported
|
|
||||||
from modules contained in the package.
|
|
||||||
|
|
||||||
parse(buf, mode="exec") -> AST
|
|
||||||
Converts a string containing Python source code to an abstract
|
|
||||||
syntax tree (AST). The AST is defined in compiler.ast.
|
|
||||||
|
|
||||||
parseFile(path) -> AST
|
|
||||||
The same as parse(open(path))
|
|
||||||
|
|
||||||
walk(ast, visitor, verbose=None)
|
|
||||||
Does a pre-order walk over the ast using the visitor instance.
|
|
||||||
See compiler.visitor for details.
|
|
||||||
|
|
||||||
compile(source, filename, mode, flags=None, dont_inherit=None)
|
|
||||||
Returns a code object. A replacement for the builtin compile() function.
|
|
||||||
|
|
||||||
compileFile(filename)
|
|
||||||
Generates a .pyc file by compilining filename.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from transformer import parse, parseFile
|
|
||||||
from visitor import walk
|
|
||||||
from pycodegen import compile, compileFile
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,280 +0,0 @@
|
||||||
"""Generate ast module from specification
|
|
||||||
|
|
||||||
This script generates the ast module from a simple specification,
|
|
||||||
which makes it easy to accomodate changes in the grammar. This
|
|
||||||
approach would be quite reasonable if the grammar changed often.
|
|
||||||
Instead, it is rather complex to generate the appropriate code. And
|
|
||||||
the Node interface has changed more often than the grammar.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import fileinput
|
|
||||||
import getopt
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
SPEC = "ast.txt"
|
|
||||||
COMMA = ", "
|
|
||||||
|
|
||||||
def load_boilerplate(file):
|
|
||||||
f = open(file)
|
|
||||||
buf = f.read()
|
|
||||||
f.close()
|
|
||||||
i = buf.find('### ''PROLOGUE')
|
|
||||||
j = buf.find('### ''EPILOGUE')
|
|
||||||
pro = buf[i+12:j].strip()
|
|
||||||
epi = buf[j+12:].strip()
|
|
||||||
return pro, epi
|
|
||||||
|
|
||||||
def strip_default(arg):
|
|
||||||
"""Return the argname from an 'arg = default' string"""
|
|
||||||
i = arg.find('=')
|
|
||||||
if i == -1:
|
|
||||||
return arg
|
|
||||||
t = arg[:i].strip()
|
|
||||||
return t
|
|
||||||
|
|
||||||
P_NODE = 1
|
|
||||||
P_OTHER = 2
|
|
||||||
P_NESTED = 3
|
|
||||||
P_NONE = 4
|
|
||||||
|
|
||||||
class NodeInfo:
|
|
||||||
"""Each instance describes a specific AST node"""
|
|
||||||
def __init__(self, name, args):
|
|
||||||
self.name = name
|
|
||||||
self.args = args.strip()
|
|
||||||
self.argnames = self.get_argnames()
|
|
||||||
self.argprops = self.get_argprops()
|
|
||||||
self.nargs = len(self.argnames)
|
|
||||||
self.init = []
|
|
||||||
|
|
||||||
def get_argnames(self):
|
|
||||||
if '(' in self.args:
|
|
||||||
i = self.args.find('(')
|
|
||||||
j = self.args.rfind(')')
|
|
||||||
args = self.args[i+1:j]
|
|
||||||
else:
|
|
||||||
args = self.args
|
|
||||||
return [strip_default(arg.strip())
|
|
||||||
for arg in args.split(',') if arg]
|
|
||||||
|
|
||||||
def get_argprops(self):
|
|
||||||
"""Each argument can have a property like '*' or '!'
|
|
||||||
|
|
||||||
XXX This method modifies the argnames in place!
|
|
||||||
"""
|
|
||||||
d = {}
|
|
||||||
hardest_arg = P_NODE
|
|
||||||
for i in range(len(self.argnames)):
|
|
||||||
arg = self.argnames[i]
|
|
||||||
if arg.endswith('*'):
|
|
||||||
arg = self.argnames[i] = arg[:-1]
|
|
||||||
d[arg] = P_OTHER
|
|
||||||
hardest_arg = max(hardest_arg, P_OTHER)
|
|
||||||
elif arg.endswith('!'):
|
|
||||||
arg = self.argnames[i] = arg[:-1]
|
|
||||||
d[arg] = P_NESTED
|
|
||||||
hardest_arg = max(hardest_arg, P_NESTED)
|
|
||||||
elif arg.endswith('&'):
|
|
||||||
arg = self.argnames[i] = arg[:-1]
|
|
||||||
d[arg] = P_NONE
|
|
||||||
hardest_arg = max(hardest_arg, P_NONE)
|
|
||||||
else:
|
|
||||||
d[arg] = P_NODE
|
|
||||||
self.hardest_arg = hardest_arg
|
|
||||||
|
|
||||||
if hardest_arg > P_NODE:
|
|
||||||
self.args = self.args.replace('*', '')
|
|
||||||
self.args = self.args.replace('!', '')
|
|
||||||
self.args = self.args.replace('&', '')
|
|
||||||
|
|
||||||
return d
|
|
||||||
|
|
||||||
def gen_source(self):
|
|
||||||
buf = StringIO()
|
|
||||||
print >> buf, "class %s(Node):" % self.name
|
|
||||||
print >> buf, ' nodes["%s"] = "%s"' % (self.name.lower(), self.name)
|
|
||||||
self._gen_init(buf)
|
|
||||||
print >> buf
|
|
||||||
self._gen_getChildren(buf)
|
|
||||||
print >> buf
|
|
||||||
self._gen_getChildNodes(buf)
|
|
||||||
print >> buf
|
|
||||||
self._gen_repr(buf)
|
|
||||||
buf.seek(0, 0)
|
|
||||||
return buf.read()
|
|
||||||
|
|
||||||
def _gen_init(self, buf):
|
|
||||||
print >> buf, " def __init__(self, %s):" % self.args
|
|
||||||
if self.argnames:
|
|
||||||
for name in self.argnames:
|
|
||||||
print >> buf, " self.%s = %s" % (name, name)
|
|
||||||
else:
|
|
||||||
print >> buf, " pass"
|
|
||||||
if self.init:
|
|
||||||
print >> buf, "".join([" " + line for line in self.init])
|
|
||||||
|
|
||||||
def _gen_getChildren(self, buf):
|
|
||||||
print >> buf, " def getChildren(self):"
|
|
||||||
if len(self.argnames) == 0:
|
|
||||||
print >> buf, " return ()"
|
|
||||||
else:
|
|
||||||
if self.hardest_arg < P_NESTED:
|
|
||||||
clist = COMMA.join(["self.%s" % c
|
|
||||||
for c in self.argnames])
|
|
||||||
if self.nargs == 1:
|
|
||||||
print >> buf, " return %s," % clist
|
|
||||||
else:
|
|
||||||
print >> buf, " return %s" % clist
|
|
||||||
else:
|
|
||||||
print >> buf, " children = []"
|
|
||||||
template = " children.%s(%sself.%s%s)"
|
|
||||||
for name in self.argnames:
|
|
||||||
if self.argprops[name] == P_NESTED:
|
|
||||||
print >> buf, template % ("extend", "flatten(",
|
|
||||||
name, ")")
|
|
||||||
else:
|
|
||||||
print >> buf, template % ("append", "", name, "")
|
|
||||||
print >> buf, " return tuple(children)"
|
|
||||||
|
|
||||||
def _gen_getChildNodes(self, buf):
|
|
||||||
print >> buf, " def getChildNodes(self):"
|
|
||||||
if len(self.argnames) == 0:
|
|
||||||
print >> buf, " return ()"
|
|
||||||
else:
|
|
||||||
if self.hardest_arg < P_NESTED:
|
|
||||||
clist = ["self.%s" % c
|
|
||||||
for c in self.argnames
|
|
||||||
if self.argprops[c] == P_NODE]
|
|
||||||
if len(clist) == 0:
|
|
||||||
print >> buf, " return ()"
|
|
||||||
elif len(clist) == 1:
|
|
||||||
print >> buf, " return %s," % clist[0]
|
|
||||||
else:
|
|
||||||
print >> buf, " return %s" % COMMA.join(clist)
|
|
||||||
else:
|
|
||||||
print >> buf, " nodes = []"
|
|
||||||
template = " nodes.%s(%sself.%s%s)"
|
|
||||||
for name in self.argnames:
|
|
||||||
if self.argprops[name] == P_NONE:
|
|
||||||
tmp = (" if self.%s is not None:"
|
|
||||||
" nodes.append(self.%s)")
|
|
||||||
print >> buf, tmp % (name, name)
|
|
||||||
elif self.argprops[name] == P_NESTED:
|
|
||||||
print >> buf, template % ("extend", "flatten_nodes(",
|
|
||||||
name, ")")
|
|
||||||
elif self.argprops[name] == P_NODE:
|
|
||||||
print >> buf, template % ("append", "", name, "")
|
|
||||||
print >> buf, " return tuple(nodes)"
|
|
||||||
|
|
||||||
def _gen_repr(self, buf):
|
|
||||||
print >> buf, " def __repr__(self):"
|
|
||||||
if self.argnames:
|
|
||||||
fmt = COMMA.join(["%s"] * self.nargs)
|
|
||||||
if '(' in self.args:
|
|
||||||
fmt = '(%s)' % fmt
|
|
||||||
vals = ["repr(self.%s)" % name for name in self.argnames]
|
|
||||||
vals = COMMA.join(vals)
|
|
||||||
if self.nargs == 1:
|
|
||||||
vals = vals + ","
|
|
||||||
print >> buf, ' return "%s(%s)" %% (%s)' % \
|
|
||||||
(self.name, fmt, vals)
|
|
||||||
else:
|
|
||||||
print >> buf, ' return "%s()"' % self.name
|
|
||||||
|
|
||||||
rx_init = re.compile('init\((.*)\):')
|
|
||||||
|
|
||||||
def parse_spec(file):
|
|
||||||
classes = {}
|
|
||||||
cur = None
|
|
||||||
for line in fileinput.input(file):
|
|
||||||
if line.strip().startswith('#'):
|
|
||||||
continue
|
|
||||||
mo = rx_init.search(line)
|
|
||||||
if mo is None:
|
|
||||||
if cur is None:
|
|
||||||
# a normal entry
|
|
||||||
try:
|
|
||||||
name, args = line.split(':')
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
classes[name] = NodeInfo(name, args)
|
|
||||||
cur = None
|
|
||||||
else:
|
|
||||||
# some code for the __init__ method
|
|
||||||
cur.init.append(line)
|
|
||||||
else:
|
|
||||||
# some extra code for a Node's __init__ method
|
|
||||||
name = mo.group(1)
|
|
||||||
cur = classes[name]
|
|
||||||
return classes.values()
|
|
||||||
|
|
||||||
def main():
|
|
||||||
prologue, epilogue = load_boilerplate(sys.argv[-1])
|
|
||||||
print prologue
|
|
||||||
print
|
|
||||||
classes = parse_spec(SPEC)
|
|
||||||
for info in classes:
|
|
||||||
print info.gen_source()
|
|
||||||
print epilogue
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
### PROLOGUE
|
|
||||||
"""Python abstract syntax node definitions
|
|
||||||
|
|
||||||
This file is automatically generated.
|
|
||||||
"""
|
|
||||||
from types import TupleType, ListType
|
|
||||||
from consts import CO_VARARGS, CO_VARKEYWORDS
|
|
||||||
|
|
||||||
def flatten(list):
|
|
||||||
l = []
|
|
||||||
for elt in list:
|
|
||||||
t = type(elt)
|
|
||||||
if t is TupleType or t is ListType:
|
|
||||||
for elt2 in flatten(elt):
|
|
||||||
l.append(elt2)
|
|
||||||
else:
|
|
||||||
l.append(elt)
|
|
||||||
return l
|
|
||||||
|
|
||||||
def flatten_nodes(list):
|
|
||||||
return [n for n in flatten(list) if isinstance(n, Node)]
|
|
||||||
|
|
||||||
def asList(nodes):
|
|
||||||
l = []
|
|
||||||
for item in nodes:
|
|
||||||
if hasattr(item, "asList"):
|
|
||||||
l.append(item.asList())
|
|
||||||
else:
|
|
||||||
t = type(item)
|
|
||||||
if t is TupleType or t is ListType:
|
|
||||||
l.append(tuple(asList(item)))
|
|
||||||
else:
|
|
||||||
l.append(item)
|
|
||||||
return l
|
|
||||||
|
|
||||||
nodes = {}
|
|
||||||
|
|
||||||
class Node: # an abstract base class
|
|
||||||
lineno = None # provide a lineno for nodes that don't have one
|
|
||||||
def getType(self):
|
|
||||||
pass # implemented by subclass
|
|
||||||
def getChildren(self):
|
|
||||||
pass # implemented by subclasses
|
|
||||||
def asList(self):
|
|
||||||
return tuple(asList(self.getChildren()))
|
|
||||||
def getChildNodes(self):
|
|
||||||
pass # implemented by subclasses
|
|
||||||
|
|
||||||
class EmptyNode(Node):
|
|
||||||
pass
|
|
||||||
|
|
||||||
### EPILOGUE
|
|
||||||
klasses = globals()
|
|
||||||
for k in nodes.keys():
|
|
||||||
nodes[k] = klasses[nodes[k]]
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
# operation flags
|
|
||||||
OP_ASSIGN = 'OP_ASSIGN'
|
|
||||||
OP_DELETE = 'OP_DELETE'
|
|
||||||
OP_APPLY = 'OP_APPLY'
|
|
||||||
|
|
||||||
SC_LOCAL = 1
|
|
||||||
SC_GLOBAL = 2
|
|
||||||
SC_FREE = 3
|
|
||||||
SC_CELL = 4
|
|
||||||
SC_UNKNOWN = 5
|
|
||||||
|
|
||||||
CO_OPTIMIZED = 0x0001
|
|
||||||
CO_NEWLOCALS = 0x0002
|
|
||||||
CO_VARARGS = 0x0004
|
|
||||||
CO_VARKEYWORDS = 0x0008
|
|
||||||
CO_NESTED = 0x0010
|
|
||||||
CO_GENERATOR = 0x0020
|
|
||||||
CO_GENERATOR_ALLOWED = 0x1000
|
|
||||||
CO_FUTURE_DIVISION = 0x2000
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
"""Parser for future statements
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from compiler import ast, walk
|
|
||||||
|
|
||||||
def is_future(stmt):
|
|
||||||
"""Return true if statement is a well-formed future statement"""
|
|
||||||
if not isinstance(stmt, ast.From):
|
|
||||||
return 0
|
|
||||||
if stmt.modname == "__future__":
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
class FutureParser:
|
|
||||||
|
|
||||||
features = ("nested_scopes", "generators", "division")
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.found = {} # set
|
|
||||||
|
|
||||||
def visitModule(self, node):
|
|
||||||
stmt = node.node
|
|
||||||
for s in stmt.nodes:
|
|
||||||
if not self.check_stmt(s):
|
|
||||||
break
|
|
||||||
|
|
||||||
def check_stmt(self, stmt):
|
|
||||||
if is_future(stmt):
|
|
||||||
for name, asname in stmt.names:
|
|
||||||
if name in self.features:
|
|
||||||
self.found[name] = 1
|
|
||||||
else:
|
|
||||||
raise SyntaxError, \
|
|
||||||
"future feature %s is not defined" % name
|
|
||||||
stmt.valid_future = 1
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_features(self):
|
|
||||||
"""Return list of features enabled by future statements"""
|
|
||||||
return self.found.keys()
|
|
||||||
|
|
||||||
class BadFutureParser:
|
|
||||||
"""Check for invalid future statements"""
|
|
||||||
|
|
||||||
def visitFrom(self, node):
|
|
||||||
if hasattr(node, 'valid_future'):
|
|
||||||
return
|
|
||||||
if node.modname != "__future__":
|
|
||||||
return
|
|
||||||
raise SyntaxError, "invalid future statement"
|
|
||||||
|
|
||||||
def find_futures(node):
|
|
||||||
p1 = FutureParser()
|
|
||||||
p2 = BadFutureParser()
|
|
||||||
walk(node, p1)
|
|
||||||
walk(node, p2)
|
|
||||||
return p1.get_features()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import sys
|
|
||||||
from compiler import parseFile, walk
|
|
||||||
|
|
||||||
for file in sys.argv[1:]:
|
|
||||||
print file
|
|
||||||
tree = parseFile(file)
|
|
||||||
v = FutureParser()
|
|
||||||
walk(tree, v)
|
|
||||||
print v.found
|
|
||||||
print
|
|
||||||
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
import types
|
|
||||||
|
|
||||||
def flatten(tup):
|
|
||||||
elts = []
|
|
||||||
for elt in tup:
|
|
||||||
if type(elt) == types.TupleType:
|
|
||||||
elts = elts + flatten(elt)
|
|
||||||
else:
|
|
||||||
elts.append(elt)
|
|
||||||
return elts
|
|
||||||
|
|
||||||
class Set:
|
|
||||||
def __init__(self):
|
|
||||||
self.elts = {}
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.elts)
|
|
||||||
def __contains__(self, elt):
|
|
||||||
return self.elts.has_key(elt)
|
|
||||||
def add(self, elt):
|
|
||||||
self.elts[elt] = elt
|
|
||||||
def elements(self):
|
|
||||||
return self.elts.keys()
|
|
||||||
def has_elt(self, elt):
|
|
||||||
return self.elts.has_key(elt)
|
|
||||||
def remove(self, elt):
|
|
||||||
del self.elts[elt]
|
|
||||||
def copy(self):
|
|
||||||
c = Set()
|
|
||||||
c.elts.update(self.elts)
|
|
||||||
return c
|
|
||||||
|
|
||||||
class Stack:
|
|
||||||
def __init__(self):
|
|
||||||
self.stack = []
|
|
||||||
self.pop = self.stack.pop
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.stack)
|
|
||||||
def push(self, elt):
|
|
||||||
self.stack.append(elt)
|
|
||||||
def top(self):
|
|
||||||
return self.stack[-1]
|
|
||||||
def __getitem__(self, index): # needed by visitContinue()
|
|
||||||
return self.stack[index]
|
|
||||||
|
|
||||||
MANGLE_LEN = 256 # magic constant from compile.c
|
|
||||||
|
|
||||||
def mangle(name, klass):
|
|
||||||
if not name.startswith('__'):
|
|
||||||
return name
|
|
||||||
if len(name) + 2 >= MANGLE_LEN:
|
|
||||||
return name
|
|
||||||
if name.endswith('__'):
|
|
||||||
return name
|
|
||||||
try:
|
|
||||||
i = 0
|
|
||||||
while klass[i] == '_':
|
|
||||||
i = i + 1
|
|
||||||
except IndexError:
|
|
||||||
return name
|
|
||||||
klass = klass[i:]
|
|
||||||
|
|
||||||
tlen = len(klass) + len(name)
|
|
||||||
if tlen > MANGLE_LEN:
|
|
||||||
klass = klass[:MANGLE_LEN-tlen]
|
|
||||||
|
|
||||||
return "_%s%s" % (klass, name)
|
|
||||||
|
|
||||||
def set_filename(filename, tree):
|
|
||||||
"""Set the filename attribute to filename on every node in tree"""
|
|
||||||
worklist = [tree]
|
|
||||||
while worklist:
|
|
||||||
node = worklist.pop(0)
|
|
||||||
node.filename = filename
|
|
||||||
worklist.extend(node.getChildNodes())
|
|
||||||
|
|
||||||
|
|
@ -1,791 +0,0 @@
|
||||||
"""A flow graph representation for Python bytecode"""
|
|
||||||
|
|
||||||
import dis
|
|
||||||
import new
|
|
||||||
import string
|
|
||||||
import sys
|
|
||||||
import types
|
|
||||||
|
|
||||||
from compiler import misc
|
|
||||||
from compiler.consts import CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, \
|
|
||||||
CO_VARKEYWORDS
|
|
||||||
|
|
||||||
def xxx_sort(l):
|
|
||||||
l = l[:]
|
|
||||||
def sorter(a, b):
|
|
||||||
return cmp(a.bid, b.bid)
|
|
||||||
l.sort(sorter)
|
|
||||||
return l
|
|
||||||
|
|
||||||
class FlowGraph:
|
|
||||||
def __init__(self):
|
|
||||||
self.current = self.entry = Block()
|
|
||||||
self.exit = Block("exit")
|
|
||||||
self.blocks = misc.Set()
|
|
||||||
self.blocks.add(self.entry)
|
|
||||||
self.blocks.add(self.exit)
|
|
||||||
|
|
||||||
def startBlock(self, block):
|
|
||||||
if self._debug:
|
|
||||||
if self.current:
|
|
||||||
print "end", repr(self.current)
|
|
||||||
print " next", self.current.next
|
|
||||||
print " ", self.current.get_children()
|
|
||||||
print repr(block)
|
|
||||||
self.current = block
|
|
||||||
|
|
||||||
def nextBlock(self, block=None):
|
|
||||||
# XXX think we need to specify when there is implicit transfer
|
|
||||||
# from one block to the next. might be better to represent this
|
|
||||||
# with explicit JUMP_ABSOLUTE instructions that are optimized
|
|
||||||
# out when they are unnecessary.
|
|
||||||
#
|
|
||||||
# I think this strategy works: each block has a child
|
|
||||||
# designated as "next" which is returned as the last of the
|
|
||||||
# children. because the nodes in a graph are emitted in
|
|
||||||
# reverse post order, the "next" block will always be emitted
|
|
||||||
# immediately after its parent.
|
|
||||||
# Worry: maintaining this invariant could be tricky
|
|
||||||
if block is None:
|
|
||||||
block = self.newBlock()
|
|
||||||
|
|
||||||
# Note: If the current block ends with an unconditional
|
|
||||||
# control transfer, then it is incorrect to add an implicit
|
|
||||||
# transfer to the block graph. The current code requires
|
|
||||||
# these edges to get the blocks emitted in the right order,
|
|
||||||
# however. :-( If a client needs to remove these edges, call
|
|
||||||
# pruneEdges().
|
|
||||||
|
|
||||||
self.current.addNext(block)
|
|
||||||
self.startBlock(block)
|
|
||||||
|
|
||||||
def newBlock(self):
|
|
||||||
b = Block()
|
|
||||||
self.blocks.add(b)
|
|
||||||
return b
|
|
||||||
|
|
||||||
def startExitBlock(self):
|
|
||||||
self.startBlock(self.exit)
|
|
||||||
|
|
||||||
_debug = 0
|
|
||||||
|
|
||||||
def _enable_debug(self):
|
|
||||||
self._debug = 1
|
|
||||||
|
|
||||||
def _disable_debug(self):
|
|
||||||
self._debug = 0
|
|
||||||
|
|
||||||
def emit(self, *inst):
|
|
||||||
if self._debug:
|
|
||||||
print "\t", inst
|
|
||||||
if inst[0] == 'RETURN_VALUE':
|
|
||||||
self.current.addOutEdge(self.exit)
|
|
||||||
if len(inst) == 2 and isinstance(inst[1], Block):
|
|
||||||
self.current.addOutEdge(inst[1])
|
|
||||||
self.current.emit(inst)
|
|
||||||
|
|
||||||
def getBlocksInOrder(self):
|
|
||||||
"""Return the blocks in reverse postorder
|
|
||||||
|
|
||||||
i.e. each node appears before all of its successors
|
|
||||||
"""
|
|
||||||
# XXX make sure every node that doesn't have an explicit next
|
|
||||||
# is set so that next points to exit
|
|
||||||
for b in self.blocks.elements():
|
|
||||||
if b is self.exit:
|
|
||||||
continue
|
|
||||||
if not b.next:
|
|
||||||
b.addNext(self.exit)
|
|
||||||
order = dfs_postorder(self.entry, {})
|
|
||||||
order.reverse()
|
|
||||||
self.fixupOrder(order, self.exit)
|
|
||||||
# hack alert
|
|
||||||
if not self.exit in order:
|
|
||||||
order.append(self.exit)
|
|
||||||
|
|
||||||
return order
|
|
||||||
|
|
||||||
def fixupOrder(self, blocks, default_next):
|
|
||||||
"""Fixup bad order introduced by DFS."""
|
|
||||||
|
|
||||||
# XXX This is a total mess. There must be a better way to get
|
|
||||||
# the code blocks in the right order.
|
|
||||||
|
|
||||||
self.fixupOrderHonorNext(blocks, default_next)
|
|
||||||
self.fixupOrderForward(blocks, default_next)
|
|
||||||
|
|
||||||
def fixupOrderHonorNext(self, blocks, default_next):
|
|
||||||
"""Fix one problem with DFS.
|
|
||||||
|
|
||||||
The DFS uses child block, but doesn't know about the special
|
|
||||||
"next" block. As a result, the DFS can order blocks so that a
|
|
||||||
block isn't next to the right block for implicit control
|
|
||||||
transfers.
|
|
||||||
"""
|
|
||||||
index = {}
|
|
||||||
for i in range(len(blocks)):
|
|
||||||
index[blocks[i]] = i
|
|
||||||
|
|
||||||
for i in range(0, len(blocks) - 1):
|
|
||||||
b = blocks[i]
|
|
||||||
n = blocks[i + 1]
|
|
||||||
if not b.next or b.next[0] == default_next or b.next[0] == n:
|
|
||||||
continue
|
|
||||||
# The blocks are in the wrong order. Find the chain of
|
|
||||||
# blocks to insert where they belong.
|
|
||||||
cur = b
|
|
||||||
chain = []
|
|
||||||
elt = cur
|
|
||||||
while elt.next and elt.next[0] != default_next:
|
|
||||||
chain.append(elt.next[0])
|
|
||||||
elt = elt.next[0]
|
|
||||||
# Now remove the blocks in the chain from the current
|
|
||||||
# block list, so that they can be re-inserted.
|
|
||||||
l = []
|
|
||||||
for b in chain:
|
|
||||||
assert index[b] > i
|
|
||||||
l.append((index[b], b))
|
|
||||||
l.sort()
|
|
||||||
l.reverse()
|
|
||||||
for j, b in l:
|
|
||||||
del blocks[index[b]]
|
|
||||||
# Insert the chain in the proper location
|
|
||||||
blocks[i:i + 1] = [cur] + chain
|
|
||||||
# Finally, re-compute the block indexes
|
|
||||||
for i in range(len(blocks)):
|
|
||||||
index[blocks[i]] = i
|
|
||||||
|
|
||||||
def fixupOrderForward(self, blocks, default_next):
|
|
||||||
"""Make sure all JUMP_FORWARDs jump forward"""
|
|
||||||
index = {}
|
|
||||||
chains = []
|
|
||||||
cur = []
|
|
||||||
for b in blocks:
|
|
||||||
index[b] = len(chains)
|
|
||||||
cur.append(b)
|
|
||||||
if b.next and b.next[0] == default_next:
|
|
||||||
chains.append(cur)
|
|
||||||
cur = []
|
|
||||||
chains.append(cur)
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
constraints = []
|
|
||||||
|
|
||||||
for i in range(len(chains)):
|
|
||||||
l = chains[i]
|
|
||||||
for b in l:
|
|
||||||
for c in b.get_children():
|
|
||||||
if index[c] < i:
|
|
||||||
forward_p = 0
|
|
||||||
for inst in b.insts:
|
|
||||||
if inst[0] == 'JUMP_FORWARD':
|
|
||||||
if inst[1] == c:
|
|
||||||
forward_p = 1
|
|
||||||
if not forward_p:
|
|
||||||
continue
|
|
||||||
constraints.append((index[c], i))
|
|
||||||
|
|
||||||
if not constraints:
|
|
||||||
break
|
|
||||||
|
|
||||||
# XXX just do one for now
|
|
||||||
# do swaps to get things in the right order
|
|
||||||
goes_before, a_chain = constraints[0]
|
|
||||||
assert a_chain > goes_before
|
|
||||||
c = chains[a_chain]
|
|
||||||
chains.remove(c)
|
|
||||||
chains.insert(goes_before, c)
|
|
||||||
|
|
||||||
|
|
||||||
del blocks[:]
|
|
||||||
for c in chains:
|
|
||||||
for b in c:
|
|
||||||
blocks.append(b)
|
|
||||||
|
|
||||||
def getBlocks(self):
|
|
||||||
return self.blocks.elements()
|
|
||||||
|
|
||||||
def getRoot(self):
|
|
||||||
"""Return nodes appropriate for use with dominator"""
|
|
||||||
return self.entry
|
|
||||||
|
|
||||||
def getContainedGraphs(self):
|
|
||||||
l = []
|
|
||||||
for b in self.getBlocks():
|
|
||||||
l.extend(b.getContainedGraphs())
|
|
||||||
return l
|
|
||||||
|
|
||||||
def dfs_postorder(b, seen):
|
|
||||||
"""Depth-first search of tree rooted at b, return in postorder"""
|
|
||||||
order = []
|
|
||||||
seen[b] = b
|
|
||||||
for c in b.get_children():
|
|
||||||
if seen.has_key(c):
|
|
||||||
continue
|
|
||||||
order = order + dfs_postorder(c, seen)
|
|
||||||
order.append(b)
|
|
||||||
return order
|
|
||||||
|
|
||||||
class Block:
|
|
||||||
_count = 0
|
|
||||||
|
|
||||||
def __init__(self, label=''):
|
|
||||||
self.insts = []
|
|
||||||
self.inEdges = misc.Set()
|
|
||||||
self.outEdges = misc.Set()
|
|
||||||
self.label = label
|
|
||||||
self.bid = Block._count
|
|
||||||
self.next = []
|
|
||||||
Block._count = Block._count + 1
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.label:
|
|
||||||
return "<block %s id=%d>" % (self.label, self.bid)
|
|
||||||
else:
|
|
||||||
return "<block id=%d>" % (self.bid)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
insts = map(str, self.insts)
|
|
||||||
return "<block %s %d:\n%s>" % (self.label, self.bid,
|
|
||||||
string.join(insts, '\n'))
|
|
||||||
|
|
||||||
def emit(self, inst):
|
|
||||||
op = inst[0]
|
|
||||||
if op[:4] == 'JUMP':
|
|
||||||
self.outEdges.add(inst[1])
|
|
||||||
self.insts.append(inst)
|
|
||||||
|
|
||||||
def getInstructions(self):
|
|
||||||
return self.insts
|
|
||||||
|
|
||||||
def addInEdge(self, block):
|
|
||||||
self.inEdges.add(block)
|
|
||||||
|
|
||||||
def addOutEdge(self, block):
|
|
||||||
self.outEdges.add(block)
|
|
||||||
|
|
||||||
def addNext(self, block):
|
|
||||||
self.next.append(block)
|
|
||||||
assert len(self.next) == 1, map(str, self.next)
|
|
||||||
|
|
||||||
_uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS',
|
|
||||||
'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'CONTINUE_LOOP')
|
|
||||||
|
|
||||||
def pruneNext(self):
|
|
||||||
"""Remove bogus edge for unconditional transfers
|
|
||||||
|
|
||||||
Each block has a next edge that accounts for implicit control
|
|
||||||
transfers, e.g. from a JUMP_IF_FALSE to the block that will be
|
|
||||||
executed if the test is true.
|
|
||||||
|
|
||||||
These edges must remain for the current assembler code to
|
|
||||||
work. If they are removed, the dfs_postorder gets things in
|
|
||||||
weird orders. However, they shouldn't be there for other
|
|
||||||
purposes, e.g. conversion to SSA form. This method will
|
|
||||||
remove the next edge when it follows an unconditional control
|
|
||||||
transfer.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
op, arg = self.insts[-1]
|
|
||||||
except (IndexError, ValueError):
|
|
||||||
return
|
|
||||||
if op in self._uncond_transfer:
|
|
||||||
self.next = []
|
|
||||||
|
|
||||||
def get_children(self):
|
|
||||||
if self.next and self.next[0] in self.outEdges:
|
|
||||||
self.outEdges.remove(self.next[0])
|
|
||||||
return self.outEdges.elements() + self.next
|
|
||||||
|
|
||||||
def getContainedGraphs(self):
|
|
||||||
"""Return all graphs contained within this block.
|
|
||||||
|
|
||||||
For example, a MAKE_FUNCTION block will contain a reference to
|
|
||||||
the graph for the function body.
|
|
||||||
"""
|
|
||||||
contained = []
|
|
||||||
for inst in self.insts:
|
|
||||||
if len(inst) == 1:
|
|
||||||
continue
|
|
||||||
op = inst[1]
|
|
||||||
if hasattr(op, 'graph'):
|
|
||||||
contained.append(op.graph)
|
|
||||||
return contained
|
|
||||||
|
|
||||||
# flags for code objects
|
|
||||||
|
|
||||||
# the FlowGraph is transformed in place; it exists in one of these states
|
|
||||||
RAW = "RAW"
|
|
||||||
FLAT = "FLAT"
|
|
||||||
CONV = "CONV"
|
|
||||||
DONE = "DONE"
|
|
||||||
|
|
||||||
class PyFlowGraph(FlowGraph):
|
|
||||||
super_init = FlowGraph.__init__
|
|
||||||
|
|
||||||
def __init__(self, name, filename, args=(), optimized=0, klass=None):
|
|
||||||
self.super_init()
|
|
||||||
self.name = name
|
|
||||||
self.filename = filename
|
|
||||||
self.docstring = None
|
|
||||||
self.args = args # XXX
|
|
||||||
self.argcount = getArgCount(args)
|
|
||||||
self.klass = klass
|
|
||||||
if optimized:
|
|
||||||
self.flags = CO_OPTIMIZED | CO_NEWLOCALS
|
|
||||||
else:
|
|
||||||
self.flags = 0
|
|
||||||
self.consts = []
|
|
||||||
self.names = []
|
|
||||||
# Free variables found by the symbol table scan, including
|
|
||||||
# variables used only in nested scopes, are included here.
|
|
||||||
self.freevars = []
|
|
||||||
self.cellvars = []
|
|
||||||
# The closure list is used to track the order of cell
|
|
||||||
# variables and free variables in the resulting code object.
|
|
||||||
# The offsets used by LOAD_CLOSURE/LOAD_DEREF refer to both
|
|
||||||
# kinds of variables.
|
|
||||||
self.closure = []
|
|
||||||
self.varnames = list(args) or []
|
|
||||||
for i in range(len(self.varnames)):
|
|
||||||
var = self.varnames[i]
|
|
||||||
if isinstance(var, TupleArg):
|
|
||||||
self.varnames[i] = var.getName()
|
|
||||||
self.stage = RAW
|
|
||||||
|
|
||||||
def setDocstring(self, doc):
|
|
||||||
self.docstring = doc
|
|
||||||
|
|
||||||
def setFlag(self, flag):
|
|
||||||
self.flags = self.flags | flag
|
|
||||||
if flag == CO_VARARGS:
|
|
||||||
self.argcount = self.argcount - 1
|
|
||||||
|
|
||||||
def checkFlag(self, flag):
|
|
||||||
if self.flags & flag:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def setFreeVars(self, names):
|
|
||||||
self.freevars = list(names)
|
|
||||||
|
|
||||||
def setCellVars(self, names):
|
|
||||||
self.cellvars = names
|
|
||||||
|
|
||||||
def getCode(self):
|
|
||||||
"""Get a Python code object"""
|
|
||||||
if self.stage == RAW:
|
|
||||||
self.flattenGraph()
|
|
||||||
if self.stage == FLAT:
|
|
||||||
self.convertArgs()
|
|
||||||
if self.stage == CONV:
|
|
||||||
self.makeByteCode()
|
|
||||||
if self.stage == DONE:
|
|
||||||
return self.newCodeObject()
|
|
||||||
raise RuntimeError, "inconsistent PyFlowGraph state"
|
|
||||||
|
|
||||||
def dump(self, io=None):
|
|
||||||
if io:
|
|
||||||
save = sys.stdout
|
|
||||||
sys.stdout = io
|
|
||||||
pc = 0
|
|
||||||
for t in self.insts:
|
|
||||||
opname = t[0]
|
|
||||||
if opname == "SET_LINENO":
|
|
||||||
print
|
|
||||||
if len(t) == 1:
|
|
||||||
print "\t", "%3d" % pc, opname
|
|
||||||
pc = pc + 1
|
|
||||||
else:
|
|
||||||
print "\t", "%3d" % pc, opname, t[1]
|
|
||||||
pc = pc + 3
|
|
||||||
if io:
|
|
||||||
sys.stdout = save
|
|
||||||
|
|
||||||
def flattenGraph(self):
|
|
||||||
"""Arrange the blocks in order and resolve jumps"""
|
|
||||||
assert self.stage == RAW
|
|
||||||
self.insts = insts = []
|
|
||||||
pc = 0
|
|
||||||
begin = {}
|
|
||||||
end = {}
|
|
||||||
for b in self.getBlocksInOrder():
|
|
||||||
begin[b] = pc
|
|
||||||
for inst in b.getInstructions():
|
|
||||||
insts.append(inst)
|
|
||||||
if len(inst) == 1:
|
|
||||||
pc = pc + 1
|
|
||||||
else:
|
|
||||||
# arg takes 2 bytes
|
|
||||||
pc = pc + 3
|
|
||||||
end[b] = pc
|
|
||||||
pc = 0
|
|
||||||
for i in range(len(insts)):
|
|
||||||
inst = insts[i]
|
|
||||||
if len(inst) == 1:
|
|
||||||
pc = pc + 1
|
|
||||||
else:
|
|
||||||
pc = pc + 3
|
|
||||||
opname = inst[0]
|
|
||||||
if self.hasjrel.has_elt(opname):
|
|
||||||
oparg = inst[1]
|
|
||||||
offset = begin[oparg] - pc
|
|
||||||
insts[i] = opname, offset
|
|
||||||
elif self.hasjabs.has_elt(opname):
|
|
||||||
insts[i] = opname, begin[inst[1]]
|
|
||||||
self.stacksize = findDepth(self.insts)
|
|
||||||
self.stage = FLAT
|
|
||||||
|
|
||||||
hasjrel = misc.Set()
|
|
||||||
for i in dis.hasjrel:
|
|
||||||
hasjrel.add(dis.opname[i])
|
|
||||||
hasjabs = misc.Set()
|
|
||||||
for i in dis.hasjabs:
|
|
||||||
hasjabs.add(dis.opname[i])
|
|
||||||
|
|
||||||
def convertArgs(self):
|
|
||||||
"""Convert arguments from symbolic to concrete form"""
|
|
||||||
assert self.stage == FLAT
|
|
||||||
self.consts.insert(0, self.docstring)
|
|
||||||
self.sort_cellvars()
|
|
||||||
for i in range(len(self.insts)):
|
|
||||||
t = self.insts[i]
|
|
||||||
if len(t) == 2:
|
|
||||||
opname, oparg = t
|
|
||||||
conv = self._converters.get(opname, None)
|
|
||||||
if conv:
|
|
||||||
self.insts[i] = opname, conv(self, oparg)
|
|
||||||
self.stage = CONV
|
|
||||||
|
|
||||||
def sort_cellvars(self):
|
|
||||||
"""Sort cellvars in the order of varnames and prune from freevars.
|
|
||||||
"""
|
|
||||||
cells = {}
|
|
||||||
for name in self.cellvars:
|
|
||||||
cells[name] = 1
|
|
||||||
self.cellvars = [name for name in self.varnames
|
|
||||||
if cells.has_key(name)]
|
|
||||||
for name in self.cellvars:
|
|
||||||
del cells[name]
|
|
||||||
self.cellvars = self.cellvars + cells.keys()
|
|
||||||
self.closure = self.cellvars + self.freevars
|
|
||||||
|
|
||||||
def _lookupName(self, name, list):
|
|
||||||
"""Return index of name in list, appending if necessary
|
|
||||||
|
|
||||||
This routine uses a list instead of a dictionary, because a
|
|
||||||
dictionary can't store two different keys if the keys have the
|
|
||||||
same value but different types, e.g. 2 and 2L. The compiler
|
|
||||||
must treat these two separately, so it does an explicit type
|
|
||||||
comparison before comparing the values.
|
|
||||||
"""
|
|
||||||
t = type(name)
|
|
||||||
for i in range(len(list)):
|
|
||||||
if t == type(list[i]) and list[i] == name:
|
|
||||||
return i
|
|
||||||
end = len(list)
|
|
||||||
list.append(name)
|
|
||||||
return end
|
|
||||||
|
|
||||||
_converters = {}
|
|
||||||
def _convert_LOAD_CONST(self, arg):
|
|
||||||
if hasattr(arg, 'getCode'):
|
|
||||||
arg = arg.getCode()
|
|
||||||
return self._lookupName(arg, self.consts)
|
|
||||||
|
|
||||||
def _convert_LOAD_FAST(self, arg):
|
|
||||||
self._lookupName(arg, self.names)
|
|
||||||
return self._lookupName(arg, self.varnames)
|
|
||||||
_convert_STORE_FAST = _convert_LOAD_FAST
|
|
||||||
_convert_DELETE_FAST = _convert_LOAD_FAST
|
|
||||||
|
|
||||||
def _convert_LOAD_NAME(self, arg):
|
|
||||||
if self.klass is None:
|
|
||||||
self._lookupName(arg, self.varnames)
|
|
||||||
return self._lookupName(arg, self.names)
|
|
||||||
|
|
||||||
def _convert_NAME(self, arg):
|
|
||||||
if self.klass is None:
|
|
||||||
self._lookupName(arg, self.varnames)
|
|
||||||
return self._lookupName(arg, self.names)
|
|
||||||
_convert_STORE_NAME = _convert_NAME
|
|
||||||
_convert_DELETE_NAME = _convert_NAME
|
|
||||||
_convert_IMPORT_NAME = _convert_NAME
|
|
||||||
_convert_IMPORT_FROM = _convert_NAME
|
|
||||||
_convert_STORE_ATTR = _convert_NAME
|
|
||||||
_convert_LOAD_ATTR = _convert_NAME
|
|
||||||
_convert_DELETE_ATTR = _convert_NAME
|
|
||||||
_convert_LOAD_GLOBAL = _convert_NAME
|
|
||||||
_convert_STORE_GLOBAL = _convert_NAME
|
|
||||||
_convert_DELETE_GLOBAL = _convert_NAME
|
|
||||||
|
|
||||||
def _convert_DEREF(self, arg):
|
|
||||||
self._lookupName(arg, self.names)
|
|
||||||
self._lookupName(arg, self.varnames)
|
|
||||||
return self._lookupName(arg, self.closure)
|
|
||||||
_convert_LOAD_DEREF = _convert_DEREF
|
|
||||||
_convert_STORE_DEREF = _convert_DEREF
|
|
||||||
|
|
||||||
def _convert_LOAD_CLOSURE(self, arg):
|
|
||||||
self._lookupName(arg, self.varnames)
|
|
||||||
return self._lookupName(arg, self.closure)
|
|
||||||
|
|
||||||
_cmp = list(dis.cmp_op)
|
|
||||||
def _convert_COMPARE_OP(self, arg):
|
|
||||||
return self._cmp.index(arg)
|
|
||||||
|
|
||||||
# similarly for other opcodes...
|
|
||||||
|
|
||||||
for name, obj in locals().items():
|
|
||||||
if name[:9] == "_convert_":
|
|
||||||
opname = name[9:]
|
|
||||||
_converters[opname] = obj
|
|
||||||
del name, obj, opname
|
|
||||||
|
|
||||||
def makeByteCode(self):
|
|
||||||
assert self.stage == CONV
|
|
||||||
self.lnotab = lnotab = LineAddrTable()
|
|
||||||
for t in self.insts:
|
|
||||||
opname = t[0]
|
|
||||||
if len(t) == 1:
|
|
||||||
lnotab.addCode(self.opnum[opname])
|
|
||||||
else:
|
|
||||||
oparg = t[1]
|
|
||||||
if opname == "SET_LINENO":
|
|
||||||
lnotab.nextLine(oparg)
|
|
||||||
hi, lo = twobyte(oparg)
|
|
||||||
try:
|
|
||||||
lnotab.addCode(self.opnum[opname], lo, hi)
|
|
||||||
except ValueError:
|
|
||||||
print opname, oparg
|
|
||||||
print self.opnum[opname], lo, hi
|
|
||||||
raise
|
|
||||||
self.stage = DONE
|
|
||||||
|
|
||||||
opnum = {}
|
|
||||||
for num in range(len(dis.opname)):
|
|
||||||
opnum[dis.opname[num]] = num
|
|
||||||
del num
|
|
||||||
|
|
||||||
def newCodeObject(self):
|
|
||||||
assert self.stage == DONE
|
|
||||||
if (self.flags & CO_NEWLOCALS) == 0:
|
|
||||||
nlocals = 0
|
|
||||||
else:
|
|
||||||
nlocals = len(self.varnames)
|
|
||||||
argcount = self.argcount
|
|
||||||
if self.flags & CO_VARKEYWORDS:
|
|
||||||
argcount = argcount - 1
|
|
||||||
return new.code(argcount, nlocals, self.stacksize, self.flags,
|
|
||||||
self.lnotab.getCode(), self.getConsts(),
|
|
||||||
tuple(self.names), tuple(self.varnames),
|
|
||||||
self.filename, self.name, self.lnotab.firstline,
|
|
||||||
self.lnotab.getTable(), tuple(self.freevars),
|
|
||||||
tuple(self.cellvars))
|
|
||||||
|
|
||||||
def getConsts(self):
|
|
||||||
"""Return a tuple for the const slot of the code object
|
|
||||||
|
|
||||||
Must convert references to code (MAKE_FUNCTION) to code
|
|
||||||
objects recursively.
|
|
||||||
"""
|
|
||||||
l = []
|
|
||||||
for elt in self.consts:
|
|
||||||
if isinstance(elt, PyFlowGraph):
|
|
||||||
elt = elt.getCode()
|
|
||||||
l.append(elt)
|
|
||||||
return tuple(l)
|
|
||||||
|
|
||||||
def isJump(opname):
|
|
||||||
if opname[:4] == 'JUMP':
|
|
||||||
return 1
|
|
||||||
|
|
||||||
class TupleArg:
|
|
||||||
"""Helper for marking func defs with nested tuples in arglist"""
|
|
||||||
def __init__(self, count, names):
|
|
||||||
self.count = count
|
|
||||||
self.names = names
|
|
||||||
def __repr__(self):
|
|
||||||
return "TupleArg(%s, %s)" % (self.count, self.names)
|
|
||||||
def getName(self):
|
|
||||||
return ".%d" % self.count
|
|
||||||
|
|
||||||
def getArgCount(args):
|
|
||||||
argcount = len(args)
|
|
||||||
if args:
|
|
||||||
for arg in args:
|
|
||||||
if isinstance(arg, TupleArg):
|
|
||||||
numNames = len(misc.flatten(arg.names))
|
|
||||||
argcount = argcount - numNames
|
|
||||||
return argcount
|
|
||||||
|
|
||||||
def twobyte(val):
|
|
||||||
"""Convert an int argument into high and low bytes"""
|
|
||||||
assert type(val) == types.IntType
|
|
||||||
return divmod(val, 256)
|
|
||||||
|
|
||||||
class LineAddrTable:
|
|
||||||
"""lnotab
|
|
||||||
|
|
||||||
This class builds the lnotab, which is documented in compile.c.
|
|
||||||
Here's a brief recap:
|
|
||||||
|
|
||||||
For each SET_LINENO instruction after the first one, two bytes are
|
|
||||||
added to lnotab. (In some cases, multiple two-byte entries are
|
|
||||||
added.) The first byte is the distance in bytes between the
|
|
||||||
instruction for the last SET_LINENO and the current SET_LINENO.
|
|
||||||
The second byte is offset in line numbers. If either offset is
|
|
||||||
greater than 255, multiple two-byte entries are added -- see
|
|
||||||
compile.c for the delicate details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.code = []
|
|
||||||
self.codeOffset = 0
|
|
||||||
self.firstline = 0
|
|
||||||
self.lastline = 0
|
|
||||||
self.lastoff = 0
|
|
||||||
self.lnotab = []
|
|
||||||
|
|
||||||
def addCode(self, *args):
|
|
||||||
for arg in args:
|
|
||||||
self.code.append(chr(arg))
|
|
||||||
self.codeOffset = self.codeOffset + len(args)
|
|
||||||
|
|
||||||
def nextLine(self, lineno):
|
|
||||||
if self.firstline == 0:
|
|
||||||
self.firstline = lineno
|
|
||||||
self.lastline = lineno
|
|
||||||
else:
|
|
||||||
# compute deltas
|
|
||||||
addr = self.codeOffset - self.lastoff
|
|
||||||
line = lineno - self.lastline
|
|
||||||
# Python assumes that lineno always increases with
|
|
||||||
# increasing bytecode address (lnotab is unsigned char).
|
|
||||||
# Depending on when SET_LINENO instructions are emitted
|
|
||||||
# this is not always true. Consider the code:
|
|
||||||
# a = (1,
|
|
||||||
# b)
|
|
||||||
# In the bytecode stream, the assignment to "a" occurs
|
|
||||||
# after the loading of "b". This works with the C Python
|
|
||||||
# compiler because it only generates a SET_LINENO instruction
|
|
||||||
# for the assignment.
|
|
||||||
if line > 0:
|
|
||||||
push = self.lnotab.append
|
|
||||||
while addr > 255:
|
|
||||||
push(255); push(0)
|
|
||||||
addr -= 255
|
|
||||||
while line > 255:
|
|
||||||
push(addr); push(255)
|
|
||||||
line -= 255
|
|
||||||
addr = 0
|
|
||||||
if addr > 0 or line > 0:
|
|
||||||
push(addr); push(line)
|
|
||||||
self.lastline = lineno
|
|
||||||
self.lastoff = self.codeOffset
|
|
||||||
|
|
||||||
def getCode(self):
|
|
||||||
return string.join(self.code, '')
|
|
||||||
|
|
||||||
def getTable(self):
|
|
||||||
return string.join(map(chr, self.lnotab), '')
|
|
||||||
|
|
||||||
class StackDepthTracker:
|
|
||||||
# XXX 1. need to keep track of stack depth on jumps
|
|
||||||
# XXX 2. at least partly as a result, this code is broken
|
|
||||||
|
|
||||||
def findDepth(self, insts):
|
|
||||||
depth = 0
|
|
||||||
maxDepth = 0
|
|
||||||
for i in insts:
|
|
||||||
opname = i[0]
|
|
||||||
delta = self.effect.get(opname, 0)
|
|
||||||
if delta > 1:
|
|
||||||
depth = depth + delta
|
|
||||||
elif delta < 0:
|
|
||||||
if depth > maxDepth:
|
|
||||||
maxDepth = depth
|
|
||||||
depth = depth + delta
|
|
||||||
else:
|
|
||||||
if depth > maxDepth:
|
|
||||||
maxDepth = depth
|
|
||||||
# now check patterns
|
|
||||||
for pat, pat_delta in self.patterns:
|
|
||||||
if opname[:len(pat)] == pat:
|
|
||||||
delta = pat_delta
|
|
||||||
depth = depth + delta
|
|
||||||
break
|
|
||||||
# if we still haven't found a match
|
|
||||||
if delta == 0:
|
|
||||||
meth = getattr(self, opname, None)
|
|
||||||
if meth is not None:
|
|
||||||
depth = depth + meth(i[1])
|
|
||||||
if depth < 0:
|
|
||||||
depth = 0
|
|
||||||
return maxDepth
|
|
||||||
|
|
||||||
effect = {
|
|
||||||
'POP_TOP': -1,
|
|
||||||
'DUP_TOP': 1,
|
|
||||||
'SLICE+1': -1,
|
|
||||||
'SLICE+2': -1,
|
|
||||||
'SLICE+3': -2,
|
|
||||||
'STORE_SLICE+0': -1,
|
|
||||||
'STORE_SLICE+1': -2,
|
|
||||||
'STORE_SLICE+2': -2,
|
|
||||||
'STORE_SLICE+3': -3,
|
|
||||||
'DELETE_SLICE+0': -1,
|
|
||||||
'DELETE_SLICE+1': -2,
|
|
||||||
'DELETE_SLICE+2': -2,
|
|
||||||
'DELETE_SLICE+3': -3,
|
|
||||||
'STORE_SUBSCR': -3,
|
|
||||||
'DELETE_SUBSCR': -2,
|
|
||||||
# PRINT_EXPR?
|
|
||||||
'PRINT_ITEM': -1,
|
|
||||||
'RETURN_VALUE': -1,
|
|
||||||
'EXEC_STMT': -3,
|
|
||||||
'BUILD_CLASS': -2,
|
|
||||||
'STORE_NAME': -1,
|
|
||||||
'STORE_ATTR': -2,
|
|
||||||
'DELETE_ATTR': -1,
|
|
||||||
'STORE_GLOBAL': -1,
|
|
||||||
'BUILD_MAP': 1,
|
|
||||||
'COMPARE_OP': -1,
|
|
||||||
'STORE_FAST': -1,
|
|
||||||
'IMPORT_STAR': -1,
|
|
||||||
'IMPORT_NAME': 0,
|
|
||||||
'IMPORT_FROM': 1,
|
|
||||||
# close enough...
|
|
||||||
'SETUP_EXCEPT': 3,
|
|
||||||
'SETUP_FINALLY': 3,
|
|
||||||
'FOR_ITER': 1,
|
|
||||||
}
|
|
||||||
# use pattern match
|
|
||||||
patterns = [
|
|
||||||
('BINARY_', -1),
|
|
||||||
('LOAD_', 1),
|
|
||||||
]
|
|
||||||
|
|
||||||
def UNPACK_SEQUENCE(self, count):
|
|
||||||
return count-1
|
|
||||||
def BUILD_TUPLE(self, count):
|
|
||||||
return -count+1
|
|
||||||
def BUILD_LIST(self, count):
|
|
||||||
return -count+1
|
|
||||||
def CALL_FUNCTION(self, argc):
|
|
||||||
hi, lo = divmod(argc, 256)
|
|
||||||
return lo + hi * 2
|
|
||||||
def CALL_FUNCTION_VAR(self, argc):
|
|
||||||
return self.CALL_FUNCTION(argc)+1
|
|
||||||
def CALL_FUNCTION_KW(self, argc):
|
|
||||||
return self.CALL_FUNCTION(argc)+1
|
|
||||||
def CALL_FUNCTION_VAR_KW(self, argc):
|
|
||||||
return self.CALL_FUNCTION(argc)+2
|
|
||||||
def MAKE_FUNCTION(self, argc):
|
|
||||||
return -argc
|
|
||||||
def BUILD_SLICE(self, argc):
|
|
||||||
if argc == 2:
|
|
||||||
return -1
|
|
||||||
elif argc == 3:
|
|
||||||
return -2
|
|
||||||
|
|
||||||
findDepth = StackDepthTracker().findDepth
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,415 +0,0 @@
|
||||||
"""Module symbol-table generator"""
|
|
||||||
|
|
||||||
from compiler import ast
|
|
||||||
from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL, SC_UNKNOWN
|
|
||||||
from compiler.misc import mangle
|
|
||||||
import types
|
|
||||||
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
MANGLE_LEN = 256
|
|
||||||
|
|
||||||
class Scope:
|
|
||||||
# XXX how much information do I need about each name?
|
|
||||||
def __init__(self, name, module, klass=None):
|
|
||||||
self.name = name
|
|
||||||
self.module = module
|
|
||||||
self.defs = {}
|
|
||||||
self.uses = {}
|
|
||||||
self.globals = {}
|
|
||||||
self.params = {}
|
|
||||||
self.frees = {}
|
|
||||||
self.cells = {}
|
|
||||||
self.children = []
|
|
||||||
# nested is true if the class could contain free variables,
|
|
||||||
# i.e. if it is nested within another function.
|
|
||||||
self.nested = None
|
|
||||||
self.generator = None
|
|
||||||
self.klass = None
|
|
||||||
if klass is not None:
|
|
||||||
for i in range(len(klass)):
|
|
||||||
if klass[i] != '_':
|
|
||||||
self.klass = klass[i:]
|
|
||||||
break
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s: %s>" % (self.__class__.__name__, self.name)
|
|
||||||
|
|
||||||
def mangle(self, name):
|
|
||||||
if self.klass is None:
|
|
||||||
return name
|
|
||||||
return mangle(name, self.klass)
|
|
||||||
|
|
||||||
def add_def(self, name):
|
|
||||||
self.defs[self.mangle(name)] = 1
|
|
||||||
|
|
||||||
def add_use(self, name):
|
|
||||||
self.uses[self.mangle(name)] = 1
|
|
||||||
|
|
||||||
def add_global(self, name):
|
|
||||||
name = self.mangle(name)
|
|
||||||
if self.uses.has_key(name) or self.defs.has_key(name):
|
|
||||||
pass # XXX warn about global following def/use
|
|
||||||
if self.params.has_key(name):
|
|
||||||
raise SyntaxError, "%s in %s is global and parameter" % \
|
|
||||||
(name, self.name)
|
|
||||||
self.globals[name] = 1
|
|
||||||
self.module.add_def(name)
|
|
||||||
|
|
||||||
def add_param(self, name):
|
|
||||||
name = self.mangle(name)
|
|
||||||
self.defs[name] = 1
|
|
||||||
self.params[name] = 1
|
|
||||||
|
|
||||||
def get_names(self):
|
|
||||||
d = {}
|
|
||||||
d.update(self.defs)
|
|
||||||
d.update(self.uses)
|
|
||||||
d.update(self.globals)
|
|
||||||
return d.keys()
|
|
||||||
|
|
||||||
def add_child(self, child):
|
|
||||||
self.children.append(child)
|
|
||||||
|
|
||||||
def get_children(self):
|
|
||||||
return self.children
|
|
||||||
|
|
||||||
def DEBUG(self):
|
|
||||||
print >> sys.stderr, self.name, self.nested and "nested" or ""
|
|
||||||
print >> sys.stderr, "\tglobals: ", self.globals
|
|
||||||
print >> sys.stderr, "\tcells: ", self.cells
|
|
||||||
print >> sys.stderr, "\tdefs: ", self.defs
|
|
||||||
print >> sys.stderr, "\tuses: ", self.uses
|
|
||||||
print >> sys.stderr, "\tfrees:", self.frees
|
|
||||||
|
|
||||||
def check_name(self, name):
|
|
||||||
"""Return scope of name.
|
|
||||||
|
|
||||||
The scope of a name could be LOCAL, GLOBAL, FREE, or CELL.
|
|
||||||
"""
|
|
||||||
if self.globals.has_key(name):
|
|
||||||
return SC_GLOBAL
|
|
||||||
if self.cells.has_key(name):
|
|
||||||
return SC_CELL
|
|
||||||
if self.defs.has_key(name):
|
|
||||||
return SC_LOCAL
|
|
||||||
if self.nested and (self.frees.has_key(name) or
|
|
||||||
self.uses.has_key(name)):
|
|
||||||
return SC_FREE
|
|
||||||
if self.nested:
|
|
||||||
return SC_UNKNOWN
|
|
||||||
else:
|
|
||||||
return SC_GLOBAL
|
|
||||||
|
|
||||||
def get_free_vars(self):
|
|
||||||
if not self.nested:
|
|
||||||
return ()
|
|
||||||
free = {}
|
|
||||||
free.update(self.frees)
|
|
||||||
for name in self.uses.keys():
|
|
||||||
if not (self.defs.has_key(name) or
|
|
||||||
self.globals.has_key(name)):
|
|
||||||
free[name] = 1
|
|
||||||
return free.keys()
|
|
||||||
|
|
||||||
def handle_children(self):
|
|
||||||
for child in self.children:
|
|
||||||
frees = child.get_free_vars()
|
|
||||||
globals = self.add_frees(frees)
|
|
||||||
for name in globals:
|
|
||||||
child.force_global(name)
|
|
||||||
|
|
||||||
def force_global(self, name):
|
|
||||||
"""Force name to be global in scope.
|
|
||||||
|
|
||||||
Some child of the current node had a free reference to name.
|
|
||||||
When the child was processed, it was labelled a free
|
|
||||||
variable. Now that all its enclosing scope have been
|
|
||||||
processed, the name is known to be a global or builtin. So
|
|
||||||
walk back down the child chain and set the name to be global
|
|
||||||
rather than free.
|
|
||||||
|
|
||||||
Be careful to stop if a child does not think the name is
|
|
||||||
free.
|
|
||||||
"""
|
|
||||||
self.globals[name] = 1
|
|
||||||
if self.frees.has_key(name):
|
|
||||||
del self.frees[name]
|
|
||||||
for child in self.children:
|
|
||||||
if child.check_name(name) == SC_FREE:
|
|
||||||
child.force_global(name)
|
|
||||||
|
|
||||||
def add_frees(self, names):
|
|
||||||
"""Process list of free vars from nested scope.
|
|
||||||
|
|
||||||
Returns a list of names that are either 1) declared global in the
|
|
||||||
parent or 2) undefined in a top-level parent. In either case,
|
|
||||||
the nested scope should treat them as globals.
|
|
||||||
"""
|
|
||||||
child_globals = []
|
|
||||||
for name in names:
|
|
||||||
sc = self.check_name(name)
|
|
||||||
if self.nested:
|
|
||||||
if sc == SC_UNKNOWN or sc == SC_FREE \
|
|
||||||
or isinstance(self, ClassScope):
|
|
||||||
self.frees[name] = 1
|
|
||||||
elif sc == SC_GLOBAL:
|
|
||||||
child_globals.append(name)
|
|
||||||
elif isinstance(self, FunctionScope) and sc == SC_LOCAL:
|
|
||||||
self.cells[name] = 1
|
|
||||||
elif sc != SC_CELL:
|
|
||||||
child_globals.append(name)
|
|
||||||
else:
|
|
||||||
if sc == SC_LOCAL:
|
|
||||||
self.cells[name] = 1
|
|
||||||
elif sc != SC_CELL:
|
|
||||||
child_globals.append(name)
|
|
||||||
return child_globals
|
|
||||||
|
|
||||||
def get_cell_vars(self):
|
|
||||||
return self.cells.keys()
|
|
||||||
|
|
||||||
class ModuleScope(Scope):
|
|
||||||
__super_init = Scope.__init__
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.__super_init("global", self)
|
|
||||||
|
|
||||||
class FunctionScope(Scope):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class LambdaScope(FunctionScope):
|
|
||||||
__super_init = Scope.__init__
|
|
||||||
|
|
||||||
__counter = 1
|
|
||||||
|
|
||||||
def __init__(self, module, klass=None):
|
|
||||||
i = self.__counter
|
|
||||||
self.__counter += 1
|
|
||||||
self.__super_init("lambda.%d" % i, module, klass)
|
|
||||||
|
|
||||||
class ClassScope(Scope):
|
|
||||||
__super_init = Scope.__init__
|
|
||||||
|
|
||||||
def __init__(self, name, module):
|
|
||||||
self.__super_init(name, module, name)
|
|
||||||
|
|
||||||
class SymbolVisitor:
|
|
||||||
def __init__(self):
|
|
||||||
self.scopes = {}
|
|
||||||
self.klass = None
|
|
||||||
|
|
||||||
# node that define new scopes
|
|
||||||
|
|
||||||
def visitModule(self, node):
|
|
||||||
scope = self.module = self.scopes[node] = ModuleScope()
|
|
||||||
self.visit(node.node, scope)
|
|
||||||
|
|
||||||
def visitFunction(self, node, parent):
|
|
||||||
parent.add_def(node.name)
|
|
||||||
for n in node.defaults:
|
|
||||||
self.visit(n, parent)
|
|
||||||
scope = FunctionScope(node.name, self.module, self.klass)
|
|
||||||
if parent.nested or isinstance(parent, FunctionScope):
|
|
||||||
scope.nested = 1
|
|
||||||
self.scopes[node] = scope
|
|
||||||
self._do_args(scope, node.argnames)
|
|
||||||
self.visit(node.code, scope)
|
|
||||||
self.handle_free_vars(scope, parent)
|
|
||||||
|
|
||||||
def visitLambda(self, node, parent):
|
|
||||||
for n in node.defaults:
|
|
||||||
self.visit(n, parent)
|
|
||||||
scope = LambdaScope(self.module, self.klass)
|
|
||||||
if parent.nested or isinstance(parent, FunctionScope):
|
|
||||||
scope.nested = 1
|
|
||||||
self.scopes[node] = scope
|
|
||||||
self._do_args(scope, node.argnames)
|
|
||||||
self.visit(node.code, scope)
|
|
||||||
self.handle_free_vars(scope, parent)
|
|
||||||
|
|
||||||
def _do_args(self, scope, args):
|
|
||||||
for name in args:
|
|
||||||
if type(name) == types.TupleType:
|
|
||||||
self._do_args(scope, name)
|
|
||||||
else:
|
|
||||||
scope.add_param(name)
|
|
||||||
|
|
||||||
def handle_free_vars(self, scope, parent):
|
|
||||||
parent.add_child(scope)
|
|
||||||
scope.handle_children()
|
|
||||||
|
|
||||||
def visitClass(self, node, parent):
|
|
||||||
parent.add_def(node.name)
|
|
||||||
for n in node.bases:
|
|
||||||
self.visit(n, parent)
|
|
||||||
scope = ClassScope(node.name, self.module)
|
|
||||||
if parent.nested or isinstance(parent, FunctionScope):
|
|
||||||
scope.nested = 1
|
|
||||||
self.scopes[node] = scope
|
|
||||||
prev = self.klass
|
|
||||||
self.klass = node.name
|
|
||||||
self.visit(node.code, scope)
|
|
||||||
self.klass = prev
|
|
||||||
self.handle_free_vars(scope, parent)
|
|
||||||
|
|
||||||
# name can be a def or a use
|
|
||||||
|
|
||||||
# XXX a few calls and nodes expect a third "assign" arg that is
|
|
||||||
# true if the name is being used as an assignment. only
|
|
||||||
# expressions contained within statements may have the assign arg.
|
|
||||||
|
|
||||||
def visitName(self, node, scope, assign=0):
|
|
||||||
if assign:
|
|
||||||
scope.add_def(node.name)
|
|
||||||
else:
|
|
||||||
scope.add_use(node.name)
|
|
||||||
|
|
||||||
# operations that bind new names
|
|
||||||
|
|
||||||
def visitFor(self, node, scope):
|
|
||||||
self.visit(node.assign, scope, 1)
|
|
||||||
self.visit(node.list, scope)
|
|
||||||
self.visit(node.body, scope)
|
|
||||||
if node.else_:
|
|
||||||
self.visit(node.else_, scope)
|
|
||||||
|
|
||||||
def visitFrom(self, node, scope):
|
|
||||||
for name, asname in node.names:
|
|
||||||
if name == "*":
|
|
||||||
continue
|
|
||||||
scope.add_def(asname or name)
|
|
||||||
|
|
||||||
def visitImport(self, node, scope):
|
|
||||||
for name, asname in node.names:
|
|
||||||
i = name.find(".")
|
|
||||||
if i > -1:
|
|
||||||
name = name[:i]
|
|
||||||
scope.add_def(asname or name)
|
|
||||||
|
|
||||||
def visitGlobal(self, node, scope):
|
|
||||||
for name in node.names:
|
|
||||||
scope.add_global(name)
|
|
||||||
|
|
||||||
def visitAssign(self, node, scope):
|
|
||||||
"""Propagate assignment flag down to child nodes.
|
|
||||||
|
|
||||||
The Assign node doesn't itself contains the variables being
|
|
||||||
assigned to. Instead, the children in node.nodes are visited
|
|
||||||
with the assign flag set to true. When the names occur in
|
|
||||||
those nodes, they are marked as defs.
|
|
||||||
|
|
||||||
Some names that occur in an assignment target are not bound by
|
|
||||||
the assignment, e.g. a name occurring inside a slice. The
|
|
||||||
visitor handles these nodes specially; they do not propagate
|
|
||||||
the assign flag to their children.
|
|
||||||
"""
|
|
||||||
for n in node.nodes:
|
|
||||||
self.visit(n, scope, 1)
|
|
||||||
self.visit(node.expr, scope)
|
|
||||||
|
|
||||||
def visitAssName(self, node, scope, assign=1):
|
|
||||||
scope.add_def(node.name)
|
|
||||||
|
|
||||||
def visitAssAttr(self, node, scope, assign=0):
|
|
||||||
self.visit(node.expr, scope, 0)
|
|
||||||
|
|
||||||
def visitSubscript(self, node, scope, assign=0):
|
|
||||||
self.visit(node.expr, scope, 0)
|
|
||||||
for n in node.subs:
|
|
||||||
self.visit(n, scope, 0)
|
|
||||||
|
|
||||||
def visitSlice(self, node, scope, assign=0):
|
|
||||||
self.visit(node.expr, scope, 0)
|
|
||||||
if node.lower:
|
|
||||||
self.visit(node.lower, scope, 0)
|
|
||||||
if node.upper:
|
|
||||||
self.visit(node.upper, scope, 0)
|
|
||||||
|
|
||||||
def visitAugAssign(self, node, scope):
|
|
||||||
# If the LHS is a name, then this counts as assignment.
|
|
||||||
# Otherwise, it's just use.
|
|
||||||
self.visit(node.node, scope)
|
|
||||||
if isinstance(node.node, ast.Name):
|
|
||||||
self.visit(node.node, scope, 1) # XXX worry about this
|
|
||||||
self.visit(node.expr, scope)
|
|
||||||
|
|
||||||
# prune if statements if tests are false
|
|
||||||
|
|
||||||
_const_types = types.StringType, types.IntType, types.FloatType
|
|
||||||
|
|
||||||
def visitIf(self, node, scope):
|
|
||||||
for test, body in node.tests:
|
|
||||||
if isinstance(test, ast.Const):
|
|
||||||
if type(test.value) in self._const_types:
|
|
||||||
if not test.value:
|
|
||||||
continue
|
|
||||||
self.visit(test, scope)
|
|
||||||
self.visit(body, scope)
|
|
||||||
if node.else_:
|
|
||||||
self.visit(node.else_, scope)
|
|
||||||
|
|
||||||
# a yield statement signals a generator
|
|
||||||
|
|
||||||
def visitYield(self, node, scope):
|
|
||||||
scope.generator = 1
|
|
||||||
self.visit(node.value, scope)
|
|
||||||
|
|
||||||
def sort(l):
|
|
||||||
l = l[:]
|
|
||||||
l.sort()
|
|
||||||
return l
|
|
||||||
|
|
||||||
def list_eq(l1, l2):
|
|
||||||
return sort(l1) == sort(l2)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import sys
|
|
||||||
from compiler import parseFile, walk
|
|
||||||
import symtable
|
|
||||||
|
|
||||||
def get_names(syms):
|
|
||||||
return [s for s in [s.get_name() for s in syms.get_symbols()]
|
|
||||||
if not (s.startswith('_[') or s.startswith('.'))]
|
|
||||||
|
|
||||||
for file in sys.argv[1:]:
|
|
||||||
print file
|
|
||||||
f = open(file)
|
|
||||||
buf = f.read()
|
|
||||||
f.close()
|
|
||||||
syms = symtable.symtable(buf, file, "exec")
|
|
||||||
mod_names = get_names(syms)
|
|
||||||
tree = parseFile(file)
|
|
||||||
s = SymbolVisitor()
|
|
||||||
walk(tree, s)
|
|
||||||
|
|
||||||
# compare module-level symbols
|
|
||||||
names2 = s.scopes[tree].get_names()
|
|
||||||
|
|
||||||
if not list_eq(mod_names, names2):
|
|
||||||
print
|
|
||||||
print "oops", file
|
|
||||||
print sort(mod_names)
|
|
||||||
print sort(names2)
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
d = {}
|
|
||||||
d.update(s.scopes)
|
|
||||||
del d[tree]
|
|
||||||
scopes = d.values()
|
|
||||||
del d
|
|
||||||
|
|
||||||
for s in syms.get_symbols():
|
|
||||||
if s.is_namespace():
|
|
||||||
l = [sc for sc in scopes
|
|
||||||
if sc.name == s.get_name()]
|
|
||||||
if len(l) > 1:
|
|
||||||
print "skipping", s.get_name()
|
|
||||||
else:
|
|
||||||
if not list_eq(get_names(s.get_namespace()),
|
|
||||||
l[0].get_names()):
|
|
||||||
print s.get_name()
|
|
||||||
print sort(get_names(s.get_namespace()))
|
|
||||||
print sort(l[0].get_names())
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
"""Check for errs in the AST.
|
|
||||||
|
|
||||||
The Python parser does not catch all syntax errors. Others, like
|
|
||||||
assignments with invalid targets, are caught in the code generation
|
|
||||||
phase.
|
|
||||||
|
|
||||||
The compiler package catches some errors in the transformer module.
|
|
||||||
But it seems clearer to write checkers that use the AST to detect
|
|
||||||
errors.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from compiler import ast, walk
|
|
||||||
|
|
||||||
def check(tree, multi=None):
|
|
||||||
v = SyntaxErrorChecker(multi)
|
|
||||||
walk(tree, v)
|
|
||||||
return v.errors
|
|
||||||
|
|
||||||
class SyntaxErrorChecker:
|
|
||||||
"""A visitor to find syntax errors in the AST."""
|
|
||||||
|
|
||||||
def __init__(self, multi=None):
|
|
||||||
"""Create new visitor object.
|
|
||||||
|
|
||||||
If optional argument multi is not None, then print messages
|
|
||||||
for each error rather than raising a SyntaxError for the
|
|
||||||
first.
|
|
||||||
"""
|
|
||||||
self.multi = multi
|
|
||||||
self.errors = 0
|
|
||||||
|
|
||||||
def error(self, node, msg):
|
|
||||||
self.errors = self.errors + 1
|
|
||||||
if self.multi is not None:
|
|
||||||
print "%s:%s: %s" % (node.filename, node.lineno, msg)
|
|
||||||
else:
|
|
||||||
raise SyntaxError, "%s (%s:%s)" % (msg, node.filename, node.lineno)
|
|
||||||
|
|
||||||
def visitAssign(self, node):
|
|
||||||
# the transformer module handles many of these
|
|
||||||
for target in node.nodes:
|
|
||||||
pass
|
|
||||||
## if isinstance(target, ast.AssList):
|
|
||||||
## if target.lineno is None:
|
|
||||||
## target.lineno = node.lineno
|
|
||||||
## self.error(target, "can't assign to list comprehension")
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,121 +0,0 @@
|
||||||
from compiler import ast
|
|
||||||
|
|
||||||
# XXX should probably rename ASTVisitor to ASTWalker
|
|
||||||
# XXX can it be made even more generic?
|
|
||||||
|
|
||||||
class ASTVisitor:
|
|
||||||
"""Performs a depth-first walk of the AST
|
|
||||||
|
|
||||||
The ASTVisitor will walk the AST, performing either a preorder or
|
|
||||||
postorder traversal depending on which method is called.
|
|
||||||
|
|
||||||
methods:
|
|
||||||
preorder(tree, visitor)
|
|
||||||
postorder(tree, visitor)
|
|
||||||
tree: an instance of ast.Node
|
|
||||||
visitor: an instance with visitXXX methods
|
|
||||||
|
|
||||||
The ASTVisitor is responsible for walking over the tree in the
|
|
||||||
correct order. For each node, it checks the visitor argument for
|
|
||||||
a method named 'visitNodeType' where NodeType is the name of the
|
|
||||||
node's class, e.g. Class. If the method exists, it is called
|
|
||||||
with the node as its sole argument.
|
|
||||||
|
|
||||||
The visitor method for a particular node type can control how
|
|
||||||
child nodes are visited during a preorder walk. (It can't control
|
|
||||||
the order during a postorder walk, because it is called _after_
|
|
||||||
the walk has occurred.) The ASTVisitor modifies the visitor
|
|
||||||
argument by adding a visit method to the visitor; this method can
|
|
||||||
be used to visit a particular child node. If the visitor method
|
|
||||||
returns a true value, the ASTVisitor will not traverse the child
|
|
||||||
nodes.
|
|
||||||
|
|
||||||
XXX The interface for controlling the preorder walk needs to be
|
|
||||||
re-considered. The current interface is convenient for visitors
|
|
||||||
that mostly let the ASTVisitor do everything. For something like
|
|
||||||
a code generator, where you want to walk to occur in a specific
|
|
||||||
order, it's a pain to add "return 1" to the end of each method.
|
|
||||||
"""
|
|
||||||
|
|
||||||
VERBOSE = 0
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.node = None
|
|
||||||
self._cache = {}
|
|
||||||
|
|
||||||
def default(self, node, *args):
|
|
||||||
for child in node.getChildNodes():
|
|
||||||
self.dispatch(child, *args)
|
|
||||||
|
|
||||||
def dispatch(self, node, *args):
|
|
||||||
self.node = node
|
|
||||||
klass = node.__class__
|
|
||||||
meth = self._cache.get(klass, None)
|
|
||||||
if meth is None:
|
|
||||||
className = klass.__name__
|
|
||||||
meth = getattr(self.visitor, 'visit' + className, self.default)
|
|
||||||
self._cache[klass] = meth
|
|
||||||
## if self.VERBOSE > 0:
|
|
||||||
## className = klass.__name__
|
|
||||||
## if self.VERBOSE == 1:
|
|
||||||
## if meth == 0:
|
|
||||||
## print "dispatch", className
|
|
||||||
## else:
|
|
||||||
## print "dispatch", className, (meth and meth.__name__ or '')
|
|
||||||
return meth(node, *args)
|
|
||||||
|
|
||||||
def preorder(self, tree, visitor, *args):
|
|
||||||
"""Do preorder walk of tree using visitor"""
|
|
||||||
self.visitor = visitor
|
|
||||||
visitor.visit = self.dispatch
|
|
||||||
self.dispatch(tree, *args) # XXX *args make sense?
|
|
||||||
|
|
||||||
class ExampleASTVisitor(ASTVisitor):
|
|
||||||
"""Prints examples of the nodes that aren't visited
|
|
||||||
|
|
||||||
This visitor-driver is only useful for development, when it's
|
|
||||||
helpful to develop a visitor incremently, and get feedback on what
|
|
||||||
you still have to do.
|
|
||||||
"""
|
|
||||||
examples = {}
|
|
||||||
|
|
||||||
def dispatch(self, node, *args):
|
|
||||||
self.node = node
|
|
||||||
meth = self._cache.get(node.__class__, None)
|
|
||||||
className = node.__class__.__name__
|
|
||||||
if meth is None:
|
|
||||||
meth = getattr(self.visitor, 'visit' + className, 0)
|
|
||||||
self._cache[node.__class__] = meth
|
|
||||||
if self.VERBOSE > 1:
|
|
||||||
print "dispatch", className, (meth and meth.__name__ or '')
|
|
||||||
if meth:
|
|
||||||
meth(node, *args)
|
|
||||||
elif self.VERBOSE > 0:
|
|
||||||
klass = node.__class__
|
|
||||||
if not self.examples.has_key(klass):
|
|
||||||
self.examples[klass] = klass
|
|
||||||
print
|
|
||||||
print self.visitor
|
|
||||||
print klass
|
|
||||||
for attr in dir(node):
|
|
||||||
if attr[0] != '_':
|
|
||||||
print "\t", "%-12.12s" % attr, getattr(node, attr)
|
|
||||||
print
|
|
||||||
return self.default(node, *args)
|
|
||||||
|
|
||||||
# XXX this is an API change
|
|
||||||
|
|
||||||
_walker = ASTVisitor
|
|
||||||
def walk(tree, visitor, walker=None, verbose=None):
|
|
||||||
if walker is None:
|
|
||||||
walker = _walker()
|
|
||||||
if verbose is not None:
|
|
||||||
walker.VERBOSE = verbose
|
|
||||||
walker.preorder(tree, visitor)
|
|
||||||
return walker.visitor
|
|
||||||
|
|
||||||
def dumpNode(node):
|
|
||||||
print node.__class__
|
|
||||||
for attr in dir(node):
|
|
||||||
if attr[0] != '_':
|
|
||||||
print "\t", "%-10.10s" % attr, getattr(node, attr)
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue