mirror of
				https://github.com/c64scene-ar/llvm-6502.git
				synced 2025-11-04 05:17:07 +00:00 
			
		
		
		
	Also, fix unit tests. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@81194 91177308-0d34-0410-b5e6-96231b3b80d8
		
			
				
	
	
		
			323 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import itertools
 | 
						|
 | 
						|
from ShCommands import Command, Pipeline
 | 
						|
 | 
						|
def tcl_preprocess(data):
 | 
						|
    # Tcl has a preprocessing step to replace escaped newlines.
 | 
						|
    i = data.find('\\\n')
 | 
						|
    if i == -1:
 | 
						|
        return data
 | 
						|
 | 
						|
    # Replace '\\\n' and subsequent whitespace by a single space.
 | 
						|
    n = len(data)
 | 
						|
    str = data[:i]
 | 
						|
    i += 2
 | 
						|
    while i < n and data[i] in ' \t':
 | 
						|
        i += 1
 | 
						|
    return str + ' ' + data[i:]
 | 
						|
 | 
						|
class TclLexer:
 | 
						|
    """TclLexer - Lex a string into "words", following the Tcl syntax."""
 | 
						|
 | 
						|
    def __init__(self, data):
 | 
						|
        self.data = tcl_preprocess(data)
 | 
						|
        self.pos = 0
 | 
						|
        self.end = len(self.data)
 | 
						|
 | 
						|
    def at_end(self):
 | 
						|
        return self.pos == self.end
 | 
						|
 | 
						|
    def eat(self):
 | 
						|
        c = self.data[self.pos]
 | 
						|
        self.pos += 1
 | 
						|
        return c
 | 
						|
 | 
						|
    def look(self):
 | 
						|
        return self.data[self.pos]
 | 
						|
 | 
						|
    def maybe_eat(self, c):
 | 
						|
        """
 | 
						|
        maybe_eat(c) - Consume the character c if it is the next character,
 | 
						|
        returning True if a character was consumed. """
 | 
						|
        if self.data[self.pos] == c:
 | 
						|
            self.pos += 1
 | 
						|
            return True
 | 
						|
        return False
 | 
						|
 | 
						|
    def escape(self, c):
 | 
						|
        if c == 'a':
 | 
						|
            return '\x07'
 | 
						|
        elif c == 'b':
 | 
						|
            return '\x08'
 | 
						|
        elif c == 'f':
 | 
						|
            return '\x0c'
 | 
						|
        elif c == 'n':
 | 
						|
            return '\n'
 | 
						|
        elif c == 'r':
 | 
						|
            return '\r'
 | 
						|
        elif c == 't':
 | 
						|
            return '\t'
 | 
						|
        elif c == 'v':
 | 
						|
            return '\x0b'
 | 
						|
        elif c in 'uxo':
 | 
						|
            raise ValueError,'Invalid quoted character %r' % c
 | 
						|
        else:
 | 
						|
            return c
 | 
						|
        
 | 
						|
    def lex_braced(self):
 | 
						|
        # Lex until whitespace or end of string, the opening brace has already
 | 
						|
        # been consumed.
 | 
						|
 | 
						|
        str = ''        
 | 
						|
        while 1:
 | 
						|
            if self.at_end():
 | 
						|
                raise ValueError,"Unterminated '{' quoted word"
 | 
						|
            
 | 
						|
            c = self.eat()
 | 
						|
            if c == '}':
 | 
						|
                break
 | 
						|
            elif c == '{':
 | 
						|
                str += '{' + self.lex_braced() + '}'
 | 
						|
            elif c == '\\' and self.look() in '{}':
 | 
						|
                str += self.eat()
 | 
						|
            else:
 | 
						|
                str += c
 | 
						|
 | 
						|
        return str
 | 
						|
 | 
						|
    def lex_quoted(self):
 | 
						|
        str = ''
 | 
						|
 | 
						|
        while 1:
 | 
						|
            if self.at_end():
 | 
						|
                raise ValueError,"Unterminated '\"' quoted word"
 | 
						|
            
 | 
						|
            c = self.eat()
 | 
						|
            if c == '"':
 | 
						|
                break
 | 
						|
            elif c == '\\':
 | 
						|
                if self.at_end():
 | 
						|
                    raise ValueError,'Missing quoted character'
 | 
						|
 | 
						|
                str += self.escape(self.eat())
 | 
						|
            else:
 | 
						|
                str += c
 | 
						|
 | 
						|
        return str
 | 
						|
 | 
						|
    def lex_unquoted(self, process_all=False):
 | 
						|
        # Lex until whitespace or end of string.
 | 
						|
        str = ''
 | 
						|
        while not self.at_end():
 | 
						|
            if not process_all:
 | 
						|
                if self.look().isspace() or self.look() == ';':
 | 
						|
                    break
 | 
						|
 | 
						|
            c = self.eat()
 | 
						|
            if c == '\\':
 | 
						|
                if self.at_end():
 | 
						|
                    raise ValueError,'Missing quoted character'
 | 
						|
 | 
						|
                str += self.escape(self.eat())
 | 
						|
            elif c == '[':
 | 
						|
                raise NotImplementedError, ('Command substitution is '
 | 
						|
                                            'not supported')
 | 
						|
            elif c == '$' and not self.at_end() and (self.look().isalpha() or
 | 
						|
                                                     self.look() == '{'):
 | 
						|
                raise NotImplementedError, ('Variable substitution is '
 | 
						|
                                            'not supported')
 | 
						|
            else:
 | 
						|
                str += c
 | 
						|
 | 
						|
        return str
 | 
						|
 | 
						|
    def lex_one_token(self):
 | 
						|
        if self.maybe_eat('"'):
 | 
						|
            return self.lex_quoted()
 | 
						|
        elif self.maybe_eat('{'):
 | 
						|
            # Check for argument substitution.
 | 
						|
            if not self.maybe_eat('*'):
 | 
						|
                return self.lex_braced()
 | 
						|
 | 
						|
            if not self.maybe_eat('}'):
 | 
						|
                    return '*' + self.lex_braced()
 | 
						|
                
 | 
						|
            if self.at_end() or self.look().isspace():
 | 
						|
                return '*'
 | 
						|
 | 
						|
            raise NotImplementedError, "Argument substitution is unsupported"
 | 
						|
        else:
 | 
						|
            return self.lex_unquoted()
 | 
						|
 | 
						|
    def lex(self):
 | 
						|
        while not self.at_end():
 | 
						|
            c = self.look()
 | 
						|
            if c in ' \t':
 | 
						|
                self.eat()
 | 
						|
            elif c in ';\n':
 | 
						|
                self.eat()
 | 
						|
                yield (';',)
 | 
						|
            else:
 | 
						|
                yield self.lex_one_token()
 | 
						|
 | 
						|
