bc: use only G_interrupt as interrupt flag
[oweals/busybox.git] / miscutils / bc.c
index 6cc9fe2f749f2f46f02e1dcac5ba833641b7a737..0330c43e567e59c0dfd4a06d2355fe3a27c0e459 100644 (file)
 //usage:#define bc_full_usage "\n"
 //usage:     "\nArbitrary precision calculator"
 //usage:     "\n"
-//usage:     "\n       -i      Interactive"
+///////:     "\n       -i      Interactive" - has no effect for now
+//usage:     "\n       -q      Quiet"
 //usage:     "\n       -l      Load standard math library"
 //usage:     "\n       -s      Be POSIX compatible"
-//usage:     "\n       -q      Quiet"
 //usage:     "\n       -w      Warn if extensions are used"
 ///////:     "\n       -v      Version"
+//usage:     "\n"
 //usage:     "\n$BC_LINE_LENGTH changes output width"
 //usage:
 //usage:#define bc_example_usage
 //usage:       "obase = A\n"
 //usage:
 //usage:#define dc_trivial_usage
-//usage:       "EXPRESSION..."
+//usage:       "[-eSCRIPT]... [-fFILE]... [FILE]..."
 //usage:
 //usage:#define dc_full_usage "\n"
 //usage:     "\nTiny RPN calculator. Operations:"
-//usage:     "\n+, add, -, sub, *, mul, /, div, %, mod, ^, exp, ~, divmod, |, "
+//usage:     "\n+, -, *, /, %, ^, 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:     "\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 -e'2 2 + p' -> 4, dc -e'8 8 * 2 2 + / p' -> 16"
 //usage:
 //usage:#define dc_example_usage
-//usage:       "$ dc 2 2 + p\n"
+//usage:       "$ dc -e'2 2 + p'\n"
 //usage:       "4\n"
-//usage:       "$ dc 8 8 \\* 2 2 + / p\n"
+//usage:       "$ dc -e'8 8 \\* 2 2 + / p'\n"
 //usage:       "16\n"
-//usage:       "$ dc 0 1 and p\n"
+//usage:       "$ dc -e'0 1 & p'\n"
 //usage:       "0\n"
-//usage:       "$ dc 0 1 or p\n"
+//usage:       "$ dc -e'0 1 | p'\n"
 //usage:       "1\n"
-//usage:       "$ echo 72 9 div 8 mul p | dc\n"
+//usage:       "$ echo '72 9 / 8 * p' | dc\n"
 //usage:       "64\n"
 
 #include "libbb.h"
@@ -508,34 +509,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)
@@ -563,6 +565,7 @@ enum {
                | (1 << 18)
                | (1 << 19)
 };
+#define bc_lex_kws_POSIX(i) ((1 << (i)) & POSIX_KWORD_MASK)
 #endif
 
 struct BcLex;
