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_accesses = [] 298 accesses = {} 299 for attr in self.namespace.types: 300 attributes = get_attributes(attr.type, loadattr.name) 301 if not attributes: 302 if not attr.type in non_accesses: 303 non_accesses.append(attr) 304 for attribute, accessor in attributes: 305 if attribute is not None: 306 types.append(attribute) 307 if not accesses.has_key(attr.type): 308 accesses[attr.type] = [] 309 if not (attribute, accessor) in accesses[attr.type]: 310 accesses[attr.type].append((attribute, accessor)) 311 else: 312 if not attr in non_accesses: 313 non_accesses.append(attr) 314 if not types: 315 print "No attribute found for", loadattr.name, "given", self.namespace.types 316 self.namespace.set_types(types) 317 loadattr.non_accesses = non_accesses 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 non_writes = [] 401 for attr in self.namespace.types: 402 if attr is None: 403 if not attr in non_writes: 404 non_writes.append(attr) 405 continue 406 attr.type.namespace.store(storeattr.name, expr) 407 writes[attr.type] = attr.type.namespace.load(storeattr.name) 408 if not writes: 409 print "Unable to store attribute", storeattr.name, "given", self.namespace.types 410 storeattr.writes = writes 411 storeattr.non_writes = non_writes 412 return storeattr 413 414 def visitStoreName(self, storename): 415 storename.expr = self.dispatch(storename.expr) 416 self.namespace.store(storename.name, self.namespace.types) 417 return storename 418 419 def visitStoreTemp(self, storetemp): 420 storetemp.expr = self.dispatch(storetemp.expr) 421 index = getattr(storetemp, "index", None) 422 if not self.namespace.temp.has_key(index): 423 self.namespace.temp[index] = [] 424 self.namespace.temp[index].append(self.namespace.types) 425 return storetemp 426 427 # Invocations are a chapter of their own. 428 429 def visitInvokeBlock(self, invoke): 430 431 # First find the callables. 432 433 invoke.expr = self.dispatch(invoke.expr) 434 invocation_types = self.namespace.types 435 return self._visitInvoke(invoke, invocation_types, have_args=0) 436 437 def visitInvokeFunction(self, invoke): 438 439 # First find the callables. 440 441 invoke.expr = self.dispatch(invoke.expr) 442 invocation_types = self.namespace.types 443 444 # Invocation processing starts with making sure that the arguments have 445 # been processed. 446 447 return self._visitInvoke(invoke, invocation_types, have_args=self.process_args(invoke)) 448 449 def _visitInvoke(self, invoke, invocation_types, have_args): 450 451 # Now locate and invoke the subprogram. This can be complicated because 452 # the target may be a class or object, and there may be many different 453 # related subprograms. 454 455 invocations = [] 456 457 # Visit each callable in turn, finding subprograms. 458 459 for attr in invocation_types: 460 461 # Deal with class invocations by providing instance objects. 462 # Here, each class is queried for the __init__ method, which may 463 # exist for some combinations of classes in a hierarchy but not for 464 # others. 465 466 if isinstance(attr.type, Class): 467 attributes = get_attributes(attr.type, "__init__") 468 469 # Deal with object invocations by using __call__ methods. 470 471 elif isinstance(attr.type, Instance): 472 attributes = get_attributes(attr.type, "__call__") 473 474 # Normal functions or methods are more straightforward. 475 # Here, we model them using an attribute with no context and with 476 # no associated accessor. 477 478 else: 479 attributes = [(attr, None)] 480 481 # Inspect each attribute and extract the subprogram. 482 483 for attribute, accessor in attributes: 484 485 # If a class is involved, presume that it must create a new 486 # object. 487 488 if isinstance(attr.type, Class): 489 490 # Instantiate the class. 491 # NOTE: Should probably only allocate a single instance. 492 493 instance = self.new_instance(invoke, "new", attr.type.full_name(), attr.type) 494 495 # For instantiations, switch the context. 496 497 if attribute is not None: 498 attribute = Attribute(instance, attribute.type) 499 500 # Skip cases where no callable is found. 501 502 if attribute is not None: 503 504 # If a subprogram is defined, invoke it. 505 506 self.invoke_subprogram(invoke, attribute) 507 if attribute.type not in invocations: 508 invocations.append(attribute.type) 509 510 elif not isinstance(attr.type, Class): 511 print "Invocation type is None for", accessor 512 513 else: 514 515 # Test to see if no arguments were supplied in cases where no 516 # initialiser was found. 517 518 if have_args: 519 raise AnnotationMessage, "No initialiser found for '%s' with arguments." % attr.type 520 521 # Special case: initialisation. 522 523 if isinstance(attr.type, Class): 524 525 # Associate the instance with the result of this invocation. 526 527 self.namespace.set_types([Attribute(None, instance)]) 528 self.annotate(invoke) 529 530 # Remember the invocations that were found, along with the return type 531 # information. 532 533 invoke.invocations = invocations 534 self.namespace.set_types(getattr(invoke, "types", [])) 535 return invoke 536 537 # Utility methods. 538 539 def new_instance(self, node, reason, target, type): 540 541 "Create, on the given 'node', a new instance with the given 'type'." 542 543 if not hasattr(node, "instances"): 544 node.instances = {} 545 546 if not node.instances.has_key((reason, target, type)): 547 548 # Insist on a single instance per type. 549 # NOTE: Strategy-dependent instantiation. 550 551 if len(type.instances) == 0: 552 instance = Instance() 553 instance.namespace = Namespace() 554 instance.namespace.store("__class__", [Attribute(None, type)]) 555 type.instances.append(instance) 556 else: 557 instance = type.instances[0] 558 559 #instance = Instance() 560 #instance.namespace = Namespace() 561 #instance.namespace.store("__class__", [Attribute(None, type)]) 562 #type.instances.append(instance) 563 564 node.instances[(reason, target, type)] = instance 565 566 return node.instances[(reason, target, type)] 567 568 def invoke_subprogram(self, invoke, subprogram): 569 570 "Invoke using the given 'invoke' node the given 'subprogram'." 571 572 # Test to see if anything has changed. 573 574 if hasattr(invoke, "syscount") and invoke.syscount == self.system.count: 575 return 576 577 # Remember the state of the system. 578 579 else: 580 invoke.syscount = self.system.count 581 582 # Test for context information, making it into a real attribute. 583 584 if subprogram.context is not None: 585 context = Attribute(None, subprogram.context) 586 target = subprogram.type 587 else: 588 context = None 589 target = subprogram.type 590 591 # Provide the correct namespace for the invocation. 592 593 if getattr(invoke, "share_locals", 0): 594 namespace = Namespace() 595 namespace.merge_namespace(self.namespace) 596 elif getattr(target, "structure", None): 597 namespace = Namespace() 598 else: 599 items = self.make_items(invoke, target, context) 600 namespace = self.make_namespace(items) 601 602 # Process the subprogram. 603 604 self.process_node(target, namespace) 605 606 # NOTE: Improve and verify this. 607 # If the invocation returns a value, acquire the return types. 608 609 if getattr(target, "returns_value", 0): 610 self.namespace.set_types(self.last_returns) 611 self.annotate(invoke) 612 613 # Otherwise, assuming it is a normal block, merge the locals. 614 615 elif getattr(invoke, "share_locals", 0): 616 for locals in self.returned_locals: 617 self.namespace.merge_namespace(locals) 618 619 # Incorporate any raised exceptions. 620 621 combine(self.namespace.raises, self.last_raises) 622 623 def process_args(self, invocation): 624 625 """ 626 Process the arguments associated with an 'invocation'. Return whether 627 any arguments were processed. 628 """ 629 630 invocation.pos_args = self.dispatches(invocation.pos_args) 631 invocation.kw_args = self.dispatch_dict(invocation.kw_args) 632 633 # Get type information for star and dstar arguments. 634 635 if invocation.star is not None: 636 param, default = invocation.star 637 default = self.dispatch(default) 638 invocation.star = param, default 639 640 if invocation.dstar is not None: 641 param, default = invocation.dstar 642 default = self.dispatch(default) 643 invocation.dstar = param, default 644 645 if invocation.pos_args or invocation.kw_args or invocation.star or invocation.dstar: 646 return 1 647 else: 648 return 0 649 650 def make_items(self, invocation, subprogram, context): 651 652 """ 653 Make an items mapping for the 'invocation' of the 'subprogram' using the 654 given 'context' (which may be None). 655 """ 656 657 if context is not None: 658 pos_args = [Self(context)] + invocation.pos_args 659 else: 660 pos_args = invocation.pos_args 661 kw_args = invocation.kw_args 662 663 # Sort the arguments into positional and keyword arguments. 664 665 params = subprogram.params 666 items = [] 667 star_args = [] 668 669 # Match each positional argument, taking excess arguments as star args. 670 671 for arg in pos_args: 672 if params: 673 param, default = params[0] 674 if arg is None: 675 arg = default 676 items.append((param, arg.types)) 677 params = params[1:] 678 else: 679 star_args.append(arg) 680 681 # Collect the remaining defaults. 682 683 while params: 684 param, default = params[0] 685 if kw_args.has_key(param): 686 arg = kw_args[param] 687 del kw_args[param] 688 elif default is not None: 689 arg = self.dispatch(default) 690 else: 691 raise AnnotationMessage, "No argument supplied in '%s' for parameter '%s'." % (subprogram, param) 692 items.append((param, arg.types)) 693 params = params[1:] 694 695 dstar_args = kw_args.values() 696 697 # Construct temporary objects. 698 699 if star_args: 700 star_invocation = self.make_star_args(invocation, subprogram, star_args) 701 self.dispatch(star_invocation) 702 star_types = star_invocation.types 703 else: 704 star_types = None 705 706 if dstar_args: 707 dstar_invocation = self.make_dstar_args(invocation, subprogram, dstar_args) # NOTE: To be written! 708 self.dispatch(dstar_invocation) 709 dstar_types = dstar_invocation.types 710 else: 711 dstar_types = None 712 713 # NOTE: Merge the objects properly. 714 715 star_types = star_types or invocation.star and invocation.star.types 716 dstar_types = dstar_types or invocation.dstar and invocation.dstar.types 717 718 # Add star and dstar. 719 720 if star_types is not None: 721 if subprogram.star is not None: 722 param, default = subprogram.star 723 items.append((param, star_types)) 724 else: 725 raise AnnotationMessage, "Invocation provides unwanted *args." 726 elif subprogram.star is not None: 727 param, default = subprogram.star 728 arg = self.dispatch(default) # NOTE: Review reprocessing. 729 items.append((param, arg.types)) 730 731 if dstar_types is not None: 732 if subprogram.dstar is not None: 733 param, default = subprogram.dstar 734 items.append((param, dstar_types)) 735 else: 736 raise AnnotationMessage, "Invocation provides unwanted **args." 737 elif subprogram.dstar is not None: 738 param, default = subprogram.dstar 739 arg = self.dispatch(default) # NOTE: Review reprocessing. 740 items.append((param, arg.types)) 741 742 # Record the parameter types. 743 744 subprogram.paramtypes = {} 745 for param, types in items: 746 subprogram.paramtypes[param] = types 747 748 return items 749 750 def make_star_args(self, invocation, subprogram, star_args): 751 752 "Make a subprogram which initialises a list containing 'star_args'." 753 754 if not hasattr(invocation, "stars"): 755 invocation.stars = {} 756 757 if not invocation.stars.has_key(subprogram.full_name()): 758 code=[ 759 StoreTemp( 760 expr=InvokeFunction( 761 expr=LoadAttr( 762 expr=LoadRef( 763 ref=self.builtins 764 ), 765 name="list", 766 nstype="module", 767 ), 768 args=[], 769 star=None, 770 dstar=None 771 ) 772 ) 773 ] 774 775 for arg in star_args: 776 code.append( 777 InvokeFunction( 778 expr=LoadAttr( 779 expr=LoadTemp(), 780 name="append" 781 ), 782 args=[arg], 783 star=None, 784 dstar=None 785 ) 786 ) 787 788 code += [ 789 Return(expr=LoadTemp(release=1)) 790 ] 791 792 invocation.stars[subprogram.full_name()] = InvokeBlock( 793 produces_result=1, 794 expr=LoadRef( 795 ref=Subprogram( 796 name=None, 797 returns_value=1, 798 params=[], 799 star=None, 800 dstar=None, 801 code=code 802 ) 803 ) 804 ) 805 806 return invocation.stars[subprogram.full_name()] 807 808 def make_namespace(self, items): 809 namespace = Namespace() 810 namespace.merge_items(items) 811 return namespace 812 813 # Namespace-related abstractions. 814 815 class Namespace: 816 817 """ 818 A local namespace which may either relate to a genuine set of function 819 locals or the initialisation of a structure or module. 820 """ 821 822 def __init__(self): 823 824 """ 825 Initialise the namespace with a mapping of local names to possible 826 types, a list of return values and of possible returned local 827 namespaces. The namespace also tracks the "current" types and a mapping 828 of temporary value names to types. 829 """ 830 831 self.names = {} 832 self.returns = [] 833 self.return_locals = [] 834 self.raises = [] 835 self.temp = {} 836 self.types = [] 837 838 def set_types(self, types): 839 self.types = types 840 841 def store(self, name, types): 842 self.names[name] = types 843 844 __setitem__ = store 845 846 def load(self, name): 847 return self.names[name] 848 849 __getitem__ = load 850 851 def merge_namespace(self, namespace): 852 self.merge_items(namespace.names.items()) 853 combine(self.returns, namespace.returns) 854 combine(self.raises, namespace.raises) 855 self.temp = {} 856 self.temp.update(namespace.temp) 857 858 def merge_items(self, items): 859 for name, types in items: 860 self.merge(name, types) 861 862 def merge(self, name, types): 863 if not self.names.has_key(name): 864 self.names[name] = types[:] 865 else: 866 existing = self.names[name] 867 combine(existing, types) 868 869 def snapshot(self): 870 871 "Make a snapshot of the locals and remember them." 872 873 namespace = Namespace() 874 namespace.merge_namespace(self) 875 self.return_locals.append(namespace) 876 877 def __repr__(self): 878 return repr(self.names) 879 880 class Attribute: 881 882 """ 883 An attribute abstraction, indicating the type of the attribute along with 884 its context or origin. 885 """ 886 887 def __init__(self, context, type): 888 self.context = context 889 self.type = type 890 891 def __eq__(self, other): 892 return hasattr(other, "type") and other.type == self.type or other == self.type 893 894 def __repr__(self): 895 return "Attribute(%s, %s)" % (repr(self.context), repr(self.type)) 896 897 class Self: 898 899 """ 900 A program node encapsulating object/context information in an argument list. 901 This is not particularly like Attribute, Class, Instance or other such 902 things, since it actually appears in the program representation. 903 """ 904 905 def __init__(self, attribute): 906 self.types = [attribute] 907 908 def combine(target, additions): 909 910 """ 911 Merge into the 'target' sequence the given 'additions', preventing duplicate 912 items. 913 """ 914 915 for addition in additions: 916 if addition not in target: 917 target.append(addition) 918 919 def find_attributes(structure, name): 920 921 """ 922 Find for the given 'structure' all attributes for the given 'name', visiting 923 base classes where appropriate and returning the attributes in order of 924 descending precedence for all possible base classes. 925 926 The elements in the result list are 2-tuples which contain the attribute and 927 the structure involved in accessing the attribute. 928 """ 929 930 # First attempt to search the instance/class namespace. 931 932 try: 933 l = structure.namespace.load(name) 934 attributes = [] 935 for attribute in l: 936 attributes.append((attribute, structure)) 937 938 # If that does not work, attempt to investigate any class or base classes. 939 940 except KeyError: 941 attributes = [] 942 943 # Investigate any instance's implementing class. 944 945 if isinstance(structure, Instance): 946 for attr in structure.namespace.load("__class__"): 947 cls = attr.type 948 l = get_attributes(cls, name) 949 combine(attributes, l) 950 951 # Investigate any class's base classes. 952 953 elif isinstance(structure, Class): 954 955 # If no base classes exist, return an indicator that no attribute 956 # exists. 957 958 if not structure.base_refs: 959 return [(None, structure)] 960 961 # Otherwise, find all possible base classes. 962 963 for base_refs in structure.base_refs: 964 base_attributes = [] 965 966 # For each base class, find attributes either in the base 967 # class or its own base classes. 968 969 for base_ref in base_refs: 970 l = get_attributes(base_ref, name) 971 combine(base_attributes, l) 972 973 combine(attributes, base_attributes) 974 975 return attributes 976 977 def get_attributes(structure, name): 978 979 """ 980 Return all possible attributes for the given 'structure' having the given 981 'name', wrapping each attribute in an Attribute object which includes 982 context information for the attribute access. 983 984 The elements in the result list are 2-tuples which contain the attribute and 985 the structure involved in accessing the attribute. 986 """ 987 988 if isinstance(structure, Attribute): 989 structure = structure.type 990 results = [] 991 for attribute, accessor in find_attributes(structure, name): 992 if attribute is not None and isinstance(structure, Structure): 993 results.append((Attribute(structure, attribute.type), accessor)) 994 else: 995 results.append((attribute, accessor)) 996 return results 997 998 # Convenience functions. 999 1000 def annotate(module, builtins=None): 1001 1002 """ 1003 Annotate the given 'module', also employing the optional 'builtins' module, 1004 if specified. 1005 """ 1006 1007 annotator = Annotator() 1008 if builtins is not None: 1009 annotator.process(module, builtins) 1010 else: 1011 annotator.process(module) 1012 1013 def annotate_all(modules, builtins): 1014 annotate(builtins) 1015 for module in modules: 1016 annotate(module, builtins) 1017 1018 # vim: tabstop=4 expandtab shiftwidth=4