Exemple #1
0
    def __init__(self, pwd):
        self._opt = set()  #Shell options

        self._functions = {}
        self._env = {'?': '0', '#': '0'}
        self._exported = set(['HOME', 'IFS', 'PATH'])

        # Set environment vars with side-effects
        self._ifs_ws = None  # Set of IFS whitespace characters
        self._ifs_re = None  # Regular expression used to split between words using IFS classes
        self['IFS'] = ''.join(_IFS_WHITESPACES)  #Default environment values
        self['PWD'] = pwd
        self.traps = Traps()
Exemple #2
0
    def __init__(self, pwd):
        self._opt = set()  # Shell options

        self._functions = {}
        self._env = {"?": "0", "#": "0"}
        self._exported = set(["HOME", "IFS", "PATH"])

        # Set environment vars with side-effects
        self._ifs_ws = None  # Set of IFS whitespace characters
        self._ifs_re = None  # Regular expression used to split between words using IFS classes
        self["IFS"] = "".join(_IFS_WHITESPACES)  # Default environment values
        self["PWD"] = pwd
        self.traps = Traps()
Exemple #3
0
 def __init__(self, pwd):
     self._opt = set()       #Shell options
     
     self._functions = {}        
     self._env = {'?': '0', '#': '0'}
     self._exported = set([
         'HOME', 'IFS', 'PATH'
     ])
     
     # Set environment vars with side-effects
     self._ifs_ws = None     # Set of IFS whitespace characters
     self._ifs_re = None     # Regular expression used to split between words using IFS classes
     self['IFS'] = ''.join(_IFS_WHITESPACES) #Default environment values
     self['PWD'] = pwd
     self.traps = Traps()
Exemple #4
0
    def clone(self, subshell=False):
        env = Environment(self["PWD"])
        env._opt = set(self._opt)
        for k, v in self.get_variables().iteritems():
            if k in self._exported:
                env.export(k, v)
            elif subshell:
                env[k] = v

        if subshell:
            env._functions = dict(self._functions)

        return env
Exemple #5
0
    def clone(self, subshell=False):
        env = Environment(self['PWD'])
        env._opt = set(self._opt)
        for k, v in self.get_variables().iteritems():
            if k in self._exported:
                env.export(k, v)
            elif subshell:
                env[k] = v

        if subshell:
            env._functions = dict(self._functions)

        return env
Exemple #6
0
    def _update_ifs(self, value):
        """Update the split_fields related variables when IFS character set is
        changed.
        """
        # TODO: handle NULL IFS

        # Separate characters in whitespace and non-whitespace
        chars = set(value)
        ws = [c for c in chars if c in _IFS_WHITESPACES]
        nws = [c for c in chars if c not in _IFS_WHITESPACES]

        # Keep whitespaces in a string for left and right stripping
        self._ifs_ws = "".join(ws)

        # Build a regexp to split fields
        trailing = "[" + "".join([re.escape(c) for c in ws]) + "]"
        if nws:
            # First, the single non-whitespace occurence.
            nws = "[" + "".join([re.escape(c) for c in nws]) + "]"
            nws = "(?:" + trailing + "*" + nws + trailing + "*" + "|" + trailing + "+)"
        else:
            # Then mix all parts with quantifiers
            nws = trailing + "+"
        self._ifs_re = re.compile(nws)
Exemple #7
0
    def _update_ifs(self, value):
        """Update the split_fields related variables when IFS character set is
        changed.
        """
        # TODO: handle NULL IFS

        # Separate characters in whitespace and non-whitespace
        chars = set(value)
        ws = [c for c in chars if c in _IFS_WHITESPACES]
        nws = [c for c in chars if c not in _IFS_WHITESPACES]

        # Keep whitespaces in a string for left and right stripping
        self._ifs_ws = ''.join(ws)

        # Build a regexp to split fields
        trailing = '[' + ''.join([re.escape(c) for c in ws]) + ']'
        if nws:
            # First, the single non-whitespace occurence.
            nws = '[' + ''.join([re.escape(c) for c in nws]) + ']'
            nws = '(?:' + trailing + '*' + nws + trailing + '*' + '|' + trailing + '+)'
        else:
            # Then mix all parts with quantifiers
            nws = trailing + '+'
        self._ifs_re = re.compile(nws)
Exemple #8
0
#
# Copyright 2007 Patrick Mezard
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

# TODO:
# - review all "char in 'abc'" snippets: the empty string can be matched
# - test line continuations within quoted/expansion strings
# - eof is buggy wrt sublexers
# - the lexer cannot really work in pull mode as it would be required to run
# PLY in pull mode. It was designed to work incrementally and it would not be
# that hard to enable pull mode.
import re
try:
    s = set()
    del s
except NameError:
    from Set import Set as set

from ply import lex
from bb.pysh.sherrors import *

class NeedMore(Exception):
    pass

def is_blank(c):
    return c in (' ', '\t')
    
_RE_DIGITS = re.compile(r'^\d+$')
class WordLexer:
    """WordLexer parse quoted or expansion expressions and return an expression
    tree. The input string can be any well formed sequence beginning with quoting
    or expansion character. Embedded expressions are handled recursively. The
    resulting tree is made of lists and strings. Lists represent quoted or
    expansion expressions. Each list first element is the opening separator,
    the last one the closing separator. In-between can be any number of strings
    or lists for sub-expressions. Non quoted/expansion expression can written as
    strings or as lists with empty strings as starting and ending delimiters.
    """

    NAME_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
    NAME_CHARSET = dict(zip(NAME_CHARSET, NAME_CHARSET))

    SPECIAL_CHARSET = '@*#?-$!0'

    #Characters which can be escaped depends on the current delimiters
    ESCAPABLE = {
        '`': set(['$', '\\', '`']),
        '"': set(['$', '\\', '`', '"']),
        "'": set(),
    }

    def __init__(self, heredoc=False):
        # _buffer is the unprocessed input characters buffer
        self._buffer = []
        # _stack is empty or contains a quoted list being processed
        # (this is the DFS path to the quoted expression being evaluated).
        self._stack = []
        self._escapable = None
        # True when parsing unquoted here documents
        self._heredoc = heredoc

    def add(self, data, eof=False):
        """Feed the lexer with more data. If the quoted expression can be
        delimited, return a tuple (expr, remaining) containing the expression
        tree and the unconsumed data.
        Otherwise, raise NeedMore.
        """
        self._buffer += list(data)
        self._parse(eof)

        result = self._stack[0]
        remaining = ''.join(self._buffer)
        self._stack = []
        self._buffer = []
        return result, remaining

    def _is_escapable(self, c, delim=None):
        if delim is None:
            if self._heredoc:
                # Backslashes works as if they were double quoted in unquoted
                # here-documents
                delim = '"'
            else:
                if len(self._stack) <= 1:
                    return True
                delim = self._stack[-2][0]

        escapables = self.ESCAPABLE.get(delim, None)
        return escapables is None or c in escapables

    def _parse_squote(self, buf, result, eof):
        if not buf:
            raise NeedMore()
        try:
            pos = buf.index("'")
        except ValueError:
            raise NeedMore()
        result[-1] += ''.join(buf[:pos])
        result += ["'"]
        return pos + 1, True

    def _parse_bquote(self, buf, result, eof):
        if not buf:
            raise NeedMore()

        if buf[0] == '\n':
            #Remove line continuations
            result[:] = ['', '', '']
        elif self._is_escapable(buf[0]):
            result[-1] += buf[0]
            result += ['']
        else:
            #Keep as such
            result[:] = ['', '\\' + buf[0], '']

        return 1, True

    def _parse_dquote(self, buf, result, eof):
        if not buf:
            raise NeedMore()
        pos, sep = find_chars(buf, '$\\`"')
        if pos == -1:
            raise NeedMore()

        result[-1] += ''.join(buf[:pos])
        if sep == '"':
            result += ['"']
            return pos + 1, True
        else:
            #Keep everything until the separator and defer processing
            return pos, False

    def _parse_command(self, buf, result, eof):
        if not buf:
            raise NeedMore()

        chars = '$\\`"\''
        if result[0] == '$(':
            chars += ')'
        pos, sep = find_chars(buf, chars)
        if pos == -1:
            raise NeedMore()

        result[-1] += ''.join(buf[:pos])
        if (result[0] == '$(' and sep == ')') or (result[0] == '`'
                                                  and sep == '`'):
            result += [sep]
            return pos + 1, True
        else:
            return pos, False

    def _parse_parameter(self, buf, result, eof):
        if not buf:
            raise NeedMore()

        pos, sep = find_chars(buf, '$\\`"\'}')
        if pos == -1:
            raise NeedMore()

        result[-1] += ''.join(buf[:pos])
        if sep == '}':
            result += [sep]
            return pos + 1, True
        else:
            return pos, False

    def _parse_dollar(self, buf, result, eof):
        sep = result[0]
        if sep == '$':
            if not buf:
                #TODO: handle empty $
                raise NeedMore()
            if buf[0] == '(':
                if len(buf) == 1:
                    raise NeedMore()

                if buf[1] == '(':
                    result[0] = '$(('
                    buf[:2] = []
                else:
                    result[0] = '$('
                    buf[:1] = []

            elif buf[0] == '{':
                result[0] = '${'
                buf[:1] = []
            else:
                if buf[0] in self.SPECIAL_CHARSET:
                    result[-1] = buf[0]
                    read = 1
                else:
                    for read, c in enumerate(buf):
                        if c not in self.NAME_CHARSET:
                            break
                    else:
                        if not eof:
                            raise NeedMore()
                        read += 1

                    result[-1] += ''.join(buf[0:read])

                if not result[-1]:
                    result[:] = ['', result[0], '']
                else:
                    result += ['']
                return read, True

        sep = result[0]
        if sep == '$(':
            parsefunc = self._parse_command
        elif sep == '${':
            parsefunc = self._parse_parameter
        else:
            raise NotImplementedError(sep)

        pos, closed = parsefunc(buf, result, eof)
        return pos, closed

    def _parse(self, eof):
        buf = self._buffer
        stack = self._stack
        recurse = False

        while 1:
            if not stack or recurse:
                if not buf:
                    raise NeedMore()
                if buf[0] not in ('"\\`$\''):
                    raise ShellSyntaxError('Invalid quoted string sequence')
                stack.append([buf[0], ''])
                buf[:1] = []
                recurse = False

            result = stack[-1]
            if result[0] == "'":
                parsefunc = self._parse_squote
            elif result[0] == '\\':
                parsefunc = self._parse_bquote
            elif result[0] == '"':
                parsefunc = self._parse_dquote
            elif result[0] == '`':
                parsefunc = self._parse_command
            elif result[0][0] == '$':
                parsefunc = self._parse_dollar
            else:
                raise NotImplementedError()

            read, closed = parsefunc(buf, result, eof)

            buf[:read] = []
            if closed:
                if len(stack) > 1:
                    #Merge in parent expression
                    parsed = stack.pop()
                    stack[-1] += [parsed]
                    stack[-1] += ['']
                else:
                    break
            else:
                recurse = True
#
# Copyright 2007 Patrick Mezard
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

# TODO:
# - review all "char in 'abc'" snippets: the empty string can be matched
# - test line continuations within quoted/expansion strings
# - eof is buggy wrt sublexers
# - the lexer cannot really work in pull mode as it would be required to run
# PLY in pull mode. It was designed to work incrementally and it would not be
# that hard to enable pull mode.
import re
try:
    s = set()
    del s
except NameError:
    from Set import Set as set

from ply import lex
from sherrors import *


class NeedMore(Exception):
    pass


def is_blank(c):
    return c in (' ', '\t')