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