##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################

__version__='$Revision: 1.6 $'[11:-2]

from SelectCompiler import ast

ListType = type([])
TupleType = type(())
SequenceTypes = (ListType, TupleType)

class MutatingWalker:

    def __init__(self, visitor):
        self.visitor = visitor
        self._cache = {}

    def defaultVisitNode(self, node, walker=None, exclude=None):
        for name, child in list(node.__dict__.items()):
            if exclude is not None and name in exclude:
                continue
            v = self.dispatchObject(child)
            if v is not child:
                # Replace the node.
                node.__dict__[name] = v
        return node

    def visitSequence(self, seq):
        res = seq
        for idx in range(len(seq)):
            child = seq[idx]
            v = self.dispatchObject(child)
            if v is not child:
                # Change the sequence.
                if type(res) is ListType:
                    res[idx : idx + 1] = [v]
                else:
                    res = res[:idx] + (v,) + res[idx + 1:]
        return res

    def dispatchObject(self, ob):
        '''
        Expected to return either ob or something that will take
        its place.
        '''
        if isinstance(ob, ast.Node):
            return self.dispatchNode(ob)
        elif type(ob) in SequenceTypes:
            return self.visitSequence(ob)
        else:
            return ob

    def dispatchNode(self, node):
        klass = node.__class__
        meth = self._cache.get(klass, None)
        if meth is None:
            className = klass.__name__
            meth = getattr(self.visitor, 'visit' + className,
                           self.defaultVisitNode)
            self._cache[klass] = meth
        return meth(node, self)

def walk(tree, visitor):
    return MutatingWalker(visitor).dispatchNode(tree)
