Skip to content

Commit

Permalink
Implement ${assoc["key"]}.
Browse files Browse the repository at this point in the history
Also, try to correctly implement the "return 124 protocol".  Not sure
it's working quite right.
  • Loading branch information
Andy Chu committed Sep 28, 2018
1 parent d5071a2 commit f1a54e5
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 32 deletions.
55 changes: 33 additions & 22 deletions core/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@
log = util.log


class _RetryCompletion(Exception):
"""For the 'exit 124' protocol."""
pass


class NullCompleter(object):

def Matches(self, words, index, to_complete):
Expand Down Expand Up @@ -205,7 +210,6 @@ def log(self, *args):
self.ex.debug_f.log(*args)

def Matches(self, comp_words, index, to_complete):

# TODO: Delete COMPREPLY here? It doesn't seem to be defined in bash by
# default.
command = comp_words[0]
Expand All @@ -226,28 +230,23 @@ def Matches(self, comp_words, index, to_complete):

status = self.ex.RunFuncForCompletion(self.func, argv)
if status == 124:
# TODO: DETECT CHANGES IN COMPSPEC to command! To avoid infinite loop?
# How do you do this? By the argv?

self.log('Got status 124 from %r', self.func.name)
# The previous run may have registered another function via 'complete',
# i.e. by sourcing a file. Try it again.
status = self.ex.RunFuncForCompletion(self.func, argv)
if status == 124:
util.warn('Got exit code 124 from function %r TWICE', self.func.name)
raise _RetryCompletion()

# Should be COMP_REPLY to follow naming convention! Lame.
val = state.GetGlobal(self.ex.mem, 'COMPREPLY')
if val.tag == value_e.Undef:
util.error('Ran function %s but COMPREPLY was not defined', self.func.name)
return
return []

if val.tag != value_e.StrArray:
log('ERROR: COMPREPLY should be an array, got %s', val)
return
return []
self.log('COMPREPLY %s', val)
for name in val.strs:
yield name

# Return this all at once so we don't have a generator. COMPREPLY happens
# all at once anyway.
return val.strs


class VariablesAction(object):
Expand Down Expand Up @@ -375,6 +374,8 @@ def __init__(self, actions, predicate=None, prefix='', suffix=''):
self.suffix = suffix

def Matches(self, words, index, to_complete, filter_func_matches=True):
# NOTE: This has to be evaluated eagerly so we get the _RetryCompletion
# exception.
for a in self.actions:
for match in a.Matches(words, index, to_complete):
# Special case hack to match bash for compgen -F. It doesn't filter by
Expand Down Expand Up @@ -652,8 +653,6 @@ def Matches(self, buf):
else:
comp_type, to_complete, comp_words = _GetCompletionType1(self.parser, buf)

# TODO: I don't get bash -D vs -E. Might need to write a test program.

if comp_type == completion_state_e.VAR_NAME:
# Non-user chain
chain = self.var_comp
Expand All @@ -679,9 +678,9 @@ def Matches(self, buf):
self.progress_f.Write('Completing %r ... (Ctrl-C to cancel)', buf)
start_time = time.time()

index = len(comp_words) - 1 # COMP_CWORD -1 when it's empty
self.debug_f.log('Using %s', chain)

index = len(comp_words) - 1 # COMP_CWORD -1 when it's empty
i = 0
for m in chain.Matches(comp_words, index, to_complete):
# TODO: need to dedupe these
Expand All @@ -698,6 +697,7 @@ def Matches(self, buf):
self.progress_f.Write(
'Found %d match%s for %r in %.2f seconds', i,
plural, buf, elapsed)
done = True

# TODO: Have to de-dupe and sort these? Because 'echo' is a builtin as
# well as a command, and we don't want to show it twice. Although then
Expand Down Expand Up @@ -734,13 +734,24 @@ def _GetNextCompletion(self, state):

self.comp_iter = self.root_comp.Matches(buf)

if self.comp_iter is None:
self.debug_f.log("ASSERT comp_iter shouldn't be None")
assert self.comp_iter is not None, self.comp_iter

try:
next_completion = self.comp_iter.next()
except StopIteration:
next_completion = None # sentinel?
done = False
while not done:
self.debug_f.log('comp_iter.next()')
try:
next_completion = self.comp_iter.next()
done = True
except _RetryCompletion:
# TODO: Is it OK to retry here? Shouldn't we retry in
# RootCompleter, after we already know the words? That seems to run
# into some problems with Python generators and exceptions.
# I kind of want the 'g.send()' pattern to "prime the generator",
# revealing the first exception.
pass
except StopIteration:
next_completion = None # sentinel?
done = True

return next_completion

Expand Down
4 changes: 4 additions & 0 deletions core/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,10 @@ def _InitDefaults(self):
# For xtrace
SetGlobalString(self, 'PS4', '+ ')

# bash-completion uses this. Value copied from bash. It doesn't integrate
# with 'readline' yet.
SetGlobalString(self, 'COMP_WORDBREAKS', '"\'><=;|&(:')

def _InitVarsFromEnv(self, environ):
# This is the way dash and bash work -- at startup, they turn everything in
# 'environ' variable into shell variables. Bash has an export_env
Expand Down
26 changes: 17 additions & 9 deletions core/word_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,24 +508,32 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):

elif part.bracket_op.tag == bracket_op_e.ArrayIndex:
anode = part.bracket_op.expr
index = self.arith_ev.Eval(anode)

if val.tag == value_e.Undef:
pass # it will be checked later

elif val.tag == value_e.Str:
# TODO: Implement this as an extension. Requires unicode support.
# Bash treats it as an array.
e_die("Can't index string %r with integer", part.token.val)
# Bash treats any string as an array, so we can't add our own
# behavior here without making valid OSH invalid bash.
e_die("Can't index string %r with integer", part.token.val,
token=part.token)

elif val.tag == value_e.StrArray:
index = self.arith_ev.Eval(anode)
try:
s = val.strs[index]
val = runtime.Str(val.strs[index])
except IndexError:
s = None
val = runtime.Undef()

if s is None:
elif val.tag == value_e.AssocArray:
key = self.arith_ev.Eval(anode, int_coerce=False)
try:
s = runtime.Str(val.d[key])
except KeyError:
val = runtime.Undef()
else:
val = runtime.Str(s)

else:
raise AssertionError(val.__class__.__name__)

else:
raise AssertionError(part.bracket_op.tag)
Expand Down
2 changes: 1 addition & 1 deletion test/spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ word-eval() {
}

assign() {
sh-spec spec/assign.test.sh --osh-failures-allowed 3 \
sh-spec spec/assign.test.sh --osh-failures-allowed 2 \
${REF_SHELLS[@]} $OSH_LIST "$@"
}

Expand Down

0 comments on commit f1a54e5

Please sign in to comment.