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 self.enter_scope(node, node.local_scope) 374 self._process_children(node) 375 self.exit_scope() 376 return node 377 378 def visit_GeneratorBodyDefNode(self, node): 379 self._process_children(node) 380 return node 381 382 def visit_ClassDefNode(self, node): 383 self.enter_scope(node, node.scope) 384 self._process_children(node) 385 self.exit_scope() 386 return node 387 388 def visit_CStructOrUnionDefNode(self, node): 389 self.enter_scope(node, node.scope) 390 self._process_children(node) 391 self.exit_scope() 392 return node 393 394 def visit_ScopedExprNode(self, node): 395 if node.expr_scope: 396 self.enter_scope(node, node.expr_scope) 397 self._process_children(node) 398 self.exit_scope() 399 else: 400 self._process_children(node) 401 return node 402 403 def visit_CArgDeclNode(self, node): 404 # default arguments are evaluated in the outer scope 405 if node.default: 406 attrs = [attr for attr in node.child_attrs if attr != 'default'] 407 self._process_children(node, attrs) 408 self.enter_scope(node, self.current_env().outer_scope) 409 self.visitchildren(node, ('default',)) 410 self.exit_scope() 411 else: 412 self._process_children(node) 413 return node 414 415 416class NodeRefCleanupMixin(object): 417 """ 418 Clean up references to nodes that were replaced. 419 420 NOTE: this implementation assumes that the replacement is 421 done first, before hitting any further references during 422 normal tree traversal. This needs to be arranged by calling 423 "self.visitchildren()" at a proper place in the transform 424 and by ordering the "child_attrs" of nodes appropriately. 425 """ 426 def __init__(self, *args): 427 super(NodeRefCleanupMixin, self).__init__(*args) 428 self._replacements = {} 429 430 def visit_CloneNode(self, node): 431 arg = node.arg 432 if arg not in self._replacements: 433 self.visitchildren(arg) 434 node.arg = self._replacements.get(arg, arg) 435 return node 436 437 def visit_ResultRefNode(self, node): 438 expr = node.expression 439 if expr is None or expr not in self._replacements: 440 self.visitchildren(node) 441 expr = node.expression 442 if expr is not None: 443 node.expression = self._replacements.get(expr, expr) 444 return node 445 446 def replace(self, node, replacement): 447 self._replacements[node] = replacement 448 return replacement 449 450 451find_special_method_for_binary_operator = { 452 '<': '__lt__', 453 '<=': '__le__', 454 '==': '__eq__', 455 '!=': '__ne__', 456 '>=': '__ge__', 457 '>': '__gt__', 458 '+': '__add__', 459 '&': '__and__', 460 '/': '__div__', 461 '//': '__floordiv__', 462 '<<': '__lshift__', 463 '%': '__mod__', 464 '*': '__mul__', 465 '|': '__or__', 466 '**': '__pow__', 467 '>>': '__rshift__', 468 '-': '__sub__', 469 '^': '__xor__', 470 'in': '__contains__', 471}.get 472 473 474find_special_method_for_unary_operator = { 475 'not': '__not__', 476 '~': '__inv__', 477 '-': '__neg__', 478 '+': '__pos__', 479}.get 480 481 482class MethodDispatcherTransform(EnvTransform): 483 """ 484 Base class for transformations that want to intercept on specific 485 builtin functions or methods of builtin types, including special 486 methods triggered by Python operators. Must run after declaration 487 analysis when entries were assigned. 488 489 Naming pattern for handler methods is as follows: 490 491 * builtin functions: _handle_(general|simple|any)_function_NAME 492 493 * builtin methods: _handle_(general|simple|any)_method_TYPENAME_METHODNAME 494 """ 495 # only visit call nodes and Python operations 496 def visit_GeneralCallNode(self, node): 497 self._process_children(node) 498 function = node.function 499 if not function.type.is_pyobject: 500 return node 501 arg_tuple = node.positional_args 502 if not isinstance(arg_tuple, ExprNodes.TupleNode): 503 return node 504 keyword_args = node.keyword_args 505 if keyword_args and not isinstance(keyword_args, ExprNodes.DictNode): 506 # can't handle **kwargs 507 return node 508 args = arg_tuple.args 509 return self._dispatch_to_handler(node, function, args, keyword_args) 510 511 def visit_SimpleCallNode(self, node): 512 self._process_children(node) 513 function = node.function 514 if function.type.is_pyobject: 515 arg_tuple = node.arg_tuple 516 if not isinstance(arg_tuple, ExprNodes.TupleNode): 517 return node 518 args = arg_tuple.args 519 else: 520 args = node.args 521 return self._dispatch_to_handler(node, function, args, None) 522 523 def visit_PrimaryCmpNode(self, node): 524 if node.cascade: 525 # not currently handled below 526 self._process_children(node) 527 return node 528 return self._visit_binop_node(node) 529 530 def visit_BinopNode(self, node): 531 return self._visit_binop_node(node) 532 533 def _visit_binop_node(self, node): 534 self._process_children(node) 535 # FIXME: could special case 'not_in' 536 special_method_name = find_special_method_for_binary_operator(node.operator) 537 if special_method_name: 538 operand1, operand2 = node.operand1, node.operand2 539 if special_method_name == '__contains__': 540 operand1, operand2 = operand2, operand1 541 elif special_method_name == '__div__': 542 if Future.division in self.current_env().global_scope().context.future_directives: 543 special_method_name = '__truediv__' 544 obj_type = operand1.type 545 if obj_type.is_builtin_type: 546 type_name = obj_type.name 547 else: 548 type_name = "object" # safety measure 549 node = self._dispatch_to_method_handler( 550 special_method_name, None, False, type_name, 551 node, None, [operand1, operand2], None) 552 return node 553 554 def visit_UnopNode(self, node): 555 self._process_children(node) 556 special_method_name = find_special_method_for_unary_operator(node.operator) 557 if special_method_name: 558 operand = node.operand 559 obj_type = operand.type 560 if obj_type.is_builtin_type: 561 type_name = obj_type.name 562 else: 563 type_name = "object" # safety measure 564 node = self._dispatch_to_method_handler( 565 special_method_name, None, False, type_name, 566 node, None, [operand], None) 567 return node 568 569 ### dispatch to specific handlers 570 571 def _find_handler(self, match_name, has_kwargs): 572 call_type = has_kwargs and 'general' or 'simple' 573 handler = getattr(self, '_handle_%s_%s' % (call_type, match_name), None) 574 if handler is None: 575 handler = getattr(self, '_handle_any_%s' % match_name, None) 576 return handler 577 578 def _delegate_to_assigned_value(self, node, function, arg_list, kwargs): 579 assignment = function.cf_state[0] 580 value = assignment.rhs 581 if value.is_name: 582 if not value.entry or len(value.entry.cf_assignments) > 1: 583 # the variable might have been reassigned => play safe 584 return node 585 elif value.is_attribute and value.obj.is_name: 586 if not value.obj.entry or len(value.obj.entry.cf_assignments) > 1: 587 # the underlying variable might have been reassigned => play safe 588 return node 589 else: 590 return node 591 return self._dispatch_to_handler( 592 node, value, arg_list, kwargs) 593 594 def _dispatch_to_handler(self, node, function, arg_list, kwargs): 595 if function.is_name: 596 # we only consider functions that are either builtin 597 # Python functions or builtins that were already replaced 598 # into a C function call (defined in the builtin scope) 599 if not function.entry: 600 return node 601 entry = function.entry 602 is_builtin = ( 603 entry.is_builtin or 604 entry is self.current_env().builtin_scope().lookup_here(function.name)) 605 if not is_builtin: 606 if function.cf_state and function.cf_state.is_single: 607 # we know the value of the variable 608 # => see if it's usable instead 609 return self._delegate_to_assigned_value( 610 node, function, arg_list, kwargs) 611 if arg_list and entry.is_cmethod and entry.scope and entry.scope.parent_type.is_builtin_type: 612 if entry.scope.parent_type is arg_list[0].type: 613 # Optimised (unbound) method of a builtin type => try to "de-optimise". 614 return self._dispatch_to_method_handler( 615 entry.name, self_arg=None, is_unbound_method=True, 616 type_name=entry.scope.parent_type.name, 617 node=node, function=function, arg_list=arg_list, kwargs=kwargs) 618 return node 619 function_handler = self._find_handler( 620 "function_%s" % function.name, kwargs) 621 if function_handler is None: 622 return self._handle_function(node, function.name, function, arg_list, kwargs) 623 if kwargs: 624 return function_handler(node, function, arg_list, kwargs) 625 else: 626 return function_handler(node, function, arg_list) 627 elif function.is_attribute: 628 attr_name = function.attribute 629 if function.type.is_pyobject: 630 self_arg = function.obj 631 elif node.self and function.entry: 632 entry = function.entry.as_variable 633 if not entry or not entry.is_builtin: 634 return node 635 # C implementation of a Python builtin method - see if we find further matches 636 self_arg = node.self 637 arg_list = arg_list[1:] # drop CloneNode of self argument 638 else: 639 return node 640 obj_type = self_arg.type 641 is_unbound_method = False 642 if obj_type.is_builtin_type: 643 if obj_type is Builtin.type_type and self_arg.is_name and arg_list and arg_list[0].type.is_pyobject: 644 # calling an unbound method like 'list.append(L,x)' 645 # (ignoring 'type.mro()' here ...) 646 type_name = self_arg.name 647 self_arg = None 648 is_unbound_method = True 649 else: 650 type_name = obj_type.name 651 else: 652 type_name = "object" # safety measure 653 return self._dispatch_to_method_handler( 654 attr_name, self_arg, is_unbound_method, type_name, 655 node, function, arg_list, kwargs) 656 else: 657 return node 658 659 def _dispatch_to_method_handler(self, attr_name, self_arg, 660 is_unbound_method, type_name, 661 node, function, arg_list, kwargs): 662 method_handler = self._find_handler( 663 "method_%s_%s" % (type_name, attr_name), kwargs) 664 if method_handler is None: 665 if (attr_name in TypeSlots.method_name_to_slot 666 or attr_name == '__new__'): 667 method_handler = self._find_handler( 668 "slot%s" % attr_name, kwargs) 669 if method_handler is None: 670 return self._handle_method( 671 node, type_name, attr_name, function, 672 arg_list, is_unbound_method, kwargs) 673 if self_arg is not None: 674 arg_list = [self_arg] + list(arg_list) 675 if kwargs: 676 result = method_handler( 677 node, function, arg_list, is_unbound_method, kwargs) 678 else: 679 result = method_handler( 680 node, function, arg_list, is_unbound_method) 681 return result 682 683 def _handle_function(self, node, function_name, function, arg_list, kwargs): 684 """Fallback handler""" 685 return node 686 687 def _handle_method(self, node, type_name, attr_name, function, 688 arg_list, is_unbound_method, kwargs): 689 """Fallback handler""" 690 return node 691 692 693class RecursiveNodeReplacer(VisitorTransform): 694 """ 695 Recursively replace all occurrences of a node in a subtree by 696 another node. 697 """ 698 def __init__(self, orig_node, new_node): 699 super(RecursiveNodeReplacer, self).__init__() 700 self.orig_node, self.new_node = orig_node, new_node 701 702 def visit_CloneNode(self, node): 703 if node is self.orig_node: 704 return self.new_node 705 if node.arg is self.orig_node: 706 node.arg = self.new_node 707 return node 708 709 def visit_Node(self, node): 710 self._process_children(node) 711 if node is self.orig_node: 712 return self.new_node 713 else: 714 return node 715 716def recursively_replace_node(tree, old_node, new_node): 717 replace_in = RecursiveNodeReplacer(old_node, new_node) 718 replace_in(tree) 719 720 721class NodeFinder(TreeVisitor): 722 """ 723 Find out if a node appears in a subtree. 724 """ 725 def __init__(self, node): 726 super(NodeFinder, self).__init__() 727 self.node = node 728 self.found = False 729 730 def visit_Node(self, node): 731 if self.found: 732 pass # short-circuit 733 elif node is self.node: 734 self.found = True 735 else: 736 self._visitchildren(node, None) 737 738def tree_contains(tree, node): 739 finder = NodeFinder(node) 740 finder.visit(tree) 741 return finder.found 742 743 744# Utils 745def replace_node(ptr, value): 746 """Replaces a node. ptr is of the form used on the access path stack 747 (parent, attrname, listidx|None) 748 """ 749 parent, attrname, listidx = ptr 750 if listidx is None: 751 setattr(parent, attrname, value) 752 else: 753 getattr(parent, attrname)[listidx] = value 754 755 756class PrintTree(TreeVisitor): 757 """Prints a representation of the tree to standard output. 758 Subclass and override repr_of to provide more information 759 about nodes. """ 760 def __init__(self, start=None, end=None): 761 TreeVisitor.__init__(self) 762 self._indent = "" 763 if start is not None or end is not None: 764 self._line_range = (start or 0, end or 2**30) 765 else: 766 self._line_range = None 767 768 def indent(self): 769 self._indent += " " 770 771 def unindent(self): 772 self._indent = self._indent[:-2] 773 774 def __call__(self, tree, phase=None): 775 print("Parse tree dump at phase '%s'" % phase) 776 self.visit(tree) 777 return tree 778 779 # Don't do anything about process_list, the defaults gives 780 # nice-looking name[idx] nodes which will visually appear 781 # under the parent-node, not displaying the list itself in 782 # the hierarchy. 783 def visit_Node(self, node): 784 self._print_node(node) 785 self.indent() 786 self.visitchildren(node) 787 self.unindent() 788 return node 789 790 def visit_CloneNode(self, node): 791 self._print_node(node) 792 self.indent() 793 line = node.pos[1] 794 if self._line_range is None or self._line_range[0] <= line <= self._line_range[1]: 795 print("%s- %s: %s" % (self._indent, 'arg', self.repr_of(node.arg))) 796 self.indent() 797 self.visitchildren(node.arg) 798 self.unindent() 799 self.unindent() 800 return node 801 802 def _print_node(self, node): 803 line = node.pos[1] 804 if self._line_range is None or self._line_range[0] <= line <= self._line_range[1]: 805 if len(self.access_path) == 0: 806 name = "(root)" 807 else: 808 parent, attr, idx = self.access_path[-1] 809 if idx is not None: 810 name = "%s[%d]" % (attr, idx) 811 else: 812 name = attr 813 print("%s- %s: %s" % (self._indent, name, self.repr_of(node))) 814 815 def repr_of(self, node): 816 if node is None: 817 return "(none)" 818 else: 819 result = node.__class__.__name__ 820 if isinstance(node, ExprNodes.NameNode): 821 result += "(type=%s, name=\"%s\")" % (repr(node.type), node.name) 822 elif isinstance(node, Nodes.DefNode): 823 result += "(name=\"%s\")" % node.name 824 elif isinstance(node, ExprNodes.ExprNode): 825 t = node.type 826 result += "(type=%s)" % repr(t) 827 elif node.pos: 828 pos = node.pos 829 path = pos[0].get_description() 830 if '/' in path: 831 path = path.split('/')[-1] 832 if '\\' in path: 833 path = path.split('\\')[-1] 834 result += "(pos=(%s:%s:%s))" % (path, pos[1], pos[2]) 835 836 return result 837 838if __name__ == "__main__": 839 import doctest 840 doctest.testmod() 841