Last active
April 27, 2024 22:07
-
-
Save ormaaj/4942297 to your computer and use it in GitHub Desktop.
Array evaluation order tests
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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"' | |
) | |
) | |
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' "${ { _=$("$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 | |
# typeset -ft runTests | |
runTests tests shells | |
} | |
# vim: set fenc=utf-8 ff=unix ft=sh : |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.42(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 | |
ksh93: Version AJM 93v- 2013-02-22 | |
1 2 b b a | |
1 2 b b a | |
1 2 b b a | |
1 2 b b a | |
1 2 3 c b a | |
1 2 b b a | |
1 2 b b a 4 5 e d | |
1 2 b b a 4 5 d e | |
mksh: @(#)MIRBSD KSH R44 2013/02/24 | |
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment