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 NamespaceDict: 36 37 "A mix-in providing dictionary methods." 38 39 def __getitem__(self, name): 40 return self.namespace[name] 41 42 def keys(self): 43 return self.namespace.keys() 44 45 class Naming: 46 47 "A mix-in providing naming conveniences." 48 49 def full_name(self): 50 if self.name is not None: 51 return self.module_name + "." + self.name 52 else: 53 return self.module_name 54 55 class Class(NamespaceDict, Naming): 56 57 "An inspected class." 58 59 def __init__(self, name, module_name): 60 self.name = name 61 self.module_name = module_name 62 self.bases = [] 63 self.namespace = {} 64 self.instattr = set() 65 66 def add_base(self, base): 67 self.bases.append(base) 68 69 def __repr__(self): 70 return "Class(%r, %r)" % (self.name, self.module_name) 71 72 def class_attributes(self): 73 74 "Return all class attributes, indicating the class which provides them." 75 76 attr = {} 77 reversed_bases = self.bases[:] 78 reversed_bases.reverse() 79 for cls in reversed_bases: 80 attr.update(cls.class_attributes()) 81 for name, value in self.namespace.items(): 82 attr[name] = self.full_name() 83 return attr 84 85 def instance_attributes(self): 86 87 """ 88 Return all attributes for an instance, indicating either the class which 89 provides them or that the instance itself provides them. 90 """ 91 92 attr = {} 93 attr.update(self.class_attributes()) 94 for name in self.instattr: 95 if attr.has_key(name): 96 print "Instance attribute", name, "overrides class attribute." 97 attr[name] = None 98 return attr 99 100 class Function(NamespaceDict, Naming): 101 102 "An inspected function." 103 104 def __init__(self, name, module_name, argnames, has_star, has_dstar): 105 self.name = name 106 self.module_name = module_name 107 self.argnames = argnames 108 self.has_star = has_star 109 self.has_dstar = has_dstar 110 self.namespace = {} 111 112 def __repr__(self): 113 return "Function(%r, %r, %r, %r, %r)" % ( 114 self.name, self.module_name, self.argnames, self.has_star, self.has_dstar 115 ) 116 117 class UnresolvedName(NamespaceDict, Naming): 118 119 "A module, class or function which was mentioned but could not be imported." 120 121 def __init__(self, name, module_name): 122 self.name = name 123 self.module_name = module_name 124 self.namespace = {} 125 126 def __repr__(self): 127 return "UnresolvedName(%r, %r)" % (self.name, self.module_name) 128 129 class Module(ASTVisitor, NamespaceDict): 130 131 "An inspected module." 132 133 def __init__(self, name, importer=None): 134 ASTVisitor.__init__(self) 135 self.visitor = self 136 137 self.name = name 138 self.importer = importer 139 self.loaded = 0 140 141 # Module namespace. 142 143 self.namespace = {} 144 145 # Current expression state. 146 147 self.expr = None 148 149 # Namespace state. 150 151 self.in_global = 1 152 self.in_init = 0 153 self.classes = [] 154 self.module = None 155 156 def parse(self, filename): 157 module = compiler.parseFile(filename) 158 self.process(module) 159 160 def process(self, module): 161 self.module = module 162 processed = self.dispatch(module) 163 if self.namespace.has_key("__all__"): 164 all = self.namespace["__all__"] 165 if isinstance(all, compiler.ast.List): 166 for n in all.nodes: 167 self.namespace[n.value] = self.importer.add_module(self.name + "." + n.value) 168 return processed 169 170 def vacuum(self): 171 for name, value in self.namespace.items(): 172 if isinstance(value, Module) and not value.loaded: 173 del self.namespace[name] 174 175 def full_name(self): 176 return self.name 177 178 def __repr__(self): 179 return "Module(%r)" % self.name 180 181 # Namespace methods. 182 183 def store(self, name, obj): 184 if self.in_global: 185 if not self.classes: 186 self.namespace[name] = obj 187 else: 188 self.classes[-1].namespace[name] = obj 189 190 def store_attr(self, name): 191 if self.in_init: 192 self.classes[-1].instattr.add(name) 193 194 # Visitor methods. 195 196 def default(self, node, *args): 197 raise InspectError, node.__class__ 198 199 def dispatch(self, node, *args): 200 return ASTVisitor.dispatch(self, node, *args) 201 202 def NOP(self, node): 203 return node 204 205 visitAdd = NOP 206 207 visitAnd = NOP 208 209 visitAssert = NOP 210 211 def visitAssign(self, node): 212 self.expr = self.dispatch(node.expr) 213 for n in node.nodes: 214 self.dispatch(n) 215 return None 216 217 def visitAssAttr(self, node): 218 expr = self.dispatch(node.expr) 219 if expr is not None and isinstance(expr, Self): 220 self.store_attr(node.attrname) 221 return None 222 223 def visitAssList(self, node): 224 for n in node.nodes: 225 self.dispatch(n) 226 return None 227 228 def visitAssName(self, node): 229 self.store(node.name, self.expr) 230 return None 231 232 visitAssTuple = visitAssList 233 234 visitAugAssign = NOP 235 236 visitBackquote = NOP 237 238 visitBitand = NOP 239 240 visitBitor = NOP 241 242 visitBitxor = NOP 243 244 visitBreak = NOP 245 246 visitCallFunc = NOP 247 248 def visitClass(self, node): 249 if not self.in_global: 250 print "Class %r in %r is not global: ignored." % (node.name, self) 251 else: 252 cls = Class(node.name, self.name) 253 for base in node.bases: 254 base_ref = self.dispatch(base) 255 if base_ref is None: 256 raise InspectError, "Base class %r for class %r in %r is not found: it may be hidden in some way." % (base, self, cls) 257 cls.add_base(base_ref) 258 259 # Make an entry for the class. 260 261 self.store(node.name, cls) 262 263 self.classes.append(cls) 264 self.dispatch(node.code) 265 self.classes.pop() 266 267 return node 268 269 visitCompare = NOP 270 271 visitConst = NOP 272 273 visitContinue = NOP 274 275 visitDecorators = NOP 276 277 visitDict = NOP 278 279 visitDiscard = NOP 280 281 visitDiv = NOP 282 283 visitEllipsis = NOP 284 285 visitExec = NOP 286 287 visitExpression = NOP 288 289 visitFloorDiv = NOP 290 291 visitFor = NOP 292 293 def visitFrom(self, node): 294 if self.importer is None: 295 raise InspectError, "Please use the micropython.Importer class for code which uses the 'from' statement." 296 297 module = self.importer.load(node.modname, 1) 298 299 if module is None: 300 print "Warning:", node.modname, "not imported." 301 302 for name, alias in node.names: 303 if name != "*": 304 if module is not None and module.namespace.has_key(name): 305 self.namespace[alias or name] = attr = module.namespace[name] 306 if isinstance(attr, Module) and not attr.loaded: 307 self.importer.load(attr.name) 308 else: 309 self.namespace[alias or name] = UnresolvedName(name, node.modname) 310 else: 311 if module is not None: 312 for n in module.namespace.keys(): 313 self.namespace[n] = attr = module.namespace[n] 314 if isinstance(attr, Module) and not attr.loaded: 315 self.importer.load(attr.name) 316 317 return None 318 319 def visitFunction(self, node): 320 function = Function( 321 node.name, 322 self.name, 323 node.argnames, 324 has_star=(node.flags & 4 != 0), 325 has_dstar=(node.flags & 8 != 0) 326 ) 327 328 in_global = self.in_global 329 self.in_global = 0 330 if node.name == "__init__" and self.classes: 331 self.in_init = 1 332 self.dispatch(node.code) 333 self.in_init = 0 334 self.in_global = in_global 335 336 self.store(node.name, function) 337 return None 338 339 visitGenExpr = NOP 340 341 visitGenExprFor = NOP 342 343 visitGenExprIf = NOP 344 345 visitGenExprInner = NOP 346 347 def visitGetattr(self, node): 348 expr = self.dispatch(node.expr) 349 if expr is not None: 350 if isinstance(expr, Module): 351 return expr.namespace.get(node.attrname) 352 elif isinstance(expr, UnresolvedName): 353 return UnresolvedName(node.attrname, expr.full_name()) 354 return builtins_namespace.get(node.attrname) 355 356 visitGlobal = NOP 357 358 def visitIf(self, node): 359 for test, body in node.tests: 360 self.dispatch(body) 361 if node.else_ is not None: 362 self.dispatch(node.else_) 363 return None 364 365 def visitImport(self, node): 366 if self.importer is None: 367 raise InspectError, "Please use the micropython.Importer class for code which uses the 'import' statement." 368 369 for name, alias in node.names: 370 if alias is not None: 371 self.namespace[alias] = self.importer.load(name, 1) or UnresolvedName(None, name) 372 else: 373 self.namespace[name.split(".")[0]] = self.importer.load(name) or UnresolvedName(None, name.split(".")[0]) 374 375 return None 376 377 visitInvert = NOP 378 379 visitKeyword = NOP 380 381 visitLambda = NOP 382 383 visitLeftShift = NOP 384 385 visitList = NOP 386 387 visitListComp = NOP 388 389 visitListCompFor = NOP 390 391 visitListCompIf = NOP 392 393 visitMod = NOP 394 395 def visitModule(self, node): 396 return self.dispatch(node.node) 397 398 visitMul = NOP 399 400 def visitName(self, node): 401 name = node.name 402 if name == "self": 403 return Self() 404 elif self.namespace.has_key(name): 405 return self.namespace[name] 406 elif builtins_namespace.has_key(name): 407 return builtins_namespace[name] 408 else: 409 return None 410 411 visitNot = NOP 412 413 visitOr = NOP 414 415 visitPass = NOP 416 417 visitPower = NOP 418 419 visitPrint = NOP 420 421 visitPrintnl = NOP 422 423 visitRaise = NOP 424 425 visitReturn = NOP 426 427 visitRightShift = NOP 428 429 visitSlice = NOP 430 431 def visitStmt(self, node): 432 for n in node.nodes: 433 self.dispatch(n) 434 return None 435 436 visitSub = NOP 437 438 visitSubscript = NOP 439 440 def visitTryExcept(self, node): 441 self.dispatch(node.body) 442 for name, var, n in node.handlers: 443 self.dispatch(n) 444 if node.else_ is not None: 445 self.dispatch(node.else_) 446 return None 447 448 visitTryFinally = NOP 449 450 visitTuple = NOP 451 452 visitUnaryAdd = NOP 453 454 visitUnarySub = NOP 455 456 visitWhile = NOP 457 458 visitWith = NOP 459 460 visitYield = NOP 461 462 class Self: 463 464 "A reference to an object within a method." 465 466 pass 467 468 # Built-in types initialisation. 469 470 builtins_namespace = {} 471 for key in ['ArithmeticError', 'AssertionError', 'AttributeError', 472 'BaseException', 'DeprecationWarning', 'EOFError', 'Ellipsis', 473 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 474 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 475 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 476 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 477 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 478 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 479 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 480 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 481 'TabError', 'True', 'TypeError', 'UnboundLocalError', 482 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 483 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 484 'ValueError', 'Warning', 'ZeroDivisionError', 485 'bool', 'buffer', 'complex', 'dict', 'file', 'float', 'int', 'list', 486 'long', 'object', 'slice', 'str', 'tuple', 'type', 'unicode']: 487 builtins_namespace[key] = Class(key, "__builtins__") 488 489 # vim: tabstop=4 expandtab shiftwidth=4