bc: fix comparison bug, closes 12336
[oweals/busybox.git] / miscutils / bc.c
index c10cd73fa111c88660b0c66997c1c42d8b87cf34..c7246ea1a1610195e78de07d9e09b4e38535fcdd 100644 (file)
@@ -4,6 +4,15 @@
  * Adapted from https://github.com/gavinhoward/bc
  * Original code copyright (c) 2018 Gavin D. Howard and contributors.
  */
+//TODO:
+// maybe implement a^b for non-integer b?
+
+#define DEBUG_LEXER   0
+#define DEBUG_COMPILE 0
+#define DEBUG_EXEC    0
+// This can be left enabled for production as well:
+#define SANITY_CHECKS 1
+
 //config:config BC
 //config:      bool "bc (45 kb)"
 //config:      default y
 # include "dc.c"
 #else
 
-#define DEBUG_LEXER   0
-#define DEBUG_COMPILE 0
-#define DEBUG_EXEC    0
-// This can be left enabled for production as well:
-#define SANITY_CHECKS 1
-
 #if DEBUG_LEXER
 static uint8_t lex_indent;
 #define dbg_lex(...) \
@@ -200,11 +203,10 @@ static uint8_t lex_indent;
 typedef enum BcStatus {
        BC_STATUS_SUCCESS = 0,
        BC_STATUS_FAILURE = 1,
-       BC_STATUS_PARSE_EMPTY_EXP = 2, // bc_parse_expr_empty_ok() uses this
 } BcStatus;
 
-#define BC_VEC_INVALID_IDX ((size_t) -1)
-#define BC_VEC_START_CAP (1 << 5)
+#define BC_VEC_INVALID_IDX  ((size_t) -1)
+#define BC_VEC_START_CAP    (1 << 5)
 
 typedef void (*BcVecFree)(void *) FAST_FUNC;
 
@@ -226,12 +228,12 @@ typedef struct BcNum {
        bool neg;
 } BcNum;
 
-#define BC_NUM_MAX_IBASE        ((unsigned long) 16)
+#define BC_NUM_MAX_IBASE        36
 // larger value might speed up BIGNUM calculations a bit:
-#define BC_NUM_DEF_SIZE         (16)
-#define BC_NUM_PRINT_WIDTH      (69)
+#define BC_NUM_DEF_SIZE         16
+#define BC_NUM_PRINT_WIDTH      69
 
