| 1 | # This program is free software; you can redistribute it and/or modify |
|---|
| 2 | # it under the terms of the GNU 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 General Public License for more details. |
|---|
| 9 | # |
|---|
| 10 | # You should have received a copy of the GNU 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 | """this module contains some utilities to navigate in the tree or to |
|---|
| 14 | extract information from it |
|---|
| 15 | |
|---|
| 16 | :author: Sylvain Thenault |
|---|
| 17 | :copyright: 2003-2007 LOGILAB S.A. (Paris, FRANCE) |
|---|
| 18 | :contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org |
|---|
| 19 | :copyright: 2003-2007 Sylvain Thenault |
|---|
| 20 | :contact: mailto:thenault@gmail.com |
|---|
| 21 | """ |
|---|
| 22 | |
|---|
| 23 | __docformat__ = "restructuredtext en" |
|---|
| 24 | |
|---|
| 25 | from logilab.common.compat import enumerate |
|---|
| 26 | from logilab.astng._exceptions import IgnoreChild |
|---|
| 27 | |
|---|
| 28 | def extend_class(original, addons): |
|---|
| 29 | """add methods and attribute defined in the addons class to the original |
|---|
| 30 | class |
|---|
| 31 | """ |
|---|
| 32 | brain = addons.__dict__.copy() |
|---|
| 33 | for special_key in ('__doc__', '__module__'): |
|---|
| 34 | if special_key in addons.__dict__: |
|---|
| 35 | del brain[special_key] |
|---|
| 36 | original.__dict__.update(brain) |
|---|
| 37 | |
|---|
| 38 | class ASTWalker: |
|---|
| 39 | """a walker visiting a tree in preorder, calling on the handler: |
|---|
| 40 | |
|---|
| 41 | * visit_<class name> on entering a node, where class name is the class of |
|---|
| 42 | the node in lower case |
|---|
| 43 | |
|---|
| 44 | * leave_<class name> on leaving a node, where class name is the class of |
|---|
| 45 | the node in lower case |
|---|
| 46 | """ |
|---|
| 47 | def __init__(self, handler): |
|---|
| 48 | self.handler = handler |
|---|
| 49 | self._cache = {} |
|---|
| 50 | |
|---|
| 51 | def walk(self, node): |
|---|
| 52 | """walk on the tree from <node>, getting callbacks from handler |
|---|
| 53 | """ |
|---|
| 54 | try: |
|---|
| 55 | self.visit(node) |
|---|
| 56 | except IgnoreChild: |
|---|
| 57 | pass |
|---|
| 58 | else: |
|---|
| 59 | for child_node in node.getChildNodes(): |
|---|
| 60 | self.walk(child_node) |
|---|
| 61 | self.leave(node) |
|---|
| 62 | |
|---|
| 63 | def get_callbacks(self, node): |
|---|
| 64 | """get callbacks from handler for the visited node |
|---|
| 65 | """ |
|---|
| 66 | klass = node.__class__ |
|---|
| 67 | methods = self._cache.get(klass) |
|---|
| 68 | if methods is None: |
|---|
| 69 | handler = self.handler |
|---|
| 70 | kid = klass.__name__.lower() |
|---|
| 71 | e_method = getattr(handler, 'visit_%s' % kid, |
|---|
| 72 | getattr(handler, 'visit_default', None)) |
|---|
| 73 | l_method = getattr(handler, 'leave_%s' % kid, |
|---|
| 74 | getattr(handler, 'leave_default', None)) |
|---|
| 75 | self._cache[klass] = (e_method, l_method) |
|---|
| 76 | else: |
|---|
| 77 | e_method, l_method = methods |
|---|
| 78 | return e_method, l_method |
|---|
| 79 | |
|---|
| 80 | def visit(self, node): |
|---|
| 81 | """walk on the tree from <node>, getting callbacks from handler""" |
|---|
| 82 | method = self.get_callbacks(node)[0] |
|---|
| 83 | if method is not None: |
|---|
| 84 | method(node) |
|---|
| 85 | |
|---|
| 86 | def leave(self, node): |
|---|
| 87 | """walk on the tree from <node>, getting callbacks from handler""" |
|---|
| 88 | method = self.get_callbacks(node)[1] |
|---|
| 89 | if method is not None: |
|---|
| 90 | method(node) |
|---|
| 91 | |
|---|
| 92 | |
|---|
| 93 | class LocalsVisitor(ASTWalker): |
|---|
| 94 | """visit a project by traversing the locals dictionnary""" |
|---|
| 95 | def __init__(self): |
|---|
| 96 | ASTWalker.__init__(self, self) |
|---|
| 97 | self._visited = {} |
|---|
| 98 | |
|---|
| 99 | def visit(self, node): |
|---|
| 100 | """launch the visit starting from the given node""" |
|---|
| 101 | if self._visited.has_key(node): |
|---|
| 102 | return |
|---|
| 103 | self._visited[node] = 1 |
|---|
| 104 | methods = self.get_callbacks(node) |
|---|
| 105 | recurse = 1 |
|---|
| 106 | if methods[0] is not None: |
|---|
| 107 | try: |
|---|
| 108 | methods[0](node) |
|---|
| 109 | except IgnoreChild: |
|---|
| 110 | recurse = 0 |
|---|
| 111 | if recurse: |
|---|
| 112 | if hasattr(node, 'locals'): |
|---|
| 113 | for local_node in node.values(): |
|---|
| 114 | self.visit(local_node) |
|---|
| 115 | if methods[1] is not None: |
|---|
| 116 | return methods[1](node) |
|---|
| 117 | |
|---|
| 118 | def are_exclusive(stmt1, stmt2): |
|---|
| 119 | """return true if the two given statement are mutually exclusive |
|---|
| 120 | |
|---|
| 121 | algorithm : |
|---|
| 122 | 1) index stmt1's parents |
|---|
| 123 | 2) climb among stmt2's parents until we find a common parent |
|---|
| 124 | 3) if the common parent is a If or TryExcept statement, look if nodes are |
|---|
| 125 | in exclusive branchs |
|---|
| 126 | """ |
|---|
| 127 | from logilab.astng.nodes import If, TryExcept |
|---|
| 128 | # index stmt1's parents |
|---|
| 129 | stmt1_parents = {} |
|---|
| 130 | children = {} |
|---|
| 131 | node = stmt1.parent |
|---|
| 132 | previous = stmt1 |
|---|
| 133 | while node: |
|---|
| 134 | stmt1_parents[node] = 1 |
|---|
| 135 | children[node] = previous |
|---|
| 136 | previous = node |
|---|
| 137 | node = node.parent |
|---|
| 138 | # climb among stmt2's parents until we find a common parent |
|---|
| 139 | node = stmt2.parent |
|---|
| 140 | previous = stmt2 |
|---|
| 141 | while node: |
|---|
| 142 | if stmt1_parents.has_key(node): |
|---|
| 143 | # if the common parent is a If or TryExcept statement, look if |
|---|
| 144 | # nodes are in exclusive branchs |
|---|
| 145 | if isinstance(node, If): |
|---|
| 146 | if previous != children[node]: |
|---|
| 147 | return True |
|---|
| 148 | elif isinstance(node, TryExcept): |
|---|
| 149 | stmt1_previous = children[node] |
|---|
| 150 | if not previous is stmt1_previous: |
|---|
| 151 | stmt1_branch, stmt1_num = _try_except_from_branch(node, stmt1_previous) |
|---|
| 152 | stmt2_branch, stmt2_num = _try_except_from_branch(node, previous) |
|---|
| 153 | if stmt1_branch != stmt1_branch: |
|---|
| 154 | if not ((stmt2_branch == 'body' and stmt1_branch == 'else') or |
|---|
| 155 | (stmt1_branch == 'body' and stmt2_branch == 'else') or |
|---|
| 156 | (stmt2_branch == 'body' and stmt1_branch == 'except') or |
|---|
| 157 | (stmt1_branch == 'body' and stmt2_branch == 'except')): |
|---|
| 158 | return True |
|---|
| 159 | elif stmt1_num != stmt2_num: |
|---|
| 160 | return True |
|---|
| 161 | return False |
|---|
| 162 | previous = node |
|---|
| 163 | node = node.parent |
|---|
| 164 | return False |
|---|
| 165 | |
|---|
| 166 | def _try_except_from_branch(node, stmt): |
|---|
| 167 | if stmt is node.body: |
|---|
| 168 | return 'body', 1 |
|---|
| 169 | if stmt is node.else_: |
|---|
| 170 | return 'else', 1 |
|---|
| 171 | for i, block_nodes in enumerate(node.handlers): |
|---|
| 172 | if stmt in block_nodes: |
|---|
| 173 | return 'except', i |
|---|