diff --git a/osh/expr_eval.py b/osh/expr_eval.py index f0f83a1a57..6eba994b6e 100755 --- a/osh/expr_eval.py +++ b/osh/expr_eval.py @@ -438,13 +438,21 @@ def Eval(self, node, int_coerce=True): rhs = self.Eval(node.right) # eager evaluation for the rest + # TODO: Validate that in lhs + rhs, both are STRINGS and not [] or {}. + if op_id == Id.Arith_LBracket: - if not isinstance(lhs, list): + # StrArray or AssocArray + if not isinstance(lhs, (list, dict)): # TODO: Add error context e_die('Expected array in index expression, got %s', lhs) try: item = lhs[rhs] + except KeyError: + if self.exec_opts.nounset: + e_die('Invalid key %r' % rhs) + else: + return 0 # If not fatal, return 0 except IndexError: if self.exec_opts.nounset: e_die('Index out of bounds') diff --git a/osh/runtime.asdl b/osh/runtime.asdl index c2fe746ec5..10272c437c 100644 --- a/osh/runtime.asdl +++ b/osh/runtime.asdl @@ -41,7 +41,7 @@ module runtime -- TODO: -- * add spid for last-assigned location -- * use a bitfield for flags. - cell = (value val, bool exported, bool readonly, bool is_assoc_array) + cell = (value val, bool exported, bool readonly) -- An undefined variable can become an indexed array with s[x]=1. But if we -- 'declare -A' it, it will be undefined and waiting to turn into an diff --git a/osh/state.py b/osh/state.py index e380a213b3..7066d54244 100644 --- a/osh/state.py +++ b/osh/state.py @@ -857,8 +857,6 @@ def IsAssocArray(self, name, lookup_mode): if cell: if cell.val.tag == value_e.AssocArray: # foo=([key]=value) return True - if cell.is_assoc_array: # declare -A - return True return False def SetVar(self, lval, val, new_flags, lookup_mode): @@ -922,10 +920,6 @@ def SetVar(self, lval, val, new_flags, lookup_mode): cell.exported = True if var_flags_e.ReadOnly in new_flags: cell.readonly = True - if var_flags_e.AssocArray in new_flags: - # doesn't do anything - #cell.is_assoc_array = True - pass else: if val is None: # set -o nounset; local foo; echo $foo # It's still undefined! @@ -936,9 +930,7 @@ def SetVar(self, lval, val, new_flags, lookup_mode): cell = runtime_asdl.cell(val, var_flags_e.Exported in new_flags, - var_flags_e.ReadOnly in new_flags, - #var_flags_e.AssocArray in new_flags) - False) + var_flags_e.ReadOnly in new_flags) namespace[lval.name] = cell if (cell.val is not None and @@ -977,13 +969,9 @@ def SetVar(self, lval, val, new_flags, lookup_mode): if cell.readonly: e_die("Can't assign to readonly value", span_id=left_spid) - # This is for the case where we did declare -a foo or declare -A foo. - # There IS a cell, but it's still undefined. + # undef[0]=y is allowed if cell_tag == value_e.Undef: - if cell.is_assoc_array: - self._BindNewAssocArrayWithEntry(namespace, lval, val, new_flags) - else: - self._BindNewArrayWithEntry(namespace, lval, val, new_flags) + self._BindNewArrayWithEntry(namespace, lval, val, new_flags) return if cell_tag == value_e.StrArray: @@ -1016,7 +1004,7 @@ def _BindNewArrayWithEntry(self, namespace, lval, val, new_flags): # arrays can't be exported; can't have AssocArray flag readonly = var_flags_e.ReadOnly in new_flags - namespace[lval.name] = runtime_asdl.cell(new_value, False, readonly, False) + namespace[lval.name] = runtime_asdl.cell(new_value, False, readonly) def _BindNewAssocArrayWithEntry(self, namespace, lval, val, new_flags): """Fill 'namespace' with a new indexed array entry.""" @@ -1025,7 +1013,7 @@ def _BindNewAssocArrayWithEntry(self, namespace, lval, val, new_flags): # associative arrays can't be exported; don't need AssocArray flag readonly = var_flags_e.ReadOnly in new_flags - namespace[lval.name] = runtime_asdl.cell(new_value, False, readonly, False) + namespace[lval.name] = runtime_asdl.cell(new_value, False, readonly) def InternalSetGlobal(self, name, new_val): """For setting read-only globals internally. diff --git a/osh/word_eval.py b/osh/word_eval.py index 7fd387df30..c0edb3e1c2 100644 --- a/osh/word_eval.py +++ b/osh/word_eval.py @@ -425,6 +425,12 @@ def _ApplyPrefixOp(self, val, op_id, token): # There can be empty placeholder values in the array. length = sum(1 for s in val.strs if s is not None) + elif val.tag == value_e.AssocArray: + length = len(val.d) + + else: + raise AssertionError(val.__class__.__name__) + return value.Str(str(length)) elif op_id == Id.VSub_Bang: # ${!foo}, "indirect expansion" diff --git a/spec/assign-extended.test.sh b/spec/assign-extended.test.sh index a689392a3c..c481840cdc 100644 --- a/spec/assign-extended.test.sh +++ b/spec/assign-extended.test.sh @@ -276,9 +276,10 @@ argv.py "${ev["ev1"]}" #### myvar=typeset (another form of dynamic assignment) myvar=typeset -$myvar x=42 +x='a b' +$myvar x=$x echo $x ## STDOUT: -42 +a ## END diff --git a/spec/assoc.test.sh b/spec/assoc.test.sh index 13df1ebc61..02aa5ed401 100644 --- a/spec/assoc.test.sh +++ b/spec/assoc.test.sh @@ -14,9 +14,17 @@ # http://www.gnu.org/software/bash/manual/html_node/Arrays.html # TODO: Need a SETUP section. -#### TODO: SETUP should be share +#### Literal syntax ([x]=y) declare -A a a=([aa]=b [foo]=bar ['a+1']=c) +echo ${a["aa"]} +echo ${a["foo"]} +echo ${a["a+1"]} +## STDOUT: +b +bar +c +## END #### create empty assoc array, put, then get declare -A d # still undefined @@ -26,7 +34,6 @@ echo ${d['foo']} #### retrieve indices with ! declare -A a -#a=([aa]=b [foo]=bar ['a+1']=c) var='x' a["$var"]=b a['foo']=bar @@ -48,55 +55,79 @@ echo "${a}" ## OK osh stdout-json: "" ## OK osh status: 1 -#### length of dict does not work +#### length ${#a[@]} declare -A a -a=([aa]=b [foo]=bar ['a+1']=c) -echo "${#a}" -## stdout: 0 +a["x"]=1 +a["y"]=2 +a["z"]=3 +echo "${#a[@]}" +## stdout: 3 -#### index by number doesn't work +#### retrieve values with numeric keys declare -A a -a=([aa]=b [foo]=bar ['a+1']=c) +a["0"]=a +a["1"]=b +a["2"]=c echo 0 "${a[0]}" 1 "${a[1]}" 2 "${a[2]}" -## stdout-json: "0 1 2 \n" +## STDOUT: +0 a 1 b 2 c +## END -#### index by key name +#### retrieve values with string keys declare -A a -a=([aa]=b [foo]=bar ['a+1']=c) -echo "${a[aa]}" "${a[foo]}" "${a['a+1']}" -# WTF: Why do we get bar bar c? -## stdout-json: "b bar c\n" +a["aa"]=b +a["foo"]=bar +a['a+1']=c +echo "${a["aa"]}" "${a["foo"]}" "${a["a+1"]}" +## STDOUT: +b bar c +## END -#### index by quoted string +#### retrieve value with single quoted string declare -A a -a=([aa]=b [foo]=bar ['a+1']=c) +a["aa"]=b +a["foo"]=bar +a['a+1']=c echo "${a['a+1']}" ## stdout: c -#### index by unquoted string +#### index by unquoted string doesn't work in OSH because it's a variable declare -A a -a=([aa]=b [foo]=bar ['a+1']=c) +a["aa"]=b +a["foo"]=bar +a['a+1']=c echo "${a[a+1]}" ## stdout: c #### index by unquoted string as arithmetic -# For assoc arrays, unquoted string is just raw. -# For regular arrays, unquoted string is an arithmetic expression! -# How do I parse this? -declare -A assoc -assoc=([a+1]=c) + +i=1 array=(5 6 7) -a=1 -echo "${assoc[a]}" -echo "${assoc[a+1]}" # This works -echo "${array[a+1]}" -## stdout-json: "\nc\n7\n" +echo array[i]="${array[i]}" +echo array[i+1]="${array[i+1]}" -#### WTF index by key name -declare -A a -a=([xx]=bb [cc]=dd) -echo "${a[xx]}" "${a[cc]}" -## stdout-json: "bb dd\n" +# arithmetic does NOT work here in bash. These are unquoted strings! +declare -A assoc +assoc[i]=$i +assoc[i+1]=$i+1 + +assoc["i"]=string +assoc["i+1"]=string+1 + +echo assoc[i]="${assoc[i]}" +echo assoc[i+1]="${assoc[i+1]}" + +echo assoc[i]="${assoc["i"]}" +echo assoc[i+1]="${assoc["i+1"]}" + +## STDOUT: +array[i]=6 +array[i+1]=7 +assoc[i]=string +assoc[i+1]=string+1 +assoc[i]=string +assoc[i+1]=string+1 +## END #### Array stored in associative array gets converted to string array=('1 2' 3) @@ -201,3 +232,15 @@ echo ${assoc[1]} ${assoc[2]} ${assoc} 1 2 1 zero 2 ## END ## N-I osh status: 1 + +#### Associative array expressions inside (( )) +declare -A assoc +assoc[0]=42 +(( var = ${assoc[0]} )) +echo $var +(( var = assoc[0] )) +echo $var +## STDOUT: +42 +42 +## END diff --git a/spec/osh-only.test.sh b/spec/osh-only.test.sh index 1b013990e7..0e143f1bf8 100644 --- a/spec/osh-only.test.sh +++ b/spec/osh-only.test.sh @@ -12,7 +12,7 @@ echo status=$? repr nonexistent echo status=$? ## STDOUT: -x = (cell val:(value.Str s:42) exported:F readonly:F is_assoc_array:F) +x = (cell val:(value.Str s:42) exported:F readonly:F) status=0 'nonexistent' is not defined status=1 diff --git a/test/spec.sh b/test/spec.sh index 352ebfb51e..340d054451 100755 --- a/test/spec.sh +++ b/test/spec.sh @@ -569,7 +569,7 @@ append() { # associative array -- mksh and zsh implement different associative arrays. assoc() { - sh-spec spec/assoc.test.sh --osh-failures-allowed 12 \ + sh-spec spec/assoc.test.sh --osh-failures-allowed 9 \ $BASH $OSH_LIST "$@" }