1 #!/usr/bin/env python 2 3 """ 4 Common functions. 5 6 Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 7 2014, 2015, 2016, 2017 Paul Boddie <paul@boddie.org.uk> 8 9 This program is free software; you can redistribute it and/or modify it under 10 the terms of the GNU General Public License as published by the Free Software 11 Foundation; either version 3 of the License, or (at your option) any later 12 version. 13 14 This program is distributed in the hope that it will be useful, but WITHOUT 15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17 details. 18 19 You should have received a copy of the GNU General Public License along with 20 this program. If not, see <http://www.gnu.org/licenses/>. 21 """ 22 23 from errors import InspectError 24 from os import listdir, makedirs, remove 25 from os.path import exists, isdir, join, split 26 from results import ConstantValueRef, LiteralSequenceRef, NameRef 27 from compiler.transformer import Transformer 28 import compiler.ast 29 30 class CommonOutput: 31 32 "Common output functionality." 33 34 def check_output(self): 35 36 "Check the existing output and remove it if irrelevant." 37 38 if not exists(self.output): 39 makedirs(self.output) 40 41 details = self.importer.get_cache_details() 42 recorded_details = self.get_output_details() 43 44 if recorded_details != details: 45 self.remove_output() 46 47 writefile(self.get_output_details_filename(), details) 48 49 def get_output_details_filename(self): 50 51 "Return the output details filename." 52 53 return join(self.output, "$details") 54 55 def get_output_details(self): 56 57 "Return details of the existing output." 58 59 details_filename = self.get_output_details_filename() 60 61 if not exists(details_filename): 62 return None 63 else: 64 return readfile(details_filename) 65 66 def remove_output(self, dirname=None): 67 68 "Remove the output." 69 70 dirname = dirname or self.output 71 72 for filename in listdir(dirname): 73 path = join(dirname, filename) 74 if isdir(path): 75 self.remove_output(path) 76 else: 77 remove(path) 78 79 class CommonModule: 80 81 "A common module representation." 82 83 def __init__(self, name, importer): 84 85 """ 86 Initialise this module with the given 'name' and an 'importer' which is 87 used to provide access to other modules when required. 88 """ 89 90 self.name = name 91 self.importer = importer 92 self.filename = None 93 94 # Inspection-related attributes. 95 96 self.astnode = None 97 self.encoding = None 98 self.iterators = {} 99 self.temp = {} 100 self.lambdas = {} 101 102 # Constants, literals and values. 103 104 self.constants = {} 105 self.constant_values = {} 106 self.literals = {} 107 self.literal_types = {} 108 109 # Nested namespaces. 110 111 self.namespace_path = [] 112 self.in_function = False 113 114 # Retain the assignment value expression and track invocations. 115 116 self.in_assignment = None 117 self.in_invocation = False 118 119 # Attribute chain state management. 120 121 self.attrs = [] 122 self.chain_assignment = [] 123 self.chain_invocation = [] 124 125 def __repr__(self): 126 return "CommonModule(%r, %r)" % (self.name, self.importer) 127 128 def parse_file(self, filename): 129 130 "Parse the file with the given 'filename', initialising attributes." 131 132 self.filename = filename 133 134 # Use the Transformer directly to obtain encoding information. 135 136 t = Transformer() 137 f = open(filename) 138 139 try: 140 self.astnode = t.parsesuite(f.read() + "\n") 141 self.encoding = t.encoding 142 finally: 143 f.close() 144 145 # Module-relative naming. 146 147 def get_global_path(self, name): 148 return "%s.%s" % (self.name, name) 149 150 def get_namespace_path(self): 151 return ".".join([self.name] + self.namespace_path) 152 153 def get_object_path(self, name): 154 return ".".join([self.name] + self.namespace_path + [name]) 155 156 def get_parent_path(self): 157 return ".".join([self.name] + self.namespace_path[:-1]) 158 159 # Namespace management. 160 161 def enter_namespace(self, name): 162 163 "Enter the namespace having the given 'name'." 164 165 self.namespace_path.append(name) 166 167 def exit_namespace(self): 168 169 "Exit the current namespace." 170 171 self.namespace_path.pop() 172 173 # Constant reference naming. 174 175 def get_constant_name(self, value, value_type, encoding=None): 176 177 """ 178 Add a new constant to the current namespace for 'value' with 179 'value_type'. 180 """ 181 182 path = self.get_namespace_path() 183 init_item(self.constants, path, dict) 184 return "$c%d" % add_counter_item(self.constants[path], (value, value_type, encoding)) 185 186 # Literal reference naming. 187 188 def get_literal_name(self): 189 190 "Add a new literal to the current namespace." 191 192 path = self.get_namespace_path() 193 init_item(self.literals, path, lambda: 0) 194 return "$C%d" % self.literals[path] 195 196 def next_literal(self): 197 self.literals[self.get_namespace_path()] += 1 198 199 # Temporary iterator naming. 200 201 def get_iterator_path(self): 202 return self.in_function and self.get_namespace_path() or self.name 203 204 def get_iterator_name(self): 205 path = self.get_iterator_path() 206 init_item(self.iterators, path, lambda: 0) 207 return "$i%d" % self.iterators[path] 208 209 def next_iterator(self): 210 self.iterators[self.get_iterator_path()] += 1 211 212 # Temporary variable naming. 213 214 def get_temporary_name(self): 215 path = self.get_namespace_path() 216 init_item(self.temp, path, lambda: 0) 217 return "$t%d" % self.temp[path] 218 219 def next_temporary(self): 220 self.temp[self.get_namespace_path()] += 1 221 222 # Arbitrary function naming. 223 224 def get_lambda_name(self): 225 path = self.get_namespace_path() 226 init_item(self.lambdas, path, lambda: 0) 227 name = "$l%d" % self.lambdas[path] 228 self.lambdas[path] += 1 229 return name 230 231 def reset_lambdas(self): 232 self.lambdas = {} 233 234 # Constant and literal recording. 235 236 def get_constant_value(self, value, literal=None): 237 238 """ 239 Encode the 'value' if appropriate, returning a value, a typename and any 240 encoding. 241 """ 242 243 if isinstance(value, unicode): 244 return value.encode("utf-8"), "unicode", self.encoding 245 246 # Attempt to convert plain strings to text. 247 248 elif isinstance(value, str) and self.encoding: 249 if not literal.startswith("b"): 250 try: 251 return unicode(value, self.encoding).encode("utf-8"), "unicode", self.encoding 252 except UnicodeDecodeError: 253 pass 254 255 return value, value.__class__.__name__, None 256 257 def get_constant_reference(self, ref, value, encoding=None): 258 259 """ 260 Return a constant reference for the given 'ref' type and 'value', with 261 the optional 'encoding' applying to text values. 262 """ 263 264 constant_name = self.get_constant_name(value, ref.get_origin(), encoding) 265 266 # Return a reference for the constant. 267 268 objpath = self.get_object_path(constant_name) 269 name_ref = ConstantValueRef(constant_name, ref.instance_of(objpath), value) 270 271 # Record the value and type for the constant. 272 273 self._reserve_constant(objpath, name_ref.value, name_ref.get_origin(), encoding) 274 return name_ref 275 276 def reserve_constant(self, objpath, value, origin, encoding=None): 277 278 """ 279 Reserve a constant within 'objpath' with the given 'value' and having a 280 type with the given 'origin', with the optional 'encoding' applying to 281 text values. 282 """ 283 284 constant_name = self.get_constant_name(value, origin) 285 objpath = self.get_object_path(constant_name) 286 self._reserve_constant(objpath, value, origin, encoding) 287 288 def _reserve_constant(self, objpath, value, origin, encoding): 289 290 """ 291 Store a constant for 'objpath' with the given 'value' and 'origin', with 292 the optional 'encoding' applying to text values. 293 """ 294 295 self.constant_values[objpath] = value, origin, encoding 296 297 def get_literal_reference(self, name, ref, items, cls): 298 299 """ 300 Return a literal reference for the given type 'name', literal 'ref', 301 node 'items' and employing the given 'cls' as the class of the returned 302 reference object. 303 """ 304 305 # Construct an invocation using the items as arguments. 306 307 typename = "$L%s" % name 308 309 invocation = compiler.ast.CallFunc( 310 compiler.ast.Name(typename), 311 items 312 ) 313 314 # Get a name for the actual literal. 315 316 instname = self.get_literal_name() 317 self.next_literal() 318 319 # Record the type for the literal. 320 321 objpath = self.get_object_path(instname) 322 self.literal_types[objpath] = ref.get_origin() 323 324 # Return a wrapper for the invocation exposing the items. 325 326 return cls( 327 instname, 328 ref.instance_of(), 329 self.process_structure_node(invocation), 330 invocation.args 331 ) 332 333 # Node handling. 334 335 def process_structure(self, node): 336 337 """ 338 Within the given 'node', process the program structure. 339 340 During inspection, this will process global declarations, adjusting the 341 module namespace, and import statements, building a module dependency 342 hierarchy. 343 344 During translation, this will consult deduced program information and 345 output translated code. 346 """ 347 348 l = [] 349 for n in node.getChildNodes(): 350 l.append(self.process_structure_node(n)) 351 return l 352 353 def process_augassign_node(self, n): 354 355 "Process the given augmented assignment node 'n'." 356 357 op = operator_functions[n.op] 358 359 if isinstance(n.node, compiler.ast.Getattr): 360 target = compiler.ast.AssAttr(n.node.expr, n.node.attrname, "OP_ASSIGN") 361 elif isinstance(n.node, compiler.ast.Name): 362 target = compiler.ast.AssName(n.node.name, "OP_ASSIGN") 363 else: 364 target = n.node 365 366 assignment = compiler.ast.Assign( 367 [target], 368 compiler.ast.CallFunc( 369 compiler.ast.Name("$op%s" % op), 370 [n.node, n.expr])) 371 372 return self.process_structure_node(assignment) 373 374 def process_assignment_for_object(self, original_name, source): 375 376 """ 377 Return an assignment operation making 'original_name' refer to the given 378 'source'. 379 """ 380 381 assignment = compiler.ast.Assign( 382 [compiler.ast.AssName(original_name, "OP_ASSIGN")], 383 source 384 ) 385 386 return self.process_structure_node(assignment) 387 388 def process_assignment_node_items(self, n, expr): 389 390 """ 391 Process the given assignment node 'n' whose children are to be assigned 392 items of 'expr'. 393 """ 394 395 name_ref = self.process_structure_node(expr) 396 397 # Either unpack the items and present them directly to each assignment 398 # node. 399 400 if isinstance(name_ref, LiteralSequenceRef) and \ 401 self.process_literal_sequence_items(n, name_ref): 402 403 pass 404 405 # Or have the assignment nodes access each item via the sequence API. 406 407 else: 408 self.process_assignment_node_items_by_position(n, expr, name_ref) 409 410 def process_assignment_node_items_by_position(self, n, expr, name_ref): 411 412 """ 413 Process the given sequence assignment node 'n', converting the node to 414 the separate assignment of each target using positional access on a 415 temporary variable representing the sequence. Use 'expr' as the assigned 416 value and 'name_ref' as the reference providing any existing temporary 417 variable. 418 """ 419 420 assignments = [] 421 422 # Employ existing names to access the sequence. 423 # Literal sequences do not provide names of accessible objects. 424 425 if isinstance(name_ref, NameRef) and not isinstance(name_ref, LiteralSequenceRef): 426 temp = name_ref.name 427 428 # For other expressions, create a temporary name to reference the items. 429 430 else: 431 temp = self.get_temporary_name() 432 self.next_temporary() 433 434 assignments.append( 435 compiler.ast.Assign([compiler.ast.AssName(temp, "OP_ASSIGN")], expr) 436 ) 437 438 # Assign the items to the target nodes. 439 440 for i, node in enumerate(n.nodes): 441 assignments.append( 442 compiler.ast.Assign([node], compiler.ast.Subscript( 443 compiler.ast.Name(temp), "OP_APPLY", [compiler.ast.Const(i, str(i))])) 444 ) 445 446 return self.process_structure_node(compiler.ast.Stmt(assignments)) 447 448 def process_literal_sequence_items(self, n, name_ref): 449 450 """ 451 Process the given assignment node 'n', obtaining from the given 452 'name_ref' the items to be assigned to the assignment targets. 453 454 Return whether this method was able to process the assignment node as 455 a sequence of direct assignments. 456 """ 457 458 if len(n.nodes) == len(name_ref.items): 459 assigned_names, count = get_names_from_nodes(n.nodes) 460 accessed_names, _count = get_names_from_nodes(name_ref.items) 461 462 # Only assign directly between items if all assigned names are 463 # plain names (not attribute assignments), and if the assigned names 464 # do not appear in the accessed names. 465 466 if len(assigned_names) == count and \ 467 not assigned_names.intersection(accessed_names): 468 469 for node, item in zip(n.nodes, name_ref.items): 470 self.process_assignment_node(node, item) 471 472 return True 473 474 # Otherwise, use the position-based mechanism to obtain values. 475 476 else: 477 return False 478 else: 479 raise InspectError("In %s, item assignment needing %d items is given %d items." % ( 480 self.get_namespace_path(), len(n.nodes), len(name_ref.items))) 481 482 def process_compare_node(self, n): 483 484 """ 485 Process the given comparison node 'n', converting an operator sequence 486 from... 487 488 <expr1> <op1> <expr2> <op2> <expr3> 489 490 ...to... 491 492 <op1>(<expr1>, <expr2>) and <op2>(<expr2>, <expr3>) 493 """ 494 495 invocations = [] 496 last = n.expr 497 498 for op, op_node in n.ops: 499 op = operator_functions.get(op) 500 501 invocations.append(compiler.ast.CallFunc( 502 compiler.ast.Name("$op%s" % op), 503 [last, op_node])) 504 505 last = op_node 506 507 if len(invocations) > 1: 508 result = compiler.ast.And(invocations) 509 else: 510 result = invocations[0] 511 512 return self.process_structure_node(result) 513 514 def process_dict_node(self, node): 515 516 """ 517 Process the given dictionary 'node', returning a list of (key, value) 518 tuples. 519 """ 520 521 l = [] 522 for key, value in node.items: 523 l.append(( 524 self.process_structure_node(key), 525 self.process_structure_node(value))) 526 return l 527 528 def process_for_node(self, n): 529 530 """ 531 Generate attribute accesses for {n.list}.__iter__ and the next method on 532 the iterator, producing a replacement node for the original. 533 """ 534 535 node = compiler.ast.Stmt([ 536 537 # <iterator> = {n.list}.__iter__ 538 539 compiler.ast.Assign( 540 [compiler.ast.AssName(self.get_iterator_name(), "OP_ASSIGN")], 541 compiler.ast.CallFunc( 542 compiler.ast.Getattr(n.list, "__iter__"), 543 [] 544 )), 545 546 # try: 547 # while True: 548 # <var>... = <iterator>.next() 549 # ... 550 # except StopIteration: 551 # pass 552 553 compiler.ast.TryExcept( 554 compiler.ast.While( 555 compiler.ast.Name("True"), 556 compiler.ast.Stmt([ 557 compiler.ast.Assign( 558 [n.assign], 559 compiler.ast.CallFunc( 560 compiler.ast.Getattr(compiler.ast.Name(self.get_iterator_name()), "next"), 561 [] 562 )), 563 n.body]), 564 None), 565 [(compiler.ast.Name("StopIteration"), None, compiler.ast.Stmt([compiler.ast.Pass()]))], 566 None) 567 ]) 568 569 self.next_iterator() 570 self.process_structure_node(node) 571 572 def process_literal_sequence_node(self, n, name, ref, cls): 573 574 """ 575 Process the given literal sequence node 'n' as a function invocation, 576 with 'name' indicating the type of the sequence, and 'ref' being a 577 reference to the type. The 'cls' is used to instantiate a suitable name 578 reference. 579 """ 580 581 if name == "dict": 582 items = [] 583 for key, value in n.items: 584 items.append(compiler.ast.Tuple([key, value])) 585 else: # name in ("list", "tuple"): 586 items = n.nodes 587 588 return self.get_literal_reference(name, ref, items, cls) 589 590 def process_operator_node(self, n): 591 592 """ 593 Process the given operator node 'n' as an operator function invocation. 594 """ 595 596 op = operator_functions[n.__class__.__name__] 597 invocation = compiler.ast.CallFunc( 598 compiler.ast.Name("$op%s" % op), 599 list(n.getChildNodes()) 600 ) 601 return self.process_structure_node(invocation) 602 603 def process_print_node(self, n): 604 605 """ 606 Process the given print node 'n' as an invocation on a stream of the 607 form... 608 609 $print(dest, args, nl) 610 611 The special function name will be translated elsewhere. 612 """ 613 614 nl = isinstance(n, compiler.ast.Printnl) 615 invocation = compiler.ast.CallFunc( 616 compiler.ast.Name("$print"), 617 [n.dest or compiler.ast.Name("None"), 618 compiler.ast.List(list(n.nodes)), 619 nl and compiler.ast.Name("True") or compiler.ast.Name("False")] 620 ) 621 return self.process_structure_node(invocation) 622 623 def process_slice_node(self, n, expr=None): 624 625 """ 626 Process the given slice node 'n' as an operator function invocation. 627 """ 628 629 op = n.flags == "OP_ASSIGN" and "setslice" or "getslice" 630 invocation = compiler.ast.CallFunc( 631 compiler.ast.Name("$op%s" % op), 632 [n.expr, n.lower or compiler.ast.Name("None"), n.upper or compiler.ast.Name("None")] + 633 (expr and [expr] or []) 634 ) 635 return self.process_structure_node(invocation) 636 637 def process_sliceobj_node(self, n): 638 639 """ 640 Process the given slice object node 'n' as a slice constructor. 641 """ 642 643 op = "slice" 644 invocation = compiler.ast.CallFunc( 645 compiler.ast.Name("$op%s" % op), 646 n.nodes 647 ) 648 return self.process_structure_node(invocation) 649 650 def process_subscript_node(self, n, expr=None): 651 652 """ 653 Process the given subscript node 'n' as an operator function invocation. 654 """ 655 656 op = n.flags == "OP_ASSIGN" and "setitem" or "getitem" 657 invocation = compiler.ast.CallFunc( 658 compiler.ast.Name("$op%s" % op), 659 [n.expr] + list(n.subs) + (expr and [expr] or []) 660 ) 661 return self.process_structure_node(invocation) 662 663 def process_attribute_chain(self, n): 664 665 """ 666 Process the given attribute access node 'n'. Return a reference 667 describing the expression. 668 """ 669 670 # AssAttr/Getattr are nested with the outermost access being the last 671 # access in any chain. 672 673 self.attrs.insert(0, n.attrname) 674 attrs = self.attrs 675 676 # Break attribute chains where non-access nodes are found. 677 678 if not self.have_access_expression(n): 679 self.reset_attribute_chain() 680 681 # Descend into the expression, extending backwards any existing chain, 682 # or building another for the expression. 683 684 name_ref = self.process_structure_node(n.expr) 685 686 # Restore chain information applying to this node. 687 688 if not self.have_access_expression(n): 689 self.restore_attribute_chain(attrs) 690 691 # Return immediately if the expression was another access and thus a 692 # continuation backwards along the chain. The above processing will 693 # have followed the chain all the way to its conclusion. 694 695 if self.have_access_expression(n): 696 del self.attrs[0] 697 698 return name_ref 699 700 # Attribute chain handling. 701 702 def reset_attribute_chain(self): 703 704 "Reset the attribute chain for a subexpression of an attribute access." 705 706 self.attrs = [] 707 self.chain_assignment.append(self.in_assignment) 708 self.chain_invocation.append(self.in_invocation) 709 self.in_assignment = None 710 self.in_invocation = False 711 712 def restore_attribute_chain(self, attrs): 713 714 "Restore the attribute chain for an attribute access." 715 716 self.attrs = attrs 717 self.in_assignment = self.chain_assignment.pop() 718 self.in_invocation = self.chain_invocation.pop() 719 720 def have_access_expression(self, node): 721 722 "Return whether the expression associated with 'node' is Getattr." 723 724 return isinstance(node.expr, compiler.ast.Getattr) 725 726 def get_name_for_tracking(self, name, path=None): 727 728 """ 729 Return the name to be used for attribute usage observations involving 730 the given 'name' in the current namespace. If 'path' is indicated and 731 the name is being used outside a function, return the path value; 732 otherwise, return a path computed using the current namespace and the 733 given name. 734 735 The intention of this method is to provide a suitably-qualified name 736 that can be tracked across namespaces. Where globals are being 737 referenced in class namespaces, they should be referenced using their 738 path within the module, not using a path within each class. 739 740 It may not be possible to identify a global within a function at the 741 time of inspection (since a global may appear later in a file). 742 Consequently, globals are identified by their local name rather than 743 their module-qualified path. 744 """ 745 746 # For functions, use the appropriate local names. 747 748 if self.in_function: 749 return name 750 751 # For static namespaces, use the given qualified name. 752 753 elif path: 754 return path 755 756 # Otherwise, establish a name in the current namespace. 757 758 else: 759 return self.get_object_path(name) 760 761 def get_path_for_access(self): 762 763 "Outside functions, register accesses at the module level." 764 765 if not self.in_function: 766 return self.name 767 else: 768 return self.get_namespace_path() 769 770 def get_module_name(self, node): 771 772 """ 773 Using the given From 'node' in this module, calculate any relative import 774 information, returning a tuple containing a module to import along with any 775 names to import based on the node's name information. 776 777 Where the returned module is given as None, whole module imports should 778 be performed for the returned modules using the returned names. 779 """ 780 781 # Absolute import. 782 783 if node.level == 0: 784 return node.modname, node.names 785 786 # Relative to an ancestor of this module. 787 788 else: 789 path = self.name.split(".") 790 level = node.level 791 792 # Relative imports treat package roots as submodules. 793 794 if split(self.filename)[-1] == "__init__.py": 795 level -= 1 796 797 if level > len(path): 798 raise InspectError("Relative import %r involves too many levels up from module %r" % ( 799 ("%s%s" % ("." * node.level, node.modname or "")), self.name)) 800 801 basename = ".".join(path[:len(path)-level]) 802 803 # Name imports from a module. 804 805 if node.modname: 806 return "%s.%s" % (basename, node.modname), node.names 807 808 # Relative whole module imports. 809 810 else: 811 return basename, node.names 812 813 def get_argnames(args): 814 815 """ 816 Return a list of all names provided by 'args'. Since tuples may be 817 employed, the arguments are traversed depth-first. 818 """ 819 820 l = [] 821 for arg in args: 822 if isinstance(arg, tuple): 823 l += get_argnames(arg) 824 else: 825 l.append(arg) 826 return l 827 828 def get_names_from_nodes(nodes): 829 830 """ 831 Return the names employed in the given 'nodes' along with the number of 832 nodes excluding sequences. 833 """ 834 835 names = set() 836 count = 0 837 838 for node in nodes: 839 840 # Add names and count them. 841 842 if isinstance(node, (compiler.ast.AssName, compiler.ast.Name)): 843 names.add(node.name) 844 count += 1 845 846 # Add names from sequences and incorporate their counts. 847 848 elif isinstance(node, (compiler.ast.AssList, compiler.ast.AssTuple, 849 compiler.ast.List, compiler.ast.Set, 850 compiler.ast.Tuple)): 851 _names, _count = get_names_from_nodes(node.nodes) 852 names.update(_names) 853 count += _count 854 855 # Count non-name, non-sequence nodes. 856 857 else: 858 count += 1 859 860 return names, count 861 862 # Result classes. 863 864 class InstructionSequence: 865 866 "A generic sequence of instructions." 867 868 def __init__(self, instructions): 869 self.instructions = instructions 870 871 def get_value_instruction(self): 872 return self.instructions[-1] 873 874 def get_init_instructions(self): 875 return self.instructions[:-1] 876 877 # Dictionary utilities. 878 879 def init_item(d, key, fn): 880 881 """ 882 Add to 'd' an entry for 'key' using the callable 'fn' to make an initial 883 value where no entry already exists. 884 """ 885 886 if not d.has_key(key): 887 d[key] = fn() 888 return d[key] 889 890 def dict_for_keys(d, keys): 891 892 "Return a new dictionary containing entries from 'd' for the given 'keys'." 893 894 nd = {} 895 for key in keys: 896 if d.has_key(key): 897 nd[key] = d[key] 898 return nd 899 900 def make_key(s): 901 902 "Make sequence 's' into a tuple-based key, first sorting its contents." 903 904 l = list(s) 905 l.sort() 906 return tuple(l) 907 908 def add_counter_item(d, key): 909 910 """ 911 Make a mapping in 'd' for 'key' to the number of keys added before it, thus 912 maintaining a mapping of keys to their order of insertion. 913 """ 914 915 if not d.has_key(key): 916 d[key] = len(d.keys()) 917 return d[key] 918 919 def remove_items(d1, d2): 920 921 "Remove from 'd1' all items from 'd2'." 922 923 for key in d2.keys(): 924 if d1.has_key(key): 925 del d1[key] 926 927 # Set utilities. 928 929 def first(s): 930 return list(s)[0] 931 932 def same(s1, s2): 933 return set(s1) == set(s2) 934 935 # General input/output. 936 937 def readfile(filename): 938 939 "Return the contents of 'filename'." 940 941 f = open(filename) 942 try: 943 return f.read() 944 finally: 945 f.close() 946 947 def writefile(filename, s): 948 949 "Write to 'filename' the string 's'." 950 951 f = open(filename, "w") 952 try: 953 f.write(s) 954 finally: 955 f.close() 956 957 # General encoding. 958 959 def sorted_output(x): 960 961 "Sort sequence 'x' and return a string with commas separating the values." 962 963 x = map(str, x) 964 x.sort() 965 return ", ".join(x) 966 967 # Attribute chain decoding. 968 969 def get_attrnames(attrnames): 970 971 """ 972 Split the qualified attribute chain 'attrnames' into its components, 973 handling special attributes starting with "#" that indicate type 974 conformance. 975 """ 976 977 if attrnames.startswith("#"): 978 return [attrnames] 979 else: 980 return attrnames.split(".") 981 982 def get_attrname_from_location(location): 983 984 """ 985 Extract the first attribute from the attribute names employed in a 986 'location'. 987 """ 988 989 path, name, attrnames, access = location 990 if not attrnames: 991 return attrnames 992 return get_attrnames(attrnames)[0] 993 994 def get_name_path(path, name): 995 996 "Return a suitable qualified name from the given 'path' and 'name'." 997 998 if "." in name: 999 return name 1000 else: 1001 return "%s.%s" % (path, name) 1002 1003 # Usage-related functions. 1004 1005 def get_types_for_usage(attrnames, objects): 1006 1007 """ 1008 Identify the types that can support the given 'attrnames', using the 1009 given 'objects' as the catalogue of type details. 1010 """ 1011 1012 types = [] 1013 for name, _attrnames in objects.items(): 1014 if set(attrnames).issubset(_attrnames): 1015 types.append(name) 1016 return types 1017 1018 def get_invoked_attributes(usage): 1019 1020 "Obtain invoked attribute from the given 'usage'." 1021 1022 invoked = [] 1023 if usage: 1024 for attrname, invocation, assignment in usage: 1025 if invocation: 1026 invoked.append(attrname) 1027 return invoked 1028 1029 def get_assigned_attributes(usage): 1030 1031 "Obtain assigned attribute from the given 'usage'." 1032 1033 assigned = [] 1034 if usage: 1035 for attrname, invocation, assignment in usage: 1036 if assignment: 1037 assigned.append(attrname) 1038 return assigned 1039 1040 # Type and module functions. 1041 1042 def get_builtin_module(name): 1043 1044 "Return the module name containing the given type 'name'." 1045 1046 # NOTE: This makes assumptions about the __builtins__ structure. 1047 1048 if name == "string": 1049 return "str" 1050 elif name == "utf8string": 1051 return "unicode" 1052 elif name == "NoneType": 1053 return "none" 1054 else: 1055 return name 1056 1057 def get_builtin_type(name): 1058 1059 "Return the type name provided by the given Python value 'name'." 1060 1061 if name == "str": 1062 return "string" 1063 elif name == "unicode": 1064 return "utf8string" 1065 else: 1066 return name 1067 1068 # Useful data. 1069 1070 predefined_constants = "False", "None", "NotImplemented", "True" 1071 1072 operator_functions = { 1073 1074 # Fundamental operations. 1075 1076 "is" : "is_", 1077 "is not" : "is_not", 1078 1079 # Binary operations. 1080 1081 "in" : "in_", 1082 "not in" : "not_in", 1083 "Add" : "add", 1084 "Bitand" : "and_", 1085 "Bitor" : "or_", 1086 "Bitxor" : "xor", 1087 "Div" : "div", 1088 "FloorDiv" : "floordiv", 1089 "LeftShift" : "lshift", 1090 "Mod" : "mod", 1091 "Mul" : "mul", 1092 "Power" : "pow", 1093 "RightShift" : "rshift", 1094 "Sub" : "sub", 1095 1096 # Unary operations. 1097 1098 "Invert" : "invert", 1099 "UnaryAdd" : "pos", 1100 "UnarySub" : "neg", 1101 1102 # Augmented assignment. 1103 1104 "+=" : "iadd", 1105 "-=" : "isub", 1106 "*=" : "imul", 1107 "/=" : "idiv", 1108 "//=" : "ifloordiv", 1109 "%=" : "imod", 1110 "**=" : "ipow", 1111 "<<=" : "ilshift", 1112 ">>=" : "irshift", 1113 "&=" : "iand", 1114 "^=" : "ixor", 1115 "|=" : "ior", 1116 1117 # Comparisons. 1118 1119 "==" : "eq", 1120 "!=" : "ne", 1121 "<" : "lt", 1122 "<=" : "le", 1123 ">=" : "ge", 1124 ">" : "gt", 1125 } 1126 1127 # vim: tabstop=4 expandtab shiftwidth=4