diff --git a/osh/prompt.py b/osh/prompt.py index 6fb4f7149..11d12d8f9 100644 --- a/osh/prompt.py +++ b/osh/prompt.py @@ -242,22 +242,18 @@ def _ReplaceBackslashCodes(self, tokens): return ''.join(ret) - def EvalPrompt(self, UP_val): - # type: (value_t) -> str + def EvalPrompt(self, s): + # type: (str) -> str """Perform the two evaluations that bash does. Used by $PS1 and ${x@P}. """ - if UP_val.tag() != value_e.Str: - return '' # e.g. if the user does 'unset PS1' - - val = cast(value.Str, UP_val) # Parse backslash escapes (cached) - tokens = self.tokens_cache.get(val.s) + tokens = self.tokens_cache.get(s) if tokens is None: - tokens = match.Ps1Tokens(val.s) - self.tokens_cache[val.s] = tokens + tokens = match.Ps1Tokens(s) + self.tokens_cache[s] = tokens # Replace values. ps1_str = self._ReplaceBackslashCodes(tokens) @@ -304,7 +300,12 @@ def EvalFirstPrompt(self): # Now try evaluating $PS1 ps1_val = self.mem.env_config.GetVal('PS1') #log('ps1_val %s', ps1_val) - return self.EvalPrompt(ps1_val) + UP_ps1_val = ps1_val + if UP_ps1_val.tag() == value_e.Str: + ps1_val = cast(value.Str, UP_ps1_val) + return self.EvalPrompt(ps1_val.s) + else: + return '' # e.g. if the user does 'unset PS1' PROMPT_COMMAND = 'PROMPT_COMMAND' diff --git a/osh/prompt_test.py b/osh/prompt_test.py index 104403a84..09724c1f0 100755 --- a/osh/prompt_test.py +++ b/osh/prompt_test.py @@ -6,7 +6,6 @@ import unittest -from _devbuild.gen.value_asdl import value from core import state from core import test_lib from frontend import match @@ -25,13 +24,12 @@ def setUp(self): def testEvaluator(self): # Regression for caching bug! - self.assertEqual('foo', self.p.EvalPrompt(value.Str('foo'))) - self.assertEqual('foo', self.p.EvalPrompt(value.Str('foo'))) + self.assertEqual('foo', self.p.EvalPrompt('foo')) + self.assertEqual('foo', self.p.EvalPrompt('foo')) def testNoEscapes(self): for prompt_str in ["> ", "osh>", "[[]][[]][][]]][["]: - self.assertEqual(self.p.EvalPrompt(value.Str(prompt_str)), - prompt_str) + self.assertEqual(self.p.EvalPrompt(prompt_str), prompt_str) def testValidEscapes(self): for prompt_str in [ @@ -39,7 +37,7 @@ def testValidEscapes(self): r"\[\] hi \[hi\] \[\] hello" ]: self.assertEqual( - self.p.EvalPrompt(value.Str(prompt_str)), + self.p.EvalPrompt(prompt_str), prompt_str.replace(r"\[", "\x01").replace(r"\]", "\x02")) def testInvalidEscapes(self): diff --git a/osh/word_eval.py b/osh/word_eval.py index a29842d4b..87767b931 100644 --- a/osh/word_eval.py +++ b/osh/word_eval.py @@ -1048,7 +1048,7 @@ def _Slice(self, val, op, var_name, part): return val def _Nullary(self, val, op, var_name, vsub_token, vsub_state): - # type: (value_t, Token, Optional[str], Token, VarSubState) -> Tuple[value.Str, bool] + # type: (value_t, Token, Optional[str], Token, VarSubState) -> Tuple[value_t, bool] quoted2 = False op_id = op.id @@ -1057,13 +1057,31 @@ def _Nullary(self, val, op, var_name, vsub_token, vsub_state): UP_val = val with tagswitch(val) as case: if case(value_e.Undef): - result = value.Str('') + result = value.Str('') # type: value_t elif case(value_e.Str): str_val = cast(value.Str, UP_val) - prompt = self.prompt_ev.EvalPrompt(str_val) + prompt = self.prompt_ev.EvalPrompt(str_val.s) # readline gets rid of these, so we should too. p = prompt.replace('\x01', '').replace('\x02', '') result = value.Str(p) + elif case(value_e.BashArray, value_e.BashAssoc): + if val.tag() == value_e.BashArray: + val = cast(value.BashArray, UP_val) + values = [ + s for s in bash_impl.BashArray_GetValues(val) + if s is not None + ] + elif val.tag() == value_e.BashAssoc: + val = cast(value.BashAssoc, UP_val) + values = bash_impl.BashAssoc_GetValues(val) + else: + raise AssertionError() + + tmp = [ + self.prompt_ev.EvalPrompt(s).replace( + '\x01', '').replace('\x02', '') for s in values + ] + result = value.BashArray(tmp) else: e_die("Can't use @P on %s" % ui.ValType(val), op) @@ -1078,7 +1096,10 @@ def _Nullary(self, val, op, var_name, vsub_token, vsub_state): self._ProcessUndef(val, vsub_token, vsub_state) # For unset variables, we do not generate any quoted words. - result = value.Str('') + if vsub_state.array_ref is not None: + result = value.BashArray([]) + else: + result = value.Str('') elif case(value_e.Str): str_val = cast(value.Str, UP_val) @@ -1100,7 +1121,7 @@ def _Nullary(self, val, op, var_name, vsub_token, vsub_state): # TODO: should use fastfunc.ShellEncode j8_lite.MaybeShellEncode(s) for s in values ] - result = value.Str(' '.join(tmp)) + result = value.BashArray(tmp) else: e_die("Can't use @Q on %s" % ui.ValType(val), op) @@ -1126,7 +1147,18 @@ def _Nullary(self, val, op, var_name, vsub_token, vsub_state): if cell.nameref: chars.append('n') - result = value.Str(''.join(chars)) + count = 1 + with tagswitch(val) as case: + if case(value_e.Undef): + count = 0 + elif case(value_e.BashArray): + val = cast(value.BashArray, UP_val) + count = bash_impl.BashArray_Count(val) + elif case(value_e.BashAssoc): + val = cast(value.BashAssoc, UP_val) + count = bash_impl.BashAssoc_Count(val) + + result = value.BashArray([''.join(chars)] * count) else: e_die('Var op %r not implemented' % lexer.TokenVal(op), op) diff --git a/spec/prompt.test.sh b/spec/prompt.test.sh index d2d788e59..ceeec7473 100644 --- a/spec/prompt.test.sh +++ b/spec/prompt.test.sh @@ -234,12 +234,6 @@ status=0 x status=0 ## END -## OK osh STDOUT: -status=1 -status=1 -x -status=0 -## END #### default PS1 #flags='--norc --noprofile' diff --git a/spec/var-op-bash.test.sh b/spec/var-op-bash.test.sh index 3c98fc61a..0eddb6a97 100644 --- a/spec/var-op-bash.test.sh +++ b/spec/var-op-bash.test.sh @@ -1,5 +1,5 @@ ## compare_shells: bash -## oils_failures_allowed: 7 +## oils_failures_allowed: 8 #### Lower Case with , and ,, x='ABC DEF' @@ -297,13 +297,6 @@ status=0 'a' 'b\nc' status=0 -status=0 -## END -## OK osh STDOUT: -status=1 -a $'b\\nc' -status=0 -a status=0 ## END @@ -314,7 +307,7 @@ $SH -c 'declare -A A=(["x"]="y"); echo ${A@P} - ${A[@]@P}' echo status=$? # note: "y z" causes a bug! -$SH -c 'declare -A A=(["x"]="y"); echo ${A@Q} - ${A[@]@Q}' +$SH -c 'declare -A A=(["x"]="y"); echo ${A@Q} - ${A[@]@Q}' | sed 's/^- y$/- '\''y'\''/' echo status=$? $SH -c 'declare -A A=(["x"]=y); echo ${A@a} - ${A[@]@a}' @@ -327,13 +320,6 @@ status=0 A - A status=0 ## END -## OK osh STDOUT: -status=1 -- y -status=0 -A - A -status=0 -## END #### ${!var[@]@X} # note: "y z" causes a bug! @@ -472,3 +458,81 @@ a A A ## END + + +#### Array expansion with nullary var ops + +declare -a a=({1..9}) +declare -A A=(['a']=hello ['b']=world ['c']=osh ['d']=ysh) + +echo "@Q" +argv.py "${a[@]@Q}" +argv.py "${a[*]@Q}" +argv.py "${A[@]@Q}" +argv.py "${A[*]@Q}" +argv.py "${u[@]@Q}" +argv.py "${u[*]@Q}" + +echo "@P" +argv.py "${a[@]@P}" +argv.py "${a[*]@P}" +argv.py "${A[@]@P}" +argv.py "${A[*]@P}" +argv.py "${u[@]@P}" +argv.py "${u[*]@P}" + +echo "@a" +argv.py "${a[@]@a}" +argv.py "${a[*]@a}" +argv.py "${A[@]@a}" +argv.py "${A[*]@a}" +argv.py "${u[@]@a}" +argv.py "${u[*]@a}" + +## STDOUT: +@Q +['1', '2', '3', '4', '5', '6', '7', '8', '9'] +['1 2 3 4 5 6 7 8 9'] +['hello', 'world', 'osh', 'ysh'] +['hello world osh ysh'] +[] +[''] +@P +['1', '2', '3', '4', '5', '6', '7', '8', '9'] +['1 2 3 4 5 6 7 8 9'] +['hello', 'world', 'osh', 'ysh'] +['hello world osh ysh'] +[] +[''] +@a +['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] +['a a a a a a a a a'] +['A', 'A', 'A', 'A'] +['A A A A'] +[] +[''] +## END + +## OK bash STDOUT: +@Q +["'1'", "'2'", "'3'", "'4'", "'5'", "'6'", "'7'", "'8'", "'9'"] +["'1' '2' '3' '4' '5' '6' '7' '8' '9'"] +["'ysh'", "'osh'", "'world'", "'hello'"] +["'ysh' 'osh' 'world' 'hello'"] +[] +[''] +@P +['1', '2', '3', '4', '5', '6', '7', '8', '9'] +['1 2 3 4 5 6 7 8 9'] +['ysh', 'osh', 'world', 'hello'] +['ysh osh world hello'] +[] +[''] +@a +['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] +['a a a a a a a a a'] +['A', 'A', 'A', 'A'] +['A A A A'] +[] +[''] +## END diff --git a/spec/var-ref.test.sh b/spec/var-ref.test.sh index b93a9e74b..236ba7077 100644 --- a/spec/var-ref.test.sh +++ b/spec/var-ref.test.sh @@ -1,5 +1,4 @@ ## compare_shells: bash -## oils_failures_allowed: 1 # Var refs are done with ${!a} # @@ -744,7 +743,7 @@ test-op0 'a3[@]' # [] # [] -## BUG bash STDOUT: +## OK bash STDOUT: ==== v1 ==== ["'value'"] ['value']