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