@@ -716,13 +719,13 @@ typedef struct BcProgram {
 
 typedef unsigned long (*BcProgramBuiltIn)(BcNum *);
 
-#define BC_FLAG_X (1 << 0)
-#define BC_FLAG_W (1 << 1)
-#define BC_FLAG_V (1 << 2)
-#define BC_FLAG_S (1 << 3)
-#define BC_FLAG_Q (1 << 4)
-#define BC_FLAG_L (1 << 5)
-#define BC_FLAG_I (1 << 6)
+#define BC_FLAG_W (1 << 0)
+#define BC_FLAG_V (1 << 1)
+#define BC_FLAG_S (1 << 2)
+#define BC_FLAG_Q (1 << 3)
+#define BC_FLAG_L (1 << 4)
+#define BC_FLAG_I (1 << 5)
+#define DC_FLAG_X (1 << 6)
 
 #define BC_MAX(a, b) ((a) > (b) ? (a) : (b))
 #define BC_MIN(a, b) ((a) < (b) ? (a) : (b))
@@ -738,6 +741,7 @@ typedef unsigned long (*BcProgramBuiltIn)(BcNum *);
 
 struct globals {
        IF_FEATURE_BC_SIGNALS(smallint ttyin;)
+       IF_FEATURE_CLEAN_UP(smallint exiting;)
        smallint eof;
        char sbgn;
        char send;
@@ -766,12 +770,18 @@ struct globals {
 } while (0)
 #define G_posix (ENABLE_BC && (option_mask32 & BC_FLAG_S))
 #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)
+#define G_exreg (ENABLE_DC && (option_mask32 & DC_FLAG_X))
 #if ENABLE_FEATURE_BC_SIGNALS
-# define G_ttyin G.ttyin
+# define G_interrupt bb_got_signal
+# define G_ttyin     G.ttyin
 #else
-# define G_ttyin 0
+# define G_interrupt 0
+# define G_ttyin     0
+#endif
+#if ENABLE_FEATURE_CLEAN_UP
+# define G_exiting G.exiting
+#else
+# define G_exiting 0
 #endif
 #define IS_BC (ENABLE_BC && (!ENABLE_DC || applet_name[0] == 'b'))
 
@@ -918,13 +928,14 @@ static void fflush_and_check(void)
 }
 
 #if ENABLE_FEATURE_CLEAN_UP
-#define quit_or_return_for_exit() \
+#define QUIT_OR_RETURN_TO_MAIN \
 do { \
        IF_FEATURE_BC_SIGNALS(G_ttyin = 0;) /* do not loop in main loop anymore */ \
+       G_exiting = 1; \
        return BC_STATUS_FAILURE; \
 } while (0)
 #else
-#define quit_or_return_for_exit() quit()
+#define QUIT_OR_RETURN_TO_MAIN quit()
 #endif
 
 static void quit(void) NORETURN;
@@ -1067,8 +1078,12 @@ static void bc_vec_expand(BcVec *v, size_t req)
        }
 }
 
-#define bc_vec_pop(v) (bc_vec_npop((v), 1))
-#define bc_vec_top(v) (bc_vec_item_rev((v), 0))
+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)
 {
@@ -1092,8 +1107,6 @@ static void bc_vec_push(BcVec *v, const void *data)
        v->len += 1;
 }
 
