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 # Namespace methods. 422 423 def store(self, name, obj): 424 425 "Record attribute or local 'name', storing 'obj'." 426 427 if not self.namespaces: 428 self[name] = obj 429 else: 430 self.namespaces[-1][name] = obj 431 432 # Record all non-local objects. 433 434 if not (self.namespaces and isinstance(self.namespaces[-1], Function)): 435 self.all_objects.append(obj) 436 437 def store_attr(self, name): 438 439 "Record instance attribute 'name' in the current class." 440 441 if self.in_init: 442 443 # Current namespace is the function. 444 # Previous namespace is the class. 445 446 self.namespaces[-2].add_instance_attribute(name) 447 448 def get_parent(self): 449 return (self.namespaces[-1:] or [self])[0] 450 451 # Visitor methods. 452 453 def default(self, node, *args): 454 raise InspectError, node.__class__ 455 456 def dispatch(self, node, *args): 457 return ASTVisitor.dispatch(self, node, *args) 458 459 def NOP(self, node): 460 return node 461 462 visitAdd = NOP 463 464 visitAnd = NOP 465 466 visitAssert = NOP 467 468 def visitAssign(self, node): 469 self.expr = self.dispatch(node.expr) 470 for n in node.nodes: 471 self.dispatch(n) 472 return None 473 474 def visitAssAttr(self, node): 475 expr = self.dispatch(node.expr) 476 if expr is not None and isinstance(expr, Self): 477 self.store_attr(node.attrname) 478 return None 479 480 def visitAssList(self, node): 481 for n in node.nodes: 482 self.dispatch(n) 483 return None 484 485 def visitAssName(self, node): 486 self.store(node.name, self.expr) 487 return None 488 489 visitAssTuple = visitAssList 490 491 visitAugAssign = NOP 492 493 visitBackquote = NOP 494 495 visitBitand = NOP 496 497 visitBitor = NOP 498 499 visitBitxor = NOP 500 501 visitBreak = NOP 502 503 visitCallFunc = NOP 504 505 def visitClass(self, node): 506 if self.namespaces: 507 print "Class %r in %r is not global: ignored." % (node.name, self) 508 else: 509 cls = Class(node.name, self.get_parent().full_name()) 510 for base in node.bases: 511 base_ref = self.dispatch(base) 512 if base_ref is None: 513 raise InspectError, "Base class %r for class %r in %r is not found: it may be hidden in some way." % (base, self, cls) 514 cls.add_base(base_ref) 515 516 # Make an entry for the class. 517 518 self.store(node.name, cls) 519 520 self.namespaces.append(cls) 521 self.dispatch(node.code) 522 self.namespaces.pop() 523 524 return node 525 526 visitCompare = NOP 527 528 visitConst = NOP 529 530 visitContinue = NOP 531 532 visitDecorators = NOP 533 534 visitDict = NOP 535 536 visitDiscard = NOP 537 538 visitDiv = NOP 539 540 visitEllipsis = NOP 541 542 visitExec = NOP 543 544 visitExpression = NOP 545 546 visitFloorDiv = NOP 547 548 visitFor = NOP 549 550 def visitFrom(self, node): 551 if self.importer is None: 552 raise InspectError, "Please use the micropython.Importer class for code which uses the 'from' statement." 553 554 module = self.importer.load(node.modname, 1) 555 556 if module is None: 557 print "Warning:", node.modname, "not imported." 558 559 for name, alias in node.names: 560 if name != "*": 561 if module is not None and module.namespace.has_key(name): 562 self[alias or name] = attr = module[name] 563 if isinstance(attr, Module) and not attr.loaded: 564 self.importer.load(attr.name) 565 else: 566 self[alias or name] = UnresolvedName(name, node.modname) 567 else: 568 if module is not None: 569 for n in module.namespace.keys(): 570 self[n] = attr = module[n] 571 if isinstance(attr, Module) and not attr.loaded: 572 self.importer.load(attr.name) 573 574 return None 575 576 def visitFunction(self, node): 577 function = Function( 578 node.name, 579 self.get_parent().full_name(), 580 node.argnames, 581 has_star=(node.flags & 4 != 0), 582 has_dstar=(node.flags & 8 != 0) 583 ) 584 585 self.namespaces.append(function) 586 587 # Current namespace is the function. 588 # Previous namespace is the class. 589 590 if node.name == "__init__" and isinstance(self.namespaces[-2], Class): 591 self.in_init = 1 592 593 self.dispatch(node.code) 594 self.in_init = 0 595 self.namespaces.pop() 596 597 self.store(node.name, function) 598 return None 599 600 visitGenExpr = NOP 601 602 visitGenExprFor = NOP 603 604 visitGenExprIf = NOP 605 606 visitGenExprInner = NOP 607 608 def visitGetattr(self, node): 609 expr = self.dispatch(node.expr) 610 if expr is not None: 611 if isinstance(expr, Module): 612 return expr.namespace.get(node.attrname) 613 elif isinstance(expr, UnresolvedName): 614 return UnresolvedName(node.attrname, expr.full_name()) 615 return builtins.get(node.attrname) 616 617 def visitGlobal(self, node): 618 if self.namespaces: 619 for name in node.names: 620 self.namespaces[-1].make_global(name) 621 if not self.has_key(name): 622 self[name] = None 623 624 def visitIf(self, node): 625 for test, body in node.tests: 626 self.dispatch(body) 627 if node.else_ is not None: 628 self.dispatch(node.else_) 629 return None 630 631 visitIfExp = NOP 632 633 def visitImport(self, node): 634 if self.importer is None: 635 raise InspectError, "Please use the micropython.Importer class for code which uses the 'import' statement." 636 637 for name, alias in node.names: 638 if alias is not None: 639 self[alias] = self.importer.load(name, 1) or UnresolvedName(None, name) 640 else: 641 self[name.split(".")[0]] = self.importer.load(name) or UnresolvedName(None, name.split(".")[0]) 642 643 return None 644 645 visitInvert = NOP 646 647 visitKeyword = NOP 648 649 visitLambda = NOP 650 651 visitLeftShift = NOP 652 653 visitList = NOP 654 655 visitListComp = NOP 656 657 visitListCompFor = NOP 658 659 visitListCompIf = NOP 660 661 visitMod = NOP 662 663 def visitModule(self, node): 664 return self.dispatch(node.node) 665 666 visitMul = NOP 667 668 def visitName(self, node): 669 name = node.name 670 if name == "self": 671 return Self() 672 elif self.has_key(name): 673 return self[name] 674 elif builtins.has_key(name): 675 return builtins[name] 676 else: 677 return None 678 679 visitNot = NOP 680 681 visitOr = NOP 682 683 visitPass = NOP 684 685 visitPower = NOP 686 687 visitPrint = NOP 688 689 visitPrintnl = NOP 690 691 visitRaise = NOP 692 693 visitReturn = NOP 694 695 visitRightShift = NOP 696 697 visitSlice = NOP 698 699 def visitStmt(self, node): 700 for n in node.nodes: 701 self.dispatch(n) 702 return None 703 704 visitSub = NOP 705 706 visitSubscript = NOP 707 708 def visitTryExcept(self, node): 709 self.dispatch(node.body) 710 for name, var, n in node.handlers: 711 self.dispatch(n) 712 if node.else_ is not None: 713 self.dispatch(node.else_) 714 return None 715 716 visitTryFinally = NOP 717 718 visitTuple = NOP 719 720 visitUnaryAdd = NOP 721 722 visitUnarySub = NOP 723 724 visitWhile = NOP 725 726 visitWith = NOP 727 728 visitYield = NOP 729 730 class Self: 731 732 "A reference to an object within a method." 733 734 pass 735 736 # Built-in types initialisation. 737 738 class Builtins(Module): 739 740 "The special built-in types module." 741 742 def __init__(self): 743 Module.__init__(self, "__builtins__") 744 745 for key in ['ArithmeticError', 'AssertionError', 'AttributeError', 746 'BaseException', 'DeprecationWarning', 'EOFError', 'Ellipsis', 747 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 748 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 749 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 750 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 751 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 752 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 753 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 754 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 755 'TabError', 'True', 'TypeError', 'UnboundLocalError', 756 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 757 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 758 'ValueError', 'Warning', 'ZeroDivisionError', 759 'bool', 'buffer', 'complex', 'dict', 'file', 'float', 'int', 'list', 760 'long', 'object', 'slice', 'str', 'tuple', 'type', 'unicode']: 761 self[key] = Class(key, self.full_name()) 762 763 builtins = Builtins() 764 765 # vim: tabstop=4 expandtab shiftwidth=4