Beispiel #1
0
 def LastColForWord(w):
     span_id = word.RightMostSpanForWord(w)
     span = arena.GetLineSpan(span_id)
     debug_f.log('span %s', span)
     debug_f.log('span col %d length %d', span.col, span.length)
     return span.col + span.length
Beispiel #2
0
  def _MaybeExpandAliases(self, words, cur_aliases):
    """Try to expand aliases.

    Our implementation of alias has two design choices:
    - Where to insert it in parsing.  We do it at the end of ParseSimpleCommand.
    - What grammar rule to parse the expanded alias buffer with.  In our case
      it's ParseCommand().

    This doesn't quite match what other shells do, but I can't figure out a
    better places.

    Most test cases pass, except for ones like:

    alias LBRACE='{'
    LBRACE echo one; echo two; }

    alias MULTILINE='echo 1
    echo 2
    echo 3'
    MULTILINE

    NOTE: dash handles aliases in a totally diferrent way.  It has a global
    variable checkkwd in parser.c.  It assigns it all over the grammar, like
    this:

    checkkwd = CHKNL | CHKKWD | CHKALIAS;

    The readtoken() function checks (checkkwd & CHKALIAS) and then calls
    lookupalias().  This seems to provide a consistent behavior among shells,
    but it's less modular and testable.

    Bash also uses a global 'parser_state & PST_ALEXPNEXT'.

    Returns:
      A command node if any aliases were expanded, or None otherwise.
    """
    # The last char that we might parse.
    right_spid = word.RightMostSpanForWord(words[-1])
    first_word_str = None  # for error message

    expanded = []
    i = 0
    n = len(words)

    while i < n:
      w = words[i]

      ok, word_str, quoted = word.StaticEval(w)
      if not ok or quoted:
        break

      alias_exp = self.aliases.get(word_str)
      if alias_exp is None:
        break

      # Prevent infinite loops.  This is subtle: we want to prevent infinite
      # expansion of alias echo='echo x'.  But we don't want to prevent
      # expansion of the second word in 'echo echo', so we add 'i' to
      # "cur_aliases".
      if (word_str, i) in cur_aliases:
        break

      if i == 0:
        first_word_str = word_str  # for error message

      #log('%r -> %r', word_str, alias_exp)
      cur_aliases.append((word_str, i))
      expanded.append(alias_exp)
      i += 1

      if not alias_exp.endswith(' '):
        # alias e='echo [ ' is the same expansion as
        # alias e='echo ['
        # The trailing space indicates whether we should continue to expand
        # aliases; it's not part of it.
        expanded.append(' ')
        break  # No more expansions

    if not expanded:  # No expansions; caller does parsing.
      return None

    # We got some expansion.  Now copy the rest of the words.

    # We need each NON-REDIRECT word separately!  For example:
    # $ echo one >out two
    # dash/mksh/zsh go beyond the first redirect!
    while i < n:
      w = words[i]
      left_spid = word.LeftMostSpanForWord(w)
      right_spid = word.RightMostSpanForWord(w)

      # Adapted from tools/osh2oil.py Cursor.PrintUntil
      for span_id in xrange(left_spid, right_spid + 1):
        span = self.arena.GetLineSpan(span_id)
        line = self.arena.GetLine(span.line_id)
        piece = line[span.col : span.col + span.length]
        expanded.append(piece)

      expanded.append(' ')  # Put space back between words.
      i += 1

    code_str = ''.join(expanded)
    lines = code_str.splitlines(True)  # Keep newlines

    line_info = []
    # TODO: Add location information
    self.arena.PushSource(
        '<expansion of alias %r at line %d of %s>' %
        (first_word_str, -1, 'TODO'))
    try:
      for i, line in enumerate(lines):
        line_id = self.arena.AddLine(line, i+1)
        line_info.append((line_id, line, 0))
    finally:
      self.arena.PopSource()

    line_reader = reader.VirtualLineReader(line_info, self.arena)
    cp = self.parse_ctx.MakeOshParser(line_reader)

    try:
      node = cp.ParseCommand(cur_aliases=cur_aliases)
    except util.ParseError as e:
      # Failure to parse alias expansion is a fatal error
      # We don't need more handling here/
      raise

    if 0:
      log('AFTER expansion:')
      from osh import ast_lib
      ast_lib.PrettyPrint(node)
    return node
