Skip to content

Commit

Permalink
[osh-language] Implement @q on arrays, and tighten ${} language
Browse files Browse the repository at this point in the history
- The ${!prefix@} query was too loose on 2 counts!  Tightened it up
- Fix crash with undefined variable and @A
- Added more spec tests

Addresses one part of #780.
  • Loading branch information
Andy Chu committed Jun 27, 2020
1 parent 84c64b9 commit bfb28ee
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 41 deletions.
80 changes: 51 additions & 29 deletions osh/word_eval.py
Expand Up @@ -30,6 +30,7 @@
from core import error
from core import pyos
from core import pyutil
from core import ui
from qsn_ import qsn
from core.pyerror import log, e_die
from frontend import consts
Expand Down Expand Up @@ -893,20 +894,33 @@ def _Slice(self, val, op, var_name, part):
def _Nullary(self, val, op, var_name):
# type: (value_t, Token, Optional[str]) -> Tuple[value_t, bool]

UP_val = val
quoted2 = False
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)
with tagswitch(val) as case:
if case(value_e.Str):
val = cast(value__Str, UP_val)
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)
else:
e_die("Can't use @P on %s", ui.ValType(val)) # TODO: location

elif op_id == Id.VOp0_Q:
assert val.tag_() == value_e.Str, val
val = cast(value__Str, val)
val = value.Str(qsn.maybe_shell_encode(val.s))
# oddly, 'echo ${x@Q}' is equivalent to 'echo "${x@Q}"' in bash
quoted2 = True
with tagswitch(val) as case:
if case(value_e.Str):
val = cast(value__Str, UP_val)
val = value.Str(qsn.maybe_shell_encode(val.s))
# oddly, 'echo ${x@Q}' is equivalent to 'echo "${x@Q}"' in bash
quoted2 = True
elif case(value_e.MaybeStrArray):
val = cast(value__MaybeStrArray, UP_val)
q = ' '.join(qsn.maybe_shell_encode(s) for s in val.strs)
val = value.Str(q)
else:
e_die("Can't use @Q on %s", ui.ValType(val)) # TODO: location

elif op_id == Id.VOp0_a:
# We're ONLY simluating -a and -A, not -r -x -n for now. See
Expand All @@ -920,12 +934,13 @@ def _Nullary(self, val, op, var_name):

if var_name is not None: # e.g. ${?@a} is allowed
cell = self.mem.GetCell(var_name)
if cell.readonly:
chars.append('r')
if cell.exported:
chars.append('x')
if cell.nameref:
chars.append('n')
if cell:
if cell.readonly:
chars.append('r')
if cell.exported:
chars.append('x')
if cell.nameref:
chars.append('n')

val = value.Str(''.join(chars))

Expand Down Expand Up @@ -1110,20 +1125,24 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
# 1. Evaluate from (var_name, var_num, token Id) -> value
if part.token.id == Id.VSub_Name:
# Handle ${!prefix@} first, since that looks at names and not values
# Do NOT handle ${!A[@]@a} here!
if (part.prefix_op is not None and
part.bracket_op is None and
part.suffix_op is not None and
part.suffix_op.tag_() == suffix_op_e.Nullary):

names = self.mem.VarNamesStartingWith(part.token.val)
names.sort()

suffix_op_ = cast(Token, part.suffix_op)
if quoted and suffix_op_.id == Id.VOp3_At:
part_vals.append(part_value.Array(names))
else:
sep = self.splitter.GetJoinChar()
part_vals.append(part_value.String(sep.join(names), quoted, True))
return # EARLY RETURN
# ${!x@} but not ${!x@P}
if consts.GetKind(suffix_op_.id) == Kind.VOp3:
names = self.mem.VarNamesStartingWith(part.token.val)
names.sort()

suffix_op_ = cast(Token, part.suffix_op)
if quoted and suffix_op_.id == Id.VOp3_At:
part_vals.append(part_value.Array(names))
else:
sep = self.splitter.GetJoinChar()
part_vals.append(part_value.String(sep.join(names), quoted, True))
return # EARLY RETURN

