1 #!/usr/bin/env python 2 3 """ 4 Inspect source files, obtaining details of classes and attributes. 5 6 Copyright (C) 2007 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 import compiler.ast 23 from compiler.visitor import ASTVisitor 24 try: 25 set 26 except NameError: 27 from sets import Set as set 28 29 class InspectError(Exception): 30 31 "An inspection error." 32 33 pass 34 35 class Class: 36 37 "An inspected class." 38 39 def __init__(self, name): 40 self.name = name 41 self.bases = [] 42 self.namespace = {} 43 self.instattr = set() 44 45 def add_base(self, base): 46 self.bases.append(base) 47 48 class Function: 49 50 "An inspected function." 51 52 def __init__(self, name): 53 self.name = name 54 55 class Module(ASTVisitor): 56 57 "An inspected module." 58 59 def __init__(self, importer=None): 60 ASTVisitor.__init__(self) 61 self.visitor = self 62 63 self.importer = importer 64 65 self.namespace = {} 66 self.namespace.update(builtins_namespace) 67 68 self.in_global = 1 69 self.in_init = 0 70 self.expr = None 71 72 self.classes = [] 73 self.module = None 74 75 def parse(self, filename): 76 module = compiler.parseFile(filename) 77 self.process(module) 78 79 def process(self, module): 80 self.module = module 81 return self.dispatch(module) 82 83 def default(self, node, *args): 84 raise InspectError, (None, node) 85 86 def dispatch(self, node, *args): 87 return ASTVisitor.dispatch(self, node, *args) 88 89 def store(self, name, obj): 90 if self.in_global: 91 if not self.classes: 92 self.namespace[name] = obj 93 else: 94 self.classes[-1].namespace[name] = obj 95 96 def store_attr(self, name): 97 if self.in_init: 98 self.classes[-1].instattr.add(name) 99 100 def NOP(self, node): 101 return node 102 103 visitAnd = NOP 104 105 def visitAssign(self, node): 106 self.expr = self.dispatch(node.expr) 107 for n in node.nodes: 108 self.dispatch(n) 109 return None 110 111 def visitAssAttr(self, node): 112 expr = self.dispatch(node.expr) 113 if expr is not None and isinstance(expr, Self): 114 self.store_attr(node.attrname) 115 return None 116 117 def visitAssList(self, node): 118 for n in node.nodes: 119 self.dispatch(n) 120 return None 121 122 def visitAssName(self, node): 123 self.store(node.name, self.expr) 124 return None 125 126 visitAssTuple = visitAssList 127 128 visitCallFunc = NOP 129 130 def visitClass(self, node): 131 if not self.in_global: 132 raise InspectError, "Class is %s not global: cannot handle this reasonably." % node.name 133 else: 134 cls = Class(node) 135 for base in node.bases: 136 base_ref = self.dispatch(base) 137 if base_ref is None: 138 raise InspectError, "Base class %s for class %s is not found: it may be hidden in some way." % (base, node.name) 139 cls.add_base(base_ref) 140 141 # Make an entry for the class. 142 143 self.store(node.name, cls) 144 145 self.classes.append(cls) 146 self.dispatch(node.code) 147 self.classes.pop() 148 149 return node 150 151 visitConst = NOP 152 153 visitDict = NOP 154 155 visitDiscard = NOP 156 157 visitFor = NOP 158 159 def visitFrom(self, node): 160 if self.importer is None: 161 raise InspectError, "Please use the micropython.Importer class for code which uses the 'from' statement." 162 163 module = self.importer.load(node.modname, 1) 164 165 if module is None: 166 print "Warning:", node.modname, "not imported." 167 return None 168 169 for name, alias in node.names: 170 if name != "*": 171 self.namespace[alias] = module.namespace[name] 172 else: 173 for n in module.namespace.keys(): 174 self.namespace[n] = module.namespace[n] 175 176 return None 177 178 def visitFunction(self, node): 179 self.store(node.name, Function(node.name)) 180 181 in_global = self.in_global 182 self.in_global = 0 183 if node.name == "__init__" and self.classes: 184 self.in_init = 1 185 self.dispatch(node.code) 186 self.in_init = 0 187 self.in_global = in_global 188 return None 189 190 def visitGetattr(self, node): 191 expr = self.dispatch(node.expr) 192 if expr is not None and isinstance(expr, Module): 193 return expr.namespace.get(node.attrname) 194 else: 195 return None 196 197 def visitIf(self, node): 198 for test, body in node.tests: 199 self.dispatch(body) 200 if node.else_ is not None: 201 self.dispatch(node.else_) 202 return None 203 204 def visitImport(self, node): 205 if self.importer is None: 206 raise InspectError, "Please use the micropython.Importer class for code which uses the 'import' statement." 207 208 for name, alias in node.names: 209 self.namespace[alias] = self.importer.load(name, 1) 210 211 return None 212 213 visitList = NOP 214 215 def visitModule(self, node): 216 return self.dispatch(node.node) 217 218 def visitName(self, node): 219 name = node.name 220 if name == "self": 221 return Self() 222 elif self.namespace.has_key(name): 223 return self.namespace[name] 224 else: 225 return None 226 227 visitNot = NOP 228 229 visitOr = NOP 230 231 visitPass = NOP 232 233 visitRaise = NOP 234 235 def visitStmt(self, node): 236 for n in node.nodes: 237 self.dispatch(n) 238 return None 239 240 visitSubscript = NOP 241 242 def visitTryExcept(self, node): 243 self.dispatch(node.body) 244 for name, var, n in node.handlers: 245 self.dispatch(n) 246 if node.else_ is not None: 247 self.dispatch(node.else_) 248 return None 249 250 class Self: 251 252 "A reference to an object within a method." 253 254 pass 255 256 builtins_namespace = {} 257 for key in ['ArithmeticError', 'AssertionError', 'AttributeError', 258 'BaseException', 'DeprecationWarning', 'EOFError', 'Ellipsis', 259 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 260 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 261 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 262 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 263 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 264 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 265 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 266 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 267 'TabError', 'True', 'TypeError', 'UnboundLocalError', 268 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 269 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 270 'ValueError', 'Warning', 'ZeroDivisionError']: 271 builtins_namespace[key] = Class(key) 272 273 # vim: tabstop=4 expandtab shiftwidth=4