-#define bc_parse_push(p, i) bc_vec_pushByte(&(p)->func->code, (char) (i))
-
 static void bc_vec_pushByte(BcVec *v, char data)
 {
        bc_vec_push(v, &data);
@@ -1157,6 +1170,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;
@@ -1245,9 +1263,9 @@ static BcStatus bc_read_line(BcVec *vec, const char *prompt)
                fflush_and_check();
 
 #if ENABLE_FEATURE_BC_SIGNALS
-               if (bb_got_signal) { // ^C was pressed
+               if (G_interrupt) { // ^C was pressed
  intr:
-                       bb_got_signal = 0; // resets G_interrupt to zero
+                       G_interrupt = 0;
                        fputs(IS_BC
                                ? "\ninterrupt (type \"quit\" to exit)\n"
                                : "\ninterrupt (type \"q\" to exit)\n"
@@ -1282,7 +1300,7 @@ static BcStatus bc_read_line(BcVec *vec, const char *prompt)
                                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) {
+                               if (errno == EINTR || G_interrupt) {
                                        // ^C was pressed
                                        clearerr(stdin);
                                        goto intr;
@@ -1313,7 +1331,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];
@@ -2982,7 +3001,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;
                }
@@ -3528,6 +3547,8 @@ static void bc_parse_addFunc(BcParse *p, char *name, size_t *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);
@@ -4695,7 +4716,7 @@ static BcStatus bc_parse_stmt(BcParse *p)
                        // "quit" is a compile-time command. For example,
                        // "if (0 == 1) quit" terminates when parsing the statement,
                        // not when it is executed
-                       quit_or_return_for_exit();
+                       QUIT_OR_RETURN_TO_MAIN;
                }
 
                case BC_LEX_KEY_RETURN:
@@ -6398,9 +6419,7 @@ static BcStatus bc_program_nquit(void)
        if (G.prog.stack.len < val)
                return bc_error_stack_has_too_few_elements();
        if (G.prog.stack.len == val) {
-               if (ENABLE_FEATURE_CLEAN_UP)
-                       return BC_STATUS_FAILURE;
-               quit();
+               QUIT_OR_RETURN_TO_MAIN;
        }
 
        bc_vec_npop(&G.prog.stack, val);
@@ -6614,7 +6633,7 @@ static BcStatus bc_program_exec(void)
 
                        case BC_INST_HALT:
                        {
-                               quit_or_return_for_exit();
+                               QUIT_OR_RETURN_TO_MAIN;
                                break;
                        }
 
@@ -6859,7 +6878,7 @@ static BcStatus bc_program_exec(void)
                        case BC_INST_QUIT:
                        {
                                if (G.prog.stack.len <= 2)
-                                       quit_or_return_for_exit();
+                                       QUIT_OR_RETURN_TO_MAIN;
                                bc_vec_npop(&G.prog.stack, 2);
                                break;
                        }
@@ -6886,6 +6905,7 @@ static BcStatus bc_program_exec(void)
        return s;
 }
 
+#if ENABLE_BC
 static void bc_vm_info(void)
 {
        printf("%s "BB_VER"\n"
@@ -6902,8 +6922,7 @@ static void bc_args(char **argv)
 
        GETOPT_RESET();
 #if ENABLE_FEATURE_BC_LONG_OPTIONS
-       opts = option_mask32 |= getopt32long(argv, "xwvsqli",
-               "extended-register\0" No_argument "x"
+       opts = option_mask32 |= getopt32long(argv, "wvsqli",
                "warn\0"              No_argument "w"
                "version\0"           No_argument "v"
                "standard\0"          No_argument "s"
@@ -6912,7 +6931,7 @@ static void bc_args(char **argv)
                "interactive\0"       No_argument "i"
        );
 #else
-       opts = option_mask32 |= getopt32(argv, "xwvsqli");
+       opts = option_mask32 |= getopt32(argv, "wvsqli");
 #endif
        if (getenv("POSIXLY_CORRECT"))
                option_mask32 |= BC_FLAG_S;
@@ -6927,7 +6946,6 @@ static void bc_args(char **argv)
                bc_vec_push(&G.files, argv + i);
 }
 
-#if ENABLE_BC
 static void bc_vm_envArgs(void)
 {
        BcVec v;
@@ -7296,7 +7314,7 @@ static const char bc_lib[] = {
 
 static BcStatus bc_vm_exec(void)
 {
-       BcStatus s = BC_STATUS_SUCCESS;
+       BcStatus s;
        size_t i;
 
 #if ENABLE_BC
@@ -7318,21 +7336,24 @@ static BcStatus bc_vm_exec(void)
        }
 #endif
 
+       s = BC_STATUS_SUCCESS;
        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) {
-                       // Debug config, non-interactive mode:
-                       // return all the way back to main.
-                       // Non-debug builds do not come here, they exit.
-                       return s;
-               }
-               fflush_and_check();
-               fputs("ready for more input\n", stderr);
+       if (ENABLE_FEATURE_CLEAN_UP && s && !G_ttyin) {
+               // Debug config, non-interactive mode:
+               // return all the way back to main.
+               // Non-debug builds do not come here, they exit.
+               return s;
        }
 
-       if (IS_BC || !G.files.len)
+       if (IS_BC || (option_mask32 & BC_FLAG_I)) {
+               if (s) {
+                       fflush_and_check();
+                       fputs("ready for more input\n", stderr);
+               }
                s = bc_vm_stdin();
+       }
+
        if (!s && !BC_PARSE_CAN_EXEC(&G.prs))
                s = bc_vm_process("");
 
@@ -7427,8 +7448,13 @@ static void bc_program_init(void)
        bc_vec_push(&G.prog.stack, &ip);
 }
 
-static void bc_vm_init(void)
+static int bc_vm_init(const char *env_len)
 {
+#if ENABLE_FEATURE_EDITING
+       G.line_input_state = new_line_input_t(DO_HISTORY);
+#endif
+       G.prog.len = bc_vm_envLen(env_len);
+
        bc_vec_init(&G.files, sizeof(char *), NULL);
        if (IS_BC)
                IF_BC(bc_vm_envArgs();)
@@ -7438,19 +7464,6 @@ static void bc_vm_init(void)
        } else {
                IF_DC(dc_parse_init(&G.prs, BC_PROG_MAIN);)
        }
-}
-
-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);
 
        if (isatty(0)) {
 #if ENABLE_FEATURE_BC_SIGNALS
@@ -7473,13 +7486,17 @@ static BcStatus bc_vm_run(char **argv, const char *env_len)
                // and exit.
                //signal_no_SA_RESTART_empty_mask(SIGINT, record_signo);
 #endif
-               if (!(option_mask32 & BC_FLAG_Q))
-                       bc_vm_info();
+               return 1; // "tty"
        }
+       return 0; // "not a tty"
+}
 
