Permalink
Browse files

Basic implementation of alias and unalias hooked up.

Also: added spec tests for alias semantics, which are quite subtle.

- Still need to do the actual expansion in the parser.
- Still need spec tests for the usage of the builtins.
  • Loading branch information...
Andy Chu
Andy Chu committed Aug 31, 2018
1 parent 86888cf commit 46640afddc74af6b803c766d1926f4d115bab5f4
Showing with 164 additions and 38 deletions.
  1. +5 −4 bin/oil.py
  2. +48 −0 core/builtin.py
  3. +9 −4 core/cmd_exec.py
  4. +2 −1 core/cmd_exec_test.py
  5. +1 −1 core/runtime.asdl
  6. +21 −5 osh/cmd_parse.py
  7. +3 −2 osh/parse_lib.py
  8. +74 −20 spec/alias.test.sh
  9. +1 −1 test/spec.sh
View
@@ -229,12 +229,13 @@ def OshMain(argv0, argv, login_shell):
# TODO: NullLookup?
comp_lookup = None
fd_state = process.FdState()
exec_opts = state.ExecOpts(mem)
builtin.SetExecOpts(exec_opts, opts.opt_changes)
aliases = {} # feedback between runtime and parser
fd_state = process.FdState()
ex = cmd_exec.Executor(mem, fd_state, status_lines, funcs, readline,
completion, comp_lookup, exec_opts, arena)
completion, comp_lookup, exec_opts, arena, aliases)
# NOTE: The rc file can contain both commands and functions... ideally we
# would only want to save nodes/lines for the functions.
@@ -243,7 +244,7 @@ def OshMain(argv0, argv, login_shell):
arena.PushSource(rc_path)
with open(rc_path) as f:
rc_line_reader = reader.FileLineReader(f, arena)
_, rc_c_parser = parse_lib.MakeParser(rc_line_reader, arena)
_, rc_c_parser = parse_lib.MakeParser(rc_line_reader, arena, aliases)
try:
rc_node = rc_c_parser.ParseWholeFile()
if not rc_node:
@@ -291,7 +292,7 @@ def OshMain(argv0, argv, login_shell):
# TODO: assert arena.NumSourcePaths() == 1
# TODO: .rc file needs its own arena.
w_parser, c_parser = parse_lib.MakeParser(line_reader, arena)
w_parser, c_parser = parse_lib.MakeParser(line_reader, arena, aliases)
if exec_opts.interactive:
# NOTE: We're using a different evaluator here. The completion system can
View
@@ -109,6 +109,9 @@
"declare": builtin_e.DECLARE,
"typeset": builtin_e.TYPESET,
"alias": builtin_e.ALIAS,
"unalias": builtin_e.UNALIAS,
"help": builtin_e.HELP,
"debug-line": builtin_e.DEBUG_LINE,
}
@@ -983,6 +986,51 @@ def DeclareTypeset(argv, mem, funcs):
return status
ALIAS_SPEC = _Register('alias')
# TODO: Test out all the details here
def Alias(argv, aliases):
if not argv:
for name, alias_exp in aliases.iteritems():
# TODO: Print this better
print('%s %s' % (name, alias_exp))
return 0
for arg in argv:
parts = arg.split('=', 1)
log(' p %s', parts)
if len(parts) == 1:
name = parts[0]
alias_exp = aliases.get(name)
if alias_exp is None:
print('alias %r is not defined' % name) # TODO: error?
else:
print('%s %s' % (name, alias_exp))
else:
name, alias_exp = parts
aliases[name] = alias_exp
print(argv)
print(aliases)
return 0
UNALIAS_SPEC = _Register('unalias')
# TODO: Test out all the details here
def UnAlias(argv, aliases):
status = 0
for name in argv:
try:
del aliases[name]
except KeyError:
print('alias %r is not defined' % name) # TODO: error?
status = 1
return status
import signal
View
@@ -106,7 +106,7 @@ class Executor(object):
CompoundWord/WordPart.
"""
def __init__(self, mem, fd_state, status_lines, funcs, readline, completion,
comp_lookup, exec_opts, arena):
comp_lookup, exec_opts, arena, aliases):
"""
Args:
mem: Mem instance for storing variables
@@ -141,8 +141,7 @@ def __init__(self, mem, fd_state, status_lines, funcs, readline, completion,
self.nodes_to_run = [] # list of nodes, appended to by signal handlers
self.dir_stack = state.DirStack()
# TODO: Pass these in from main()
self.aliases = {} # alias name -> string
self.aliases = aliases # alias name -> string
self.targets = [] # make syntax enters stuff here -- Target()
# metaprogramming or regular target syntax
# Whether argv[0] is make determines if it is executed
@@ -203,7 +202,7 @@ def _Eval(self, argv):
# TODO: set -o sane-eval should change eval to take a single string.
code_str = ' '.join(argv)
line_reader = reader.StringLineReader(code_str, self.arena)
_, c_parser = parse_lib.MakeParser(line_reader, self.arena)
_, c_parser = parse_lib.MakeParser(line_reader, self.arena, self.aliases)
return self._EvalHelper(c_parser, '<eval string>')
def ParseTrapCode(self, code_str):
@@ -380,6 +379,12 @@ def _RunBuiltin(self, builtin_id, argv):
# These are synonyms
status = builtin.DeclareTypeset(argv, self.mem, self.funcs)
elif builtin_id == builtin_e.ALIAS:
status = builtin.Alias(argv, self.aliases)
elif builtin_id == builtin_e.UNALIAS:
status = builtin.UnAlias(argv, self.aliases)
elif builtin_id == builtin_e.HELP:
loader = util.GetResourceLoader()
status = builtin.Help(argv, loader)
View
@@ -43,10 +43,11 @@ def InitExecutor(arena=None):
status_lines = None # not needed for what we're testing
funcs = {}
comp_funcs = {}
aliases = {}
exec_opts = state.ExecOpts(mem)
# For the tests, we do not use 'readline'.
return cmd_exec.Executor(mem, fd_state, status_lines, funcs, None,
completion, comp_funcs, exec_opts, arena)
completion, comp_funcs, exec_opts, arena, aliases)
def InitEvaluator():
View
@@ -66,7 +66,7 @@ module runtime
| COLON
| TEST | BRACKET | GETOPTS
| COMMAND | TYPE | HELP
| DECLARE | TYPESET
| DECLARE | TYPESET | ALIAS | UNALIAS
| PWD
-- word_eval.py: SliceParts is for ${a-} and ${a+}, Error is for ${a?}, and
View
@@ -120,11 +120,12 @@ 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):
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.line_reader = line_reader # for here docs
self.arena = arena
self.arena = arena # for adding here doc spans
self.aliases = aliases or {} # aliases to expand at parse time
self.Reset()
@@ -301,6 +302,7 @@ def _ScanSimpleCommand(self):
else:
break
self._Next()
return redirects, words
@@ -1183,11 +1185,25 @@ def ParseCommand(self):
node = self.ParseCompoundCommand()
assert node is not None
if node.tag != command_e.TimeBlock: # The only one without redirects
redirects = self._ParseRedirectList()
assert redirects is not None
node.redirects = redirects
node.redirects = self._ParseRedirectList()
assert node.redirects is not None
return node
# TODO: Is this the natural place to check for aliases? It's NOT a
# keyword.
# 1) check if self.cur_word is a plain word in self.aliases
# 2) Iteratively check next work based on trailing space
# 2) And then change state Then RECURSIVELY call ParseCommand?
#
# 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)
if alias_exp is not None:
log('expand %s -> %s', word_str, 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
@@ -36,12 +36,13 @@ def InitLexer(s, arena):
# you need somewhere to store the side effects -- errors for parsers, and the
# actual values for the evaluators/executors.
def MakeParser(line_reader, arena):
def MakeParser(line_reader, arena, aliases):
"""Top level parser."""
line_lexer = lexer.LineLexer(match.MATCHER, '', arena)
lx = lexer.Lexer(line_lexer, line_reader)
w_parser = word_parse.WordParser(lx, line_reader)
c_parser = cmd_parse.CommandParser(w_parser, lx, line_reader, arena)
c_parser = cmd_parse.CommandParser(w_parser, lx, line_reader, arena,
aliases=aliases)
return w_parser, c_parser
View
@@ -6,17 +6,19 @@
#
# Bash is the only one that doesn't support aliases!
#### basic alias
#### Basic alias
shopt -s expand_aliases # bash requires this
alias hi='echo hello world'
hi
hi || echo 'should not run this'
echo hi # second word is not
'hi' || echo 'expected failure'
## STDOUT:
hello world
hi
expected failure
## END
#### alias with trailing space causes second alias expansion
#### alias with trailing space causes alias expansion on second word
shopt -s expand_aliases # bash requires this
alias hi='echo hello world '
@@ -33,7 +35,7 @@ hello world !!!
hello world punct
## END
#### iterative alias expansion of first word
#### Recursive alias expansion of first word
shopt -s expand_aliases # bash requires this
alias hi='echo hello world'
alias echo='echo --; echo '
@@ -43,8 +45,7 @@ hi # first hi is expanded to echo hello world; then echo is expanded. gah.
hello world
## END
#### expansion of alias with variable
#### Expansion of alias with variable
shopt -s expand_aliases # bash requires this
x=x
alias echo-x='echo $x' # nothing is evaluated here
@@ -54,6 +55,18 @@ echo-x hi
y hi
## END
#### Alias must be an unquoted word, no expansions allowed
shopt -s expand_aliases # bash requires this
alias echo_alias_='echo'
cmd=echo_alias_
echo_alias_ X # this works
$cmd X # this fails because it's quoted
echo status=$?
## STDOUT:
X
status=127
## END
#### first and second word are the same
shopt -s expand_aliases # bash requires this
@@ -109,18 +122,6 @@ alias "$name$val"
echo_alias_ X
## stdout: X
#### Alias detection happens before expansion
shopt -s expand_aliases # bash requires this
alias echo_alias_='echo'
cmd=echo_alias_
echo_alias_ X
$cmd X
echo status=$?
## STDOUT:
X
status=127
## END
#### Alias name with punctuation
# NOTE: / is not OK in bash, but OK in other shells. Must less restrictive
# than var names.
@@ -136,7 +137,7 @@ e_ x
## status: 2
## OK mksh/zsh status: 1
#### Loop split across alias and arg
#### Loop split across alias and arg works
shopt -s expand_aliases # bash requires this
alias e_='for i in 1 2 3; do echo $i;'
e_ done
@@ -146,10 +147,63 @@ e_ done
3
## END
#### Loop split across alias and arg 2
#### Loop split across alias in another way is syntax error
# For some reason this doesn't work, but the previous case does.
shopt -s expand_aliases
alias e_='for i in 1 2 3; do echo '
e_ '$i done;'
## status: 2
## OK mksh/zsh status: 1
#### Loop split across both iterative and recursive aliases
shopt -s expand_aliases # bash requires this
alias FOR1='for '
alias FOR2='FOR1 '
alias eye1='i '
alias eye2='eye1 '
alias IN='in '
alias onetwo='$one "2" ' # NOTE: this does NOT work in any shell except bash.
one=1
FOR2 eye2 IN onetwo 3; do echo $i; done
## STDOUT:
1
2
3
## END
## BUG zsh stdout-json: ""
#### Alias with a quote in the middle is a syntax error
shopt -s expand_aliases
alias e_='echo "'
var=x
e_ '${var}"'
## status: 2
## OK mksh/zsh status: 1
#### Alias with a newline
# The second echo command is run in dash/mksh!
shopt -s expand_aliases
alias e_='echo 1
'
var='echo foo'
e_ ${var}
## stdout-json: "1\nfoo\n"
## OK zsh stdout-json: "1\n"
## OK zsh status: 127
#### Two aliases in pipeline
shopt -s expand_aliases
alias SEQ='seq '
alias THREE='3 '
alias WC='wc '
SEQ THREE | WC -l
## stdout: 3
#### Alias for { block
shopt -s expand_aliases
alias LBRACE='{ '
LBRACE echo one; echo two; }
## STDOUT:
one
two
## END
View
@@ -232,7 +232,7 @@ blog-other1() {
}
alias() {
sh-spec spec/alias.test.sh --osh-failures-allowed 13 \
sh-spec spec/alias.test.sh --osh-failures-allowed 18 \
${REF_SHELLS[@]} $ZSH $OSH_LIST "$@"
}

0 comments on commit 46640af

Please sign in to comment.