dc: fix "dc does_not_exist" SEGVing
[oweals/busybox.git] / miscutils / bc.c
index 0943f606d38d95931114b21a1ac097419c970376..0200afca27d8ccec79176351c1e955756666f796 100644 (file)
 //config:           Also note that, like the FreeBSD dc, extended registers are not
 //config:           allowed unless the "-x" option is given.
 //config:
+//config:config FEATURE_DC_SMALL
+//config:      bool "Minimal dc implementation (4.2 kb), not using bc code base"
+//config:      depends on DC && !BC
+//config:      default y
+//config:
+//config:config FEATURE_DC_LIBM
+//config:      bool "Enable power and exp functions (requires libm)"
+//config:      default y
+//config:      depends on FEATURE_DC_SMALL
+//config:      help
+//config:      Enable power and exp functions.
+//config:      NOTE: This will require libm to be present for linking.
+//config:
 //config:config FEATURE_BC_SIGNALS
 //config:      bool "Enable bc/dc signal handling"
 //config:      default y
-//config:      depends on BC || DC
+//config:      depends on (BC || DC) && !FEATURE_DC_SMALL
 //config:      help
 //config:      Enable signal handling for bc and dc.
 //config:
 //config:config FEATURE_BC_LONG_OPTIONS
 //config:      bool "Enable bc/dc long options"
 //config:      default y
-//config:      depends on BC || DC
+//config:      depends on (BC || DC) && !FEATURE_DC_SMALL
 //config:      help
 //config:      Enable long options for bc and dc.
 
 
 //See www.gnu.org/software/bc/manual/bc.html
 //usage:#define bc_trivial_usage
-//usage:       "[-sqli] FILE..."
+//usage:       "[-sqliw] FILE..."
 //usage:
 //usage:#define bc_full_usage "\n"
 //usage:     "\nArbitrary precision calculator"
 //usage:     "\n       -q      Quiet"
 //usage:     "\n       -w      Warn if extensions are used"
 ///////:     "\n       -v      Version"
+//usage:     "\n$BC_LINE_LENGTH changes output width"
 //usage:
 //usage:#define bc_example_usage
 //usage:       "3 + 4.129\n"
 //usage:#define dc_trivial_usage
 //usage:       "EXPRESSION..."
 //usage:
-//usage:#define dc_full_usage "\n\n"
-//usage:       "Tiny RPN calculator. Operations:\n"
-//usage:       "+, add, -, sub, *, mul, /, div, %, mod, ^, exp, ~, divmod, |, "
-//usage:       "modular exponentiation,\n"
-//usage:       "p - print top of the stack (without popping),\n"
-//usage:       "f - print entire stack,\n"
-//usage:       "k - pop the value and set the precision.\n"
-//usage:       "i - pop the value and set input radix.\n"
-//usage:       "o - pop the value and set output radix.\n"
-//usage:       "Examples: 'dc 2 2 add p' -> 4, 'dc 8 8 mul 2 2 + / p' -> 16"
+//usage:#define dc_full_usage "\n"
+//usage:     "\nTiny RPN calculator. Operations:"
+//usage:     "\n+, add, -, sub, *, mul, /, div, %, mod, ^, exp, ~, divmod, |, "
+//usage:       "modular exponentiation,"
+//usage:     "\np - print top of the stack (without popping),"
+//usage:     "\nf - print entire stack,"
+//usage:     "\nk - pop the value and set the precision."
+//usage:     "\ni - pop the value and set input radix."
+//usage:     "\no - pop the value and set output radix."
+//usage:     "\nExamples: 'dc 2 2 add p' -> 4, 'dc 8 8 mul 2 2 + / p' -> 16"
 //usage:
 //usage:#define dc_example_usage
 //usage:       "$ dc 2 2 + p\n"
 //usage:       "64\n"
 
 #include "libbb.h"
