diff --git a/frontend/syntax.asdl b/frontend/syntax.asdl index 386a29634c..94de319b3d 100644 --- a/frontend/syntax.asdl +++ b/frontend/syntax.asdl @@ -112,7 +112,7 @@ module syntax | ArrayIndex(arith_expr expr) suffix_op = - Nullary(id op_id) -- ${x@Q} or ${!prefix@} (which also has prefix_op) + Nullary %Token -- ${x@Q} or ${!prefix@} (which also has prefix_op) | Unary(id op_id, word arg_word) -- e.g. ${v:-default} -- TODO: token for / to attribute errors | PatSub(word pat, word? replace, id replace_mode) diff --git a/osh/word_eval.py b/osh/word_eval.py index 0b9ee333af..083811d97b 100644 --- a/osh/word_eval.py +++ b/osh/word_eval.py @@ -7,7 +7,7 @@ braced_var_sub, Token, word, word_e, word_t, compound_word, bracket_op_e, bracket_op__ArrayIndex, bracket_op__WholeArray, - suffix_op_e, suffix_op__Nullary, suffix_op__PatSub, suffix_op__Slice, + suffix_op_e, suffix_op__PatSub, suffix_op__Slice, suffix_op__Unary, sh_array_literal, single_quoted, double_quoted, simple_var_sub, command_sub, @@ -862,9 +862,9 @@ def _EvalBracedVarSub(self, part, part_vals, quoted): names.sort() val = value.MaybeStrArray(names) # type: value_t - suffix_op = cast(suffix_op__Nullary, part.suffix_op) + suffix_op = cast(Token, part.suffix_op) # "${!prefix@}" is the only one that doesn't decay - maybe_decay_array = not (quoted and suffix_op.op_id == Id.VOp3_At) + maybe_decay_array = not (quoted and suffix_op.id == Id.VOp3_At) name_query = True else: var_name = part.token.val @@ -995,18 +995,29 @@ def _EvalBracedVarSub(self, part, part_vals, quoted): UP_op = op with tagswitch(op) as case: if case(suffix_op_e.Nullary): - op = cast(suffix_op__Nullary, UP_op) - if op.op_id == Id.VOp0_P: + op = cast(Token, UP_op) + op_id = op.id + if op_id == Id.VOp0_P: prompt = self.prompt_ev.EvalPrompt(val) # readline gets rid of these, so we should too. p = prompt.replace('\x01', '').replace('\x02', '') val = value.Str(p) - elif op.op_id == Id.VOp0_Q: + elif op_id == Id.VOp0_Q: assert val.tag_() == value_e.Str, val val = cast(value__Str, val) val = value.Str(string_ops.ShellQuote(val.s)) + elif op_id == Id.VOp0_a: + # We're ONLY simluating -a and -A, not -r -x -n for now. See + # spec/ble-idioms.test.sh. + s = '' + with tagswitch(val) as case: + if case(value_e.MaybeStrArray): + s += 'a' + elif case(value_e.AssocArray): + s += 'A' + val = value.Str(s) else: - raise NotImplementedError(op.op_id) + e_die('Var op %r not implemented', op.val, token=op) elif case(suffix_op_e.Unary): op = cast(suffix_op__Unary, UP_op) diff --git a/osh/word_parse.py b/osh/word_parse.py index 8b5a4578c3..3bedfcf809 100644 --- a/osh/word_parse.py +++ b/osh/word_parse.py @@ -334,8 +334,7 @@ def _ParseVarExpr(self, arg_lex_mode, allow_query=False): part.suffix_op = suffix_op.Unary(op_id, arg_word) elif op_kind == Kind.VOp0: - op_id = self.token_type - part.suffix_op = suffix_op.Nullary(op_id) + part.suffix_op = self.cur_token # Nullary self._Next(lex_mode_e.VSub_2) # Expecting } self._Peek() @@ -373,8 +372,7 @@ def _ParseVarExpr(self, arg_lex_mode, allow_query=False): elif op_kind == Kind.VOp3: if allow_query: - op_id = self.token_type - part.suffix_op = suffix_op.Nullary(op_id) + part.suffix_op = self.cur_token # Nullary self._Next(lex_mode_e.VSub_2) # Expecting } self._Peek() else: diff --git a/spec/ble-idioms.test.sh b/spec/ble-idioms.test.sh index 502e3f70f8..fd03fc4f27 100644 --- a/spec/ble-idioms.test.sh +++ b/spec/ble-idioms.test.sh @@ -136,3 +136,26 @@ echo c_x=$c_x c_y=$c_y c_x=3 c_y=4 c_x=7 c_y=24 ## END + +#### is-array with ${var@a} +case $SH in (mksh|ash) exit 1 ;; esac + +function ble/is-array { [[ ${!1@a} == *a* ]]; } + +ble/is-array undef +echo undef $? + +string='' +ble/is-array string +echo string $? + +array=(one two three) +ble/is-array array +echo array $? +## STDOUT: +undef 1 +string 1 +array 0 +## END +## N-I zsh/mksh/ash status: 1 +## N-I zsh/mksh/ash stdout-json: ""