211 lines
6.9 KiB
Python
211 lines
6.9 KiB
Python
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)
|