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