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
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
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