| Index: third_party/cython/src/Cython/Compiler/Visitor.py
|
| diff --git a/third_party/cython/src/Cython/Compiler/Visitor.py b/third_party/cython/src/Cython/Compiler/Visitor.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..21e8b12552d0dcace9f93556eb680620d97c52e7
|
| --- /dev/null
|
| +++ b/third_party/cython/src/Cython/Compiler/Visitor.py
|
| @@ -0,0 +1,763 @@
|
| +# cython: infer_types=True
|
| +
|
| +#
|
| +# Tree visitor and transform framework
|
| +#
|
| +import inspect
|
| +
|
| +from Cython.Compiler import TypeSlots
|
| +from Cython.Compiler import Builtin
|
| +from Cython.Compiler import Nodes
|
| +from Cython.Compiler import ExprNodes
|
| +from Cython.Compiler import Errors
|
| +from Cython.Compiler import DebugFlags
|
| +
|
| +import cython
|
| +
|
| +
|
| +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", node.value, self.access_path
|
| + ... self.visitchildren(node)
|
| + ... print "out", 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, indent=0):
|
| + 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)
|
| + attribute_names.sort()
|
| + for attr in attribute_names:
|
| + if attr in ignored:
|
| + continue
|
| + if attr.startswith(u'_') or attr.endswith(u'_'):
|
| + 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, (str, unicode, long, int, float)):
|
| + 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):
|
| + import sys
|
| + 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)
|
| +
|
| + @cython.final
|
| + def find_handler(self, obj):
|
| + # to resolve, try entire hierarchy
|
| + cls = type(obj)
|
| + pattern = "visit_%s"
|
| + mro = inspect.getmro(cls)
|
| + handler_method = None
|
| + 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)
|
| +
|
| + @cython.final
|
| + 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, e:
|
| + if DebugFlags.debug_no_exception_intercept:
|
| + raise
|
| + self._raise_compiler_error(obj, e)
|
| +
|
| + @cython.final
|
| + 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)
|
| +
|
| + @cython.final
|
| + @cython.locals(idx=int)
|
| + 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):
|
| + result = self._visitchildren(parent, attrs)
|
| + for attr, newnode in result.iteritems():
|
| + if type(newnode) is not list:
|
| + setattr(parent, attr, newnode)
|
| + else:
|
| + # Flatten the list one level and remove any None
|
| + newlist = []
|
| + for x in newnode:
|
| + if x is not None:
|
| + if type(x) is list:
|
| + newlist += x
|
| + else:
|
| + newlist.append(x)
|
| + setattr(parent, attr, newlist)
|
| + return result
|
| +
|
| + def recurse_to_children(self, node):
|
| + self.visitchildren(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):
|
| + 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.visitchildren(node)
|
| + self.current_directives = old
|
| + return node
|
| +
|
| + def visit_Node(self, node):
|
| + self.visitchildren(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.visitchildren(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.visitchildren(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.visitchildren(node)
|
| + self.exit_scope()
|
| + return node
|
| +
|
| + def visit_GeneratorBodyDefNode(self, node):
|
| + self.visitchildren(node)
|
| + return node
|
| +
|
| + def visit_ClassDefNode(self, node):
|
| + self.enter_scope(node, node.scope)
|
| + self.visitchildren(node)
|
| + self.exit_scope()
|
| + return node
|
| +
|
| + def visit_CStructOrUnionDefNode(self, node):
|
| + self.enter_scope(node, node.scope)
|
| + self.visitchildren(node)
|
| + self.exit_scope()
|
| + return node
|
| +
|
| + def visit_ScopedExprNode(self, node):
|
| + if node.expr_scope:
|
| + self.enter_scope(node, node.expr_scope)
|
| + self.visitchildren(node)
|
| + self.exit_scope()
|
| + else:
|
| + self.visitchildren(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.visitchildren(node, attrs)
|
| + self.enter_scope(node, self.current_env().outer_scope)
|
| + self.visitchildren(node, ('default',))
|
| + self.exit_scope()
|
| + else:
|
| + self.visitchildren(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(node)
|
| + arg = node.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__',
|
| + '/': '__truediv__',
|
| + '//': '__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.visitchildren(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.visitchildren(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.visitchildren(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.visitchildren(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
|
| + 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.visitchildren(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
|
| + is_builtin = (
|
| + function.entry.is_builtin or
|
| + function.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)
|
| + 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 and function.type.is_pyobject:
|
| + attr_name = function.attribute
|
| + self_arg = function.obj
|
| + 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:
|
| + return method_handler(
|
| + node, function, arg_list, is_unbound_method, kwargs)
|
| + else:
|
| + return method_handler(
|
| + node, function, arg_list, is_unbound_method)
|
| +
|
| + 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_Node(self, node):
|
| + self.visitchildren(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):
|
| + TreeVisitor.__init__(self)
|
| + self._indent = ""
|
| +
|
| + 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):
|
| + 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)))
|
| + self.indent()
|
| + self.visitchildren(node)
|
| + self.unindent()
|
| + return 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()
|
|
|