class TclExecCommand:
 | 
						|
    kRedirectPrefixes1 = ('<', '>')
 | 
						|
    kRedirectPrefixes2 = ('<@', '<<', '2>', '>&', '>>', '>@')
 | 
						|
    kRedirectPrefixes3 = ('2>@', '2>>', '>>&', '>&@')
 | 
						|
    kRedirectPrefixes4 = ('2>@1',)
 | 
						|
 | 
						|
    def __init__(self, args):
 | 
						|
        self.args = iter(args)
 | 
						|
 | 
						|
    def lex(self):
 | 
						|
        try:
 | 
						|
            return self.args.next()
 | 
						|
        except StopIteration:
 | 
						|
            return None
 | 
						|
 | 
						|
    def look(self):
 | 
						|
        next = self.lex()
 | 
						|
        if next is not None:
 | 
						|
            self.args = itertools.chain([next], self.args)
 | 
						|
        return next
 | 
						|
 | 
						|
    def parse_redirect(self, tok, length):
 | 
						|
        if len(tok) == length:
 | 
						|
            arg = self.lex()
 | 
						|
            if arg is None:
 | 
						|
                raise ValueError,'Missing argument to %r redirection' % tok
 | 
						|
        else:
 | 
						|
            tok,arg = tok[:length],tok[length:]
 | 
						|
 | 
						|
        if tok[0] == '2':
 | 
						|
            op = (tok[1:],2)
 | 
						|
        else:
 | 
						|
            op = (tok,)
 | 
						|
        return (op, arg)
 | 
						|
 | 
						|
    def parse_pipeline(self):
 | 
						|
        if self.look() is None:
 | 
						|
            raise ValueError,"Expected at least one argument to exec"
 | 
						|
 | 
						|
        commands = [Command([],[])]
 | 
						|
        while 1:
 | 
						|
            arg = self.lex()
 | 
						|
            if arg is None:
 | 
						|
                break
 | 
						|
            elif arg == '|':
 | 
						|
                commands.append(Command([],[]))
 | 
						|
            elif arg == '|&':
 | 
						|
                # Write this as a redirect of stderr; it must come first because
 | 
						|
                # stdout may have already been redirected.
 | 
						|
                commands[-1].redirects.insert(0, (('>&',2),'1'))
 | 
						|
                commands.append(Command([],[]))
 | 
						|
            elif arg[:4] in TclExecCommand.kRedirectPrefixes4:
 | 
						|
                commands[-1].redirects.append(self.parse_redirect(arg, 4))
 | 
						|
            elif arg[:3] in TclExecCommand.kRedirectPrefixes3:
 | 
						|
                commands[-1].redirects.append(self.parse_redirect(arg, 3))
 | 
						|
            elif arg[:2] in TclExecCommand.kRedirectPrefixes2:
 | 
						|
                commands[-1].redirects.append(self.parse_redirect(arg, 2))
 | 
						|
            elif arg[:1] in TclExecCommand.kRedirectPrefixes1:
 | 
						|
                commands[-1].redirects.append(self.parse_redirect(arg, 1))
 | 
						|
            else:
 | 
						|
                commands[-1].args.append(arg)
 | 
						|
 | 
						|
        return Pipeline(commands, False, pipe_err=True)
 | 
						|
 | 
						|
    def parse(self):
 | 
						|
        ignoreStderr = False
 | 
						|
        keepNewline = False
 | 
						|
 | 
						|
        # Parse arguments.
 | 
						|
        while 1:
 | 
						|
            next = self.look()
 | 
						|
            if not isinstance(next, str) or next[0] != '-':
 | 
						|
                break
 | 
						|
 | 
						|
            if next == '--':
 | 
						|
                self.lex()
 | 
						|
                break
 | 
						|
            elif next == '-ignorestderr':
 | 
						|
                ignoreStderr = True
 | 
						|
            elif next == '-keepnewline':
 | 
						|
                keepNewline = True
 | 
						|
            else:
 | 
						|
                raise ValueError,"Invalid exec argument %r" % next
 | 
						|
 | 
						|
        return (ignoreStderr, keepNewline, self.parse_pipeline())
 | 
						|
 | 
						|