+#include "common_bufsiz.h"
+
+#if ENABLE_FEATURE_DC_SMALL
+# include "dc.c"
+#else
 
 typedef enum BcStatus {
        BC_STATUS_SUCCESS = 0,
@@ -186,9 +205,6 @@ typedef struct BcVec {
        BcVecFree dtor;
 } BcVec;
 
-#define bc_vec_pop(v) (bc_vec_npop((v), 1))
-#define bc_vec_top(v) (bc_vec_item_rev((v), 0))
-
 typedef signed char BcDig;
 
 typedef struct BcNum {
@@ -199,16 +215,17 @@ typedef struct BcNum {
        bool neg;
 } BcNum;
 
-#define BC_NUM_MIN_BASE ((unsigned long) 2)
-#define BC_NUM_MAX_IBASE ((unsigned long) 16)
-#define BC_NUM_DEF_SIZE (16)
-#define BC_NUM_PRINT_WIDTH (69)
+#define BC_NUM_MIN_BASE         ((unsigned long) 2)
+#define BC_NUM_MAX_IBASE        ((unsigned long) 16)
+// larger value might speed up BIGNUM calculations a bit:
+#define BC_NUM_DEF_SIZE         (16)
+#define BC_NUM_PRINT_WIDTH      (69)
 
-#define BC_NUM_KARATSUBA_LEN (32)
+#define BC_NUM_KARATSUBA_LEN    (32)
 
-#define BC_NUM_NEG(n, neg) ((((ssize_t)(n)) ^ -((ssize_t)(neg))) + (neg))
-#define BC_NUM_ONE(n) ((n)->len == 1 && (n)->rdx == 0 && (n)->num[0] == 1)
-#define BC_NUM_INT(n) ((n)->len - (n)->rdx)
+#define BC_NUM_NEG(n, neg)      ((((ssize_t)(n)) ^ -((ssize_t)(neg))) + (neg))
+#define BC_NUM_ONE(n)           ((n)->len == 1 && (n)->rdx == 0 && (n)->num[0] == 1)
+#define BC_NUM_INT(n)           ((n)->len - (n)->rdx)
 #define BC_NUM_AREQ(a, b) \
        (BC_MAX((a)->rdx, (b)->rdx) + BC_MAX(BC_NUM_INT(a), BC_NUM_INT(b)) + 1)
 #define BC_NUM_MREQ(a, b, scale) \
@@ -491,34 +508,35 @@ typedef enum BcLexType {
 struct BcLexKeyword {
        char name8[8];
 };
-#define BC_LEX_KW_ENTRY(a, b, c)            \
-       { .name8 = a /*, .len = b, .posix = c*/ }
+#define BC_LEX_KW_ENTRY(a, b) \
+       { .name8 = a /*, .posix = b */ }
 static const struct BcLexKeyword bc_lex_kws[20] = {
-       BC_LEX_KW_ENTRY("auto"    , 4, 1), // 0
-       BC_LEX_KW_ENTRY("break"   , 5, 1), // 1
-       BC_LEX_KW_ENTRY("continue", 8, 0), // 2 note: this one has no terminating NUL
-       BC_LEX_KW_ENTRY("define"  , 6, 1), // 3
-
-       BC_LEX_KW_ENTRY("else"    , 4, 0), // 4
-       BC_LEX_KW_ENTRY("for"     , 3, 1), // 5
-       BC_LEX_KW_ENTRY("halt"    , 4, 0), // 6
-       BC_LEX_KW_ENTRY("ibase"   , 5, 1), // 7
-
-       BC_LEX_KW_ENTRY("if"      , 2, 1), // 8
-       BC_LEX_KW_ENTRY("last"    , 4, 0), // 9
-       BC_LEX_KW_ENTRY("length"  , 6, 1), // 10
-       BC_LEX_KW_ENTRY("limits"  , 6, 0), // 11
-
-       BC_LEX_KW_ENTRY("obase"   , 5, 1), // 12
-       BC_LEX_KW_ENTRY("print"   , 5, 0), // 13
-       BC_LEX_KW_ENTRY("quit"    , 4, 1), // 14
-       BC_LEX_KW_ENTRY("read"    , 4, 0), // 15
-
-       BC_LEX_KW_ENTRY("return"  , 6, 1), // 16
-       BC_LEX_KW_ENTRY("scale"   , 5, 1), // 17
-       BC_LEX_KW_ENTRY("sqrt"    , 4, 1), // 18
-       BC_LEX_KW_ENTRY("while"   , 5, 1), // 19
+       BC_LEX_KW_ENTRY("auto"    , 1), // 0
+       BC_LEX_KW_ENTRY("break"   , 1), // 1
+       BC_LEX_KW_ENTRY("continue", 0), // 2 note: this one has no terminating NUL
+       BC_LEX_KW_ENTRY("define"  , 1), // 3
+
+       BC_LEX_KW_ENTRY("else"    , 0), // 4
+       BC_LEX_KW_ENTRY("for"     , 1), // 5
+       BC_LEX_KW_ENTRY("halt"    , 0), // 6
+       BC_LEX_KW_ENTRY("ibase"   , 1), // 7
+
+       BC_LEX_KW_ENTRY("if"      , 1), // 8
+       BC_LEX_KW_ENTRY("last"    , 0), // 9
+       BC_LEX_KW_ENTRY("length"  , 1), // 10
+       BC_LEX_KW_ENTRY("limits"  , 0), // 11
+
+       BC_LEX_KW_ENTRY("obase"   , 1), // 12
+       BC_LEX_KW_ENTRY("print"   , 0), // 13
+       BC_LEX_KW_ENTRY("quit"    , 1), // 14
+       BC_LEX_KW_ENTRY("read"    , 0), // 15
+
+       BC_LEX_KW_ENTRY("return"  , 1), // 16
+       BC_LEX_KW_ENTRY("scale"   , 1), // 17
+       BC_LEX_KW_ENTRY("sqrt"    , 1), // 18
+       BC_LEX_KW_ENTRY("while"   , 1), // 19
 };
+#undef BC_LEX_KW_ENTRY
 enum {
        POSIX_KWORD_MASK = 0
                | (1 << 0)
@@ -546,6 +564,7 @@ enum {
                | (1 << 18)
                | (1 << 19)
 };
+#define bc_lex_kws_POSIX(i) ((1 << (i)) & POSIX_KWORD_MASK)
 #endif
 
 struct BcLex;
@@ -571,15 +590,11 @@ typedef struct BcLex {
 
 #define BC_PARSE_STREND ((char) UCHAR_MAX)
 
-#define bc_parse_push(p, i) (bc_vec_pushByte(&(p)->func->code, (char) (i)))
-#define bc_parse_updateFunc(p, f) \
-       ((p)->func = bc_vec_item(&G.prog.fns, ((p)->fidx = (f))))
-
-#define BC_PARSE_REL (1 << 0)
-#define BC_PARSE_PRINT (1 << 1)
+#define BC_PARSE_REL    (1 << 0)
+#define BC_PARSE_PRINT  (1 << 1)
 #define BC_PARSE_NOCALL (1 << 2)
 #define BC_PARSE_NOREAD (1 << 3)
-#define BC_PARSE_ARRAY (1 << 4)
+#define BC_PARSE_ARRAY  (1 << 4)
 
 #define BC_PARSE_TOP_FLAG_PTR(parse) ((uint8_t *) bc_vec_top(&(parse)->flags))
 #define BC_PARSE_TOP_FLAG(parse) (*(BC_PARSE_TOP_FLAG_PTR(parse)))
@@ -621,12 +636,6 @@ typedef struct BcParseNext {
        BcLexType tokens[4];
 } BcParseNext;
 
-#define BC_PARSE_NEXT_TOKENS(...) .tokens = { __VA_ARGS__ }
-#define BC_PARSE_NEXT(a, ...)                         \
-       {                                                 \
-               .len = (a), BC_PARSE_NEXT_TOKENS(__VA_ARGS__) \
-       }
-
 struct BcParse;
 
 struct BcProgram;
@@ -699,7 +708,6 @@ typedef struct BcProgram {
 
 #define BC_PROG_MAIN (0)
 #define BC_PROG_READ (1)
-
 #if ENABLE_DC
 #define BC_PROG_REQ_FUNCS (2)
 #endif
@@ -710,9 +718,6 @@ typedef struct BcProgram {
 
 typedef unsigned long (*BcProgramBuiltIn)(BcNum *);
 
-static void bc_program_addFunc(char *name, size_t *idx);
-static void bc_program_reset(void);
-
 #define BC_FLAG_X (1 << 0)
 #define BC_FLAG_W (1 << 1)
 #define BC_FLAG_V (1 << 2)
@@ -734,7 +739,7 @@ static void bc_program_reset(void);
 #define BC_MAX_VARS   ((unsigned long) SIZE_MAX - 1)
 
 struct globals {
-       smallint ttyin;
+       IF_FEATURE_BC_SIGNALS(smallint ttyin;)
        smallint eof;
        char sbgn;
        char send;
@@ -749,6 +754,10 @@ struct globals {
        BcVec files;
 
        char *env_args;
+
+#if ENABLE_FEATURE_EDITING
+       line_input_t *line_input_state;
+#endif
 } FIX_ALIASING;
 #define G (*ptr_to_globals)
 #define INIT_G() do { \
@@ -761,7 +770,11 @@ struct globals {
 #define G_warn  (ENABLE_BC && (option_mask32 & BC_FLAG_W))
 #define G_exreg (ENABLE_DC && (option_mask32 & BC_FLAG_X))
 #define G_interrupt (ENABLE_FEATURE_BC_SIGNALS ? bb_got_signal : 0)
-
+#if ENABLE_FEATURE_BC_SIGNALS
+# define G_ttyin G.ttyin
+#else
+# define G_ttyin 0
+#endif
 #define IS_BC (ENABLE_BC && (!ENABLE_DC || applet_name[0] == 'b'))
 
 #if ENABLE_BC
@@ -814,6 +827,11 @@ static const uint8_t bc_parse_ops[] = {
 #define bc_parse_op_LEFT(i) (bc_parse_ops[i] & 0x10)
 
 // These identify what tokens can come after expressions in certain cases.
+#define BC_PARSE_NEXT_TOKENS(...) .tokens = { __VA_ARGS__ }
+#define BC_PARSE_NEXT(a, ...)                         \
+       {                                                 \
+               .len = (a), BC_PARSE_NEXT_TOKENS(__VA_ARGS__) \
+       }
 static const BcParseNext bc_parse_next_expr =
        BC_PARSE_NEXT(4, BC_LEX_NLINE, BC_LEX_SCOLON, BC_LEX_RBRACE, BC_LEX_EOF);
 static const BcParseNext bc_parse_next_param =
@@ -904,7 +922,7 @@ static void fflush_and_check(void)
 #if ENABLE_FEATURE_CLEAN_UP
 #define quit_or_return_for_exit() \
 do { \
-       G.ttyin = 0; /* do not loop in main loop anymore */ \
+       IF_FEATURE_BC_SIGNALS(G_ttyin = 0;) /* do not loop in main loop anymore */ \
        return BC_STATUS_FAILURE; \
 } while (0)
 #else
@@ -942,11 +960,12 @@ static NOINLINE int bc_error_fmt(const char *fmt, ...)
        bc_verror_msg(fmt, p);
        va_end(p);
 
-       if (!ENABLE_FEATURE_CLEAN_UP && !G.ttyin)
+       if (!ENABLE_FEATURE_CLEAN_UP && !G_ttyin)
                exit(1);
        return BC_STATUS_FAILURE;
 }
 
+#if ENABLE_BC
 static NOINLINE int bc_posix_error_fmt(const char *fmt, ...)
 {
        va_list p;
@@ -962,23 +981,25 @@ static NOINLINE int bc_posix_error_fmt(const char *fmt, ...)
        // Do we treat non-POSIX constructs as errors?
        if (!(option_mask32 & BC_FLAG_S))
                return BC_STATUS_SUCCESS; // no, it's a warning
-       if (!ENABLE_FEATURE_CLEAN_UP && !G.ttyin)
+       if (!ENABLE_FEATURE_CLEAN_UP && !G_ttyin)
                exit(1);
        return BC_STATUS_FAILURE;
 }
+#endif
 
 // We use error functions with "return bc_error(FMT[, PARAMS])" idiom.
 // This idiom begs for tail-call optimization, but for it to work,
-// function must not have calller-cleaned parameters on stack.
-// Unfortunately, vararg functions do exactly that on most arches.
-// Thus, these shims for the cases when we have no PARAMS:
+// function must not have caller-cleaned parameters on stack.
+// Unfortunately, vararg function API does exactly that on most arches.
+// Thus, use these shims for the cases when we have no vararg PARAMS:
 static int bc_error(const char *msg)
 {
        return bc_error_fmt("%s", msg);
 }
-static int bc_posix_error(const char *msg)
+#if ENABLE_BC
+static int bc_POSIX_requires(const char *msg)
 {
-       return bc_posix_error_fmt("%s", msg);
+       return bc_posix_error_fmt("POSIX requires %s", msg);
 }
 static int bc_POSIX_does_not_allow(const char *msg)
 {
@@ -992,6 +1013,7 @@ static int bc_POSIX_does_not_allow_empty_X_expression_in_for(const char *msg)
 {
        return bc_posix_error_fmt("%san empty %s expression in a for loop", "POSIX does not allow ", msg);
 }
+#endif
 static int bc_error_bad_character(char c)
 {
        return bc_error_fmt("bad character '%c'", c);
@@ -1047,6 +1069,13 @@ static void bc_vec_expand(BcVec *v, size_t req)
        }
 }
 
+static void bc_vec_pop(BcVec *v)
+{
+       v->len--;
+       if (v->dtor)
+               v->dtor(v->v + (v->size * v->len));
+}
+
 static void bc_vec_npop(BcVec *v, size_t n)
 {
        if (!v->dtor)
@@ -1117,7 +1146,7 @@ static void bc_vec_concat(BcVec *v, const char *str)
        len = v->len + strlen(str);
 
        if (v->cap < len) bc_vec_grow(v, len - v->len);
-       strcat(v->v, str);
+       strcpy(v->v + v->len - 1, str);
 
        v->len = len;
 }
@@ -1132,6 +1161,11 @@ static void *bc_vec_item_rev(const BcVec *v, size_t idx)
        return v->v + v->size * (v->len - idx - 1);
 }
 
+static void *bc_vec_top(const BcVec *v)
+{
+       return v->v + v->size * (v->len - 1);
+}
+
 static void bc_vec_free(void *vec)
 {
        BcVec *v = (BcVec *) vec;
@@ -1183,24 +1217,42 @@ 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_index(const BcVec *v, const void *ptr)
 {
        size_t i = bc_map_find(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 int push_input_byte(BcVec *vec, char c)
+{
+       if ((c < ' ' && c != '\t' && c != '\r' && c != '\n') // also allow '\v' '\f'?
+        || c > 0x7e
+       ) {
+               // Bad chars on this line, ignore entire line
+               bc_error_fmt("illegal character 0x%02x", c);
+               return 1;
+       }
+       bc_vec_pushByte(vec, (char)c);
+       return 0;
+}
 
 static BcStatus bc_read_line(BcVec *vec, const char *prompt)
 {
        bool bad_chars;
 
+       if (G_posix) prompt = "";
+
        do {
-               int i;
+               int c;
 
                bad_chars = 0;
                bc_vec_pop_all(vec);
 
                fflush_and_check();
+
 #if ENABLE_FEATURE_BC_SIGNALS
                if (bb_got_signal) { // ^C was pressed
  intr:
@@ -1210,17 +1262,34 @@ static BcStatus bc_read_line(BcVec *vec, const char *prompt)
                                : "\ninterrupt (type \"q\" to exit)\n"
                                , stderr);
                }
+# if ENABLE_FEATURE_EDITING
+               if (G_ttyin) {
+                       int n, i;
+#  define line_buf bb_common_bufsiz1
+                       n = read_line_input(G.line_input_state, prompt, line_buf, COMMON_BUFSIZE);
+                       if (n <= 0) { // read errors or EOF, or ^D, or ^C
+                               if (n == 0) // ^C
+                                       goto intr;
+                               G.eof = 1;
+                               break;
+                       }
+                       i = 0;
+                       for (;;) {
+                               c = line_buf[i++];
+                               if (!c) break;
+                               bad_chars |= push_input_byte(vec, c);
+                       }
+#  undef line_buf
+               } else
+# endif
 #endif
-               if (G.ttyin && !G_posix)
-                       fputs(prompt, stderr);
-
-#if ENABLE_FEATURE_BC_SIGNALS
-               errno = 0;
-#endif
-               do {
-                       i = fgetc(stdin);
-                       if (i == EOF) {
-#if ENABLE_FEATURE_BC_SIGNALS
+               {
+                       if (G_ttyin)
+                               fputs(prompt, stderr);
+                       IF_FEATURE_BC_SIGNALS(errno = 0;)
+                       do {
+                               c = fgetc(stdin);
+#if ENABLE_FEATURE_BC_SIGNALS && !ENABLE_FEATURE_EDITING
                                // Both conditions appear simultaneously, check both just in case
                                if (errno == EINTR || bb_got_signal) {
                                        // ^C was pressed
@@ -1228,24 +1297,18 @@ static BcStatus bc_read_line(BcVec *vec, const char *prompt)
                                        goto intr;
                                }
 #endif
-                               if (ferror(stdin))
-                                       quit(); // this emits error message
-                               G.eof = 1;
-                               // Note: EOF does not append '\n', therefore:
-                               // printf 'print 123\n' | bc - works
-                               // printf 'print 123' | bc   - fails (syntax error)
-                               break;
-                       }
-
-                       if ((i < ' ' && i != '\t' && i != '\r' && i != '\n') // also allow '\v' '\f'?
-                        || i > 0x7e
-                       ) {
-                               // Bad chars on this line, ignore entire line
-                               bc_error_fmt("illegal character 0x%02x", i);
-                               bad_chars = 1;
-                       }
-                       bc_vec_pushByte(vec, (char)i);
-               } while (i != '\n');
+                               if (c == EOF) {
+                                       if (ferror(stdin))
+                                               quit(); // this emits error message
+                                       G.eof = 1;
+                                       // Note: EOF does not append '\n', therefore:
+                                       // printf 'print 123\n' | bc - works
+                                       // printf 'print 123' | bc   - fails (syntax error)
+                                       break;
+                               }
+                               bad_chars |= push_input_byte(vec, c);
+                       } while (c != '\n');
+               }
        } while (bad_chars);
 
        bc_vec_pushZeroByte(vec);
@@ -1259,7 +1322,8 @@ static char* bc_read_file(const char *path)
        size_t size = ((size_t) -1);
        size_t i;
 
-       buf = xmalloc_open_read_close(path, &size);
+       // Never returns NULL (dies on errors)
+       buf = xmalloc_xopen_read_close(path, &size);
 
        for (i = 0; i < size; ++i) {
                char c = buf[i];
@@ -2619,6 +2683,7 @@ err:
 }
 #endif // ENABLE_DC
 
+#if ENABLE_BC
 static BcStatus bc_func_insert(BcFunc *f, char *name, bool var)
 {
        BcId a;
@@ -2636,6 +2701,7 @@ static BcStatus bc_func_insert(BcFunc *f, char *name, bool var)
 
        return BC_STATUS_SUCCESS;
 }
+#endif
 
 static void bc_func_init(BcFunc *f)
 {
@@ -2926,7 +2992,7 @@ static BcStatus bc_lex_identifier(BcLex *l)
  match:
                // buf starts with keyword bc_lex_kws[i]
                l->t.t = BC_LEX_KEY_1st_keyword + i;
-               if (!((1 << i) & POSIX_KWORD_MASK)) {
+               if (!bc_lex_kws_POSIX(i)) {
                        s = bc_posix_error_fmt("%sthe '%.8s' keyword", "POSIX does not allow ", bc_lex_kws[i].name8);
                        if (s) return s;
                }
@@ -3314,7 +3380,7 @@ static BcStatus dc_lex_register(BcLex *l)
        }
        else {
                bc_vec_pop_all(&l->t.v);
-               bc_vec_pushByte(&l->t.v, l->buf[l->i - 1]);
+               bc_vec_push(&l->t.v, &l->buf[l->i - 1]);
                bc_vec_pushZeroByte(&l->t.v);
                l->t.t = BC_LEX_NAME;
        }
@@ -3464,12 +3530,16 @@ static BcStatus dc_lex_token(BcLex *l)
 }
 #endif // ENABLE_DC
 
+static void bc_program_addFunc(char *name, size_t *idx);
+
 static void bc_parse_addFunc(BcParse *p, char *name, size_t *idx)
 {
        bc_program_addFunc(name, idx);
        p->func = bc_vec_item(&G.prog.fns, p->fidx);
 }
 
+#define bc_parse_push(p, i) bc_vec_pushByte(&(p)->func->code, (char) (i))
+
 static void bc_parse_pushName(BcParse *p, char *name)
 {
        size_t i = 0, len = strlen(name);
@@ -3524,6 +3594,24 @@ static BcStatus bc_parse_text(BcParse *p, const char *text)
        return bc_lex_text(&p->l, text);
 }
 
+// Called when parsing or execution detects a failure,
+// resets execution structures.
+static void bc_program_reset(void)
+{
+       BcFunc *f;
+       BcInstPtr *ip;
+
+       bc_vec_npop(&G.prog.stack, G.prog.stack.len - 1);
+       bc_vec_pop_all(&G.prog.results);
+
+       f = bc_vec_item(&G.prog.fns, 0);
+       ip = bc_vec_top(&G.prog.stack);
+       ip->idx = f->code.len;
+}
+
+#define bc_parse_updateFunc(p, f) \
+       ((p)->func = bc_vec_item(&G.prog.fns, ((p)->fidx = (f))))
+
 // Called when bc/dc_parse_parse() detects a failure,
 // resets parsing structures.
 static void bc_parse_reset(BcParse *p)
@@ -4010,7 +4098,7 @@ static BcStatus bc_parse_return(BcParse *p)
                if (s) return s;
 
                if (!paren || p->l.t.last != BC_LEX_RPAREN) {
-                       s = bc_posix_error("POSIX requires parentheses around return expressions");
+                       s = bc_POSIX_requires("parentheses around return expressions");
                        if (s) return s;
                }
 
@@ -4379,7 +4467,7 @@ static BcStatus bc_parse_func(BcParse *p)
        if (s) return s;
 
        if (p->l.t.t != BC_LEX_LBRACE)
-               s = bc_posix_error("POSIX requires the left brace be on the same line as the function header");
+               s = bc_POSIX_requires("the left brace be on the same line as the function header");
 
        return s;
 
@@ -4907,7 +4995,7 @@ static BcStatus bc_parse_expr_empty_ok(BcParse *p, uint8_t flags, BcParseNext ne
                if (s) return s;
        }
        else if ((flags & BC_PARSE_REL) && nrelops > 1) {
-               s = bc_posix_error("POSIX requires exactly one comparison operator per condition");
+               s = bc_POSIX_requires("exactly one comparison operator per condition");
                if (s) return s;
        }
 
@@ -5173,18 +5261,18 @@ static void dc_parse_init(BcParse *p, size_t func)
 static void common_parse_init(BcParse *p, size_t func)
 {
        if (IS_BC) {
-               bc_parse_init(p, func);
+               IF_BC(bc_parse_init(p, func);)
        } else {
-               dc_parse_init(p, func);
+               IF_DC(dc_parse_init(p, func);)
        }
 }
 
 static BcStatus common_parse_expr(BcParse *p, uint8_t flags)
 {
        if (IS_BC) {
-               return bc_parse_expression(p, flags);
+               IF_BC(return bc_parse_expression(p, flags);)
        } else {
-               return dc_parse_expr(p, flags);
+               IF_DC(return dc_parse_expr(p, flags);)
        }
 }
 
@@ -6486,24 +6574,6 @@ static void bc_program_addFunc(char *name, size_t *idx)
        }
 }
 
-// Called when parsing or execution detects a failure,
-// resets execution structures.
-static void bc_program_reset(void)
-{
-       BcFunc *f;
-       BcInstPtr *ip;
-
-       bc_vec_npop(&G.prog.stack, G.prog.stack.len - 1);
-       bc_vec_pop_all(&G.prog.results);
-
-       f = bc_vec_item(&G.prog.fns, 0);
-       ip = bc_vec_top(&G.prog.stack);
-       ip->idx = f->code.len;
-
-       // If !tty, no need to check for ^C: we don't have ^C handler,
-       // we would be killed by a signal and won't reach this place
-}
-
 static BcStatus bc_program_exec(void)
 {
        BcStatus s = BC_STATUS_SUCCESS;
@@ -7034,7 +7104,7 @@ static BcStatus bc_vm_stdin(void)
                bc_vec_concat(&buffer, buf.v);
                s = bc_vm_process(buffer.v);
                if (s) {
-                       if (ENABLE_FEATURE_CLEAN_UP && !G.ttyin) {
+                       if (ENABLE_FEATURE_CLEAN_UP && !G_ttyin) {
                                // Debug config, non-interactive mode:
                                // return all the way back to main.
                                // Non-debug builds do not come here, they exit.
@@ -7263,7 +7333,7 @@ static BcStatus bc_vm_exec(void)
        for (i = 0; !s && i < G.files.len; ++i)
                s = bc_vm_file(*((char **) bc_vec_item(&G.files, i)));
        if (s) {
-               if (ENABLE_FEATURE_CLEAN_UP && !G.ttyin) {
+               if (ENABLE_FEATURE_CLEAN_UP && !G_ttyin) {
                        // Debug config, non-interactive mode:
                        // return all the way back to main.
                        // Non-debug builds do not come here, they exit.
@@ -7373,12 +7443,12 @@ static void bc_vm_init(void)
 {
        bc_vec_init(&G.files, sizeof(char *), NULL);
        if (IS_BC)
-               bc_vm_envArgs();
+               IF_BC(bc_vm_envArgs();)
        bc_program_init();
        if (IS_BC) {
-               bc_parse_init(&G.prs, BC_PROG_MAIN);
+               IF_BC(bc_parse_init(&G.prs, BC_PROG_MAIN);)
        } else {
-               dc_parse_init(&G.prs, BC_PROG_MAIN);
+               IF_DC(dc_parse_init(&G.prs, BC_PROG_MAIN);)
        }
 }
 
@@ -7386,14 +7456,17 @@ static BcStatus bc_vm_run(char **argv, const char *env_len)
 {
        BcStatus st;
 
+#if ENABLE_FEATURE_EDITING
+       G.line_input_state = new_line_input_t(DO_HISTORY);
+#endif
        G.prog.len = bc_vm_envLen(env_len);
 
        bc_vm_init();
        bc_args(argv);
 
-       G.ttyin = isatty(0);
-       if (G.ttyin) {
+       if (isatty(0)) {
 #if ENABLE_FEATURE_BC_SIGNALS
+               G_ttyin = 1;
                // With SA_RESTART, most system calls will restart
                // (IOW: they won't fail with EINTR).
                // In particular, this means ^C won't cause
@@ -7420,6 +7493,9 @@ static BcStatus bc_vm_run(char **argv, const char *env_len)
 
 #if ENABLE_FEATURE_CLEAN_UP
        bc_vm_free();
+# if ENABLE_FEATURE_EDITING
+       free_line_input_t(G.line_input_state);
+# endif
        FREE_G();
 #endif
        return st;
@@ -7447,3 +7523,5 @@ int dc_main(int argc UNUSED_PARAM, char **argv)
        return bc_vm_run(argv, "DC_LINE_LENGTH");
 }
 #endif
+
+#endif // not DC_SMALL