var_name = part.token.val
# TODO: LINENO can use its own span_id!
Expand Down Expand Up @@ -1166,11 +1185,10 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
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 == Id.VOp0_a):
cast(Token, tmp).id in (Id.VOp0_a, Id.VOp0_Q)):
# ${array@a} is a string
# TODO: ${array[@]@Q} is valid but ${array@Q} requires compat_array
pass
# TODO: ${array[@]@Q} is valid but ${array@Q} requires
# compat_array
else:
e_die("Array %r can't be referred to as a scalar (without @ or *)",
var_name, part=part)
Expand All @@ -1195,9 +1213,13 @@ def _EvalBracedVarSub(self, part, part_vals, quoted):
# NOTE: The length operator followed by a suffix operator is a SYNTAX
# error.
val = self._ApplyPrefixOp(val, part.prefix_op, part.token)
if suffix_op:
# TODO: why does it go to the wrong location?
# It would also be nice to point to both operators!
e_die("Can't combine prefix and suffix op",
span_id=part.prefix_op.span_id)

quoted2 = False # another bit for @Q

if suffix_op:
op = suffix_op # could get rid of this alias

Expand Down
10 changes: 5 additions & 5 deletions spec/prompt.test.sh
Expand Up @@ -182,15 +182,15 @@ echo status=$?
$SH -c 'a=(x y); echo ${a@P}' dummy a b c
echo status=$?
## STDOUT:
status=1
status=1
status=1
## END
## OK bash STDOUT:
a b c
status=0
a b c
status=0
x
status=0
## END
## OK osh STDOUT:
status=1
status=1
status=1
## END
93 changes: 86 additions & 7 deletions spec/var-op-bash.test.sh
Expand Up @@ -50,7 +50,7 @@ test "$x" = "$new" && echo OK
OK
## END

#### ${array@Q}
#### ${array@Q} and ${array[@]@Q}
array=(x 'y\nz')
echo ${array@Q}
echo ${array[@]@Q}
Expand Down Expand Up @@ -123,6 +123,31 @@ echo [${?@a}]
[]
## END

#### undef and @P @Q @a
$SH -c 'echo ${undef@P}'
echo status=$?
$SH -c 'echo ${undef@Q}'
echo status=$?
$SH -c 'echo ${undef@a}'
echo status=$?
## STDOUT:

status=0

status=0

status=0
## END
## OK osh STDOUT:

status=0
''
status=0

status=0
## END


#### argv array and @P @Q @a
$SH -c 'echo ${@@P}' dummy a b c
echo status=$?
Expand All @@ -136,26 +161,80 @@ status=0
'a' 'b\nc'
status=0

status=0
## END
## OK osh STDOUT:
status=1
a $'b\\nc'
status=0
a
status=0
## END

#### assoc array and @P @Q @a

# note: "y z" causes a bug!
$SH -c 'declare -A A=(["x"]="y"); echo ${A@P} - ${A[@]@P} - ${!A[@]@P}'
$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@P} - ${A[@]@Q} - ${!A[@]@Q}'
$SH -c 'declare -A A=(["x"]="y"); echo ${A@Q} - ${A[@]@Q}'
echo status=$?

$SH -c 'declare -A A=(["x"]=y); echo ${A@a} - ${A[@]@a} - ${!A[@]@a}'
$SH -c 'declare -A A=(["x"]=y); echo ${A@a} - ${A[@]@a}'
echo status=$?
## STDOUT:
- y -
- y
status=0
- 'y'
status=0
- 'y' -
A - A
status=0
A - A -
## END
## OK osh STDOUT:
status=1
status=1
A - A
status=0
## END

#### ${!var@X} is a runtime error
# note: "y z" causes a bug!
$SH -c 'declare -A A=(["x"]="y"); echo ${#A[@]@P}'
if test $? -ne 0; then echo fail; fi

# note: "y z" causes a bug!
$SH -c 'declare -A A=(["x"]="y"); ${#A[@]@Q}'
if test $? -ne 0; then echo fail; fi

$SH -c 'declare -A A=(["x"]=y); ${#A[@]@a}'
if test $? -ne 0; then echo fail; fi
## STDOUT:
fail
fail
fail
## END

#### ${#var@X} is a parse error
# note: "y z" causes a bug!
$SH -c 'declare -A A=(["x"]="y"); echo ${#A[@]@P}'
if test $? -ne 0; then echo fail; fi

# note: "y z" causes a bug!
$SH -c 'declare -A A=(["x"]="y"); ${#A[@]@Q}'
if test $? -ne 0; then echo fail; fi

$SH -c 'declare -A A=(["x"]=y); ${#A[@]@a}'
if test $? -ne 0; then echo fail; fi
## STDOUT:
fail
fail
fail
## END

#### ${!A[@]@a} is weird
declare -A A=(["x"]=y)
echo x=${!A[@]@a}
## STDOUT:
x=
## END

0 comments on commit bfb28ee

Please sign in to comment.