1# cython: infer_types=True 2# cython: language_level=3 3# cython: auto_pickle=False 4 5# 6# Tree visitor and transform framework 7# 8 9from __future__ import absolute_import, print_function 10 11import sys 12import inspect 13 14from . import TypeSlots 15from . import Builtin 16from . import Nodes 17from . import ExprNodes 18from . import Errors 19from . import DebugFlags 20from . import Future 21 22import cython 23 24 25cython.declare(_PRINTABLE=tuple) 26 27if sys.version_info[0] >= 3: 28 _PRINTABLE = (bytes, str, int, float) 29else: 30 _PRINTABLE = (str, unicode, long, int, float) 31 32 33class TreeVisitor(object): 34 """ 35 Base class for writing visitors for a Cython tree, contains utilities for 36 recursing such trees using visitors. Each node is 37 expected to have a child_attrs iterable containing the names of attributes 38 containing child nodes or lists of child nodes. Lists are not considered 39 part of the tree structure (i.e. contained nodes are considered direct 40 children of the parent node). 41 42 visit_children visits each of the children of a given node (see the visit_children 43 documentation). When recursing the tree using visit_children, an attribute 44 access_path is maintained which gives information about the current location 45 in the tree as a stack of tuples: (parent_node, attrname, index), representing 46 the node, attribute and optional list index that was taken in each step in the path to 47 the current node. 48 49 Example: 50 51 >>> class SampleNode(object): 52 ... child_attrs = ["head", "body"] 53 ... def __init__(self, value, head=None, body=None): 54 ... self.value = value 55 ... self.head = head 56 ... self.body = body 57 ... def __repr__(self): return "SampleNode(%s)" % self.value 58 ... 59 >>> tree = SampleNode(0, SampleNode(1), [SampleNode(2), SampleNode(3)]) 60 >>> class MyVisitor(TreeVisitor): 61 ... def visit_SampleNode(self, node): 62 ... print("in %s %s" % (node.value, self.access_path)) 63 ... self.visitchildren(node) 64 ... print("out %s" % node.value) 65 ... 66 >>> MyVisitor().visit(tree) 67 in 0 [] 68 in 1 [(SampleNode(0), 'head', None)] 69 out 1 70 in 2 [(SampleNode(0), 'body', 0)] 71 out 2 72 in 3 [(SampleNode(0), 'body', 1)] 73 out 3 74 out 0 75 """ 76 def __init__(self): 77 super(TreeVisitor, self).__init__() 78 self.dispatch_table = {} 79 self.access_path = [] 80 81 def dump_node(self, node): 82 ignored = list(node.child_attrs or []) + [ 83 u'child_attrs', u'pos', u'gil_message', u'cpp_message', u'subexprs'] 84 values = [] 85 pos = getattr(node, 'pos', None) 86 if pos: 87 source = pos[0] 88 if source: 89 import os.path 90 source = os.path.basename(source.get_description()) 91 values.append(u'%s:%s:%s' % (source, pos[1], pos[2])) 92 attribute_names = dir(node) 93 for attr in attribute_names: 94 if attr in ignored: 95 continue 96 if attr.startswith('_') or attr.endswith('_'): 97 continue 98 try: 99 value = getattr(node, attr) 100 except AttributeError: 101 continue 102 if value is None or value == 0: 103 continue 104 elif isinstance(value, list): 105 value = u'[...]/%d' % len(value) 106 elif not isinstance(value, _PRINTABLE): 107 continue 108 else: 109 value = repr(value) 110 values.append(u'%s = %s' % (attr, value)) 111 return u'%s(%s)' % (node.__class__.__name__, u',\n '.join(values)) 112 113 def _find_node_path(self, stacktrace): 114 import os.path 115 last_traceback = stacktrace 116 nodes = [] 117 while hasattr(stacktrace, 'tb_frame'): 118 frame = stacktrace.tb_frame 119 node = frame.f_locals.get(u'self') 120 if isinstance(node, Nodes.Node): 121 code = frame.f_code 122 method_name = code.co_name 123 pos = (os.path.basename(code.co_filename), 124 frame.f_lineno) 125 nodes.append((node, method_name, pos)) 126 last_traceback = stacktrace 127 stacktrace = stacktrace.tb_next 128 return (last_traceback, nodes) 129 130 def _raise_compiler_error(self, child, e): 131 trace = [''] 132 for parent, attribute, index in self.access_path: 133 node = getattr(parent, attribute) 134 if index is None: 135 index = '' 136 else: 137 node = node[index] 138 index = u'[%d]' % index 139 trace.append(u'%s.%s%s = %s' % ( 140 parent.__class__.__name__, attribute, index, 141 self.dump_node(node))) 142 stacktrace, called_nodes = self._find_node_path(sys.exc_info()[2]) 143 last_node = child 144 for node, method_name, pos in called_nodes: 145 last_node = node 146 trace.append(u"File '%s', line %d, in %s: %s" % ( 147 pos[0], pos[1], method_name, self.dump_node(node))) 148 raise Errors.CompilerCrash( 149 getattr(last_node, 'pos', None), self.__class__.__name__, 150 u'\n'.join(trace), e, stacktrace) 151 152 @cython.final 153 def find_handler(self, obj): 154 # to resolve, try entire hierarchy 155 cls = type(obj) 156 pattern = "visit_%s" 157 mro = inspect.getmro(cls) 158 for mro_cls in mro: 159 handler_method = getattr(self, pattern % mro_cls.__name__, None) 160 if handler_method is not None: 161 return handler_method 162 print(type(self), cls) 163 if self.access_path: 164 print(self.access_path) 165 print(self.access_path[-1][0].pos) 166 print(self.access_path[-1][0].__dict__) 167 raise RuntimeError("Visitor %r does not accept object: %s" % (self, obj)) 168 169 def visit(self, obj): 170 return self._visit(obj) 171 172 @cython.final 173 def _visit(self, obj): 174 try: 175 try: 176 handler_method = self.dispatch_table[type(obj)] 177 except KeyError: 178 handler_method = self.find_handler(obj) 179 self.dispatch_table[type(obj)] = handler_method 180 return handler_method(obj) 181 except Errors.CompileError: 182 raise 183 except Errors.AbortError: 184 raise 185 except Exception as e: 186 if DebugFlags.debug_no_exception_intercept: 187 raise 188 self._raise_compiler_error(obj, e) 189 190 @cython.final 191 def _visitchild(self, child, parent, attrname, idx): 192 self.access_path.append((parent, attrname, idx)) 193 result = self._visit(child) 194 self.access_path.pop() 195 return result 196 197 def visitchildren(self, parent, attrs=None): 198 return self._visitchildren(parent, attrs) 199 200 @cython.final 201 @cython.locals(idx=int) 202 def _visitchildren(self, parent, attrs): 203 """ 204 Visits the children of the given parent. If parent is None, returns 205 immediately (returning None). 206 207 The return value is a dictionary giving the results for each 208 child (mapping the attribute name to either the return value 209 or a list of return values (in the case of multiple children 210 in an attribute)). 211 """ 212 if parent is None: return None 213 result = {} 214 for attr in parent.child_attrs: 215 if attrs is not None and attr not in attrs: continue 216 child = getattr(parent, attr) 217 if child is not None: 218 if type(child) is list: 219 childretval = [self._visitchild(x, parent, attr, idx) for idx, x in enumerate(child)] 220 else: 221 childretval = self._visitchild(child, parent, attr, None) 222 assert not isinstance(childretval, list), 'Cannot insert list here: %s in %r' % (attr, parent) 223 result[attr] = childretval 224 return result 225 226 227class VisitorTransform(TreeVisitor): 228 """ 229 A tree transform is a base class for visitors that wants to do stream 230 processing of the structure (rather than attributes etc.) of a tree. 231 232 It implements __call__ to simply visit the argument node. 233 234 It requires the visitor methods to return the nodes which should take 235 the place of the visited node in the result tree (which can be the same 236 or one or more replacement). Specifically, if the return value from 237 a visitor method is: 238 239 - [] or None; the visited node will be removed (set to None if an attribute and 240 removed if in a list) 241 - A single node; the visited node will be replaced by the returned node. 242 - A list of nodes; the visited nodes will be replaced by all the nodes in the 243 list. This will only work if the node was already a member of a list; if it 244 was not, an exception will be raised. (Typically you want to ensure that you 245 are within a StatListNode or similar before doing this.) 246 """ 247 def visitchildren(self, parent, attrs=None, exclude=None): 248 # generic def entry point for calls from Python subclasses 249 if exclude is not None: 250 attrs = self._select_attrs(parent.child_attrs if attrs is None else attrs, exclude) 251 return self._process_children(parent, attrs) 252 253 @cython.final 254 def _select_attrs(self, attrs, exclude): 255 return [name for name in attrs if name not in exclude] 256 257 @cython.final 258 def _process_children(self, parent, attrs=None): 259 # fast cdef entry point for calls from Cython subclasses 260 result = self._visitchildren(parent, attrs) 261 for attr, newnode in result.items(): 262 if type(newnode) is list: 263 newnode = self._flatten_list(newnode) 264 setattr(parent, attr, newnode) 265 return result 266 267 @cython.final 268 def _flatten_list(self, orig_list): 269 # Flatten the list one level and remove any None 270 newlist = [] 271 for x in orig_list: 272 if x is not None: 273 if type(x) is list: 274 newlist.extend(x) 275 else: 276 newlist.append(x) 277 return newlist 278 279 def recurse_to_children(self, node): 280 self._process_children(node) 281 return node 282 283 def __call__(self, root): 284 return self._visit(root) 285 286 287class CythonTransform(VisitorTransform): 288 """ 289 Certain common conventions and utilities for Cython transforms. 290 291 - Sets up the context of the pipeline in self.context 292 - Tracks directives in effect in self.current_directives 293 """ 294 def __init__(self, context): 295 super(CythonTransform, self).__init__() 296 self.context = context 297 298 def __call__(self, node): 299 from . import ModuleNode 300 if isinstance(node, ModuleNode.ModuleNode): 301 self.current_directives = node.directives 302 return super(CythonTransform, self).__call__(node) 303 304 def visit_CompilerDirectivesNode(self, node): 305 old = self.current_directives 306 self.current_directives = node.directives 307 self._process_children(node) 308 self.current_directives = old 309 return node 310 311 def visit_Node(self, node): 312 self._process_children(node) 313 return node 314 315 316class ScopeTrackingTransform(CythonTransform): 317 # Keeps track of type of scopes 318 #scope_type: can be either of 'module', 'function', 'cclass', 'pyclass', 'struct' 319 #scope_node: the node that owns the current scope 320 321 def visit_ModuleNode(self, node): 322 self.scope_type = 'module' 323 self.scope_node = node 324 self._process_children(node) 325 return node 326 327 def visit_scope(self, node, scope_type): 328 prev = self.scope_type, self.scope_node 329 self.scope_type = scope_type 330 self.scope_node = node 331 self._process_children(node) 332 self.scope_type, self.scope_node = prev 333 return node 334 335 def visit_CClassDefNode(self, node): 336 return self.visit_scope(node, 'cclass') 337 338 def visit_PyClassDefNode(self, node): 339 return self.visit_scope(node, 'pyclass') 340 341 def visit_FuncDefNode(self, node): 342 return self.visit_scope(node, 'function') 343 344 def visit_CStructOrUnionDefNode(self, node): 345 return self.visit_scope(node, 'struct') 346 347 348class EnvTransform(CythonTransform): 349 """ 350 This transformation keeps a stack of the environments. 351 """ 352 def __call__(self, root): 353 self.env_stack = [] 354 self.enter_scope(root, root.scope) 355 return super(EnvTransform, self).__call__(root) 356 357 def current_env(self): 358 return self.env_stack[-1][1] 359 360 def current_scope_node(self): 361 return self.env_stack[-1][0] 362 363 def global_scope(self): 364 return self.current_env().global_scope() 365 366 def enter_scope(self, node, scope): 367 self.env_stack.append((node, scope)) 368 369 def exit_scope(self): 370 self.env_stack.pop() 371 372 def visit_FuncDefNode(self, node): 373 outer_attrs = node.outer_attrs 374 self.visitchildren(node, attrs=outer_attrs) 375 self.enter_scope(node, node.local_scope) 376 self.visitchildren(node, attrs=None, exclude=outer_attrs) 377 self.exit_scope() 378 return node 379 380 def visit_GeneratorBodyDefNode(self, node): 381 self._process_children(node) 382 return node 383 384 def visit_ClassDefNode(self, node): 385 self.enter_scope(node, node.scope) 386 self._process_children(node) 387 self.exit_scope() 388 return node 389 390 def visit_CStructOrUnionDefNode(self, node): 391 self.enter_scope(node, node.scope) 392 self._process_children(node) 393 self.exit_scope() 394 return node 395 396 def visit_ScopedExprNode(self, node): 397 if node.expr_scope: 398 self.enter_scope(node, node.expr_scope) 399 self._process_children(node) 400 self.exit_scope() 401 else: 402 self._process_children(node) 403 return node 404 405 def visit_CArgDeclNode(self, node): 406 # default arguments are evaluated in the outer scope 407 if node.default: 408 attrs = [attr for attr in node.child_attrs if attr != 'default'] 409 self._process_children(node, attrs) 410 self.enter_scope(node, self.current_env().outer_scope) 411 self.visitchildren(node, ('default',)) 412 self.exit_scope() 413 else: 414 self._process_children(node) 415 return node 416 417 418class NodeRefCleanupMixin(object): 419 """ 420 Clean up references to nodes that were replaced. 421 422 NOTE: this implementation assumes that the replacement is 423 done first, before hitting any further references during 424 normal tree traversal. This needs to be arranged by calling 425 "self.visitchildren()" at a proper place in the transform 426 and by ordering the "child_attrs" of nodes appropriately. 427 """ 428 def __init__(self, *args): 429 super(NodeRefCleanupMixin, self).__init__(*args) 430 self._replacements = {} 431 432 def visit_CloneNode(self, node): 433 arg = node.arg 434 if arg not in self._replacements: 435 self.visitchildren(arg) 436 node.arg = self._replacements.get(arg, arg) 437 return node 438 439 def visit_ResultRefNode(self, node): 440 expr = node.expression 441 if expr is None or expr not in self._replacements: 442 self.visitchildren(node) 443 expr = node.expression 444 if expr is not None: 445 node.expression = self._replacements.get(expr, expr) 446 return node 447 448 def replace(self, node, replacement): 449 self._replacements[node] = replacement 450 return replacement 451 452 453find_special_method_for_binary_operator = { 454 '<': '__lt__', 455 '<=': '__le__', 456 '==': '__eq__', 457 '!=': '__ne__', 458 '>=': '__ge__', 459 '>': '__gt__', 460 '+': '__add__', 461 '&': '__and__', 462 '/': '__div__', 463 '//': '__floordiv__', 464 '<<': '__lshift__', 465 '%': '__mod__', 466 '*': '__mul__', 467 '|': '__or__', 468 '**': '__pow__', 469 '>>': '__rshift__', 470 '-': '__sub__', 471 '^': '__xor__', 472 'in': '__contains__', 473}.get 474 475 476find_special_method_for_unary_operator = { 477 'not': '__not__', 478 '~': '__inv__', 479 '-': '__neg__', 480 '+': '__pos__', 481}.get 482 483 484class MethodDispatcherTransform(EnvTransform): 485 """ 486 Base class for transformations that want to intercept on specific 487 builtin functions or methods of builtin types, including special 488 methods triggered by Python operators. Must run after declaration 489 analysis when entries were assigned. 490 491 Naming pattern for handler methods is as follows: 492 493 * builtin functions: _handle_(general|simple|any)_function_NAME 494 495 * builtin methods: _handle_(general|simple|any)_method_TYPENAME_METHODNAME 496 """ 497 # only visit call nodes and Python operations 498 def visit_GeneralCallNode(self, node): 499 self._process_children(node) 500 function = node.function 501 if not function.type.is_pyobject: 502 return node 503 arg_tuple = node.positional_args 504 if not isinstance(arg_tuple, ExprNodes.TupleNode): 505 return node 506 keyword_args = node.keyword_args 507 if keyword_args and not isinstance(keyword_args, ExprNodes.DictNode): 508 # can't handle **kwargs 509 return node 510 args = arg_tuple.args 511 return self._dispatch_to_handler(node, function, args, keyword_args) 512 513 def visit_SimpleCallNode(self, node): 514 self._process_children(node) 515 function = node.function 516 if function.type.is_pyobject: 517 arg_tuple = node.arg_tuple 518 if not isinstance(arg_tuple, ExprNodes.TupleNode): 519 return node 520 args = arg_tuple.args 521 else: 522 args = node.args 523 return self._dispatch_to_handler(node, function, args, None) 524 525 def visit_PrimaryCmpNode(self, node): 526 if node.cascade: 527 # not currently handled below 528 self._process_children(node) 529 return node 530 return self._visit_binop_node(node) 531 532 def visit_BinopNode(self, node): 533 return self._visit_binop_node(node) 534 535 def _visit_binop_node(self, node): 536 self._process_children(node) 537 # FIXME: could special case 'not_in' 538 special_method_name = find_special_method_for_binary_operator(node.operator) 539 if special_method_name: 540 operand1, operand2 = node.operand1, node.operand2 541 if special_method_name == '__contains__': 542 operand1, operand2 = operand2, operand1 543 elif special_method_name == '__div__': 544 if Future.division in self.current_env().global_scope().context.future_directives: 545 special_method_name = '__truediv__' 546 obj_type = operand1.type 547 if obj_type.is_builtin_type: 548 type_name = obj_type.name 549 else: 550 type_name = "object" # safety measure 551 node = self._dispatch_to_method_handler( 552 special_method_name, None, False, type_name, 553 node, None, [operand1, operand2], None) 554 return node 555 556 def visit_UnopNode(self, node): 557 self._process_children(node) 558 special_method_name = find_special_method_for_unary_operator(node.operator) 559 if special_method_name: 560 operand = node.operand 561 obj_type = operand.type 562 if obj_type.is_builtin_type: 563 type_name = obj_type.name 564 else: 565 type_name = "object" # safety measure 566 node = self._dispatch_to_method_handler( 567 special_method_name, None, False, type_name, 568 node, None, [operand], None) 569 return node 570 571 ### dispatch to specific handlers 572 573 def _find_handler(self, match_name, has_kwargs): 574 try: 575 match_name.encode('ascii') 576 except UnicodeEncodeError: 577 # specifically when running the Cython compiler under Python 2 578 # getattr can't take a unicode string. 579 # Classes with unicode names won't have specific handlers and thus it 580 # should be OK to return None. 581 # Doing the test here ensures that the same code gets run on 582 # Python 2 and 3 583 return None 584 585 call_type = has_kwargs and 'general' or 'simple' 586 handler = getattr(self, '_handle_%s_%s' % (call_type, match_name), None) 587 if handler is None: 588 handler = getattr(self, '_handle_any_%s' % match_name, None) 589 return handler 590 591 def _delegate_to_assigned_value(self, node, function, arg_list, kwargs): 592 assignment = function.cf_state[0] 593 value = assignment.rhs 594 if value.is_name: 595 if not value.entry or len(value.entry.cf_assignments) > 1: 596 # the variable might have been reassigned => play safe 597 return node 598 elif value.is_attribute and value.obj.is_name: 599 if not value.obj.entry or len(value.obj.entry.cf_assignments) > 1: 600 # the underlying variable might have been reassigned => play safe 601 return node 602 else: 603 return node 604 return self._dispatch_to_handler( 605 node, value, arg_list, kwargs) 606 607 def _dispatch_to_handler(self, node, function, arg_list, kwargs): 608 if function.is_name: 609 # we only consider functions that are either builtin 610 # Python functions or builtins that were already replaced 611 # into a C function call (defined in the builtin scope) 612 if not function.entry: 613 return node 614 entry = function.entry 615 is_builtin = ( 616 entry.is_builtin or 617 entry is self.current_env().builtin_scope().lookup_here(function.name)) 618 if not is_builtin: 619 if function.cf_state and function.cf_state.is_single: 620 # we know the value of the variable 621 # => see if it's usable instead 622 return self._delegate_to_assigned_value( 623 node, function, arg_list, kwargs) 624 if arg_list and entry.is_cmethod and entry.scope and entry.scope.parent_type.is_builtin_type: 625 if entry.scope.parent_type is arg_list[0].type: 626 # Optimised (unbound) method of a builtin type => try to "de-optimise". 627 return self._dispatch_to_method_handler( 628 entry.name, self_arg=None, is_unbound_method=True, 629 type_name=entry.scope.parent_type.name, 630 node=node, function=function, arg_list=arg_list, kwargs=kwargs) 631 return node 632 function_handler = self._find_handler( 633 "function_%s" % function.name, kwargs) 634 if function_handler is None: 635 return self._handle_function(node, function.name, function, arg_list, kwargs) 636 if kwargs: 637 return function_handler(node, function, arg_list, kwargs) 638 else: 639 return function_handler(node, function, arg_list) 640 elif function.is_attribute: 641 attr_name = function.attribute 642 if function.type.is_pyobject: 643 self_arg = function.obj 644 elif node.self and function.entry: 645 entry = function.entry.as_variable 646 if not entry or not entry.is_builtin: 647 return node 648 # C implementation of a Python builtin method - see if we find further matches 649 self_arg = node.self 650 arg_list = arg_list[1:] # drop CloneNode of self argument 651 else: 652 return node 653 obj_type = self_arg.type 654 is_unbound_method = False 655 if obj_type.is_builtin_type: 656 if obj_type is Builtin.type_type and self_arg.is_name and arg_list and arg_list[0].type.is_pyobject: 657 # calling an unbound method like 'list.append(L,x)' 658 # (ignoring 'type.mro()' here ...) 659 type_name = self_arg.name 660 self_arg = None 661 is_unbound_method = True 662 else: 663 type_name = obj_type.name 664 else: 665 type_name = "object" # safety measure 666 return self._dispatch_to_method_handler( 667 attr_name, self_arg, is_unbound_method, type_name, 668 node, function, arg_list, kwargs) 669 else: 670 return node 671 672 def _dispatch_to_method_handler(self, attr_name, self_arg, 673 is_unbound_method, type_name, 674 node, function, arg_list, kwargs): 675 method_handler = self._find_handler( 676 "method_%s_%s" % (type_name, attr_name), kwargs) 677 if method_handler is None: 678 if (attr_name in TypeSlots.method_name_to_slot 679 or attr_name in ['__new__', '__class__']): 680 method_handler = self._find_handler( 681 "slot%s" % attr_name, kwargs) 682 if method_handler is None: 683 return self._handle_method( 684 node, type_name, attr_name, function, 685 arg_list, is_unbound_method, kwargs) 686 if self_arg is not None: 687 arg_list = [self_arg] + list(arg_list) 688 if kwargs: 689 result = method_handler( 690 node, function, arg_list, is_unbound_method, kwargs) 691 else: 692 result = method_handler( 693 node, function, arg_list, is_unbound_method) 694 return result 695 696 def _handle_function(self, node, function_name, function, arg_list, kwargs): 697 """Fallback handler""" 698 return node 699 700 def _handle_method(self, node, type_name, attr_name, function, 701 arg_list, is_unbound_method, kwargs): 702 """Fallback handler""" 703 return node 704 705 706class RecursiveNodeReplacer(VisitorTransform): 707 """ 708 Recursively replace all occurrences of a node in a subtree by 709 another node. 710 """ 711 def __init__(self, orig_node, new_node): 712 super(RecursiveNodeReplacer, self).__init__() 713 self.orig_node, self.new_node = orig_node, new_node 714 715 def visit_CloneNode(self, node): 716 if node is self.orig_node: 717 return self.new_node 718 if node.arg is self.orig_node: 719 node.arg = self.new_node 720 return node 721 722 def visit_Node(self, node): 723 self._process_children(node) 724 if node is self.orig_node: 725 return self.new_node 726 else: 727 return node 728 729def recursively_replace_node(tree, old_node, new_node): 730 replace_in = RecursiveNodeReplacer(old_node, new_node) 731 replace_in(tree) 732 733 734class NodeFinder(TreeVisitor): 735 """ 736 Find out if a node appears in a subtree. 737 """ 738 def __init__(self, node): 739 super(NodeFinder, self).__init__() 740 self.node = node 741 self.found = False 742 743 def visit_Node(self, node): 744 if self.found: 745 pass # short-circuit 746 elif node is self.node: 747 self.found = True 748 else: 749 self._visitchildren(node, None) 750 751def tree_contains(tree, node): 752 finder = NodeFinder(node) 753 finder.visit(tree) 754 return finder.found 755 756 757# Utils 758def replace_node(ptr, value): 759 """Replaces a node. ptr is of the form used on the access path stack 760 (parent, attrname, listidx|None) 761 """ 762 parent, attrname, listidx = ptr 763 if listidx is None: 764 setattr(parent, attrname, value) 765 else: 766 getattr(parent, attrname)[listidx] = value 767 768 769class PrintTree(TreeVisitor): 770 """Prints a representation of the tree to standard output. 771 Subclass and override repr_of to provide more information 772 about nodes. """ 773 def __init__(self, start=None, end=None): 774 TreeVisitor.__init__(self) 775 self._indent = "" 776 if start is not None or end is not None: 777 self._line_range = (start or 0, end or 2**30) 778 else: 779 self._line_range = None 780 781 def indent(self): 782 self._indent += " " 783 784 def unindent(self): 785 self._indent = self._indent[:-2] 786 787 def __call__(self, tree, phase=None): 788 print("Parse tree dump at phase '%s'" % phase) 789 self.visit(tree) 790 return tree 791 792 # Don't do anything about process_list, the defaults gives 793 # nice-looking name[idx] nodes which will visually appear 794 # under the parent-node, not displaying the list itself in 795 # the hierarchy. 796 def visit_Node(self, node): 797 self._print_node(node) 798 self.indent() 799 self.visitchildren(node) 800 self.unindent() 801 return node 802 803 def visit_CloneNode(self, node): 804 self._print_node(node) 805 self.indent() 806 line = node.pos[1] 807 if self._line_range is None or self._line_range[0] <= line <= self._line_range[1]: 808 print("%s- %s: %s" % (self._indent, 'arg', self.repr_of(node.arg))) 809 self.indent() 810 self.visitchildren(node.arg) 811 self.unindent() 812 self.unindent() 813 return node 814 815 def _print_node(self, node): 816 line = node.pos[1] 817 if self._line_range is None or self._line_range[0] <= line <= self._line_range[1]: 818 if len(self.access_path) == 0: 819 name = "(root)" 820 else: 821 parent, attr, idx = self.access_path[-1] 822 if idx is not None: 823 name = "%s[%d]" % (attr, idx) 824 else: 825 name = attr 826 print("%s- %s: %s" % (self._indent, name, self.repr_of(node))) 827 828 def repr_of(self, node): 829 if node is None: 830 return "(none)" 831 else: 832 result = node.__class__.__name__ 833 if isinstance(node, ExprNodes.NameNode): 834 result += "(type=%s, name=\"%s\")" % (repr(node.type), node.name) 835 elif isinstance(node, Nodes.DefNode): 836 result += "(name=\"%s\")" % node.name 837 elif isinstance(node, ExprNodes.AttributeNode): 838 result += "(type=%s, attribute=\"%s\")" % (repr(node.type), node.attribute) 839 elif isinstance(node, (ExprNodes.ConstNode, ExprNodes.PyConstNode)): 840 result += "(type=%s, value=%r)" % (repr(node.type), node.value) 841 elif isinstance(node, ExprNodes.ExprNode): 842 t = node.type 843 result += "(type=%s)" % repr(t) 844 elif node.pos: 845 pos = node.pos 846 path = pos[0].get_description() 847 if '/' in path: 848 path = path.split('/')[-1] 849 if '\\' in path: 850 path = path.split('\\')[-1] 851 result += "(pos=(%s:%s:%s))" % (path, pos[1], pos[2]) 852 853 return result 854 855if __name__ == "__main__": 856 import doctest 857 doctest.testmod() 858