+static FAST_FUNC void bc_result_free(void *result)
+{
+ BcResult *r = (BcResult *) result;
+
+ switch (r->t) {
+ case XC_RESULT_TEMP:
+ IF_BC(case BC_RESULT_VOID:)
+ case XC_RESULT_IBASE:
+ case XC_RESULT_SCALE:
+ case XC_RESULT_OBASE:
+ bc_num_free(&r->d.n);
+ break;
+ case XC_RESULT_VAR:
+ case XC_RESULT_ARRAY:
+ case XC_RESULT_ARRAY_ELEM:
+ free(r->d.id.name);
+ break;
+ default:
+ // Do nothing.
+ break;
+ }
+}
+
+static int bad_input_byte(char c)
+{
+ if ((c < ' ' && c != '\t' && c != '\r' && c != '\n') // also allow '\v' '\f'?
+ || c > 0x7e
+ ) {
+ bc_error_fmt("illegal character 0x%02x", c);
+ return 1;
+ }
+ return 0;
+}
+
+static void xc_read_line(BcVec *vec, FILE *fp)
+{
+ again:
+ bc_vec_pop_all(vec);
+ fflush_and_check();
+
+#if ENABLE_FEATURE_BC_INTERACTIVE
+ if (G_interrupt) { // ^C was pressed
+ intr:
+ if (fp != stdin) {
+ // ^C while running a script (bc SCRIPT): die.
+ // We do not return to interactive prompt:
+ // user might be running us from a shell,
+ // and SCRIPT might be intended to terminate
+ // (e.g. contain a "halt" stmt).
+ // ^C dropping user into a bc prompt instead of
+ // the shell would be unexpected.
+ xfunc_die();
+ }
+ // ^C while interactive input
+ G_interrupt = 0;
+ // GNU bc says "interrupted execution."
+ // GNU dc says "Interrupt!"
+ fputs("\ninterrupted execution\n", stderr);
+ }
+
+# if ENABLE_FEATURE_EDITING
+ if (G_ttyin && fp == stdin) {
+ int n, i;
+# define line_buf bb_common_bufsiz1
+ n = read_line_input(G.line_input_state, "", line_buf, COMMON_BUFSIZE);
+ if (n <= 0) { // read errors or EOF, or ^D, or ^C
+ if (n == 0) // ^C
+ goto intr;
+ bc_vec_pushZeroByte(vec); // ^D or EOF (or error)
+ return;
+ }
+ i = 0;
+ for (;;) {
+ char c = line_buf[i++];
+ if (c == '\0') break;
+ if (bad_input_byte(c)) goto again;
+ }
+ bc_vec_string(vec, n, line_buf);
+# undef line_buf
+ } else
+# endif
+#endif
+ {
+ int c;
+ bool bad_chars = 0;
+
+ do {
+ get_char:
+#if ENABLE_FEATURE_BC_INTERACTIVE
+ if (G_interrupt) {
+ // ^C was pressed: ignore entire line, get another one
+ goto again;
+ }
+#endif
+ c = fgetc(fp);
+ if (c == '\0')
+ goto get_char;
+ if (c == EOF) {
+ if (ferror(fp))
+ bb_simple_perror_msg_and_die("input error");
+ // Note: EOF does not append '\n'
+ break;
+ }
+ bad_chars |= bad_input_byte(c);
+ bc_vec_pushByte(vec, (char)c);
+ } while (c != '\n');
+
+ if (bad_chars) {
+ // Bad chars on this line
+ if (!G.prs.lex_filename) { // stdin
+ // ignore entire line, get another one
+ goto again;
+ }
+ bb_perror_msg_and_die("file '%s' is not text", G.prs.lex_filename);
+ }
+ bc_vec_pushZeroByte(vec);
+ }
+}
+
+//
+// Parsing routines
+//
+
+// "Input numbers may contain the characters 0-9 and A-Z.
+// (Note: They must be capitals. Lower case letters are variable names.)
+// Single digit numbers always have the value of the digit regardless of
+// the value of ibase. (i.e. A = 10.) For multi-digit numbers, bc changes
+// all input digits greater or equal to ibase to the value of ibase-1.
+// This makes the number ZZZ always be the largest 3 digit number of the
+// input base."
+static bool xc_num_strValid(const char *val)
+{
+ bool radix = false;
+ for (;;) {
+ BcDig c = *val++;
+ if (c == '\0')
+ break;
+ if (c == '.') {
+ if (radix) return false;
+ radix = true;
+ continue;
+ }
+ if ((c < '0' || c > '9') && (c < 'A' || c > 'Z'))
+ return false;
+ }
+ return true;
+}
+
+// Note: n is already "bc_num_zero()"ed,
+// leading zeroes in "val" are removed
+static void bc_num_parseDecimal(BcNum *n, const char *val)
+{
+ size_t len, i;
+ const char *ptr;
+
+ len = strlen(val);
+ if (len == 0)
+ return;
+
+ bc_num_expand(n, len + 1); // +1 for e.g. "A" converting into 10
+
+ ptr = strchr(val, '.');
+
+ n->rdx = 0;
+ if (ptr != NULL)
+ n->rdx = (size_t)((val + len) - (ptr + 1));
+
+ for (i = 0; val[i]; ++i) {
+ if (val[i] != '0' && val[i] != '.') {
+ // Not entirely zero value - convert it, and exit
+ if (len == 1) {
+ unsigned c = val[0] - '0';
+ n->len = 1;
+ if (c > 9) { // A-Z => 10-36
+ n->len = 2;
+ c -= ('A' - '9' - 1);
+ n->num[1] = c/10;
+ c = c%10;
+ }
+ n->num[0] = c;
+ break;
+ }
+ i = len - 1;
+ for (;;) {
+ char c = val[i] - '0';
+ if (c > 9) // A-Z => 9
+ c = 9;
+ n->num[n->len] = c;
+ n->len++;
+ skip_dot:
+ if (i == 0) break;
+ if (val[--i] == '.') goto skip_dot;
+ }
+ break;
+ }
+ }
+ // if for() exits without hitting if(), the value is entirely zero
+}
+
+// Note: n is already "bc_num_zero()"ed,
+// leading zeroes in "val" are removed
+static void bc_num_parseBase(BcNum *n, const char *val, unsigned base_t)
+{
+ BcStatus s;
+ BcNum mult, result;
+ BcNum temp;
+ BcNum base;
+ BcDig temp_digs[ULONG_NUM_BUFSIZE];
+ BcDig base_digs[ULONG_NUM_BUFSIZE];
+ size_t digits;
+
+ bc_num_init_DEF_SIZE(&mult);
+
+ temp.cap = ARRAY_SIZE(temp_digs);
+ temp.num = temp_digs;
+
+ base.cap = ARRAY_SIZE(base_digs);
+ base.num = base_digs;
+ bc_num_ulong2num(&base, base_t);
+ base_t--;
+
+ for (;;) {
+ unsigned v;
+ char c;
+
+ c = *val++;
+ if (c == '\0') goto int_err;
+ if (c == '.') break;
+
+ v = (unsigned)(c <= '9' ? c - '0' : c - 'A' + 10);
+ if (v > base_t) v = base_t;
+
+ s = zbc_num_mul(n, &base, &mult, 0);
+ if (s) goto int_err;
+ bc_num_ulong2num(&temp, v);
+ s = zbc_num_add(&mult, &temp, n, 0);
+ if (s) goto int_err;
+ }
+
+ bc_num_init(&result, base.len);
+ //bc_num_zero(&result); - already is
+ bc_num_one(&mult);
+
+ digits = 0;
+ for (;;) {
+ unsigned v;
+ char c;
+
+ c = *val++;
+ if (c == '\0') break;
+ digits++;
+
+ v = (unsigned)(c <= '9' ? c - '0' : c - 'A' + 10);
+ if (v > base_t) v = base_t;
+
+ s = zbc_num_mul(&result, &base, &result, 0);
+ if (s) goto err;
+ bc_num_ulong2num(&temp, v);
+ s = zbc_num_add(&result, &temp, &result, 0);
+ if (s) goto err;
+ s = zbc_num_mul(&mult, &base, &mult, 0);
+ if (s) goto err;
+ }
+
+ s = zbc_num_div(&result, &mult, &result, digits);
+ if (s) goto err;
+ s = zbc_num_add(n, &result, n, digits);
+ if (s) goto err;
+
+ if (n->len != 0) {
+ if (n->rdx < digits)
+ bc_num_extend(n, digits - n->rdx);
+ } else
+ bc_num_zero(n);
+ err:
+ bc_num_free(&result);
+ int_err:
+ bc_num_free(&mult);
+}
+
+static BC_STATUS zxc_num_parse(BcNum *n, const char *val, unsigned base_t)
+{
+ size_t i;
+
+ if (!xc_num_strValid(val))
+ RETURN_STATUS(bc_error("bad number string"));
+
+ bc_num_zero(n);
+ while (*val == '0')
+ val++;
+ for (i = 0; ; ++i) {
+ if (val[i] == '\0')
+ RETURN_STATUS(BC_STATUS_SUCCESS);
+ if (val[i] != '.' && val[i] != '0')
+ break;
+ }
+
+ if (base_t == 10 || val[1] == '\0')
+ // Decimal, or single-digit number
+ bc_num_parseDecimal(n, val);
+ else
+ bc_num_parseBase(n, val, base_t);
+
+ RETURN_STATUS(BC_STATUS_SUCCESS);
+}
+#define zxc_num_parse(...) (zxc_num_parse(__VA_ARGS__) COMMA_SUCCESS)