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()
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()
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()
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
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
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)
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)
# # 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')