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