diff options
Diffstat (limited to 'astroid/utils.py')
-rw-r--r-- | astroid/utils.py | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/astroid/utils.py b/astroid/utils.py new file mode 100644 index 00000000..ae72a92c --- /dev/null +++ b/astroid/utils.py @@ -0,0 +1,239 @@ +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid 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. +# +# astroid 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 astroid. If not, see <http://www.gnu.org/licenses/>. +"""this module contains some utilities to navigate in the tree or to +extract information from it +""" +from __future__ import print_function + +__docformat__ = "restructuredtext en" + +from astroid.exceptions import AstroidBuildingException +from astroid.builder import parse + + +class ASTWalker(object): + """a walker visiting a tree in preorder, calling on the handler: + + * visit_<class name> on entering a node, where class name is the class of + the node in lower case + + * leave_<class name> 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 <node>, 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 <node>, 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 <node>, 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 local_node in node.values(): + 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 AstroidBuildingException + _check_children(child) + + +class TreeTester(object): + '''A helper class to see _ast tree and compare with astroid 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() + + <Module> + . body = [ + . <Print> + . . nl = True + . ] + >>> print tester.astroid_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_astroid_tree(self): + """build astroid tree from the _ast tree + """ + from astroid.builder import AstroidBuilder + tree = AstroidBuilder().string_build(self.sourcecode) + return tree + + def astroid_tree_repr(self, ids=False): + """build the astroid tree and return a nice tree representation""" + mod = self.build_astroid_tree() + return mod.repr_tree(ids) + + +__all__ = ('LocalsVisitor', 'ASTWalker',) + |