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