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