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