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