class node(object):
    """
    This class represents a node in the AST built while parsing command lines.
    It's basically an object container for various attributes, with a slightly
    specialised representation to make it a little easier to debug the parser.
    """

    def __init__(self, **kwargs):
        assert 'kind' in kwargs
        self.__dict__.update(kwargs)

    def dump(self, indent='  '):
        return _dump(self, indent)

    def __repr__(self):
        chunks = []
        d = dict(self.__dict__)
        kind = d.pop('kind')
        for k, v in sorted(d.items()):
            chunks.append('%s=%r' % (k, v))
        return '%sNode(%s)' % (kind.title(), ' '.join(chunks))

    def __eq__(self, other):
        if not isinstance(other, node):
            return False
        return self.__dict__ == other.__dict__
    
    def __hash__(self):
        return hash(tuple(sorted(self.__dict__)))

class nodevisitor(object):
    def _visitnode(self, n, *args, **kwargs):
        k = n.kind
        self.visitnode(n)
        return getattr(self, 'visit%s' % k)(n, *args, **kwargs)

    def visit(self, n):
        k = n.kind
        if k == 'operator':
            self._visitnode(n, n.op)
        elif k == 'list':
            dochild = self._visitnode(n, n.parts)
            if dochild is None or dochild:
                for child in n.parts:
                    self.visit(child)
        elif k == 'reservedword':
            self._visitnode(n, n.word)
        elif k == 'pipe':
            self._visitnode(n, n.pipe)
        elif k == 'pipeline':
            dochild = self._visitnode(n, n.parts)
            if dochild is None or dochild:
                for child in n.parts:
                    self.visit(child)
        elif k == 'compound':
            dochild = self._visitnode(n, n.list, n.redirects)
            if dochild is None or dochild:
                for child in n.list:
                    self.visit(child)
                for child in n.redirects:
                    self.visit(child)
        elif k in ('if', 'for', 'while', 'until'):
            dochild = self._visitnode(n, n.parts)
            if dochild is None or dochild:
                for child in n.parts:
                    self.visit(child)
        elif k == 'command':
            dochild = self._visitnode(n, n.parts)
            if dochild is None or dochild:
                for child in n.parts:
                    self.visit(child)
        elif k == 'function':
            dochild = self._visitnode(n, n.name, n.body, n.parts)
            if dochild is None or dochild:
                for child in n.parts:
                    self.visit(child)
        elif k == 'redirect':
            dochild = self._visitnode(n, n.input, n.type, n.output, n.heredoc)
            if dochild is None or dochild:
                if isinstance(n.output, node):
                    self.visit(n.output)
                if n.heredoc:
                    self.visit(n.heredoc)
        elif k in ('word', 'assignment'):
            dochild = self._visitnode(n, n.word)
            if dochild is None or dochild:
                for child in n.parts:
                    self.visit(child)
        elif k in ('parameter', 'tilde', 'heredoc'):
            self._visitnode(n, n.value)
        elif k in ('commandsubstitution', 'processsubstitution'):
            dochild = self._visitnode(n, n.command)
            if dochild is None or dochild:
                self.visit(n.command)
        else:
            raise ValueError('unknown node kind %r' % k)
        self.visitnodeend(n)

    def visitnode(self, n):
        pass
    def visitnodeend(self, n):
        pass
    def visitoperator(self, n, op):
        pass
    def visitlist(self, n, parts):
        pass
    def visitpipe(self, n, pipe):
        pass
    def visitpipeline(self, n, parts):
        pass
    def visitcompound(self, n, list, redirects):
        pass
    def visitif(self, node, parts):
        pass
    def visitfor(self, node, parts):
        pass
    def visitwhile(self, node, parts):
        pass
    def visituntil(self, node, parts):
        pass
    def visitcommand(self, n, parts):
        pass
    def visitfunction(self, n, name, body, parts):
        pass
    def visitword(self, n, word):
        pass
    def visitassignment(self, n, word):
        pass
    def visitreservedword(self, n, word):
        pass
    def visitparameter(self, n, value):
        pass
    def visittilde(self, n, value):
        pass
    def visitredirect(self, n, input, type, output, heredoc):
        pass
    def visitheredoc(self, n, value):
        pass
    def visitprocesssubstitution(self, n, command):
        pass
    def visitcommandsubstitution(self, n, command):
        pass

def _dump(tree, indent='  '):
    def _format(n, level=0):
        if isinstance(n, node):
            d = dict(n.__dict__)
            kind = d.pop('kind')
            if kind == 'list' and level > 0:
                level = level + 1
            fields = []
            v = d.pop('s', None)
            if v:
                fields.append(('s', _format(v, level)))
            for k, v in sorted(d.items()):
                if not v or k == 'parts':
                    continue
                llevel = level
                if isinstance(v, node):
                    llevel += 1
                    fields.append((k, '\n' + (indent * llevel) + _format(v, llevel)))
                else:
                    fields.append((k, _format(v, level)))
            if kind == 'function':
                fields = [f for f in fields if f[0] not in ('name', 'body')]
            v = d.pop('parts', None)
            if v:
                fields.append(('parts', _format(v, level)))
            return ''.join([
                '%sNode' % kind.title(),
                '(',
                ', '.join(('%s=%s' % field for field in fields)),
                ')'])
        elif isinstance(n, list):
            lines = ['[']
            lines.extend((indent * (level + 1) + _format(x, level + 1) + ','
                         for x in n))
            if len(lines) > 1:
                lines.append(indent * (level) + ']')
            else:
                lines[-1] += ']'
            return '\n'.join(lines)
        return repr(n)

    if not isinstance(tree, node):
        raise TypeError('expected node, got %r' % tree.__class__.__name__)
    return _format(tree)

def findfirstkind(parts, kind):
    for i, node in enumerate(parts):
        if node.kind == kind:
            return i
    return -1

class posconverter(nodevisitor):
    def __init__(self, string):
        self.string = string

    def visitnode(self, node):
        assert hasattr(node, 'pos'), 'node %r is missing pos attr' % node
        start, end = node.__dict__.pop('pos')
        node.s = self.string[start:end]

class posshifter(nodevisitor):
    def __init__(self, count):
        self.count = count

    def visitnode(self, node):
        #assert node.pos[1] + base <= endlimit
        node.pos = (node.pos[0] + self.count, node.pos[1] + self.count)