-#define BC_NUM_KARATSUBA_LEN    (32)
+#define BC_NUM_KARATSUBA_LEN    32
 
 typedef enum BcInst {
 #if ENABLE_BC
@@ -340,10 +342,12 @@ typedef struct BcFunc {
        IF_BC(BcVec strs;)
        IF_BC(BcVec consts;)
        IF_BC(size_t nparams;)
+       IF_BC(bool voidfunc;)
 } BcFunc;
 
 typedef enum BcResultType {
        XC_RESULT_TEMP,
+       IF_BC(BC_RESULT_VOID,) // same as TEMP, but INST_PRINT will ignore it
 
        XC_RESULT_VAR,
        XC_RESULT_ARRAY_ELEM,
@@ -374,9 +378,14 @@ typedef struct BcResult {
 typedef struct BcInstPtr {
        size_t func;
        size_t inst_idx;
-       IF_BC(size_t results_len_before_call;)
 } BcInstPtr;
 
+typedef enum BcType {
+       BC_TYPE_VAR,
+       BC_TYPE_ARRAY,
+       BC_TYPE_REF,
+} BcType;
+
 typedef enum BcLexType {
        XC_LEX_EOF,
        XC_LEX_INVALID,
@@ -441,10 +450,10 @@ typedef enum BcLexType {
        BC_LEX_KEY_FOR,
        BC_LEX_KEY_HALT,
        // code uses "type - BC_LEX_KEY_IBASE + XC_INST_IBASE" construct,
-       BC_LEX_KEY_IBASE,       // relative order should match for: XC_INST_IBASE
-       BC_LEX_KEY_OBASE,       // relative order should match for: XC_INST_OBASE
+       BC_LEX_KEY_IBASE,    // relative order should match for: XC_INST_IBASE
+       BC_LEX_KEY_OBASE,    // relative order should match for: XC_INST_OBASE
        BC_LEX_KEY_IF,
-       IF_BC(BC_LEX_KEY_LAST,) // relative order should match for: BC_INST_LAST
+       BC_LEX_KEY_LAST,     // relative order should match for: BC_INST_LAST
        BC_LEX_KEY_LENGTH,
        BC_LEX_KEY_LIMITS,
        BC_LEX_KEY_PRINT,
@@ -598,7 +607,7 @@ static ALWAYS_INLINE long lex_allowed_in_bc_expr(unsigned i)
 
 // This is an array of data for operators that correspond to
 // [XC_LEX_1st_op...] token types.
-static const uint8_t bc_parse_ops[] ALIGN1 = {
+static const uint8_t bc_ops_prec_and_assoc[] ALIGN1 = {
 #define OP(p,l) ((int)(l) * 0x10 + (p))
        OP(1, false), // neg
        OP(6, true ), OP( 6, true  ), OP( 6, true  ), OP( 6, true  ), OP( 6, true  ), OP( 6, true ), // == <= >= != < >
@@ -612,8 +621,8 @@ static const uint8_t bc_parse_ops[] ALIGN1 = {
        OP(0, false), OP( 0, false ), // inc dec
 #undef OP
 };
-#define bc_parse_op_PREC(i) (bc_parse_ops[i] & 0x0f)
-#define bc_parse_op_LEFT(i) (bc_parse_ops[i] & 0x10)
+#define bc_operation_PREC(i) (bc_ops_prec_and_assoc[i] & 0x0f)
+#define bc_operation_LEFT(i) (bc_ops_prec_and_assoc[i] & 0x10)
 #endif // ENABLE_BC
 
 #if ENABLE_DC
@@ -639,11 +648,13 @@ dc_char_to_LEX[] ALIGN1 = {
        // IJKLMNOP
        DC_LEX_IBASE, XC_LEX_INVALID, DC_LEX_SCALE, DC_LEX_LOAD_POP,
        XC_LEX_INVALID, DC_LEX_OP_BOOL_NOT, DC_LEX_OBASE, DC_LEX_PRINT_STREAM,
-       // QRSTUVWXY
-       DC_LEX_NQUIT, DC_LEX_POP, DC_LEX_STORE_PUSH, XC_LEX_INVALID, XC_LEX_INVALID,
-       XC_LEX_INVALID, XC_LEX_INVALID, DC_LEX_SCALE_FACTOR, XC_LEX_INVALID,
-       // Z[\]
-       DC_LEX_LENGTH, XC_LEX_INVALID, XC_LEX_INVALID, XC_LEX_INVALID,
+       // QRSTUVWX
+       DC_LEX_NQUIT, DC_LEX_POP, DC_LEX_STORE_PUSH, XC_LEX_INVALID,
+       XC_LEX_INVALID, XC_LEX_INVALID, XC_LEX_INVALID, DC_LEX_SCALE_FACTOR,
+       // YZ
+       XC_LEX_INVALID, DC_LEX_LENGTH,
+       // [\]
+       XC_LEX_INVALID, XC_LEX_INVALID, XC_LEX_INVALID,
        // ^_`
        XC_LEX_OP_POWER, XC_LEX_NEG, XC_LEX_INVALID,
        // abcdefgh
@@ -749,7 +760,7 @@ struct globals {
 
        // For error messages. Can be set to current parsed line,
        // or [TODO] to current executing line (can be before last parsed one)
-       unsigned err_line;
+       size_t err_line;
 
        BcVec input_buffer;
 
@@ -798,16 +809,12 @@ struct globals {
 # define BC_PARSE_NOCALL        (1 << 3)
 #endif
 
-#define BC_PROG_MAIN (0)
-#define BC_PROG_READ (1)
+#define BC_PROG_MAIN      0
+#define BC_PROG_READ      1
 #if ENABLE_DC
-#define BC_PROG_REQ_FUNCS (2)
+#define BC_PROG_REQ_FUNCS 2
 #endif
 
-#define BC_PROG_STR(n) (!(n)->num && !(n)->cap)
-#define BC_PROG_NUM(r, n) \
-       ((r)->t != XC_RESULT_ARRAY && (r)->t != XC_RESULT_STR && !BC_PROG_STR(n))
-
 #define BC_FLAG_W (1 << 0)
 #define BC_FLAG_V (1 << 1)
 #define BC_FLAG_S (1 << 2)
@@ -816,19 +823,14 @@ struct globals {
 #define BC_FLAG_I ((1 << 5) * ENABLE_DC)
 #define DC_FLAG_X ((1 << 6) * ENABLE_DC)
 
-#define BC_MAX(a, b) ((a) > (b) ? (a) : (b))
-#define BC_MIN(a, b) ((a) < (b) ? (a) : (b))
-
 #define BC_MAX_OBASE    ((unsigned) 999)
 #define BC_MAX_DIM      ((unsigned) INT_MAX)
 #define BC_MAX_SCALE    ((unsigned) UINT_MAX)
 #define BC_MAX_STRING   ((unsigned) UINT_MAX - 1)
 #define BC_MAX_NUM      BC_MAX_STRING
 // Unused apart from "limits" message. Just show a "biggish number" there.
-//#define BC_MAX_NAME     BC_MAX_STRING
 //#define BC_MAX_EXP      ((unsigned long) LONG_MAX)
 //#define BC_MAX_VARS     ((unsigned long) SIZE_MAX - 1)
-#define BC_MAX_NAME_STR "999999999"
 #define BC_MAX_EXP_STR  "999999999"
 #define BC_MAX_VARS_STR "999999999"
 
@@ -842,10 +844,10 @@ struct globals {
 # error Strange INT_MAX
 #endif
 
-#if UINT_MAX == 4294967295
+#if UINT_MAX == 4294967295U
 # define BC_MAX_SCALE_STR  "4294967295"
 # define BC_MAX_STRING_STR "4294967294"
-#elif UINT_MAX == 18446744073709551615
+#elif UINT_MAX == 18446744073709551615U
 # define BC_MAX_SCALE_STR  "18446744073709551615"
 # define BC_MAX_STRING_STR "18446744073709551614"
 #else
@@ -884,11 +886,14 @@ struct globals {
 // Utility routines
 //
 
+#define BC_MAX(a, b) ((a) > (b) ? (a) : (b))
+#define BC_MIN(a, b) ((a) < (b) ? (a) : (b))
+
 static void fflush_and_check(void)
 {
        fflush_all();
        if (ferror(stdout) || ferror(stderr))
-               bb_perror_msg_and_die("output error");
+               bb_simple_perror_msg_and_die("output error");
 }
 
 #if ENABLE_FEATURE_CLEAN_UP
@@ -903,7 +908,7 @@ static void quit(void) NORETURN;
 static void quit(void)
 {
        if (ferror(stdin))
-               bb_perror_msg_and_die("input error");
+               bb_simple_perror_msg_and_die("input error");
        fflush_and_check();
        dbg_exec("quit(): exiting with exitcode SUCCESS");
        exit(0);
@@ -916,7 +921,9 @@ static void bc_verror_msg(const char *fmt, va_list p)
        const char *sv = sv; // for compiler
        if (G.prs.lex_filename) {
                sv = applet_name;
-               applet_name = xasprintf("%s: %s:%u", applet_name, G.prs.lex_filename, G.err_line);
+               applet_name = xasprintf("%s: %s:%lu", applet_name,
+                       G.prs.lex_filename, (unsigned long)G.err_line
+               );
        }
        bb_verror_msg(fmt, p, NULL);
        if (G.prs.lex_filename) {
@@ -971,19 +978,44 @@ static ERRORFUNC int bc_error(const char *msg)
 {
        IF_ERROR_RETURN_POSSIBLE(return) bc_error_fmt("%s", msg);
 }
+static ERRORFUNC int bc_error_at(const char *msg)
+{
+       const char *err_at = G.prs.lex_next_at;
+       if (err_at) {
+               IF_ERROR_RETURN_POSSIBLE(return) bc_error_fmt(
+                       "%s at '%.*s'",
+                       msg,
+                       (int)(strchrnul(err_at, '\n') - err_at),
+                       err_at
+               );
+       }
+       IF_ERROR_RETURN_POSSIBLE(return) bc_error_fmt("%s", msg);
+}
 static ERRORFUNC int bc_error_bad_character(char c)
 {
        if (!c)
                IF_ERROR_RETURN_POSSIBLE(return) bc_error("NUL character");
        IF_ERROR_RETURN_POSSIBLE(return) bc_error_fmt("bad character '%c'", c);
 }
+#if ENABLE_BC
+static ERRORFUNC int bc_error_bad_function_definition(void)
+{
+       IF_ERROR_RETURN_POSSIBLE(return) bc_error_at("bad function definition");
+}
+#endif
 static ERRORFUNC int bc_error_bad_expression(void)
 {
-       IF_ERROR_RETURN_POSSIBLE(return) bc_error("bad expression");
+       IF_ERROR_RETURN_POSSIBLE(return) bc_error_at("bad expression");
+}
+static ERRORFUNC int bc_error_bad_assignment(void)
+{
+       IF_ERROR_RETURN_POSSIBLE(return) bc_error_at(
+               "bad assignment: left side must be variable or array element"
+       );
 }
 static ERRORFUNC int bc_error_bad_token(void)
 {
-       IF_ERROR_RETURN_POSSIBLE(return) bc_error("bad token");
+       IF_ERROR_RETURN_POSSIBLE(return) bc_error_at("bad token");
 }
 static ERRORFUNC int bc_error_stack_has_too_few_elements(void)
 {
@@ -1068,15 +1100,25 @@ static void bc_vec_pop_all(BcVec *v)
        bc_vec_npop(v, v->len);
 }
 
-static size_t bc_vec_push(BcVec *v, const void *data)
+static size_t bc_vec_npush(BcVec *v, size_t n, const void *data)
 {
        size_t len = v->len;
-       if (len >= v->cap) bc_vec_grow(v, 1);
-       memmove(v->v + (v->size * len), data, v->size);
-       v->len++;
+       if (len + n > v->cap) bc_vec_grow(v, n);
+       memmove(v->v + (v->size * len), data, v->size * n);
+       v->len = len + n;
        return len;
 }
 
+static size_t bc_vec_push(BcVec *v, const void *data)
+{
+       return bc_vec_npush(v, 1, data);
+       //size_t len = v->len;
+       //if (len >= v->cap) bc_vec_grow(v, 1);
+       //memmove(v->v + (v->size * len), data, v->size);
+       //v->len = len + 1;
+       //return len;
+}
+
 // G.prog.results often needs "pop old operand, push result" idiom.
 // Can do this without a few extra ops
 static size_t bc_result_pop_and_push(const void *data)
@@ -1233,14 +1275,12 @@ static int bc_map_insert(BcVec *v, const void *ptr, size_t *i)
        return 1; // "was inserted"
 }
 
-#if ENABLE_BC
 static size_t bc_map_find_exact(const BcVec *v, const void *ptr)
 {
        size_t i = bc_map_find_ge(v, ptr);
        if (i >= v->len) return BC_VEC_INVALID_IDX;
        return bc_id_cmp(ptr, bc_vec_item(v, i)) ? BC_VEC_INVALID_IDX : i;
 }
-#endif
 
 static void bc_num_setToZero(BcNum *n, size_t scale)
 {
@@ -1425,7 +1465,10 @@ static ssize_t bc_num_cmp(BcNum *a, BcNum *b)
        b_int = BC_NUM_INT(b);
        a_int -= b_int;
 
-       if (a_int != 0) return (ssize_t) a_int;
+       if (a_int != 0) {
+               if (neg) return - (ssize_t) a_int;
+               return (ssize_t) a_int;
+       }
 
        a_max = (a->rdx > b->rdx);
        if (a_max) {
@@ -2157,6 +2200,7 @@ static BC_STATUS zbc_num_sqrt(BcNum *a, BcNum *restrict b, size_t scale)
 {
        BcStatus s;
        BcNum num1, num2, half, f, fprime, *x0, *x1, *temp;
+       BcDig half_digs[1];
        size_t pow, len, digs, digs1, resrdx, req, times = 0;
        ssize_t cmp = 1, cmp1 = SSIZE_MAX, cmp2 = SSIZE_MAX;
 
@@ -2181,10 +2225,11 @@ static BC_STATUS zbc_num_sqrt(BcNum *a, BcNum *restrict b, size_t scale)
 
        bc_num_init(&num1, len);
        bc_num_init(&num2, len);
-       bc_num_init_DEF_SIZE(&half);
 
+       half.cap = ARRAY_SIZE(half_digs);
+       half.num = half_digs;
        bc_num_one(&half);
-       half.num[0] = 5;
+       half_digs[0] = 5;
        half.rdx = 1;
 
        bc_num_init(&f, len);
@@ -2247,7 +2292,6 @@ static BC_STATUS zbc_num_sqrt(BcNum *a, BcNum *restrict b, size_t scale)
  err:
        bc_num_free(&fprime);
        bc_num_free(&f);
-       bc_num_free(&half);
        bc_num_free(&num2);
        bc_num_free(&num1);
        RETURN_STATUS(s);
@@ -2285,7 +2329,7 @@ static BC_STATUS zdc_num_modexp(BcNum *a, BcNum *b, BcNum *c, BcNum *restrict d)
 {
        BcStatus s;
        BcNum base, exp, two, temp;
-       BcDig two_digs[2];
+       BcDig two_digs[1];
 
        if (c->len == 0)
                RETURN_STATUS(bc_error("divide by zero"));
@@ -2426,11 +2470,12 @@ static void dc_result_copy(BcResult *d, BcResult *src)
                        d->d.id.name = xstrdup(src->d.id.name);
                        break;
                case XC_RESULT_CONSTANT:
-               IF_BC(case BC_RESULT_LAST:)
-               IF_BC(case BC_RESULT_ONE:)
                case XC_RESULT_STR:
                        memcpy(&d->d.n, &src->d.n, sizeof(BcNum));
                        break;
+               default: // placate compiler
+                       // BC_RESULT_VOID, BC_RESULT_LAST, BC_RESULT_ONE - do not happen
+                       break;
        }
 }
 #endif // ENABLE_DC
@@ -2441,6 +2486,7 @@ static FAST_FUNC void bc_result_free(void *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:
@@ -2508,7 +2554,7 @@ static void xc_read_line(BcVec *vec, FILE *fp)
                i = 0;
                for (;;) {
                        char c = line_buf[i++];
-                       if (!c) break;
+                       if (c == '\0') break;
                        if (bad_input_byte(c)) goto again;
                }
                bc_vec_string(vec, n, line_buf);
@@ -2521,17 +2567,19 @@ static void xc_read_line(BcVec *vec, FILE *fp)
                bool bad_chars = 0;
 
                do {
+ get_char:
 #if ENABLE_FEATURE_BC_INTERACTIVE
                        if (G_interrupt) {
                                // ^C was pressed: ignore entire line, get another one
-                               bc_vec_pop_all(vec);
-                               goto intr;
+                               goto again;
                        }
 #endif
-                       do c = fgetc(fp); while (c == '\0');
+                       c = fgetc(fp);
+                       if (c == '\0')
+                               goto get_char;
                        if (c == EOF) {
                                if (ferror(fp))
-                                       bb_perror_msg_and_die("input error");
+                                       bb_simple_perror_msg_and_die("input error");
                                // Note: EOF does not append '\n'
                                break;
                        }
@@ -2555,13 +2603,16 @@ static void xc_read_line(BcVec *vec, FILE *fp)
 // Parsing routines
 //
 
-static bool xc_num_strValid(const char *val, size_t base)
+// "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)
 {
-       BcDig b;
-       bool radix;
-
-       b = (BcDig)(base <= 10 ? base + '0' : base - 10 + 'A');
-       radix = false;
+       bool radix = false;
        for (;;) {
                BcDig c = *val++;
                if (c == '\0')
@@ -2571,7 +2622,7 @@ static bool xc_num_strValid(const char *val, size_t base)
                        radix = true;
                        continue;
                }
-               if (c < '0' || c >= b || (c > '9' && c < 'A'))
+               if ((c < '0' || c > '9') && (c < 'A' || c > 'Z'))
                        return false;
        }
        return true;
@@ -2588,7 +2639,7 @@ static void bc_num_parseDecimal(BcNum *n, const char *val)
        if (len == 0)
                return;
 
-       bc_num_expand(n, len);
+       bc_num_expand(n, len + 1); // +1 for e.g. "A" converting into 10
 
        ptr = strchr(val, '.');
 
@@ -2599,10 +2650,25 @@ static void bc_num_parseDecimal(BcNum *n, const char *val)
        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 (;;) {
-                               n->num[n->len] = val[i] - '0';
-                               ++n->len;
+                               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;
@@ -2618,32 +2684,33 @@ static void bc_num_parseDecimal(BcNum *n, const char *val)
 static void bc_num_parseBase(BcNum *n, const char *val, unsigned base_t)
 {
        BcStatus s;
-       BcNum temp, mult, result;
+       BcNum mult, result;
+       BcNum temp;
        BcNum base;
+       BcDig temp_digs[ULONG_NUM_BUFSIZE];
        BcDig base_digs[ULONG_NUM_BUFSIZE];
-       BcDig c = '\0';
-       unsigned long v;
-       size_t i, digits;
+       size_t digits;
 
-       for (i = 0; ; ++i) {
-               if (val[i] == '\0')
-                       return;
-               if (val[i] != '.' && val[i] != '0')
-                       break;
-       }
-
-       bc_num_init_DEF_SIZE(&temp);
        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 long) (c <= '9' ? c - '0' : c - 'A' + 10);
+               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;
@@ -2658,11 +2725,15 @@ static void bc_num_parseBase(BcNum *n, const char *val, unsigned base_t)
 
        digits = 0;
        for (;;) {
+               unsigned v;
+               char c;
+
                c = *val++;
                if (c == '\0') break;
                digits++;
 
-               v = (unsigned long) (c <= '9' ? c - '0' : c - 'A' + 10);
+               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;
@@ -2687,18 +2758,27 @@ static void bc_num_parseBase(BcNum *n, const char *val, unsigned base_t)
        bc_num_free(&result);
  int_err:
        bc_num_free(&mult);
-       bc_num_free(&temp);
 }
 
 static BC_STATUS zxc_num_parse(BcNum *n, const char *val, unsigned base_t)
 {
-       if (!xc_num_strValid(val, 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++;
+       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)
+       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);
@@ -2707,20 +2787,6 @@ static BC_STATUS zxc_num_parse(BcNum *n, const char *val, unsigned base_t)
 }
 #define zxc_num_parse(...) (zxc_num_parse(__VA_ARGS__) COMMA_SUCCESS)
 
-static bool xc_lex_more_input(void)
-{
-       BcParse *p = &G.prs;
-
-       bc_vec_pop_all(&G.input_buffer);
-
-       xc_read_line(&G.input_buffer, G.prs.lex_input_fp);
-
-       p->lex_inbuf = G.input_buffer.v;
-//     bb_error_msg("G.input_buffer.len:%d '%s'", G.input_buffer.len, G.input_buffer.v);
-
-       return G.input_buffer.len > 1;
-}
-
 // p->lex_inbuf points to the current string to be parsed.
 // if p->lex_inbuf points to '\0', it's either EOF or it points after
 // last processed line's terminating '\n' (and more reading needs to be done
@@ -2754,10 +2820,13 @@ static bool xc_lex_more_input(void)
 // end"         - ...prints "str#\<newline>end"
 static char peek_inbuf(void)
 {
-       if (*G.prs.lex_inbuf == '\0') {
-               if (G.prs.lex_input_fp)
-                       if (!xc_lex_more_input())
-                               G.prs.lex_input_fp = NULL;
+       if (*G.prs.lex_inbuf == '\0'
+        && G.prs.lex_input_fp
+       ) {
+               xc_read_line(&G.input_buffer, G.prs.lex_input_fp);
+               G.prs.lex_inbuf = G.input_buffer.v;
+               if (G.input_buffer.len <= 1) // on EOF, len is 1 (NUL byte)
+                       G.prs.lex_input_fp = NULL;
        }
        return *G.prs.lex_inbuf;
 }
@@ -2776,7 +2845,7 @@ static void xc_lex_lineComment(void)
        // Try: echo -n '#foo' | bc
        p->lex = XC_LEX_WHITESPACE;
 
-       // We depend here on input being done in whole lines:
+       // Not peek_inbuf(): we depend on input being done in whole lines:
        // '\0' which isn't the EOF can only be seen after '\n'.
        while ((c = *p->lex_inbuf) != '\n' && c != '\0')
                p->lex_inbuf++;
@@ -2803,10 +2872,20 @@ static BC_STATUS zxc_lex_number(char last)
 {
        BcParse *p = &G.prs;
        bool pt;
+       char last_valid_ch;
 
        bc_vec_pop_all(&p->lex_strnumbuf);
        bc_vec_pushByte(&p->lex_strnumbuf, last);
 
+// bc: "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."
+// dc only allows A-F, the rules about single-char and multi-char are the same.
+       last_valid_ch = (IS_BC ? 'Z' : 'F');
        pt = (last == '.');
        p->lex = XC_LEX_NUMBER;
        for (;;) {
@@ -2819,16 +2898,17 @@ static BC_STATUS zxc_lex_number(char last)
                if (c == '\\' && p->lex_inbuf[1] == '\n') {
                        p->lex_inbuf += 2;
                        p->lex_line++;
+                       dbg_lex("++p->lex_line=%zd", p->lex_line);
                        c = peek_inbuf(); // force next line to be read
                        goto check_c;
                }
-               if (!isdigit(c) && (c < 'A' || c > 'F')) {
+               if (!isdigit(c) && (c < 'A' || c > last_valid_ch)) {
                        if (c != '.') break;
                        // if '.' was already seen, stop on second one:
                        if (pt) break;
                        pt = true;
                }
-               // c is one of "0-9A-F."
+               // c is one of "0-9A-Z."
                last = c;
                bc_vec_push(&p->lex_strnumbuf, p->lex_inbuf);
                p->lex_inbuf++;
@@ -2885,6 +2965,7 @@ static BC_STATUS zxc_lex_next(void)
        BcParse *p = &G.prs;
        BcStatus s;
 
+       G.err_line = p->lex_line;
        p->lex_last = p->lex;
 //why?
 //     if (p->lex_last == XC_LEX_EOF)
@@ -2997,8 +3078,10 @@ static BC_STATUS zbc_lex_string(void)
                }
                if (c == '"')
                        break;
-               if (c == '\n')
+               if (c == '\n') {
                        p->lex_line++;
+                       dbg_lex("++p->lex_line=%zd", p->lex_line);
+               }
                bc_vec_push(&p->lex_strnumbuf, p->lex_inbuf);
                p->lex_inbuf++;
        }
@@ -3045,8 +3128,10 @@ static BC_STATUS zbc_lex_comment(void)
                if (c == '\0') {
                        RETURN_STATUS(bc_error("unterminated comment"));
                }
-               if (c == '\n')
+               if (c == '\n') {
                        p->lex_line++;
+                       dbg_lex("++p->lex_line=%zd", p->lex_line);
+               }
        }
        p->lex_inbuf++; // skip trailing '/'
 
@@ -3071,6 +3156,7 @@ static BC_STATUS zbc_lex_token(void)
 //             break;
        case '\n':
                p->lex_line++;
+               dbg_lex("++p->lex_line=%zd", p->lex_line);
                p->lex = XC_LEX_NLINE;
                break;
        case '\t':
@@ -3167,6 +3253,26 @@ static BC_STATUS zbc_lex_token(void)
        case 'D':
        case 'E':
        case 'F':
+       case 'G':
+       case 'H':
+       case 'I':
+       case 'J':
+       case 'K':
+       case 'L':
+       case 'M':
+       case 'N':
+       case 'O':
+       case 'P':
+       case 'Q':
+       case 'R':
+       case 'S':
+       case 'T':
+       case 'U':
+       case 'V':
+       case 'W':
+       case 'X':
+       case 'Y':
+       case 'Z':
                s = zxc_lex_number(c);
                break;
        case ';':
@@ -3287,8 +3393,10 @@ static BC_STATUS zdc_lex_string(void)
                if (c == ']')
                        if (--depth == 0)
                                break;
-               if (c == '\n')
+               if (c == '\n') {
                        p->lex_line++;
+                       dbg_lex("++p->lex_line=%zd", p->lex_line);
+               }
                bc_vec_push(&p->lex_strnumbuf, p->lex_inbuf);
                p->lex_inbuf++;
        }
@@ -3345,6 +3453,7 @@ static BC_STATUS zdc_lex_token(void)
                // IOW: typing "1p<enter>" should print "1" _at once_,
                // not after some more input.
                p->lex_line++;
+               dbg_lex("++p->lex_line=%zd", p->lex_line);
                p->lex = XC_LEX_NLINE;
                break;
        case '\t':
@@ -3407,11 +3516,11 @@ static BC_STATUS zdc_lex_token(void)
 #define zdc_lex_token(...) (zdc_lex_token(__VA_ARGS__) COMMA_SUCCESS)
 #endif // ENABLE_DC
 
-static void xc_parse_push(char i)
+static void xc_parse_push(unsigned i)
 {
        BcVec *code = &G.prs.func->code;
        dbg_compile("%s:%d pushing bytecode %zd:%d", __func__, __LINE__, code->len, i);
-       bc_vec_pushByte(code, i);
+       bc_vec_pushByte(code, (uint8_t)i);
 }
 
 static void xc_parse_pushName(char *name)
@@ -3438,44 +3547,54 @@ static void xc_parse_pushName(char *name)
 // (The above describes 32-bit case).
 #define SMALL_INDEX_LIMIT (0x100 - sizeof(size_t))
 
-static void xc_parse_pushIndex(size_t idx)
+static void bc_vec_pushIndex(BcVec *v, size_t idx)
 {
        size_t mask;
        unsigned amt;
 
        dbg_lex("%s:%d pushing index %zd", __func__, __LINE__, idx);
        if (idx < SMALL_INDEX_LIMIT) {
-               goto push_idx;
+               bc_vec_pushByte(v, idx);
+               return;
        }
 
        mask = ((size_t)0xff) << (sizeof(idx) * 8 - 8);
        amt = sizeof(idx);
-       do {
+       for (;;) {
                if (idx & mask) break;
                mask >>= 8;
                amt--;
-       } while (amt != 0);
+       }
+       // amt is at least 1 here - "one byte of length data follows"
 
-       xc_parse_push(SMALL_INDEX_LIMIT + amt);
+       bc_vec_pushByte(v, (SMALL_INDEX_LIMIT - 1) + amt);
 
-       while (idx != 0) {
- push_idx:
-               xc_parse_push((unsigned char)idx);
+       do {
+               bc_vec_pushByte(v, (unsigned char)idx);
                idx >>= 8;
-       }
+       } while (idx != 0);
+}
+
+static void xc_parse_pushIndex(size_t idx)
+{
+       bc_vec_pushIndex(&G.prs.func->code, idx);
+}
+
+static void xc_parse_pushInst_and_Index(unsigned inst, size_t idx)
+{
+       xc_parse_push(inst);
+       xc_parse_pushIndex(idx);
 }
 
 #if ENABLE_BC
 static void bc_parse_pushJUMP(size_t idx)
 {
-       xc_parse_push(BC_INST_JUMP);
-       xc_parse_pushIndex(idx);
+       xc_parse_pushInst_and_Index(BC_INST_JUMP, idx);
 }
 
 static void bc_parse_pushJUMP_ZERO(size_t idx)
 {
-       xc_parse_push(BC_INST_JUMP_ZERO);
-       xc_parse_pushIndex(idx);
+       xc_parse_pushInst_and_Index(BC_INST_JUMP_ZERO, idx);
 }
 
 static BC_STATUS zbc_parse_pushSTR(void)
@@ -3483,8 +3602,7 @@ static BC_STATUS zbc_parse_pushSTR(void)
        BcParse *p = &G.prs;
        char *str = xstrdup(p->lex_strnumbuf.v);
 
-       xc_parse_push(XC_INST_STR);
-       xc_parse_pushIndex(p->func->strs.len);
+       xc_parse_pushInst_and_Index(XC_INST_STR, p->func->strs.len);
        bc_vec_push(&p->func->strs, &str);
 
        RETURN_STATUS(zxc_lex_next());
@@ -3503,8 +3621,7 @@ static void xc_parse_pushNUM(void)
 #else // DC
        size_t idx = bc_vec_push(&G.prog.consts, &num);
 #endif
-       xc_parse_push(XC_INST_NUM);
-       xc_parse_pushIndex(idx);
+       xc_parse_pushInst_and_Index(XC_INST_NUM, idx);
 }
 
 static BC_STATUS zxc_parse_text_init(const char *text)
@@ -3623,17 +3740,7 @@ static size_t bc_program_addFunc(char *name)
 // first in the expr enum. Note: This only works for binary operators.
 #define BC_TOKEN_2_INST(t) ((char) ((t) - XC_LEX_OP_POWER + XC_INST_POWER))
 
-static BcStatus bc_parse_expr_empty_ok(uint8_t flags);
-
-static BC_STATUS zbc_parse_expr(uint8_t flags)
-{
-       BcStatus s;
-
-       s = bc_parse_expr_empty_ok(flags);
-       if (s == BC_STATUS_PARSE_EMPTY_EXP)
-               RETURN_STATUS(bc_error("empty expression"));
-       RETURN_STATUS(s);
-}
+static BC_STATUS zbc_parse_expr(uint8_t flags);
 #define zbc_parse_expr(...) (zbc_parse_expr(__VA_ARGS__) COMMA_SUCCESS)
 
 static BC_STATUS zbc_parse_stmt_possibly_auto(bool auto_allowed);
@@ -3662,14 +3769,14 @@ static BC_STATUS zbc_parse_stmt_allow_NLINE_before(const char *after_X)
 static void bc_parse_operator(BcLexType type, size_t start, size_t *nexprs)
 {
        BcParse *p = &G.prs;
-       char l, r = bc_parse_op_PREC(type - XC_LEX_1st_op);
-       bool left = bc_parse_op_LEFT(type - XC_LEX_1st_op);
+       char l, r = bc_operation_PREC(type - XC_LEX_1st_op);
+       bool left = bc_operation_LEFT(type - XC_LEX_1st_op);
 
        while (p->ops.len > start) {
                BcLexType t = BC_PARSE_TOP_OP(p);
                if (t == BC_LEX_LPAREN) break;
 
-               l = bc_parse_op_PREC(t - XC_LEX_1st_op);
+               l = bc_operation_PREC(t - XC_LEX_1st_op);
                if (l >= r && (l != r || !left)) break;
 
                xc_parse_push(BC_TOKEN_2_INST(t));
@@ -3734,8 +3841,7 @@ static BC_STATUS zbc_parse_params(uint8_t flags)
                }
        }
 
-       xc_parse_push(BC_INST_CALL);
-       xc_parse_pushIndex(nparams);
+       xc_parse_pushInst_and_Index(BC_INST_CALL, nparams);
 
        RETURN_STATUS(BC_STATUS_SUCCESS);
 }
@@ -3905,8 +4011,7 @@ static BC_STATUS zbc_parse_scale(BcInst *type, uint8_t flags)
 }
 #define zbc_parse_scale(...) (zbc_parse_scale(__VA_ARGS__) COMMA_SUCCESS)
 
-static BC_STATUS zbc_parse_incdec(BcInst *prev, bool *paren_expr,
-                               size_t *nexprs, uint8_t flags)
+static BC_STATUS zbc_parse_incdec(BcInst *prev, size_t *nexs, uint8_t flags)
 {
        BcParse *p = &G.prs;
        BcStatus s;
@@ -3923,7 +4028,6 @@ static BC_STATUS zbc_parse_incdec(BcInst *prev, bool *paren_expr,
                s = zxc_lex_next();
        } else {
                *prev = inst = BC_INST_INC_PRE + (p->lex != BC_LEX_OP_INC);
-               *paren_expr = true;
 
                s = zxc_lex_next();
                if (s) RETURN_STATUS(s);
@@ -3931,7 +4035,7 @@ static BC_STATUS zbc_parse_incdec(BcInst *prev, bool *paren_expr,
 
                // Because we parse the next part of the expression
                // right here, we need to increment this.
-               *nexprs = *nexprs + 1;
+               *nexs = *nexs + 1;
 
                switch (type) {
                case XC_LEX_NAME:
@@ -3963,36 +4067,27 @@ static BC_STATUS zbc_parse_incdec(BcInst *prev, bool *paren_expr,
 }
 #define zbc_parse_incdec(...) (zbc_parse_incdec(__VA_ARGS__) COMMA_SUCCESS)
 
-#if 0
-#define BC_PARSE_LEAF(p, rparen) \
-       ((rparen) \
-        || ((p) >= XC_INST_NUM && (p) <= XC_INST_SQRT) \
-        || (p) == BC_INST_INC_POST \
-        || (p) == BC_INST_DEC_POST \
-       )
-#else
-static int ok_in_expr(BcInst p)
+static int bc_parse_inst_isLeaf(BcInst p)
 {
        return (p >= XC_INST_NUM && p <= XC_INST_SQRT)
                || p == BC_INST_INC_POST
                || p == BC_INST_DEC_POST
                ;
 }
-#define BC_PARSE_LEAF(p, rparen) ((rparen) || ok_in_expr(p))
-#endif
+#define BC_PARSE_LEAF(prev, bin_last, rparen) \
+       (!(bin_last) && ((rparen) || bc_parse_inst_isLeaf(prev)))
 
 static BC_STATUS zbc_parse_minus(BcInst *prev, size_t ops_bgn,
-                               bool rparen, size_t *nexprs)
+                               bool rparen, bool bin_last, size_t *nexprs)
 {
        BcParse *p = &G.prs;
        BcStatus s;
        BcLexType type;
-       BcInst etype = *prev;
 
        s = zxc_lex_next();
        if (s) RETURN_STATUS(s);
 
-       type = BC_PARSE_LEAF(etype, rparen) ? XC_LEX_OP_MINUS : XC_LEX_NEG;
+       type = BC_PARSE_LEAF(*prev, bin_last, rparen) ? XC_LEX_OP_MINUS : XC_LEX_NEG;
        *prev = BC_TOKEN_2_INST(type);
 
        // We can just push onto the op stack because this is the largest
@@ -4042,18 +4137,16 @@ static BC_STATUS zbc_parse_return(void)
        if (s) RETURN_STATUS(s);
 
        t = p->lex;
-       if (t == XC_LEX_NLINE || t == BC_LEX_SCOLON)
+       if (t == XC_LEX_NLINE || t == BC_LEX_SCOLON || t == BC_LEX_RBRACE)
                xc_parse_push(BC_INST_RET0);
        else {
-               bool paren = (t == BC_LEX_LPAREN);
-               s = bc_parse_expr_empty_ok(0);
-               if (s == BC_STATUS_PARSE_EMPTY_EXP) {
-                       xc_parse_push(BC_INST_RET0);
-                       s = zxc_lex_next();
-               }
+//TODO: if (p->func->voidfunc) ERROR
+               s = zbc_parse_expr(0);
                if (s) RETURN_STATUS(s);
 
-               if (!paren || p->lex_last != BC_LEX_RPAREN) {
+               if (t != BC_LEX_LPAREN   // "return EXPR", no ()
+                || p->lex_last != BC_LEX_RPAREN  // example: "return (a) + b"
+               ) {
                        s = zbc_POSIX_requires("parentheses around return expressions");
                        if (s) RETURN_STATUS(s);
                }
@@ -4271,7 +4364,7 @@ static BC_STATUS zbc_parse_break_or_continue(BcLexType type)
 }
 #define zbc_parse_break_or_continue(...) (zbc_parse_break_or_continue(__VA_ARGS__) COMMA_SUCCESS)
 
-static BC_STATUS zbc_func_insert(BcFunc *f, char *name, bool var)
+static BC_STATUS zbc_func_insert(BcFunc *f, char *name, BcType type)
 {
        BcId *autoid;
        BcId a;
@@ -4279,11 +4372,14 @@ static BC_STATUS zbc_func_insert(BcFunc *f, char *name, bool var)
 
        autoid = (void*)f->autos.v;
        for (i = 0; i < f->autos.len; i++, autoid++) {
-               if (strcmp(name, autoid->name) == 0)
+               if (strcmp(name, autoid->name) == 0
+                && type == (BcType) autoid->idx
+               ) {
                        RETURN_STATUS(bc_error("duplicate function parameter or auto name"));
+               }
        }
 
-       a.idx = var;
+       a.idx = type;
        a.name = name;
 
        bc_vec_push(&f->autos, &a);
@@ -4296,29 +4392,56 @@ static BC_STATUS zbc_parse_funcdef(void)
 {
        BcParse *p = &G.prs;
        BcStatus s;
-       bool var, comma = false;
+       bool comma, voidfunc;
        char *name;
 
        dbg_lex_enter("%s:%d entered", __func__, __LINE__);
        s = zxc_lex_next();
        if (s) RETURN_STATUS(s);
        if (p->lex != XC_LEX_NAME)
-               RETURN_STATUS(bc_error("bad function definition"));
+               RETURN_STATUS(bc_error_bad_function_definition());
 
-       name = xstrdup(p->lex_strnumbuf.v);
-       p->fidx = bc_program_addFunc(name);
-       p->func = xc_program_func(p->fidx);
+       // To be maximally both POSIX and GNU-compatible,
+       // "void" is not treated as a normal keyword:
+       // you can have variable named "void", and even a function
+       // named "void": "define void() { return 6; }" is ok.
+       // _Only_ "define void f() ..." syntax treats "void"
+       // specially.
+       voidfunc = (strcmp(p->lex_strnumbuf.v, "void") == 0);
 
        s = zxc_lex_next();
        if (s) RETURN_STATUS(s);
+
+       voidfunc = (voidfunc && p->lex == XC_LEX_NAME);
+       if (voidfunc) {
+               s = zxc_lex_next();
+               if (s) RETURN_STATUS(s);
+       }
+
        if (p->lex != BC_LEX_LPAREN)
-               RETURN_STATUS(bc_error("bad function definition"));
+               RETURN_STATUS(bc_error_bad_function_definition());
+
+       p->fidx = bc_program_addFunc(xstrdup(p->lex_strnumbuf.v));
+       p->func = xc_program_func(p->fidx);
+       p->func->voidfunc = voidfunc;
+
        s = zxc_lex_next();
        if (s) RETURN_STATUS(s);
 
+       comma = false;
        while (p->lex != BC_LEX_RPAREN) {
+               BcType t = BC_TYPE_VAR;
+
+               if (p->lex == XC_LEX_OP_MULTIPLY) {
+                       t = BC_TYPE_REF;
+                       s = zxc_lex_next();
+                       if (s) RETURN_STATUS(s);
+                       s = zbc_POSIX_does_not_allow("references");
+                       if (s) RETURN_STATUS(s);
+               }
+
                if (p->lex != XC_LEX_NAME)
-                       RETURN_STATUS(bc_error("bad function definition"));
+                       RETURN_STATUS(bc_error_bad_function_definition());
 
                ++p->func->nparams;
 
@@ -4326,20 +4449,23 @@ static BC_STATUS zbc_parse_funcdef(void)
                s = zxc_lex_next();
                if (s) goto err;
 
-               var = p->lex != BC_LEX_LBRACKET;
-
-               if (!var) {
+               if (p->lex == BC_LEX_LBRACKET) {
+                       if (t == BC_TYPE_VAR) t = BC_TYPE_ARRAY;
                        s = zxc_lex_next();
                        if (s) goto err;
 
                        if (p->lex != BC_LEX_RBRACKET) {
-                               s = bc_error("bad function definition");
+                               s = bc_error_bad_function_definition();
                                goto err;
                        }
 
                        s = zxc_lex_next();
                        if (s) goto err;
                }
+               else if (t == BC_TYPE_REF) {
+                       s = bc_error_at("vars can't be references");
+                       goto err;
+               }
 
                comma = p->lex == BC_LEX_COMMA;
                if (comma) {
@@ -4347,11 +4473,11 @@ static BC_STATUS zbc_parse_funcdef(void)
                        if (s) goto err;
                }
 
-               s = zbc_func_insert(p->func, name, var);
+               s = zbc_func_insert(p->func, name, t);
                if (s) goto err;
        }
 
-       if (comma) RETURN_STATUS(bc_error("bad function definition"));
+       if (comma) RETURN_STATUS(bc_error_bad_function_definition());
 
        s = zxc_lex_next();
        if (s) RETURN_STATUS(s);
@@ -4364,7 +4490,7 @@ static BC_STATUS zbc_parse_funcdef(void)
        // Prevent "define z()<newline>" from being interpreted as function with empty stmt as body
        s = zbc_lex_skip_if_at_NLINE();
        if (s) RETURN_STATUS(s);
-       //GNU bc requires a {} block even if function body has single stmt, enforce this?
+       // GNU bc requires a {} block even if function body has single stmt, enforce this
        if (p->lex != BC_LEX_LBRACE)
                RETURN_STATUS(bc_error("function { body } expected"));
 
@@ -4399,29 +4525,30 @@ static BC_STATUS zbc_parse_auto(void)
        if (s) RETURN_STATUS(s);
 
        for (;;) {
-               bool var;
+               BcType t;
 
                if (p->lex != XC_LEX_NAME)
-                       RETURN_STATUS(bc_error("bad 'auto' syntax"));
+                       RETURN_STATUS(bc_error_at("bad 'auto' syntax"));
 
                name = xstrdup(p->lex_strnumbuf.v);
                s = zxc_lex_next();
                if (s) goto err;
 
-               var = (p->lex != BC_LEX_LBRACKET);
-               if (!var) {
+               t = BC_TYPE_VAR;
+               if (p->lex == BC_LEX_LBRACKET) {
+                       t = BC_TYPE_ARRAY;
                        s = zxc_lex_next();
                        if (s) goto err;
 
                        if (p->lex != BC_LEX_RBRACKET) {
-                               s = bc_error("bad 'auto' syntax");
+                               s = bc_error_at("bad 'auto' syntax");
                                goto err;
                        }
                        s = zxc_lex_next();
                        if (s) goto err;
                }
 
-               s = zbc_func_insert(p->func, name, var);
+               s = zbc_func_insert(p->func, name, t);
                if (s) goto err;
 
                if (p->lex == XC_LEX_NLINE
@@ -4431,7 +4558,7 @@ static BC_STATUS zbc_parse_auto(void)
                        break;
                }
                if (p->lex != BC_LEX_COMMA)
-                       RETURN_STATUS(bc_error("bad 'auto' syntax"));
+                       RETURN_STATUS(bc_error_at("bad 'auto' syntax"));
                s = zxc_lex_next(); // skip comma
                if (s) RETURN_STATUS(s);
        }
@@ -4455,11 +4582,11 @@ static BC_STATUS zbc_parse_stmt_possibly_auto(bool auto_allowed)
 
        if (p->lex == XC_LEX_NLINE) {
                dbg_lex_done("%s:%d done (seen XC_LEX_NLINE)", __func__, __LINE__);
-               RETURN_STATUS(zxc_lex_next());
+               RETURN_STATUS(s);
        }
        if (p->lex == BC_LEX_SCOLON) {
                dbg_lex_done("%s:%d done (seen BC_LEX_SCOLON)", __func__, __LINE__);
-               RETURN_STATUS(zxc_lex_next());
+               RETURN_STATUS(s);
        }
 
        if (p->lex == BC_LEX_LBRACE) {
@@ -4477,6 +4604,17 @@ static BC_STATUS zbc_parse_stmt_possibly_auto(bool auto_allowed)
                        dbg_lex("%s:%d block parsing loop", __func__, __LINE__);
                        s = zbc_parse_stmt();
                        if (s) RETURN_STATUS(s);
+                       // Check that next token is a correct stmt delimiter -
+                       // disallows "print 1 print 2" and such.
+                       if (p->lex == BC_LEX_RBRACE)
+                               break;
+                       if (p->lex != BC_LEX_SCOLON
+                        && p->lex != XC_LEX_NLINE
+                       ) {
+                               RETURN_STATUS(bc_error_at("bad statement terminator"));
+                       }
+                       s = zxc_lex_next();
+                       if (s) RETURN_STATUS(s);
                }
                s = zxc_lex_next();
                dbg_lex_done("%s:%d done (seen BC_LEX_RBRACE)", __func__, __LINE__);
@@ -4527,8 +4665,7 @@ static BC_STATUS zbc_parse_stmt_possibly_auto(bool auto_allowed)
                        "BC_DIM_MAX      = "BC_MAX_DIM_STR   "\n"
                        "BC_SCALE_MAX    = "BC_MAX_SCALE_STR "\n"
                        "BC_STRING_MAX   = "BC_MAX_STRING_STR"\n"
-                       "BC_NAME_MAX     = "BC_MAX_NAME_STR  "\n"
-                       "BC_NUM_MAX      = "BC_MAX_NUM_STR   "\n"
+               //      "BC_NUM_MAX      = "BC_MAX_NUM_STR   "\n" - GNU bc does not show this
                        "MAX Exponent    = "BC_MAX_EXP_STR   "\n"
                        "Number of vars  = "BC_MAX_VARS_STR  "\n"
                );
@@ -4566,9 +4703,11 @@ static BC_STATUS zbc_parse_stmt_or_funcdef(void)
        BcStatus s;
 
        dbg_lex_enter("%s:%d entered", __func__, __LINE__);
-       if (p->lex == XC_LEX_EOF)
-               s = bc_error("end of file");
-       else if (p->lex == BC_LEX_KEY_DEFINE) {
+//why?
+//     if (p->lex == XC_LEX_EOF)
+//             s = bc_error("end of file");
+//     else
+       if (p->lex == BC_LEX_KEY_DEFINE) {
                dbg_lex("%s:%d p->lex:BC_LEX_KEY_DEFINE", __func__, __LINE__);
                s = zbc_parse_funcdef();
        } else {
@@ -4581,19 +4720,19 @@ static BC_STATUS zbc_parse_stmt_or_funcdef(void)
 }
 #define zbc_parse_stmt_or_funcdef(...) (zbc_parse_stmt_or_funcdef(__VA_ARGS__) COMMA_SUCCESS)
 
-// This is not a "z" function: can also return BC_STATUS_PARSE_EMPTY_EXP
-static BcStatus bc_parse_expr_empty_ok(uint8_t flags)
+#undef zbc_parse_expr
+static BC_STATUS zbc_parse_expr(uint8_t flags)
 {
        BcParse *p = &G.prs;
        BcInst prev = XC_INST_PRINT;
        size_t nexprs = 0, ops_bgn = p->ops.len;
        unsigned nparens, nrelops;
-       bool paren_first, paren_expr, rprn, assign, bin_last;
+       bool paren_first, rprn, assign, bin_last, incdec;
 
        dbg_lex_enter("%s:%d entered", __func__, __LINE__);
        paren_first = (p->lex == BC_LEX_LPAREN);
        nparens = nrelops = 0;
-       paren_expr = rprn = assign = false;
+       rprn = assign = incdec = false;
        bin_last = true;
 
        for (;;) {
@@ -4611,16 +4750,19 @@ static BcStatus bc_parse_expr_empty_ok(uint8_t flags)
                case BC_LEX_OP_INC:
                case BC_LEX_OP_DEC:
                        dbg_lex("%s:%d LEX_OP_INC/DEC", __func__, __LINE__);
-                       s = zbc_parse_incdec(&prev, &paren_expr, &nexprs, flags);
+                       if (incdec) RETURN_STATUS(bc_error_bad_assignment());
+                       s = zbc_parse_incdec(&prev, &nexprs, flags);
+                       incdec = true;
                        rprn = bin_last = false;
                        //get_token = false; - already is
                        break;
                case XC_LEX_OP_MINUS:
                        dbg_lex("%s:%d LEX_OP_MINUS", __func__, __LINE__);
-                       s = zbc_parse_minus(&prev, ops_bgn, rprn, &nexprs);
+                       s = zbc_parse_minus(&prev, ops_bgn, rprn, bin_last, &nexprs);
                        rprn = false;
                        //get_token = false; - already is
                        bin_last = (prev == XC_INST_MINUS);
+                       if (bin_last) incdec = false;
                        break;
                case BC_LEX_OP_ASSIGN_POWER:
                case BC_LEX_OP_ASSIGN_MULTIPLY:
@@ -4634,10 +4776,7 @@ static BcStatus bc_parse_expr_empty_ok(uint8_t flags)
                         && prev != XC_INST_SCALE && prev != XC_INST_IBASE
                         && prev != XC_INST_OBASE && prev != BC_INST_LAST
                        ) {
-                               return bc_error("bad assignment:"
-                                       " left side must be variable"
-                                       " or array element"
-                               ); // note: shared string
+                               RETURN_STATUS(bc_error_bad_assignment());
                        }
                // Fallthrough.
                case XC_LEX_OP_POWER:
@@ -4655,64 +4794,63 @@ static BcStatus bc_parse_expr_empty_ok(uint8_t flags)
                case BC_LEX_OP_BOOL_OR:
                case BC_LEX_OP_BOOL_AND:
                        dbg_lex("%s:%d LEX_OP_xyz", __func__, __LINE__);
-                       if (((t == BC_LEX_OP_BOOL_NOT) != bin_last)
-                        || (t != BC_LEX_OP_BOOL_NOT && prev == XC_INST_BOOL_NOT)
-                       ) {
-                               return bc_error_bad_expression();
+                       if (t == BC_LEX_OP_BOOL_NOT) {
+                               if (!bin_last && p->lex_last != BC_LEX_OP_BOOL_NOT)
+                                       RETURN_STATUS(bc_error_bad_expression());
+                       } else if (prev == XC_INST_BOOL_NOT) {
+                               RETURN_STATUS(bc_error_bad_expression());
                        }
+
                        nrelops += (t >= XC_LEX_OP_REL_EQ && t <= XC_LEX_OP_REL_GT);
                        prev = BC_TOKEN_2_INST(t);
                        bc_parse_operator(t, ops_bgn, &nexprs);
-                       s = zxc_lex_next();
-                       rprn = false;
-                       //get_token = false; - already is
+                       rprn = incdec = false;
+                       get_token = true;
                        bin_last = (t != BC_LEX_OP_BOOL_NOT);
                        break;
                case BC_LEX_LPAREN:
                        dbg_lex("%s:%d LEX_LPAREN", __func__, __LINE__);
-                       if (BC_PARSE_LEAF(prev, rprn))
-                               return bc_error_bad_expression();
+                       if (BC_PARSE_LEAF(prev, bin_last, rprn))
+                               RETURN_STATUS(bc_error_bad_expression());
                        bc_vec_push(&p->ops, &t);
                        nparens++;
                        get_token = true;
-                       paren_expr = false;
-                       rprn = bin_last = false;
+                       rprn = incdec = false;
                        break;
                case BC_LEX_RPAREN:
                        dbg_lex("%s:%d LEX_RPAREN", __func__, __LINE__);
+//why?
+//                     if (p->lex_last == BC_LEX_LPAREN) {
+//                             RETURN_STATUS(bc_error_at("empty expression"));
+//                     }
                        if (bin_last || prev == XC_INST_BOOL_NOT)
-                               return bc_error_bad_expression();
+                               RETURN_STATUS(bc_error_bad_expression());
                        if (nparens == 0) {
                                goto exit_loop;
                        }
-                       if (!paren_expr) {
-                               dbg_lex_done("%s:%d done (returning EMPTY_EXP)", __func__, __LINE__);
-                               return BC_STATUS_PARSE_EMPTY_EXP;
-                       }
                        s = zbc_parse_rightParen(ops_bgn, &nexprs);
                        nparens--;
                        get_token = true;
-                       paren_expr = rprn = true;
-                       bin_last = false;
+                       rprn = true;
+                       bin_last = incdec = false;
                        break;
                case XC_LEX_NAME:
                        dbg_lex("%s:%d LEX_NAME", __func__, __LINE__);
-                       if (BC_PARSE_LEAF(prev, rprn))
-                               return bc_error_bad_expression();
+                       if (BC_PARSE_LEAF(prev, bin_last, rprn))
+                               RETURN_STATUS(bc_error_bad_expression());
                        s = zbc_parse_name(&prev, flags & ~BC_PARSE_NOCALL);
-                       paren_expr = true;
-                       rprn = bin_last = false;
+                       rprn = (prev == BC_INST_CALL);
+                       bin_last = false;
                        //get_token = false; - already is
                        nexprs++;
                        break;
                case XC_LEX_NUMBER:
                        dbg_lex("%s:%d LEX_NUMBER", __func__, __LINE__);
-                       if (BC_PARSE_LEAF(prev, rprn))
-                               return bc_error_bad_expression();
+                       if (BC_PARSE_LEAF(prev, bin_last, rprn))
+                               RETURN_STATUS(bc_error_bad_expression());
                        xc_parse_pushNUM();
                        prev = XC_INST_NUM;
                        get_token = true;
-                       paren_expr = true;
                        rprn = bin_last = false;
                        nexprs++;
                        break;
@@ -4720,57 +4858,52 @@ static BcStatus bc_parse_expr_empty_ok(uint8_t flags)
                case BC_LEX_KEY_LAST:
                case BC_LEX_KEY_OBASE:
                        dbg_lex("%s:%d LEX_IBASE/LAST/OBASE", __func__, __LINE__);
-                       if (BC_PARSE_LEAF(prev, rprn))
-                               return bc_error_bad_expression();
+                       if (BC_PARSE_LEAF(prev, bin_last, rprn))
+                               RETURN_STATUS(bc_error_bad_expression());
                        prev = (char) (t - BC_LEX_KEY_IBASE + XC_INST_IBASE);
                        xc_parse_push((char) prev);
                        get_token = true;
-                       paren_expr = true;
                        rprn = bin_last = false;
                        nexprs++;
                        break;
                case BC_LEX_KEY_LENGTH:
                case BC_LEX_KEY_SQRT:
                        dbg_lex("%s:%d LEX_LEN/SQRT", __func__, __LINE__);
-                       if (BC_PARSE_LEAF(prev, rprn))
-                               return bc_error_bad_expression();
+                       if (BC_PARSE_LEAF(prev, bin_last, rprn))
+                               RETURN_STATUS(bc_error_bad_expression());
                        s = zbc_parse_builtin(t, flags, &prev);
                        get_token = true;
-                       paren_expr = true;
-                       rprn = bin_last = false;
+                       rprn = bin_last = incdec = false;
                        nexprs++;
                        break;
                case BC_LEX_KEY_READ:
                        dbg_lex("%s:%d LEX_READ", __func__, __LINE__);
-                       if (BC_PARSE_LEAF(prev, rprn))
-                               return bc_error_bad_expression();
+                       if (BC_PARSE_LEAF(prev, bin_last, rprn))
+                               RETURN_STATUS(bc_error_bad_expression());
                        s = zbc_parse_read();
                        prev = XC_INST_READ;
                        get_token = true;
-                       paren_expr = true;
-                       rprn = bin_last = false;
+                       rprn = bin_last = incdec = false;
                        nexprs++;
                        break;
                case BC_LEX_KEY_SCALE:
                        dbg_lex("%s:%d LEX_SCALE", __func__, __LINE__);
-                       if (BC_PARSE_LEAF(prev, rprn))
-                               return bc_error_bad_expression();
+                       if (BC_PARSE_LEAF(prev, bin_last, rprn))
+                               RETURN_STATUS(bc_error_bad_expression());
                        s = zbc_parse_scale(&prev, flags);
-                       prev = XC_INST_SCALE;
                        //get_token = false; - already is
-                       paren_expr = true;
                        rprn = bin_last = false;
                        nexprs++;
                        break;
                default:
-                       return bc_error_bad_token();
+                       RETURN_STATUS(bc_error_bad_token());
                }
 
                if (s || G_interrupt) // error, or ^C: stop parsing
-                       return BC_STATUS_FAILURE;
+                       RETURN_STATUS(BC_STATUS_FAILURE);
                if (get_token) {
                        s = zxc_lex_next();
-                       if (s) return s;
+                       if (s) RETURN_STATUS(s);
                }
        }
  exit_loop:
@@ -4780,7 +4913,7 @@ static BcStatus bc_parse_expr_empty_ok(uint8_t flags)
                assign = (top >= BC_LEX_OP_ASSIGN_POWER && top <= BC_LEX_OP_ASSIGN);
 
                if (top == BC_LEX_LPAREN || top == BC_LEX_RPAREN)
-                       return bc_error_bad_expression();
+                       RETURN_STATUS(bc_error_bad_expression());
 
                xc_parse_push(BC_TOKEN_2_INST(top));
 
@@ -4789,16 +4922,16 @@ static BcStatus bc_parse_expr_empty_ok(uint8_t flags)
        }
 
        if (prev == XC_INST_BOOL_NOT || nexprs != 1)
-               return bc_error_bad_expression();
+               RETURN_STATUS(bc_error_bad_expression());
 
        if (!(flags & BC_PARSE_REL) && nrelops) {
                BcStatus s;
                s = zbc_POSIX_does_not_allow("comparison operators outside if or loops");
-               if (s) return s;
+               if (s) RETURN_STATUS(s);
        } else if ((flags & BC_PARSE_REL) && nrelops > 1) {
                BcStatus s;
                s = zbc_POSIX_requires("exactly one comparison operator per condition");
-               if (s) return s;
+               if (s) RETURN_STATUS(s);
        }
 
        if (flags & BC_PARSE_PRINT) {
@@ -4808,8 +4941,9 @@ static BcStatus bc_parse_expr_empty_ok(uint8_t flags)
        }
 
        dbg_lex_done("%s:%d done", __func__, __LINE__);
-       return BC_STATUS_SUCCESS;
+       RETURN_STATUS(BC_STATUS_SUCCESS);
 }
+#define zbc_parse_expr(...) (zbc_parse_expr(__VA_ARGS__) COMMA_SUCCESS)
 
 #endif // ENABLE_BC
 
@@ -4839,11 +4973,12 @@ static void dc_parse_string(void)
        dbg_lex_enter("%s:%d entered", __func__, __LINE__);
 
        str = xstrdup(p->lex_strnumbuf.v);
-       xc_parse_push(XC_INST_STR);
-       xc_parse_pushIndex(len);
+       xc_parse_pushInst_and_Index(XC_INST_STR, len);
        bc_vec_push(&G.prog.strs, &str);
 
-       // Explanation needed here
+       // Add an empty function so that if zdc_program_execStr ever needs to
+       // parse the string into code (from the 'x' command) there's somewhere
+       // to store the bytecode.
        xc_program_add_fn();
        p->func = xc_program_func(p->fidx);
 
@@ -4982,6 +5117,9 @@ static BC_STATUS zdc_parse_expr(void)
        BcParse *p = &G.prs;
        int i;
 
+       if (p->lex == XC_LEX_NLINE)
+               RETURN_STATUS(zxc_lex_next());
+
        i = (int)p->lex - (int)XC_LEX_OP_POWER;
        if (i >= 0) {
                BcInst inst = dc_LEX_to_INST[i];
@@ -5014,15 +5152,71 @@ static BC_STATUS zdc_parse_exprs_until_eof(void)
 // Execution engine
 //
 
+#define BC_PROG_STR(n) (!(n)->num && !(n)->cap)
+#define BC_PROG_NUM(r, n) \
+       ((r)->t != XC_RESULT_ARRAY && (r)->t != XC_RESULT_STR && !BC_PROG_STR(n))
+
 #define STACK_HAS_MORE_THAN(s, n)          ((s)->len > ((size_t)(n)))
 #define STACK_HAS_EQUAL_OR_MORE_THAN(s, n) ((s)->len >= ((size_t)(n)))
 
-static BcVec* xc_program_search(char *id, bool var)
+static size_t xc_program_index(char *code, size_t *bgn)
+{
+       unsigned char *bytes = (void*)(code + *bgn);
+       unsigned amt;
+       unsigned i;
+       size_t res;
+
+       amt = *bytes++;
+       if (amt < SMALL_INDEX_LIMIT) {
+               *bgn += 1;
+               return amt;
+       }
+       amt -= (SMALL_INDEX_LIMIT - 1); // amt is 1 or more here
+       *bgn += amt + 1;
+
+       res = 0;
+       i = 0;
+       do {
+               res |= (size_t)(*bytes++) << i;
+               i += 8;
+       } while (--amt != 0);
+
+       return res;
+}
+
+static char *xc_program_name(char *code, size_t *bgn)
+{
+       code += *bgn;
+       *bgn += strlen(code) + 1;
+
+       return xstrdup(code);
+}
+
+static BcVec* xc_program_dereference(BcVec *vec)
+{
+       BcVec *v;
+       size_t vidx, nidx, i = 0;
+
+       //assert(vec->size == sizeof(uint8_t));
+
+       vidx = xc_program_index(vec->v, &i);
+       nidx = xc_program_index(vec->v, &i);
+
+       v = bc_vec_item(&G.prog.arrs, vidx);
+       v = bc_vec_item(v, nidx);
+
+       //assert(v->size != sizeof(uint8_t));
+
+       return v;
+}
+
+static BcVec* xc_program_search(char *id, BcType type)
 {
        BcId e, *ptr;
        BcVec *v, *map;
        size_t i;
        int new;
+       bool var = (type == BC_TYPE_VAR);
 
        v = var ? &G.prog.vars : &G.prog.arrs;
        map = var ? &G.prog.var_map : &G.prog.arr_map;
@@ -5043,11 +5237,12 @@ static BcVec* xc_program_search(char *id, bool var)
 }
 
 // 'num' need not be initialized on entry
-static BC_STATUS zxc_program_num(BcResult *r, BcNum **num, bool hex)
+static BC_STATUS zxc_program_num(BcResult *r, BcNum **num)
 {
        switch (r->t) {
        case XC_RESULT_STR:
        case XC_RESULT_TEMP:
+       IF_BC(case BC_RESULT_VOID:)
        case XC_RESULT_IBASE:
        case XC_RESULT_SCALE:
        case XC_RESULT_OBASE:
@@ -5056,7 +5251,6 @@ static BC_STATUS zxc_program_num(BcResult *r, BcNum **num, bool hex)
        case XC_RESULT_CONSTANT: {
                BcStatus s;
                char *str;
-               unsigned base_t;
                size_t len;
 
                str = *xc_program_const(r->d.id.idx);
@@ -5064,9 +5258,7 @@ static BC_STATUS zxc_program_num(BcResult *r, BcNum **num, bool hex)
 
                bc_num_init(&r->d.n, len);
 
-               hex = hex && len == 1;
-               base_t = hex ? 16 : G.prog.ib_t;
-               s = zxc_num_parse(&r->d.n, str, base_t);
+               s = zxc_num_parse(&r->d.n, str, G.prog.ib_t);
                if (s) {
                        bc_num_free(&r->d.n);
                        RETURN_STATUS(s);
@@ -5078,16 +5270,23 @@ static BC_STATUS zxc_program_num(BcResult *r, BcNum **num, bool hex)
        case XC_RESULT_VAR:
        case XC_RESULT_ARRAY:
        case XC_RESULT_ARRAY_ELEM: {
-               BcVec *v;
-
-               v = xc_program_search(r->d.id.name, r->t == XC_RESULT_VAR);
+               BcType type = (r->t == XC_RESULT_VAR) ? BC_TYPE_VAR : BC_TYPE_ARRAY;
+               BcVec *v = xc_program_search(r->d.id.name, type);
+               void *p = bc_vec_top(v);
 
                if (r->t == XC_RESULT_ARRAY_ELEM) {
-                       v = bc_vec_top(v);
-                       if (v->len <= r->d.id.idx) bc_array_expand(v, r->d.id.idx + 1);
-                       *num = bc_vec_item(v, r->d.id.idx);
-               } else
-                       *num = bc_vec_top(v);
+                       size_t idx = r->d.id.idx;
+
+                       v = p;
+                       if (v->size == sizeof(uint8_t))
+                               v = xc_program_dereference(v);
+                       //assert(v->size == sizeof(BcNum));
+                       if (v->len <= idx)
+                               bc_array_expand(v, idx + 1);
+                       *num = bc_vec_item(v, idx);
+               } else {
+                       *num = p;
+               }
                break;
        }
 #if ENABLE_BC
@@ -5113,7 +5312,6 @@ static BC_STATUS zxc_program_binOpPrep(BcResult **l, BcNum **ln,
                                      BcResult **r, BcNum **rn, bool assign)
 {
        BcStatus s;
-       bool hex;
        BcResultType lt, rt;
 
        if (!STACK_HAS_MORE_THAN(&G.prog.results, 1))
@@ -5122,19 +5320,18 @@ static BC_STATUS zxc_program_binOpPrep(BcResult **l, BcNum **ln,
        *r = bc_vec_item_rev(&G.prog.results, 0);
        *l = bc_vec_item_rev(&G.prog.results, 1);
 
-       lt = (*l)->t;
-       rt = (*r)->t;
-       hex = assign && (lt == XC_RESULT_IBASE || lt == XC_RESULT_OBASE);
-
-       s = zxc_program_num(*l, ln, false);
+       s = zxc_program_num(*l, ln);
        if (s) RETURN_STATUS(s);
-       s = zxc_program_num(*r, rn, hex);
+       s = zxc_program_num(*r, rn);
        if (s) RETURN_STATUS(s);
 
+       lt = (*l)->t;
+       rt = (*r)->t;
+
        // We run this again under these conditions in case any vector has been
        // reallocated out from under the BcNums or arrays we had.
        if (lt == rt && (lt == XC_RESULT_VAR || lt == XC_RESULT_ARRAY_ELEM)) {
-               s = zxc_program_num(*l, ln, false);
+               s = zxc_program_num(*l, ln);
                if (s) RETURN_STATUS(s);
        }
 
@@ -5163,7 +5360,7 @@ static BC_STATUS zxc_program_prep(BcResult **r, BcNum **n)
                RETURN_STATUS(bc_error_stack_has_too_few_elements());
        *r = bc_vec_top(&G.prog.results);
 
-       s = zxc_program_num(*r, n, false);
+       s = zxc_program_num(*r, n);
        if (s) RETURN_STATUS(s);
 
        if (!BC_PROG_NUM((*r), (*n)))
@@ -5227,17 +5424,14 @@ static BC_STATUS zxc_program_read(void)
                IF_DC(s = zdc_parse_exprs_until_eof());
        }
        if (s) goto exec_err;
-
        if (G.prs.lex != XC_LEX_NLINE && G.prs.lex != XC_LEX_EOF) {
-               s = bc_error("bad read() expression");
+               s = bc_error_at("bad read() expression");
                goto exec_err;
        }
+       xc_parse_push(XC_INST_RET);
 
        ip.func = BC_PROG_READ;
        ip.inst_idx = 0;
-       IF_BC(ip.results_len_before_call = G.prog.results.len;)
-
-       xc_parse_push(XC_INST_RET);
        bc_vec_push(&G.prog.exestack, &ip);
 
  exec_err:
@@ -5248,41 +5442,10 @@ static BC_STATUS zxc_program_read(void)
 }
 #define zxc_program_read(...) (zxc_program_read(__VA_ARGS__) COMMA_SUCCESS)
 
-static size_t xc_program_index(char *code, size_t *bgn)
-{
-       unsigned char *bytes = (void*)(code + *bgn);
-       unsigned amt;
-       unsigned i;
-       size_t res;
-
-       amt = *bytes++;
-       if (amt < SMALL_INDEX_LIMIT) {
-               *bgn += 1;
-               return amt;
-       }
-       amt -= SMALL_INDEX_LIMIT;
-       *bgn += amt + 1;
-
-       amt *= 8;
-       res = 0;
-       for (i = 0; i < amt; i += 8)
-               res |= (size_t)(*bytes++) << i;
-
-       return res;
-}
-
-static char *xc_program_name(char *code, size_t *bgn)
-{
-       code += *bgn;
-       *bgn += strlen(code) + 1;
-
-       return xstrdup(code);
-}
-
 static void xc_program_printString(const char *str)
 {
 #if ENABLE_DC
-       if (!str[0]) {
+       if (!str[0] && IS_DC) {
                // Example: echo '[]ap' | dc
                // should print two bytes: 0x00, 0x0A
                bb_putchar('\0');
@@ -5290,46 +5453,27 @@ static void xc_program_printString(const char *str)
        }
 #endif
        while (*str) {
-               int c = *str++;
-               if (c != '\\' || !*str)
-                       bb_putchar(c);
-               else {
+               char c = *str++;
+               if (c == '\\') {
+                       static const char esc[] ALIGN1 = "nabfrt""e\\";
+                       char *n;
+
                        c = *str++;
-                       switch (c) {
-                       case 'a':
-                               bb_putchar('\a');
-                               break;
-                       case 'b':
-                               bb_putchar('\b');
-                               break;
-                       case '\\':
-                       case 'e':
-                               bb_putchar('\\');
-                               break;
-                       case 'f':
-                               bb_putchar('\f');
-                               break;
-                       case 'n':
-                               bb_putchar('\n');
-                               G.prog.nchars = SIZE_MAX;
-                               break;
-                       case 'r':
-                               bb_putchar('\r');
-                               break;
-                       case 'q':
-                               bb_putchar('"');
-                               break;
-                       case 't':
-                               bb_putchar('\t');
-                               break;
-                       default:
-                               // Just print the backslash and following character.
+                       n = strchr(esc, c); // note: if c is NUL, n = \0 at end of esc
+                       if (!n || !c) {
+                               // Just print the backslash and following character
                                bb_putchar('\\');
                                ++G.prog.nchars;
-                               bb_putchar(c);
-                               break;
+                               // But if we're at the end of the string, stop
+                               if (!c) break;
+                       } else {
+                               if (n - esc == 0) // "\n" ?
+                                       G.prog.nchars = SIZE_MAX;
+                               c = "\n\a\b\f\r\t""\\\\""\\"[n - esc];
+                               //   n a b f r t   e \   \<end of line>
                        }
                }
+               putchar(c);
                ++G.prog.nchars;
        }
 }
@@ -5483,7 +5627,7 @@ static BC_STATUS zxc_num_printBase(BcNum *n)
 
        n->neg = false;
 
-       if (G.prog.ob_t <= BC_NUM_MAX_IBASE) {
+       if (G.prog.ob_t <= 16) {
                width = 1;
                print = bc_num_printHex;
        } else {
@@ -5528,22 +5672,32 @@ static BC_STATUS zxc_num_print(BcNum *n, bool newline)
 }
 #define zxc_num_print(...) (zxc_num_print(__VA_ARGS__) COMMA_SUCCESS)
 
-static BC_STATUS zxc_program_print(char inst, size_t idx)
+#if !ENABLE_DC
+// for bc, idx is always 0
+#define xc_program_print(inst, idx) \
+       xc_program_print(inst)
+#endif
+static BC_STATUS xc_program_print(char inst, size_t idx)
 {
        BcStatus s;
        BcResult *r;
        BcNum *num;
-       bool pop = (inst != XC_INST_PRINT);
+       IF_NOT_DC(size_t idx = 0);
 
        if (!STACK_HAS_MORE_THAN(&G.prog.results, idx))
                RETURN_STATUS(bc_error_stack_has_too_few_elements());
 
        r = bc_vec_item_rev(&G.prog.results, idx);
-       s = zxc_program_num(r, &num, false);
+#if ENABLE_BC
+       if (inst == XC_INST_PRINT && r->t == BC_RESULT_VOID)
+               // void function's result on stack, ignore
+               RETURN_STATUS(BC_STATUS_SUCCESS);
+#endif
+       s = zxc_program_num(r, &num);
        if (s) RETURN_STATUS(s);
 
        if (BC_PROG_NUM(r, num)) {
-               s = zxc_num_print(num, !pop);
+               s = zxc_num_print(num, /*newline:*/ inst == XC_INST_PRINT);
 #if ENABLE_BC
                if (!s && IS_BC) bc_num_copy(&G.prog.last, num);
 #endif
@@ -5554,24 +5708,23 @@ static BC_STATUS zxc_program_print(char inst, size_t idx)
                str = *xc_program_str(idx);
 
                if (inst == XC_INST_PRINT_STR) {
-                       for (;;) {
-                               char c = *str++;
-                               if (c == '\0') break;
-                               bb_putchar(c);
-                               ++G.prog.nchars;
-                               if (c == '\n') G.prog.nchars = 0;
-                       }
+                       char *nl;
+                       G.prog.nchars += printf("%s", str);
+                       nl = strrchr(str, '\n');
+                       if (nl)
+                               G.prog.nchars = strlen(nl + 1);
                } else {
                        xc_program_printString(str);
-                       if (inst == XC_INST_PRINT) bb_putchar('\n');
+                       if (inst == XC_INST_PRINT)
+                               bb_putchar('\n');
                }
        }
 
-       if (!s && pop) bc_vec_pop(&G.prog.results);
+       if (!s && inst != XC_INST_PRINT) bc_vec_pop(&G.prog.results);
 
        RETURN_STATUS(s);
 }
-#define zxc_program_print(...) (zxc_program_print(__VA_ARGS__) COMMA_SUCCESS)
+#define zxc_program_print(...) (xc_program_print(__VA_ARGS__) COMMA_SUCCESS)
 
 static BC_STATUS zxc_program_negate(void)
 {
@@ -5666,48 +5819,86 @@ static BC_STATUS zdc_program_assignStr(BcResult *r, BcVec *v, bool push)
 #define zdc_program_assignStr(...) (zdc_program_assignStr(__VA_ARGS__) COMMA_SUCCESS)
 #endif // ENABLE_DC
 
-static BC_STATUS zxc_program_copyToVar(char *name, bool var)
+static BC_STATUS zxc_program_popResultAndCopyToVar(char *name, BcType t)
 {
        BcStatus s;
        BcResult *ptr, r;
-       BcVec *v;
+       BcVec *vec;
        BcNum *n;
+       bool var = (t == BC_TYPE_VAR);
 
        if (!STACK_HAS_MORE_THAN(&G.prog.results, 0))
                RETURN_STATUS(bc_error_stack_has_too_few_elements());
 
        ptr = bc_vec_top(&G.prog.results);
-       if ((ptr->t == XC_RESULT_ARRAY) != !var)
+       if ((ptr->t == XC_RESULT_ARRAY) == var)
                RETURN_STATUS(bc_error_variable_is_wrong_type());
-       v = xc_program_search(name, var);
+       vec = xc_program_search(name, t);
 
 #if ENABLE_DC
-       if (ptr->t == XC_RESULT_STR && !var)
-               RETURN_STATUS(bc_error_variable_is_wrong_type());
-       if (ptr->t == XC_RESULT_STR)
-               RETURN_STATUS(zdc_program_assignStr(ptr, v, true));
+       if (ptr->t == XC_RESULT_STR) {
+               if (!var)
+                       RETURN_STATUS(bc_error_variable_is_wrong_type());
+               RETURN_STATUS(zdc_program_assignStr(ptr, vec, true));
+       }
 #endif
 
-       s = zxc_program_num(ptr, &n, false);
+       s = zxc_program_num(ptr, &n);
        if (s) RETURN_STATUS(s);
 
        // Do this once more to make sure that pointers were not invalidated.
-       v = xc_program_search(name, var);
+       vec = xc_program_search(name, t);
 
        if (var) {
                bc_num_init_DEF_SIZE(&r.d.n);
                bc_num_copy(&r.d.n, n);
        } else {
+               BcVec *v = (BcVec*) n;
+               bool ref, ref_size;
+
+               ref = (v->size == sizeof(BcVec) && t != BC_TYPE_ARRAY);
+               ref_size = (v->size == sizeof(uint8_t));
+
+               if (ref || (ref_size && t == BC_TYPE_REF)) {
+                       bc_vec_init(&r.d.v, sizeof(uint8_t), NULL);
+                       if (ref) {
+                               size_t vidx, idx;
+                               BcId id;
+
+                               id.name = ptr->d.id.name;
+                               v = xc_program_search(ptr->d.id.name, BC_TYPE_REF);
+
+                               // Make sure the pointer was not invalidated.
+                               vec = xc_program_search(name, t);
+
+                               vidx = bc_map_find_exact(&G.prog.arr_map, &id);
+                               //assert(vidx != BC_VEC_INVALID_IDX);
+                               vidx = ((BcId*) bc_vec_item(&G.prog.arr_map, vidx))->idx;
+                               idx = v->len - 1;
+
+                               bc_vec_pushIndex(&r.d.v, vidx);
+                               bc_vec_pushIndex(&r.d.v, idx);
+                       }
+                       // If we get here, we are copying a ref to a ref.
+                       else bc_vec_npush(&r.d.v, v->len, v->v);
+
+                       // We need to return early.
+                       goto ret;
+               }
+
+               if (ref_size && t != BC_TYPE_REF)
+                       v = xc_program_dereference(v);
+
                bc_array_init(&r.d.v, true);
-               bc_array_copy(&r.d.v, (BcVec *) n);
+               bc_array_copy(&r.d.v, v);
        }
-
-       bc_vec_push(v, &r.d);
+ ret:
+       bc_vec_push(vec, &r.d);
        bc_vec_pop(&G.prog.results);
 
        RETURN_STATUS(s);
 }
-#define zxc_program_copyToVar(...) (zxc_program_copyToVar(__VA_ARGS__) COMMA_SUCCESS)
+#define zxc_program_popResultAndCopyToVar(...) (zxc_program_popResultAndCopyToVar(__VA_ARGS__) COMMA_SUCCESS)
 
 static BC_STATUS zxc_program_assign(char inst)
 {
@@ -5729,22 +5920,20 @@ static BC_STATUS zxc_program_assign(char inst)
 
                if (left->t != XC_RESULT_VAR)
                        RETURN_STATUS(bc_error_variable_is_wrong_type());
-               v = xc_program_search(left->d.id.name, true);
+               v = xc_program_search(left->d.id.name, BC_TYPE_VAR);
 
                RETURN_STATUS(zdc_program_assignStr(right, v, false));
        }
 #endif
 
-       if (left->t == XC_RESULT_CONSTANT || left->t == XC_RESULT_TEMP)
-               RETURN_STATUS(bc_error("bad assignment:"
-                               " left side must be variable"
-                               " or array element"
-               )); // note: shared string
+       if (left->t == XC_RESULT_CONSTANT
+        || left->t == XC_RESULT_TEMP
+       IF_BC(|| left->t == BC_RESULT_VOID)
+       ) {
+               RETURN_STATUS(bc_error_bad_assignment());
+       }
 
 #if ENABLE_BC
-       if (inst == BC_INST_ASSIGN_DIVIDE && !bc_num_cmp(r, &G.prog.zero))
-               RETURN_STATUS(bc_error("divide by zero"));
-
        if (assign)
                bc_num_copy(l, r);
        else {
@@ -5810,7 +5999,7 @@ static BC_STATUS xc_program_pushVar(char *code, size_t *bgn,
 
 #if ENABLE_DC
        if (pop || copy) {
-               BcVec *v = xc_program_search(name, true);
+               BcVec *v = xc_program_search(name, BC_TYPE_VAR);
                BcNum *num = bc_vec_top(v);
 
                free(name);
@@ -5909,12 +6098,10 @@ static BC_STATUS zbc_program_call(char *code, size_t *idx)
 {
        BcInstPtr ip;
        size_t i, nparams;
-       BcFunc *func;
        BcId *a;
-       BcResult *arg;
+       BcFunc *func;
 
        nparams = xc_program_index(code, idx);
-       ip.inst_idx = 0;
        ip.func = xc_program_index(code, idx);
        func = xc_program_func(ip.func);
 
@@ -5924,18 +6111,24 @@ static BC_STATUS zbc_program_call(char *code, size_t *idx)
        if (nparams != func->nparams) {
                RETURN_STATUS(bc_error_fmt("function has %u parameters, but called with %u", func->nparams, nparams));
        }
-       ip.results_len_before_call = G.prog.results.len - nparams;
+       ip.inst_idx = 0;
 
        for (i = 0; i < nparams; ++i) {
+               BcResult *arg;
                BcStatus s;
+               bool arr;
 
                a = bc_vec_item(&func->autos, nparams - 1 - i);
                arg = bc_vec_top(&G.prog.results);
 
-               if ((!a->idx) != (arg->t == XC_RESULT_ARRAY) || arg->t == XC_RESULT_STR)
-                       RETURN_STATUS(bc_error_variable_is_wrong_type());
+               arr = (a->idx == BC_TYPE_ARRAY || a->idx == BC_TYPE_REF);
 
-               s = zxc_program_copyToVar(a->name, a->idx);
+               if (arr != (arg->t == XC_RESULT_ARRAY) // array/variable mismatch
+               // || arg->t == XC_RESULT_STR - impossible, f("str") is not a legal syntax (strings are not bc expressions)
+               ) {
+                       RETURN_STATUS(bc_error_variable_is_wrong_type());
+               }
+               s = zxc_program_popResultAndCopyToVar(a->name, (BcType) a->idx);
                if (s) RETURN_STATUS(s);
        }
 
@@ -5943,12 +6136,13 @@ static BC_STATUS zbc_program_call(char *code, size_t *idx)
        for (; i < func->autos.len; i++, a++) {
                BcVec *v;
 
-               v = xc_program_search(a->name, a->idx);
-               if (a->idx) {
+               v = xc_program_search(a->name, (BcType) a->idx);
+               if (a->idx == BC_TYPE_VAR) {
                        BcNum n2;
                        bc_num_init_DEF_SIZE(&n2);
                        bc_vec_push(v, &n2);
                } else {
+                       //assert(a->idx == BC_TYPE_ARRAY);
                        BcVec v2;
                        bc_array_init(&v2, true);
                        bc_vec_push(v, &v2);
@@ -5969,38 +6163,40 @@ static BC_STATUS zbc_program_return(char inst)
        size_t i;
        BcInstPtr *ip = bc_vec_top(&G.prog.exestack);
 
-       if (!STACK_HAS_EQUAL_OR_MORE_THAN(&G.prog.results, ip->results_len_before_call + (inst == XC_INST_RET)))
-               RETURN_STATUS(bc_error_stack_has_too_few_elements());
-
        f = xc_program_func(ip->func);
-       res.t = XC_RESULT_TEMP;
 
+       res.t = XC_RESULT_TEMP;
        if (inst == XC_INST_RET) {
+               // bc needs this for e.g. RESULT_CONSTANT ("return 5")
+               // because bc constants are per-function.
+               // TODO: maybe avoid if value is already RESULT_TEMP?
                BcStatus s;
                BcNum *num;
                BcResult *operand = bc_vec_top(&G.prog.results);
 
-               s = zxc_program_num(operand, &num, false);
+               s = zxc_program_num(operand, &num);
                if (s) RETURN_STATUS(s);
                bc_num_init(&res.d.n, num->len);
                bc_num_copy(&res.d.n, num);
+               bc_vec_pop(&G.prog.results);
        } else {
+               if (f->voidfunc)
+                       res.t = BC_RESULT_VOID;
                bc_num_init_DEF_SIZE(&res.d.n);
                //bc_num_zero(&res.d.n); - already is
        }
+       bc_vec_push(&G.prog.results, &res);
+
+       bc_vec_pop(&G.prog.exestack);
 
        // We need to pop arguments as well, so this takes that into account.
        a = (void*)f->autos.v;
        for (i = 0; i < f->autos.len; i++, a++) {
                BcVec *v;
-               v = xc_program_search(a->name, a->idx);
+               v = xc_program_search(a->name, (BcType) a->idx);
                bc_vec_pop(v);
        }
 
-       bc_vec_npop(&G.prog.results, G.prog.results.len - ip->results_len_before_call);
-       bc_vec_push(&G.prog.results, &res);
-       bc_vec_pop(&G.prog.exestack);
-
        RETURN_STATUS(BC_STATUS_SUCCESS);
 }
 #define zbc_program_return(...) (zbc_program_return(__VA_ARGS__) COMMA_SUCCESS)
@@ -6036,7 +6232,7 @@ static BC_STATUS zxc_program_builtin(char inst)
                RETURN_STATUS(bc_error_stack_has_too_few_elements());
        opnd = bc_vec_top(&G.prog.results);
 
-       s = zxc_program_num(opnd, &num, false);
+       s = zxc_program_num(opnd, &num);
        if (s) RETURN_STATUS(s);
 
 #if ENABLE_DC
@@ -6112,7 +6308,7 @@ static BC_STATUS zdc_program_modexp(void)
        if (s) RETURN_STATUS(s);
 
        r1 = bc_vec_item_rev(&G.prog.results, 2);
-       s = zxc_program_num(r1, &n1, false);
+       s = zxc_program_num(r1, &n1);
        if (s) RETURN_STATUS(s);
        if (!BC_PROG_NUM(r1, n1))
                RETURN_STATUS(bc_error_variable_is_wrong_type());
@@ -6120,11 +6316,11 @@ static BC_STATUS zdc_program_modexp(void)
        // Make sure that the values have their pointers updated, if necessary.
        if (r1->t == XC_RESULT_VAR || r1->t == XC_RESULT_ARRAY_ELEM) {
                if (r1->t == r2->t) {
-                       s = zxc_program_num(r2, &n2, false);
+                       s = zxc_program_num(r2, &n2);
                        if (s) RETURN_STATUS(s);
                }
                if (r1->t == r3->t) {
-                       s = zxc_program_num(r3, &n3, false);
+                       s = zxc_program_num(r3, &n3);
                        if (s) RETURN_STATUS(s);
                }
        }
@@ -6167,9 +6363,9 @@ static BC_STATUS zdc_program_asciify(void)
 
        if (!STACK_HAS_MORE_THAN(&G.prog.results, 0))
                RETURN_STATUS(bc_error_stack_has_too_few_elements());
-       r = bc_vec_top(&G.prog.results);
 
-       s = zxc_program_num(r, &num, false);
+       r = bc_vec_top(&G.prog.results);
+       s = zxc_program_num(r, &num);
        if (s) RETURN_STATUS(s);
 
        if (BC_PROG_NUM(r, num)) {
@@ -6184,8 +6380,8 @@ static BC_STATUS zdc_program_asciify(void)
                strmb.cap = ARRAY_SIZE(strmb_digs);
                strmb.num = strmb_digs;
                bc_num_ulong2num(&strmb, 0x100);
-               s = zbc_num_mod(&n, &strmb, &n, 0);
 
+               s = zbc_num_mod(&n, &strmb, &n, 0);
                if (s) goto num_err;
                s = zbc_num_ulong(&n, &val);
                if (s) goto num_err;
@@ -6209,7 +6405,11 @@ static BC_STATUS zdc_program_asciify(void)
        str = xzalloc(2);
        str[0] = c;
        //str[1] = '\0'; - already is
-       bc_vec_push(&G.prog.strs, &str);
+       idx = bc_vec_push(&G.prog.strs, &str);
+       // Add an empty function so that if zdc_program_execStr ever needs to
+       // parse the string into code (from the 'x' command) there's somewhere
+       // to store the bytecode.
+       xc_program_add_fn();
  dup:
        res.t = XC_RESULT_STR;
        res.d.id.idx = idx;
@@ -6233,7 +6433,7 @@ static BC_STATUS zdc_program_printStream(void)
                RETURN_STATUS(bc_error_stack_has_too_few_elements());
        r = bc_vec_top(&G.prog.results);
 
-       s = zxc_program_num(r, &n, false);
+       s = zxc_program_num(r, &n);
        if (s) RETURN_STATUS(s);
 
        if (BC_PROG_NUM(r, n)) {
@@ -6309,7 +6509,7 @@ static BC_STATUS zdc_program_execStr(char *code, size_t *bgn, bool cond)
 
                if (exec) {
                        BcVec *v;
-                       v = xc_program_search(name, true);
+                       v = xc_program_search(name, BC_TYPE_VAR);
                        n = bc_vec_top(v);
                }
 
@@ -6328,11 +6528,11 @@ static BC_STATUS zdc_program_execStr(char *code, size_t *bgn, bool cond)
                        sidx = r->d.id.idx;
                } else if (r->t == XC_RESULT_VAR) {
                        BcNum *n;
-                       s = zxc_program_num(r, &n, false);
+                       s = zxc_program_num(r, &n);
                        if (s || !BC_PROG_STR(n)) goto exit;
                        sidx = n->rdx;
                } else
-                       goto exit;
+                       goto exit_nopop;
        }
 
        fidx = sidx + BC_PROG_REQ_FUNCS;
@@ -6372,6 +6572,7 @@ static BC_STATUS zdc_program_execStr(char *code, size_t *bgn, bool cond)
        RETURN_STATUS(BC_STATUS_SUCCESS);
  exit:
        bc_vec_pop(&G.prog.results);
+ exit_nopop:
        RETURN_STATUS(s);
 }
 #define zdc_program_execStr(...) (zdc_program_execStr(__VA_ARGS__) COMMA_SUCCESS)
@@ -6410,7 +6611,17 @@ static BC_STATUS zxc_program_exec(void)
 
                dbg_exec("inst at %zd:%d results.len:%d", ip->inst_idx - 1, inst, G.prog.results.len);
                switch (inst) {
+               case XC_INST_RET:
+                       if (IS_DC) { // end of '?' reached
+                               bc_vec_pop(&G.prog.exestack);
+                               goto read_updated_ip;
+                       }
+                       // bc: fall through
 #if ENABLE_BC
+               case BC_INST_RET0:
+                       dbg_exec("BC_INST_RET[0]:");
+                       s = zbc_program_return(inst);
+                       goto read_updated_ip;
                case BC_INST_JUMP_ZERO: {
                        BcNum *num;
                        bool zero;
@@ -6447,11 +6658,6 @@ static BC_STATUS zxc_program_exec(void)
                        dbg_exec("BC_INST_HALT:");
                        QUIT_OR_RETURN_TO_MAIN;
                        break;
-               case XC_INST_RET:
-               case BC_INST_RET0:
-                       dbg_exec("BC_INST_RET[0]:");
-                       s = zbc_program_return(inst);
-                       goto read_updated_ip;
                case XC_INST_BOOL_OR:
                case XC_INST_BOOL_AND:
 #endif // ENABLE_BC
@@ -6512,7 +6718,7 @@ static BC_STATUS zxc_program_exec(void)
                case XC_INST_PRINT:
                case XC_INST_PRINT_POP:
                case XC_INST_PRINT_STR:
-                       dbg_exec("XC_INST_PRINTxyz:");
+                       dbg_exec("XC_INST_PRINTxyz(%d):", inst - XC_INST_PRINT);
                        s = zxc_program_print(inst, 0);
                        break;
                case XC_INST_STR:
@@ -6629,7 +6835,7 @@ static BC_STATUS zxc_program_exec(void)
                }
                case DC_INST_PUSH_TO_VAR: {
                        char *name = xc_program_name(code, &ip->inst_idx);
-                       s = zxc_program_copyToVar(name, true);
+                       s = zxc_program_popResultAndCopyToVar(name, BC_TYPE_VAR);
                        free(name);
                        break;
                }
@@ -6688,7 +6894,6 @@ static BC_STATUS zxc_vm_process(const char *text)
        s = zxc_parse_text_init(text); // does the first zxc_lex_next()
        if (s) RETURN_STATUS(s);
 
- IF_BC(check_eof:)
        while (G.prs.lex != XC_LEX_EOF) {
                BcInstPtr *ip;
                BcFunc *f;
@@ -6696,14 +6901,6 @@ static BC_STATUS zxc_vm_process(const char *text)
                dbg_lex("%s:%d G.prs.lex:%d, parsing...", __func__, __LINE__, G.prs.lex);
                if (IS_BC) {
 #if ENABLE_BC
-                       if (G.prs.lex == BC_LEX_SCOLON
-                        || G.prs.lex == XC_LEX_NLINE
-                       ) {
-                               s = zxc_lex_next();
-                               if (s) goto err;
-                               goto check_eof;
-                       }
-
                        s = zbc_parse_stmt_or_funcdef();
                        if (s) goto err;
 
@@ -6713,13 +6910,7 @@ static BC_STATUS zxc_vm_process(const char *text)
                         && G.prs.lex != XC_LEX_NLINE
                         && G.prs.lex != XC_LEX_EOF
                        ) {
-                               const char *err_at;
-//TODO: commonalize for other parse errors:
-                               err_at = G.prs.lex_next_at ? G.prs.lex_next_at : "UNKNOWN";
-                               bc_error_fmt("bad statement terminator at '%.*s'",
-                                       (int)(strchrnul(err_at, '\n') - err_at),
-                                       err_at
-                               );
+                               bc_error_at("bad statement terminator");
                                goto err;
                        }
                        // The above logic is fragile. Check these examples:
@@ -6727,14 +6918,6 @@ static BC_STATUS zxc_vm_process(const char *text)
 #endif
                } else {
 #if ENABLE_DC
-                       // Most of dc parsing assumes all whitespace,
-                       // including '\n', is eaten.
-                       while (G.prs.lex == XC_LEX_NLINE) {
-                               s = zxc_lex_next();
-                               if (s) goto err;
-                               if (G.prs.lex == XC_LEX_EOF)
-                                       goto done;
-                       }
                        s = zdc_parse_expr();
 #endif
                }
@@ -6754,9 +6937,9 @@ static BC_STATUS zxc_vm_process(const char *text)
                ip = (void*)G.prog.exestack.v;
 #if SANITY_CHECKS
                if (G.prog.exestack.len != 1) // should have only main's IP
-                       bb_error_msg_and_die("BUG:call stack");
+                       bb_simple_error_msg_and_die("BUG:call stack");
                if (ip->func != BC_PROG_MAIN)
-                       bb_error_msg_and_die("BUG:not MAIN");
+                       bb_simple_error_msg_and_die("BUG:not MAIN");
 #endif
                f = xc_program_func_BC_PROG_MAIN();
                // bc discards strings, constants and code after each
@@ -6772,10 +6955,13 @@ static BC_STATUS zxc_vm_process(const char *text)
                if (IS_BC) {
 #if SANITY_CHECKS
                        if (G.prog.results.len != 0) // should be empty
-                               bb_error_msg_and_die("BUG:data stack");
+                               bb_simple_error_msg_and_die("BUG:data stack");
 #endif
                        IF_BC(bc_vec_pop_all(&f->strs);)
                        IF_BC(bc_vec_pop_all(&f->consts);)
+                       // We are at SCOLON/NLINE, skip it:
+                       s = zxc_lex_next();
+                       if (s) goto err;
                } else {
                        if (G.prog.results.len == 0
                         && G.prog.vars.len == 0
@@ -6797,7 +6983,7 @@ static BC_STATUS zxc_vm_process(const char *text)
                bc_vec_pop_all(&f->code);
                ip->inst_idx = 0;
        }
- IF_DC(done:)
+
        dbg_lex_done("%s:%d done", __func__, __LINE__);
        RETURN_STATUS(s);
 }
@@ -6813,6 +6999,7 @@ static BC_STATUS zxc_vm_execute_FILE(FILE *fp, const char *filename)
        G.prs.lex_filename = filename;
        G.prs.lex_input_fp = fp;
        G.err_line = G.prs.lex_line = 1;
+       dbg_lex("p->lex_line reset to 1");
 
        do {
                s = zxc_vm_process("");
@@ -7137,14 +7324,6 @@ static void xc_program_free(void)
        IF_BC(bc_num_free(&G.prog.one);)
        bc_vec_free(&G.input_buffer);
 }
-
-static void xc_vm_free(void)
-{
-       bc_vec_free(&G.files);
-       xc_program_free();
-       xc_parse_free();
-       free(G.env_args);
-}
 #endif
 
 static void xc_program_init(void)
@@ -7198,12 +7377,12 @@ static void xc_program_init(void)
 
 static int xc_vm_init(const char *env_len)
 {
+       G.prog.len = xc_vm_envLen(env_len);
 #if ENABLE_FEATURE_EDITING
        G.line_input_state = new_line_input_t(DO_HISTORY);
 #endif
-       G.prog.len = xc_vm_envLen(env_len);
-
        bc_vec_init(&G.files, sizeof(char *), NULL);
+
        xc_program_init();
        IF_BC(if (IS_BC) bc_vm_envArgs();)
        xc_parse_create(BC_PROG_MAIN);
@@ -7244,7 +7423,11 @@ static BcStatus xc_vm_run(void)
 #if ENABLE_FEATURE_CLEAN_UP
        if (G_exiting) // it was actually "halt" or "quit"
                st = EXIT_SUCCESS;
-       xc_vm_free();
+
+       bc_vec_free(&G.files);
+       xc_program_free();
+       xc_parse_free();
+       free(G.env_args);
 # if ENABLE_FEATURE_EDITING
        free_line_input_t(G.line_input_state);
 # endif
@@ -7331,4 +7514,3 @@ int dc_main(int argc UNUSED_PARAM, char **argv)
 #endif
 
 #endif // DC_BIG
-