-       st = bc_vm_exec();
-
+static BcStatus bc_vm_run(void)
+{
+       BcStatus st = bc_vm_exec();
 #if ENABLE_FEATURE_CLEAN_UP
+       if (G_exiting) // it was actually "halt" or "quit"
+               st = EXIT_SUCCESS;
        bc_vm_free();
 # if ENABLE_FEATURE_EDITING
        free_line_input_t(G.line_input_state);
@@ -7493,10 +7510,19 @@ static BcStatus bc_vm_run(char **argv, const char *env_len)
 int bc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int bc_main(int argc UNUSED_PARAM, char **argv)
 {
+       int is_tty;
+
        INIT_G();
        G.sbgn = G.send = '"';
 
-       return bc_vm_run(argv, "BC_LINE_LENGTH");
+       is_tty = bc_vm_init("BC_LINE_LENGTH");
+
+       bc_args(argv);
+
+       if (is_tty && !(option_mask32 & BC_FLAG_Q))
+               bc_vm_info();
+
+       return bc_vm_run();
 }
 #endif
 
@@ -7504,11 +7530,48 @@ int bc_main(int argc UNUSED_PARAM, char **argv)
 int dc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int dc_main(int argc UNUSED_PARAM, char **argv)
 {
+       int noscript;
+
        INIT_G();
        G.sbgn = '[';
        G.send = ']';
+       // TODO: dc (GNU bc 1.07.1) 1.4.1 seems to use default width
+       // 1 char narrower than bc from the same package. Do the same?
+       bc_vm_init("DC_LINE_LENGTH");
+
+       // Run -e'SCRIPT' and -fFILE in order of appearance, then handle FILEs
+       noscript = BC_FLAG_I;
+       for (;;) {
+               int n = getopt(argc, argv, "e:f:x");
+               if (n <= 0)
+                       break;
+               switch (n) {
+               case 'e':
+                       noscript = 0;
+                       n = bc_vm_process(optarg);
+                       if (n) return n;
+                       break;
+               case 'f':
+                       noscript = 0;
+                       bc_vm_file(optarg);
+                       break;
+               case 'x':
+                       option_mask32 |= DC_FLAG_X;
+                       break;
+               default:
+                       bb_show_usage();
+               }
+       }
+       argv += optind;
+
+       while (*argv) {
+               noscript = 0;
+               bc_vec_push(&G.files, argv++);
+       }
+
+       option_mask32 |= noscript; // set BC_FLAG_I if we need to interpret stdin
 
-       return bc_vm_run(argv, "DC_LINE_LENGTH");
+       return bc_vm_run();
 }
 #endif