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