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