Permalink
Browse files

Initial implementation of alias.

This isn't totally correct since multiple alias expansion aren't
handled.  But we go from 22 test cases failing to 6!

The alias expansion is detected in CommandParser.ParseCommand(), and
then we push a buffer onto the Lexer at that point.  This seems to be
the correct place to do it.

Addresses issue #160.
  • Loading branch information...
Andy Chu
Andy Chu committed Sep 4, 2018
1 parent d16aabf commit 4cdae03a882334d924e6de14300444bd85e1513d
Showing with 60 additions and 14 deletions.
  1. +24 −4 core/lexer.py
  2. +35 −9 osh/cmd_parse.py
  3. +1 −1 test/spec.sh
View
@@ -143,9 +143,11 @@ def __init__(self, line_lexer, line_reader):
self.line_lexer = line_lexer
self.line_reader = line_reader
self.was_line_cont = False # last token was line continuation?
# TODO: unused?
self.line_id = -1 # Invalid one
self.translation_stack = []
self.buffers = []
def ResetInputObjects(self):
self.line_lexer.Reset('', -1, 0)
@@ -188,17 +190,18 @@ def PushHint(self, old_id, new_id):
"""
self.translation_stack.append((old_id, new_id))
def _Read(self, lex_mode):
def _ReadNormalInput(self, lex_mode):
"""Read from the normal line buffer, not an alias."""
t = self.line_lexer.Read(lex_mode)
if t.id == Id.Eol_Tok: # hit \0
if t.id == Id.Eol_Tok: # hit \0, read a new line
line_id, line, line_pos = self.line_reader.GetLine()
if line is None: # no more lines
span_id = self.line_lexer.GetSpanIdForEof()
t = ast.token(Id.Eof_Real, '', span_id)
return t
self.line_lexer.Reset(line, line_id, line_pos)
self.line_lexer.Reset(line, line_id, line_pos) # fill with a new line
t = self.line_lexer.Read(lex_mode)
# e.g. translate ) or ` into EOF
@@ -214,7 +217,20 @@ def _Read(self, lex_mode):
# TODO: Collapse newlines here instead of in the WordParser?
def Read(self, lex_mode):
while True:
t = self._Read(lex_mode)
# Read from alias buffers first
if self.buffers:
if 0:
log('Reading from %r at %d',
self.buffers[-1].line, self.buffers[-1].line_pos)
t = self.buffers[-1].Read(lex_mode)
if t.id == Id.Eol_Tok:
self.buffers.pop()
continue # read from next buffer or from the original line_Lexer
# TODO: Translate tokens here?
return t
t = self._ReadNormalInput(lex_mode)
self.was_line_cont = (t.id == Id.Ignored_LineCont)
@@ -225,3 +241,7 @@ def Read(self, lex_mode):
#log('Read() Returning %s', t)
return t
def PushAliasBuffer(self, line_lexer):
"""Read from this stack of buffer before resuming normal input."""
self.buffers.append(line_lexer)
View
@@ -13,6 +13,7 @@
from asdl import const
from core import braces
from core import lexer # alias needs LineLexer
from core import reader
from core import word
from core import util
@@ -120,9 +121,9 @@ class CommandParser(object):
lexer: for lookahead in function def, PushHint of ()
line_reader: for here doc
"""
def __init__(self, w_parser, lexer, line_reader, arena, aliases=None):
def __init__(self, w_parser, lexer_, line_reader, arena, aliases=None):
self.w_parser = w_parser # for normal parsing
self.lexer = lexer # for fast lookahead to (, for function defs
self.lexer = lexer_ # for fast lookahead to (, for function defs
self.line_reader = line_reader # for here docs
self.arena = arena # for adding here doc spans
if aliases is None:
@@ -1164,12 +1165,35 @@ def ParseDParen(self):
assert anode is not None
node = ast.DParen(anode)
node.spids.append(left_spid)
node.spids.append(right_spid)
return node
def ParseCommand(self):
def _DoAlias(self, alias_exp):
line_lexer = self.lexer.line_lexer
log('%r %d %r', line_lexer.line, line_lexer.line_pos,
line_lexer.line[line_lexer.line_pos:])
if alias_exp.endswith(' '):
do_exp = True
else:
# alias e='echo [ ' is the same expansion as
# alias e='echo ['
# The trailing space indicates something else.
alias_exp += ' '
# NOTE: alias_exp might not end with a newline, but that's OK because it's
# NUL-terminated.
buf = lexer.LineLexer(match.MATCHER, alias_exp, self.arena)
self.lexer.PushAliasBuffer(buf)
# Need to prime it I guess. This can get more than one word out.
self._Next()
# Call it recursively
return self.ParseCommand(do_alias=False)
def ParseCommand(self, do_alias=True):
"""
command : simple_command
| compound_command io_redirect*
@@ -1201,12 +1225,14 @@ def ParseCommand(self):
# self.w_parser.PushAliasBuffer() -- this means the lexer reads from it?
# TODO: To this in a loop
ok, word_str, quoted = word.StaticEval(self.cur_word)
if ok and not quoted:
alias_exp = self.aliases.get(word_str)
#log('AT PARSE TIME %s', self.aliases)
if alias_exp is not None:
log('expand %s -> %s', word_str, alias_exp)
if do_alias:
ok, word_str, quoted = word.StaticEval(self.cur_word)
if ok and not quoted:
alias_exp = self.aliases.get(word_str)
#log('AT PARSE TIME %s', self.aliases)
if alias_exp is not None:
log('expand %s -> %r', word_str, alias_exp)
return self._DoAlias(alias_exp)
# NOTE: I added this to fix cases in parse-errors.test.sh, but it doesn't
# work because Lit_RBrace is in END_LIST below.
View
@@ -232,7 +232,7 @@ blog-other1() {
}
alias() {
sh-spec spec/alias.test.sh --osh-failures-allowed 22 \
sh-spec spec/alias.test.sh --osh-failures-allowed 6 \
${REF_SHELLS[@]} $ZSH $OSH_LIST "$@"
}

0 comments on commit 4cdae03

Please sign in to comment.