/* dc.c - desktop calculator implementation Public domain. GMP based DC. Uses readline. No limitations, either. See README for more details. */ #include #include #include #include #include #include #include #include #include #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 .\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