diff options
Diffstat (limited to 'shell')
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 | */ | ||
2554 | static int | ||
2555 | varcmp(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 | */ |
2576 | static struct var ** | 2572 | static 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 @@ | |||
1 | 20:20 | ||
2 | a=b=10 | ||
3 | b=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 @@ | |||
1 | exec 2>&1 | ||
2 | a='b=10' | ||
3 | b=3 | ||
4 | # The variables should evaluate left-to-right, | ||
5 | # thus b is set to 10 _before_ addition | ||
6 | echo 20:$((a + b)) | ||
7 | echo "a=$a" | ||
8 | echo "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 @@ | |||
1 | 7:7 | ||
2 | x=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 @@ | |||
1 | exec 2>&1 | ||
2 | a='x=1' | ||
3 | b='x=2' | ||
4 | c='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: | ||
8 | echo 7:$((a+b*c)) | ||
9 | echo "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 @@ | |||
1 | 18 digits: 999999999999999999 | ||
2 | 19 digits: -8446744073709551617 | ||
3 | 20 digits: 7766279631452241919 | ||
4 | 18 digits- -999999999999999999 | ||
5 | 19 digits- 8446744073709551617 | ||
6 | 20 digits- -7766279631452241919 | ||
7 | Hex base#: | ||
8 | 16 digits: 9876543210abcedf | ||
9 | 17 digits: 876543210abcedfc | ||
10 | 18 digits: 76543210abcedfcc | ||
11 | 16 digits: 6789abcdef543121 | ||
12 | 17 digits: 789abcdef5431204 | ||
13 | 18 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 @@ | |||
1 | exec 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). | ||
5 | echo 18 digits: $((999999999999999999)) | ||
6 | echo 19 digits: $((9999999999999999999)) | ||
7 | echo 20 digits: $((99999999999999999999)) | ||
8 | echo 18 digits- $((-999999999999999999)) | ||
9 | echo 19 digits- $((-9999999999999999999)) | ||
10 | echo 20 digits- $((-99999999999999999999)) | ||
11 | echo "Hex base#:" | ||
12 | printf '16 digits: %016x\n' $((16#9876543210abcedf)) | ||
13 | printf '17 digits: %016x\n' $((16#9876543210abcedfc)) | ||
14 | printf '18 digits: %016x\n' $((16#9876543210abcedfcc)) | ||
15 | printf '16 digits: %016x\n' $((-16#9876543210abcedf)) | ||
16 | printf '17 digits: %016x\n' $((-16#9876543210abcedfc)) | ||
17 | printf '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 @@ | |||
1 | 10:10 | ||
2 | a=b=10 | ||
3 | b=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 @@ | |||
1 | exec 2>&1 | ||
2 | a='b=10' | ||
3 | b=3 | ||
4 | echo 10:$((a,b)) | ||
5 | echo "a=$a" | ||
6 | echo "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 @@ | |||
1 | 4:4 | ||
2 | 4:4 | ||
3 | 4:4 | ||
4 | 4: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 @@ | |||
1 | exec 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) | ||
12 | echo 4:$((0 ? 1,2 : 3,4)) | ||
13 | echo 4:$((1 ? 1,2 : 3,4)) | ||
14 | echo 4:"$((0 ? 1,2 : 3,4))" | ||
15 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | a='@' | ||
3 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | x='@' | ||
3 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | x='@' | ||
3 | echo 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 @@ | |||
1 | 42:42 | ||
2 | a=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 @@ | |||
1 | exec 2>&1 | ||
2 | a=0 | ||
3 | # The not-taken branch should not evaluate | ||
4 | echo 42:$((1 ? 42 : (a+=2))) | ||
5 | echo "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 @@ | |||
1 | 6:6 | ||
2 | a=b=+err+ | ||
3 | b=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 @@ | |||
1 | exec 2>&1 | ||
2 | a='b=+err+' | ||
3 | b=5 | ||
4 | # The not-taken branch should not parse variables | ||
5 | echo 6:$((0 ? a : ++b)) | ||
6 | echo "a=$a" | ||
7 | echo "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 @@ | |||
1 | exec 2>&1 | ||
2 | # "EXPR ?..." should check _evaluated_ EXPR, | ||
3 | # not its last value | ||
4 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | echo 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 @@ | |||
1 | 42:42 | ||
2 | a=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 @@ | |||
1 | exec 2>&1 | ||
2 | x='@' | ||
3 | a=2 | ||
4 | # After processing nested ?:, outermost ?: should still remember to NOT evaluate a*=2 | ||
5 | echo 42:$((1?0?41:42:(a*=2))) | ||
6 | echo "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 | |||
80 | 62 62 | 80 | 62 62 |
81 | 63 63 | 81 | 63 63 |
82 | missing number after base | 82 | missing number after base |
83 | 0 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 |
93 | 16 16 | 94 | 16 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 |
97 | 9 9 | 98 | 9 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 | ||
158 | echo missing number after base | ||
159 | echo 0 $(( 2# )) | ||
160 | 158 | ||
161 | # these should generate errors | 159 | # these should generate errors |
160 | echo 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 @@ | |||
1 | 1:'b c' | ||
2 | 2:'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 @@ | |||
1 | a=b | ||
2 | a=${a:+$a }c | ||
3 | echo "1:'$a'" | ||
4 | a=b | ||
5 | a="${a:+$a }c" | ||
6 | echo "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 |
104 | fi | 117 | fi |
105 | 118 | ||
106 | exit ${ret} | 119 | exit $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 | */ |
2258 | static struct variable **get_ptr_to_local_var(const char *name, unsigned len) | 2258 | static 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) | |||
2272 | static const char* FAST_FUNC get_local_var_value(const char *name) | 2272 | static 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 |
2322 | static void handle_changed_special_names(const char *name, unsigned name_len) | 2321 | static 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 |
2502 | static int unset_local_var_len(const char *name, int name_len) | 2499 | static 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 | |||
2535 | static 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 |
11182 | static void print_escaped(const char *s) | 11177 | static 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 @@ | |||
1 | 20:20 | ||
2 | a=b=10 | ||
3 | b=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 @@ | |||
1 | exec 2>&1 | ||
2 | a='b=10' | ||
3 | b=3 | ||
4 | # The variables should evaluate left-to-right, | ||
5 | # thus b is set to 10 _before_ addition | ||
6 | echo 20:$((a + b)) | ||
7 | echo "a=$a" | ||
8 | echo "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 @@ | |||
1 | 7:7 | ||
2 | x=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 @@ | |||
1 | exec 2>&1 | ||
2 | a='x=1' | ||
3 | b='x=2' | ||
4 | c='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: | ||
8 | echo 7:$((a+b*c)) | ||
9 | echo "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 @@ | |||
1 | 18 digits: 999999999999999999 | ||
2 | 19 digits: -8446744073709551617 | ||
3 | 20 digits: 7766279631452241919 | ||
4 | 18 digits- -999999999999999999 | ||
5 | 19 digits- 8446744073709551617 | ||
6 | 20 digits- -7766279631452241919 | ||
7 | Hex base#: | ||
8 | 16 digits: 9876543210abcedf | ||
9 | 17 digits: 876543210abcedfc | ||
10 | 18 digits: 76543210abcedfcc | ||
11 | 16 digits: 6789abcdef543121 | ||
12 | 17 digits: 789abcdef5431204 | ||
13 | 18 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 @@ | |||
1 | exec 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). | ||
5 | echo 18 digits: $((999999999999999999)) | ||
6 | echo 19 digits: $((9999999999999999999)) | ||
7 | echo 20 digits: $((99999999999999999999)) | ||
8 | echo 18 digits- $((-999999999999999999)) | ||
9 | echo 19 digits- $((-9999999999999999999)) | ||
10 | echo 20 digits- $((-99999999999999999999)) | ||
11 | echo "Hex base#:" | ||
12 | printf '16 digits: %016x\n' $((16#9876543210abcedf)) | ||
13 | printf '17 digits: %016x\n' $((16#9876543210abcedfc)) | ||
14 | printf '18 digits: %016x\n' $((16#9876543210abcedfcc)) | ||
15 | printf '16 digits: %016x\n' $((-16#9876543210abcedf)) | ||
16 | printf '17 digits: %016x\n' $((-16#9876543210abcedfc)) | ||
17 | printf '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 @@ | |||
1 | 10:10 | ||
2 | a=b=10 | ||
3 | b=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 @@ | |||
1 | exec 2>&1 | ||
2 | a='b=10' | ||
3 | b=3 | ||
4 | echo 10:$((a,b)) | ||
5 | echo "a=$a" | ||
6 | echo "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 @@ | |||
1 | 4:4 | ||
2 | 4:4 | ||
3 | 4:4 | ||
4 | 4: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 @@ | |||
1 | exec 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) | ||
12 | echo 4:$((0 ? 1,2 : 3,4)) | ||
13 | echo 4:$((1 ? 1,2 : 3,4)) | ||
14 | echo 4:"$((0 ? 1,2 : 3,4))" | ||
15 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | a='@' | ||
3 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | x='@' | ||
3 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | x='@' | ||
3 | echo 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 @@ | |||
1 | 42:42 | ||
2 | a=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 @@ | |||
1 | exec 2>&1 | ||
2 | a=0 | ||
3 | # The not-taken branch should not evaluate | ||
4 | echo 42:$((1 ? 42 : (a+=2))) | ||
5 | echo "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 @@ | |||
1 | 6:6 | ||
2 | a=b=+err+ | ||
3 | b=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 @@ | |||
1 | exec 2>&1 | ||
2 | a='b=+err+' | ||
3 | b=5 | ||
4 | # The not-taken branch should not parse variables | ||
5 | echo 6:$((0 ? a : ++b)) | ||
6 | echo "a=$a" | ||
7 | echo "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 @@ | |||
1 | exec 2>&1 | ||
2 | # "EXPR ?..." should check _evaluated_ EXPR, | ||
3 | # not its last value | ||
4 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | echo 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 @@ | |||
1 | exec 2>&1 | ||
2 | echo 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 @@ | |||
1 | 42:42 | ||
2 | a=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 @@ | |||
1 | exec 2>&1 | ||
2 | x='@' | ||
3 | a=2 | ||
4 | # After processing nested ?:, outermost ?: should still remember to NOT evaluate a*=2 | ||
5 | echo 42:$((1?0?41:42:(a*=2))) | ||
6 | echo "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 | |||
82 | 62 62 | 82 | 62 62 |
83 | 63 63 | 83 | 63 63 |
84 | missing number after base | 84 | missing number after base |
85 | 0 0 | 85 | hush: arithmetic syntax error |
86 | hush: arithmetic syntax error | ||
86 | hush: arithmetic syntax error | 87 | hush: arithmetic syntax error |
87 | hush: divide by zero | 88 | hush: divide by zero |
88 | hush: can't execute 'let': No such file or directory | 89 | hush: can't execute 'let': No such file or directory |
@@ -94,7 +95,7 @@ ghi | |||
94 | hush: arithmetic syntax error | 95 | hush: arithmetic syntax error |
95 | 16 16 | 96 | 16 16 |
96 | hush: arithmetic syntax error | 97 | hush: arithmetic syntax error |
97 | hush: malformed ?: operator | 98 | hush: arithmetic syntax error |
98 | hush: arithmetic syntax error | 99 | hush: arithmetic syntax error |
99 | 9 9 | 100 | 9 9 |
100 | hush: arithmetic syntax error | 101 | hush: 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 | ||
162 | echo missing number after base | ||
163 | echo 0 $(( 2# )) | ||
164 | 162 | ||
165 | # these should generate errors | 163 | # these should generate errors |
164 | echo 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 @@ | |||
1 | 1:'b c' | ||
2 | 2:'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 @@ | |||
1 | a=b | ||
2 | a=${a:+$a }c | ||
3 | echo "1:'$a'" | ||
4 | a=b | ||
5 | a="${a:+$a }c" | ||
6 | echo "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 @@ | |||
1 | readonly a=A | 1 | readonly a='A' |
2 | readonly b=B | 2 | readonly b='B' |
3 | Ok:0 | 3 | Ok:0 |
4 | 4 | ||
5 | hush: a=A: readonly variable | 5 | hush: 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 | ||
30 | eval $(sed -e '/^#/d' -e '/^$/d' -e 's:^:export :' .config) | 30 | eval $(sed -e '/^#/d' -e '/^$/d' -e 's:^:export :' .config) |
31 | 31 | ||
32 | PATH="`pwd`:$PATH" # for hush and recho/zecho/printenv | 32 | PATH="`pwd`:$PATH" # for hush |
33 | export PATH | 33 | export PATH |
34 | 34 | ||
35 | THIS_SH="`pwd`/hush" | 35 | THIS_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 | |||
95 | if [ $# -lt 1 ]; then | 96 | if [ $# -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 |
109 | fi | 113 | fi |
110 | 114 | ||
111 | exit ${ret} | 115 | exit $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 | |||
119 | typedef unsigned char operator; | 122 | typedef 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 | ||
211 | static int | 232 | static int |
212 | is_assign_op(operator op) | 233 | is_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 | |||
230 | typedef struct { | 250 | typedef 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 | |||
244 | typedef struct remembered_name { | 258 | typedef 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 | ||
263 | static ALWAYS_INLINE int isalnum_(int c) | ||
264 | { | ||
265 | return (isalnum(c) || c == '_'); | ||
266 | } | ||
249 | 267 | ||
250 | static arith_t | 268 | static arith_t |
251 | evaluate_string(arith_state_t *math_state, const char *expr); | 269 | evaluate_string(arith_state_t *math_state, const char *expr); |
252 | 270 | ||
253 | static const char* | 271 | static arith_t |
254 | arith_lookup_val(arith_state_t *math_state, var_or_num_t *t) | 272 | arith_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 | */ | ||
295 | static NOINLINE const char* | 323 | static NOINLINE const char* |
296 | arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack, var_or_num_t **numstackptr) | 324 | arith_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 |
512 | static arith_t strto_arith_t(const char *nptr, char **endptr) | 551 | static 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 | |||
597 | static 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) | |||
580 | static arith_t | 644 | static arith_t |
581 | evaluate_string(arith_state_t *math_state, const char *expr) | 645 | evaluate_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; |
938 | dbg(" 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 | ||
818 | arith_t FAST_FUNC | 1006 | arith_t FAST_FUNC |
819 | arith(arith_state_t *math_state, const char *expr) | 1007 | arith(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 | ||
74 | typedef const char* FAST_FUNC (*arith_var_lookup_t)(const char *name); | 56 | typedef const char* FAST_FUNC (*arith_var_lookup_t)(const char *name); |
75 | typedef void FAST_FUNC (*arith_var_set_t)(const char *name, const char *val); | 57 | typedef 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 | ||
78 | typedef struct arith_state_t { | 59 | typedef 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 | ||
86 | arith_t FAST_FUNC arith(arith_state_t *state, const char *expr); | 67 | arith_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 @@ | |||
22 | const char defifsvar[] ALIGN1 = "IFS= \t\n"; | 22 | const char defifsvar[] ALIGN1 = "IFS= \t\n"; |
23 | const char defoptindvar[] ALIGN1 = "OPTIND=1"; | 23 | const char defoptindvar[] ALIGN1 = "OPTIND=1"; |
24 | 24 | ||
25 | /* Compare two strings up to the first '=' or '\0'. */ | ||
26 | int 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 | ||
27 | extern const char defoptindvar[] ALIGN1; /* "OPTIND=1" */ | 27 | extern const char defoptindvar[] ALIGN1; /* "OPTIND=1" */ |
28 | 28 | ||
29 | int FAST_FUNC varcmp(const char *p, const char *q); | ||
30 | |||
29 | /* Builtins */ | 31 | /* Builtins */ |
30 | 32 | ||
31 | struct builtin_read_params { | 33 | struct builtin_read_params { |