| 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 child node of arbitrary type.
|
| """
|
|
|
| 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 incrementally, 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 klass not in self.examples:
|
| 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)
|