The former implementation was not correct regarding whiteouts in
?: conditional branches. The new one also parses a bit better, in
effect on equal level than bash with its recursive descendent parser.
---
Hello!
I implemented that one heavily inspired from the current busybox
implementation, so thanks to everyone who worked on that. I have
to admit that the size increasement is quite large, the error msg
stuff in arith() alone is ~400 bytes:
2938 0 0 2938 b7a math.o
4668 0 0 4668 123c math.o
For that you get an evaluator that is en par with bash, and it
even gets
+10++VAR -> +10 + +VAR. (Since we do handle +10++11
correctly via "prefix split", we should also handle this)
In advance i will post a test script (i also have a "should fail"
one, but this does not work fine here, it is ok for bash and my
one), and if i run this i get
--- .good.x 2022-08-05 17:54:42.453434049 +0200
+++ .good.sh 2022-08-05 17:50:08.990105909 +0200
@@ -9,7 +9,7 @@
<1>
<1>
<1>
-<9223372036854775807>
+<0>
<10>
<9191919191919>
<13>
compared to my one (and bash). All the integer conversion is up
to the current code, i did not touch it. The evaluator also
supports unsigned right shift >>> and exponent-assignment **=,
which makes for the other differences to bash.
"For now" i simply copied over that shexp-arith.h unchanged, and
added compatibility shims so that arith() can use it. The
evaluator is quite fresh, maybe i can optimize it, and the way it
is done for now allows easy-most comparison. You surely could
make the object smaller if the compatibility shims would be
replaced with inline code.
I hope you like it! Thanks for busybox!
shell/ash.c | 4 +-
shell/hush.c | 13 +-
shell/math.c | 740 +++++-----------------------
shell/math.h | 3 +-
shell/shexp-arith.h | 1134 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 1261 insertions(+), 633 deletions(-)
create mode 100644 shell/shexp-arith.h
diff --git a/shell/ash.c b/shell/ash.c
index 105edd4c8d..bcb5161117 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -6029,8 +6029,10 @@ ash_arith(const char *s)
INT_OFF;
result = arith(&math_state, s);
- if (math_state.errmsg)
+ if (math_state.errmsg) {
ash_msg_and_raise_error(math_state.errmsg);
+ free(math_state.errmsg);
+ }
INT_ON;
return result;
diff --git a/shell/hush.c b/shell/hush.c
index 051b123e78..f59bb57b3b 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -6475,7 +6475,7 @@ static NOINLINE int
encode_then_append_var_plusminus(o_string *output, int n,
}
#if ENABLE_FEATURE_SH_MATH
-static arith_t expand_and_evaluate_arith(const char *arg, const char
**errmsg_p)
+static arith_t expand_and_evaluate_arith(const char *arg, char **errmsg_p)
{
arith_state_t math_state;
arith_t res;
@@ -6489,8 +6489,11 @@ static arith_t expand_and_evaluate_arith(const char
*arg, const char **errmsg_p)
free(exp_str);
if (errmsg_p)
*errmsg_p = math_state.errmsg;
- if (math_state.errmsg)
+ if (math_state.errmsg) {
msg_and_die_if_script(math_state.errmsg);
+ if (errmsg_p == NULL)
+ free(math_state.errmsg);
+ }
return res;
}
#endif
@@ -6814,11 +6817,13 @@ static NOINLINE int expand_one_var(o_string *output,
int n,
*/
arith_t beg, len;
unsigned vallen;
- const char *errmsg;
+ char *errmsg;
beg = expand_and_evaluate_arith(exp_word, &errmsg);
- if (errmsg)
+ if (errmsg) {
+ free(errmsg);
goto empty_result;
+ }
debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long
long)beg);
*p++ = SPECIAL_VAR_SYMBOL;
exp_word = p;
diff --git a/shell/math.c b/shell/math.c
index 76d22c9bd5..a20596089d 100644
--- a/shell/math.c
+++ b/shell/math.c
@@ -116,398 +116,6 @@
#include "libbb.h"
#include "math.h"
-typedef unsigned char operator;
-
-/* An operator's token id is a bit of a bitfield. The lower 5 bits are the
- * precedence, and 3 high bits are an ID unique across operators of that
- * precedence. The ID portion is so that multiple operators can have the
- * same precedence, ensuring that the leftmost one is evaluated first.
- * Consider * and /
- */
-#define tok_decl(prec,id) (((id)<<5) | (prec))
-#define PREC(op) ((op) & 0x1F)
-
-#define TOK_LPAREN tok_decl(0,0)
-
-#define TOK_COMMA tok_decl(1,0)
-
-/* All assignments are right associative and have the same precedence,
- * but there are 11 of them, which doesn't fit into 3 bits for unique id.
- * Abusing another precedence level:
- */
-#define TOK_ASSIGN tok_decl(2,0)
-#define TOK_AND_ASSIGN tok_decl(2,1)
-#define TOK_OR_ASSIGN tok_decl(2,2)
-#define TOK_XOR_ASSIGN tok_decl(2,3)
-#define TOK_PLUS_ASSIGN tok_decl(2,4)
-#define TOK_MINUS_ASSIGN tok_decl(2,5)
-#define TOK_LSHIFT_ASSIGN tok_decl(2,6)
-#define TOK_RSHIFT_ASSIGN tok_decl(2,7)
-
-#define TOK_MUL_ASSIGN tok_decl(3,0)
-#define TOK_DIV_ASSIGN tok_decl(3,1)
-#define TOK_REM_ASSIGN tok_decl(3,2)
-
-#define fix_assignment_prec(prec) do { if (prec == 3) prec = 2; } while (0)
-
-/* Ternary conditional operator is right associative too */
-#define TOK_CONDITIONAL tok_decl(4,0)
-#define TOK_CONDITIONAL_SEP tok_decl(4,1)
-
-#define TOK_OR tok_decl(5,0)
-
-#define TOK_AND tok_decl(6,0)
-
-#define TOK_BOR tok_decl(7,0)
-
-#define TOK_BXOR tok_decl(8,0)
-
-#define TOK_BAND tok_decl(9,0)
-
-#define TOK_EQ tok_decl(10,0)
-#define TOK_NE tok_decl(10,1)
-
-#define TOK_LT tok_decl(11,0)
-#define TOK_GT tok_decl(11,1)
-#define TOK_GE tok_decl(11,2)
-#define TOK_LE tok_decl(11,3)
-
-#define TOK_LSHIFT tok_decl(12,0)
-#define TOK_RSHIFT tok_decl(12,1)
-
-#define TOK_ADD tok_decl(13,0)
-#define TOK_SUB tok_decl(13,1)
-
-#define TOK_MUL tok_decl(14,0)
-#define TOK_DIV tok_decl(14,1)
-#define TOK_REM tok_decl(14,2)
-
-/* Exponent is right associative */
-#define TOK_EXPONENT tok_decl(15,1)
-
-/* Unary operators */
-#define UNARYPREC 16
-#define TOK_BNOT tok_decl(UNARYPREC,0)
-#define TOK_NOT tok_decl(UNARYPREC,1)
-
-#define TOK_UMINUS tok_decl(UNARYPREC+1,0)
-#define TOK_UPLUS tok_decl(UNARYPREC+1,1)
-
-#define PREC_PRE (UNARYPREC+2)
-
-#define TOK_PRE_INC tok_decl(PREC_PRE, 0)
-#define TOK_PRE_DEC tok_decl(PREC_PRE, 1)
-
-#define PREC_POST (UNARYPREC+3)
-
-#define TOK_POST_INC tok_decl(PREC_POST, 0)
-#define TOK_POST_DEC tok_decl(PREC_POST, 1)
-
-#define SPEC_PREC (UNARYPREC+4)
-
-#define TOK_NUM tok_decl(SPEC_PREC, 0)
-#define TOK_RPAREN tok_decl(SPEC_PREC, 1)
-
-static int
-is_assign_op(operator op)
-{
- operator prec = PREC(op);
- fix_assignment_prec(prec);
- return prec == PREC(TOK_ASSIGN)
- || prec == PREC_PRE
- || prec == PREC_POST;
-}
-
-static int
-is_right_associative(operator prec)
-{
- return prec == PREC(TOK_ASSIGN)
- || prec == PREC(TOK_EXPONENT)
- || prec == PREC(TOK_CONDITIONAL);
-}
-
-
-typedef struct {
- arith_t val;
- /* We acquire second_val only when "expr1 : expr2" part
- * of ternary ?: op is evaluated.
- * We treat ?: as two binary ops: (expr ? (expr1 : expr2)).
- * ':' produces a new value which has two parts, val and second_val;
- * then '?' selects one of them based on its left side.
- */
- arith_t second_val;
- char second_val_present;
- /* If NULL then it's just a number, else it's a named variable */
- char *var;
-} var_or_num_t;
-
-typedef struct remembered_name {
- struct remembered_name *next;
- const char *var;
-} remembered_name;
-
-
-static arith_t
-evaluate_string(arith_state_t *math_state, const char *expr);
-
-static const char*
-arith_lookup_val(arith_state_t *math_state, var_or_num_t *t)
-{
- if (t->var) {
- const char *p = math_state->lookupvar(t->var);
- if (p) {
- remembered_name *cur;
- remembered_name cur_save;
-
- /* did we already see this name?
- * testcase: a=b; b=a; echo $((a))
- */
- for (cur = math_state->list_of_recursed_names; cur; cur
= cur->next) {
- if (strcmp(cur->var, t->var) == 0) {
- /* Yes */
- return "expression recursion loop
detected";
- }
- }
-
- /* push current var name */
- cur = math_state->list_of_recursed_names;
- cur_save.var = t->var;
- cur_save.next = cur;
- math_state->list_of_recursed_names = &cur_save;
-
- /* recursively evaluate p as expression */
- t->val = evaluate_string(math_state, p);
-
- /* pop current var name */
- math_state->list_of_recursed_names = cur;
-
- return math_state->errmsg;
- }
- /* treat undefined var as 0 */
- t->val = 0;
- }
- return 0;
-}
-
-/* "Applying" a token means performing it on the top elements on the integer
- * stack. For an unary operator it will only change the top element, but a
- * binary operator will pop two arguments and push the result */
-static NOINLINE const char*
-arith_apply(arith_state_t *math_state, operator op, var_or_num_t *numstack,
var_or_num_t **numstackptr)
-{
-#define NUMPTR (*numstackptr)
-
- var_or_num_t *top_of_stack;
- arith_t rez;
- const char *err;
-
- /* There is no operator that can work without arguments */
- if (NUMPTR == numstack)
- goto err;
-
- top_of_stack = NUMPTR - 1;
-
- /* Resolve name to value, if needed */
- err = arith_lookup_val(math_state, top_of_stack);
- if (err)
- return err;
-
- rez = top_of_stack->val;
- if (op == TOK_UMINUS)
- rez = -rez;
- else if (op == TOK_NOT)
- rez = !rez;
- else if (op == TOK_BNOT)
- rez = ~rez;
- else if (op == TOK_POST_INC || op == TOK_PRE_INC)
- rez++;
- else if (op == TOK_POST_DEC || op == TOK_PRE_DEC)
- rez--;
- else if (op != TOK_UPLUS) {
- /* Binary operators */
- arith_t right_side_val;
- char bad_second_val;
-
- /* Binary operators need two arguments */
- if (top_of_stack == numstack)
- goto err;
- /* ...and they pop one */
- NUMPTR = top_of_stack; /* this decrements NUMPTR */
-
- bad_second_val = top_of_stack->second_val_present;
- if (op == TOK_CONDITIONAL) { /* ? operation */
- /* Make next if (...) protect against
- * $((expr1 ? expr2)) - that is, missing ": expr" */
- bad_second_val = !bad_second_val;
- }
- if (bad_second_val) {
- /* Protect against $((expr <not_?_op> expr1 : expr2)) */
- return "malformed ?: operator";
- }
-
- top_of_stack--; /* now points to left side */
-
- if (op != TOK_ASSIGN) {
- /* Resolve left side value (unless the op is '=') */
- err = arith_lookup_val(math_state, top_of_stack);
- if (err)
- return err;
- }
-
- right_side_val = rez;
- rez = top_of_stack->val;
- if (op == TOK_CONDITIONAL) /* ? operation */
- rez = (rez ? right_side_val :
top_of_stack[1].second_val);
- else if (op == TOK_CONDITIONAL_SEP) { /* : operation */
- if (top_of_stack == numstack) {
- /* Protect against $((expr : expr)) */
- return "malformed ?: operator";
- }
- top_of_stack->second_val_present = op;
- top_of_stack->second_val = right_side_val;
- }
- else if (op == TOK_BOR || op == TOK_OR_ASSIGN)
- rez |= right_side_val;
- else if (op == TOK_OR)
- rez = right_side_val || rez;
- else if (op == TOK_BAND || op == TOK_AND_ASSIGN)
- rez &= right_side_val;
- else if (op == TOK_BXOR || op == TOK_XOR_ASSIGN)
- rez ^= right_side_val;
- else if (op == TOK_AND)
- rez = rez && right_side_val;
- else if (op == TOK_EQ)
- rez = (rez == right_side_val);
- else if (op == TOK_NE)
- rez = (rez != right_side_val);
- else if (op == TOK_GE)
- rez = (rez >= right_side_val);
- else if (op == TOK_RSHIFT || op == TOK_RSHIFT_ASSIGN)
- rez >>= right_side_val;
- else if (op == TOK_LSHIFT || op == TOK_LSHIFT_ASSIGN)
- rez <<= right_side_val;
- else if (op == TOK_GT)
- rez = (rez > right_side_val);
- else if (op == TOK_LT)
- rez = (rez < right_side_val);
- else if (op == TOK_LE)
- rez = (rez <= right_side_val);
- else if (op == TOK_MUL || op == TOK_MUL_ASSIGN)
- rez *= right_side_val;
- else if (op == TOK_ADD || op == TOK_PLUS_ASSIGN)
- rez += right_side_val;
- else if (op == TOK_SUB || op == TOK_MINUS_ASSIGN)
- rez -= right_side_val;
- else if (op == TOK_ASSIGN || op == TOK_COMMA)
- rez = right_side_val;
- else if (op == TOK_EXPONENT) {
- arith_t c;
- if (right_side_val < 0)
- return "exponent less than 0";
- c = 1;
- while (--right_side_val >= 0)
- c *= rez;
- rez = c;
- }
- else if (right_side_val == 0)
- return "divide by zero";
- else if (op == TOK_DIV || op == TOK_DIV_ASSIGN
- || op == TOK_REM || op == TOK_REM_ASSIGN) {
- /*
- * bash 4.2.45 x86 64bit: SEGV on 'echo $((2**63 / -1))'
- *
- * MAX_NEGATIVE_INT / -1 = MAX_POSITIVE_INT+1
- * and thus is not representable.
- * Some CPUs segfault trying such op.
- * Others overflow MAX_POSITIVE_INT+1 to
- * MAX_NEGATIVE_INT (0x7fff+1 = 0x8000).
- * Make sure to at least not SEGV here:
- */
- if (right_side_val == -1
- && rez << 1 == 0 /* MAX_NEGATIVE_INT or 0 */
- ) {
- right_side_val = 1;
- }
- if (op == TOK_DIV || op == TOK_DIV_ASSIGN)
- rez /= right_side_val;
- else {
- rez %= right_side_val;
- }
- }
- }
-
- if (is_assign_op(op)) {
- char buf[sizeof(arith_t)*3 + 2];
-
- if (top_of_stack->var == NULL) {
- /* Hmm, 1=2 ? */
- goto err;
- }
- /* Save to shell variable */
- sprintf(buf, ARITH_FMT, rez);
- math_state->setvar(top_of_stack->var, buf);
- /* After saving, make previous value for v++ or v-- */
- if (op == TOK_POST_INC)
- rez--;
- if (op == TOK_POST_DEC)
- rez++;
- }
-
- top_of_stack->val = rez;
- /* Erase var name, it is just a number now */
- top_of_stack->var = NULL;
- return NULL;
- err:
- return "arithmetic syntax error";
-#undef NUMPTR
-}
-
-/* longest must be first */
-static const char op_tokens[] ALIGN1 = {
- '<','<','=',0, TOK_LSHIFT_ASSIGN,
- '>','>','=',0, TOK_RSHIFT_ASSIGN,
- '<','<', 0, TOK_LSHIFT,
- '>','>', 0, TOK_RSHIFT,
- '|','|', 0, TOK_OR,
- '&','&', 0, TOK_AND,
- '!','=', 0, TOK_NE,
- '<','=', 0, TOK_LE,
- '>','=', 0, TOK_GE,
- '=','=', 0, TOK_EQ,
- '|','=', 0, TOK_OR_ASSIGN,
- '&','=', 0, TOK_AND_ASSIGN,
- '*','=', 0, TOK_MUL_ASSIGN,
- '/','=', 0, TOK_DIV_ASSIGN,
- '%','=', 0, TOK_REM_ASSIGN,
- '+','=', 0, TOK_PLUS_ASSIGN,
- '-','=', 0, TOK_MINUS_ASSIGN,
- '-','-', 0, TOK_POST_DEC,
- '^','=', 0, TOK_XOR_ASSIGN,
- '+','+', 0, TOK_POST_INC,
- '*','*', 0, TOK_EXPONENT,
- '!', 0, TOK_NOT,
- '<', 0, TOK_LT,
- '>', 0, TOK_GT,
- '=', 0, TOK_ASSIGN,
- '|', 0, TOK_BOR,
- '&', 0, TOK_BAND,
- '*', 0, TOK_MUL,
- '/', 0, TOK_DIV,
- '%', 0, TOK_REM,
- '+', 0, TOK_ADD,
- '-', 0, TOK_SUB,
- '^', 0, TOK_BXOR,
- /* uniq */
- '~', 0, TOK_BNOT,
- ',', 0, TOK_COMMA,
- '?', 0, TOK_CONDITIONAL,
- ':', 0, TOK_CONDITIONAL_SEP,
- ')', 0, TOK_RPAREN,
- '(', 0, TOK_LPAREN,
- 0
-};
-#define ptr_to_rparen (&op_tokens[sizeof(op_tokens)-7])
-
#if ENABLE_FEATURE_SH_MATH_BASE
static arith_t strto_arith_t(const char *nptr, char **endptr)
{
@@ -577,250 +185,130 @@ static arith_t strto_arith_t(const char *nptr, char
**endptr)
# endif
#endif
-static arith_t
-evaluate_string(arith_state_t *math_state, const char *expr)
-{
- operator lasttok;
- const char *errmsg;
- const char *start_expr = expr = skip_whitespace(expr);
- unsigned expr_len = strlen(expr) + 2;
- /* Stack of integers */
- /* The proof that there can be no more than strlen(startbuf)/2+1
- * integers in any given correct or incorrect expression
- * is left as an exercise to the reader. */
- var_or_num_t *const numstack = alloca((expr_len / 2) *
sizeof(numstack[0]));
- var_or_num_t *numstackptr = numstack;
- /* Stack of operator tokens */
- operator *const stack = alloca(expr_len * sizeof(stack[0]));
- operator *stackptr = stack;
-
- /* Start with a left paren */
- *stackptr++ = lasttok = TOK_LPAREN;
- errmsg = NULL;
-
- while (1) {
- const char *p;
- operator op;
- operator prec;
-
- expr = skip_whitespace(expr);
- if (*expr == '\0') {
- if (expr == start_expr) {
- /* Null expression */
- numstack->val = 0;
- goto ret;
- }
-
- /* This is only reached after all tokens have been
extracted from the
- * input stream. If there are still tokens on the
operator stack, they
- * are to be applied in order. At the end, there should
be a final
- * result on the integer stack */
-
- if (expr != ptr_to_rparen + 1) {
- /* If we haven't done so already,
- * append a closing right paren
- * and let the loop process it */
- expr = ptr_to_rparen;
-//bb_error_msg("expr=')'");
- continue;
- }
- /* At this point, we're done with the expression */
- if (numstackptr != numstack + 1) {
- /* ...but if there isn't, it's bad */
- goto err;
- }
- goto ret;
- }
-
- p = endofname(expr);
- if (p != expr) {
- /* Name */
- size_t var_name_size = (p - expr) + 1; /* +1 for NUL */
- numstackptr->var = alloca(var_name_size);
- safe_strncpy(numstackptr->var, expr, var_name_size);
-//bb_error_msg("var:'%s'", numstackptr->var);
- expr = p;
- num:
- numstackptr->second_val_present = 0;
- numstackptr++;
- lasttok = TOK_NUM;
- continue;
- }
-
- if (isdigit(*expr)) {
- /* Number */
- numstackptr->var = NULL;
- errno = 0;
- numstackptr->val = strto_arith_t(expr, (char**) &expr);
-//bb_error_msg("val:%lld", numstackptr->val);
- if (errno)
- numstackptr->val = 0; /* bash compat */
- goto num;
- }
-
- /* Should be an operator */
-
- /* Special case: XYZ--, XYZ++, --XYZ, ++XYZ are recognized
- * only if XYZ is a variable name, not a number or EXPR. IOW:
- * "a+++v" is a++ + v.
- * "(a)+++7" is ( a ) + + + 7.
- * "7+++v" is 7 + ++v, not 7++ + v.
- * "--7" is - - 7, not --7.
- * "++++a" is + + ++a, not ++ ++a.
- */
- if ((expr[0] == '+' || expr[0] == '-')
- && (expr[1] == expr[0])
- ) {
- if (numstackptr == numstack || !numstackptr[-1].var) {
/* not a VAR++ */
- char next = skip_whitespace(expr + 2)[0];
- if (!(isalpha(next) || next == '_')) { /* not a
++VAR */
- //bb_error_msg("special %c%c", expr[0],
expr[0]);
- op = (expr[0] == '+' ? TOK_ADD :
TOK_SUB);
- expr++;
- goto tok_found1;
- }
- }
- }
-
- p = op_tokens;
- while (1) {
- /* Compare expr to current op_tokens[] element */
- const char *e = expr;
- while (1) {
- if (*p == '\0') {
- /* Match: operator is found */
- expr = e;
- goto tok_found;
- }
- if (*p != *e)
- break;
- p++;
- e++;
- }
- /* No match, go to next element of op_tokens[] */
- while (*p)
- p++;
- p += 2; /* skip NUL and TOK_foo bytes */
- if (*p == '\0') {
- /* No next element, operator not found */
- //math_state->syntax_error_at = expr;
- goto err;
- }
- }
- tok_found:
- op = p[1]; /* fetch TOK_foo value */
- tok_found1:
- /* NB: expr now points past the operator */
+#define boole bool
+# define FAL0 false
+# define TRU1 true
+#define s64 arith_t
+#if ENABLE_FEATURE_SH_MATH_64
+# define S64_MIN LLONG_MIN
+# define u64 unsigned long long
+#else
+# define S64_MIN LONG_MIN
+# define u64 unsigned long
+#endif
+#define u8 uint8_t
+#define u16 uint16_t
+#define u32 uint32_t
+#define U32_MAX UINT32_MAX
+#define ul unsigned long
+#define up uintptr_t
+#define UZ_MAX SIZE_MAX
+#define uz size_t
+
+#define a_SHEXP_ISVARC(C) ((C) == '_' || isalnum(S(unsigned char,C)))
+#define a_SHEXP_ISVARC_BAD1ST(C) su_cs_is_digit(C)
+#define a_SHEXP_ISVARC_BADNST(C) FAL0
+#define ASSERT(X)
+#define ASSERT_NYD_EXEC(X,Y)
+#define BITENUM_IS(X,Y) X
+#define CONCAT(S1,S2) su__CONCAT_1(S1, S2)
+# define su__CONCAT_1(S1,S2) su__CONCAT_2(S1, S2)
+# define su__CONCAT_2(S1,S2) S1 ## S2
+#define DBG(X)
+#define FALLTHRU
+#define N_(X) X
+#define NIL NULL
+#define NYD_IN S(void,0)
+#define NYD2_IN S(void,0)
+#define NYD_OU S(void,0)
+#define NYD2_OU S(void,0)
+#define P2UZ(X) S(size_t,X)
+#define S(X,Y) ((X)(Y))
+#define savestr(X) xstrdup(X)
+#define su_ALIGNOF(X) ((sizeof(X) + 15) & ~15)
+#define su_cs_cmp(X,Y) strcmp(X, Y)
+#define su_cs_is_digit(X) isdigit(S(unsigned char,X))
+#define su_cs_is_space(X) isspace(S(unsigned char,X))
+#define su_empty ""
+#define su_IDEC_STATE_EBASE 0 /* (could case $CC optimiz.) */
+#define su_IDEC_STATE_EMASK (1u<<0)
+#define su_IDEC_STATE_CONSUMED (1u<<1)
+#define su_IENC_BUFFER_SIZE 80u
+#define su_LOFI_ALLOC(X) alloca(X)
+#define su_LOFI_FREE(X)
+#define su_mem_move(X,Y,Z) memmove(X, Y, Z)
+#define STRUCT_ZERO(X,Y) memset(Y, 0, sizeof(X))
+#define UNLIKELY(X) X
+#define UNUSED(X) S(void,X)
+
+#if LONG_MAX - 1 > 0x7FFFFFFFl - 1
+# define su_64(X) X
+#else
+# define su_64(X)
+#endif
- /* post grammar: a++ reduce to num */
- if (lasttok == TOK_POST_INC || lasttok == TOK_POST_DEC)
- lasttok = TOK_NUM;
+//
+#define a_SHEXP_ARITH_COOKIE arith_state_t *
- /* Plus and minus are binary (not unary) _only_ if the last
- * token was a number, or a right paren (which pretends to be
- * a number, since it evaluates to one). Think about it.
- * It makes sense. */
- if (lasttok != TOK_NUM) {
- switch (op) {
- case TOK_ADD:
- op = TOK_UPLUS;
- break;
- case TOK_SUB:
- op = TOK_UMINUS;
- break;
- case TOK_POST_INC:
- op = TOK_PRE_INC;
- break;
- case TOK_POST_DEC:
- op = TOK_PRE_DEC;
- break;
- }
- }
- /* We don't want an unary operator to cause recursive descent
on the
- * stack, because there can be many in a row and it could cause
an
- * operator to be evaluated before its argument is pushed onto
the
- * integer stack.
- * But for binary operators, "apply" everything on the operator
- * stack until we find an operator with a lesser priority than
the
- * one we have just extracted. If op is right-associative,
- * then stop "applying" on the equal priority too.
- * Left paren is given the lowest priority so it will never be
- * "applied" in this way.
- */
- prec = PREC(op);
-//bb_error_msg("prec:%02x", prec);
- if ((prec > 0 && prec < UNARYPREC) || prec == SPEC_PREC) {
- /* not left paren or unary */
- if (lasttok != TOK_NUM) {
- /* binary op must be preceded by a num */
- goto err;
- }
- /* The algorithm employed here is simple: while we don't
- * hit an open paren nor the bottom of the stack, pop
- * tokens and apply them */
- while (stackptr != stack) {
- operator prev_op = *--stackptr;
- if (op == TOK_RPAREN) {
-//bb_error_msg("op == TOK_RPAREN");
- if (prev_op == TOK_LPAREN) {
-//bb_error_msg("prev_op == TOK_LPAREN");
-//bb_error_msg(" %p %p numstackptr[-1].var:'%s'", numstack, numstackptr-1,
numstackptr[-1].var);
- if (numstackptr[-1].var) {
- /* Expression is (var),
lookup now */
- errmsg =
arith_lookup_val(math_state, &numstackptr[-1]);
- if (errmsg)
- goto
err_with_custom_msg;
- /* Erase var name:
(var) is just a number, for example, (var) = 1 is not valid */
- numstackptr[-1].var =
NULL;
- }
- /* Any operator directly after a
- * close paren should consider
itself binary */
- lasttok = TOK_NUM;
- goto next;
- }
-//bb_error_msg("prev_op != TOK_LPAREN");
- } else {
- operator prev_prec = PREC(prev_op);
-//bb_error_msg("op != TOK_RPAREN");
- fix_assignment_prec(prec);
- fix_assignment_prec(prev_prec);
- if (prev_prec < prec
- || (prev_prec == prec &&
is_right_associative(prec))
- ) {
- stackptr++;
- break;
- }
- }
-//bb_error_msg("arith_apply(prev_op:%02x)", prev_op);
- errmsg = arith_apply(math_state, prev_op,
numstack, &numstackptr);
- if (errmsg)
- goto err_with_custom_msg;
- }
- if (op == TOK_RPAREN)
- goto err;
- }
+static inline u32 a_idec_x(void *resp, char const *cbuf,
+ char const **endptr_or_nil){
+ u32 rv;
+ arith_t res;
+#if ENABLE_FEATURE_SH_MATH_BASE
+ char const *eptr;
- /* Push this operator to the stack and remember it */
-//bb_error_msg("push op:%02x", op);
- *stackptr++ = lasttok = op;
- next: ;
- } /* while (1) */
+ if(endptr_or_nil == NIL)
+ endptr_or_nil = &eptr;
+#endif
- err:
- errmsg = "arithmetic syntax error";
- err_with_custom_msg:
- numstack->val = -1;
- ret:
- math_state->errmsg = errmsg;
- return numstack->val;
+ errno = 0;
+ res = strto_arith_t(cbuf, (char**)endptr_or_nil);
+ rv = 0;
+ if(errno == 0){
+ if(**endptr_or_nil == '\0')
+ rv = su_IDEC_STATE_CONSUMED;
+ }else{
+ rv = su_IDEC_STATE_EMASK;
+ res = 0;
+ }
+ *S(s64*,resp) = res;
+ return rv;
}
+#define su_idec_cp(A,B,C,D,E) a_idec_x(A, B, E)
+#define su_ienc_s64(X,Y,Z) (sprintf(X, ARITH_FMT, Y), X)
+#define n_var_vlook(X,Y) (*self->sac_cookie->lookupvar)(X)
+#define n_var_vset(X,Y,Z) (*self->sac_cookie->setvar)(X, (char*)(Y))
+
+#include "shexp-arith.h"
arith_t FAST_FUNC
arith(arith_state_t *math_state, const char *expr)
{
+ char const *err_rest, *emsg;
+ s64 res;
+
math_state->errmsg = NULL;
- math_state->list_of_recursed_names = NULL;
- return evaluate_string(math_state, expr);
+
+ switch(a_shexp_arith_eval(math_state, &res, expr, UZ_MAX, &err_rest)){
+ default:
+ return res;
+#undef a_X
+#define a_X(X,N) case CONCAT(a_SHEXP_ARITH_ERR_,X): emsg = N_(N); break
+ a_X(NOMEM, "out of memory");
+ a_X(SYNTAX, "syntax error");
+ a_X(ASSIGN_NO_VAR, "assignment without variable");
+ a_X(DIV_BY_ZERO, "division by zero");
+ a_X(EXP_INVALID, "invalid exponent");
+ a_X(NO_OPERAND, "syntax error, expected operand");
+ a_X(COND_NO_COLON, "syntax error, incomplete ?: condition");
+ a_X(NAME_LOOP, "recursive variable name reference");
+ a_X(OP_INVALID, "unknown operator");
+ }
+#undef a_X
+
+ math_state->errmsg = xasprintf("%s (rest: %s)", emsg, err_rest);
+
+ return -1;
}
/*
diff --git a/shell/math.h b/shell/math.h
index 41ef6e8dfa..6d54201086 100644
--- a/shell/math.h
+++ b/shell/math.h
@@ -76,11 +76,10 @@ typedef void FAST_FUNC (*arith_var_set_t)(const char
*name, const char *v
//typedef const char* FAST_FUNC (*arith_var_endofname_t)(const char *name);
typedef struct arith_state_t {
- const char *errmsg;
+ char *errmsg;
arith_var_lookup_t lookupvar;
arith_var_set_t setvar;
// arith_var_endofname_t endofname;
- void *list_of_recursed_names;
} arith_state_t;
arith_t FAST_FUNC arith(arith_state_t *state, const char *expr);
diff --git a/shell/shexp-arith.h b/shell/shexp-arith.h
new file mode 100644
index 0000000000..96a63bd2b9
--- /dev/null
+++ b/shell/shexp-arith.h
@@ -0,0 +1,1134 @@
+/*@ S-nail - a mail user agent derived from Berkeley Mail.
+ *@ Signed 64-bit sh(1)ell-style $(( ARITH ))metic expression evaluator.
+ *@ POW2 bases are parsed as unsigned, operation overflow is not handled,
+ *@ saturated mode is not supported, division by zero is handled via error.
+ *@ The maximum expression length is ~100.000.000 on 32-bit, U32_MAX otherwise.
+ *@ After reading on Dijkstra's two stack algorithm; found in the internet:
+ *@
+ *@ We can use Dijkstra's two stack algorithm to solve an equation.
+ *@ You need two stacks, a value stack (operands), and an operator stack.
+ *@ Numbers will be double values, operators will be char values.
+ *@ The whole of the expression is made up of tokens, ignoring whitespace.
+ *@
+ *@ While there are still tokens to read
+ *@ Get the next item
+ *@ If the item is:
+ *@ A number: push it onto the value stack.
+ *@ A left parenthesis: push it onto the operator stack.
+ *@ A right parenthesis:
+ *@ While the top of the operator stack is not a left parenthesis
+ *@ Pop the operator from the operator stack.
+ *@ Pop the value stack twice, getting two operands.
+ *@ Apply the operator to the operands, in the correct order.
+ *@ Push the result onto the value stack.
+ *@ Pop the left parenthesis from the operator stack
+ *@ An operator op:
+ *@ While the operator stack is not empty, and the top of the
+ *@ operator stack has the same or greater precedence as op,
+ *@ Pop the operator from the operator stack.
+ *@ Pop the value stack twice, getting two operands.
+ *@ Apply the operator to the operands, in the correct order.
+ *@ Push the result onto the value stack.
+ *@ Push op onto the operator stack.
+ *@ While the operator stack is not empty... [less push op]
+ *@
+ *@ At this point the operator stack should be empty, and the value stack
+ *@ should have only one value in it, which is the final result.
+ *@
+ *@ as well as bash:expr.c, a bit. Most heavily inspired by busybox.
+ *@ Conclusion: the algorithm scales badly to ternary and whiteouts; in order
+ *@ XXX to provide good error feedback the operator stack would need to contain
+ *@ XXX structs that remember string positions.
+ *
+ * Copyright (c) 2022 Steffen Nurpmeso <[email protected]>.
+ * SPDX-License-Identifier: ISC
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if 1
+# define a_SHEXP_ARITH_DBG 0
+# define a_SHEXP_ARITH_L(X)
+#else
+# define a_SHEXP_ARITH_DBG 1
+# define a_SHEXP_ARITH_L(X) a_shexp__arith_log X
+#endif
+
+/* We parse with base 0: set _RESCAN to allow "I=' -10';$((10#$I))" */
+#define a_SHEXP_ARITH_IDEC_MODE (su_IDEC_MODE_SIGNED_TYPE |\
+ su_IDEC_MODE_POW2BASE_UNSIGNED | su_IDEC_MODE_LIMIT_NOERROR |\
+ su_IDEC_MODE_BASE0_NUMBER_SIGN_RESCAN)
+
+enum a_shexp_arith_error{
+ a_SHEXP_ARITH_ERR_NONE,
+ a_SHEXP_ARITH_ERR_NOMEM, /* Out of memory */
+ a_SHEXP_ARITH_ERR_SYNTAX, /* General syntax error */
+ a_SHEXP_ARITH_ERR_ASSIGN_NO_VAR, /* Assignment without variable */
+ a_SHEXP_ARITH_ERR_DIV_BY_ZERO,
+ a_SHEXP_ARITH_ERR_EXP_INVALID, /* Invalid exponent */
+ a_SHEXP_ARITH_ERR_NO_OPERAND, /* Expected an argument here */
+ a_SHEXP_ARITH_ERR_COND_NO_COLON, /* Incomplete ?: condition */
+ a_SHEXP_ARITH_ERR_NAME_LOOP, /* Variable self-reference loop */
+ a_SHEXP_ARITH_ERR_OP_INVALID /* Unknown operator */
+};
+
+/* Operators and precedences in increasing precedence order.
+ * (The operator stack as such is u16: [OP_FLAGS |] (OP<<8) | PREC.)
+ * bash's expr.c says:
+ * Sub-expressions within parentheses have a precedence level greater than
+ * all of these levels and are evaluated first. Within a single precedence
+ * group, evaluation is left-to-right, except for arithmetic assignment,
+ * which is evaluated right-to-left (as in C). */
+enum a_shexp_arith_ops{
+#undef a_X
+#define a_X(N,P,O) \
+ CONCAT(a_SHEXP_ARITH_PREC_,N) = CONCAT(P,u),\
+ CONCAT(a_SHEXP_ARITH_OP_,N) =\
+ (CONCAT(O,u) << 8) | CONCAT(a_SHEXP_ARITH_PREC_,N)
+
+ a_X(PAREN_LEFT, 0, 0),
+
+ a_X(COMMA, 1, 0),
+
+ a_X(ASSIGN, 2, 0),
+ a_X(ASSIGN_BIT_OR, 2, 1),
+ a_X(ASSIGN_BIT_XOR, 2, 2),
+ a_X(ASSIGN_BIT_AND, 2, 3),
+ a_X(ASSIGN_SHIFT_LEFT, 2, 4), a_X(ASSIGN_SHIFT_RIGHT, 2, 5),
+ a_X(ASSIGN_SHIFT_RIGHTU, 2, 6),
+ a_X(ASSIGN_ADD, 2, 7), a_X(ASSIGN_SUB, 2, 8),
+ a_X(ASSIGN_MUL, 2, 9), a_X(ASSIGN_DIV, 2, 10), a_X(ASSIGN_MOD, 2, 11),
+ a_X(ASSIGN_EXP, 2, 12),
+
+ a_X(COND, 3, 0),
+ a_X(COND_COLON, 3, 1),
+
+ a_X(OR, 4, 0),
+ a_X(AND, 5, 0),
+ a_X(BIT_OR, 6, 0),
+ a_X(BIT_XOR, 7, 0),
+ a_X(BIT_AND, 8, 0),
+ a_X(EQ, 9, 0), a_X(NE, 9, 1),
+ a_X(LE, 10, 0), a_X(GE, 10, 1), a_X(LT, 10, 2), a_X(GT, 10, 3),
+ a_X(SHIFT_LEFT, 11, 0), a_X(SHIFT_RIGHT, 11, 1), a_X(SHIFT_RIGHTU, 11, 2),
+ a_X(ADD, 12, 0), a_X(SUB, 12, 1),
+ a_X(MUL, 13, 0), a_X(DIV, 13, 1), a_X(MOD, 13, 2),
+ a_X(EXP, 14, 0),
+
+ /* Further operators are unary, pre- or postfix */
+ a_SHEXP_ARITH_PREC_UNARY = 15,
+ a_SHEXP_ARITH_PREC_PREFIX = 16,
+ a_SHEXP_ARITH_PREC_POSTFIX = 18,
+
+ a_X(UNARY_NOT, 15, 0), a_X(UNARY_BIT_NOT, 15, 1),
+ a_X(PREFIX_INC, 16, 0), a_X(PREFIX_DEC, 16, 1),
+ a_X(UNARY_PLUS, 17, 1), a_X(UNARY_MINUS, 17, 0),
+ a_X(POSTFIX_INC, 18, 0), a_X(POSTFIX_DEC, 18, 1),
+
+ /* Beyond operator profanity; the first "is a number" */
+ a_SHEXP_ARITH_PREC_SKY = 19,
+ a_X(NUM, 19, 0), a_X(PAREN_RIGHT, 19, 1),
+
+#undef a_X
+};
+
+enum arith_op_flags{
+ /* Mask off operator and precision */
+ a_SHEXP_ARITH_OP_MASK = 0x1FFF,
+ a_SHEXP_ARITH_OP_FLAG_COND_SAW_COLON = 1u<<13,
+ a_SHEXP_ARITH_OP_FLAG_OUTER_WHITEOUT = 1u<<14,
+ a_SHEXP_ARITH_OP_FLAG_WHITEOUT = 1u<<15,
+ a_SHEXP_ARITH_OP_FLAG_WHITE_MASK = a_SHEXP_ARITH_OP_FLAG_OUTER_WHITEOUT |
+ a_SHEXP_ARITH_OP_FLAG_WHITEOUT,
+ a_SHEXP_ARITH_OP_FLAG_MASK = a_SHEXP_ARITH_OP_FLAG_COND_SAW_COLON |
+ a_SHEXP_ARITH_OP_FLAG_WHITE_MASK
+};
+
+struct a_shexp_arith_name_stack{
+ struct a_shexp_arith_name_stack *sans_last;
+ char const *sans_var;
+};
+
+struct a_shexp_arith_val{
+ s64 sav_val;
+ char *sav_var; /* Named variable or NIL */
+};
+
+struct a_shexp_arith_stack{
+ u16 *sas_ops;
+ u16 *sas_ops_top;
+ struct a_shexp_arith_val *sas_nums;
+ struct a_shexp_arith_val *sas_nums_top;
+};
+
+struct a_shexp_arith_ctx{
+ enum a_shexp_arith_error sac_error;
+ su_64( u8 sac__pad[4]; )
+ char const *sac_error_rest;
+ s64 sac_rv;
+ struct a_shexp_arith_stack *sac_stack;
+ struct a_shexp_arith_name_stack *sac_name_stack;
+#undef a_SHEXP_ARITH_IFS
+#ifdef mx_SOURCE
+# define a_SHEXP_ARITH_IFS(X) X
+ char const *sac_ifs_ws; /* IFS whitespace */
+#else
+# define a_SHEXP_ARITH_IFS(X)
+#endif
+#ifdef a_SHEXP_ARITH_COOKIE
+ a_SHEXP_ARITH_COOKIE sac_cookie;
+#endif
+};
+
+/* Sort by ~expected usage -- however, longest first if ambiguous!
+ * Follow busybox, save space by compressing data in char[] not struct[]!
+ * (XXX Instead use 1-st byte jump table like for commands) */
+static char const a_shexp_arith_op_toks[] = {
+#undef a_X
+#define a_X(X) \
+ S(char,(CONCAT(a_SHEXP_ARITH_OP_,X) & 0xFF00u) >> 8),\
+ S(char,CONCAT(a_SHEXP_ARITH_PREC_,X))
+
+ '+','+','\0', a_X(POSTFIX_INC),
+ '+','=','\0', a_X(ASSIGN_ADD),
+ '+','\0', a_X(ADD),
+ '-','-','\0', a_X(POSTFIX_DEC),
+ '-','=','\0', a_X(ASSIGN_SUB),
+ '-','\0', a_X(SUB),
+ '*','*','=','\0', a_X(ASSIGN_EXP),
+ '*','*','\0', a_X(EXP),
+ '*','=','\0', a_X(ASSIGN_MUL),
+ '*','\0', a_X(MUL),
+ '/','=','\0', a_X(ASSIGN_DIV),
+ '/','\0', a_X(DIV),
+ '%','=','\0', a_X(ASSIGN_MOD),
+ '%','\0', a_X(MOD),
+ '|','|','\0', a_X(OR),
+ '|','=','\0', a_X(ASSIGN_BIT_OR),
+ '|','\0', a_X(BIT_OR),
+ '^','=','\0', a_X(ASSIGN_BIT_XOR),
+ '^','\0', a_X(BIT_XOR),
+ '&','&','\0', a_X(AND),
+ '&','=','\0', a_X(ASSIGN_BIT_AND),
+ '&','\0', a_X(BIT_AND),
+ '<','<','=',0, a_X(ASSIGN_SHIFT_LEFT),
+ '<','<','\0', a_X(SHIFT_LEFT),
+ '>','>','>','=',0, a_X(ASSIGN_SHIFT_RIGHTU),
+ '>','>','>','\0', a_X(SHIFT_RIGHTU),
+ '>','>','=',0, a_X(ASSIGN_SHIFT_RIGHT),
+ '>','>','\0', a_X(SHIFT_RIGHT),
+
+ '~','\0', a_X(UNARY_BIT_NOT),
+ '!','=','\0', a_X(NE),
+ '!','\0', a_X(UNARY_NOT),
+
+ ')','\0', a_X(PAREN_RIGHT),
+ '(','\0', a_X(PAREN_LEFT),
+ ',','\0', a_X(COMMA),
+
+ '<','=','\0', a_X(LE),
+ '>','=','\0', a_X(GE),
+ '=','=','\0', a_X(EQ),
+ '<','\0', a_X(LT),
+ '>','\0', a_X(GT),
+ '=','\0', a_X(ASSIGN),
+
+ '?','\0', a_X(COND),
+ ':','\0', a_X(COND_COLON),
+
+ '\0'
+#undef a_X
+};
+
+/* Our "public" entry point. exp_buf can be NIL if exp_len is 0, it need not
+ * be NUL terminated (stop for NUL or out of length).
+ * Upon error *err_rest is set to a "newly allocated" string that points to
+ * where parse stopped (which could be at EOS if error happens in trailer). */
+static enum a_shexp_arith_error a_shexp_arith_eval(
+#ifdef a_SHEXP_ARITH_COOKIE
+ a_SHEXP_ARITH_COOKIE cookie,
+#endif
+ s64 *resp, char const *exp_buf, uz exp_len, char const **err_rest);
+
+static void a_shexp__arith_eval(struct a_shexp_arith_ctx *self,
+ char const *exp_buf, uz exp_len);
+
+/* Count non-WS as well as normalized WS ([:"space":]+ -> ' ') in exp_buf,
+ * return count. If store!=NIL, also copy normalization.
+ * An all-WS exp_buf returns 0 */
+static uz a_shexp__arith_ws_squeeze(struct a_shexp_arith_ctx *self,
+ char const *exp_buf, uz exp_len, char *store_or_nil);
+
+/* Resolve and evaluate the "self-contained string" savp->sav_var.
+ * Take care to avoid name lookup loops */
+static boole a_shexp__arith_val_eval(struct a_shexp_arith_ctx *self,
+ struct a_shexp_arith_val *savp);
+
+/* Work top of the stack, which may pop & push etc */
+static boole a_shexp__arith_op_apply(struct a_shexp_arith_ctx *self);
+
+static boole a_shexp__arith_op_apply_colons(struct a_shexp_arith_ctx *self);
+
+#if a_SHEXP_ARITH_DBG
+static void a_shexp__arith_log(char const *fmt, ...);
+#endif
+
+static enum a_shexp_arith_error
+a_shexp_arith_eval(
+#ifdef a_SHEXP_ARITH_COOKIE
+ a_SHEXP_ARITH_COOKIE cookie,
+#endif
+ s64 *resp, char const *exp_buf, uz exp_len, char const **err_rest){
+ struct a_shexp_arith_stack sas_stack;
+ struct a_shexp_arith_ctx self;
+ NYD_IN;
+
+ a_SHEXP_ARITH_L(("> arith_eval %zu <%.*s>\n",
+ exp_len, S(int,exp_len != UZ_MAX ? exp_len : su_cs_len(exp_buf)),
+ exp_buf));
+
+ STRUCT_ZERO(struct a_shexp_arith_ctx, &self);
+#ifdef a_SHEXP_ARITH_COOKIE
+ self.sac_cookie = cookie;
+#endif
+
+ ASSERT_NYD_EXEC(resp != NIL,
+ self.sac_error = a_SHEXP_ARITH_ERR_NO_OPERAND);
+ DBG( *resp = 0; )
+ ASSERT_NYD_EXEC(exp_len == 0 || exp_buf != NIL,
+ self.sac_error = a_SHEXP_ARITH_ERR_NO_OPERAND);
+
+ a_SHEXP_ARITH_IFS( self.sac_ifs_ws = ok_vlook(ifs_ws); )
+ self.sac_stack = &sas_stack;
+ a_shexp__arith_eval(&self, exp_buf, exp_len);
+
+ *resp = self.sac_rv;
+ if(self.sac_error != a_SHEXP_ARITH_ERR_NONE)
+ *err_rest = self.sac_error_rest;
+
+ a_SHEXP_ARITH_L(("< arith_eval %zu <%.*s> -> <%lld> ERR<%d>\n",
+ exp_len, S(int,exp_len != UZ_MAX ? exp_len : su_cs_len(exp_buf)),
+ exp_buf, self.sac_rv, self.sac_error));
+
+ NYD_OU;
+ return self.sac_error;
+}
+
+static void
+a_shexp__arith_eval(struct a_shexp_arith_ctx *self,
+ char const *exp_buf, uz exp_len){
+ char *ep, *cp, c;
+ u16 lop;
+ struct a_shexp_arith_stack *sasp;
+ void *mem_p;
+ NYD2_IN;
+
+ a_SHEXP_ARITH_L((" > _arith_eval %zu <%.*s>\n",
+ exp_len, S(int,exp_len != UZ_MAX ? exp_len : su_cs_len(exp_buf)),
+ exp_buf));
+
+ self->sac_error_rest = su_empty;
+ mem_p = NIL;
+
+ sasp = self->sac_stack;
+
+ /* Create single contiguous allocation for anything.
+ * Since we need to keep pointers to variable names along the way, simply
+ * NUL terminate in this large buffer (move backward by one first) */
+ /* C99 */{
+ union {void *v; char *c;} p;
+ uz i, j, a;
+
+ /* Done for empty expression */
+ if((i = a_shexp__arith_ws_squeeze(self, exp_buf, exp_len, NIL)) == 0)
+ goto jleave;
+
+ /* Overflow check: since arithmetic expressions are rarely long enough
+ * to come near this limit, laxe & fuzzy, not exact; max U32_MAX! */
+ if(su_64( i > U32_MAX || )
+ i >= ((UZ_MAX - i) / (su_ALIGNOF(*sasp->sas_nums) +
+ sizeof(*sasp->sas_ops) + 1))){
+ self->sac_error = a_SHEXP_ARITH_ERR_NOMEM;
+ goto jleave;
+ }
+
+ j = su_ALIGNOF(*sasp->sas_nums) * ((i + 1) >> 1);
+ a = j + (sizeof(*sasp->sas_ops) * (i + 1)) + i + 1 +1;
+ mem_p = p.v = su_LOFI_ALLOC(a);
+ if(p.v == NIL){
+ /* (For MX LOFI has _MUSTFAIL set though) */
+ self->sac_error = a_SHEXP_ARITH_ERR_NOMEM;
+ goto jleave;
+ }
+ sasp->sas_nums = sasp->sas_nums_top = S(struct a_shexp_arith_val*,p.v);
+ p.c += j;
+ sasp->sas_ops = sasp->sas_ops_top = S(u16*,p.v);
+ p.c += sizeof(*sasp->sas_ops) * i;
+ /* (room for moving back vars by one, to NUL terminate 'em) */
+ a_shexp__arith_ws_squeeze(self, exp_buf, exp_len, ep = ++p.c);
+
+ a_SHEXP_ARITH_L((" ! _arith_eval ALLOC <%lu> "
+ "nums=%p (%lu) ops=%p %lu <%s>\n",
+ S(ul,a), sasp->sas_nums, S(ul,j / su_ALIGNOF(*sasp->sas_nums)),
+ sasp->sas_ops, S(ul,i), ep));
+ }
+
+ /* Start with a left paren */
+ *sasp->sas_ops_top++ = lop = a_SHEXP_ARITH_OP_PAREN_LEFT;
+
+ for(;;) Jouter:{
+ u16 op;
+
+ a_SHEXP_ARITH_L((" = _arith_eval TICK LOP <0x%02X %u> "
+ "nums=%lu ops=%lu DATA %lu <%s>\n",
+ lop, lop & 0xFF, S(ul,sasp->sas_nums_top - sasp->sas_nums),
+ S(ul,sasp->sas_ops_top - sasp->sas_ops), S(ul,su_cs_len(ep)), ep));
+
+ self->sac_error_rest = ep;
+
+ if(*ep == '\0'){
+ /* At the end of the expression pop anything left.
+ * Assume we have read PAREN_RIGHT */
+ if(exp_buf != NIL){
+ exp_buf = NIL;
+ op = a_SHEXP_ARITH_OP_PAREN_RIGHT;
+ ASSERT(sasp->sas_ops_top > sasp->sas_ops);
+ goto jtok_go;
+ }
+
+ /* After PAREN_RIGHT, we must be finished */
+ if(sasp->sas_nums_top != &sasp->sas_nums[1])
+ self->sac_error = a_SHEXP_ARITH_ERR_SYNTAX;
+ break;
+ }
+
+ /* Skip (normalized) WS now */
+ if(*ep == ' ')
+ ++ep;
+ ASSERT(!su_cs_is_space(*ep));
+
+ /* A number? */
+ if(su_cs_is_digit(*ep)){
+ BITENUM_IS(u32,su_idec_state) is;
+
+ is = su_idec_cp(&sasp->sas_nums_top->sav_val, ep, 0,
+ a_SHEXP_ARITH_IDEC_MODE, S(char const**,&ep));
+ if((is &= su_IDEC_STATE_EMASK) && is != su_IDEC_STATE_EBASE)
+ sasp->sas_nums_top->sav_val = 0;
+ sasp->sas_nums_top->sav_var = NIL;
+
+ ++sasp->sas_nums_top;
+ lop = a_SHEXP_ARITH_OP_NUM;
+ a_SHEXP_ARITH_L((" + _arith_eval NUM <%lld>\n",
+ sasp->sas_nums_top[-1].sav_val));
+ continue;
+ }
+
+ /* Is it a variable name? */
+ for(cp = ep; (c = *cp, a_SHEXP_ISVARC(c)); ++cp)
+ if(cp == ep && a_SHEXP_ISVARC_BAD1ST(c))
+ break;
+
+ if(cp != ep){
+ for(;;){
+ c = cp[-1];
+ if(!a_SHEXP_ISVARC_BADNST(c))
+ break;
+ if(--cp == ep){
+ self->sac_error = a_SHEXP_ARITH_ERR_SYNTAX;
+ goto jleave;
+ }
+ }
+
+ /* We reserved one byte at the front, so we can simply move back
+ * the variable name by one, and then NUL terminate it.
+ * (Unfortunately we do _have_ to copy; even more weird: move!) */
+ su_mem_move(sasp->sas_nums_top->sav_var = &ep[-1], ep, P2UZ(cp - ep));
+ cp[-1] = '\0';
+ ep = cp;
+
+ ++sasp->sas_nums_top;
+ lop = a_SHEXP_ARITH_OP_NUM;
+ a_SHEXP_ARITH_L((" + _arith_eval VAR <%s>\n",
+ sasp->sas_nums_top[-1].sav_var));
+ continue;
+ }
+
+ /* An operator.
+ * We turn prefix operators to multiple unary plus/minus if
+ * not attached to a variable name (++10 -> + + 10).
+ * (We adjust postfix to prefix below) */
+ if((ep[0] == '+' || ep[0] == '-') && (ep[1] == ep[0])){
+ if(sasp->sas_nums_top == sasp->sas_nums ||
+ sasp->sas_nums_top[-1].sav_var == NIL){
+ if((c = ep[2]) == ' ')
+ c = ep[3];
+
+ if(c != '\0' && (!a_SHEXP_ISVARC(c) || a_SHEXP_ISVARC_BAD1ST(c))){
+ op = (ep[0] == '+') ? a_SHEXP_ARITH_OP_ADD
+ : a_SHEXP_ARITH_OP_SUB;
+ ++ep;
+ a_SHEXP_ARITH_L((" + _arith_eval OP PREFIX INC/DEC SPLIT "
+ "<%c%c> -> <%c>\n", ep[0], ep[0], ep[0]));
+ goto jtok_go;
+ }
+ }
+ }
+
+ /* Operator search */
+ /* C99 */{
+ char const *tokp;
+
+ /* 3=NUL+OP+PREC */
+ for(tokp = a_shexp_arith_op_toks; *tokp != '\0'; tokp += 3){
+ for(cp = ep;; ++tokp, ++cp){
+ if(*tokp == '\0'){
+ ep = cp;
+ op = (S(u16,tokp[1]) << 8) | S(u8,tokp[2]);
+ goto jtok_go;
+ }else if(*tokp != *cp)
+ break;
+ }
+
+ while(*tokp != '\0')
+ ++tokp;
+ }
+ self->sac_error = a_SHEXP_ARITH_ERR_OP_INVALID;
+ goto jleave;
+ }
+
+jtok_go:/* C99 */{
+ u8 prec;
+
+ prec = op & 0xFF;
+ a_SHEXP_ARITH_L((" + _arith_eval OP <0x%02X %u> LOP <0x%02X %u> "
+ "nums=%lu ops=%lu %lu <%s>\n",
+ op, prec, lop, lop & 0xFF, S(ul,sasp->sas_nums_top - sasp->sas_nums),
+ S(ul,sasp->sas_ops_top - sasp->sas_ops), S(ul,su_cs_len(ep)), ep));
+
+ if(op == a_SHEXP_ARITH_OP_UNARY_PLUS){
+ a_SHEXP_ARITH_L((" + _arith_eval IGNORE UNARY PLUS\n"));
+ continue;
+ }
+
+ /* Correct our understanding of what there is.
+ * Post grammar: a++ reduce to num */
+ if((lop & 0xFF) == a_SHEXP_ARITH_PREC_POSTFIX){
+ lop = a_SHEXP_ARITH_OP_NUM;
+ a_SHEXP_ARITH_L((" + _arith_eval LOP POSTFIX REDUCED to NUM\n"));
+ }
+ /* Adjust some binary/postfix operators */
+ else if(lop != a_SHEXP_ARITH_OP_NUM){
+ switch(op){
+ case a_SHEXP_ARITH_OP_ADD:
+ a_SHEXP_ARITH_L((" + _arith_eval OP ADJUST: IGNORE UNARY PLUS\n"));
+ continue;
+ case a_SHEXP_ARITH_OP_SUB:
+ op = a_SHEXP_ARITH_OP_UNARY_MINUS;
+ goto junapre;
+ case a_SHEXP_ARITH_OP_POSTFIX_INC:
+ op = a_SHEXP_ARITH_OP_PREFIX_INC;
+ goto junapre;
+ case a_SHEXP_ARITH_OP_POSTFIX_DEC:
+ op = a_SHEXP_ARITH_OP_PREFIX_DEC;
+junapre:
+ prec = a_SHEXP_ARITH_PREC_PREFIX;
+ a_SHEXP_ARITH_L((" + _arith_eval OP ADJUST TO UNARY/PREFIX\n"));
+ break;
+ }
+ }
+ /* Special: +10++VAR -> +10 + +VAR. (Since we do handle +10++11
+ * correctly via "prefix split", we should also handle this) */
+ else if(prec == a_SHEXP_ARITH_PREC_POSTFIX){
+ ASSERT(lop == a_SHEXP_ARITH_OP_NUM);
+ if((c = ep[0]) == ' ')
+ c = ep[1];
+ if(c != '\0' && (a_SHEXP_ISVARC(c) && !a_SHEXP_ISVARC_BAD1ST(c))){
+ c = *--ep;
+ op = (c == '+') ? a_SHEXP_ARITH_OP_ADD : a_SHEXP_ARITH_OP_SUB;
+ prec = op & 0xFF;
+ a_SHEXP_ARITH_L((" + _arith_eval OP POSTFIX INC/DEC SPLIT "
+ "<%c%c> -> <%c>\n", c, c, c));
+ }
+ }
+
+ if((prec > a_SHEXP_ARITH_PREC_PAREN_LEFT &&
+ prec < a_SHEXP_ARITH_PREC_UNARY) ||
+ prec >= a_SHEXP_ARITH_PREC_SKY){
+ if(lop != a_SHEXP_ARITH_OP_NUM){
+ self->sac_error = a_SHEXP_ARITH_ERR_NO_OPERAND;
+ goto jleave;
+ }
+
+ /* Pop as much as possible */
+ while(sasp->sas_ops_top != sasp->sas_ops){
+ lop = *--sasp->sas_ops_top & a_SHEXP_ARITH_OP_MASK;
+
+ a_SHEXP_ARITH_L((" + _arith_eval TRY POP - OP "
+ "<0x%02X %u>, NEW LOP <0x%02X %u 0x%X> nums=%lu ops=%lu\n",
+ op, op & 0xFF, lop, lop & 0xFF,
+ (*sasp->sas_ops_top & a_SHEXP_ARITH_OP_FLAG_MASK),
+ S(ul,sasp->sas_nums_top - sasp->sas_nums),
+ S(ul,sasp->sas_ops_top - sasp->sas_ops)));
+
+ /* Special-case parenthesis groups */
+ if(op == a_SHEXP_ARITH_OP_PAREN_RIGHT){
+ if(lop == a_SHEXP_ARITH_OP_PAREN_LEFT){
+ ASSERT(sasp->sas_nums_top > sasp->sas_nums);
+ if(!(*sasp->sas_ops_top & a_SHEXP_ARITH_OP_FLAG_WHITE_MASK)){
+ /* Resolve VAR to NUM */
+ if(sasp->sas_nums_top[-1].sav_var != NIL){
+ if(!a_shexp__arith_val_eval(self,
+ &sasp->sas_nums_top[-1]))
+ goto jleave;
+ }
+ a_SHEXP_ARITH_L((" + _arith_eval OP () RESOLVED <%lld>\n",
+ sasp->sas_nums_top[-1].sav_val));
+ }
+ sasp->sas_nums_top[-1].sav_var = NIL;
+ lop = a_SHEXP_ARITH_OP_NUM;
+ goto Jouter;
+ }
+ }else{
+ u8 lprec;
+
+ lprec = lop & 0xFF;
+
+ /* */
+ if(op == a_SHEXP_ARITH_OP_COND){
+ u16 x;
+
+ x = *sasp->sas_ops_top;
+ x &= a_SHEXP_ARITH_OP_FLAG_MASK;
+ if(x & a_SHEXP_ARITH_OP_FLAG_WHITEOUT){
+ x ^= a_SHEXP_ARITH_OP_FLAG_WHITEOUT;
+ x |= a_SHEXP_ARITH_OP_FLAG_OUTER_WHITEOUT;
+ }
+ op |= x;
+
+ /* Resolve as much as possible */
+ while(lprec > a_SHEXP_ARITH_PREC_PAREN_LEFT &&
+ lprec != a_SHEXP_ARITH_PREC_COND){
+ if(!a_shexp__arith_op_apply(self))
+ goto jleave;
+ lop = *--sasp->sas_ops_top & a_SHEXP_ARITH_OP_MASK;
+ lprec = lop & 0xFF;
+ }
+
+ ASSERT(sasp->sas_nums_top > sasp->sas_nums);
+ if((--sasp->sas_nums_top)->sav_val == 0)
+ op |= a_SHEXP_ARITH_OP_FLAG_WHITEOUT;
+
+ op |= *sasp->sas_ops_top & a_SHEXP_ARITH_OP_FLAG_MASK;
+ /* Delay ternary */
+ ++sasp->sas_ops_top;
+ break;
+ }else if(op == a_SHEXP_ARITH_OP_COND_COLON){
+ uz recur;
+ u16 *opsp, x;
+ boole delay;
+
+ delay = TRU1;
+
+ /* Find our counterpart ? so we can toggle whiteout */
+ opsp = sasp->sas_ops_top;
+ for(recur = 1;; --opsp){
+ if(opsp == sasp->sas_ops){
+ self->sac_error = a_SHEXP_ARITH_ERR_SYNTAX;
+ goto jleave;
+ }
+
+ x = *opsp & a_SHEXP_ARITH_OP_MASK;
+ if(x == a_SHEXP_ARITH_OP_COND_COLON)
+ ++recur;
+ else if(x == a_SHEXP_ARITH_OP_COND && --recur == 0){
+ *opsp |= a_SHEXP_ARITH_OP_FLAG_COND_SAW_COLON;
+ break;
+ }
+ }
+ op |= *opsp & a_SHEXP_ARITH_OP_FLAG_MASK;
+ op ^= a_SHEXP_ARITH_OP_FLAG_WHITEOUT;
+
+ /* Resolve innermost condition asap.
+ * In "1 ? 0 ? 5 : 6 : 3", resolve innermost upon :3 */
+ while(lprec > a_SHEXP_ARITH_PREC_PAREN_LEFT &&
+ lprec != a_SHEXP_ARITH_PREC_COND){
+ if(!a_shexp__arith_op_apply(self))
+ goto jleave;
+ lop = *--sasp->sas_ops_top & a_SHEXP_ARITH_OP_MASK;
+ lprec = lop & 0xFF;
+ }
+
+ /* If we now see a COLON, we have to resolve further!
+ * This is because of code flow restrictions of the Dijkstra
+ * algorithm, which fits ternary (as well as visual user
+ * error feedback) badly (the way we do): pop as pop can! */
+ if(lop == a_SHEXP_ARITH_OP_COND_COLON){
+ delay = FAL0;
+ if(!a_shexp__arith_op_apply_colons(self))
+ goto jleave;
+ lop = *sasp->sas_ops_top & a_SHEXP_ARITH_OP_MASK;
+ }
+
+ if(lop != a_SHEXP_ARITH_OP_COND){
+ self->sac_error = a_SHEXP_ARITH_ERR_SYNTAX;
+ goto jleave;
+ }
+
+ if(delay)
+ ++sasp->sas_ops_top;
+ a_SHEXP_ARITH_L((" + _arith_eval DELAY TERNARY ?:%s\n",
+ ((op & a_SHEXP_ARITH_OP_FLAG_WHITE_MASK)
+ ? " WHITEOUT" : su_empty)));
+ break;
+ }
+ /* Is this a right-associative operation? */
+ else if(lprec < prec){
+ ++sasp->sas_ops_top;
+ a_SHEXP_ARITH_L((" + _arith_eval DELAY PRECEDENCE\n"));
+ break;
+ }else if(lprec == prec && prec == a_SHEXP_ARITH_PREC_ASSIGN){
+ ++sasp->sas_ops_top;
+ a_SHEXP_ARITH_L((" + _arith_eval DELAY RIGHT ASSOC\n"));
+ break;
+ }else if(lop == a_SHEXP_ARITH_OP_COND){
+ ++sasp->sas_ops_top;
+ a_SHEXP_ARITH_L((" + _arith_eval DELAY CONDITION\n"));
+ break;
+ }
+ }
+
+ /* */
+ if(!a_shexp__arith_op_apply(self))
+ goto jleave;
+
+ if(lop == a_SHEXP_ARITH_OP_COND_COLON){
+ ASSERT(sasp->sas_ops_top > sasp->sas_ops &&
+ &sasp->sas_ops_top[-1] > sasp->sas_ops);
+ ASSERT((sasp->sas_ops_top[-1] & a_SHEXP_ARITH_OP_MASK
+ ) == a_SHEXP_ARITH_OP_COND);
+ --sasp->sas_ops_top;
+ *sasp->sas_ops_top ^= a_SHEXP_ARITH_OP_FLAG_WHITEOUT;
+ }
+ }
+
+ if(op == a_SHEXP_ARITH_OP_PAREN_RIGHT){
+ self->sac_error = a_SHEXP_ARITH_ERR_SYNTAX;
+ goto jleave;
+ }
+ }
+
+ /* Push this operator to the stack and remember it */
+ if(sasp->sas_ops_top > sasp->sas_ops &&
+ (op & 0xFF) != a_SHEXP_ARITH_PREC_COND)
+ op |= sasp->sas_ops_top[-1] & a_SHEXP_ARITH_OP_FLAG_MASK;
+ *sasp->sas_ops_top++ = op;
+ lop = op & a_SHEXP_ARITH_OP_MASK;
+ a_SHEXP_ARITH_L((" + _arith_eval OP PUSH <0x%02X %u> nums=%lu ops=%lu\n",
+ op, (op & 0xFF), S(ul,sasp->sas_nums_top - sasp->sas_nums),
+ S(ul,sasp->sas_ops_top - sasp->sas_ops)));
+ }
+ }
+
+ self->sac_rv = sasp->sas_nums->sav_val;
+
+jleave:
+ if(self->sac_error != a_SHEXP_ARITH_ERR_NONE)
+ self->sac_error_rest = savestr(self->sac_error_rest);
+
+ if(mem_p != NIL)
+ su_LOFI_FREE(mem_p);
+
+ a_SHEXP_ARITH_L((" < _arith_eval <%lld> ERR<%d>\n",
+ self->sac_rv, self->sac_error));
+
+ NYD2_OU;
+}
+
+static uz
+a_shexp__arith_ws_squeeze(struct a_shexp_arith_ctx *self,
+ char const *exp_buf, uz exp_len, char *store_or_nil){
+ a_SHEXP_ARITH_IFS( char const *ifs_ws; )
+ char c;
+ boole last_ws, ws;
+ uz rv;
+ NYD2_IN;
+ UNUSED(self);
+
+ rv = 0;
+ a_SHEXP_ARITH_IFS( ifs_ws = self->sac_ifs_ws; )
+
+ for(;; ++exp_buf, --exp_len){
+ if(UNLIKELY(exp_len == 0) || UNLIKELY((c = *exp_buf) == '\0'))
+ goto jleave;
+ if(!(su_cs_is_space(c)
+ a_SHEXP_ARITH_IFS( || su_cs_find_c(ifs_ws, c) != NIL )
+ ))
+ break;
+ }
+
+ for(last_ws = FAL0;; ++exp_buf, --exp_len){
+ if(UNLIKELY(exp_len == 0) || UNLIKELY((c = *exp_buf) == '\0'))
+ break;
+
+ ws = (su_cs_is_space(c)
+ a_SHEXP_ARITH_IFS( || su_cs_find_c(ifs_ws, c) != NIL )
+ );
+ if(ws){
+ if(last_ws)
+ continue;
+ c = ' ';
+ }
+ last_ws = ws;
+
+ ++rv;
+ if(store_or_nil != NIL)
+ *store_or_nil++ = c;
+ }
+
+ if(last_ws){
+ --rv;
+ if(store_or_nil != NIL)
+ --store_or_nil;
+ }
+
+jleave:
+ if(store_or_nil != NIL)
+ *store_or_nil = '\0';
+
+ NYD2_OU;
+ return rv;
+}
+
+static boole
+a_shexp__arith_val_eval(struct a_shexp_arith_ctx *self,
+ struct a_shexp_arith_val *savp){
+ struct a_shexp_arith_name_stack sans_stack, *sansp;
+ struct a_shexp_arith_stack sas_stack, *sasp;
+ char const *cp;
+ NYD_IN;
+ ASSERT(savp->sav_var != NIL);
+
+ a_SHEXP_ARITH_L(("> _arith_val_eval %p <%s>\n", savp, savp->sav_var));
+
+ savp->sav_val = 0;
+
+ /* Also look in program environment XXX configurable? */
+ cp = n_var_vlook(savp->sav_var, TRU1);
+ if(cp == NIL)
+ goto jleave;
+
+ for(sansp = self->sac_name_stack; sansp != NIL; sansp = sansp->sans_last){
+ if(!su_cs_cmp(sansp->sans_var, savp->sav_var)){
+ self->sac_error = a_SHEXP_ARITH_ERR_NAME_LOOP;
+ goto jleave;
+ }
+ }
+
+ /* cp must be a self-contained expression.
+ * However, in most cases it solely consists of an integer, shortcut it! */
+ if(su_cs_is_digit(*cp) && (su_idec_cp(&savp->sav_val, cp, 0,
+ a_SHEXP_ARITH_IDEC_MODE, NIL) & su_IDEC_STATE_CONSUMED)){
+ a_SHEXP_ARITH_L((" + _arith_val_eval NUM DIRECT <%lld>\n",
+ savp->sav_val));
+ }else{
+ sasp = self->sac_stack;
+ self->sac_stack = &sas_stack;
+
+ sans_stack.sans_last = sansp = self->sac_name_stack;
+ sans_stack.sans_var = savp->sav_var;
+ self->sac_name_stack = &sans_stack;
+
+ a_shexp__arith_eval(self, cp, UZ_MAX);
+ savp->sav_val = self->sac_rv;
+ /* .sav_var may be needed further on for updating purposes */
+
+ self->sac_stack = sasp;
+ self->sac_name_stack = sansp;
+ }
+
+ cp = NIL;
+jleave:
+ a_SHEXP_ARITH_L(("< _arith_val_eval %p <%s> <%lld> -> OK <%d>\n",
+ savp, savp->sav_var, savp->sav_val,
+ (cp == NIL && self->sac_error == a_SHEXP_ARITH_ERR_NONE)));
+
+ NYD_OU;
+ return (cp == NIL && self->sac_error == a_SHEXP_ARITH_ERR_NONE);
+}
+
+static boole
+a_shexp__arith_op_apply(struct a_shexp_arith_ctx *self){
+ struct a_shexp_arith_val *nums_top;
+ u8 prec;
+ u16 op;
+ struct a_shexp_arith_stack *sasp;
+ s64 val;
+ boole rv, ign;
+ NYD_IN;
+
+ rv = FAL0;
+ val = 0;
+ sasp = self->sac_stack;
+ op = *sasp->sas_ops_top & a_SHEXP_ARITH_OP_MASK;
+ ign = ((*sasp->sas_ops_top & a_SHEXP_ARITH_OP_FLAG_WHITE_MASK) != 0);
+
+ a_SHEXP_ARITH_L((" > _arith_op_apply %s<0x%02X %u> "
+ "nums_top=%p (%lu) ops_top=%p (%lu)\n",
+ (ign ? "WHITEOUT " : su_empty), op, (op & 0xFF), sasp->sas_nums_top,
+ S(ul,sasp->sas_nums_top - sasp->sas_nums),
+ sasp->sas_ops_top, S(ul,sasp->sas_ops_top - sasp->sas_ops)));
+
+ /* At least one argument is always needed */
+ if((nums_top = sasp->sas_nums_top) == sasp->sas_nums){
+ self->sac_error = a_SHEXP_ARITH_ERR_NO_OPERAND;
+ goto jleave;
+ }
+ --nums_top;
+
+ /* Resolve name to value as necessary */
+ if(!ign && nums_top->sav_var != NIL &&
+ !a_shexp__arith_val_eval(self, nums_top))
+ goto jleave;
+
+ val = nums_top->sav_val;
+ prec = op & 0xFF;
+
+ /* Not a binary operator? */
+ if(prec >= a_SHEXP_ARITH_PREC_UNARY && prec < a_SHEXP_ARITH_PREC_SKY){
+ if(ign)
+ goto jquick;
+
+ switch(op){
+ default: break;
+ case a_SHEXP_ARITH_OP_UNARY_NOT: val = !val; break;
+ case a_SHEXP_ARITH_OP_UNARY_BIT_NOT: val = ~val; break;
+ case a_SHEXP_ARITH_OP_UNARY_MINUS: val = -val; break;
+ case a_SHEXP_ARITH_OP_PREFIX_INC: FALLTHRU
+ case a_SHEXP_ARITH_OP_POSTFIX_INC: ++val; break;
+ case a_SHEXP_ARITH_OP_PREFIX_DEC: FALLTHRU
+ case a_SHEXP_ARITH_OP_POSTFIX_DEC: --val; break;
+ }
+ }else if(op == a_SHEXP_ARITH_OP_COND){
+ if(!(*sasp->sas_ops_top & a_SHEXP_ARITH_OP_FLAG_COND_SAW_COLON)){
+ self->sac_error = a_SHEXP_ARITH_ERR_COND_NO_COLON;
+ goto jleave;
+ }
+ goto jquick;
+ }else if(op == a_SHEXP_ARITH_OP_COND_COLON){
+ ASSERT(nums_top > sasp->sas_nums);
+ if(!ign)
+ nums_top[-1].sav_val = nums_top[0].sav_val;
+ else
+ val = nums_top[-1].sav_val;
+ sasp->sas_nums_top = nums_top;
+
+ ASSERT(sasp->sas_ops_top > sasp->sas_ops);
+ if((sasp->sas_ops_top[-1] & a_SHEXP_ARITH_OP_MASK
+ ) == a_SHEXP_ARITH_OP_COND_COLON){
+ --sasp->sas_ops_top;
+ if(!a_shexp__arith_op_apply_colons(self))
+ goto jleave;
+ if(!ign)
+ sasp->sas_nums_top[-1].sav_val = val;
+ }
+ }else{
+ /* Binaries need two numbers: one is popped, the other replaced */
+ s64 rval;
+
+ if(nums_top == sasp->sas_nums){
+ self->sac_error = a_SHEXP_ARITH_ERR_NO_OPERAND;
+ goto jleave;
+ }
+ sasp->sas_nums_top = nums_top--;
+
+ if(ign)
+ goto jquick;
+
+ /* Resolve LHV as necessary */
+ if(op != a_SHEXP_ARITH_OP_COMMA && op != a_SHEXP_ARITH_OP_ASSIGN &&
+ nums_top->sav_var != NIL &&
+ !a_shexp__arith_val_eval(self, nums_top))
+ goto jleave;
+
+ rval = val;
+ val = nums_top->sav_val; /* (may be bogus for assign, fixed soon) */
+
+ /* In precedence order (excluding assignments) */
+ switch(op){
+ default: break;
+ case a_SHEXP_ARITH_OP_COMMA: FALLTHRU
+
+ case a_SHEXP_ARITH_OP_ASSIGN: val = rval; break;
+
+ case a_SHEXP_ARITH_OP_OR: val = (val != 0 || rval != 0); break;
+ case a_SHEXP_ARITH_OP_AND: val = (val != 0 && rval != 0); break;
+
+ case a_SHEXP_ARITH_OP_BIT_OR: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_BIT_OR: val |= rval; break;
+ case a_SHEXP_ARITH_OP_BIT_XOR: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_BIT_XOR: val ^= rval; break;
+ case a_SHEXP_ARITH_OP_BIT_AND: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_BIT_AND: val &= rval; break;
+
+ case a_SHEXP_ARITH_OP_EQ: val = (val == rval); break;
+ case a_SHEXP_ARITH_OP_NE: val = (val != rval); break;
+
+ case a_SHEXP_ARITH_OP_LE: val = (val <= rval); break;
+ case a_SHEXP_ARITH_OP_GE: val = (val >= rval); break;
+ case a_SHEXP_ARITH_OP_LT: val = (val < rval); break;
+ case a_SHEXP_ARITH_OP_GT: val = (val > rval); break;
+
+ case a_SHEXP_ARITH_OP_SHIFT_LEFT: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_SHIFT_LEFT: val <<= rval; break;
+
+ case a_SHEXP_ARITH_OP_SHIFT_RIGHT: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_SHIFT_RIGHT: val >>= rval; break;
+
+ case a_SHEXP_ARITH_OP_SHIFT_RIGHTU: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_SHIFT_RIGHTU:
+ val = S(s64,S(u64,val) >> rval);
+ break;
+
+ case a_SHEXP_ARITH_OP_ADD: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_ADD: val += rval; break;
+ case a_SHEXP_ARITH_OP_SUB: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_SUB: val -= rval; break;
+
+ case a_SHEXP_ARITH_OP_MUL: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_MUL: val *= rval; break;
+ /* For /,%, avoid lvh=S64_MIN, rhv=-1:
+ * CHANGES, bash 4.3 [ac50fbac377e32b98d2de396f016ea81e8ee9961]:
+ * Fixed a bug that caused floating-point exceptions and
+ * overflow errors for the / and % arithmetic operators when
+ * using INTMAX_MIN and -1. */
+ case a_SHEXP_ARITH_OP_DIV: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_DIV:
+ if(rval == 0){
+ self->sac_error = a_SHEXP_ARITH_ERR_DIV_BY_ZERO;
+ goto jleave;
+ }else if(val != S64_MIN || rval != -1)
+ val /= rval;
+ break;
+ case a_SHEXP_ARITH_OP_MOD: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_MOD:
+ if(rval == 0){
+ self->sac_error = a_SHEXP_ARITH_ERR_DIV_BY_ZERO;
+ goto jleave;
+ }else if(val == S64_MIN && rval == -1)
+ val = 0;
+ else
+ val %= rval;
+ break;
+
+ case a_SHEXP_ARITH_OP_EXP: FALLTHRU
+ case a_SHEXP_ARITH_OP_ASSIGN_EXP:
+ if(rval < 0){
+ self->sac_error = a_SHEXP_ARITH_ERR_EXP_INVALID;
+ goto jleave;
+ }else{
+ s64 i;
+
+ for(i = 1; rval > 0; --rval)
+ i *= val;
+ val = i;
+ }
+ break;
+ }
+ }
+
+ /* Assignment updates a variable, which must exist.
+ * For prefix and postfix operators, too: we already turned them into
+ * multiple unary plus/minus unless we had seen a variable name */
+jquick:
+ if(prec == a_SHEXP_ARITH_PREC_ASSIGN || prec == a_SHEXP_ARITH_PREC_PREFIX ||
+ prec == a_SHEXP_ARITH_PREC_POSTFIX){
+ char buf[su_IENC_BUFFER_SIZE], *bp;
+
+ if(nums_top->sav_var == NIL){
+ self->sac_error = a_SHEXP_ARITH_ERR_ASSIGN_NO_VAR;
+ goto jleave;
+ }
+
+ if(!ign){
+ bp = su_ienc_s64(buf, val, 10);
+ n_var_vset(nums_top->sav_var, S(up,bp), FAL0);
+ }
+
+ /* And restore the stack value again for postfix operators */
+ if(op == a_SHEXP_ARITH_OP_POSTFIX_INC)
+ --val;
+ else if(op == a_SHEXP_ARITH_OP_POSTFIX_DEC)
+ ++val;
+
+ if(!ign)
+ a_SHEXP_ARITH_L((" + _arith_op_apply VAR <%s> SET <%s> VAL <%lld>\n",
+ nums_top->sav_var, bp, val));
+ }
+
+ nums_top->sav_val = val;
+ nums_top->sav_var = NIL;
+
+ rv = TRU1;
+jleave:
+ a_SHEXP_ARITH_L((" < _arith_op_apply RV %d <0x%02X %u> RES<%lld> ERR<%d> "
+ "nums=%lu ops=%lu\n",
+ rv, op, op & 0xFF, val, self->sac_error,
+ S(ul,sasp->sas_nums_top - sasp->sas_nums),
+ S(ul,sasp->sas_ops_top - sasp->sas_ops)));
+
+ NYD_OU;
+ return rv;
+}
+
+static boole
+a_shexp__arith_op_apply_colons(struct a_shexp_arith_ctx *self){
+ u16 lop, lprec;
+ boole next_stop;
+ NYD_IN;
+
+ for(next_stop = FAL0;;){
+ if(!a_shexp__arith_op_apply(self)){
+ next_stop = FAL0;
+ break;
+ }
+ if(next_stop)
+ break;
+ lop = *--self->sac_stack->sas_ops_top & a_SHEXP_ARITH_OP_MASK;
+ lprec = lop & 0xFF;
+ next_stop = (lprec == a_SHEXP_ARITH_PREC_PAREN_LEFT ||
+ lop == a_SHEXP_ARITH_OP_COND);
+ }
+
+ NYD_OU;
+ return next_stop;
+}
+
+#if a_SHEXP_ARITH_DBG
+static void
+a_shexp__arith_log(char const *fmt, ...){
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+#endif
+
+#undef a_SHEXP_ARITH_IFS
+
+#undef a_SHEXP_ARITH_DBG
+#undef a_SHEXP_ARITH_L
+#undef a_SHEXP_ARITH_IDEC_MODE
+
+/* s-it-mode */
--
2.37.1
--steffen
|
|Der Kragenbaer, The moon bear,
|der holt sich munter he cheerfully and one by one
|einen nach dem anderen runter wa.ks himself off
|(By Robert Gernhardt)
_______________________________________________
busybox mailing list
[email protected]
http://lists.busybox.net/mailman/listinfo/busybox