[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok
From: |
Dan Douglas |
Subject: |
Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok |
Date: |
Sun, 28 Jul 2013 03:09:55 -0500 |
User-agent: |
KMail/4.10.5 (Linux/3.10.1-pf+; KDE/4.10.5; x86_64; ; ) |
On Saturday, July 27, 2013 10:06:34 PM Chet Ramey wrote:
> I'm leaving things as they are for the time being.
I'd only change it if somebody thinks portability is more important than
backwards-compatibility. Currently this works most places:
typeset -ia arr
arr+=(1+1 2+2)
which avoids unsetting the attribute in those shells that do so without +=.
BTW, evaluation order can get quite complicated especially when integer
attributes are involved. This table reveals some interesting properties such as
Bash's short-circuiting and sometimes double-evaluation of parameter
expansions. I wrote the test runner in ksh:
------------------
output
Each testcase prints evaluation order for indexed array assignment contexts.
Each context is tested for expansions (represented by digits) and arithmetic
(letters), ordered from left to right within the expression. The output
corresponds to the way evaluation is re-ordered for each shell:
a[ $1 a ]=${b[ $2 b ]:=${c[ $3 c ]}} No attributes
a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} typeset -ia a
a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} typeset -ia b
a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} typeset -ia a
b
(( a[ $1 a ] = b[ $2 b ] ${c[ $3 c ]} )) No attributes
(( a[ $1 a ] = ${b[ $2 b ]:=c[ $3 c ]} )) typeset -ia b
a+=( [ $1 a ]=${b[ $2 b ]:=${c[ $3 c ]}} [ $4 d ]=$(( $5 e )) ) typeset -a a
a+=( [ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} [ $4 d ]=${5}e ) typeset -ia a
bash: 4.2.45(1)-release
2 b 3 c 2 b 1 a
2 b 3 2 b 1 a c
2 b 3 2 b c 1 a
2 b 3 2 b c 1 a c
1 2 3 c b a
1 2 b 3 2 b c c a
1 2 b 3 c 2 b 4 5 e a d
1 2 b 3 2 b 4 5 a c d e
bash: 4.3.0(1)-beta
2 b 3 c 2 b 1 a
2 b 3 2 b 1 a c
2 b 3 2 b c 1 a
2 b 3 2 b c 1 a c
1 2 3 c b a
1 2 b 3 2 b c c a
1 2 b 3 c 2 b 4 5 e a d
1 2 b 3 2 b 4 5 a c d e
ksh93: Version AIJM 93v- 2013-07-18
1 2 b a
1 2 b a
1 2 b a
1 2 b a
1 2 3 c b a
1 2 b a
1 2 b a 4 5 e d
1 2 b a 4 5 d e
mksh: @(#)MIRBSD KSH R47 2013/07/25
2 b 3 c 1 a
2 b 3 1 a c
2 b 3 c 1 a
2 b 3 c 1 a
1 2 3 c a b
1 2 b 3 c a
1 2 b 3 c 4 5 e a d
1 2 b 3 4 5 a c d e
zsh: 5.0.2
2 b 3 c 2 b 1 a
2 b 3 2 b 1 a c
2 b 1 a
2 b 1 a
1 2 3 c b a
1 2 b a
1 2 b 3 c 2 b 4 5 e
1 2 b 3 2 b 4 5
------------------
#!/usr/bin/env ksh
# Testcase runs in ksh93t or greater. Tests should run in any shell provided
# you can supply all the necessary workarounds, and they correctly interpret
# ksh93 printf %q output (requires $'...'). At least one level of recursive
# arithmetic variable evaluation must also be supported.
# Dan Douglas <ormaaj@gmail.com>
namespace main {
# e.g. add "set -x" to hacks
typeset -A shells=(
[mksh]=(
typeset -a hacks=('unset -v _')
vvar='print -r -- "mksh: $KSH_VERSION"'
)
[ksh]=(
typeset -a hacks=(
'typeset -a kshKludge alpha=({a..z})'
'function .sh.math.kshKludge x { printf "%s "
${alpha[x]}; }'
'function kshKludge.get {
.sh.value=${.sh.name}\(${.sh.subscript}\); }'
)
vvar='print -r -- "ksh93: ${.sh.version}"'
)
[zsh]=(
typeset -a hacks=('emulate ksh')
vvar='print -r -- "zsh: $ZSH_VERSION"'
)
[bash]=(
typeset -a hacks=('shopt -s extglob lastpipe')
vvar='printf %s\\n "bash: $BASH_VERSION"'
)
[bash43]=(
typeset -a hacks=('shopt -s extglob lastpipe')
vvar='printf %s\\n "bash: $BASH_VERSION"'
path=~/doc/programs/bash43
)
)
typeset -a tests=(
# Simple assignment:
(
docstring='a[ $1 a ]=${b[ $2 b ]:=${c[ $3 c ]}}'
setup=
testcase='a[$(printf "1 " >&3)x[0]]=${b[$(printf "2 "
>&3)${kshKludge[1]:-x[1]},0]:=${c[$(printf "3 " >&3)x[2]]}}'
)
# Integer array simple assignment:
(
docstring='a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]}'
setup='typeset -ia a'
testcase='a[$(printf "1 "
>&3)x[0]]=${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=c[$(printf "3 "
>&3)x[2]]}'
)
# simple assignment w/ integer PE assignment:
(
docstring='a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]}'
setup='typeset -ia b'
testcase='a[$(printf "1 "
>&3)x[0]]=${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=c[$(printf "3 "
>&3)x[2]]}'
)
# Both Integer array simple assignment + integer PE assignment:
(
docstring='a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]}'
setup='typeset -ia a b'
testcase='a[$(printf "1 "
>&3)x[0]]=${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=c[$(printf "3 "
>&3)x[2]]}'
)
# Array assignment within arithmetic context:
(
docstring='(( a[ $1 a ] = b[ $2 b ] ${c[ $3 c ]} ))'
setup=
testcase='((a[$(printf "1 " >&3)x[0]]=b[$(printf "2 "
>&3)x[1],1]${c[${kshKludge[2]:-x[2]}$(printf "3 " >&3),1]}))'
)
# Array assignment within arithmetic context + PE assignment:
(
docstring='(( a[ $1 a ] = ${b[ $2 b ]:=c[ $3 c ]} ))'
setup='typeset -ia b'
testcase='((a[$(printf "1 "
>&3)x[0]]=${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=c[$(printf "3 "
>&3)x[2],1]}))'
)
# Compound assignment
(
docstring='a+=( [ $1 a ]=${b[ $2 b ]:=${c[ $3 c ]}} [
$4 d ]=$(( $5 e )) )'
setup='typeset -a a'
testcase='a+=([x[0]$(printf "1 "
>&3),0]="${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=${c[$(printf "3 "
>&3)x[2],1]}}" [x[3]$(printf "4 " >&3)]="$((x[4]$(printf "5 " >&3)))")'
)
# Compound integer array assignment
(
docstring='a+=( [ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} [ $4 d
]=${5}e )'
setup='typeset -ia a'
testcase='a+=([x[0]$(printf "1 "
>&3),0]="${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=c[$(printf "3 "
>&3)x[2],1]}" [x[3]$(printf "4 " >&3)]="x[4]$(printf "5 " >&3)")'
)
)
# Allow sort-of Bash-like -v logic with printf. Nice ksh command
# substitution performance at the cost of stripping trailing newlines
function printfv {
if [[ $1 == -v && ${2:+_} ]]; then
nameref x=$2
shift 2
x=$(command printf "$@")
else
command printf "$@"
fi
}
# Combine commands to eval sequentially. Builds a "qball".
# ''eval $qball'' unravels it.
# Strings of nested escaping grow faster as nesting gets deeper!
# qball [ -v var ] var1 var2 var3 ...
function qball {
if [[ $1 == -v && ${2:+_} ]]; then
nameref ret=$2
typeset assign=ret
shift 2
else
typeset assign
fi
typeset x ref=x
while ! ${1:+false}; do
nameref var=$1
${2:+:} typeset ref=$assign
${var:+printfv ${ref:+-v "$ref"} 'eval %q'
"${x:+${x};}${var}"}
shift
done
}
# Build scripts from the given template vars and run the testcases.
# Normally I dislike "shell templates". This seems an acceptable usage.
function runTests {
nameref t=$1 s=$2
typeset ts sh
# Bundle setup + testcase pairs into an eval-able tuple. This
# guarantees sequential commands are safely concatenated into
one
# variable that may be evaluated within the same subshell.
# Print docstrings at the same time.
typeset -a allTests
for ts in "${!t[@]}"; do
allTests+=("$(qball ${t[ts].setup:+"t[$ts].setup"}
"t[${ts}].testcase")")
printf '%-65s %s\n' "${t[ts].docstring}"
"${t[ts].setup:-No attributes}"
done
echo
# This isn't as confusing as it looks. The outer heredoc is the
# testcase part, which only needs to expand once. The inner
heredoc is
# shell-specific code expanded for each iteration.
for sh in "${!s[@]}"; do
{
printf '%s\n' "${ { _=$("${s[$sh].path:-$sh}"
-s </dev/fd/0 2>&1 >&3); } 3>&1;}" ${_:+"${sh} errors:" "$_"}
echo
} <<-EOF
set -f
# Setup shell-specific hacks
typeset z
for z in $(printf '%q ' "${s[$sh].hacks[@]}"); do
eval "\$z"
done
# Print the shell version string
${s[$sh].vvar}
# Inject the testcase code (probably faster than
sourcing.)
$(</dev/fd/4)
EOF
done 3<&0 <<-EOF 4<&0 <&3-
function main {
# A variable to access through arithmetic recursion.
Sadly, not all
# shells support nice sequence expansions.
typeset -a $(print -rn -- {a..z})
x+=($(printf '%q ' 'c[$(printf "%s " '{a..z}' >&3)1]'))
# Run the actual tests.
typeset testCode
for testCode; do
(eval "\$testCode")
echo
done 3>&1
}
# Ensure single-letter globals are free
unset -v $(print -rn -- {a..z})
main $(printf '%q ' "${allTests[@]}")
EOF
}
cat <<-"EOF"
Each testcase prints evaluation order for indexed array assignment
contexts. Each context is tested for expansions (represented by digits)
and
arithmetic (letters), ordered from left to right within the expression.
The
output corresponds to the way evaluation is re-ordered for each shell:
EOF
echo
set -f # optional. We don't glob anywhere.
# typeset -ft runTests
runTests tests shells
}
# vim: set fenc=utf-8 ff=unix ft=sh :
--
Dan Douglas
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, (continued)
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, Chet Ramey, 2013/07/21
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, Dan Douglas, 2013/07/21
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, Linda Walsh, 2013/07/21
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, Dan Douglas, 2013/07/21
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, Linda Walsh, 2013/07/22
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, Dan Douglas, 2013/07/22
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, Chet Ramey, 2013/07/22
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, Linda Walsh, 2013/07/23
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, Chet Ramey, 2013/07/27
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok, Linda Walsh, 2013/07/28
- Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok,
Dan Douglas <=