# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # copyright 2003-2010 Sylvain Thenault, all rights reserved. # contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # # logilab-astng is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # # logilab-astng is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along # with logilab-astng. If not, see . """this module contains some utilities to navigate in the tree or to extract information from it """ __docformat__ = "restructuredtext en" from logilab.astng.exceptions import ASTNGBuildingException class ASTWalker: """a walker visiting a tree in preorder, calling on the handler: * visit_ on entering a node, where class name is the class of the node in lower case * leave_ on leaving a node, where class name is the class of the node in lower case """ def __init__(self, handler): self.handler = handler self._cache = {} def walk(self, node, _done=None): """walk on the tree from , getting callbacks from handler""" if _done is None: _done = set() if node in _done: raise AssertionError((id(node), node, node.parent)) _done.add(node) self.visit(node) for child_node in node.get_children(): self.handler.set_context(node, child_node) assert child_node is not node self.walk(child_node, _done) self.leave(node) assert node.parent is not node def get_callbacks(self, node): """get callbacks from handler for the visited node""" klass = node.__class__ methods = self._cache.get(klass) if methods is None: handler = self.handler kid = klass.__name__.lower() e_method = getattr(handler, 'visit_%s' % kid, getattr(handler, 'visit_default', None)) l_method = getattr(handler, 'leave_%s' % kid, getattr(handler, 'leave_default', None)) self._cache[klass] = (e_method, l_method) else: e_method, l_method = methods return e_method, l_method def visit(self, node): """walk on the tree from , getting callbacks from handler""" method = self.get_callbacks(node)[0] if method is not None: method(node) def leave(self, node): """walk on the tree from , getting callbacks from handler""" method = self.get_callbacks(node)[1] if method is not None: method(node) class LocalsVisitor(ASTWalker): """visit a project by traversing the locals dictionary""" def __init__(self): ASTWalker.__init__(self, self) self._visited = {} def visit(self, node): """launch the visit starting from the given node""" if node in self._visited: return self._visited[node] = 1 # FIXME: use set ? methods = self.get_callbacks(node) if methods[0] is not None: methods[0](node) if 'locals' in node.__dict__: # skip Instance and other proxy for name, local_node in node.items(): self.visit(local_node) if methods[1] is not None: return methods[1](node) def _check_children(node): """a helper function to check children - parent relations""" for child in node.get_children(): ok = False if child is None: print "Hm, child of %s is None" % node continue if not hasattr(child, 'parent'): print " ERROR: %s has child %s %x with no parent" % (node, child, id(child)) elif not child.parent: print " ERROR: %s has child %s %x with parent %r" % (node, child, id(child), child.parent) elif child.parent is not node: print " ERROR: %s %x has child %s %x with wrong parent %s" % (node, id(node), child, id(child), child.parent) else: ok = True if not ok: print "lines;", node.lineno, child.lineno print "of module", node.root(), node.root().name raise ASTNGBuildingException _check_children(child) from _ast import PyCF_ONLY_AST def parse(string): return compile(string, "", 'exec', PyCF_ONLY_AST) class TreeTester(object): '''A helper class to see _ast tree and compare with astng tree indent: string for tree indent representation lineno: bool to tell if we should print the line numbers >>> tester = TreeTester('print') >>> print tester.native_tree_repr() . body = [ . . . nl = True . ] >>> print tester.astng_tree_repr() Module() body = [ Print() dest = values = [ ] ] ''' indent = '. ' lineno = False def __init__(self, sourcecode): self._string = '' self.sourcecode = sourcecode self._ast_node = None self.build_ast() def build_ast(self): """build the _ast tree from the source code""" self._ast_node = parse(self.sourcecode) def native_tree_repr(self, node=None, indent=''): """get a nice representation of the _ast tree""" self._string = '' if node is None: node = self._ast_node self._native_repr_tree(node, indent) return self._string def _native_repr_tree(self, node, indent, _done=None): """recursive method for the native tree representation""" from _ast import Load as _Load, Store as _Store, Del as _Del from _ast import AST as Node if _done is None: _done = set() if node in _done: self._string += '\nloop in tree: %r (%s)' % (node, getattr(node, 'lineno', None)) return _done.add(node) self._string += '\n' + indent + '<%s>' % node.__class__.__name__ indent += self.indent if not hasattr(node, '__dict__'): self._string += '\n' + self.indent + " ** node has no __dict__ " + str(node) return node_dict = node.__dict__ if hasattr(node, '_attributes'): for a in node._attributes: attr = node_dict[a] if attr is None: continue if a in ("lineno", "col_offset") and not self.lineno: continue self._string +='\n' + indent + a + " = " + repr(attr) for field in node._fields or (): attr = node_dict[field] if attr is None: continue if isinstance(attr, list): if not attr: continue self._string += '\n' + indent + field + ' = [' for elt in attr: self._native_repr_tree(elt, indent, _done) self._string += '\n' + indent + ']' continue if isinstance(attr, (_Load, _Store, _Del)): continue if isinstance(attr, Node): self._string += '\n' + indent + field + " = " self._native_repr_tree(attr, indent, _done) else: self._string += '\n' + indent + field + " = " + repr(attr) def build_astng_tree(self): """build astng tree from the _ast tree """ from logilab.astng.builder import ASTNGBuilder tree = ASTNGBuilder().string_build(self.sourcecode) return tree def astng_tree_repr(self, ids=False): """build the astng tree and return a nice tree representation""" mod = self.build_astng_tree() return mod.repr_tree(ids) __all__ = ('LocalsVisitor', 'ASTWalker',)