PageRenderTime 1118ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/packages/logilab-astng/utils.py

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