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