1 #!/usr/bin/env python 2 3 """ 4 Translate the AST of a Python program into a more interpretable representation. 5 6 Copyright (C) 2007, 2008 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 import micropython.inspect 23 from micropython.rsvp import * 24 from micropython.common import * 25 import compiler.ast 26 from compiler.visitor import ASTVisitor 27 try: 28 set 29 except NameError: 30 from sets import Set as set 31 32 class TranslateError(ProcessingError): pass 33 34 class Label: 35 36 "A reference to a location." 37 38 def __init__(self, number, location=None): 39 self.number = number 40 self.location = location 41 42 def __repr__(self): 43 return "Label(%r, location=%r)" % (self.number, self.location) 44 45 # Program visitors. 46 47 class Translation(ASTVisitor): 48 49 "A translated module." 50 51 supported_optimisations = ["constant_storage", "known_target"] 52 53 def __init__(self, module, objtable, paramtable, builtins=None, optimisations=None): 54 55 """ 56 Initialise the translation with an inspected 'module' and an attribute 57 table 'objtable' and parameter table 'paramtable'. 58 """ 59 60 ASTVisitor.__init__(self) 61 self.visitor = self 62 self.module = module 63 self.objtable = objtable 64 self.paramtable = paramtable 65 self.builtins = builtins 66 67 # Desired optimisations. 68 69 self.optimisations = set(optimisations or []) 70 71 # The current unit being translated. 72 73 self.unit = None 74 75 # Wiring within the code. 76 77 self.labels = {} 78 self.label_number = 0 79 self.loop_labels = [] 80 self.exception_labels = [] 81 82 # The code itself. 83 84 self.code = None 85 86 def get_module_code(self): 87 88 "Return the top-level module code." 89 90 self.unit = self.module 91 self.code = [] 92 if self.module.module is not None: 93 self.dispatch(self.module.module) 94 return self.code 95 96 def get_code(self, unit): 97 98 "Return the code for the given 'unit'." 99 100 self.unit = unit 101 self.code = [] 102 if unit.node is not None: 103 self.dispatch(unit.node) 104 return self.code 105 106 def __repr__(self): 107 return "Translation(%r)" % self.module 108 109 def get_scope(self, name): 110 if self.unit.has_key(name): 111 return "local" 112 elif self.module.has_key(name): 113 return "global" 114 else: 115 return "builtins" 116 117 # Code writing methods. 118 119 def new_label(self): 120 121 "Return a new label object for use with set_label." 122 123 number = self.label_number 124 label = Label(number) 125 self.labels[label] = label 126 self.label_number += 1 127 return label 128 129 def set_label(self, label): 130 131 """ 132 Set the location of 'label' to that within the entire image: the 133 location within the code combined with location of the code unit. 134 """ 135 136 label.location = len(self.code) + self.unit.code_location 137 138 def get_loop_labels(self): 139 return self.loop_labels[-1] 140 141 def add_loop_labels(self, next_label, exit_label): 142 self.loop_labels.append((next_label, exit_label)) 143 144 def drop_loop_labels(self): 145 self.loop_labels.pop() 146 147 def get_exception_labels(self): 148 return self.exception_labels[-1] 149 150 def add_exception_labels(self, handler_label, exit_label): 151 self.exception_labels.append((handler_label, exit_label)) 152 153 def drop_exception_labels(self): 154 self.exception_labels.pop() 155 156 def new_op(self, op): 157 158 "Add 'op' to the generated code." 159 160 self.code.append(op) 161 162 def replace_op(self, op): 163 164 "Replace the last added instruction with 'op'." 165 166 self.code[-1] = op 167 168 def remove_ops(self, n): 169 170 "Remove the last 'n' instructions." 171 172 del self.code[-n:] 173 174 def last_ops(self, n): 175 176 "Return the last 'n' added instructions in reverse chronological order." 177 178 ops = self.code[-n:] 179 ops.reverse() 180 return ops 181 182 def last_op(self): 183 184 "Return the last added instruction." 185 186 return self.code[-1] 187 188 # Internal helper methods. 189 190 def _visitAttr(self, node, classes): 191 192 """ 193 Visit the attribute-related 'node', generating instructions based on the 194 given 'classes'. 195 """ 196 197 self.dispatch(node.expr) 198 self._generateAttr(node.attrname, classes) 199 200 def _generateAttr(self, attrname, classes): 201 202 """ 203 Generate code for the access to 'attrname' using the given 'classes'. 204 """ 205 206 AttrInstruction, AttrIndexInstruction = classes 207 # NOTE: Only simple cases are used for optimisations. 208 209 last = self.last_op() 210 211 # Where the last operation (defining the attribute owner) yields a 212 # constant... 213 214 if self._have_constant_input(0): 215 216 # Optimise away the constant storage if appropriate. 217 218 if self._optimise_constant_storage(AttrInstruction, 1): 219 return 220 221 # Get the details of the access. 222 223 target = last.attr.value 224 target_name = target.full_name() 225 table_entry = self.objtable.table[target_name] 226 pos = table_entry[attrname] 227 self.replace_op(AttrInstruction(pos)) 228 229 # Where the last operation involves the special 'self' name, check to 230 # see if the attribute is acceptably positioned. 231 232 elif isinstance(last, LoadName) and last.attr.name == "self" and not self.unit.is_relocated(attrname): 233 attr = self.unit.parent.all_attributes()[attrname] 234 self.new_op(AttrInstruction(attr)) 235 236 # Otherwise, perform a normal operation. 237 238 else: 239 index = self.objtable.get_index(attrname) 240 self.new_op(AttrIndexInstruction(index)) 241 242 def _startCallFunc(self): 243 244 "Record the location of the invocation." 245 246 self.new_op(MakeFrame()) # records the start of the frame 247 248 def _generateCallFunc(self, args, node): 249 250 # NOTE: Only simple cases are used for optimisations. 251 252 target, context = self._optimise_known_target() 253 254 # Where a target is known and has a known context, avoid generating any 255 # first argument. Instance methods do not have a known target since they 256 # are accessed via an instance whose identity cannot generally be known 257 # at compile-time. 258 259 if context is None: 260 continue_label = self.new_label() 261 self.new_op(LoadContext()) 262 self.new_op(CheckContext()) 263 self.new_op(JumpIfTrue(continue_label)) 264 self.dispatch(compiler.ast.Name("TypeError")) 265 self.new_op(RaiseException()) 266 self.set_label(continue_label) 267 else: 268 pass # NOTE: Class methods should be supported. 269 270 # Evaluate the arguments. 271 272 positional = 1 273 start_keywords = None 274 employed_keywords = set() 275 extra_keywords = [] 276 277 for i, arg in enumerate(args): 278 if isinstance(arg, compiler.ast.Keyword): 279 if positional: 280 #self.new_op(ReserveFrame(len(args) - i)) 281 start_keywords = i 282 positional = 0 283 284 # Optimise where the target is known now. 285 286 if target is not None: 287 288 # Find the parameter table entry for the target. 289 290 target_name = target.full_name() 291 292 # Look for a callable with the precise target name. 293 294 table_entry = self.paramtable.table[target_name] 295 296 # Look the name up in the parameter table entry. 297 298 try: 299 pos = table_entry[arg.name] 300 301 # Where no position is found, this could be an extra keyword 302 # argument. 303 304 except KeyError: 305 extra_keywords.append(arg) 306 continue 307 308 # Test for illegal conditions. 309 310 if pos < start_keywords: 311 raise TranslateError(self.module.full_name(), node, 312 "Keyword argument %r overwrites parameter %r." % (arg.name, pos)) 313 elif pos in employed_keywords: 314 raise TranslateError(self.module.full_name(), node, 315 "Keyword argument %r is repeated, overwriting parameter %r." % (arg.name, pos)) 316 317 employed_keywords.add(pos) 318 319 # Generate code for the keyword and the positioning 320 # operation. 321 322 self.dispatch(arg.expr) 323 self.new_op(StoreFrame(pos)) 324 325 # Otherwise, generate the code needed to obtain the details of 326 # the parameter location. 327 328 else: 329 330 # Combine the target details with the name to get the location. 331 # See the access method on the List class. 332 333 try: 334 paramindex = self.paramtable.get_index(arg.name) 335 336 # Where no position is found, this could be an extra keyword 337 # argument. 338 339 except ValueError: 340 extra_keywords.append(arg) 341 continue 342 343 # Generate code for the keyword and the positioning 344 # operation. 345 346 self.dispatch(arg.expr) 347 self.new_op(StoreFrameIndex(paramindex)) 348 349 # use (callable+0)+paramindex+table 350 # checks embedded offset against (callable+0) 351 # moves the top of stack to frame+position 352 353 else: 354 self.dispatch(arg) 355 356 # If any extra keywords were identified, generate them now. 357 358 for arg in extra_keywords: 359 const = self.module.constant_values[arg.name] 360 self.new_op(LoadConst(const)) 361 self.dispatch(arg.expr) 362 363 # NOTE: Somehow, the above needs to be combined with * and ** arguments. 364 365 # Either test for a complete set of arguments. 366 367 if target is not None: 368 nargs_max = len(target.positional_names) 369 nargs_min = nargs_max - len(target.defaults) 370 if len(args) < nargs_min: 371 raise TranslateError(self.module.full_name(), node, 372 "Insufficient arguments for %r: need at least %d arguments." % (target.name, nargs_min)) 373 elif len(args) > nargs_max and not target.has_star and not target.has_dstar: 374 raise TranslateError(self.module.full_name(), node, 375 "Too many arguments for %r: need at most %d arguments." % (target.name, nargs)) 376 377 # Or generate instructions to do this at run-time. 378 379 else: 380 self.new_op(CheckFrame()) 381 382 def _endCallFunc(self): 383 384 "Make the invocation and tidy up afterwards." 385 386 self.new_op(LoadCallable()) # uses the start of the frame to get the callable 387 self.new_op(Jump()) 388 389 # NOTE: Exception handling required. 390 391 self.new_op(DropFrame()) 392 393 def _visitName(self, node, classes): 394 395 """ 396 Visit the name-related 'node', generating instructions based on the 397 given 'classes'. 398 """ 399 400 name = node.name 401 scope = self.get_scope(name) 402 #print self.module.name, node.lineno, name, scope 403 self._generateName(name, scope, classes, node) 404 405 def _generateName(self, name, scope, classes, node): 406 407 """ 408 Generate code for the access to 'name' in 'scope' using the given 409 'classes', and using the given 'node' as the source of the access. 410 """ 411 412 NameInstruction, AttrInstruction = classes 413 414 if self._optimise_constant_storage(NameInstruction, 0): 415 return 416 417 if scope == "local": 418 unit = self.unit 419 if isinstance(unit, micropython.inspect.Function): 420 self.new_op(NameInstruction(unit.all_locals()[name])) 421 elif isinstance(unit, micropython.inspect.Class): 422 self.new_op(AttrInstruction(unit.all_class_attributes()[name])) 423 elif isinstance(unit, micropython.inspect.Module): 424 self.new_op(AttrInstruction(unit.module_attributes()[name])) 425 else: 426 raise TranslateError(self.module.full_name(), node, "Program unit %r has no local %r." % (unit, name)) 427 428 elif scope == "global": 429 globals = self.module.module_attributes() 430 if globals.has_key(name): 431 self.new_op(AttrInstruction(globals[name])) 432 else: 433 raise TranslateError(self.module.full_name(), node, "Module %r has no attribute %r." % (self.module, name)) 434 435 else: 436 self.new_op(AttrInstruction(self._get_builtin(name, node))) 437 438 def _get_builtin(self, name, node): 439 if self.builtins is not None: 440 return self.builtins[name] 441 else: 442 raise TranslateError(self.module.full_name(), node, "No __builtins__ module is available for name %r." % name) 443 444 # Optimisation tests. 445 446 def _should_optimise_constant_storage(self): 447 return "constant_storage" in self.optimisations 448 449 def _should_optimise_known_target(self): 450 return "known_target" in self.optimisations 451 452 def _have_constant_input(self, n): 453 last = self.last_ops(n+1) 454 return len(last) > n and (isinstance(last[n], LoadAttr) and last[n].attr.assignments == 1 or 455 isinstance(last[n], LoadConst)) 456 457 def _have_known_target(self): 458 return self._have_constant_input(0) 459 460 # Optimisation methods. See the supported_optimisations class attribute. 461 462 def _optimise_constant_storage(self, cls, n): 463 464 """ 465 Where this operation should store a constant into a target which is 466 also constant, optimise away both operations. 467 """ 468 469 if self._should_optimise_constant_storage() and cls in (StoreAttr, StoreName) and \ 470 self._have_constant_input(n) and self._have_constant_input(n-1): 471 self.remove_ops(n+1) 472 return 1 473 else: 474 return 0 475 476 def _optimise_known_target(self): 477 478 """ 479 Where the target of an invocation is known, provide information about it 480 and its context. If a class is being invoked and the conditions are 481 appropriate, get information about the specific initialiser. 482 """ 483 484 if self._should_optimise_known_target() and self._have_known_target(): 485 last = self.last_op() 486 target = last.attr.value 487 context = last.attr.parent 488 489 # Handle calls to classes. 490 491 if isinstance(target, micropython.inspect.Class): 492 target = target.get_instantiator() 493 context = micropython.inspect.Instance() 494 495 # A special context is chosen to avoid generating unnecessary 496 # context loading and checking instructions. 497 498 else: 499 target = None 500 context = None 501 502 return target, context 503 504 # Visitor methods. 505 506 def default(self, node, *args): 507 raise TranslateError(self.module.full_name(), node, "Node class %r is not supported." % node.__class__) 508 509 def dispatch(self, node, *args): 510 return ASTVisitor.dispatch(self, node, *args) 511 512 def _visitBinary(self, node, left_method, right_method): 513 514 """ 515 _t1 = node.left 516 _t2 = node.right 517 try: 518 _t1.__add__(_t2) 519 except AttributeError: 520 _t2.__radd__(_t1) 521 """ 522 523 right_label = self.new_label() 524 end_label = self.new_label() 525 526 # NOTE: Decide on temporary storage access. 527 528 self.dispatch(node.left) 529 self.new_op(StoreTemp(1)) 530 self.dispatch(node.right) 531 self.new_op(StoreTemp(2)) 532 533 # Left method. 534 535 self._startCallFunc() 536 self.new_op(LoadTemp(1)) 537 self._generateAttr(left_method, (LoadAttr, LoadAttrIndex)) 538 self.new_op(LoadTemp(1)) # Explicit context as first argument. 539 self.new_op(LoadTemp(2)) 540 self._endCallFunc() 541 542 self.dispatch(compiler.ast.Name("AttributeError")) 543 self.new_op(CheckException()) 544 self.new_op(JumpIfFalse(end_label)) 545 546 # Right method. 547 548 self.set_label(right_label) 549 self._startCallFunc() 550 self.new_op(LoadTemp(2)) 551 self._generateAttr(right_method, (LoadAttr, LoadAttrIndex)) 552 self.new_op(LoadTemp(2)) # Explicit context as first argument. 553 self.new_op(LoadTemp(1)) 554 self._endCallFunc() 555 556 self.set_label(end_label) 557 558 def visitAdd(self, node): 559 self._visitBinary(node, "__add__", "__radd__") 560 561 def visitAnd(self, node): pass 562 563 def visitAssert(self, node): pass 564 565 def visitAssign(self, node): 566 self.dispatch(node.expr) 567 for n in node.nodes: 568 self.dispatch(n) 569 570 def visitAssAttr(self, node): 571 self._visitAttr(node, (StoreAttr, StoreAttrIndex)) 572 573 def visitAssList(self, node): pass 574 575 def visitAssName(self, node): 576 self._visitName(node, (StoreName, StoreAttr)) 577 578 visitAssTuple = visitAssList 579 580 def visitAugAssign(self, node): pass 581 582 def visitBackquote(self, node): pass 583 584 def visitBitand(self, node): pass 585 586 def visitBitor(self, node): pass 587 588 def visitBitxor(self, node): pass 589 590 def visitBreak(self, node): 591 next_label, exit_label = self.get_loop_labels() 592 self.new_op(Jump(exit_label)) 593 594 def visitCallFunc(self, node): 595 596 """ 597 Evaluate positional arguments, evaluate and store keyword arguments in 598 the correct location, then invoke the function. 599 """ 600 601 # Mark the frame, evaluate the target, generate the call. 602 603 self._startCallFunc() 604 self.dispatch(node.node) 605 self._generateCallFunc(node.args, node) 606 self._endCallFunc() 607 608 def visitClass(self, node): 609 unit = self.unit 610 self.unit = node.unit 611 self.unit.code_location = self.module.code_location # class body code is not independently addressable 612 self.dispatch(node.code) 613 self.unit = unit 614 615 def visitCompare(self, node): 616 617 """ 618 self.dispatch(node.expr) 619 for op_name, next_node in compare.ops: 620 methods = self.comparison_methods[op_name] 621 if methods is not None: 622 # Generate method call using evaluated argument and next node. 623 else: 624 # Deal with the special operators. 625 # Provide short-circuiting. 626 """ 627 628 def visitConst(self, node): 629 const = self.module.constant_values[node.value] 630 self.new_op(LoadConst(const)) 631 632 def visitContinue(self, node): 633 next_label, exit_label = self.get_loop_labels() 634 self.new_op(Jump(next_label)) 635 636 def visitDecorators(self, node): pass 637 638 def visitDict(self, node): pass 639 640 def visitDiscard(self, node): 641 self.dispatch(node.expr) 642 643 def visitDiv(self, node): 644 self._visitBinary(node, "__div__", "__rdiv__") 645 646 def visitEllipsis(self, node): pass 647 648 def visitExec(self, node): pass 649 650 def visitExpression(self, node): pass 651 652 def visitFloorDiv(self, node): 653 self._visitBinary(node, "__floordiv__", "__rfloordiv__") 654 655 def visitFor(self, node): 656 exit_label = self.new_label() 657 next_label = self.new_label() 658 else_label = self.new_label() 659 660 # Get the "list" to be iterated over, obtain its iterator. 661 662 self._startCallFunc() 663 self.dispatch(node.list) 664 self._generateAttr("__iter__", (LoadAttr, LoadAttrIndex)) 665 self._generateCallFunc([], node) 666 self._endCallFunc() 667 668 # Iterator on stack. 669 670 # In the loop... 671 672 self.set_label(next_label) 673 674 # Use the iterator to get the next value. 675 676 self._startCallFunc() 677 self.new_op(Duplicate()) 678 self._generateAttr("next", (LoadAttr, LoadAttrIndex)) 679 self._generateCallFunc([], node) 680 self._endCallFunc() 681 682 # Test for StopIteration. 683 684 self.dispatch(compiler.ast.Name("StopIteration")) 685 self.new_op(CheckException()) 686 if node.else_ is not None: 687 self.new_op(JumpIfTrue(else_label)) 688 else: 689 self.new_op(JumpIfTrue(exit_label)) 690 691 # Assign to the target. 692 693 self.dispatch(node.assign) 694 695 # Process the body with the current next and exit points. 696 697 self.add_loop_labels(next_label, exit_label) 698 self.dispatch(node.body) 699 self.drop_loop_labels() 700 701 # Repeat the loop. 702 703 self.new_op(Jump(next_label)) 704 705 # Produce the "else" section. 706 707 if node.else_ is not None: 708 self.set_label(exit_label) 709 self.dispatch(node.else_) 710 711 # Pop the iterator. 712 713 self.set_label(exit_label) 714 self.new_op(Pop()) 715 716 def visitFrom(self, node): pass 717 718 def visitFunction(self, node): 719 720 # Only store the name when visiting this node from outside. 721 722 if self.unit is not node.unit: 723 self.new_op(LoadConst(node.unit)) 724 self._visitName(node, (StoreName, StoreAttr)) 725 726 # Visiting of the code occurs when get_code is invoked on this node. 727 728 else: 729 self.dispatch(node.code) 730 self.new_op(Return()) 731 732 def visitGenExpr(self, node): pass 733 734 def visitGenExprFor(self, node): pass 735 736 def visitGenExprIf(self, node): pass 737 738 def visitGenExprInner(self, node): pass 739 740 def visitGetattr(self, node): 741 self._visitAttr(node, (LoadAttr, LoadAttrIndex)) 742 743 def visitGlobal(self, node): pass 744 745 def visitIf(self, node): 746 first = 1 747 exit_label = self.new_label() 748 749 for test, body in node.tests + [(None, node.else_)]: 750 if body is None: 751 break 752 if not first: 753 self.set_label(next_label) 754 if test is not None: 755 self.dispatch(test) 756 next_label = self.new_label() 757 self.new_op(JumpIfFalse(next_label)) 758 self.dispatch(body) 759 self.new_op(Jump(exit_label)) 760 first = 0 761 762 self.set_label(exit_label) 763 764 def visitImport(self, node): pass 765 766 def visitInvert(self, node): pass 767 768 def visitKeyword(self, node): pass 769 770 def visitLambda(self, node): pass 771 772 def visitLeftShift(self, node): pass 773 774 def visitList(self, node): pass 775 776 def visitListComp(self, node): pass 777 778 def visitListCompFor(self, node): pass 779 780 def visitListCompIf(self, node): pass 781 782 def visitMod(self, node): 783 self._visitBinary(node, "__mod__", "__rmod__") 784 785 def visitModule(self, node): 786 self.dispatch(node.node) 787 788 def visitMul(self, node): 789 self._visitBinary(node, "__mul__", "__rmul__") 790 791 def visitName(self, node): 792 self._visitName(node, (LoadName, LoadAttr)) 793 794 def visitNot(self, node): pass 795 796 def visitOr(self, node): pass 797 798 def visitPass(self, node): pass 799 800 def visitPower(self, node): pass 801 802 def visitPrint(self, node): pass 803 804 def visitPrintnl(self, node): pass 805 806 def visitRaise(self, node): pass 807 808 def visitReturn(self, node): 809 if node.value is not None: 810 self.dispatch(node.value) 811 self.new_op(Return()) 812 813 def visitRightShift(self, node): pass 814 815 def visitSlice(self, node): pass 816 817 def visitStmt(self, node): 818 for n in node.nodes: 819 self.dispatch(n) 820 821 def visitSub(self, node): 822 self._visitBinary(node, "__sub__", "__rsub__") 823 824 def visitSubscript(self, node): pass 825 826 def visitTryExcept(self, node): 827 828 """ 829 Enter try block. 830 Dispatch to code. 831 832 """ 833 834 exit_label = self.new_label() 835 handler_label = self.new_label() 836 837 self.add_exception_labels(handler_label, exit_label) 838 839 self.dispatch(node.body) 840 self.new_op(Jump(exit_label)) 841 842 self.set_label(handler_label) 843 for name, assignment, handler in node.handlers: 844 next_label = self.new_label() 845 846 if name is not None: 847 self.dispatch(name) 848 self.new_op(CheckException()) 849 self.new_op(JumpIfFalse(next_label)) 850 851 if assignment is not None: 852 self.dispatch(assignment) 853 854 self.dispatch(handler) 855 self.new_op(Jump(exit_label)) 856 857 self.set_label(next_label) 858 859 # Unhandled exceptions. 860 861 self.new_op(RaiseException()) 862 863 # After exception 864 865 self.set_label(exit_label) 866 867 # Optional else clause. 868 869 if node.else_ is not None: 870 self.dispatch(node.else_) 871 872 self.drop_exception_labels() 873 874 def visitTryFinally(self, node): pass 875 876 def visitTuple(self, node): pass 877 878 def visitUnaryAdd(self, node): pass 879 880 def visitUnarySub(self, node): pass 881 882 def visitWhile(self, node): 883 exit_label = self.new_label() 884 next_label = self.new_label() 885 else_label = self.new_label() 886 887 self.set_label(next_label) 888 self.dispatch(node.test) 889 if node.else_ is not None: 890 self.new_op(JumpIfFalse(else_label)) 891 else: 892 self.new_op(JumpIfFalse(exit_label)) 893 894 self.add_loop_labels(next_label, exit_label) 895 896 self.dispatch(node.body) 897 self.new_op(Jump(next_label)) 898 899 if node.else_ is not None: 900 self.set_label(else_label) 901 self.dispatch(node.else_) 902 903 self.set_label(exit_label) 904 self.drop_loop_labels() 905 906 def visitWith(self, node): pass 907 908 def visitYield(self, node): pass 909 910 # Useful data. 911 912 comparison_methods = { 913 "==" : ("__eq__", "__ne__"), 914 "!=" : ("__ne__", "__eq__"), 915 "<" : ("__lt__", "__gt__"), 916 "<=" : ("__le__", "__ge__"), 917 ">=" : ("__ge__", "__le__"), 918 ">" : ("__gt__", "__lt__"), 919 "is" : None, 920 "is not" : None, 921 "in" : None, 922 "not in" : None 923 } 924 925 # vim: tabstop=4 expandtab shiftwidth=4