Skip to content

Commit

Permalink
[spec/assoc] More associative array tests pass.
Browse files Browse the repository at this point in the history
- Implement ${#assoc[@]}
- Implement (( assoc[key] ))
- Also get rid of is_assoc_array flag.  Issue #288.
  • Loading branch information
Andy Chu committed Jul 13, 2019
1 parent 80b2cad commit 919298c
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 56 deletions.
10 changes: 9 additions & 1 deletion osh/expr_eval.py
Expand Up @@ -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')
Expand Down
2 changes: 1 addition & 1 deletion osh/runtime.asdl
Expand Up @@ -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
Expand Down
22 changes: 5 additions & 17 deletions osh/state.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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!
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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."""
Expand All @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions osh/word_eval.py
Expand Up @@ -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"
Expand Down
5 changes: 3 additions & 2 deletions spec/assign-extended.test.sh
Expand Up @@ -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

109 changes: 76 additions & 33 deletions spec/assoc.test.sh
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion spec/osh-only.test.sh
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/spec.sh
Expand Up @@ -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 "$@"
}

Expand Down

0 comments on commit 919298c

Please sign in to comment.