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