|
- /* dc.c - desktop calculator implementation
-
- Public domain.
-
- GMP based DC. Uses readline. No limitations, either.
-
- See README for more details.
-
- */
-
- #include <assert.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdint.h>
- #include <unistd.h>
- #include <math.h>
-
- #include <readline/readline.h>
- #include <readline/history.h>
-
- #include <gmp.h>
-
- #define DC_EXPORT static
- #define DC_EXPORT_VAR static
-
- #ifndef DC_COMPLY
- # define NS_REG_MAX 95
- # define NS_REG_OFF 32
- # define NS_REGCHAR_CLAMP(c) ((c) > NS_REG_MAX)
- #else
- # define NS_REG_MAX 256
- # define NS_REG_OFF 0
- # define NS_REGCHAR_CLAMP(c) 0
- #endif /* !DC_COMPLY */
-
- #include "config.h"
- #include "ns.c"
-
- #define REGCHAR() \
- do \
- { \
- if (!(i + 1 < len)) \
- { break; } \
- c = eval[i + 1]; \
- ++i; \
- } while (0)
-
- #define REGCLAMP() \
- if (c -= NS_REG_OFF, NS_REGCHAR_CLAMP(c)) \
- { \
- NS_REG_OOB(c, c); \
- break; \
- }
-
-
- #define AUTHOR "Emil Williams"
- #define VERSION_STRING "5"
- #define DC_EXIT (2 << 5)
-
- /* Command
-
- Note that I feel this should be ommited, as it doesn't serve
- any purpose except being able to run commands blindly.
- */
-
- static inline size_t
- commandn(char * command, size_t len)
- {
- size_t i = 0;
- char c;
- while ((command[i] != '\0' ||
- command[i] != '\n') &&
- i < len)
- { ++i; }
- c = command[i+1];
- command[i+1] = '\0';
- /* Why we're using system over anything else (direct quote):
- Will run the rest of the line as a SYSTEM command. */
- system(command);
- command[i+1] = c;
- return i;
- }
-
- static inline size_t
- command(char * command)
- { return commandn(command, strlen(command)); }
-
- /* File Helpers */
-
- static inline size_t
- frem(FILE * fp)
- {
- fseek(fp, 0, SEEK_END);
- return ftell(fp);
- }
-
- DC_EXPORT char *
- slurp(const char * fn, size_t * rlen)
- {
- FILE * fp = fopen(fn, "r");
- if (!fp)
- { PERROR_RETURN("fopen", NULL); }
- else
- {
- size_t len = frem(fp);
- char * buf = malloc(len + 1);
- rewind(fp);
- if (!buf)
- { PERROR_RETURN("malloc", NULL); }
- if (len != fread(buf, 1, len, fp))
- {
- free(buf);
- { PERROR_RETURN("fopen", NULL); }
- }
- *rlen = len;
- return buf;
- }
- }
-
- /* DC REPL and CLI->Eval */
-
- DC_EXPORT int
- dcevaln(ns_t * s, char * eval, size_t len)
- {
- size_t i;
- int comment = 0;
- int neg = 0;
- int lradix = g_iradix;
- uint8_t c = '\0';
- 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 'A': case 'B': case 'C': case 'D': case 'E':
- case 'F':
- /* DC naturally respects numbers like A100 as their hexidecimal value, rather than respecting
- the base and dropping the number for an invalid numeric symbol. */
- #ifdef DC_COMPLY
- lradix = g_iradix;
- g_iradix = 16;
- #endif /* DC_COMPLY */
- /* fallthrough */
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- i += ns_getnum(s, eval+i, len - i, g_iradix);
- if (neg)
- { mpf_neg(NS_PEEK(s), NS_PEEK(s)); }
- g_iradix = lradix;
- break;
- case '#': comment = 1; 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 '%': ns_mod(s); break; */
- /* case '~': ns_divrem(s); break; */
- /* case '|': modexp(s); break; */
- case 'c': ns_clear(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 'R': ns_nrotate(s); break;
- case 'v': ns_sqrt(s); break;
- case 'z': ns_push_ui(s,s->top); break;
- case 's': REGCHAR(); REGCLAMP(); ns_reg_set(s, c); break;
- case 'l': REGCHAR(); REGCLAMP(); ns_reg_get(s, c); break;
- case 'S': REGCHAR(); REGCLAMP(); ns_reg_push(s, c); break;
- case 'L': REGCHAR(); REGCLAMP(); ns_reg_pop(s, c); break;
- case 'k': ns_pop_prec(s); break;
- case 'K': ns_push_prec(s); break;
- case 'i': ns_pop_iradix(s); break;
- case 'I': ns_push_iradix(s); break;
- case 'o': ns_pop_oradix(s); break;
- case 'O': ns_push_oradix(s); break;
- case ':': REGCHAR(); REGCLAMP(); ns_reg_push_index(s, c); break;
- case ';': REGCHAR(); REGCLAMP(); ns_reg_pop_index(s, c); break;
- case '!': i += command(eval + i + 1); break;
- #if 0
- case '?': /* take user input, and execute that
- as a slave to current level */ break;
- #endif /* 0 */
- /* New non-conflicting features not present in DC */
- case '@': ns_abs(s); break;
- case '"': ns_ceil(s); break;
- case '\'': ns_floor(s); break;
- #ifndef DC_COMPLY
- /*** CONFLICTION ***/
- case 'P': (void) ns_pop(s); break;
- #else
- case 'P':
- #endif /* !DC_COMPLY */
- /* Intended to be implemented */
- case 'Z': case 'X': case '?': case 'Q':
- default:
- if (31 < eval[i] &&
- eval[i] < 127)
- { fprintf(stderr, PROGN ": '%c' (%#o) unimplemented\n", (uint8_t) eval[i], (uint8_t) eval[i]); }
- else
- { fprintf(stderr, PROGN ": (%#o) unimplemented\n", (uint8_t) eval[i]); }
- }
- neg = 0;
- }
- return 0;
- }
-
- DC_EXPORT inline int
- dceval(ns_t * s, char * eval)
- {
- return dcevaln(s, eval, strlen(eval));
- }
-
- DC_EXPORT inline int
- dcfile(ns_t * s, char * fn)
- {
- int ret = 0;
- size_t sz;
- char * buf;
- buf = slurp(fn, &sz);
- if (!buf)
- { ret = 1; }
- else
- { ret += dcevaln(s, buf, sz); }
- free(buf);
- return ret;
- }
-
- /* DC CLI and ADDITIONAL INFORMATION */
-
- #ifndef OTHER_MAIN
-
- static inline void
- help(void)
- { fprintf(stderr,
- "Usage: " PROGN " [OPTION] [file ...]\n"
- "\t-e, --expression[=...] Evaluates an expression\n"
- "\t-f, --file[=...] Evaluates the contents of file\n"
- "\t-h, --help Displays this message and exits\n"
- "\t-V, --version Outputs version information and exits\n"); }
-
- static inline void
- version(void)
- { fprintf(stderr,
- PROGN " " VERSION_STRING "\n"
- "Copyright 2021, 2022, 2023 " AUTHOR "\n\n"
-
- PROGN " 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"
-
- "See <https://www.gnu.org/licenses/gpl-3.0.txt>.\n"); }
-
- #define DC_EQOP(op,arg,off) \
- if (arg[off] == '=') \
- { op(s,arg+off+1); } \
- else if (arg[off] == '\0' && \
- argc > 1) \
- { \
- ret = op(s, argv[1]); \
- ++argv; --argc; \
- } \
- else \
- { goto help; }
-
- int
- main(int argc,
- char ** argv)
- {
- int ret = 0;
- ns_t sreal = (ns_t) {0, -1, NULL};
- ns_t * s = &sreal;
- mpf_set_default_prec(g_prec);
- ns_reg_init();
- if (!s)
- { ret = 1; }
- else if (argc > 1)
- {
- char * arg;
- while (++argv, --argc)
- {
- arg = *argv;
- if (arg[0] == '-')
- {
- if (arg[1] == '-')
- {
- if (strcmp(arg+2, "version") == 0)
- { goto version; }
- else if (strcmp(arg+2, "help") == 0)
- { goto help; }
- else if (strncmp(arg+2, "expression", 10) == 0)
- { DC_EQOP(dceval,arg,12) }
- else if (strncmp(arg+2, "file", 4) == 0)
- { DC_EQOP(dcfile,arg,6) }
- }
- else switch(arg[1])
- {
- case 'e': DC_EQOP(dceval,arg,2) break;
- case 'f': DC_EQOP(dcfile,arg,2) break;
- default:
- fprintf(stderr, PROGN ": invaild option -- '%c'\n", argv[0][1]);
- ret = 1;
- /* fallthrough */
- help: case 'h': help(); goto stop;
- version: case 'V': version(); goto stop;
- }
- }
- else
- { dcfile(s, *argv++); }
- }
- }
- else /* REPL */
- {
- char * input;
- while (!ret)
- {
- input = readline("");
- if (!input)
- {
- ret = 1;
- break;
- }
- add_history(input);
- ret = dceval(s,input);
- free(input);
- }
- clear_history();
- }
- stop:
- ns_reg_free();
- ns_free(s);
- return ret == DC_EXIT ? 0 : ret;
- }
-
- #endif /* !OTHER_MAIN */
-
- #undef REGCHAR
- #undef REGCLAMP
|