###
 | 
						|
 | 
						|
import unittest
 | 
						|
 | 
						|
class TestTclLexer(unittest.TestCase):
 | 
						|
    def lex(self, str, *args, **kwargs):
 | 
						|
        return list(TclLexer(str, *args, **kwargs).lex())
 | 
						|
 | 
						|
    def test_preprocess(self):
 | 
						|
        self.assertEqual(tcl_preprocess('a b'), 'a b')
 | 
						|
        self.assertEqual(tcl_preprocess('a\\\nb c'), 'a b c')
 | 
						|
 | 
						|
    def test_unquoted(self):
 | 
						|
        self.assertEqual(self.lex('a b c'),
 | 
						|
                         ['a', 'b', 'c'])
 | 
						|
        self.assertEqual(self.lex(r'a\nb\tc\ '),
 | 
						|
                         ['a\nb\tc '])
 | 
						|
        self.assertEqual(self.lex(r'a \\\$b c $\\'),
 | 
						|
                         ['a', r'\$b', 'c', '$\\'])
 | 
						|
 | 
						|
    def test_braced(self):
 | 
						|
        self.assertEqual(self.lex('a {b c} {}'),
 | 
						|
                         ['a', 'b c', ''])
 | 
						|
        self.assertEqual(self.lex(r'a {b {c\n}}'),
 | 
						|
                         ['a', 'b {c\\n}'])
 | 
						|
        self.assertEqual(self.lex(r'a {b\{}'),
 | 
						|
                         ['a', 'b{'])
 | 
						|
        self.assertEqual(self.lex(r'{*}'), ['*'])
 | 
						|
        self.assertEqual(self.lex(r'{*} a'), ['*', 'a'])
 | 
						|
        self.assertEqual(self.lex(r'{*} a'), ['*', 'a'])
 | 
						|
        self.assertEqual(self.lex('{a\\\n   b}'),
 | 
						|
                         ['a b'])
 | 
						|
 | 
						|
    def test_quoted(self):
 | 
						|
        self.assertEqual(self.lex('a "b c"'),
 | 
						|
                         ['a', 'b c'])
 | 
						|
 | 
						|
    def test_terminators(self):
 | 
						|
        self.assertEqual(self.lex('a\nb'),
 | 
						|
                         ['a', (';',), 'b'])
 | 
						|
        self.assertEqual(self.lex('a;b'),
 | 
						|
                         ['a', (';',), 'b'])
 | 
						|
        self.assertEqual(self.lex('a   ;   b'),
 | 
						|
                         ['a', (';',), 'b'])
 | 
						|
 | 
						|
class TestTclExecCommand(unittest.TestCase):
 | 
						|
    def parse(self, str):
 | 
						|
        return TclExecCommand(list(TclLexer(str).lex())).parse()
 | 
						|
 | 
						|
    def test_basic(self):
 | 
						|
        self.assertEqual(self.parse('echo hello'),
 | 
						|
                         (False, False,
 | 
						|
                          Pipeline([Command(['echo', 'hello'], [])],
 | 
						|
                                   False, True)))
 | 
						|
        self.assertEqual(self.parse('echo hello | grep hello'),
 | 
						|
                         (False, False,
 | 
						|
                          Pipeline([Command(['echo', 'hello'], []),
 | 
						|
                                    Command(['grep', 'hello'], [])],
 | 
						|
                                   False, True)))
 | 
						|
 | 
						|
    def test_redirect(self):
 | 
						|
        self.assertEqual(self.parse('echo hello > a >b >>c 2> d |& e'),
 | 
						|
                         (False, False,
 | 
						|
                          Pipeline([Command(['echo', 'hello'],
 | 
						|
                                            [(('>&',2),'1'),
 | 
						|
                                             (('>',),'a'),
 | 
						|
                                             (('>',),'b'),
 | 
						|
                                             (('>>',),'c'),
 | 
						|
                                             (('>',2),'d')]),
 | 
						|
                                    Command(['e'], [])],
 | 
						|
                                   False, True)))
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    unittest.main()
 |