aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c89
-rw-r--r--shell/ash_test/ash-arith/arith-assign-in-varexp.right3
-rwxr-xr-xshell/ash_test/ash-arith/arith-assign-in-varexp.tests8
-rw-r--r--shell/ash_test/ash-arith/arith-assign-in-varexp1.right2
-rwxr-xr-xshell/ash_test/ash-arith/arith-assign-in-varexp1.tests9
-rw-r--r--shell/ash_test/ash-arith/arith-bignum1.right13
-rwxr-xr-xshell/ash_test/ash-arith/arith-bignum1.tests17
-rw-r--r--shell/ash_test/ash-arith/arith-comma1.right3
-rwxr-xr-xshell/ash_test/ash-arith/arith-comma1.tests6
-rw-r--r--shell/ash_test/ash-arith/arith-precedence1.right4
-rwxr-xr-xshell/ash_test/ash-arith/arith-precedence1.tests15
-rw-r--r--shell/ash_test/ash-arith/arith-ternary-assign.right1
-rwxr-xr-xshell/ash_test/ash-arith/arith-ternary-assign.tests3
-rw-r--r--shell/ash_test/ash-arith/arith-ternary-comma.right1
-rwxr-xr-xshell/ash_test/ash-arith/arith-ternary-comma.tests3
-rw-r--r--shell/ash_test/ash-arith/arith-ternary-preincr.right1
-rwxr-xr-xshell/ash_test/ash-arith/arith-ternary-preincr.tests3
-rw-r--r--shell/ash_test/ash-arith/arith-ternary1.right2
-rwxr-xr-xshell/ash_test/ash-arith/arith-ternary1.tests5
-rw-r--r--shell/ash_test/ash-arith/arith-ternary2.right3
-rwxr-xr-xshell/ash_test/ash-arith/arith-ternary2.tests7
-rw-r--r--shell/ash_test/ash-arith/arith-ternary3.right1
-rwxr-xr-xshell/ash_test/ash-arith/arith-ternary3.tests4
-rw-r--r--shell/ash_test/ash-arith/arith-ternary_nested.right1
-rwxr-xr-xshell/ash_test/ash-arith/arith-ternary_nested.tests2
-rw-r--r--shell/ash_test/ash-arith/arith-ternary_nested1.right1
-rwxr-xr-xshell/ash_test/ash-arith/arith-ternary_nested1.tests2
-rw-r--r--shell/ash_test/ash-arith/arith-ternary_nested2.right1
-rwxr-xr-xshell/ash_test/ash-arith/arith-ternary_nested2.tests2
-rw-r--r--shell/ash_test/ash-arith/arith-ternary_nested3.right2
-rwxr-xr-xshell/ash_test/ash-arith/arith-ternary_nested3.tests6
-rw-r--r--shell/ash_test/ash-arith/arith.right5
-rwxr-xr-xshell/ash_test/ash-arith/arith.tests6
-rw-r--r--shell/ash_test/ash-quoting/space_in_varexp1.right2
-rwxr-xr-xshell/ash_test/ash-quoting/space_in_varexp1.tests6
-rwxr-xr-xshell/ash_test/run-all21
-rw-r--r--shell/cttyhack.c2
-rw-r--r--shell/hush.c70
-rw-r--r--shell/hush_test/hush-arith/arith-assign-in-varexp.right3
-rwxr-xr-xshell/hush_test/hush-arith/arith-assign-in-varexp.tests8
-rw-r--r--shell/hush_test/hush-arith/arith-assign-in-varexp1.right2
-rwxr-xr-xshell/hush_test/hush-arith/arith-assign-in-varexp1.tests9
-rw-r--r--shell/hush_test/hush-arith/arith-bignum1.right13
-rwxr-xr-xshell/hush_test/hush-arith/arith-bignum1.tests17
-rw-r--r--shell/hush_test/hush-arith/arith-comma1.right3
-rwxr-xr-xshell/hush_test/hush-arith/arith-comma1.tests6
-rw-r--r--shell/hush_test/hush-arith/arith-precedence1.right4
-rwxr-xr-xshell/hush_test/hush-arith/arith-precedence1.tests15
-rw-r--r--shell/hush_test/hush-arith/arith-ternary-assign.right1
-rwxr-xr-xshell/hush_test/hush-arith/arith-ternary-assign.tests3
-rw-r--r--shell/hush_test/hush-arith/arith-ternary-comma.right1
-rwxr-xr-xshell/hush_test/hush-arith/arith-ternary-comma.tests3
-rw-r--r--shell/hush_test/hush-arith/arith-ternary-preincr.right1
-rwxr-xr-xshell/hush_test/hush-arith/arith-ternary-preincr.tests3
-rw-r--r--shell/hush_test/hush-arith/arith-ternary1.right2
-rwxr-xr-xshell/hush_test/hush-arith/arith-ternary1.tests5
-rw-r--r--shell/hush_test/hush-arith/arith-ternary2.right3
-rwxr-xr-xshell/hush_test/hush-arith/arith-ternary2.tests7
-rw-r--r--shell/hush_test/hush-arith/arith-ternary3.right1
-rwxr-xr-xshell/hush_test/hush-arith/arith-ternary3.tests4
-rw-r--r--shell/hush_test/hush-arith/arith-ternary_nested.right1
-rwxr-xr-xshell/hush_test/hush-arith/arith-ternary_nested.tests2
-rw-r--r--shell/hush_test/hush-arith/arith-ternary_nested1.right1
-rwxr-xr-xshell/hush_test/hush-arith/arith-ternary_nested1.tests2
-rw-r--r--shell/hush_test/hush-arith/arith-ternary_nested2.right1
-rwxr-xr-xshell/hush_test/hush-arith/arith-ternary_nested2.tests2
-rw-r--r--shell/hush_test/hush-arith/arith-ternary_nested3.right2
-rwxr-xr-xshell/hush_test/hush-arith/arith-ternary_nested3.tests6
-rw-r--r--shell/hush_test/hush-arith/arith.right5
-rwxr-xr-xshell/hush_test/hush-arith/arith.tests6
-rw-r--r--shell/hush_test/hush-quoting/space_in_varexp1.right2
-rwxr-xr-xshell/hush_test/hush-quoting/space_in_varexp1.tests6
-rw-r--r--shell/hush_test/hush-vars/readonly0.right4
-rwxr-xr-xshell/hush_test/run-all12
-rw-r--r--shell/math.c713
-rw-r--r--shell/math.h27
-rw-r--r--shell/shell_common.c21
-rw-r--r--shell/shell_common.h2
78 files changed, 874 insertions, 387 deletions
diff --git a/shell/ash.c b/shell/ash.c
index f3eb98d85..4d4137800 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -35,7 +35,7 @@
35//config: depends on !NOMMU 35//config: depends on !NOMMU
36//config: 36//config:
37//config:config ASH 37//config:config ASH
38//config: bool "ash (78 kb)" 38//config: bool "ash (80 kb)"
39//config: default y 39//config: default y
40//config: depends on !NOMMU 40//config: depends on !NOMMU
41//config: select SHELL_ASH 41//config: select SHELL_ASH
@@ -149,11 +149,23 @@
149//config: default y 149//config: default y
150//config: depends on SHELL_ASH 150//config: depends on SHELL_ASH
151//config: 151//config:
152//config:config ASH_SLEEP 152//
153//config: bool "sleep builtin" 153////config:config ASH_SLEEP
154//config: default y 154////config: bool "sleep builtin"
155//config: depends on SHELL_ASH 155////config: default y
156//config: 156////config: depends on SHELL_ASH
157////config:
158//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
159//Disabled for now. Has a few annoying problems:
160// * sleepcmd() -> sleep_main(), the parsing of bad arguments exits the shell.
161// * sleep_for_duration() in sleep_main() has to be interruptible for
162// ^C traps to work, which may be a problem for other users
163// of sleep_for_duration().
164// * BUT, if sleep_for_duration() is interruptible, then SIGCHLD interrupts it
165// as well (try "/bin/sleep 1 & sleep 10").
166// * sleep_main() must not allocate anything as ^C in ash longjmp's.
167// (currently, allocations are only on error paths, in message printing).
168//
157//config:config ASH_HELP 169//config:config ASH_HELP
158//config: bool "help builtin" 170//config: bool "help builtin"
159//config: default y 171//config: default y
@@ -599,7 +611,12 @@ struct globals_misc {
599 611
600 struct jmploc *exception_handler; 612 struct jmploc *exception_handler;
601 613
602 volatile int suppress_int; /* counter */ 614 /*volatile*/ int suppress_int; /* counter */
615 /* ^^^^^^^ removed "volatile" since on x86, gcc turns suppress_int++
616 * into ridiculous 3-insn sequence otherwise.
617 * We don't change suppress_int asyncronously (in a signal handler),
618 * but we do read it async.
619 */
603 volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */ 620 volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */
604#if !ENABLE_PLATFORM_MINGW32 621#if !ENABLE_PLATFORM_MINGW32
605 volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */ 622 volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */
@@ -954,7 +971,8 @@ int_on(void)
954{ 971{
955 barrier(); 972 barrier();
956 if (--suppress_int == 0 && pending_int) 973 if (--suppress_int == 0 && pending_int)
957 raise_interrupt(); 974 raise_interrupt(); /* does not return */
975 barrier();
958} 976}
959#if DEBUG_INTONOFF 977#if DEBUG_INTONOFF
960# define INT_ON do { \ 978# define INT_ON do { \
@@ -970,7 +988,8 @@ force_int_on(void)
970 barrier(); 988 barrier();
971 suppress_int = 0; 989 suppress_int = 0;
972 if (pending_int) 990 if (pending_int)
973 raise_interrupt(); 991 raise_interrupt(); /* does not return */
992 barrier();
974} 993}
975#define FORCE_INT_ON force_int_on() 994#define FORCE_INT_ON force_int_on()
976 995
@@ -980,7 +999,8 @@ force_int_on(void)
980 barrier(); \ 999 barrier(); \
981 suppress_int = (v); \ 1000 suppress_int = (v); \
982 if (suppress_int == 0 && pending_int) \ 1001 if (suppress_int == 0 && pending_int) \
983 raise_interrupt(); \ 1002 raise_interrupt(); /* does not return */ \
1003 barrier(); \
984} while (0) 1004} while (0)
985 1005
986 1006
@@ -2547,30 +2567,6 @@ getoptsreset(const char *value)
2547#endif 2567#endif
2548 2568
2549/* 2569/*
2550 * Compares two strings up to the first = or '\0'. The first
2551 * string must be terminated by '='; the second may be terminated by
2552 * either '=' or '\0'.
2553 */
2554static int
2555varcmp(const char *p, const char *q)
2556{
2557 int c, d;
2558
2559 while ((c = *p) == (d = *q)) {
2560 if (c == '\0' || c == '=')
2561 goto out;
2562 p++;
2563 q++;
2564 }
2565 if (c == '=')
2566 c = '\0';
2567 if (d == '=')
2568 d = '\0';
2569 out:
2570 return c - d;
2571}
2572
2573/*
2574 * Find the appropriate entry in the hash table from the name. 2570 * Find the appropriate entry in the hash table from the name.
2575 */ 2571 */
2576static struct var ** 2572static struct var **
@@ -2881,7 +2877,7 @@ setvar(const char *name, const char *val, int flags)
2881 p = mempcpy(nameeq, name, namelen); 2877 p = mempcpy(nameeq, name, namelen);
2882 if (val) { 2878 if (val) {
2883 *p++ = '='; 2879 *p++ = '=';
2884 memcpy(p, val, vallen); 2880 strcpy(p, val);
2885 } 2881 }
2886 vp = setvareq(nameeq, flags | VNOSAVE); 2882 vp = setvareq(nameeq, flags | VNOSAVE);
2887 INT_ON; 2883 INT_ON;
@@ -10485,7 +10481,7 @@ evaltree(union node *n, int flags)
10485 } 10481 }
10486 if (flags & EV_EXIT) { 10482 if (flags & EV_EXIT) {
10487 exexit: 10483 exexit:
10488 raise_exception(EXEND); 10484 raise_exception(EXEND); /* does not return */
10489 } 10485 }
10490 10486
10491 popstackmark(&smark); 10487 popstackmark(&smark);
@@ -11554,7 +11550,7 @@ evalcommand(union node *cmd, int flags)
11554 11550
11555 /* We have a redirection error. */ 11551 /* We have a redirection error. */
11556 if (spclbltin > 0) 11552 if (spclbltin > 0)
11557 raise_exception(EXERROR); 11553 raise_exception(EXERROR); /* does not return */
11558 11554
11559 goto out; 11555 goto out;
11560 } 11556 }
@@ -13347,18 +13343,19 @@ simplecmd(void)
13347 if (args && app == &args->narg.next 13343 if (args && app == &args->narg.next
13348 && !vars && !redir 13344 && !vars && !redir
13349 ) { 13345 ) {
13350 struct builtincmd *bcmd; 13346// struct builtincmd *bcmd;
13351 const char *name; 13347// const char *name;
13352 13348
13353 /* We have a function */ 13349 /* We have a function */
13354 if (IF_BASH_FUNCTION(!function_flag &&) readtoken() != TRP) 13350 if (IF_BASH_FUNCTION(!function_flag &&) readtoken() != TRP)
13355 raise_error_unexpected_syntax(TRP); 13351 raise_error_unexpected_syntax(TRP);
13356 name = n->narg.text; 13352//bash allows functions named "123", "..", "return"!
13357 if (!goodname(name) 13353// name = n->narg.text;
13358 || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd)) 13354// if (!goodname(name)
13359 ) { 13355// || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd))
13360 raise_error_syntax("bad function name"); 13356// ) {
13361 } 13357// raise_error_syntax("bad function name");
13358// }
13362 n->type = NDEFUN; 13359 n->type = NDEFUN;
13363 checkkwd = CHKNL | CHKKWD | CHKALIAS; 13360 checkkwd = CHKNL | CHKKWD | CHKALIAS;
13364 n->ndefun.text = n->narg.text; 13361 n->ndefun.text = n->narg.text;
@@ -15964,7 +15961,7 @@ procargs(char **argv)
15964 optlist[i] = 2; 15961 optlist[i] = 2;
15965 if (options(&login_sh)) { 15962 if (options(&login_sh)) {
15966 /* it already printed err message */ 15963 /* it already printed err message */
15967 raise_exception(EXERROR); 15964 raise_exception(EXERROR); /* does not return */
15968 } 15965 }
15969 xargv = argptr; 15966 xargv = argptr;
15970 xminusc = minusc; 15967 xminusc = minusc;
diff --git a/shell/ash_test/ash-arith/arith-assign-in-varexp.right b/shell/ash_test/ash-arith/arith-assign-in-varexp.right
new file mode 100644
index 000000000..06ac80a64
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-assign-in-varexp.right
@@ -0,0 +1,3 @@
120:20
2a=b=10
3b=10
diff --git a/shell/ash_test/ash-arith/arith-assign-in-varexp.tests b/shell/ash_test/ash-arith/arith-assign-in-varexp.tests
new file mode 100755
index 000000000..920aaa779
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-assign-in-varexp.tests
@@ -0,0 +1,8 @@
1exec 2>&1
2a='b=10'
3b=3
4# The variables should evaluate left-to-right,
5# thus b is set to 10 _before_ addition
6echo 20:$((a + b))
7echo "a=$a"
8echo "b=$b"
diff --git a/shell/ash_test/ash-arith/arith-assign-in-varexp1.right b/shell/ash_test/ash-arith/arith-assign-in-varexp1.right
new file mode 100644
index 000000000..1feb307d2
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-assign-in-varexp1.right
@@ -0,0 +1,2 @@
17:7
2x=3
diff --git a/shell/ash_test/ash-arith/arith-assign-in-varexp1.tests b/shell/ash_test/ash-arith/arith-assign-in-varexp1.tests
new file mode 100755
index 000000000..fc8ac9d97
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-assign-in-varexp1.tests
@@ -0,0 +1,9 @@
1exec 2>&1
2a='x=1'
3b='x=2'
4c='x=3'
5# The variables should evaluate immediately when they encountered,
6# not when they go into an operation. Here, order of evaluation
7# of names to numbers should be a,b,c - not b,c,a:
8echo 7:$((a+b*c))
9echo "x=$x"
diff --git a/shell/ash_test/ash-arith/arith-bignum1.right b/shell/ash_test/ash-arith/arith-bignum1.right
new file mode 100644
index 000000000..42a8016ec
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-bignum1.right
@@ -0,0 +1,13 @@
118 digits: 999999999999999999
219 digits: -8446744073709551617
320 digits: 7766279631452241919
418 digits- -999999999999999999
519 digits- 8446744073709551617
620 digits- -7766279631452241919
7Hex base#:
816 digits: 9876543210abcedf
917 digits: 876543210abcedfc
1018 digits: 76543210abcedfcc
1116 digits: 6789abcdef543121
1217 digits: 789abcdef5431204
1318 digits: 89abcdef54312034
diff --git a/shell/ash_test/ash-arith/arith-bignum1.tests b/shell/ash_test/ash-arith/arith-bignum1.tests
new file mode 100755
index 000000000..ef8f928bc
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-bignum1.tests
@@ -0,0 +1,17 @@
1exec 2>&1
2# If the number does not fit in 64 bits, bash uses truncated 64-bit value
3# (essentially, it does not check for overflow in "n = n * base + digit"
4# calculation).
5echo 18 digits: $((999999999999999999))
6echo 19 digits: $((9999999999999999999))
7echo 20 digits: $((99999999999999999999))
8echo 18 digits- $((-999999999999999999))
9echo 19 digits- $((-9999999999999999999))
10echo 20 digits- $((-99999999999999999999))
11echo "Hex base#:"
12printf '16 digits: %016x\n' $((16#9876543210abcedf))
13printf '17 digits: %016x\n' $((16#9876543210abcedfc))
14printf '18 digits: %016x\n' $((16#9876543210abcedfcc))
15printf '16 digits: %016x\n' $((-16#9876543210abcedf))
16printf '17 digits: %016x\n' $((-16#9876543210abcedfc))
17printf '18 digits: %016x\n' $((-16#9876543210abcedfcc))
diff --git a/shell/ash_test/ash-arith/arith-comma1.right b/shell/ash_test/ash-arith/arith-comma1.right
new file mode 100644
index 000000000..be1264cc0
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-comma1.right
@@ -0,0 +1,3 @@
110:10
2a=b=10
3b=10
diff --git a/shell/ash_test/ash-arith/arith-comma1.tests b/shell/ash_test/ash-arith/arith-comma1.tests
new file mode 100755
index 000000000..f86304303
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-comma1.tests
@@ -0,0 +1,6 @@
1exec 2>&1
2a='b=10'
3b=3
4echo 10:$((a,b))
5echo "a=$a"
6echo "b=$b"
diff --git a/shell/ash_test/ash-arith/arith-precedence1.right b/shell/ash_test/ash-arith/arith-precedence1.right
new file mode 100644
index 000000000..3f9320a13
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-precedence1.right
@@ -0,0 +1,4 @@
14:4
24:4
34:4
44:4
diff --git a/shell/ash_test/ash-arith/arith-precedence1.tests b/shell/ash_test/ash-arith/arith-precedence1.tests
new file mode 100755
index 000000000..bfef05292
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-precedence1.tests
@@ -0,0 +1,15 @@
1exec 2>&1
2# bash documentation says that precedence order is:
3# ...
4# expr ? expr1 : expr2
5# = *= /= %= += -= <<= >>= &= ^= |=
6# exprA , exprB
7# but in practice, the rules for expr1 and expr2 are different:
8# assignments and commas in expr1 have higher precedence than :?,
9# but in expr2 they haven't:
10# "v ? 1,2 : 3,4" is parsed as "(v ? (1,2) : 3),4"
11# "v ? a=2 : b=4" is parsed as "(v ? (a=1) : b)=4" (thus, this is a syntax error)
12echo 4:$((0 ? 1,2 : 3,4))
13echo 4:$((1 ? 1,2 : 3,4))
14echo 4:"$((0 ? 1,2 : 3,4))"
15echo 4:"$((1 ? 1,2 : 3,4))"
diff --git a/shell/ash_test/ash-arith/arith-ternary-assign.right b/shell/ash_test/ash-arith/arith-ternary-assign.right
new file mode 100644
index 000000000..6644d86bf
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary-assign.right
@@ -0,0 +1 @@
42:42
diff --git a/shell/ash_test/ash-arith/arith-ternary-assign.tests b/shell/ash_test/ash-arith/arith-ternary-assign.tests
new file mode 100755
index 000000000..fa18fe7b9
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary-assign.tests
@@ -0,0 +1,3 @@
1exec 2>&1
2a='@'
3echo 42:$((a=1?42:3,a))
diff --git a/shell/ash_test/ash-arith/arith-ternary-comma.right b/shell/ash_test/ash-arith/arith-ternary-comma.right
new file mode 100644
index 000000000..6644d86bf
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary-comma.right
@@ -0,0 +1 @@
42:42
diff --git a/shell/ash_test/ash-arith/arith-ternary-comma.tests b/shell/ash_test/ash-arith/arith-ternary-comma.tests
new file mode 100755
index 000000000..5e05b58c4
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary-comma.tests
@@ -0,0 +1,3 @@
1exec 2>&1
2x='@'
3echo 42:$((1?4:x,20*2+2))
diff --git a/shell/ash_test/ash-arith/arith-ternary-preincr.right b/shell/ash_test/ash-arith/arith-ternary-preincr.right
new file mode 100644
index 000000000..6644d86bf
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary-preincr.right
@@ -0,0 +1 @@
42:42
diff --git a/shell/ash_test/ash-arith/arith-ternary-preincr.tests b/shell/ash_test/ash-arith/arith-ternary-preincr.tests
new file mode 100755
index 000000000..3985c7079
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary-preincr.tests
@@ -0,0 +1,3 @@
1exec 2>&1
2x='@'
3echo 42:$((1?42:++x))
diff --git a/shell/ash_test/ash-arith/arith-ternary1.right b/shell/ash_test/ash-arith/arith-ternary1.right
new file mode 100644
index 000000000..6b751d7b8
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary1.right
@@ -0,0 +1,2 @@
142:42
2a=0
diff --git a/shell/ash_test/ash-arith/arith-ternary1.tests b/shell/ash_test/ash-arith/arith-ternary1.tests
new file mode 100755
index 000000000..3532ce54d
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary1.tests
@@ -0,0 +1,5 @@
1exec 2>&1
2a=0
3# The not-taken branch should not evaluate
4echo 42:$((1 ? 42 : (a+=2)))
5echo "a=$a"
diff --git a/shell/ash_test/ash-arith/arith-ternary2.right b/shell/ash_test/ash-arith/arith-ternary2.right
new file mode 100644
index 000000000..a549b1b5c
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary2.right
@@ -0,0 +1,3 @@
16:6
2a=b=+err+
3b=6
diff --git a/shell/ash_test/ash-arith/arith-ternary2.tests b/shell/ash_test/ash-arith/arith-ternary2.tests
new file mode 100755
index 000000000..cb3163932
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary2.tests
@@ -0,0 +1,7 @@
1exec 2>&1
2a='b=+err+'
3b=5
4# The not-taken branch should not parse variables
5echo 6:$((0 ? a : ++b))
6echo "a=$a"
7echo "b=$b"
diff --git a/shell/ash_test/ash-arith/arith-ternary3.right b/shell/ash_test/ash-arith/arith-ternary3.right
new file mode 100644
index 000000000..6644d86bf
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary3.right
@@ -0,0 +1 @@
42:42
diff --git a/shell/ash_test/ash-arith/arith-ternary3.tests b/shell/ash_test/ash-arith/arith-ternary3.tests
new file mode 100755
index 000000000..0bf9f3002
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary3.tests
@@ -0,0 +1,4 @@
1exec 2>&1
2# "EXPR ?..." should check _evaluated_ EXPR,
3# not its last value
4echo 42:$((1 < 1 ? -1 : 1 > 1 ? 1 : 42))
diff --git a/shell/ash_test/ash-arith/arith-ternary_nested.right b/shell/ash_test/ash-arith/arith-ternary_nested.right
new file mode 100644
index 000000000..aa54bd925
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary_nested.right
@@ -0,0 +1 @@
5:5
diff --git a/shell/ash_test/ash-arith/arith-ternary_nested.tests b/shell/ash_test/ash-arith/arith-ternary_nested.tests
new file mode 100755
index 000000000..eefc8e7ce
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary_nested.tests
@@ -0,0 +1,2 @@
1exec 2>&1
2echo 5:$((1?2?3?4?5:6:7:8:9))
diff --git a/shell/ash_test/ash-arith/arith-ternary_nested1.right b/shell/ash_test/ash-arith/arith-ternary_nested1.right
new file mode 100644
index 000000000..d80319695
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary_nested1.right
@@ -0,0 +1 @@
3:3
diff --git a/shell/ash_test/ash-arith/arith-ternary_nested1.tests b/shell/ash_test/ash-arith/arith-ternary_nested1.tests
new file mode 100755
index 000000000..469584bea
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary_nested1.tests
@@ -0,0 +1,2 @@
1exec 2>&1
2echo 3:$((1?(2?(3):4):5))
diff --git a/shell/ash_test/ash-arith/arith-ternary_nested2.right b/shell/ash_test/ash-arith/arith-ternary_nested2.right
new file mode 100644
index 000000000..d80319695
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary_nested2.right
@@ -0,0 +1 @@
3:3
diff --git a/shell/ash_test/ash-arith/arith-ternary_nested2.tests b/shell/ash_test/ash-arith/arith-ternary_nested2.tests
new file mode 100755
index 000000000..e8b8a9e1a
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary_nested2.tests
@@ -0,0 +1,2 @@
1exec 2>&1
2echo 3:$((0?1:2?3:4?5:6?7:8))
diff --git a/shell/ash_test/ash-arith/arith-ternary_nested3.right b/shell/ash_test/ash-arith/arith-ternary_nested3.right
new file mode 100644
index 000000000..1a34fde65
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary_nested3.right
@@ -0,0 +1,2 @@
142:42
2a=2:2
diff --git a/shell/ash_test/ash-arith/arith-ternary_nested3.tests b/shell/ash_test/ash-arith/arith-ternary_nested3.tests
new file mode 100755
index 000000000..6f753867e
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-ternary_nested3.tests
@@ -0,0 +1,6 @@
1exec 2>&1
2x='@'
3a=2
4# After processing nested ?:, outermost ?: should still remember to NOT evaluate a*=2
5echo 42:$((1?0?41:42:(a*=2)))
6echo "a=2:$a"
diff --git a/shell/ash_test/ash-arith/arith.right b/shell/ash_test/ash-arith/arith.right
index 61fcab55e..b2c3f56d8 100644
--- a/shell/ash_test/ash-arith/arith.right
+++ b/shell/ash_test/ash-arith/arith.right
@@ -80,8 +80,9 @@ other bases
8062 62 8062 62
8163 63 8163 63
82missing number after base 82missing number after base
830 0 83./arith.tests: line 161: arithmetic syntax error
84./arith.tests: line 162: arithmetic syntax error 84./arith.tests: line 162: arithmetic syntax error
85./arith.tests: line 163: arithmetic syntax error
85./arith.tests: line 164: divide by zero 86./arith.tests: line 164: divide by zero
86./arith.tests: let: line 165: arithmetic syntax error 87./arith.tests: let: line 165: arithmetic syntax error
87./arith.tests: line 166: arithmetic syntax error 88./arith.tests: line 166: arithmetic syntax error
@@ -92,7 +93,7 @@ ghi
92./arith.tests: line 190: arithmetic syntax error 93./arith.tests: line 190: arithmetic syntax error
9316 16 9416 16
94./arith.tests: line 195: arithmetic syntax error 95./arith.tests: line 195: arithmetic syntax error
95./arith.tests: line 196: malformed ?: operator 96./arith.tests: line 196: arithmetic syntax error
96./arith.tests: line 197: arithmetic syntax error 97./arith.tests: line 197: arithmetic syntax error
979 9 989 9
98./arith.tests: line 204: arithmetic syntax error 99./arith.tests: line 204: arithmetic syntax error
diff --git a/shell/ash_test/ash-arith/arith.tests b/shell/ash_test/ash-arith/arith.tests
index b9cb8ba4c..42cd7fdbf 100755
--- a/shell/ash_test/ash-arith/arith.tests
+++ b/shell/ash_test/ash-arith/arith.tests
@@ -155,12 +155,12 @@ echo 63 $(( 64#_ ))
155#ash# # weird bases (error) 155#ash# # weird bases (error)
156#ash# echo $(( 3425#56 )) 156#ash# echo $(( 3425#56 ))
157 157
158echo missing number after base
159echo 0 $(( 2# ))
160 158
161# these should generate errors 159# these should generate errors
160echo missing number after base
161( echo $(( 2# )) )
162( echo $(( 7 = 43 )) ) 162( echo $(( 7 = 43 )) )
163#ash# echo $(( 2#44 )) 163( echo $(( 2#44 )) )
164( echo $(( 44 / 0 )) ) 164( echo $(( 44 / 0 )) )
165( let 'jv += $iv' ) 165( let 'jv += $iv' )
166( echo $(( jv += \$iv )) ) 166( echo $(( jv += \$iv )) )
diff --git a/shell/ash_test/ash-quoting/space_in_varexp1.right b/shell/ash_test/ash-quoting/space_in_varexp1.right
new file mode 100644
index 000000000..a617a91c3
--- /dev/null
+++ b/shell/ash_test/ash-quoting/space_in_varexp1.right
@@ -0,0 +1,2 @@
11:'b c'
22:'b c'
diff --git a/shell/ash_test/ash-quoting/space_in_varexp1.tests b/shell/ash_test/ash-quoting/space_in_varexp1.tests
new file mode 100755
index 000000000..1589587d1
--- /dev/null
+++ b/shell/ash_test/ash-quoting/space_in_varexp1.tests
@@ -0,0 +1,6 @@
1a=b
2a=${a:+$a }c
3echo "1:'$a'"
4a=b
5a="${a:+$a }c"
6echo "2:'$a'"
diff --git a/shell/ash_test/run-all b/shell/ash_test/run-all
index 96703ef12..066327dc0 100755
--- a/shell/ash_test/run-all
+++ b/shell/ash_test/run-all
@@ -47,6 +47,7 @@ do_test()
47 # $1 but with / replaced by # so that it can be used as filename part 47 # $1 but with / replaced by # so that it can be used as filename part
48 noslash=`echo "$1" | sed 's:/:#:g'` 48 noslash=`echo "$1" | sed 's:/:#:g'`
49 ( 49 (
50 tret=0
50 cd "$1" || { echo "cannot cd $1!"; exit 1; } 51 cd "$1" || { echo "cannot cd $1!"; exit 1; }
51 for x in run-*; do 52 for x in run-*; do
52 test -f "$x" || continue 53 test -f "$x" || continue
@@ -69,13 +70,25 @@ do_test()
69 test -f "$name.right" || continue 70 test -f "$name.right" || continue
70# echo Running test: "$x" 71# echo Running test: "$x"
71 echo -n "$1/$x:" 72 echo -n "$1/$x:"
72 { 73 (
73 "$THIS_SH" "./$x" 2>&1 | \ 74 "$THIS_SH" "./$x" 2>&1 | \
74 grep -va "^ash: using fallback suid method$" >"$name.xx" 75 grep -va "^ash: using fallback suid method$" >"$name.xx"
76 r=$?
77 # filter C library differences
78 sed -i \
79 -e "/: invalid option /s:'::g" \
80 "$name.xx"
81 test $r -eq 77 && rm -f "$TOPDIR/$noslash-$x.fail" && exit 77
75 diff -u "$name.xx" "$name.right" >"$TOPDIR/$noslash-$x.fail" \ 82 diff -u "$name.xx" "$name.right" >"$TOPDIR/$noslash-$x.fail" \
76 && rm -f "$name.xx" "$TOPDIR/$noslash-$x.fail" 83 && rm -f "$name.xx" "$TOPDIR/$noslash-$x.fail"
77 } && echo " ok" || echo " fail" 84 )
85 case $? in
86 0) echo " ok";;
87 77) echo " skip (feature disabled)";;
88 *) echo " fail ($?)"; tret=1;;
89 esac
78 done 90 done
91 exit $tret
79 ) 92 )
80} 93}
81 94
@@ -103,4 +116,4 @@ else
103 done 116 done
104fi 117fi
105 118
106exit ${ret} 119exit $ret
diff --git a/shell/cttyhack.c b/shell/cttyhack.c
index b9ee59bd0..62dfb2bc2 100644
--- a/shell/cttyhack.c
+++ b/shell/cttyhack.c
@@ -5,7 +5,7 @@
5 * Licensed under GPLv2, see file LICENSE in this source tree. 5 * Licensed under GPLv2, see file LICENSE in this source tree.
6 */ 6 */
7//config:config CTTYHACK 7//config:config CTTYHACK
8//config: bool "cttyhack (2.4 kb)" 8//config: bool "cttyhack (2.7 kb)"
9//config: default y 9//config: default y
10//config: help 10//config: help
11//config: One common problem reported on the mailing list is the "can't 11//config: One common problem reported on the mailing list is the "can't
diff --git a/shell/hush.c b/shell/hush.c
index cdaa67a3b..8e632e0af 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -91,7 +91,7 @@
91 * in word = GLOB, quoting should be significant on char-by-char basis: a*cd"*" 91 * in word = GLOB, quoting should be significant on char-by-char basis: a*cd"*"
92 */ 92 */
93//config:config HUSH 93//config:config HUSH
94//config: bool "hush (68 kb)" 94//config: bool "hush (70 kb)"
95//config: default y 95//config: default y
96//config: select SHELL_HUSH 96//config: select SHELL_HUSH
97//config: help 97//config: help
@@ -2255,14 +2255,14 @@ static const char *get_cwd(int force)
2255/* 2255/*
2256 * Shell and environment variable support 2256 * Shell and environment variable support
2257 */ 2257 */
2258static struct variable **get_ptr_to_local_var(const char *name, unsigned len) 2258static struct variable **get_ptr_to_local_var(const char *name)
2259{ 2259{
2260 struct variable **pp; 2260 struct variable **pp;
2261 struct variable *cur; 2261 struct variable *cur;
2262 2262
2263 pp = &G.top_var; 2263 pp = &G.top_var;
2264 while ((cur = *pp) != NULL) { 2264 while ((cur = *pp) != NULL) {
2265 if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=') 2265 if (varcmp(cur->varstr, name) == 0)
2266 return pp; 2266 return pp;
2267 pp = &cur->next; 2267 pp = &cur->next;
2268 } 2268 }
@@ -2272,21 +2272,20 @@ static struct variable **get_ptr_to_local_var(const char *name, unsigned len)
2272static const char* FAST_FUNC get_local_var_value(const char *name) 2272static const char* FAST_FUNC get_local_var_value(const char *name)
2273{ 2273{
2274 struct variable **vpp; 2274 struct variable **vpp;
2275 unsigned len = strlen(name);
2276 2275
2277 if (G.expanded_assignments) { 2276 if (G.expanded_assignments) {
2278 char **cpp = G.expanded_assignments; 2277 char **cpp = G.expanded_assignments;
2279 while (*cpp) { 2278 while (*cpp) {
2280 char *cp = *cpp; 2279 char *cp = *cpp;
2281 if (strncmp(cp, name, len) == 0 && cp[len] == '=') 2280 if (varcmp(cp, name) == 0)
2282 return cp + len + 1; 2281 return strchr(cp, '=') + 1;
2283 cpp++; 2282 cpp++;
2284 } 2283 }
2285 } 2284 }
2286 2285
2287 vpp = get_ptr_to_local_var(name, len); 2286 vpp = get_ptr_to_local_var(name);
2288 if (vpp) 2287 if (vpp)
2289 return (*vpp)->varstr + len + 1; 2288 return strchr((*vpp)->varstr, '=') + 1;
2290 2289
2291 if (strcmp(name, "PPID") == 0) 2290 if (strcmp(name, "PPID") == 0)
2292 return utoa(G.root_ppid); 2291 return utoa(G.root_ppid);
@@ -2319,13 +2318,11 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
2319} 2318}
2320 2319
2321#if ENABLE_HUSH_GETOPTS 2320#if ENABLE_HUSH_GETOPTS
2322static void handle_changed_special_names(const char *name, unsigned name_len) 2321static void handle_changed_special_names(const char *name)
2323{ 2322{
2324 if (name_len == 6) { 2323 if (varcmp(name, "OPTIND") == 0) {
2325 if (strncmp(name, "OPTIND", 6) == 0) { 2324 G.getopt_count = 0;
2326 G.getopt_count = 0; 2325 return;
2327 return;
2328 }
2329 } 2326 }
2330} 2327}
2331#else 2328#else
@@ -2476,7 +2473,7 @@ static int set_local_var(char *str, unsigned flags)
2476 } 2473 }
2477 free(free_me); 2474 free(free_me);
2478 2475
2479 handle_changed_special_names(cur->varstr, name_len - 1); 2476 handle_changed_special_names(cur->varstr);
2480 2477
2481 return retval; 2478 return retval;
2482} 2479}
@@ -2499,16 +2496,14 @@ static void set_pwd_var(unsigned flag)
2499} 2496}
2500 2497
2501#if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS 2498#if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS
2502static int unset_local_var_len(const char *name, int name_len) 2499static int unset_local_var(const char *name)
2503{ 2500{
2504 struct variable *cur; 2501 struct variable *cur;
2505 struct variable **cur_pp; 2502 struct variable **cur_pp;
2506 2503
2507 cur_pp = &G.top_var; 2504 cur_pp = &G.top_var;
2508 while ((cur = *cur_pp) != NULL) { 2505 while ((cur = *cur_pp) != NULL) {
2509 if (strncmp(cur->varstr, name, name_len) == 0 2506 if (varcmp(cur->varstr, name) == 0) {
2510 && cur->varstr[name_len] == '='
2511 ) {
2512 if (cur->flg_read_only) { 2507 if (cur->flg_read_only) {
2513 bb_error_msg("%s: readonly variable", name); 2508 bb_error_msg("%s: readonly variable", name);
2514 return EXIT_FAILURE; 2509 return EXIT_FAILURE;
@@ -2527,15 +2522,10 @@ static int unset_local_var_len(const char *name, int name_len)
2527 } 2522 }
2528 2523
2529 /* Handle "unset LINENO" et al even if did not find the variable to unset */ 2524 /* Handle "unset LINENO" et al even if did not find the variable to unset */
2530 handle_changed_special_names(name, name_len); 2525 handle_changed_special_names(name);
2531 2526
2532 return EXIT_SUCCESS; 2527 return EXIT_SUCCESS;
2533} 2528}
2534
2535static int unset_local_var(const char *name)
2536{
2537 return unset_local_var_len(name, strlen(name));
2538}
2539#endif 2529#endif
2540 2530
2541 2531
@@ -2581,7 +2571,7 @@ static void set_vars_and_save_old(char **strings)
2581 eq = strchr(*s, '='); 2571 eq = strchr(*s, '=');
2582 if (HUSH_DEBUG && !eq) 2572 if (HUSH_DEBUG && !eq)
2583 bb_simple_error_msg_and_die("BUG in varexp4"); 2573 bb_simple_error_msg_and_die("BUG in varexp4");
2584 var_pp = get_ptr_to_local_var(*s, eq - *s); 2574 var_pp = get_ptr_to_local_var(*s);
2585 if (var_pp) { 2575 if (var_pp) {
2586 var_p = *var_pp; 2576 var_p = *var_pp;
2587 if (var_p->flg_read_only) { 2577 if (var_p->flg_read_only) {
@@ -4316,7 +4306,7 @@ static int done_word(struct parse_context *ctx)
4316 || endofname(command->argv[0])[0] != '\0' 4306 || endofname(command->argv[0])[0] != '\0'
4317 ) { 4307 ) {
4318 /* bash says just "not a valid identifier" */ 4308 /* bash says just "not a valid identifier" */
4319 syntax_error("bad variable name in for"); 4309 syntax_error("bad for loop variable");
4320 return 1; 4310 return 1;
4321 } 4311 }
4322 /* Force FOR to have just one word (variable name) */ 4312 /* Force FOR to have just one word (variable name) */
@@ -4693,6 +4683,11 @@ static int parse_group(struct parse_context *ctx,
4693 syntax_error_unexpected_ch(ch); 4683 syntax_error_unexpected_ch(ch);
4694 return -1; 4684 return -1;
4695 } 4685 }
4686//bash allows functions named "123", "..", "return"!
4687// if (endofname(command->argv[0])[0] != '\0') {
4688// syntax_error("bad function name");
4689// return -1;
4690// }
4696 nommu_addchr(&ctx->as_string, ch); 4691 nommu_addchr(&ctx->as_string, ch);
4697 command->cmd_type = CMD_FUNCDEF; 4692 command->cmd_type = CMD_FUNCDEF;
4698 goto skip; 4693 goto skip;
@@ -6398,7 +6393,7 @@ static NOINLINE int encode_then_append_var_plusminus(o_string *output, int n,
6398 if (!dest.o_expflags) { 6393 if (!dest.o_expflags) {
6399 if (ch == EOF) 6394 if (ch == EOF)
6400 break; 6395 break;
6401 if (!dquoted && strchr(G.ifs, ch)) { 6396 if (!dquoted && !(output->o_expflags & EXP_FLAG_SINGLEWORD) && strchr(G.ifs, ch)) {
6402 /* PREFIX${x:d${e}f ...} and we met space: expand "d${e}f" and start new word. 6397 /* PREFIX${x:d${e}f ...} and we met space: expand "d${e}f" and start new word.
6403 * do not assume we are at the start of the word (PREFIX above). 6398 * do not assume we are at the start of the word (PREFIX above).
6404 */ 6399 */
@@ -11178,7 +11173,7 @@ static int FAST_FUNC builtin_umask(char **argv)
11178} 11173}
11179#endif 11174#endif
11180 11175
11181#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_TRAP 11176#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_READONLY || ENABLE_HUSH_SET || ENABLE_HUSH_TRAP
11182static void print_escaped(const char *s) 11177static void print_escaped(const char *s)
11183{ 11178{
11184//TODO? bash "set" does not quote variables which contain only alnums and "%+,-./:=@_~", 11179//TODO? bash "set" does not quote variables which contain only alnums and "%+,-./:=@_~",
@@ -11215,7 +11210,7 @@ static int helper_export_local(char **argv, unsigned flags)
11215 if (*name_end == '\0') { 11210 if (*name_end == '\0') {
11216 struct variable *var, **vpp; 11211 struct variable *var, **vpp;
11217 11212
11218 vpp = get_ptr_to_local_var(name, name_end - name); 11213 vpp = get_ptr_to_local_var(name);
11219 var = vpp ? *vpp : NULL; 11214 var = vpp ? *vpp : NULL;
11220 11215
11221 if (flags & SETFLAG_UNEXPORT) { 11216 if (flags & SETFLAG_UNEXPORT) {
@@ -11317,8 +11312,8 @@ static int FAST_FUNC builtin_export(char **argv)
11317 11312
11318 if (!p) /* wtf? take next variable */ 11313 if (!p) /* wtf? take next variable */
11319 continue; 11314 continue;
11320 /* export var= */ 11315 /* "export VAR=" */
11321 printf("export %.*s", (int)(p - s) + 1, s); 11316 printf("%s %.*s", "export", (int)(p - s) + 1, s);
11322 print_escaped(p + 1); 11317 print_escaped(p + 1);
11323 putchar('\n'); 11318 putchar('\n');
11324# endif 11319# endif
@@ -11362,8 +11357,15 @@ static int FAST_FUNC builtin_readonly(char **argv)
11362 struct variable *e; 11357 struct variable *e;
11363 for (e = G.top_var; e; e = e->next) { 11358 for (e = G.top_var; e; e = e->next) {
11364 if (e->flg_read_only) { 11359 if (e->flg_read_only) {
11365//TODO: quote value: readonly VAR='VAL' 11360 const char *s = e->varstr;
11366 printf("readonly %s\n", e->varstr); 11361 const char *p = strchr(s, '=');
11362
11363 if (!p) /* wtf? take next variable */
11364 continue;
11365 /* "readonly VAR=" */
11366 printf("%s %.*s", "readonly", (int)(p - s) + 1, s);
11367 print_escaped(p + 1);
11368 putchar('\n');
11367 } 11369 }
11368 } 11370 }
11369 return EXIT_SUCCESS; 11371 return EXIT_SUCCESS;
diff --git a/shell/hush_test/hush-arith/arith-assign-in-varexp.right b/shell/hush_test/hush-arith/arith-assign-in-varexp.right
new file mode 100644
index 000000000..06ac80a64
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-assign-in-varexp.right
@@ -0,0 +1,3 @@
120:20
2a=b=10
3b=10
diff --git a/shell/hush_test/hush-arith/arith-assign-in-varexp.tests b/shell/hush_test/hush-arith/arith-assign-in-varexp.tests
new file mode 100755
index 000000000..920aaa779
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-assign-in-varexp.tests
@@ -0,0 +1,8 @@
1exec 2>&1
2a='b=10'
3b=3
4# The variables should evaluate left-to-right,
5# thus b is set to 10 _before_ addition
6echo 20:$((a + b))
7echo "a=$a"
8echo "b=$b"
diff --git a/shell/hush_test/hush-arith/arith-assign-in-varexp1.right b/shell/hush_test/hush-arith/arith-assign-in-varexp1.right
new file mode 100644
index 000000000..1feb307d2
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-assign-in-varexp1.right
@@ -0,0 +1,2 @@
17:7
2x=3
diff --git a/shell/hush_test/hush-arith/arith-assign-in-varexp1.tests b/shell/hush_test/hush-arith/arith-assign-in-varexp1.tests
new file mode 100755
index 000000000..fc8ac9d97
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-assign-in-varexp1.tests
@@ -0,0 +1,9 @@
1exec 2>&1
2a='x=1'
3b='x=2'
4c='x=3'
5# The variables should evaluate immediately when they encountered,
6# not when they go into an operation. Here, order of evaluation
7# of names to numbers should be a,b,c - not b,c,a:
8echo 7:$((a+b*c))
9echo "x=$x"
diff --git a/shell/hush_test/hush-arith/arith-bignum1.right b/shell/hush_test/hush-arith/arith-bignum1.right
new file mode 100644
index 000000000..42a8016ec
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-bignum1.right
@@ -0,0 +1,13 @@
118 digits: 999999999999999999
219 digits: -8446744073709551617
320 digits: 7766279631452241919
418 digits- -999999999999999999
519 digits- 8446744073709551617
620 digits- -7766279631452241919
7Hex base#:
816 digits: 9876543210abcedf
917 digits: 876543210abcedfc
1018 digits: 76543210abcedfcc
1116 digits: 6789abcdef543121
1217 digits: 789abcdef5431204
1318 digits: 89abcdef54312034
diff --git a/shell/hush_test/hush-arith/arith-bignum1.tests b/shell/hush_test/hush-arith/arith-bignum1.tests
new file mode 100755
index 000000000..ef8f928bc
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-bignum1.tests
@@ -0,0 +1,17 @@
1exec 2>&1
2# If the number does not fit in 64 bits, bash uses truncated 64-bit value
3# (essentially, it does not check for overflow in "n = n * base + digit"
4# calculation).
5echo 18 digits: $((999999999999999999))
6echo 19 digits: $((9999999999999999999))
7echo 20 digits: $((99999999999999999999))
8echo 18 digits- $((-999999999999999999))
9echo 19 digits- $((-9999999999999999999))
10echo 20 digits- $((-99999999999999999999))
11echo "Hex base#:"
12printf '16 digits: %016x\n' $((16#9876543210abcedf))
13printf '17 digits: %016x\n' $((16#9876543210abcedfc))
14printf '18 digits: %016x\n' $((16#9876543210abcedfcc))
15printf '16 digits: %016x\n' $((-16#9876543210abcedf))
16printf '17 digits: %016x\n' $((-16#9876543210abcedfc))
17printf '18 digits: %016x\n' $((-16#9876543210abcedfcc))
diff --git a/shell/hush_test/hush-arith/arith-comma1.right b/shell/hush_test/hush-arith/arith-comma1.right
new file mode 100644
index 000000000..be1264cc0
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-comma1.right
@@ -0,0 +1,3 @@
110:10
2a=b=10
3b=10
diff --git a/shell/hush_test/hush-arith/arith-comma1.tests b/shell/hush_test/hush-arith/arith-comma1.tests
new file mode 100755
index 000000000..f86304303
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-comma1.tests
@@ -0,0 +1,6 @@
1exec 2>&1
2a='b=10'
3b=3
4echo 10:$((a,b))
5echo "a=$a"
6echo "b=$b"
diff --git a/shell/hush_test/hush-arith/arith-precedence1.right b/shell/hush_test/hush-arith/arith-precedence1.right
new file mode 100644
index 000000000..3f9320a13
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-precedence1.right
@@ -0,0 +1,4 @@
14:4
24:4
34:4
44:4
diff --git a/shell/hush_test/hush-arith/arith-precedence1.tests b/shell/hush_test/hush-arith/arith-precedence1.tests
new file mode 100755
index 000000000..bfef05292
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-precedence1.tests
@@ -0,0 +1,15 @@
1exec 2>&1
2# bash documentation says that precedence order is:
3# ...
4# expr ? expr1 : expr2
5# = *= /= %= += -= <<= >>= &= ^= |=
6# exprA , exprB
7# but in practice, the rules for expr1 and expr2 are different:
8# assignments and commas in expr1 have higher precedence than :?,
9# but in expr2 they haven't:
10# "v ? 1,2 : 3,4" is parsed as "(v ? (1,2) : 3),4"
11# "v ? a=2 : b=4" is parsed as "(v ? (a=1) : b)=4" (thus, this is a syntax error)
12echo 4:$((0 ? 1,2 : 3,4))
13echo 4:$((1 ? 1,2 : 3,4))
14echo 4:"$((0 ? 1,2 : 3,4))"
15echo 4:"$((1 ? 1,2 : 3,4))"
diff --git a/shell/hush_test/hush-arith/arith-ternary-assign.right b/shell/hush_test/hush-arith/arith-ternary-assign.right
new file mode 100644
index 000000000..6644d86bf
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary-assign.right
@@ -0,0 +1 @@
42:42
diff --git a/shell/hush_test/hush-arith/arith-ternary-assign.tests b/shell/hush_test/hush-arith/arith-ternary-assign.tests
new file mode 100755
index 000000000..fa18fe7b9
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary-assign.tests
@@ -0,0 +1,3 @@
1exec 2>&1
2a='@'
3echo 42:$((a=1?42:3,a))
diff --git a/shell/hush_test/hush-arith/arith-ternary-comma.right b/shell/hush_test/hush-arith/arith-ternary-comma.right
new file mode 100644
index 000000000..6644d86bf
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary-comma.right
@@ -0,0 +1 @@
42:42
diff --git a/shell/hush_test/hush-arith/arith-ternary-comma.tests b/shell/hush_test/hush-arith/arith-ternary-comma.tests
new file mode 100755
index 000000000..5e05b58c4
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary-comma.tests
@@ -0,0 +1,3 @@
1exec 2>&1
2x='@'
3echo 42:$((1?4:x,20*2+2))
diff --git a/shell/hush_test/hush-arith/arith-ternary-preincr.right b/shell/hush_test/hush-arith/arith-ternary-preincr.right
new file mode 100644
index 000000000..6644d86bf
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary-preincr.right
@@ -0,0 +1 @@
42:42
diff --git a/shell/hush_test/hush-arith/arith-ternary-preincr.tests b/shell/hush_test/hush-arith/arith-ternary-preincr.tests
new file mode 100755
index 000000000..3985c7079
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary-preincr.tests
@@ -0,0 +1,3 @@
1exec 2>&1
2x='@'
3echo 42:$((1?42:++x))
diff --git a/shell/hush_test/hush-arith/arith-ternary1.right b/shell/hush_test/hush-arith/arith-ternary1.right
new file mode 100644
index 000000000..6b751d7b8
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary1.right
@@ -0,0 +1,2 @@
142:42
2a=0
diff --git a/shell/hush_test/hush-arith/arith-ternary1.tests b/shell/hush_test/hush-arith/arith-ternary1.tests
new file mode 100755
index 000000000..3532ce54d
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary1.tests
@@ -0,0 +1,5 @@
1exec 2>&1
2a=0
3# The not-taken branch should not evaluate
4echo 42:$((1 ? 42 : (a+=2)))
5echo "a=$a"
diff --git a/shell/hush_test/hush-arith/arith-ternary2.right b/shell/hush_test/hush-arith/arith-ternary2.right
new file mode 100644
index 000000000..a549b1b5c
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary2.right
@@ -0,0 +1,3 @@
16:6
2a=b=+err+
3b=6
diff --git a/shell/hush_test/hush-arith/arith-ternary2.tests b/shell/hush_test/hush-arith/arith-ternary2.tests
new file mode 100755
index 000000000..cb3163932
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary2.tests
@@ -0,0 +1,7 @@
1exec 2>&1
2a='b=+err+'
3b=5
4# The not-taken branch should not parse variables
5echo 6:$((0 ? a : ++b))
6echo "a=$a"
7echo "b=$b"
diff --git a/shell/hush_test/hush-arith/arith-ternary3.right b/shell/hush_test/hush-arith/arith-ternary3.right
new file mode 100644
index 000000000..6644d86bf
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary3.right
@@ -0,0 +1 @@
42:42
diff --git a/shell/hush_test/hush-arith/arith-ternary3.tests b/shell/hush_test/hush-arith/arith-ternary3.tests
new file mode 100755
index 000000000..0bf9f3002
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary3.tests
@@ -0,0 +1,4 @@
1exec 2>&1
2# "EXPR ?..." should check _evaluated_ EXPR,
3# not its last value
4echo 42:$((1 < 1 ? -1 : 1 > 1 ? 1 : 42))
diff --git a/shell/hush_test/hush-arith/arith-ternary_nested.right b/shell/hush_test/hush-arith/arith-ternary_nested.right
new file mode 100644
index 000000000..aa54bd925
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary_nested.right
@@ -0,0 +1 @@
5:5
diff --git a/shell/hush_test/hush-arith/arith-ternary_nested.tests b/shell/hush_test/hush-arith/arith-ternary_nested.tests
new file mode 100755
index 000000000..eefc8e7ce
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary_nested.tests
@@ -0,0 +1,2 @@
1exec 2>&1
2echo 5:$((1?2?3?4?5:6:7:8:9))
diff --git a/shell/hush_test/hush-arith/arith-ternary_nested1.right b/shell/hush_test/hush-arith/arith-ternary_nested1.right
new file mode 100644
index 000000000..d80319695
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary_nested1.right
@@ -0,0 +1 @@
3:3
diff --git a/shell/hush_test/hush-arith/arith-ternary_nested1.tests b/shell/hush_test/hush-arith/arith-ternary_nested1.tests
new file mode 100755
index 000000000..469584bea
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary_nested1.tests
@@ -0,0 +1,2 @@
1exec 2>&1
2echo 3:$((1?(2?(3):4):5))
diff --git a/shell/hush_test/hush-arith/arith-ternary_nested2.right b/shell/hush_test/hush-arith/arith-ternary_nested2.right
new file mode 100644
index 000000000..d80319695
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary_nested2.right
@@ -0,0 +1 @@
3:3
diff --git a/shell/hush_test/hush-arith/arith-ternary_nested2.tests b/shell/hush_test/hush-arith/arith-ternary_nested2.tests
new file mode 100755
index 000000000..e8b8a9e1a
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary_nested2.tests
@@ -0,0 +1,2 @@
1exec 2>&1
2echo 3:$((0?1:2?3:4?5:6?7:8))
diff --git a/shell/hush_test/hush-arith/arith-ternary_nested3.right b/shell/hush_test/hush-arith/arith-ternary_nested3.right
new file mode 100644
index 000000000..1a34fde65
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary_nested3.right
@@ -0,0 +1,2 @@
142:42
2a=2:2
diff --git a/shell/hush_test/hush-arith/arith-ternary_nested3.tests b/shell/hush_test/hush-arith/arith-ternary_nested3.tests
new file mode 100755
index 000000000..6f753867e
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-ternary_nested3.tests
@@ -0,0 +1,6 @@
1exec 2>&1
2x='@'
3a=2
4# After processing nested ?:, outermost ?: should still remember to NOT evaluate a*=2
5echo 42:$((1?0?41:42:(a*=2)))
6echo "a=2:$a"
diff --git a/shell/hush_test/hush-arith/arith.right b/shell/hush_test/hush-arith/arith.right
index a8612295e..d3a978611 100644
--- a/shell/hush_test/hush-arith/arith.right
+++ b/shell/hush_test/hush-arith/arith.right
@@ -82,7 +82,8 @@ other bases
8262 62 8262 62
8363 63 8363 63
84missing number after base 84missing number after base
850 0 85hush: arithmetic syntax error
86hush: arithmetic syntax error
86hush: arithmetic syntax error 87hush: arithmetic syntax error
87hush: divide by zero 88hush: divide by zero
88hush: can't execute 'let': No such file or directory 89hush: can't execute 'let': No such file or directory
@@ -94,7 +95,7 @@ ghi
94hush: arithmetic syntax error 95hush: arithmetic syntax error
9516 16 9616 16
96hush: arithmetic syntax error 97hush: arithmetic syntax error
97hush: malformed ?: operator 98hush: arithmetic syntax error
98hush: arithmetic syntax error 99hush: arithmetic syntax error
999 9 1009 9
100hush: arithmetic syntax error 101hush: arithmetic syntax error
diff --git a/shell/hush_test/hush-arith/arith.tests b/shell/hush_test/hush-arith/arith.tests
index 6b707486c..9f0399816 100755
--- a/shell/hush_test/hush-arith/arith.tests
+++ b/shell/hush_test/hush-arith/arith.tests
@@ -159,12 +159,12 @@ echo 63 $(( 64#_ ))
159#ash# # weird bases (error) 159#ash# # weird bases (error)
160#ash# echo $(( 3425#56 )) 160#ash# echo $(( 3425#56 ))
161 161
162echo missing number after base
163echo 0 $(( 2# ))
164 162
165# these should generate errors 163# these should generate errors
164echo missing number after base
165( echo $(( 2# )) )
166( echo $(( 7 = 43 )) ) 166( echo $(( 7 = 43 )) )
167#ash# echo $(( 2#44 )) 167( echo $(( 2#44 )) )
168( echo $(( 44 / 0 )) ) 168( echo $(( 44 / 0 )) )
169( let 'jv += $iv' ) 169( let 'jv += $iv' )
170( echo $(( jv += \$iv )) ) 170( echo $(( jv += \$iv )) )
diff --git a/shell/hush_test/hush-quoting/space_in_varexp1.right b/shell/hush_test/hush-quoting/space_in_varexp1.right
new file mode 100644
index 000000000..a617a91c3
--- /dev/null
+++ b/shell/hush_test/hush-quoting/space_in_varexp1.right
@@ -0,0 +1,2 @@
11:'b c'
22:'b c'
diff --git a/shell/hush_test/hush-quoting/space_in_varexp1.tests b/shell/hush_test/hush-quoting/space_in_varexp1.tests
new file mode 100755
index 000000000..1589587d1
--- /dev/null
+++ b/shell/hush_test/hush-quoting/space_in_varexp1.tests
@@ -0,0 +1,6 @@
1a=b
2a=${a:+$a }c
3echo "1:'$a'"
4a=b
5a="${a:+$a }c"
6echo "2:'$a'"
diff --git a/shell/hush_test/hush-vars/readonly0.right b/shell/hush_test/hush-vars/readonly0.right
index 8b750eb5f..7599698d2 100644
--- a/shell/hush_test/hush-vars/readonly0.right
+++ b/shell/hush_test/hush-vars/readonly0.right
@@ -1,5 +1,5 @@
1readonly a=A 1readonly a='A'
2readonly b=B 2readonly b='B'
3Ok:0 3Ok:0
4 4
5hush: a=A: readonly variable 5hush: a=A: readonly variable
diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all
index 3fbc7c531..7345fee43 100755
--- a/shell/hush_test/run-all
+++ b/shell/hush_test/run-all
@@ -29,7 +29,7 @@ fi
29 29
30eval $(sed -e '/^#/d' -e '/^$/d' -e 's:^:export :' .config) 30eval $(sed -e '/^#/d' -e '/^$/d' -e 's:^:export :' .config)
31 31
32PATH="`pwd`:$PATH" # for hush and recho/zecho/printenv 32PATH="`pwd`:$PATH" # for hush
33export PATH 33export PATH
34 34
35THIS_SH="`pwd`/hush" 35THIS_SH="`pwd`/hush"
@@ -67,7 +67,8 @@ do_test()
67# echo Running test: "$x" 67# echo Running test: "$x"
68 echo -n "$1/$x:" 68 echo -n "$1/$x:"
69 ( 69 (
70 "$THIS_SH" "./$x" >"$name.xx" 2>&1 70 "$THIS_SH" "./$x" 2>&1 | \
71 grep -va "^hush: using fallback suid method$" >"$name.xx"
71 r=$? 72 r=$?
72 # filter C library differences 73 # filter C library differences
73 sed -i \ 74 sed -i \
@@ -83,7 +84,7 @@ do_test()
83 *) echo " fail ($?)"; tret=1;; 84 *) echo " fail ($?)"; tret=1;;
84 esac 85 esac
85 done 86 done
86 exit ${tret} 87 exit $tret
87 ) 88 )
88} 89}
89 90
@@ -95,6 +96,9 @@ ret=0
95if [ $# -lt 1 ]; then 96if [ $# -lt 1 ]; then
96 # All sub directories 97 # All sub directories
97 modules=`ls -d hush-*` 98 modules=`ls -d hush-*`
99 # If you want to test hush against ash testsuite
100 # (have to copy ash_test dir to current dir first):
101 #modules=`ls -d hush-* ash_test/ash-*`
98 102
99 for module in $modules; do 103 for module in $modules; do
100 do_test $module || ret=1 104 do_test $module || ret=1
@@ -108,4 +112,4 @@ else
108 done 112 done
109fi 113fi
110 114
111exit ${ret} 115exit $ret
diff --git a/shell/math.c b/shell/math.c
index 76d22c9bd..e90a38f05 100644
--- a/shell/math.c
+++ b/shell/math.c
@@ -46,7 +46,6 @@
46 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 46 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
47 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 47 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
48 */ 48 */
49
50/* This is my infix parser/evaluator. It is optimized for size, intended 49/* This is my infix parser/evaluator. It is optimized for size, intended
51 * as a replacement for yacc-based parsers. However, it may well be faster 50 * as a replacement for yacc-based parsers. However, it may well be faster
52 * than a comparable parser written in yacc. The supported operators are 51 * than a comparable parser written in yacc. The supported operators are
@@ -61,7 +60,6 @@
61 * to the stack instead of adding them to a queue to end up with an 60 * to the stack instead of adding them to a queue to end up with an
62 * expression). 61 * expression).
63 */ 62 */
64
65/* 63/*
66 * Aug 24, 2001 Manuel Novoa III 64 * Aug 24, 2001 Manuel Novoa III
67 * 65 *
@@ -96,7 +94,6 @@
96 * 94 *
97 * Merge in Aaron's comments previously posted to the busybox list, 95 * Merge in Aaron's comments previously posted to the busybox list,
98 * modified slightly to take account of my changes to the code. 96 * modified slightly to take account of my changes to the code.
99 *
100 */ 97 */
101/* 98/*
102 * (C) 2003 Vladimir Oleynik <dzo@simtreas.ru> 99 * (C) 2003 Vladimir Oleynik <dzo@simtreas.ru>
@@ -116,6 +113,12 @@
116#include "libbb.h" 113#include "libbb.h"
117#include "math.h" 114#include "math.h"
118 115
116#if 1
117# define dbg(...) ((void)0)
118#else
119# define dbg(...) bb_error_msg(__VA_ARGS__)
120#endif
121
119typedef unsigned char operator; 122typedef unsigned char operator;
120 123
121/* An operator's token id is a bit of a bitfield. The lower 5 bits are the 124/* An operator's token id is a bit of a bitfield. The lower 5 bits are the
@@ -125,9 +128,13 @@ typedef unsigned char operator;
125 * Consider * and / 128 * Consider * and /
126 */ 129 */
127#define tok_decl(prec,id) (((id)<<5) | (prec)) 130#define tok_decl(prec,id) (((id)<<5) | (prec))
128#define PREC(op) ((op) & 0x1F) 131#define ID_SHIFT 5
132#define PREC(op) ((op) & 0x1f)
129 133
134#define PREC_LPAREN 0
130#define TOK_LPAREN tok_decl(0,0) 135#define TOK_LPAREN tok_decl(0,0)
136/* Precedence value of RPAREN is used only to distinguish it from LPAREN */
137#define TOK_RPAREN tok_decl(1,1)
131 138
132#define TOK_COMMA tok_decl(1,0) 139#define TOK_COMMA tok_decl(1,0)
133 140
@@ -135,22 +142,37 @@ typedef unsigned char operator;
135 * but there are 11 of them, which doesn't fit into 3 bits for unique id. 142 * but there are 11 of them, which doesn't fit into 3 bits for unique id.
136 * Abusing another precedence level: 143 * Abusing another precedence level:
137 */ 144 */
145#define PREC_ASSIGN1 2
138#define TOK_ASSIGN tok_decl(2,0) 146#define TOK_ASSIGN tok_decl(2,0)
139#define TOK_AND_ASSIGN tok_decl(2,1) 147#define TOK_AND_ASSIGN tok_decl(2,1)
140#define TOK_OR_ASSIGN tok_decl(2,2) 148#define TOK_OR_ASSIGN tok_decl(2,2)
141#define TOK_XOR_ASSIGN tok_decl(2,3) 149#define TOK_XOR_ASSIGN tok_decl(2,3)
142#define TOK_PLUS_ASSIGN tok_decl(2,4) 150#define TOK_ADD_ASSIGN tok_decl(2,4)
143#define TOK_MINUS_ASSIGN tok_decl(2,5) 151#define TOK_SUB_ASSIGN tok_decl(2,5)
144#define TOK_LSHIFT_ASSIGN tok_decl(2,6) 152#define TOK_LSHIFT_ASSIGN tok_decl(2,6)
145#define TOK_RSHIFT_ASSIGN tok_decl(2,7) 153#define TOK_RSHIFT_ASSIGN tok_decl(2,7)
146 154
155#define PREC_ASSIGN2 3
147#define TOK_MUL_ASSIGN tok_decl(3,0) 156#define TOK_MUL_ASSIGN tok_decl(3,0)
148#define TOK_DIV_ASSIGN tok_decl(3,1) 157/* "/" and "/=" ops have the same id bits */
158#define DIV_ID1 1
159#define TOK_DIV_ASSIGN tok_decl(3,DIV_ID1)
149#define TOK_REM_ASSIGN tok_decl(3,2) 160#define TOK_REM_ASSIGN tok_decl(3,2)
150 161
151#define fix_assignment_prec(prec) do { if (prec == 3) prec = 2; } while (0) 162#define fix_assignment_prec(prec) do { prec -= (prec == 3); } while (0)
152 163
153/* Ternary conditional operator is right associative too */ 164/* Ternary conditional operator is right associative too */
165/*
166 * bash documentation says that precedence order is:
167 * ...
168 * expr ? expr1 : expr2
169 * = *= /= %= += -= <<= >>= &= ^= |=
170 * exprA , exprB
171 * What it omits is that expr1 is parsed as if parenthesized
172 * (this matches the rules of ?: in C language):
173 * "v ? 1,2 : 3,4" is parsed as "(v ? (1,2) : 3),4"
174 * "v ? a=2 : b=4" is parsed as "(v ? (a=1) : b)=4" (thus, this is a syntax error)
175 */
154#define TOK_CONDITIONAL tok_decl(4,0) 176#define TOK_CONDITIONAL tok_decl(4,0)
155#define TOK_CONDITIONAL_SEP tok_decl(4,1) 177#define TOK_CONDITIONAL_SEP tok_decl(4,1)
156 178
@@ -179,7 +201,7 @@ typedef unsigned char operator;
179#define TOK_SUB tok_decl(13,1) 201#define TOK_SUB tok_decl(13,1)
180 202
181#define TOK_MUL tok_decl(14,0) 203#define TOK_MUL tok_decl(14,0)
182#define TOK_DIV tok_decl(14,1) 204#define TOK_DIV tok_decl(14,DIV_ID1)
183#define TOK_REM tok_decl(14,2) 205#define TOK_REM tok_decl(14,2)
184 206
185/* Exponent is right associative */ 207/* Exponent is right associative */
@@ -194,26 +216,25 @@ typedef unsigned char operator;
194#define TOK_UPLUS tok_decl(UNARYPREC+1,1) 216#define TOK_UPLUS tok_decl(UNARYPREC+1,1)
195 217
196#define PREC_PRE (UNARYPREC+2) 218#define PREC_PRE (UNARYPREC+2)
197 219#define TOK_PRE_INC tok_decl(PREC_PRE,0)
198#define TOK_PRE_INC tok_decl(PREC_PRE, 0) 220#define TOK_PRE_DEC tok_decl(PREC_PRE,1)
199#define TOK_PRE_DEC tok_decl(PREC_PRE, 1)
200 221
201#define PREC_POST (UNARYPREC+3) 222#define PREC_POST (UNARYPREC+3)
223#define TOK_POST_INC tok_decl(PREC_POST,0)
224#define TOK_POST_DEC tok_decl(PREC_POST,1)
202 225
203#define TOK_POST_INC tok_decl(PREC_POST, 0) 226/* TOK_VALUE marks a number, name, name++/name--, or (EXPR):
204#define TOK_POST_DEC tok_decl(PREC_POST, 1) 227 * IOW: something which can be used as the left side of a binary op.
205 228 * Since it's never pushed to opstack, its precedence does not matter.
206#define SPEC_PREC (UNARYPREC+4) 229 */
207 230#define TOK_VALUE tok_decl(PREC_POST,2)
208#define TOK_NUM tok_decl(SPEC_PREC, 0)
209#define TOK_RPAREN tok_decl(SPEC_PREC, 1)
210 231
211static int 232static int
212is_assign_op(operator op) 233is_assign_op(operator op)
213{ 234{
214 operator prec = PREC(op); 235 operator prec = PREC(op);
215 fix_assignment_prec(prec); 236 return prec == PREC_ASSIGN1
216 return prec == PREC(TOK_ASSIGN) 237 || prec == PREC_ASSIGN2
217 || prec == PREC_PRE 238 || prec == PREC_PRE
218 || prec == PREC_POST; 239 || prec == PREC_POST;
219} 240}
@@ -226,91 +247,107 @@ is_right_associative(operator prec)
226 || prec == PREC(TOK_CONDITIONAL); 247 || prec == PREC(TOK_CONDITIONAL);
227} 248}
228 249
229
230typedef struct { 250typedef struct {
231 arith_t val; 251 arith_t val;
232 /* We acquire second_val only when "expr1 : expr2" part 252 const char *var_name;
233 * of ternary ?: op is evaluated.
234 * We treat ?: as two binary ops: (expr ? (expr1 : expr2)).
235 * ':' produces a new value which has two parts, val and second_val;
236 * then '?' selects one of them based on its left side.
237 */
238 arith_t second_val;
239 char second_val_present;
240 /* If NULL then it's just a number, else it's a named variable */
241 char *var;
242} var_or_num_t; 253} var_or_num_t;
243 254
255#define VALID_NAME(name) (name)
256#define NOT_NAME(name) (!(name))
257
244typedef struct remembered_name { 258typedef struct remembered_name {
245 struct remembered_name *next; 259 struct remembered_name *next;
246 const char *var; 260 const char *var_name;
247} remembered_name; 261} remembered_name;
248 262
263static ALWAYS_INLINE int isalnum_(int c)
264{
265 return (isalnum(c) || c == '_');
266}
249 267
250static arith_t 268static arith_t
251evaluate_string(arith_state_t *math_state, const char *expr); 269evaluate_string(arith_state_t *math_state, const char *expr);
252 270
253static const char* 271static arith_t
254arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) 272arith_lookup_val(arith_state_t *math_state, const char *name, char *endname)
255{ 273{
256 if (t->var) { 274 char c;
257 const char *p = math_state->lookupvar(t->var); 275 const char *p;
258 if (p) { 276
259 remembered_name *cur; 277 c = *endname;
260 remembered_name cur_save; 278 *endname = '\0';
261 279 p = math_state->lookupvar(name);
262 /* did we already see this name? 280 *endname = c;
263 * testcase: a=b; b=a; echo $((a)) 281 if (p) {
264 */ 282 arith_t val;
265 for (cur = math_state->list_of_recursed_names; cur; cur = cur->next) { 283 size_t len = endname - name;
266 if (strcmp(cur->var, t->var) == 0) { 284 remembered_name *cur;
267 /* Yes */ 285 remembered_name remember;
268 return "expression recursion loop detected"; 286
269 } 287 /* did we already see this name?
288 * testcase: a=b; b=a; echo $((a))
289 */
290 for (cur = math_state->list_of_recursed_names; cur; cur = cur->next) {
291 if (strncmp(cur->var_name, name, len) == 0
292 && !isalnum_(cur->var_name[len])
293 ) {
294 /* yes */
295 math_state->errmsg = "expression recursion loop detected";
296 return -1;
270 } 297 }
298 }
271 299
272 /* push current var name */ 300 /* push current var name */
273 cur = math_state->list_of_recursed_names; 301 remember.var_name = name;
274 cur_save.var = t->var; 302 remember.next = math_state->list_of_recursed_names;
275 cur_save.next = cur; 303 math_state->list_of_recursed_names = &remember;
276 math_state->list_of_recursed_names = &cur_save;
277 304
278 /* recursively evaluate p as expression */ 305 /* recursively evaluate p as expression */
279 t->val = evaluate_string(math_state, p); 306 /* this sets math_state->errmsg on error */
307 val = evaluate_string(math_state, p);
280 308
281 /* pop current var name */ 309 /* pop current var name */
282 math_state->list_of_recursed_names = cur; 310 math_state->list_of_recursed_names = remember.next;
283 311
284 return math_state->errmsg; 312 return val;
285 }
286 /* treat undefined var as 0 */
287 t->val = 0;
288 } 313 }
314 /* treat undefined var as 0 */
289 return 0; 315 return 0;
290} 316}
291 317
292/* "Applying" a token means performing it on the top elements on the integer 318/* "Applying" a token means performing it on the top elements on the integer
293 * stack. For an unary operator it will only change the top element, but a 319 * stack. For an unary operator it will only change the top element,
294 * binary operator will pop two arguments and push the result */ 320 * a binary operator will pop two arguments and push the result,
321 * the ternary ?: op will pop three arguments and push the result.
322 */
295static NOINLINE const char* 323static NOINLINE const char*
296arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_or_num_t **numstackptr) 324arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_or_num_t **numstackptr)
297{ 325{
298#define NUMPTR (*numstackptr) 326#define NUMSTACKPTR (*numstackptr)
299 327
300 var_or_num_t *top_of_stack; 328 var_or_num_t *top_of_stack;
301 arith_t rez; 329 arith_t rez;
302 const char *err;
303 330
304 /* There is no operator that can work without arguments */ 331 /* There is no operator that can work without arguments */
305 if (NUMPTR == numstack) 332 if (NUMSTACKPTR == numstack)
306 goto err; 333 goto syntax_err;
307 334
308 top_of_stack = NUMPTR - 1; 335 top_of_stack = NUMSTACKPTR - 1;
309 336
310 /* Resolve name to value, if needed */ 337 if (op == TOK_CONDITIONAL_SEP) {
311 err = arith_lookup_val(math_state, top_of_stack); 338 /* "expr1 ? expr2 : expr3" operation */
312 if (err) 339 var_or_num_t *expr1 = &top_of_stack[-2];
313 return err; 340 NUMSTACKPTR = expr1 + 1;
341 if (expr1 < numstack) /* Example: $((2:3)) */
342 return "malformed ?: operator";
343 if (expr1->val != 0) /* select expr2 or expr3 */
344 top_of_stack--;
345 rez = top_of_stack->val;
346 top_of_stack = expr1;
347 goto ret_rez;
348 }
349 if (op == TOK_CONDITIONAL) /* Example: $((a ? b)) */
350 return "malformed ?: operator";
314 351
315 rez = top_of_stack->val; 352 rez = top_of_stack->val;
316 if (op == TOK_UMINUS) 353 if (op == TOK_UMINUS)
@@ -323,50 +360,30 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_
323 rez++; 360 rez++;
324 else if (op == TOK_POST_DEC || op == TOK_PRE_DEC) 361 else if (op == TOK_POST_DEC || op == TOK_PRE_DEC)
325 rez--; 362 rez--;
326 else if (op != TOK_UPLUS) { 363 else /*if (op != TOK_UPLUS) - always true, we drop TOK_UPLUS earlier */ {
327 /* Binary operators */ 364 /* Binary operators */
328 arith_t right_side_val; 365 arith_t right_side_val;
329 char bad_second_val;
330
331 /* Binary operators need two arguments */
332 if (top_of_stack == numstack)
333 goto err;
334 /* ...and they pop one */
335 NUMPTR = top_of_stack; /* this decrements NUMPTR */
336
337 bad_second_val = top_of_stack->second_val_present;
338 if (op == TOK_CONDITIONAL) { /* ? operation */
339 /* Make next if (...) protect against
340 * $((expr1 ? expr2)) - that is, missing ": expr" */
341 bad_second_val = !bad_second_val;
342 }
343 if (bad_second_val) {
344 /* Protect against $((expr <not_?_op> expr1 : expr2)) */
345 return "malformed ?: operator";
346 }
347 366
348 top_of_stack--; /* now points to left side */ 367 if (top_of_stack == numstack) /* have two arguments? */
368 goto syntax_err; /* no */
369
370 /* Pop numstack */
371 NUMSTACKPTR = top_of_stack; /* this decrements NUMSTACKPTR */
349 372
350 if (op != TOK_ASSIGN) { 373 if (math_state->evaluation_disabled) {
351 /* Resolve left side value (unless the op is '=') */ 374 dbg("binary op %02x skipped", op);
352 err = arith_lookup_val(math_state, top_of_stack); 375 return NULL;
353 if (err) 376 /* bash 5.2.12 does not execute "2/0" in disabled
354 return err; 377 * branches of ?: (and thus does not complain),
378 * but complains about negative exp: "2**-1".
379 * I don't think we need to emulate that.
380 */
355 } 381 }
356 382
383 top_of_stack--; /* now points to left side */
357 right_side_val = rez; 384 right_side_val = rez;
358 rez = top_of_stack->val; 385 rez = top_of_stack->val;
359 if (op == TOK_CONDITIONAL) /* ? operation */ 386 if (op == TOK_BOR || op == TOK_OR_ASSIGN)
360 rez = (rez ? right_side_val : top_of_stack[1].second_val);
361 else if (op == TOK_CONDITIONAL_SEP) { /* : operation */
362 if (top_of_stack == numstack) {
363 /* Protect against $((expr : expr)) */
364 return "malformed ?: operator";
365 }
366 top_of_stack->second_val_present = op;
367 top_of_stack->second_val = right_side_val;
368 }
369 else if (op == TOK_BOR || op == TOK_OR_ASSIGN)
370 rez |= right_side_val; 387 rez |= right_side_val;
371 else if (op == TOK_OR) 388 else if (op == TOK_OR)
372 rez = right_side_val || rez; 389 rez = right_side_val || rez;
@@ -394,9 +411,9 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_
394 rez = (rez <= right_side_val); 411 rez = (rez <= right_side_val);
395 else if (op == TOK_MUL || op == TOK_MUL_ASSIGN) 412 else if (op == TOK_MUL || op == TOK_MUL_ASSIGN)
396 rez *= right_side_val; 413 rez *= right_side_val;
397 else if (op == TOK_ADD || op == TOK_PLUS_ASSIGN) 414 else if (op == TOK_ADD || op == TOK_ADD_ASSIGN)
398 rez += right_side_val; 415 rez += right_side_val;
399 else if (op == TOK_SUB || op == TOK_MINUS_ASSIGN) 416 else if (op == TOK_SUB || op == TOK_SUB_ASSIGN)
400 rez -= right_side_val; 417 rez -= right_side_val;
401 else if (op == TOK_ASSIGN || op == TOK_COMMA) 418 else if (op == TOK_ASSIGN || op == TOK_COMMA)
402 rez = right_side_val; 419 rez = right_side_val;
@@ -405,14 +422,26 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_
405 if (right_side_val < 0) 422 if (right_side_val < 0)
406 return "exponent less than 0"; 423 return "exponent less than 0";
407 c = 1; 424 c = 1;
408 while (--right_side_val >= 0) 425 while (right_side_val != 0) {
426 if ((right_side_val & 1) == 0) {
427 /* this if() block is not necessary for correctness,
428 * but otherwise echo $((3**999999999999999999))
429 * takes a VERY LONG time
430 * (and it's not interruptible by ^C)
431 */
432 rez *= rez;
433 right_side_val >>= 1;
434 }
409 c *= rez; 435 c *= rez;
436 right_side_val--;
437 }
410 rez = c; 438 rez = c;
411 } 439 }
412 else if (right_side_val == 0) 440 else /*if (op == TOK_DIV || op == TOK_DIV_ASSIGN
413 return "divide by zero"; 441 || op == TOK_REM || op == TOK_REM_ASSIGN) - always true */
414 else if (op == TOK_DIV || op == TOK_DIV_ASSIGN 442 {
415 || op == TOK_REM || op == TOK_REM_ASSIGN) { 443 if (right_side_val == 0)
444 return "divide by zero";
416 /* 445 /*
417 * bash 4.2.45 x86 64bit: SEGV on 'echo $((2**63 / -1))' 446 * bash 4.2.45 x86 64bit: SEGV on 'echo $((2**63 / -1))'
418 * 447 *
@@ -424,42 +453,53 @@ arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_
424 * Make sure to at least not SEGV here: 453 * Make sure to at least not SEGV here:
425 */ 454 */
426 if (right_side_val == -1 455 if (right_side_val == -1
427 && rez << 1 == 0 /* MAX_NEGATIVE_INT or 0 */ 456 && (rez << 1) == 0 /* MAX_NEGATIVE_INT or 0 */
428 ) { 457 ) {
429 right_side_val = 1; 458 right_side_val = 1;
430 } 459 }
431 if (op == TOK_DIV || op == TOK_DIV_ASSIGN) 460 if (op & (DIV_ID1 << ID_SHIFT)) /* DIV or DIV_ASSIGN? */
432 rez /= right_side_val; 461 rez /= right_side_val;
433 else { 462 else
434 rez %= right_side_val; 463 rez %= right_side_val;
435 }
436 } 464 }
437 } 465 }
438 466
467 if (math_state->evaluation_disabled) {
468 dbg("unary op %02x skipped", op);
469 return NULL;
470 }
471
439 if (is_assign_op(op)) { 472 if (is_assign_op(op)) {
440 char buf[sizeof(arith_t)*3 + 2]; 473 char buf[sizeof(arith_t)*3 + 2];
441 474
442 if (top_of_stack->var == NULL) { 475 if (NOT_NAME(top_of_stack->var_name)) {
443 /* Hmm, 1=2 ? */ 476 /* Hmm, 1=2 ? */
444 goto err; 477 goto syntax_err;
445 } 478 }
446 /* Save to shell variable */ 479 /* Save to shell variable */
447 sprintf(buf, ARITH_FMT, rez); 480 sprintf(buf, ARITH_FMT, rez);
448 math_state->setvar(top_of_stack->var, buf); 481 {
449 /* After saving, make previous value for v++ or v-- */ 482 char *e = (char*)endofname(top_of_stack->var_name);
450 if (op == TOK_POST_INC) 483 char c = *e;
451 rez--; 484 *e = '\0';
452 if (op == TOK_POST_DEC) 485 math_state->setvar(top_of_stack->var_name, buf);
453 rez++; 486 *e = c;
487 }
488 /* VAR++ or VAR--? */
489 if (PREC(op) == PREC_POST) {
490 /* Do not store new value to stack (keep old value) */
491 goto ret_NULL;
492 }
454 } 493 }
455 494 ret_rez:
456 top_of_stack->val = rez; 495 top_of_stack->val = rez;
496 ret_NULL:
457 /* Erase var name, it is just a number now */ 497 /* Erase var name, it is just a number now */
458 top_of_stack->var = NULL; 498 top_of_stack->var_name = NULL;
459 return NULL; 499 return NULL;
460 err: 500 syntax_err:
461 return "arithmetic syntax error"; 501 return "arithmetic syntax error";
462#undef NUMPTR 502#undef NUMSTACKPTR
463} 503}
464 504
465/* longest must be first */ 505/* longest must be first */
@@ -479,8 +519,8 @@ static const char op_tokens[] ALIGN1 = {
479 '*','=', 0, TOK_MUL_ASSIGN, 519 '*','=', 0, TOK_MUL_ASSIGN,
480 '/','=', 0, TOK_DIV_ASSIGN, 520 '/','=', 0, TOK_DIV_ASSIGN,
481 '%','=', 0, TOK_REM_ASSIGN, 521 '%','=', 0, TOK_REM_ASSIGN,
482 '+','=', 0, TOK_PLUS_ASSIGN, 522 '+','=', 0, TOK_ADD_ASSIGN,
483 '-','=', 0, TOK_MINUS_ASSIGN, 523 '-','=', 0, TOK_SUB_ASSIGN,
484 '-','-', 0, TOK_POST_DEC, 524 '-','-', 0, TOK_POST_DEC,
485 '^','=', 0, TOK_XOR_ASSIGN, 525 '^','=', 0, TOK_XOR_ASSIGN,
486 '+','+', 0, TOK_POST_INC, 526 '+','+', 0, TOK_POST_INC,
@@ -497,7 +537,6 @@ static const char op_tokens[] ALIGN1 = {
497 '+', 0, TOK_ADD, 537 '+', 0, TOK_ADD,
498 '-', 0, TOK_SUB, 538 '-', 0, TOK_SUB,
499 '^', 0, TOK_BXOR, 539 '^', 0, TOK_BXOR,
500 /* uniq */
501 '~', 0, TOK_BNOT, 540 '~', 0, TOK_BNOT,
502 ',', 0, TOK_COMMA, 541 ',', 0, TOK_COMMA,
503 '?', 0, TOK_CONDITIONAL, 542 '?', 0, TOK_CONDITIONAL,
@@ -506,41 +545,26 @@ static const char op_tokens[] ALIGN1 = {
506 '(', 0, TOK_LPAREN, 545 '(', 0, TOK_LPAREN,
507 0 546 0
508}; 547};
509#define ptr_to_rparen (&op_tokens[sizeof(op_tokens)-7]) 548#define END_POINTER (&op_tokens[sizeof(op_tokens)-1])
510 549
511#if ENABLE_FEATURE_SH_MATH_BASE 550#if ENABLE_FEATURE_SH_MATH_BASE
512static arith_t strto_arith_t(const char *nptr, char **endptr) 551static arith_t parse_with_base(const char *nptr, char **endptr, unsigned base)
513{ 552{
514 unsigned base; 553 arith_t n = 0;
515 arith_t n; 554 const char *start = nptr;
516
517# if ENABLE_FEATURE_SH_MATH_64
518 n = strtoull(nptr, endptr, 0);
519# else
520 n = strtoul(nptr, endptr, 0);
521# endif
522 if (**endptr != '#'
523 || (*nptr < '1' || *nptr > '9')
524 || (n < 2 || n > 64)
525 ) {
526 return n;
527 }
528 555
529 /* It's "N#nnnn" or "NN#nnnn" syntax, NN can't start with 0,
530 * NN is in 2..64 range.
531 */
532 base = (unsigned)n;
533 n = 0;
534 nptr = *endptr + 1;
535 for (;;) { 556 for (;;) {
536 unsigned digit = (unsigned)*nptr - '0'; 557 unsigned digit = (unsigned)*nptr - '0';
537 if (digit >= 10 /* not 0..9 */ 558 if (digit >= 10 /* not 0..9 */
538 && digit <= 'z' - '0' /* needed to reject e.g. $((64#~)) */ 559 && digit <= 'z' - '0' /* reject e.g. $((64#~)) */
539 ) { 560 ) {
540 /* in bases up to 36, case does not matter for a-z */ 561 /* current char is one of :;<=>?@A..Z[\]^_`a..z */
562
563 /* in bases up to 36, case does not matter for a-z,
564 * map @A..Z and `a..z to 9..35: */
541 digit = (unsigned)(*nptr | 0x20) - ('a' - 10); 565 digit = (unsigned)(*nptr | 0x20) - ('a' - 10);
542 if (base > 36 && *nptr <= '_') { 566 if (base > 36 && *nptr <= '_') {
543 /* otherwise, A-Z,@,_ are 36-61,62,63 */ 567 /* base > 36: A-Z,@,_ are 36-61,62,63 */
544 if (*nptr == '_') 568 if (*nptr == '_')
545 digit = 63; 569 digit = 63;
546 else if (*nptr == '@') 570 else if (*nptr == '@')
@@ -551,8 +575,8 @@ static arith_t strto_arith_t(const char *nptr, char **endptr)
551 break; /* error: one of [\]^ */ 575 break; /* error: one of [\]^ */
552 } 576 }
553 //bb_error_msg("ch:'%c'%d digit:%u", *nptr, *nptr, digit); 577 //bb_error_msg("ch:'%c'%d digit:%u", *nptr, *nptr, digit);
554 //if (digit < 10) - example where we need this? 578 if (digit < 10) /* reject e.g. $((36#@)) */
555 // break; 579 break;
556 } 580 }
557 if (digit >= base) 581 if (digit >= base)
558 break; 582 break;
@@ -560,15 +584,55 @@ static arith_t strto_arith_t(const char *nptr, char **endptr)
560 n = n * base + digit; 584 n = n * base + digit;
561 nptr++; 585 nptr++;
562 } 586 }
563 /* Note: we do not set errno on bad chars, we just set a pointer
564 * to the first invalid char. For example, this allows
565 * "N#" (empty "nnnn" part): 64#+1 is a valid expression,
566 * it means 64# + 1, whereas 64#~... is not, since ~ is not a valid
567 * operator.
568 */
569 *endptr = (char*)nptr; 587 *endptr = (char*)nptr;
588 /* "64#" and "64#+1" used to be valid expressions, but bash 5.2.15
589 * no longer allow such, detect this:
590 */
591// NB: bash allows $((0x)), this is probably a bug...
592 if (nptr == start)
593 *endptr = NULL; /* there weren't any digits, bad */
570 return n; 594 return n;
571} 595}
596
597static arith_t strto_arith_t(const char *nptr, char **endptr)
598{
599/* NB: we do not use strtoull here to be bash-compatible:
600 * $((99999999999999999999)) is 7766279631452241919
601 * (the 64-bit truncated value).
602 */
603 unsigned base;
604
605 /* nptr[0] is '0'..'9' here */
606
607 base = nptr[0] - '0';
608 if (base == 0) { /* nptr[0] is '0' */
609 base = 8;
610 if ((nptr[1] | 0x20) == 'x') {
611 base = 16;
612 nptr += 2;
613 }
614// NB: bash allows $((0x)), this is probably a bug...
615 return parse_with_base(nptr, endptr, base);
616 }
617
618 /* base is 1..9 here */
619
620 if (nptr[1] == '#') {
621 if (base > 1)
622 return parse_with_base(nptr + 2, endptr, base);
623 /* else: "1#NN", bash says "invalid arithmetic base" */
624 }
625
626 if (isdigit(nptr[1]) && nptr[2] == '#') {
627 base = 10 * base + (nptr[1] - '0');
628 /* base is at least 10 here */
629 if (base <= 64)
630 return parse_with_base(nptr + 3, endptr, base);
631 /* else: bash says "invalid arithmetic base" */
632 }
633
634 return parse_with_base(nptr, endptr, 10);
635}
572#else /* !ENABLE_FEATURE_SH_MATH_BASE */ 636#else /* !ENABLE_FEATURE_SH_MATH_BASE */
573# if ENABLE_FEATURE_SH_MATH_64 637# if ENABLE_FEATURE_SH_MATH_64
574# define strto_arith_t(nptr, endptr) strtoull(nptr, endptr, 0) 638# define strto_arith_t(nptr, endptr) strtoull(nptr, endptr, 0)
@@ -580,23 +644,52 @@ static arith_t strto_arith_t(const char *nptr, char **endptr)
580static arith_t 644static arith_t
581evaluate_string(arith_state_t *math_state, const char *expr) 645evaluate_string(arith_state_t *math_state, const char *expr)
582{ 646{
647 /* Stack of integers/names */
648 var_or_num_t *numstack, *numstackptr;
649 /* Stack of operator tokens */
650 operator *opstack, *opstackptr;
651 /* To detect whether we are after a "value": */
583 operator lasttok; 652 operator lasttok;
653 /* To insert implicit () in ?: ternary op: */
654 operator insert_op = 0xff;
655 unsigned ternary_level = 0;
584 const char *errmsg; 656 const char *errmsg;
585 const char *start_expr = expr = skip_whitespace(expr); 657 const char *start_expr = expr = skip_whitespace(expr);
586 unsigned expr_len = strlen(expr) + 2; 658
587 /* Stack of integers */ 659 {
588 /* The proof that there can be no more than strlen(startbuf)/2+1 660 unsigned expr_len = strlen(expr);
589 * integers in any given correct or incorrect expression 661 /* If LOTS of whitespace, do not blow up the estimation */
590 * is left as an exercise to the reader. */ 662 const char *p = expr;
591 var_or_num_t *const numstack = alloca((expr_len / 2) * sizeof(numstack[0])); 663 while (*p) {
592 var_or_num_t *numstackptr = numstack; 664 /* in a run of whitespace, count only 1st char */
593 /* Stack of operator tokens */ 665 if (isspace(*p)) {
594 operator *const stack = alloca(expr_len * sizeof(stack[0])); 666 while (p++, isspace(*p))
595 operator *stackptr = stack; 667 expr_len--;
668 } else {
669 p++;
670 }
671 }
672 dbg("expr:'%s' expr_len:%u", expr, expr_len);
673 /* expr_len deep opstack is needed. Think "------------7".
674 * Only "?" operator temporarily needs two opstack slots
675 * (IOW: more than one slot), but its second slot (LPAREN)
676 * is popped off when ":" is reached.
677 */
678 expr_len++; /* +1 for 1st LPAREN. See what $((1?)) pushes to opstack */
679 opstackptr = opstack = alloca(expr_len * sizeof(opstack[0]));
680 /* There can be no more than (expr_len/2 + 1)
681 * integers/names in any given correct or incorrect expression.
682 * (modulo "09", "0v" cases where 2 chars are 2 ints/names,
683 * but we have code to detect that early)
684 */
685 expr_len = (expr_len / 2)
686 + 1 /* "1+2" has two nums, 2 = len/2+1, NOT len/2 */;
687 numstackptr = numstack = alloca(expr_len * sizeof(numstack[0]));
688 }
596 689
597 /* Start with a left paren */ 690 /* Start with a left paren */
598 *stackptr++ = lasttok = TOK_LPAREN; 691 dbg("(%d) op:TOK_LPAREN", (int)(opstackptr - opstack));
599 errmsg = NULL; 692 *opstackptr++ = lasttok = TOK_LPAREN;
600 693
601 while (1) { 694 while (1) {
602 const char *p; 695 const char *p;
@@ -607,8 +700,7 @@ evaluate_string(arith_state_t *math_state, const char *expr)
607 if (*expr == '\0') { 700 if (*expr == '\0') {
608 if (expr == start_expr) { 701 if (expr == start_expr) {
609 /* Null expression */ 702 /* Null expression */
610 numstack->val = 0; 703 return 0;
611 goto ret;
612 } 704 }
613 705
614 /* This is only reached after all tokens have been extracted from the 706 /* This is only reached after all tokens have been extracted from the
@@ -616,46 +708,71 @@ evaluate_string(arith_state_t *math_state, const char *expr)
616 * are to be applied in order. At the end, there should be a final 708 * are to be applied in order. At the end, there should be a final
617 * result on the integer stack */ 709 * result on the integer stack */
618 710
619 if (expr != ptr_to_rparen + 1) { 711 if (expr != END_POINTER) {
620 /* If we haven't done so already, 712 /* If we haven't done so already,
621 * append a closing right paren 713 * append a closing right paren
622 * and let the loop process it */ 714 * and let the loop process it */
623 expr = ptr_to_rparen; 715 expr = END_POINTER;
624//bb_error_msg("expr=')'"); 716 op = TOK_RPAREN;
625 continue; 717 goto tok_found1;
626 } 718 }
627 /* At this point, we're done with the expression */ 719 /* At this point, we're done with the expression */
628 if (numstackptr != numstack + 1) { 720 if (numstackptr != numstack + 1) {
629 /* ...but if there isn't, it's bad */ 721 /* if there is not exactly one result, it's bad */
630 goto err; 722 /* Example: $((1 2)) */
723 goto syntax_err;
631 } 724 }
632 goto ret; 725 return numstack->val;
633 } 726 }
634 727
635 p = endofname(expr); 728 p = endofname(expr);
636 if (p != expr) { 729 if (p != expr) {
637 /* Name */ 730 /* Name */
638 size_t var_name_size = (p - expr) + 1; /* +1 for NUL */ 731 if (!math_state->evaluation_disabled) {
639 numstackptr->var = alloca(var_name_size); 732 numstackptr->var_name = expr;
640 safe_strncpy(numstackptr->var, expr, var_name_size); 733 dbg("[%d] var:'%.*s'", (int)(numstackptr - numstack), (int)(p - expr), expr);
641//bb_error_msg("var:'%s'", numstackptr->var); 734 expr = skip_whitespace(p);
642 expr = p; 735 /* If it is not followed by "=" operator... */
643 num: 736 if (expr[0] != '=' /* not "=..." */
644 numstackptr->second_val_present = 0; 737 || expr[1] == '=' /* or "==..." */
738 ) {
739 /* Evaluate variable to value */
740 arith_t val = arith_lookup_val(math_state, numstackptr->var_name, (char*)p);
741 if (math_state->errmsg)
742 return val; /* -1 */
743 numstackptr->val = val;
744 }
745 } else {
746 dbg("[%d] var:IGNORED", (int)(numstackptr - numstack));
747 expr = p;
748 numstackptr->var_name = NULL; /* not needed, paranoia */
749 numstackptr->val = 0; /* not needed, paranoia */
750 }
751 push_value:
645 numstackptr++; 752 numstackptr++;
646 lasttok = TOK_NUM; 753 lasttok = TOK_VALUE;
647 continue; 754 continue;
648 } 755 }
649 756
650 if (isdigit(*expr)) { 757 if (isdigit(*expr)) {
651 /* Number */ 758 /* Number */
652 numstackptr->var = NULL; 759 char *end;
653 errno = 0; 760 numstackptr->var_name = NULL;
654 numstackptr->val = strto_arith_t(expr, (char**) &expr); 761 /* code is smaller compared to using &expr here: */
655//bb_error_msg("val:%lld", numstackptr->val); 762 numstackptr->val = strto_arith_t(expr, &end);
656 if (errno) 763 expr = end;
657 numstackptr->val = 0; /* bash compat */ 764 dbg("[%d] val:%lld", (int)(numstackptr - numstack), numstackptr->val);
658 goto num; 765 if (!expr) /* example: $((10#)) */
766 goto syntax_err;
767 /* A number can't be followed by another number, or a variable name.
768 * We'd catch this later anyway, but this would require numstack[]
769 * to be ~twice as deep to handle strings where _every_ char is
770 * a new number or name.
771 * Examples: "09" is two numbers, "0v" is number and name.
772 */
773 if (isalnum(*expr) || *expr == '_')
774 goto syntax_err;
775 goto push_value;
659 } 776 }
660 777
661 /* Should be an operator */ 778 /* Should be an operator */
@@ -671,10 +788,11 @@ evaluate_string(arith_state_t *math_state, const char *expr)
671 if ((expr[0] == '+' || expr[0] == '-') 788 if ((expr[0] == '+' || expr[0] == '-')
672 && (expr[1] == expr[0]) 789 && (expr[1] == expr[0])
673 ) { 790 ) {
674 if (numstackptr == numstack || !numstackptr[-1].var) { /* not a VAR++ */ 791 if (numstackptr == numstack || NOT_NAME(numstackptr[-1].var_name)) {
792 /* not a VAR++ */
675 char next = skip_whitespace(expr + 2)[0]; 793 char next = skip_whitespace(expr + 2)[0];
676 if (!(isalpha(next) || next == '_')) { /* not a ++VAR */ 794 if (!(isalpha(next) || next == '_')) {
677 //bb_error_msg("special %c%c", expr[0], expr[0]); 795 /* not a ++VAR */
678 op = (expr[0] == '+' ? TOK_ADD : TOK_SUB); 796 op = (expr[0] == '+' ? TOK_ADD : TOK_SUB);
679 expr++; 797 expr++;
680 goto tok_found1; 798 goto tok_found1;
@@ -704,27 +822,41 @@ evaluate_string(arith_state_t *math_state, const char *expr)
704 if (*p == '\0') { 822 if (*p == '\0') {
705 /* No next element, operator not found */ 823 /* No next element, operator not found */
706 //math_state->syntax_error_at = expr; 824 //math_state->syntax_error_at = expr;
707 goto err; 825 goto syntax_err;
708 } 826 }
709 } 827 }
828 /* NB: expr now points past the operator */
710 tok_found: 829 tok_found:
711 op = p[1]; /* fetch TOK_foo value */ 830 op = p[1]; /* fetch TOK_foo value */
712 tok_found1:
713 /* NB: expr now points past the operator */
714 831
715 /* post grammar: a++ reduce to num */ 832 /* Special rule for "? EXPR :"
716 if (lasttok == TOK_POST_INC || lasttok == TOK_POST_DEC) 833 * "EXPR in the middle of ? : is parsed as if parenthesized"
717 lasttok = TOK_NUM; 834 * (this quirk originates in C grammar, I think).
835 */
836 if (op == TOK_CONDITIONAL) {
837 insert_op = TOK_LPAREN;
838 dbg("insert_op=%02x", insert_op);
839 }
840 if (op == TOK_CONDITIONAL_SEP) {
841 insert_op = op;
842 op = TOK_RPAREN;
843 dbg("insert_op=%02x op=%02x", insert_op, op);
844 }
845 tok_found1:
846 /* NAME++ is a "value" (something suitable for a binop) */
847 if (PREC(lasttok) == PREC_POST)
848 lasttok = TOK_VALUE;
718 849
719 /* Plus and minus are binary (not unary) _only_ if the last 850 /* Plus and minus are binary (not unary) _only_ if the last
720 * token was a number, or a right paren (which pretends to be 851 * token was a "value". Think about it. It makes sense.
721 * a number, since it evaluates to one). Think about it. 852 */
722 * It makes sense. */ 853 if (lasttok != TOK_VALUE) {
723 if (lasttok != TOK_NUM) {
724 switch (op) { 854 switch (op) {
725 case TOK_ADD: 855 case TOK_ADD:
726 op = TOK_UPLUS; 856 //op = TOK_UPLUS;
727 break; 857 //break;
858 /* Unary plus does nothing, do not even push it to opstack */
859 continue;
728 case TOK_SUB: 860 case TOK_SUB:
729 op = TOK_UMINUS; 861 op = TOK_UMINUS;
730 break; 862 break;
@@ -744,80 +876,137 @@ evaluate_string(arith_state_t *math_state, const char *expr)
744 * stack until we find an operator with a lesser priority than the 876 * stack until we find an operator with a lesser priority than the
745 * one we have just extracted. If op is right-associative, 877 * one we have just extracted. If op is right-associative,
746 * then stop "applying" on the equal priority too. 878 * then stop "applying" on the equal priority too.
747 * Left paren is given the lowest priority so it will never be 879 * Left paren will never be "applied" in this way.
748 * "applied" in this way.
749 */ 880 */
750 prec = PREC(op); 881 prec = PREC(op);
751//bb_error_msg("prec:%02x", prec); 882 if (prec != PREC_LPAREN && prec < UNARYPREC) {
752 if ((prec > 0 && prec < UNARYPREC) || prec == SPEC_PREC) { 883 /* Binary, ternary or RPAREN */
753 /* not left paren or unary */ 884 if (lasttok != TOK_VALUE) {
754 if (lasttok != TOK_NUM) { 885 /* Must be preceded by a value.
755 /* binary op must be preceded by a num */ 886 * $((2 2 + * 3)) would be accepted without this.
756 goto err; 887 */
888 goto syntax_err;
757 } 889 }
758 /* The algorithm employed here is simple: while we don't 890 /* if op is RPAREN:
759 * hit an open paren nor the bottom of the stack, pop 891 * while opstack is not empty:
760 * tokens and apply them */ 892 * pop prev_op
761 while (stackptr != stack) { 893 * if prev_op is LPAREN (finished evaluating (EXPR)):
762 operator prev_op = *--stackptr; 894 * goto N
895 * evaluate prev_op on top of numstack
896 * BUG (unpaired RPAREN)
897 * else (op is not RPAREN):
898 * while opstack is not empty:
899 * pop prev_op
900 * if can't evaluate prev_op (it is lower precedence than op):
901 * push prev_op back
902 * goto C
903 * evaluate prev_op on top of numstack
904 * C:if op is "?": check result, set disable flag if needed
905 * push op
906 * N:loop to parse the rest of string
907 */
908 while (opstackptr != opstack) {
909 operator prev_op = *--opstackptr;
763 if (op == TOK_RPAREN) { 910 if (op == TOK_RPAREN) {
764//bb_error_msg("op == TOK_RPAREN");
765 if (prev_op == TOK_LPAREN) { 911 if (prev_op == TOK_LPAREN) {
766//bb_error_msg("prev_op == TOK_LPAREN"); 912 /* Erase var name: for example, (VAR) = 1 is not valid */
767//bb_error_msg(" %p %p numstackptr[-1].var:'%s'", numstack, numstackptr-1, numstackptr[-1].var); 913 numstackptr[-1].var_name = NULL;
768 if (numstackptr[-1].var) { 914 /* (EXPR) is a "value": next operator directly after
769 /* Expression is (var), lookup now */ 915 * close paren should be considered binary
770 errmsg = arith_lookup_val(math_state, &numstackptr[-1]); 916 */
771 if (errmsg) 917 lasttok = TOK_VALUE;
772 goto err_with_custom_msg;
773 /* Erase var name: (var) is just a number, for example, (var) = 1 is not valid */
774 numstackptr[-1].var = NULL;
775 }
776 /* Any operator directly after a
777 * close paren should consider itself binary */
778 lasttok = TOK_NUM;
779 goto next; 918 goto next;
780 } 919 }
781//bb_error_msg("prev_op != TOK_LPAREN"); 920 /* Not (y), but ...x~y). Fall through to evaluate x~y */
782 } else { 921 } else {
783 operator prev_prec = PREC(prev_op); 922 operator prev_prec = PREC(prev_op);
784//bb_error_msg("op != TOK_RPAREN");
785 fix_assignment_prec(prec); 923 fix_assignment_prec(prec);
786 fix_assignment_prec(prev_prec); 924 fix_assignment_prec(prev_prec);
787 if (prev_prec < prec 925 if (prev_prec < prec
788 || (prev_prec == prec && is_right_associative(prec)) 926 || (prev_prec == prec && is_right_associative(prec))
789 ) { 927 ) {
790 stackptr++; 928 /* ...x~y@. push @ on opstack */
791 break; 929 opstackptr++; /* undo removal of ~ op */
930 goto check_cond;
792 } 931 }
932 /* else: ...x~y@. Evaluate x~y, replace it on stack with result. Then repeat */
793 } 933 }
794//bb_error_msg("arith_apply(prev_op:%02x)", prev_op); 934 dbg("arith_apply(prev_op:%02x, numstack:%d)", prev_op, (int)(numstackptr - numstack));
795 errmsg = arith_apply(math_state, prev_op, numstack, &numstackptr); 935 errmsg = arith_apply(math_state, prev_op, numstack, &numstackptr);
796 if (errmsg) 936 if (errmsg)
797 goto err_with_custom_msg; 937 goto err_with_custom_msg;
938dbg(" numstack:%d val:%lld '%s'", (int)(numstackptr - numstack), numstackptr[-1].val, numstackptr[-1].var_name);
939 if (prev_op == TOK_CONDITIONAL_SEP) {
940 /* We just executed ":" */
941 /* Remove "?" from opstack too, not just ":" */
942 opstackptr--;
943 if (*opstackptr != TOK_CONDITIONAL) {
944 /* Example: $((1,2:3)) */
945 errmsg = "malformed ?: operator";
946 goto err_with_custom_msg;
947 }
948 /* Example: a=1?2:3,a. We just executed ":".
949 * Prevent assignment from being still disabled.
950 */
951 if (ternary_level == math_state->evaluation_disabled) {
952 math_state->evaluation_disabled = 0;
953 dbg("':' executed: evaluation_disabled=CLEAR");
954 }
955 ternary_level--;
956 }
957 } /* while (opstack not empty) */
958
959 if (op == TOK_RPAREN) /* unpaired RPAREN? */
960 goto syntax_err;
961 check_cond:
962 if (op == TOK_CONDITIONAL) {
963 /* We just now evaluated EXPR before "?".
964 * Should we disable evaluation now?
965 */
966 ternary_level++;
967 if (numstackptr[-1].val == 0 && !math_state->evaluation_disabled) {
968 math_state->evaluation_disabled = ternary_level;
969 dbg("'?' entered: evaluation_disabled=%u", math_state->evaluation_disabled);
970 }
971 }
972 } /* if */
973 /* else: LPAREN or UNARY: push it on opstack */
974
975 /* Push this operator to opstack */
976 dbg("(%d) op:%02x insert_op:%02x", (int)(opstackptr - opstack), op, insert_op);
977 *opstackptr++ = lasttok = op;
978 next:
979 if (insert_op != 0xff) {
980 op = insert_op;
981 insert_op = 0xff;
982 dbg("inserting %02x", op);
983 if (op == TOK_CONDITIONAL_SEP) {
984 /* The next token is ":". Toggle "do not evaluate" state */
985 if (!math_state->evaluation_disabled) {
986 math_state->evaluation_disabled = ternary_level;
987 dbg("':' entered: evaluation_disabled=%u", math_state->evaluation_disabled);
988 } else if (ternary_level == math_state->evaluation_disabled) {
989 math_state->evaluation_disabled = 0;
990 dbg("':' entered: evaluation_disabled=CLEAR");
991 } /* else: ternary_level > evaluation_disabled && evaluation_disabled != 0 */
992 /* We are in nested "?:" while in outer "?:" disabled branch */
993 /* do_nothing */
798 } 994 }
799 if (op == TOK_RPAREN) 995 goto tok_found1;
800 goto err;
801 } 996 }
802
803 /* Push this operator to the stack and remember it */
804//bb_error_msg("push op:%02x", op);
805 *stackptr++ = lasttok = op;
806 next: ;
807 } /* while (1) */ 997 } /* while (1) */
808 998
809 err: 999 syntax_err:
810 errmsg = "arithmetic syntax error"; 1000 errmsg = "arithmetic syntax error";
811 err_with_custom_msg: 1001 err_with_custom_msg:
812 numstack->val = -1;
813 ret:
814 math_state->errmsg = errmsg; 1002 math_state->errmsg = errmsg;
815 return numstack->val; 1003 return -1;
816} 1004}
817 1005
818arith_t FAST_FUNC 1006arith_t FAST_FUNC
819arith(arith_state_t *math_state, const char *expr) 1007arith(arith_state_t *math_state, const char *expr)
820{ 1008{
1009 math_state->evaluation_disabled = 0;
821 math_state->errmsg = NULL; 1010 math_state->errmsg = NULL;
822 math_state->list_of_recursed_names = NULL; 1011 math_state->list_of_recursed_names = NULL;
823 return evaluate_string(math_state, expr); 1012 return evaluate_string(math_state, expr);
diff --git a/shell/math.h b/shell/math.h
index a3fe51b6b..aeb3b93c3 100644
--- a/shell/math.h
+++ b/shell/math.h
@@ -6,7 +6,6 @@
6 * 6 *
7 * See math.c for internal documentation. 7 * See math.c for internal documentation.
8 */ 8 */
9
10/* The math library has just one function: 9/* The math library has just one function:
11 * 10 *
12 * arith_t arith(arith_state_t *state, const char *expr); 11 * arith_t arith(arith_state_t *state, const char *expr);
@@ -22,7 +21,6 @@
22 * "1 + 2 + 3" 21 * "1 + 2 + 3"
23 * you would obviously get back 6. 22 * you would obviously get back 6.
24 */ 23 */
25
26/* To add support to a shell, you need to implement three functions: 24/* To add support to a shell, you need to implement three functions:
27 * 25 *
28 * lookupvar() - look up and return the value of a variable 26 * lookupvar() - look up and return the value of a variable
@@ -36,28 +34,12 @@
36 * setvar() - set a variable to some value 34 * setvar() - set a variable to some value
37 * 35 *
38 * If the arithmetic expansion does something like: 36 * If the arithmetic expansion does something like:
39 * $(( i = 1)) 37 * $((i = 1))
40 * then the math code will make a call like so: 38 * then the math code will make a call like so:
41 * setvar("i", "1", 0); 39 * setvar("i", "1");
42 * The storage for the first two parameters are not allocated, so your 40 * The storage for the first two parameters are not allocated, so your
43 * shell implementation will most likely need to strdup() them to save. 41 * shell implementation will most likely need to strdup() them to save.
44 *
45 * endofname() - return the end of a variable name from input
46 *
47 * The arithmetic code does not know about variable naming conventions.
48 * So when it is given an experession, it knows something is not numeric,
49 * but it is up to the shell to dictate what is a valid identifiers.
50 * So when it encounters something like:
51 * $(( some_var + 123 ))
52 * It will make a call like so:
53 * end = endofname("some_var + 123");
54 * So the shell needs to scan the input string and return a pointer to the
55 * first non-identifier string. In this case, it should return the input
56 * pointer with an offset pointing to the first space. The typical
57 * implementation will return the offset of first char that does not match
58 * the regex (in C locale): ^[a-zA-Z_][a-zA-Z_0-9]*
59 */ 42 */
60
61#ifndef SHELL_MATH_H 43#ifndef SHELL_MATH_H
62#define SHELL_MATH_H 1 44#define SHELL_MATH_H 1
63 45
@@ -73,14 +55,13 @@ typedef long arith_t;
73 55
74typedef const char* FAST_FUNC (*arith_var_lookup_t)(const char *name); 56typedef const char* FAST_FUNC (*arith_var_lookup_t)(const char *name);
75typedef void FAST_FUNC (*arith_var_set_t)(const char *name, const char *val); 57typedef void FAST_FUNC (*arith_var_set_t)(const char *name, const char *val);
76//typedef const char* FAST_FUNC (*arith_var_endofname_t)(const char *name);
77 58
78typedef struct arith_state_t { 59typedef struct arith_state_t {
60 unsigned evaluation_disabled;
79 const char *errmsg; 61 const char *errmsg;
62 void *list_of_recursed_names;
80 arith_var_lookup_t lookupvar; 63 arith_var_lookup_t lookupvar;
81 arith_var_set_t setvar; 64 arith_var_set_t setvar;
82// arith_var_endofname_t endofname;
83 void *list_of_recursed_names;
84} arith_state_t; 65} arith_state_t;
85 66
86arith_t FAST_FUNC arith(arith_state_t *state, const char *expr); 67arith_t FAST_FUNC arith(arith_state_t *state, const char *expr);
diff --git a/shell/shell_common.c b/shell/shell_common.c
index 657ee9969..da157ea0e 100644
--- a/shell/shell_common.c
+++ b/shell/shell_common.c
@@ -22,6 +22,25 @@
22const char defifsvar[] ALIGN1 = "IFS= \t\n"; 22const char defifsvar[] ALIGN1 = "IFS= \t\n";
23const char defoptindvar[] ALIGN1 = "OPTIND=1"; 23const char defoptindvar[] ALIGN1 = "OPTIND=1";
24 24
25/* Compare two strings up to the first '=' or '\0'. */
26int FAST_FUNC varcmp(const char *p, const char *q)
27{
28 int c, d;
29
30 while ((c = *p) == (d = *q)) {
31 if (c == '\0' || c == '=')
32 goto out;
33 p++;
34 q++;
35 }
36 if (c == '=')
37 c = '\0';
38 if (d == '=')
39 d = '\0';
40 out:
41 return c - d;
42}
43
25/* read builtin */ 44/* read builtin */
26 45
27/* Needs to be interruptible: shell must handle traps and shell-special signals 46/* Needs to be interruptible: shell must handle traps and shell-special signals
@@ -59,7 +78,7 @@ shell_builtin_read(struct builtin_read_params *params)
59 argv = params->argv; 78 argv = params->argv;
60 pp = argv; 79 pp = argv;
61 while (*pp) { 80 while (*pp) {
62 if (endofname(*pp)[0] != '\0') { 81 if (!*pp[0] || endofname(*pp)[0] != '\0') {
63 /* Mimic bash message */ 82 /* Mimic bash message */
64 bb_error_msg("read: '%s': bad variable name", *pp); 83 bb_error_msg("read: '%s': bad variable name", *pp);
65 return (const char *)(uintptr_t)1; 84 return (const char *)(uintptr_t)1;
diff --git a/shell/shell_common.h b/shell/shell_common.h
index 7b478f1df..fab676e4a 100644
--- a/shell/shell_common.h
+++ b/shell/shell_common.h
@@ -26,6 +26,8 @@ extern const char defifsvar[] ALIGN1; /* "IFS= \t\n" */
26 26
27extern const char defoptindvar[] ALIGN1; /* "OPTIND=1" */ 27extern const char defoptindvar[] ALIGN1; /* "OPTIND=1" */
28 28
29int FAST_FUNC varcmp(const char *p, const char *q);
30
29/* Builtins */ 31/* Builtins */
30 32
31struct builtin_read_params { 33struct builtin_read_params {