1 #!/usr/bin/env python 2 3 """ 4 Annotate program node structures. The code in this module operates upon nodes 5 which are produced when simplifying AST node trees originating from the compiler 6 module. 7 8 Copyright (C) 2006 Paul Boddie <paul@boddie.org.uk> 9 10 This software is free software; you can redistribute it and/or 11 modify it under the terms of the GNU General Public License as 12 published by the Free Software Foundation; either version 2 of 13 the License, or (at your option) any later version. 14 15 This software is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU General Public License for more details. 19 20 You should have received a copy of the GNU General Public 21 License along with this library; see the file LICENCE.txt 22 If not, write to the Free Software Foundation, Inc., 23 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 24 25 -------- 26 27 To use this module, the easiest approach is to use the annotate function: 28 29 annotate(module, builtins) 30 31 The more complicated approach involves obtaining an Annotator: 32 33 annotator = Annotator() 34 35 Then, processing an existing module with it: 36 37 annotator.process(module) 38 39 If a module containing built-in classes and functions has already been 40 annotated, such a module should be passed in as an additional argument: 41 42 annotator.process(module, builtins) 43 """ 44 45 from simplified import * 46 import compiler 47 48 class System: 49 50 """ 51 A class maintaining the state of the annotation system. When the system 52 counter can no longer be incremented by any annotation operation, the 53 system may be considered stable and fully annotated. 54 """ 55 56 def __init__(self): 57 self.count = 0 58 def init(self, node): 59 if not hasattr(node, "types"): 60 node.types = [] 61 def annotate(self, node, types): 62 self.init(node) 63 for type in types: 64 if type not in node.types: 65 node.types.append(type) 66 self.count += 1 67 68 system = System() 69 70 # Exceptions. 71 72 class AnnotationError(SimplifiedError): 73 74 "An error in the annotation process." 75 76 pass 77 78 class AnnotationMessage(Exception): 79 80 "A lesser annotation error." 81 82 pass 83 84 # Annotation. 85 86 class Annotator(Visitor): 87 88 """ 89 The type annotator which traverses the program nodes, typically depth-first, 90 and maintains a record of the current set of types applying to the currently 91 considered operation. Such types are also recorded on the nodes, and a 92 special "system" record is maintained to monitor the level of annotation 93 activity with a view to recognising when no more annotations are possible. 94 95 Throughout the annotation activity, type information consists of lists of 96 Attribute objects where such objects retain information about the context of 97 the type (since a value in the program may be associated with an object or 98 class) and the actual type of the value being manipulated. Upon accessing 99 attribute information on namespaces, additional accessor information is also 100 exchanged - this provides a means of distinguishing between the different 101 types possible when the means of constructing the namespace may depend on 102 run-time behaviour. 103 """ 104 105 def __init__(self): 106 107 "Initialise the visitor." 108 109 Visitor.__init__(self) 110 self.system = system 111 112 # Satisfy visitor issues. 113 114 self.visitor = self 115 116 def process(self, module, builtins=None): 117 118 """ 119 Process the given 'module', using the optional 'builtins' to access 120 built-in classes and functions. 121 """ 122 123 self.subprograms = [] 124 self.current_subprograms = [] 125 self.current_namespaces = [] 126 self.namespace = None 127 self.module = module 128 129 # Give constants their own namespace. 130 131 for value, constant in module.simplifier.constants.items(): 132 constant.namespace = Namespace() 133 134 # Process the module, supplying builtins if possible. 135 136 self.builtins = builtins 137 self.global_namespace = Namespace() 138 139 if builtins is not None: 140 self.builtins_namespace = builtins.namespace 141 else: 142 self.builtins_namespace = self.global_namespace 143 144 return self.process_node(module) 145 146 def process_node(self, node, locals=None): 147 148 """ 149 Process a subprogram or module 'node', indicating any initial 'locals'. 150 Return an annotated subprogram or module. Note that this method may 151 mutate nodes in the original program. 152 """ 153 154 # Record the current subprogram and namespace. 155 156 self.current_subprograms.append(node) 157 158 # Determine the namespace. 159 160 if locals is not None: 161 self.current_namespaces.append(self.namespace) 162 self.namespace = locals 163 else: 164 self.namespace = self.global_namespace 165 166 # Add namespace details to any structure involved. 167 168 if getattr(node, "structure", None) is not None: 169 node.structure.namespace = Namespace() 170 171 # Initialise bases where appropriate. 172 173 if hasattr(node.structure, "bases"): 174 base_refs = [] 175 for base in node.structure.bases: 176 self.dispatch(base) 177 base_refs.append(self.namespace.types) 178 node.structure.base_refs = base_refs 179 180 # Dispatch to the code itself. 181 182 node.namespace = self.namespace 183 result = self.dispatch(node) 184 result.namespace = self.namespace 185 186 # Obtain the return values. 187 188 self.last_returns = self.namespace.returns 189 self.last_raises = self.namespace.raises 190 self.returned_locals = self.namespace.return_locals 191 192 # Restore the previous subprogram and namespace. 193 194 if locals is not None: 195 self.namespace = self.current_namespaces.pop() 196 self.current_subprograms.pop() 197 198 return result 199 200 def annotate(self, node, types=None): 201 202 """ 203 Annotate the given 'node' in the system, using either the optional 204 'types' or the namespace's current type information. 205 """ 206 207 self.system.annotate(node, types or self.namespace.types) 208 209 # Visitor methods. 210 211 def default(self, node): 212 213 """ 214 Process the given 'node', given that it does not have a specific 215 handler. 216 """ 217 218 raise AnnotationMessage, "Node '%s' not supported." % node 219 220 def dispatch(self, node, *args): 221 try: 222 return Visitor.dispatch(self, node, *args) 223 except AnnotationError, exc: 224 exc.add(node) 225 raise 226 except AnnotationMessage, exc: 227 raise AnnotationError(exc, node) 228 229 # Program structure/control-flow. 230 231 def visitAssign(self, assign): 232 assign.code = self.dispatches(assign.code) 233 return assign 234 235 def visitConditional(self, conditional): 236 237 # Conditionals keep local namespace changes isolated. 238 # With Return nodes inside the body/else sections, the changes are 239 # communicated to the caller. 240 241 conditional.test = self.dispatch(conditional.test) 242 saved_namespace = self.namespace 243 is_global = self.namespace is self.global_namespace 244 245 self.namespace = Namespace() 246 if is_global: 247 self.module.namespace = self.global_namespace = self.namespace 248 self.namespace.merge_namespace(saved_namespace) 249 conditional.body = self.dispatches(conditional.body) 250 body_namespace = self.namespace 251 252 self.namespace = Namespace() 253 if is_global: 254 self.module.namespace = self.global_namespace = self.namespace 255 self.namespace.merge_namespace(saved_namespace) 256 conditional.else_ = self.dispatches(conditional.else_) 257 else_namespace = self.namespace 258 259 self.namespace = Namespace() 260 if is_global: 261 self.module.namespace = self.global_namespace = self.namespace 262 self.namespace.merge_namespace(body_namespace) 263 self.namespace.merge_namespace(else_namespace) 264 265 return conditional 266 267 def visitModule(self, module): 268 module.code = self.dispatches(module.code) 269 return module 270 271 def visitPass(self, pass_): 272 return pass_ 273 274 def visitSubprogram(self, subprogram): 275 subprogram.code = self.dispatches(subprogram.code) 276 return subprogram 277 278 def visitTry(self, try_): 279 try_.body = self.dispatches(try_.body) 280 try_.handler = self.dispatches(try_.handler) 281 raises = self.namespace.raises 282 283 # Empty the raised exceptions for the else clause. 284 285 self.namespace.raises = [] 286 try_.else_ = self.dispatches(try_.else_) 287 self.namespace.raises = raises 288 289 try_.finally_ = self.dispatches(try_.finally_) 290 return try_ 291 292 # Namespace operations. 293 294 def visitLoadAttr(self, loadattr): 295 loadattr.expr = self.dispatch(loadattr.expr) 296 types = [] 297 non_types = [] 298 accesses = {} 299 for attr in self.namespace.types: 300 attributes = get_attributes(attr.type, loadattr.name) 301 if not attributes: 302 print "No attributes for", loadattr.name, "in", attr.type 303 if not attr.type in non_types: 304 non_types.append(attr) 305 for attribute, accessor in attributes: 306 if attribute is not None: 307 types.append(attribute) 308 if not accesses.has_key(attr.type): 309 accesses[attr.type] = [] 310 if not (attribute, accessor) in accesses[attr.type]: 311 accesses[attr.type].append((attribute, accessor)) 312 else: 313 print "Empty attribute", loadattr.name, "via accessor", accessor 314 if not attr.type in non_types: 315 non_types.append(attr) 316 self.namespace.set_types(types) 317 loadattr.non_types = non_types 318 loadattr.accesses = accesses 319 self.annotate(loadattr) 320 return loadattr 321 322 def visitLoadExc(self, loadexc): 323 self.namespace.types = self.namespace.raises[:] 324 self.annotate(loadexc) 325 return loadexc 326 327 def visitLoadName(self, loadname): 328 self.namespace.set_types(self.namespace.load(loadname.name)) 329 result = loadname 330 self.annotate(result) 331 return result 332 333 def visitLoadRef(self, loadref): 334 self.namespace.set_types([Attribute(None, loadref.ref)]) 335 self.annotate(loadref) 336 return loadref 337 338 def visitLoadTemp(self, loadtemp): 339 index = getattr(loadtemp, "index", None) 340 try: 341 if getattr(loadtemp, "release", 0): 342 self.namespace.set_types(self.namespace.temp[index].pop()) 343 else: 344 self.namespace.set_types(self.namespace.temp[index][-1]) 345 except KeyError: 346 raise AnnotationMessage, "Temporary store index '%s' not defined." % index 347 self.annotate(loadtemp) 348 return loadtemp 349 350 def visitNot(self, not_): 351 not_.expr = self.dispatch(not_.expr) 352 return not_ 353 354 def visitRaise(self, raise_): 355 if getattr(raise_, "traceback", None) is not None: 356 raise_.traceback = self.dispatch(raise_.traceback) 357 raise_.expr = self.dispatch(raise_.expr) 358 359 # Handle bare name exceptions by converting any classes to instances. 360 361 if not isinstance(raise_.expr, InvokeFunction): 362 raise_.pos_args = [] 363 raise_.kw_args = {} 364 raise_.star = None 365 raise_.dstar = None 366 types = [] 367 for attr in self.namespace.types: 368 if isinstance(attr.type, Class): 369 self._visitInvoke(raise_, [attr], have_args=0) 370 types += self.namespace.types 371 else: 372 types = self.namespace.types 373 374 combine(self.namespace.raises, types) 375 return raise_ 376 377 def visitReleaseTemp(self, releasetemp): 378 index = getattr(releasetemp, "index", None) 379 try: 380 self.namespace.temp[index].pop() 381 except KeyError: 382 raise AnnotationMessage, "Temporary store index '%s' not defined." % index 383 except IndexError: 384 pass #raise AnnotationMessage, "Temporary store index '%s' is empty." % index 385 return releasetemp 386 387 def visitReturn(self, return_): 388 if hasattr(return_, "expr"): 389 return_.expr = self.dispatch(return_.expr) 390 combine(self.namespace.returns, self.namespace.types) 391 self.annotate(return_) 392 self.namespace.snapshot() 393 return return_ 394 395 def visitStoreAttr(self, storeattr): 396 storeattr.expr = self.dispatch(storeattr.expr) 397 expr = self.namespace.types 398 storeattr.lvalue = self.dispatch(storeattr.lvalue) 399 writes = {} 400 for attr in self.namespace.types: 401 if attr is None: 402 print "Empty attribute storage attempt" 403 continue 404 attr.type.namespace.store(storeattr.name, expr) 405 writes[attr.type] = attr.type.namespace.load(storeattr.name) 406 storeattr.writes = writes 407 return storeattr 408 409 def visitStoreName(self, storename): 410 storename.expr = self.dispatch(storename.expr) 411 self.namespace.store(storename.name, self.namespace.types) 412 return storename 413 414 def visitStoreTemp(self, storetemp): 415 storetemp.expr = self.dispatch(storetemp.expr) 416 index = getattr(storetemp, "index", None) 417 if not self.namespace.temp.has_key(index): 418 self.namespace.temp[index] = [] 419 self.namespace.temp[index].append(self.namespace.types) 420 return storetemp 421 422 # Invocations are a chapter of their own. 423 424 def visitInvokeBlock(self, invoke): 425 426 # First find the callables. 427 428 invoke.expr = self.dispatch(invoke.expr) 429 invocation_types = self.namespace.types 430 return self._visitInvoke(invoke, invocation_types, have_args=0) 431 432 def visitInvokeFunction(self, invoke): 433 434 # First find the callables. 435 436 invoke.expr = self.dispatch(invoke.expr) 437 invocation_types = self.namespace.types 438 439 # Invocation processing starts with making sure that the arguments have 440 # been processed. 441 442 return self._visitInvoke(invoke, invocation_types, have_args=self.process_args(invoke)) 443 444 def _visitInvoke(self, invoke, invocation_types, have_args): 445 446 # Now locate and invoke the subprogram. This can be complicated because 447 # the target may be a class or object, and there may be many different 448 # related subprograms. 449 450 invocations = [] 451 452 # Visit each callable in turn, finding subprograms. 453 454 for attr in invocation_types: 455 456 # Deal with class invocations by providing instance objects. 457 # Here, each class is queried for the __init__ method, which may 458 # exist for some combinations of classes in a hierarchy but not for 459 # others. 460 461 if isinstance(attr.type, Class): 462 attributes = get_attributes(attr.type, "__init__") 463 464 # Deal with object invocations by using __call__ methods. 465 466 elif isinstance(attr.type, Instance): 467 attributes = get_attributes(attr.type, "__call__") 468 469 # Normal functions or methods are more straightforward. 470 # Here, we model them using an attribute with no context and with 471 # no associated accessor. 472 473 else: 474 attributes = [(attr, None)] 475 476 # Inspect each attribute and extract the subprogram. 477 478 for attribute, accessor in attributes: 479 480 # If a class is involved, presume that it must create a new 481 # object. 482 483 if isinstance(attr.type, Class): 484 485 # Instantiate the class. 486 # NOTE: Should probably only allocate a single instance. 487 488 instance = self.new_instance(invoke, "new", attr.type.full_name(), attr.type) 489 490 # For instantiations, switch the context. 491 492 if attribute is not None: 493 attribute = Attribute(instance, attribute.type) 494 495 # Skip cases where no callable is found. 496 497 if attribute is not None: 498 499 # If a subprogram is defined, invoke it. 500 501 self.invoke_subprogram(invoke, attribute) 502 if attribute.type not in invocations: 503 invocations.append(attribute.type) 504 505 elif not isinstance(attr.type, Class): 506 print "Invocation type is None for", accessor 507 508 else: 509 510 # Test to see if no arguments were supplied in cases where no 511 # initialiser was found. 512 513 if have_args: 514 raise AnnotationMessage, "No initialiser found for '%s' with arguments." % attr.type 515 516 # Special case: initialisation. 517 518 if isinstance(attr.type, Class): 519 520 # Associate the instance with the result of this invocation. 521 522 self.namespace.set_types([Attribute(None, instance)]) 523 self.annotate(invoke) 524 525 # Remember the invocations that were found, along with the return type 526 # information. 527 528 invoke.invocations = invocations 529 self.namespace.set_types(getattr(invoke, "types", [])) 530 return invoke 531 532 # Utility methods. 533 534 def new_instance(self, node, reason, target, type): 535 536 "Create, on the given 'node', a new instance with the given 'type'." 537 538 if not hasattr(node, "instances"): 539 node.instances = {} 540 541 if not node.instances.has_key((reason, target, type)): 542 543 # Insist on a single instance per type. 544 # NOTE: Strategy-dependent instantiation. 545 546 if len(type.instances) == 0: 547 instance = Instance() 548 instance.namespace = Namespace() 549 instance.namespace.store("__class__", [Attribute(None, type)]) 550 type.instances.append(instance) 551 else: 552 instance = type.instances[0] 553 554 #instance = Instance() 555 #instance.namespace = Namespace() 556 #instance.namespace.store("__class__", [Attribute(None, type)]) 557 #type.instances.append(instance) 558 559 node.instances[(reason, target, type)] = instance 560 561 return node.instances[(reason, target, type)] 562 563 def invoke_subprogram(self, invoke, subprogram): 564 565 "Invoke using the given 'invoke' node the given 'subprogram'." 566 567 # Test to see if anything has changed. 568 569 if hasattr(invoke, "syscount") and invoke.syscount == self.system.count: 570 return 571 572 # Remember the state of the system. 573 574 else: 575 invoke.syscount = self.system.count 576 577 # Test for context information, making it into a real attribute. 578 579 if subprogram.context is not None: 580 context = Attribute(None, subprogram.context) 581 target = subprogram.type 582 else: 583 context = None 584 target = subprogram.type 585 586 # Provide the correct namespace for the invocation. 587 588 if getattr(invoke, "share_locals", 0): 589 namespace = Namespace() 590 namespace.merge_namespace(self.namespace) 591 elif getattr(target, "structure", None): 592 namespace = Namespace() 593 else: 594 items = self.make_items(invoke, target, context) 595 namespace = self.make_namespace(items) 596 597 # Process the subprogram. 598 599 self.process_node(target, namespace) 600 601 # NOTE: Improve and verify this. 602 # If the invocation returns a value, acquire the return types. 603 604 if getattr(target, "returns_value", 0): 605 self.namespace.set_types(self.last_returns) 606 self.annotate(invoke) 607 608 # Otherwise, assuming it is a normal block, merge the locals. 609 610 elif getattr(invoke, "share_locals", 0): 611 for locals in self.returned_locals: 612 self.namespace.merge_namespace(locals) 613 614 # Incorporate any raised exceptions. 615 616 combine(self.namespace.raises, self.last_raises) 617 618 def process_args(self, invocation): 619 620 """ 621 Process the arguments associated with an 'invocation'. Return whether 622 any arguments were processed. 623 """ 624 625 invocation.pos_args = self.dispatches(invocation.pos_args) 626 invocation.kw_args = self.dispatch_dict(invocation.kw_args) 627 628 # Get type information for star and dstar arguments. 629 630 if invocation.star is not None: 631 param, default = invocation.star 632 default = self.dispatch(default) 633 invocation.star = param, default 634 635 if invocation.dstar is not None: 636 param, default = invocation.dstar 637 default = self.dispatch(default) 638 invocation.dstar = param, default 639 640 if invocation.pos_args or invocation.kw_args or invocation.star or invocation.dstar: 641 return 1 642 else: 643 return 0 644 645 def make_items(self, invocation, subprogram, context): 646 647 """ 648 Make an items mapping for the 'invocation' of the 'subprogram' using the 649 given 'context' (which may be None). 650 """ 651 652 if context is not None: 653 pos_args = [Self(context)] + invocation.pos_args 654 else: 655 pos_args = invocation.pos_args 656 kw_args = invocation.kw_args 657 658 # Sort the arguments into positional and keyword arguments. 659 660 params = subprogram.params 661 items = [] 662 star_args = [] 663 664 # Match each positional argument, taking excess arguments as star args. 665 666 for arg in pos_args: 667 if params: 668 param, default = params[0] 669 if arg is None: 670 arg = default 671 items.append((param, arg.types)) 672 params = params[1:] 673 else: 674 star_args.append(arg) 675 676 # Collect the remaining defaults. 677 678 while params: 679 param, default = params[0] 680 if kw_args.has_key(param): 681 arg = kw_args[param] 682 del kw_args[param] 683 elif default is not None: 684 arg = self.dispatch(default) 685 else: 686 raise AnnotationMessage, "No argument supplied in '%s' for parameter '%s'." % (subprogram, param) 687 items.append((param, arg.types)) 688 params = params[1:] 689 690 dstar_args = kw_args.values() 691 692 # Construct temporary objects. 693 694 if star_args: 695 star_invocation = self.make_star_args(invocation, subprogram, star_args) 696 self.dispatch(star_invocation) 697 star_types = star_invocation.types 698 else: 699 star_types = None 700 701 if dstar_args: 702 dstar_invocation = self.make_dstar_args(invocation, subprogram, dstar_args) # NOTE: To be written! 703 self.dispatch(dstar_invocation) 704 dstar_types = dstar_invocation.types 705 else: 706 dstar_types = None 707 708 # NOTE: Merge the objects properly. 709 710 star_types = star_types or invocation.star and invocation.star.types 711 dstar_types = dstar_types or invocation.dstar and invocation.dstar.types 712 713 # Add star and dstar. 714 715 if star_types is not None: 716 if subprogram.star is not None: 717 param, default = subprogram.star 718 items.append((param, star_types)) 719 else: 720 raise AnnotationMessage, "Invocation provides unwanted *args." 721 elif subprogram.star is not None: 722 param, default = subprogram.star 723 arg = self.dispatch(default) # NOTE: Review reprocessing. 724 items.append((param, arg.types)) 725 726 if dstar_types is not None: 727 if subprogram.dstar is not None: 728 param, default = subprogram.dstar 729 items.append((param, dstar_types)) 730 else: 731 raise AnnotationMessage, "Invocation provides unwanted **args." 732 elif subprogram.dstar is not None: 733 param, default = subprogram.dstar 734 arg = self.dispatch(default) # NOTE: Review reprocessing. 735 items.append((param, arg.types)) 736 737 # Record the parameter types. 738 739 subprogram.paramtypes = {} 740 for param, types in items: 741 subprogram.paramtypes[param] = types 742 743 return items 744 745 def make_star_args(self, invocation, subprogram, star_args): 746 747 "Make a subprogram which initialises a list containing 'star_args'." 748 749 if not hasattr(invocation, "stars"): 750 invocation.stars = {} 751 752 if not invocation.stars.has_key(subprogram.full_name()): 753 code=[ 754 StoreTemp( 755 expr=InvokeFunction( 756 expr=LoadAttr( 757 expr=LoadRef( 758 ref=self.builtins 759 ), 760 name="list", 761 nstype="module", 762 ), 763 args=[], 764 star=None, 765 dstar=None 766 ) 767 ) 768 ] 769 770 for arg in star_args: 771 code.append( 772 InvokeFunction( 773 expr=LoadAttr( 774 expr=LoadTemp(), 775 name="append" 776 ), 777 args=[arg], 778 star=None, 779 dstar=None 780 ) 781 ) 782 783 code += [ 784 Return(expr=LoadTemp(release=1)) 785 ] 786 787 invocation.stars[subprogram.full_name()] = InvokeBlock( 788 produces_result=1, 789 expr=LoadRef( 790 ref=Subprogram( 791 name=None, 792 returns_value=1, 793 params=[], 794 star=None, 795 dstar=None, 796 code=code 797 ) 798 ) 799 ) 800 801 return invocation.stars[subprogram.full_name()] 802 803 def make_namespace(self, items): 804 namespace = Namespace() 805 namespace.merge_items(items) 806 return namespace 807 808 # Namespace-related abstractions. 809 810 class Namespace: 811 812 """ 813 A local namespace which may either relate to a genuine set of function 814 locals or the initialisation of a structure or module. 815 """ 816 817 def __init__(self): 818 819 """ 820 Initialise the namespace with a mapping of local names to possible 821 types, a list of return values and of possible returned local 822 namespaces. The namespace also tracks the "current" types and a mapping 823 of temporary value names to types. 824 """ 825 826 self.names = {} 827 self.returns = [] 828 self.return_locals = [] 829 self.raises = [] 830 self.temp = {} 831 self.types = [] 832 833 def set_types(self, types): 834 self.types = types 835 836 def store(self, name, types): 837 self.names[name] = types 838 839 __setitem__ = store 840 841 def load(self, name): 842 return self.names[name] 843 844 __getitem__ = load 845 846 def merge_namespace(self, namespace): 847 self.merge_items(namespace.names.items()) 848 combine(self.returns, namespace.returns) 849 combine(self.raises, namespace.raises) 850 self.temp = {} 851 self.temp.update(namespace.temp) 852 853 def merge_items(self, items): 854 for name, types in items: 855 self.merge(name, types) 856 857 def merge(self, name, types): 858 if not self.names.has_key(name): 859 self.names[name] = types[:] 860 else: 861 existing = self.names[name] 862 combine(existing, types) 863 864 def snapshot(self): 865 866 "Make a snapshot of the locals and remember them." 867 868 namespace = Namespace() 869 namespace.merge_namespace(self) 870 self.return_locals.append(namespace) 871 872 def __repr__(self): 873 return repr(self.names) 874 875 class Attribute: 876 877 """ 878 An attribute abstraction, indicating the type of the attribute along with 879 its context or origin. 880 """ 881 882 def __init__(self, context, type): 883 self.context = context 884 self.type = type 885 886 def __eq__(self, other): 887 return hasattr(other, "type") and other.type == self.type or other == self.type 888 889 def __repr__(self): 890 return "Attribute(%s, %s)" % (repr(self.context), repr(self.type)) 891 892 class Self: 893 894 """ 895 A program node encapsulating object/context information in an argument list. 896 This is not particularly like Attribute, Class, Instance or other such 897 things, since it actually appears in the program representation. 898 """ 899 900 def __init__(self, attribute): 901 self.types = [attribute] 902 903 def combine(target, additions): 904 905 """ 906 Merge into the 'target' sequence the given 'additions', preventing duplicate 907 items. 908 """ 909 910 for addition in additions: 911 if addition not in target: 912 target.append(addition) 913 914 def find_attributes(structure, name): 915 916 """ 917 Find for the given 'structure' all attributes for the given 'name', visiting 918 base classes where appropriate and returning the attributes in order of 919 descending precedence for all possible base classes. 920 921 The elements in the result list are 2-tuples which contain the attribute and 922 the structure involved in accessing the attribute. 923 """ 924 925 # First attempt to search the instance/class namespace. 926 927 try: 928 l = structure.namespace.load(name) 929 attributes = [] 930 for attribute in l: 931 attributes.append((attribute, structure)) 932 933 # If that does not work, attempt to investigate any class or base classes. 934 935 except KeyError: 936 attributes = [] 937 938 # Investigate any instance's implementing class. 939 940 if isinstance(structure, Instance): 941 for attr in structure.namespace.load("__class__"): 942 cls = attr.type 943 l = get_attributes(cls, name) 944 combine(attributes, l) 945 946 # Investigate any class's base classes. 947 948 elif isinstance(structure, Class): 949 950 # If no base classes exist, return an indicator that no attribute 951 # exists. 952 953 if not structure.base_refs: 954 return [(None, structure)] 955 956 # Otherwise, find all possible base classes. 957 958 for base_refs in structure.base_refs: 959 base_attributes = [] 960 961 # For each base class, find attributes either in the base 962 # class or its own base classes. 963 964 for base_ref in base_refs: 965 l = get_attributes(base_ref, name) 966 combine(base_attributes, l) 967 968 combine(attributes, base_attributes) 969 970 return attributes 971 972 def get_attributes(structure, name): 973 974 """ 975 Return all possible attributes for the given 'structure' having the given 976 'name', wrapping each attribute in an Attribute object which includes 977 context information for the attribute access. 978 979 The elements in the result list are 2-tuples which contain the attribute and 980 the structure involved in accessing the attribute. 981 """ 982 983 if isinstance(structure, Attribute): 984 structure = structure.type 985 results = [] 986 for attribute, accessor in find_attributes(structure, name): 987 if attribute is not None and isinstance(structure, Structure): 988 results.append((Attribute(structure, attribute.type), accessor)) 989 else: 990 results.append((attribute, accessor)) 991 return results 992 993 # Convenience functions. 994 995 def annotate(module, builtins=None): 996 997 """ 998 Annotate the given 'module', also employing the optional 'builtins' module, 999 if specified. 1000 """ 1001 1002 annotator = Annotator() 1003 if builtins is not None: 1004 annotator.process(module, builtins) 1005 else: 1006 annotator.process(module) 1007 1008 def annotate_all(modules, builtins): 1009 annotate(builtins) 1010 for module in modules: 1011 annotate(module, builtins) 1012 1013 # vim: tabstop=4 expandtab shiftwidth=4