Skip to content

Commit

Permalink
[fix] Fix regression in spec/ble-idioms due to ${!ref} changes
Browse files Browse the repository at this point in the history
Lift the ${array@a} check to the EvalBracedVarSub level

Refactor to VarSubState as part of this change.  We don't want methods
to have too many parameters.
  • Loading branch information
Andy C committed Jul 14, 2021
1 parent 014d26b commit d6e7fcb
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 44 deletions.
4 changes: 4 additions & 0 deletions core/runtime.asdl
Expand Up @@ -67,8 +67,12 @@ module runtime
-- Used for ${a[i]=x}. TODO: also use for lvalue/place.
a_index = Str(string s) | Int(int i)

-- for the place in ${a[0]=a}
VTestPlace = (string name, a_index index)

-- evaluation state for braced_var_sub
VarSubState = (bool decay_array, bool array_op_nullary)

-- A cell is a wrapper for a value.
-- TODO: add spid for declaration for 'assigning const' error

Expand Down
89 changes: 45 additions & 44 deletions osh/word_eval.py
Expand Up @@ -24,8 +24,8 @@
assign_arg,
cmd_value_e, cmd_value_t, cmd_value, cmd_value__Assign, cmd_value__Argv,
quote_e, quote_t,
a_index, a_index_e, a_index_t, a_index__Int, a_index__Str,
VTestPlace
a_index, a_index_e, a_index__Int, a_index__Str,
VTestPlace, VarSubState,
)
from core import error
from core import pyos
Expand Down Expand Up @@ -67,6 +67,7 @@

