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