Beispiel #3
0
    def Eval(self, line):
        """Returns an expanded line."""

        if not self.readline_mod:
            return line

        tokens = list(HISTORY_LEXER.Tokens(line))
        # Common case: no history expansion.
        if all(id_ == Id.History_Other for (id_, _) in tokens):
            return line

        history_len = self.readline_mod.get_current_history_length()
        if history_len <= 0:  # no commands to expand
            return line

        self.debug_f.log('history length = %d', history_len)

        parts = []
        for id_, val in tokens:
            if id_ == Id.History_Other:
                out = val

            elif id_ == Id.History_Op:
                # TODO: the current line was ALREADY entered in the history, so we have
                # to subtact 1.  We should add it AFTER expanion, but Python's binding
                # might not allow that.  We probably need to fork it.
                prev = self.readline_mod.get_history_item(history_len - 1)

                ch = val[1]
                if ch == '!':
                    out = prev
                else:
                    self.parse_ctx.trail.Clear()  # not strictyl necessary?
                    line_reader = StringLineReader(prev, self.parse_ctx.arena)
                    c_parser = self.parse_ctx.MakeOshParser(line_reader)
                    try:
                        c_parser.ParseLogicalLine()
                    except util.ParseError as e:
                        #from core import ui
                        #ui.PrettyPrintError(e, self.parse_ctx.arena)

                        # Invalid command in history.  TODO: We should never enter these.
                        self.debug_f.log(
                            "Couldn't parse historical command %r: %s", prev,
                            e)

                    # NOTE: We're using the trail rather than the return value of
                    # ParseLogicalLine because it handles cases like
                    # $ for i in 1 2 3; do sleep ${i}; done
                    # $ echo !$
                    # which should expand to 'echo ${i}'

                    words = self.parse_ctx.trail.words
                    #self.debug_f.log('TRAIL WORDS: %s', words)

                    if ch == '^':
                        try:
                            w = words[1]
                        except IndexError:
                            raise util.HistoryError("No first word in %r",
                                                    prev)
                        spid1 = word.LeftMostSpanForWord(w)
                        spid2 = word.RightMostSpanForWord(w)

                    elif ch == '$':
                        try:
                            w = words[-1]
                        except IndexError:
                            raise util.HistoryError("No last word in %r", prev)

                        spid1 = word.LeftMostSpanForWord(w)
                        spid2 = word.RightMostSpanForWord(w)

                    elif ch == '*':
                        try:
                            w1 = words[1]
                            w2 = words[-1]
                        except IndexError:
                            raise util.HistoryError(
                                "Couldn't find words in %r", prev)

                        spid1 = word.LeftMostSpanForWord(w1)
                        spid2 = word.RightMostSpanForWord(w2)

                    else:
                        raise AssertionError(ch)

                    arena = self.parse_ctx.arena
                    span1 = arena.GetLineSpan(spid1)
                    span2 = arena.GetLineSpan(spid2)

                    begin = span1.col
                    end = span2.col + span2.length

                    out = prev[begin:end]

            elif id_ == Id.History_Num:
                index = int(
                    val[1:])  # regex ensures this.  Maybe have - on the front.
                if index < 0:
                    num = history_len + index
                else:
                    num = index

                out = self.readline_mod.get_history_item(num)
                if out is None:  # out of range
                    raise util.HistoryError('%s: not found', val)

            elif id_ == Id.History_Search:
                # Search backward
                prefix = None
                substring = None
                if val[1] == '?':
                    substring = val[2:]
                else:
                    prefix = val[1:]

                out = None
                for i in xrange(history_len, 1, -1):
                    cmd = self.readline_mod.get_history_item(i)
                    if prefix and cmd.startswith(prefix):
                        out = cmd
                    if substring and substring in cmd:
                        out = cmd
                    if out is not None:
                        break

                if out is None:
                    raise util.HistoryError('%r found no results', val)

            else:
                raise AssertionError(id_)

            parts.append(out)

        line = ''.join(parts)
        # show what we expanded to
        sys.stdout.write('! %s' % line)
        return line