def CheckCompatArray(var_name, exec_opts, is_plain_var_sub=True):
# type: (str, optview.Exec, bool) -> bool
"""Return whether we should allow ${a} to mean ${a[0]}."""
return (
exec_opts.compat_array() or
is_plain_var_sub and var_name in _STRING_AND_ARRAY
Expand Down Expand Up @@ -459,8 +460,8 @@ def _EvalVarNum(self, var_num):
assert var_num >= 0
return self.mem.GetArgNum(var_num)

def _EvalSpecialVar(self, op_id, quoted, maybe_decay_array):
# type: (int, bool, List[bool]) -> value_t
def _EvalSpecialVar(self, op_id, quoted, vsub_state):
# type: (int, bool, VarSubState) -> value_t
"""Evaluate $? and so forth"""
# $@ is special -- it need to know whether it is in a double quoted
# context.
Expand All @@ -474,9 +475,9 @@ def _EvalSpecialVar(self, op_id, quoted, maybe_decay_array):
val = value.MaybeStrArray(argv) # type: value_t
if op_id == Id.VSub_At:
# "$@" evaluates to an array, $@ should be decayed
maybe_decay_array[0] = not quoted
vsub_state.decay_array = not quoted
else: # $* "$*" are both decayed
maybe_decay_array[0] = True
vsub_state.decay_array = True

elif op_id == Id.VSub_Hyphen:
val = value.Str(_GetDollarHyphen(self.exec_opts))
Expand Down Expand Up @@ -685,8 +686,8 @@ def _Keys(self, val, token):
else:
raise AssertionError()

def _EvalVarRef(self, val, token, quoted, maybe_decay_array, vtest_place):
# type: (value_t, Token, bool, List[bool], VTestPlace) -> value_t
def _EvalVarRef(self, val, token, quoted, vsub_state, vtest_place):
# type: (value_t, Token, bool, VarSubState, VTestPlace) -> value_t
"""Handles indirect expansion like ${!var} and ${!a[0]}."""
UP_val = val
with tagswitch(val) as case:
Expand All @@ -698,7 +699,7 @@ def _EvalVarRef(self, val, token, quoted, maybe_decay_array, vtest_place):
bvs_part = self.unsafe_arith.ParseVarRef(val.s, token.span_id)
if not self.exec_opts.eval_unsafe_arith() and bvs_part.bracket_op:
e_die('a[i] not allowed without shopt -s eval_unsafe_arith', token=token)
return self._VarRefValue(bvs_part, quoted, maybe_decay_array, vtest_place)
return self._VarRefValue(bvs_part, quoted, vsub_state, vtest_place)

elif case(value_e.MaybeStrArray): # caught earlier but OK
e_die('Indirect expansion of array')
Expand Down Expand Up @@ -896,13 +897,13 @@ def _Nullary(self, val, op, var_name):

return result, quoted2

def _WholeArray(self, val, part, quoted, maybe_decay_array):
# type: (value_t, braced_var_sub, bool, List[bool]) -> value_t
def _WholeArray(self, val, part, quoted, vsub_state):
# type: (value_t, braced_var_sub, bool, VarSubState) -> value_t
bracket_op = cast(bracket_op__WholeArray, part.bracket_op)
op_id = bracket_op.op_id

if op_id == Id.Lit_At:
maybe_decay_array[0] = not quoted # ${a[@]} decays but "${a[@]}" doesn't
vsub_state.decay_array = not quoted # ${a[@]} decays but "${a[@]}" doesn't
UP_val = val
with tagswitch(val) as case2:
if case2(value_e.Undef):
Expand All @@ -916,7 +917,7 @@ def _WholeArray(self, val, part, quoted, maybe_decay_array):
val = value.MaybeStrArray(val.strs)

elif op_id == Id.Arith_Star:
maybe_decay_array[0] = True # both ${a[*]} and "${a[*]}" decay
vsub_state.decay_array = True # both ${a[*]} and "${a[*]}" decay
UP_val = val
with tagswitch(val) as case2:
if case2(value_e.Undef):
Expand All @@ -927,7 +928,7 @@ def _WholeArray(self, val, part, quoted, maybe_decay_array):
elif case2(value_e.MaybeStrArray):
val = cast(value__MaybeStrArray, UP_val)
# TODO: Is this a no-op? Just leave 'val' alone.
# ${a[*]} or "${a[*]}" : maybe_decay_array is always true
# ${a[*]} or "${a[*]}" : vsub_state.decay_array is always true
val = value.MaybeStrArray(val.strs)

else:
Expand Down Expand Up @@ -1045,15 +1046,15 @@ def _EmptyMaybeStrArrayOrError(self, token):
else:
return value.MaybeStrArray([])

def _EvalBracketOp(self, val, part, quoted, maybe_decay_array, vtest_place):
# type: (value_t, braced_var_sub, bool, List[bool], VTestPlace) -> value_t
def _EvalBracketOp(self, val, part, quoted, vsub_state, vtest_place):
# type: (value_t, braced_var_sub, bool, VarSubState, VTestPlace) -> value_t

if part.bracket_op:
bracket_op = part.bracket_op
UP_bracket_op = bracket_op
with tagswitch(bracket_op) as case:
if case(bracket_op_e.WholeArray):
val = self._WholeArray(val, part, quoted, maybe_decay_array)
val = self._WholeArray(val, part, quoted, vsub_state)

elif case(bracket_op_e.ArrayIndex):
bracket_op = cast(bracket_op__ArrayIndex, UP_bracket_op)
Expand All @@ -1070,21 +1071,14 @@ def _EvalBracketOp(self, val, part, quoted, maybe_decay_array, vtest_place):
# for ${BASH_SOURCE}, etc.
val = ResolveCompatArray(val)
else:
tmp = part.suffix_op
# TODO: An IR for ${} might simplify these lengthy conditions
if (tmp and tmp.tag_() == suffix_op_e.Nullary and
#cast(Token, tmp).id in (Id.VOp0_a, Id.VOp0_Q)):
cast(Token, tmp).id == Id.VOp0_a):
# ${array@a} is a string
pass
else:
if not vsub_state.array_op_nullary:
e_die("Array %r can't be referred to as a scalar (without @ or *)",
var_name, part=part)

return val

def _VarRefValue(self, part, quoted, maybe_decay_array, vtest_place):
# type: (braced_var_sub, bool, List[bool], VTestPlace) -> value_t
def _VarRefValue(self, part, quoted, vsub_state, vtest_place):
# type: (braced_var_sub, bool, VarSubState, VTestPlace) -> value_t
"""Duplicates some logic from _EvalBracedVarSub, but returns a value_t."""

# 1. Evaluate from (var_name, var_num, token Id) -> value
Expand All @@ -1099,10 +1093,10 @@ def _VarRefValue(self, part, quoted, maybe_decay_array, vtest_place):

else:
# $* decays
val = self._EvalSpecialVar(part.token.id, quoted, maybe_decay_array)
val = self._EvalSpecialVar(part.token.id, quoted, vsub_state)

# We don't need var_index because it's only for L-Values of test ops?
val = self._EvalBracketOp(val, part, quoted, maybe_decay_array, vtest_place)
val = self._EvalBracketOp(val, part, quoted, vsub_state, vtest_place)
return val

def _EvalBracedVarSub(self, part, part_vals, quoted):
Expand All @@ -1113,7 +1107,7 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
"""
# We have different operators that interact in a non-obvious order.
#
# 1. bracket_op: value -> (value, bool maybe_decay_array)
# 1. bracket_op: value -> value, with side effect on vsub_state
#
# 2. prefix_op
# a. length ${#x}: value -> value
Expand All @@ -1124,7 +1118,7 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
# b. Test: value -> part_value[]
# c. Other Suffix: value -> value
#
# 4. Process maybe_decay_array here before returning.
# 4. Process vsub_state.decay_array here before returning.
#
# These cases are hard to distinguish:
# - ${!prefix@} prefix query
Expand All @@ -1140,15 +1134,15 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
# 4. indirection? Only for some of the ! cases
# 5. string transformation suffix ops like ##
# 6. test op
# 7. maybe_decay_array
# 7. vsub_state.decay_array

# maybe_decay_array is for joining "${a[*]}" and unquoted ${a[@]} AFTER
# vsub_state.decay_array is for joining "${a[*]}" and unquoted ${a[@]} AFTER
# suffix ops are applied. If we take the length with a prefix op, the
# distinction is ignored.
maybe_decay_array = [False] # for $*, ${a[*]}, etc.

var_name = None # type: str
vtest_place = VTestPlace(var_name, None) # For ${foo=default}
vsub_state = VarSubState() # for $*, ${a[*]}, etc.

# 1. Evaluate from (var_name, var_num, token Id) -> value
if part.token.id == Id.VSub_Name:
Expand Down Expand Up @@ -1183,14 +1177,21 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
val = self._EvalVarNum(var_num)
else:
# $* decays
val = self._EvalSpecialVar(part.token.id, quoted, maybe_decay_array)
val = self._EvalSpecialVar(part.token.id, quoted, vsub_state)

# TODO: An IR for ${} might simplify these lengthy conditions
suffix_op = part.suffix_op
if (suffix_op and suffix_op.tag_() == suffix_op_e.Nullary and
#cast(Token, tmp).id in (Id.VOp0_a, Id.VOp0_Q)):
cast(Token, suffix_op).id == Id.VOp0_a):
# ${array@a} is a STRING, not an array
vsub_state.array_op_nullary = True

# 2. Bracket Op
val = self._EvalBracketOp(val, part, quoted, maybe_decay_array, vtest_place)
val = self._EvalBracketOp(val, part, quoted, vsub_state, vtest_place)

# Do the _EmptyStrOrError up front here, EXCEPT in the case of Kind.VTest
suffix_is_test = False
suffix_op = part.suffix_op
UP_op = suffix_op
if suffix_op is not None and suffix_op.tag_() == suffix_op_e.Unary:
suffix_op = cast(suffix_op__Unary, UP_op)
Expand All @@ -1217,7 +1218,7 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):

# ${!array[@]} to get indices/keys
val = self._Keys(val, part.token)
# already set maybe_decay_array ABOVE
# already set vsub_State.decay_array ABOVE
else:
# Process ${!ref}. SURPRISE: ${!a[0]} is an indirect expansion unlike
# ${!a[@]} !
Expand All @@ -1227,7 +1228,7 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
vtest_place.name = None
vtest_place.index = None

val = self._EvalVarRef(val, part.token, quoted, maybe_decay_array, vtest_place)
val = self._EvalVarRef(val, part.token, quoted, vsub_state, vtest_place)

if not suffix_is_test: # undef -> '' AFTER indirection
val = self._EmptyStrOrError(val, part.token)
Expand Down Expand Up @@ -1276,11 +1277,11 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
else:
raise AssertionError()

# After applying suffixes, process maybe_decay_array here.
# After applying suffixes, process decay_array here.
UP_val = val
if val.tag_() == value_e.MaybeStrArray:
array_val = cast(value__MaybeStrArray, UP_val)
if maybe_decay_array[0]:
if vsub_state.decay_array:
val = self._DecayArray(array_val)
else:
val = array_val
Expand Down Expand Up @@ -1330,7 +1331,7 @@ def EvalBracedVarSubToString(self, part):
def _EvalSimpleVarSub(self, token, part_vals, quoted):
# type: (Token, List[part_value_t], bool) -> None

maybe_decay_array = [False]
vsub_state = VarSubState()

# 1. Evaluate from (var_name, var_num, Token) -> defined, value
if token.id == Id.VSub_DollarName:
Expand All @@ -1350,14 +1351,14 @@ def _EvalSimpleVarSub(self, token, part_vals, quoted):
var_num = int(token.val[1:])
val = self._EvalVarNum(var_num)
else:
val = self._EvalSpecialVar(token.id, quoted, maybe_decay_array)
val = self._EvalSpecialVar(token.id, quoted, vsub_state)

#log('SIMPLE %s', part)
val = self._EmptyStrOrError(val, token)
UP_val = val
if val.tag_() == value_e.MaybeStrArray:
array_val = cast(value__MaybeStrArray, UP_val)
if maybe_decay_array[0]:
if vsub_state.decay_array:
val = self._DecayArray(array_val)
else:
val = array_val
Expand Down

0 comments on commit d6e7fcb

Please sign in to comment.