Spaces:
Runtime error
Runtime error
| # cython: infer_types=True | |
| # cython: language_level=3 | |
| # cython: auto_pickle=False | |
| # | |
| # Tree visitor and transform framework | |
| # | |
| from __future__ import absolute_import, print_function | |
| import sys | |
| import inspect | |
| from . import TypeSlots | |
| from . import Builtin | |
| from . import Nodes | |
| from . import ExprNodes | |
| from . import Errors | |
| from . import DebugFlags | |
| from . import Future | |
| import cython | |
| cython.declare(_PRINTABLE=tuple) | |
| if sys.version_info[0] >= 3: | |
| _PRINTABLE = (bytes, str, int, float) | |
| else: | |
| _PRINTABLE = (str, unicode, long, int, float) | |
| class TreeVisitor(object): | |
| """ | |
| Base class for writing visitors for a Cython tree, contains utilities for | |
| recursing such trees using visitors. Each node is | |
| expected to have a child_attrs iterable containing the names of attributes | |
| containing child nodes or lists of child nodes. Lists are not considered | |
| part of the tree structure (i.e. contained nodes are considered direct | |
| children of the parent node). | |
| visit_children visits each of the children of a given node (see the visit_children | |
| documentation). When recursing the tree using visit_children, an attribute | |
| access_path is maintained which gives information about the current location | |
| in the tree as a stack of tuples: (parent_node, attrname, index), representing | |
| the node, attribute and optional list index that was taken in each step in the path to | |
| the current node. | |
| Example: | |
| >>> class SampleNode(object): | |
| ... child_attrs = ["head", "body"] | |
| ... def __init__(self, value, head=None, body=None): | |
| ... self.value = value | |
| ... self.head = head | |
| ... self.body = body | |
| ... def __repr__(self): return "SampleNode(%s)" % self.value | |
| ... | |
| >>> tree = SampleNode(0, SampleNode(1), [SampleNode(2), SampleNode(3)]) | |
| >>> class MyVisitor(TreeVisitor): | |
| ... def visit_SampleNode(self, node): | |
| ... print("in %s %s" % (node.value, self.access_path)) | |
| ... self.visitchildren(node) | |
| ... print("out %s" % node.value) | |
| ... | |
| >>> MyVisitor().visit(tree) | |
| in 0 [] | |
| in 1 [(SampleNode(0), 'head', None)] | |
| out 1 | |
| in 2 [(SampleNode(0), 'body', 0)] | |
| out 2 | |
| in 3 [(SampleNode(0), 'body', 1)] | |
| out 3 | |
| out 0 | |
| """ | |
| def __init__(self): | |
| super(TreeVisitor, self).__init__() | |
| self.dispatch_table = {} | |
| self.access_path = [] | |
| def dump_node(self, node): | |
| ignored = list(node.child_attrs or []) + [ | |
| u'child_attrs', u'pos', u'gil_message', u'cpp_message', u'subexprs'] | |
| values = [] | |
| pos = getattr(node, 'pos', None) | |
| if pos: | |
| source = pos[0] | |
| if source: | |
| import os.path | |
| source = os.path.basename(source.get_description()) | |
| values.append(u'%s:%s:%s' % (source, pos[1], pos[2])) | |
| attribute_names = dir(node) | |
| for attr in attribute_names: | |
| if attr in ignored: | |
| continue | |
| if attr.startswith('_') or attr.endswith('_'): | |
| continue | |
| try: | |
| value = getattr(node, attr) | |
| except AttributeError: | |
| continue | |
| if value is None or value == 0: | |
| continue | |
| elif isinstance(value, list): | |
| value = u'[...]/%d' % len(value) | |
| elif not isinstance(value, _PRINTABLE): | |
| continue | |
| else: | |
| value = repr(value) | |
| values.append(u'%s = %s' % (attr, value)) | |
| return u'%s(%s)' % (node.__class__.__name__, u',\n '.join(values)) | |
| def _find_node_path(self, stacktrace): | |
| import os.path | |
| last_traceback = stacktrace | |
| nodes = [] | |
| while hasattr(stacktrace, 'tb_frame'): | |
| frame = stacktrace.tb_frame | |
| node = frame.f_locals.get(u'self') | |
| if isinstance(node, Nodes.Node): | |
| code = frame.f_code | |
| method_name = code.co_name | |
| pos = (os.path.basename(code.co_filename), | |
| frame.f_lineno) | |
| nodes.append((node, method_name, pos)) | |
| last_traceback = stacktrace | |
| stacktrace = stacktrace.tb_next | |
| return (last_traceback, nodes) | |
| def _raise_compiler_error(self, child, e): | |
| trace = [''] | |
| for parent, attribute, index in self.access_path: | |
| node = getattr(parent, attribute) | |
| if index is None: | |
| index = '' | |
| else: | |
| node = node[index] | |
| index = u'[%d]' % index | |
| trace.append(u'%s.%s%s = %s' % ( | |
| parent.__class__.__name__, attribute, index, | |
| self.dump_node(node))) | |
| stacktrace, called_nodes = self._find_node_path(sys.exc_info()[2]) | |
| last_node = child | |
| for node, method_name, pos in called_nodes: | |
| last_node = node | |
| trace.append(u"File '%s', line %d, in %s: %s" % ( | |
| pos[0], pos[1], method_name, self.dump_node(node))) | |
| raise Errors.CompilerCrash( | |
| getattr(last_node, 'pos', None), self.__class__.__name__, | |
| u'\n'.join(trace), e, stacktrace) | |
| def find_handler(self, obj): | |
| # to resolve, try entire hierarchy | |
| cls = type(obj) | |
| pattern = "visit_%s" | |
| mro = inspect.getmro(cls) | |
| for mro_cls in mro: | |
| handler_method = getattr(self, pattern % mro_cls.__name__, None) | |
| if handler_method is not None: | |
| return handler_method | |
| print(type(self), cls) | |
| if self.access_path: | |
| print(self.access_path) | |
| print(self.access_path[-1][0].pos) | |
| print(self.access_path[-1][0].__dict__) | |
| raise RuntimeError("Visitor %r does not accept object: %s" % (self, obj)) | |
| def visit(self, obj): | |
| return self._visit(obj) | |
| def _visit(self, obj): | |
| try: | |
| try: | |
| handler_method = self.dispatch_table[type(obj)] | |
| except KeyError: | |
| handler_method = self.find_handler(obj) | |
| self.dispatch_table[type(obj)] = handler_method | |
| return handler_method(obj) | |
| except Errors.CompileError: | |
| raise | |
| except Errors.AbortError: | |
| raise | |
| except Exception as e: | |
| if DebugFlags.debug_no_exception_intercept: | |
| raise | |
| self._raise_compiler_error(obj, e) | |
| def _visitchild(self, child, parent, attrname, idx): | |
| self.access_path.append((parent, attrname, idx)) | |
| result = self._visit(child) | |
| self.access_path.pop() | |
| return result | |
| def visitchildren(self, parent, attrs=None): | |
| return self._visitchildren(parent, attrs) | |
| def _visitchildren(self, parent, attrs): | |
| """ | |
| Visits the children of the given parent. If parent is None, returns | |
| immediately (returning None). | |
| The return value is a dictionary giving the results for each | |
| child (mapping the attribute name to either the return value | |
| or a list of return values (in the case of multiple children | |
| in an attribute)). | |
| """ | |
| if parent is None: return None | |
| result = {} | |
| for attr in parent.child_attrs: | |
| if attrs is not None and attr not in attrs: continue | |
| child = getattr(parent, attr) | |
| if child is not None: | |
| if type(child) is list: | |
| childretval = [self._visitchild(x, parent, attr, idx) for idx, x in enumerate(child)] | |
| else: | |
| childretval = self._visitchild(child, parent, attr, None) | |
| assert not isinstance(childretval, list), 'Cannot insert list here: %s in %r' % (attr, parent) | |
| result[attr] = childretval | |
| return result | |
| class VisitorTransform(TreeVisitor): | |
| """ | |
| A tree transform is a base class for visitors that wants to do stream | |
| processing of the structure (rather than attributes etc.) of a tree. | |
| It implements __call__ to simply visit the argument node. | |
| It requires the visitor methods to return the nodes which should take | |
| the place of the visited node in the result tree (which can be the same | |
| or one or more replacement). Specifically, if the return value from | |
| a visitor method is: | |
| - [] or None; the visited node will be removed (set to None if an attribute and | |
| removed if in a list) | |
| - A single node; the visited node will be replaced by the returned node. | |
| - A list of nodes; the visited nodes will be replaced by all the nodes in the | |
| list. This will only work if the node was already a member of a list; if it | |
| was not, an exception will be raised. (Typically you want to ensure that you | |
| are within a StatListNode or similar before doing this.) | |
| """ | |
| def visitchildren(self, parent, attrs=None, exclude=None): | |
| # generic def entry point for calls from Python subclasses | |
| if exclude is not None: | |
| attrs = self._select_attrs(parent.child_attrs if attrs is None else attrs, exclude) | |
| return self._process_children(parent, attrs) | |
| def _select_attrs(self, attrs, exclude): | |
| return [name for name in attrs if name not in exclude] | |
| def _process_children(self, parent, attrs=None): | |
| # fast cdef entry point for calls from Cython subclasses | |
| result = self._visitchildren(parent, attrs) | |
| for attr, newnode in result.items(): | |
| if type(newnode) is list: | |
| newnode = self._flatten_list(newnode) | |
| setattr(parent, attr, newnode) | |
| return result | |
| def _flatten_list(self, orig_list): | |
| # Flatten the list one level and remove any None | |
| newlist = [] | |
| for x in orig_list: | |
| if x is not None: | |
| if type(x) is list: | |
| newlist.extend(x) | |
| else: | |
| newlist.append(x) | |
| return newlist | |
| def recurse_to_children(self, node): | |
| self._process_children(node) | |
| return node | |
| def __call__(self, root): | |
| return self._visit(root) | |
| class CythonTransform(VisitorTransform): | |
| """ | |
| Certain common conventions and utilities for Cython transforms. | |
| - Sets up the context of the pipeline in self.context | |
| - Tracks directives in effect in self.current_directives | |
| """ | |
| def __init__(self, context): | |
| super(CythonTransform, self).__init__() | |
| self.context = context | |
| def __call__(self, node): | |
| from . import ModuleNode | |
| if isinstance(node, ModuleNode.ModuleNode): | |
| self.current_directives = node.directives | |
| return super(CythonTransform, self).__call__(node) | |
| def visit_CompilerDirectivesNode(self, node): | |
| old = self.current_directives | |
| self.current_directives = node.directives | |
| self._process_children(node) | |
| self.current_directives = old | |
| return node | |
| def visit_Node(self, node): | |
| self._process_children(node) | |
| return node | |
| class ScopeTrackingTransform(CythonTransform): | |
| # Keeps track of type of scopes | |
| #scope_type: can be either of 'module', 'function', 'cclass', 'pyclass', 'struct' | |
| #scope_node: the node that owns the current scope | |
| def visit_ModuleNode(self, node): | |
| self.scope_type = 'module' | |
| self.scope_node = node | |
| self._process_children(node) | |
| return node | |
| def visit_scope(self, node, scope_type): | |
| prev = self.scope_type, self.scope_node | |
| self.scope_type = scope_type | |
| self.scope_node = node | |
| self._process_children(node) | |
| self.scope_type, self.scope_node = prev | |
| return node | |
| def visit_CClassDefNode(self, node): | |
| return self.visit_scope(node, 'cclass') | |
| def visit_PyClassDefNode(self, node): | |
| return self.visit_scope(node, 'pyclass') | |
| def visit_FuncDefNode(self, node): | |
| return self.visit_scope(node, 'function') | |
| def visit_CStructOrUnionDefNode(self, node): | |
| return self.visit_scope(node, 'struct') | |
| class EnvTransform(CythonTransform): | |
| """ | |
| This transformation keeps a stack of the environments. | |
| """ | |
| def __call__(self, root): | |
| self.env_stack = [] | |
| self.enter_scope(root, root.scope) | |
| return super(EnvTransform, self).__call__(root) | |
| def current_env(self): | |
| return self.env_stack[-1][1] | |
| def current_scope_node(self): | |
| return self.env_stack[-1][0] | |
| def global_scope(self): | |
| return self.current_env().global_scope() | |
| def enter_scope(self, node, scope): | |
| self.env_stack.append((node, scope)) | |
| def exit_scope(self): | |
| self.env_stack.pop() | |
| def visit_FuncDefNode(self, node): | |
| self.enter_scope(node, node.local_scope) | |
| self._process_children(node) | |
| self.exit_scope() | |
| return node | |
| def visit_GeneratorBodyDefNode(self, node): | |
| self._process_children(node) | |
| return node | |
| def visit_ClassDefNode(self, node): | |
| self.enter_scope(node, node.scope) | |
| self._process_children(node) | |
| self.exit_scope() | |
| return node | |
| def visit_CStructOrUnionDefNode(self, node): | |
| self.enter_scope(node, node.scope) | |
| self._process_children(node) | |
| self.exit_scope() | |
| return node | |
| def visit_ScopedExprNode(self, node): | |
| if node.expr_scope: | |
| self.enter_scope(node, node.expr_scope) | |
| self._process_children(node) | |
| self.exit_scope() | |
| else: | |
| self._process_children(node) | |
| return node | |
| def visit_CArgDeclNode(self, node): | |
| # default arguments are evaluated in the outer scope | |
| if node.default: | |
| attrs = [attr for attr in node.child_attrs if attr != 'default'] | |
| self._process_children(node, attrs) | |
| self.enter_scope(node, self.current_env().outer_scope) | |
| self.visitchildren(node, ('default',)) | |
| self.exit_scope() | |
| else: | |
| self._process_children(node) | |
| return node | |
| class NodeRefCleanupMixin(object): | |
| """ | |
| Clean up references to nodes that were replaced. | |
| NOTE: this implementation assumes that the replacement is | |
| done first, before hitting any further references during | |
| normal tree traversal. This needs to be arranged by calling | |
| "self.visitchildren()" at a proper place in the transform | |
| and by ordering the "child_attrs" of nodes appropriately. | |
| """ | |
| def __init__(self, *args): | |
| super(NodeRefCleanupMixin, self).__init__(*args) | |
| self._replacements = {} | |
| def visit_CloneNode(self, node): | |
| arg = node.arg | |
| if arg not in self._replacements: | |
| self.visitchildren(arg) | |
| node.arg = self._replacements.get(arg, arg) | |
| return node | |
| def visit_ResultRefNode(self, node): | |
| expr = node.expression | |
| if expr is None or expr not in self._replacements: | |
| self.visitchildren(node) | |
| expr = node.expression | |
| if expr is not None: | |
| node.expression = self._replacements.get(expr, expr) | |
| return node | |
| def replace(self, node, replacement): | |
| self._replacements[node] = replacement | |
| return replacement | |
| find_special_method_for_binary_operator = { | |
| '<': '__lt__', | |
| '<=': '__le__', | |
| '==': '__eq__', | |
| '!=': '__ne__', | |
| '>=': '__ge__', | |
| '>': '__gt__', | |
| '+': '__add__', | |
| '&': '__and__', | |
| '/': '__div__', | |
| '//': '__floordiv__', | |
| '<<': '__lshift__', | |
| '%': '__mod__', | |
| '*': '__mul__', | |
| '|': '__or__', | |
| '**': '__pow__', | |
| '>>': '__rshift__', | |
| '-': '__sub__', | |
| '^': '__xor__', | |
| 'in': '__contains__', | |
| }.get | |
| find_special_method_for_unary_operator = { | |
| 'not': '__not__', | |
| '~': '__inv__', | |
| '-': '__neg__', | |
| '+': '__pos__', | |
| }.get | |
| class MethodDispatcherTransform(EnvTransform): | |
| """ | |
| Base class for transformations that want to intercept on specific | |
| builtin functions or methods of builtin types, including special | |
| methods triggered by Python operators. Must run after declaration | |
| analysis when entries were assigned. | |
| Naming pattern for handler methods is as follows: | |
| * builtin functions: _handle_(general|simple|any)_function_NAME | |
| * builtin methods: _handle_(general|simple|any)_method_TYPENAME_METHODNAME | |
| """ | |
| # only visit call nodes and Python operations | |
| def visit_GeneralCallNode(self, node): | |
| self._process_children(node) | |
| function = node.function | |
| if not function.type.is_pyobject: | |
| return node | |
| arg_tuple = node.positional_args | |
| if not isinstance(arg_tuple, ExprNodes.TupleNode): | |
| return node | |
| keyword_args = node.keyword_args | |
| if keyword_args and not isinstance(keyword_args, ExprNodes.DictNode): | |
| # can't handle **kwargs | |
| return node | |
| args = arg_tuple.args | |
| return self._dispatch_to_handler(node, function, args, keyword_args) | |
| def visit_SimpleCallNode(self, node): | |
| self._process_children(node) | |
| function = node.function | |
| if function.type.is_pyobject: | |
| arg_tuple = node.arg_tuple | |
| if not isinstance(arg_tuple, ExprNodes.TupleNode): | |
| return node | |
| args = arg_tuple.args | |
| else: | |
| args = node.args | |
| return self._dispatch_to_handler(node, function, args, None) | |
| def visit_PrimaryCmpNode(self, node): | |
| if node.cascade: | |
| # not currently handled below | |
| self._process_children(node) | |
| return node | |
| return self._visit_binop_node(node) | |
| def visit_BinopNode(self, node): | |
| return self._visit_binop_node(node) | |
| def _visit_binop_node(self, node): | |
| self._process_children(node) | |
| # FIXME: could special case 'not_in' | |
| special_method_name = find_special_method_for_binary_operator(node.operator) | |
| if special_method_name: | |
| operand1, operand2 = node.operand1, node.operand2 | |
| if special_method_name == '__contains__': | |
| operand1, operand2 = operand2, operand1 | |
| elif special_method_name == '__div__': | |
| if Future.division in self.current_env().global_scope().context.future_directives: | |
| special_method_name = '__truediv__' | |
| obj_type = operand1.type | |
| if obj_type.is_builtin_type: | |
| type_name = obj_type.name | |
| else: | |
| type_name = "object" # safety measure | |
| node = self._dispatch_to_method_handler( | |
| special_method_name, None, False, type_name, | |
| node, None, [operand1, operand2], None) | |
| return node | |
| def visit_UnopNode(self, node): | |
| self._process_children(node) | |
| special_method_name = find_special_method_for_unary_operator(node.operator) | |
| if special_method_name: | |
| operand = node.operand | |
| obj_type = operand.type | |
| if obj_type.is_builtin_type: | |
| type_name = obj_type.name | |
| else: | |
| type_name = "object" # safety measure | |
| node = self._dispatch_to_method_handler( | |
| special_method_name, None, False, type_name, | |
| node, None, [operand], None) | |
| return node | |
| ### dispatch to specific handlers | |
| def _find_handler(self, match_name, has_kwargs): | |
| call_type = has_kwargs and 'general' or 'simple' | |
| handler = getattr(self, '_handle_%s_%s' % (call_type, match_name), None) | |
| if handler is None: | |
| handler = getattr(self, '_handle_any_%s' % match_name, None) | |
| return handler | |
| def _delegate_to_assigned_value(self, node, function, arg_list, kwargs): | |
| assignment = function.cf_state[0] | |
| value = assignment.rhs | |
| if value.is_name: | |
| if not value.entry or len(value.entry.cf_assignments) > 1: | |
| # the variable might have been reassigned => play safe | |
| return node | |
| elif value.is_attribute and value.obj.is_name: | |
| if not value.obj.entry or len(value.obj.entry.cf_assignments) > 1: | |
| # the underlying variable might have been reassigned => play safe | |
| return node | |
| else: | |
| return node | |
| return self._dispatch_to_handler( | |
| node, value, arg_list, kwargs) | |
| def _dispatch_to_handler(self, node, function, arg_list, kwargs): | |
| if function.is_name: | |
| # we only consider functions that are either builtin | |
| # Python functions or builtins that were already replaced | |
| # into a C function call (defined in the builtin scope) | |
| if not function.entry: | |
| return node | |
| entry = function.entry | |
| is_builtin = ( | |
| entry.is_builtin or | |
| entry is self.current_env().builtin_scope().lookup_here(function.name)) | |
| if not is_builtin: | |
| if function.cf_state and function.cf_state.is_single: | |
| # we know the value of the variable | |
| # => see if it's usable instead | |
| return self._delegate_to_assigned_value( | |
| node, function, arg_list, kwargs) | |
| if arg_list and entry.is_cmethod and entry.scope and entry.scope.parent_type.is_builtin_type: | |
| if entry.scope.parent_type is arg_list[0].type: | |
| # Optimised (unbound) method of a builtin type => try to "de-optimise". | |
| return self._dispatch_to_method_handler( | |
| entry.name, self_arg=None, is_unbound_method=True, | |
| type_name=entry.scope.parent_type.name, | |
| node=node, function=function, arg_list=arg_list, kwargs=kwargs) | |
| return node | |
| function_handler = self._find_handler( | |
| "function_%s" % function.name, kwargs) | |
| if function_handler is None: | |
| return self._handle_function(node, function.name, function, arg_list, kwargs) | |
| if kwargs: | |
| return function_handler(node, function, arg_list, kwargs) | |
| else: | |
| return function_handler(node, function, arg_list) | |
| elif function.is_attribute: | |
| attr_name = function.attribute | |
| if function.type.is_pyobject: | |
| self_arg = function.obj | |
| elif node.self and function.entry: | |
| entry = function.entry.as_variable | |
| if not entry or not entry.is_builtin: | |
| return node | |
| # C implementation of a Python builtin method - see if we find further matches | |
| self_arg = node.self | |
| arg_list = arg_list[1:] # drop CloneNode of self argument | |
| else: | |
| return node | |
| obj_type = self_arg.type | |
| is_unbound_method = False | |
| if obj_type.is_builtin_type: | |
| if obj_type is Builtin.type_type and self_arg.is_name and arg_list and arg_list[0].type.is_pyobject: | |
| # calling an unbound method like 'list.append(L,x)' | |
| # (ignoring 'type.mro()' here ...) | |
| type_name = self_arg.name | |
| self_arg = None | |
| is_unbound_method = True | |
| else: | |
| type_name = obj_type.name | |
| else: | |
| type_name = "object" # safety measure | |
| return self._dispatch_to_method_handler( | |
| attr_name, self_arg, is_unbound_method, type_name, | |
| node, function, arg_list, kwargs) | |
| else: | |
| return node | |
| def _dispatch_to_method_handler(self, attr_name, self_arg, | |
| is_unbound_method, type_name, | |
| node, function, arg_list, kwargs): | |
| method_handler = self._find_handler( | |
| "method_%s_%s" % (type_name, attr_name), kwargs) | |
| if method_handler is None: | |
| if (attr_name in TypeSlots.method_name_to_slot | |
| or attr_name == '__new__'): | |
| method_handler = self._find_handler( | |
| "slot%s" % attr_name, kwargs) | |
| if method_handler is None: | |
| return self._handle_method( | |
| node, type_name, attr_name, function, | |
| arg_list, is_unbound_method, kwargs) | |
| if self_arg is not None: | |
| arg_list = [self_arg] + list(arg_list) | |
| if kwargs: | |
| result = method_handler( | |
| node, function, arg_list, is_unbound_method, kwargs) | |
| else: | |
| result = method_handler( | |
| node, function, arg_list, is_unbound_method) | |
| return result | |
| def _handle_function(self, node, function_name, function, arg_list, kwargs): | |
| """Fallback handler""" | |
| return node | |
| def _handle_method(self, node, type_name, attr_name, function, | |
| arg_list, is_unbound_method, kwargs): | |
| """Fallback handler""" | |
| return node | |
| class RecursiveNodeReplacer(VisitorTransform): | |
| """ | |
| Recursively replace all occurrences of a node in a subtree by | |
| another node. | |
| """ | |
| def __init__(self, orig_node, new_node): | |
| super(RecursiveNodeReplacer, self).__init__() | |
| self.orig_node, self.new_node = orig_node, new_node | |
| def visit_CloneNode(self, node): | |
| if node is self.orig_node: | |
| return self.new_node | |
| if node.arg is self.orig_node: | |
| node.arg = self.new_node | |
| return node | |
| def visit_Node(self, node): | |
| self._process_children(node) | |
| if node is self.orig_node: | |
| return self.new_node | |
| else: | |
| return node | |
| def recursively_replace_node(tree, old_node, new_node): | |
| replace_in = RecursiveNodeReplacer(old_node, new_node) | |
| replace_in(tree) | |
| class NodeFinder(TreeVisitor): | |
| """ | |
| Find out if a node appears in a subtree. | |
| """ | |
| def __init__(self, node): | |
| super(NodeFinder, self).__init__() | |
| self.node = node | |
| self.found = False | |
| def visit_Node(self, node): | |
| if self.found: | |
| pass # short-circuit | |
| elif node is self.node: | |
| self.found = True | |
| else: | |
| self._visitchildren(node, None) | |
| def tree_contains(tree, node): | |
| finder = NodeFinder(node) | |
| finder.visit(tree) | |
| return finder.found | |
| # Utils | |
| def replace_node(ptr, value): | |
| """Replaces a node. ptr is of the form used on the access path stack | |
| (parent, attrname, listidx|None) | |
| """ | |
| parent, attrname, listidx = ptr | |
| if listidx is None: | |
| setattr(parent, attrname, value) | |
| else: | |
| getattr(parent, attrname)[listidx] = value | |
| class PrintTree(TreeVisitor): | |
| """Prints a representation of the tree to standard output. | |
| Subclass and override repr_of to provide more information | |
| about nodes. """ | |
| def __init__(self, start=None, end=None): | |
| TreeVisitor.__init__(self) | |
| self._indent = "" | |
| if start is not None or end is not None: | |
| self._line_range = (start or 0, end or 2**30) | |
| else: | |
| self._line_range = None | |
| def indent(self): | |
| self._indent += " " | |
| def unindent(self): | |
| self._indent = self._indent[:-2] | |
| def __call__(self, tree, phase=None): | |
| print("Parse tree dump at phase '%s'" % phase) | |
| self.visit(tree) | |
| return tree | |
| # Don't do anything about process_list, the defaults gives | |
| # nice-looking name[idx] nodes which will visually appear | |
| # under the parent-node, not displaying the list itself in | |
| # the hierarchy. | |
| def visit_Node(self, node): | |
| self._print_node(node) | |
| self.indent() | |
| self.visitchildren(node) | |
| self.unindent() | |
| return node | |
| def visit_CloneNode(self, node): | |
| self._print_node(node) | |
| self.indent() | |
| line = node.pos[1] | |
| if self._line_range is None or self._line_range[0] <= line <= self._line_range[1]: | |
| print("%s- %s: %s" % (self._indent, 'arg', self.repr_of(node.arg))) | |
| self.indent() | |
| self.visitchildren(node.arg) | |
| self.unindent() | |
| self.unindent() | |
| return node | |
| def _print_node(self, node): | |
| line = node.pos[1] | |
| if self._line_range is None or self._line_range[0] <= line <= self._line_range[1]: | |
| if len(self.access_path) == 0: | |
| name = "(root)" | |
| else: | |
| parent, attr, idx = self.access_path[-1] | |
| if idx is not None: | |
| name = "%s[%d]" % (attr, idx) | |
| else: | |
| name = attr | |
| print("%s- %s: %s" % (self._indent, name, self.repr_of(node))) | |
| def repr_of(self, node): | |
| if node is None: | |
| return "(none)" | |
| else: | |
| result = node.__class__.__name__ | |
| if isinstance(node, ExprNodes.NameNode): | |
| result += "(type=%s, name=\"%s\")" % (repr(node.type), node.name) | |
| elif isinstance(node, Nodes.DefNode): | |
| result += "(name=\"%s\")" % node.name | |
| elif isinstance(node, ExprNodes.ExprNode): | |
| t = node.type | |
| result += "(type=%s)" % repr(t) | |
| elif node.pos: | |
| pos = node.pos | |
| path = pos[0].get_description() | |
| if '/' in path: | |
| path = path.split('/')[-1] | |
| if '\\' in path: | |
| path = path.split('\\')[-1] | |
| result += "(pos=(%s:%s:%s))" % (path, pos[1], pos[2]) | |
| return result | |
| if __name__ == "__main__": | |
| import doctest | |
| doctest.testmod() | |