PageRenderTime 33ms CodeModel.GetById 18ms app.highlight 11ms RepoModel.GetById 1ms app.codeStats 0ms

/logilab-astng-0.23.1/utils.py

#
Python | 241 lines | 182 code | 17 blank | 42 comment | 32 complexity | e7e56e5295fbbd234055c91b9aed93d7 MD5 | raw file
  1# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  2# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
  3# copyright 2003-2010 Sylvain Thenault, all rights reserved.
  4# contact mailto:thenault@gmail.com
  5#
  6# This file is part of logilab-astng.
  7#
  8# logilab-astng is free software: you can redistribute it and/or modify it
  9# under the terms of the GNU Lesser General Public License as published by the
 10# Free Software Foundation, either version 2.1 of the License, or (at your
 11# option) any later version.
 12#
 13# logilab-astng is distributed in the hope that it will be useful, but
 14# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 15# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 16# for more details.
 17#
 18# You should have received a copy of the GNU Lesser General Public License along
 19# with logilab-astng. If not, see <http://www.gnu.org/licenses/>.
 20"""this module contains some utilities to navigate in the tree or to
 21extract information from it
 22"""
 23
 24__docformat__ = "restructuredtext en"
 25
 26from logilab.astng.exceptions import ASTNGBuildingException
 27
 28
 29class ASTWalker:
 30    """a walker visiting a tree in preorder, calling on the handler:
 31
 32    * visit_<class name> on entering a node, where class name is the class of
 33    the node in lower case
 34
 35    * leave_<class name> on leaving a node, where class name is the class of
 36    the node in lower case
 37    """
 38
 39    def __init__(self, handler):
 40        self.handler = handler
 41        self._cache = {}
 42
 43    def walk(self, node, _done=None):
 44        """walk on the tree from <node>, getting callbacks from handler"""
 45        if _done is None:
 46            _done = set()
 47        if node in _done:
 48            raise AssertionError((id(node), node, node.parent))
 49        _done.add(node)
 50        self.visit(node)
 51        for child_node in node.get_children():
 52            self.handler.set_context(node, child_node)
 53            assert child_node is not node
 54            self.walk(child_node, _done)
 55        self.leave(node)
 56        assert node.parent is not node
 57
 58    def get_callbacks(self, node):
 59        """get callbacks from handler for the visited node"""
 60        klass = node.__class__
 61        methods = self._cache.get(klass)
 62        if methods is None:
 63            handler = self.handler
 64            kid = klass.__name__.lower()
 65            e_method = getattr(handler, 'visit_%s' % kid,
 66                               getattr(handler, 'visit_default', None))
 67            l_method = getattr(handler, 'leave_%s' % kid,
 68                               getattr(handler, 'leave_default', None))
 69            self._cache[klass] = (e_method, l_method)
 70        else:
 71            e_method, l_method = methods
 72        return e_method, l_method
 73
 74    def visit(self, node):
 75        """walk on the tree from <node>, getting callbacks from handler"""
 76        method = self.get_callbacks(node)[0]
 77        if method is not None:
 78            method(node)
 79
 80    def leave(self, node):
 81        """walk on the tree from <node>, getting callbacks from handler"""
 82        method = self.get_callbacks(node)[1]
 83        if method is not None:
 84            method(node)
 85
 86
 87class LocalsVisitor(ASTWalker):
 88    """visit a project by traversing the locals dictionary"""
 89    def __init__(self):
 90        ASTWalker.__init__(self, self)
 91        self._visited = {}
 92
 93    def visit(self, node):
 94        """launch the visit starting from the given node"""
 95        if node in self._visited:
 96            return
 97        self._visited[node] = 1 # FIXME: use set ?
 98        methods = self.get_callbacks(node)
 99        if methods[0] is not None:
100            methods[0](node)
101        if 'locals' in node.__dict__: # skip Instance and other proxy
102            for name, local_node in node.items():
103                self.visit(local_node)
104        if methods[1] is not None:
105            return methods[1](node)
106
107
108def _check_children(node):
109    """a helper function to check children - parent relations"""
110    for child in node.get_children():
111        ok = False
112        if child is None:
113            print "Hm, child of %s is None" % node
114            continue
115        if not hasattr(child, 'parent'):
116            print " ERROR: %s has child %s %x with no parent" % (node, child, id(child))
117        elif not child.parent:
118            print " ERROR: %s has child %s %x with parent %r" % (node, child, id(child), child.parent)
119        elif child.parent is not node:
120            print " ERROR: %s %x has child %s %x with wrong parent %s" % (node,
121                                      id(node), child, id(child), child.parent)
122        else:
123            ok = True
124        if not ok:
125            print "lines;", node.lineno, child.lineno
126            print "of module", node.root(), node.root().name
127            raise ASTNGBuildingException
128        _check_children(child)
129
130
131from _ast import PyCF_ONLY_AST
132def parse(string):
133    return compile(string, "<string>", 'exec', PyCF_ONLY_AST)
134
135class TreeTester(object):
136    '''A helper class to see _ast tree and compare with astng tree
137
138    indent: string for tree indent representation
139    lineno: bool to tell if we should print the line numbers
140
141    >>> tester = TreeTester('print')
142    >>> print tester.native_tree_repr()
143
144    <Module>
145    .   body = [
146    .   <Print>
147    .   .   nl = True
148    .   ]
149    >>> print tester.astng_tree_repr()
150    Module()
151        body = [
152        Print()
153            dest = 
154            values = [
155            ]
156        ]
157    '''
158
159    indent = '.   '
160    lineno = False
161
162    def __init__(self, sourcecode):
163        self._string = ''
164        self.sourcecode = sourcecode
165        self._ast_node = None
166        self.build_ast()
167
168    def build_ast(self):
169        """build the _ast tree from the source code"""
170        self._ast_node = parse(self.sourcecode)
171
172    def native_tree_repr(self, node=None, indent=''):
173        """get a nice representation of the _ast tree"""
174        self._string = ''
175        if node is None:
176            node = self._ast_node
177        self._native_repr_tree(node, indent)
178        return self._string
179
180
181    def _native_repr_tree(self, node, indent, _done=None):
182        """recursive method for the native tree representation"""
183        from _ast import Load as _Load, Store as _Store, Del as _Del
184        from _ast import AST as Node
185        if _done is None:
186            _done = set()
187        if node in _done:
188            self._string += '\nloop in tree: %r (%s)' % (node,
189                                            getattr(node, 'lineno', None))
190            return
191        _done.add(node)
192        self._string += '\n' + indent +  '<%s>' % node.__class__.__name__
193        indent += self.indent
194        if not hasattr(node, '__dict__'):
195            self._string += '\n' + self.indent + " ** node has no __dict__ " + str(node)
196            return
197        node_dict = node.__dict__
198        if hasattr(node, '_attributes'):
199            for a in node._attributes:
200                attr = node_dict[a]
201                if attr is None:
202                    continue
203                if a in ("lineno", "col_offset") and not self.lineno:
204                    continue
205                self._string +='\n' +  indent + a + " = " + repr(attr)
206        for field in node._fields or ():
207            attr = node_dict[field]
208            if attr is None:
209                continue
210            if isinstance(attr, list):
211                if not attr:
212                    continue
213                self._string += '\n' + indent + field + ' = ['
214                for elt in attr:
215                    self._native_repr_tree(elt, indent, _done)
216                self._string += '\n' + indent + ']'
217                continue
218            if isinstance(attr, (_Load, _Store, _Del)):
219                continue
220            if isinstance(attr, Node):
221                self._string += '\n' + indent + field + " = "
222                self._native_repr_tree(attr, indent, _done)
223            else:
224                self._string += '\n' + indent + field + " = " + repr(attr)
225
226
227    def build_astng_tree(self):
228        """build astng tree from the _ast tree
229        """
230        from logilab.astng.builder import ASTNGBuilder
231        tree = ASTNGBuilder().string_build(self.sourcecode)
232        return tree
233
234    def astng_tree_repr(self, ids=False):
235        """build the astng tree and return a nice tree representation"""
236        mod = self.build_astng_tree()
237        return mod.repr_tree(ids)
238
239
240__all__ = ('LocalsVisitor', 'ASTWalker',)
241