|
- /* dc.c - desktop calculator implementation
-
- Public domain.
-
- -- Printing --
-
- p - print top
- n - print pop no newline
- f - print all
-
- -- Arithmetic --
-
- n1 is the first popped value, and so on.
-
- +/-*% such that n1 OP n2, pushes 1 result
- ^ n2 to the power of n1.
- v n1's square root
-
- -- Stack --
-
- c clear
- d duplicate
- r reverse n1 and n2
- R rotates top n1 items
- Z pushes n1's digit count
- X pushes n1's fraction digit count
- z pushes top
-
- -- Others --
-
- b set the operational base, does not alter output
- q quit
- # a comment.
-
- -- currently omitted --
-
- marcos, strings, and registers.
-
- TODO
-
- Fix ns_expand
- Fix CLI to act the same as GNU dc? place original code under DC_COMPLY &
- implement unified stackframe option
- Implement registers
- Implement strings/macros
- */
-
- #include <assert.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <math.h>
-
- #include <readline/readline.h>
- #include <readline/history.h>
-
- #include <gmp.h>
-
- #ifndef PROGN
- # define PROGN "dc"
- #endif
-
- #include "ns.c"
- #include "slurp.c"
-
- #define AUTHOR "Emil Williams"
- #define VERSION_STRING "3.0"
- #define DC_EXIT (2 << 5)
-
- static int
- isnum(char c)
- {
- switch (c)
- {
- case '.':
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- return 1;
- default:
- return 0;
- }
- }
-
- static size_t
- getnum(ns_t * s, char * eval, size_t len, int base)
- {
- size_t i = 0;
- char t;
- while (isnum(eval[i]) && i < len) { ++i; }
- t = eval[i];
- eval[i] = '\0';
- /* fprintf(stderr, "len: %3ld i: %4ld -- %12s --\n", len, i, eval); */
- NS_EXPAND(s);
- mpf_set_str(s->num[s->top], eval, base);
- eval[i] = t;
- return i-1;
- }
-
- static int
- dcevaln(ns_t * s, char * eval, size_t len)
- {
- static int base = 10;
- size_t i;
- int comment = 0;
- int neg = 0;
- assert(s);
- for (i = 0; i < len; ++i)
- {
- if (comment)
- {
- if (eval[i] == '\n')
- { comment = 0; }
- continue;
- }
- switch (eval[i])
- {
- case '\t': case '\v': case '\n': case '\r': case ' ':
- continue;
- case '_': neg = 1; continue;
- case '.':
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- i += getnum(s, eval+i, len - i, base);
- if (neg)
- { mpf_neg(NS_PEEK(s), NS_PEEK(s)); }
- break;
- case '#': comment = 1; break;
- case '%': ns_mod(s); break;
- case '*': ns_mul(s); break;
- case '+': ns_add(s); break;
- case '-': ns_sub(s); break;
- case '/': ns_div(s); break;
- case '^': ns_exp(s); break;
- case 'b': base = (int) mpf_get_ui(*ns_pop(s)); break;
- case 'c': ns_clear(s); break;
- case 'D': ns_ndup(s); break;
- case 'd': ns_dup(s); break;
- case 'f': ns_printline_all(s); break;
- case 'p': ns_printline_peek(s); break;
- case 'n': ns_print_peek(s); ns_pop(s); break;
- case 'q': return DC_EXIT;
- case 'r': ns_reverse(s); break;
- case 'v': ns_sqrt(s); break;
- case 'z': NS_EXPAND(s); mpf_set_ui(s->num[s->top], (unsigned int) s->top); break;
- #ifndef DC_COMPLY
- /*** CONFLICTION ***/
- case 'a': ns_abs(s); break;
- case 'C': ns_ceil(s); break;
- case 'F': ns_floor(s); break;
- case 'P': (void) ns_pop(s); break;
- #else
- case 'P':
- case 'a': case 'C': case 'F':
- /* Params */
- case 'i': case 'o': case 'k':
- case 'I': case 'O': case 'K':
- #endif /* DC_COMPLY */
- default:
- fprintf(stderr, PROGN ": '%c' (%04d) unimplemented\n", eval[i], eval[i]);
- }
- neg = 0;
- }
- fflush(stdout);
- return 0;
- }
-
- static inline int
- dceval(ns_t * s, char * eval)
- {
- size_t len = strlen(eval);
- return dcevaln(s, eval, len);
- }
-
- static inline void
- help(void)
- { fprintf(stderr,
- "Usage: " PROGN " [OPTION] [file ...]\n"
- "\t-e expr ..., evaluate expression\n"
- "\t-f file ..., evaluate contents of file\n"
- "\t-h, display this help message and exits\n"
- "\t-V, output version information and exits\n"); }
-
- static inline void
- version(void)
- { fprintf(stderr,
- PROGN " " VERSION_STRING "\n"
- "Copyright 2022, 2023 " AUTHOR "\n\n"
-
- "This program is free software: you can redistribute it and/or modify\n"
- "it under the terms of the GNU General Public License version 3 as\n"
- "published by the Free Software Foundation.\n\n"
-
- "This program is distributed in the hope that it will be useful,\n"
- "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
- "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
- "GNU General Public License version 3 for more details.\n\n"
-
- "You should have received a copy of the GNU General Public License\n"
- "version 3 along with the source.\n\n"
-
- "If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.\n"); }
-
- int
- dcfile(ns_t * s, int argc, char ** argv)
- {
- int ret = 0;
- int i;
- size_t sz;
- char * buf;
- for (i = 0; !ret && i < argc; ++i)
- {
- buf = slurp(argv[i], &sz);
- if (!buf)
- { ret = 1; }
- else
- { ret += dcevaln(s, buf, sz); }
- free(buf);
- }
- return ret;
- }
-
- int
- main(int argc,
- char ** argv)
- {
- int ret = 0;
- ns_t * s;
- mpf_set_default_prec(2 << MAX_PREC);
- s = ns_init(NS_DEFAULT);
- if (!s)
- { return 1; }
- else if (argc > 1)
- {
- if (argv[1][0] == '-')
- {
- if (argv[1][1] == '-')
- {
- if (strcmp(argv[1]+2, "version") == 0)
- { goto version; }
- if (strcmp(argv[1]+2, "help") == 0)
- { goto help; }
- }
- else switch(argv[1][1])
- {
- int i;
- case 'e':
- for (i = 2; !ret && i < argc; ++i)
- { ret += dceval(s, argv[i]); }
- goto stop;
- case 'f':
- dcfile(s, argc-=2, argv+=2);
- goto stop;
- default:
- fprintf(stderr, PROGN ": invaild option -- '%c'\n", argv[1][1]);
- ret = 1;
- /* fallthrough */
- help: case 'h': help(); goto stop;
- version: case 'V': version(); goto stop;
- }
- }
- else
- { dcfile(s, --argc, ++argv); }
- }
- else
- {
- char * input;
- while (!ret)
- {
- input = readline("");
- if (!input || (ret += dceval(s,input)))
- { ret = 1; }
- free(input);
- }
- }
- stop:
- ns_free(s);
- return ret == DC_EXIT ? 0 : ret;
- }
|