It's a stack calculator for the unwise. Public Domain.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

351 lines
8.9KB

  1. /* dc.c - desktop calculator implementation
  2. Public domain.
  3. GMP based DC. Uses readline. No limitations, either.
  4. See README for more details.
  5. */
  6. #include <assert.h>
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <stdint.h>
  10. #include <unistd.h>
  11. #include <math.h>
  12. #include <readline/readline.h>
  13. #include <readline/history.h>
  14. #include <gmp.h>
  15. #define DC_EXPORT static
  16. #define DC_EXPORT_VAR static
  17. #ifndef DC_COMPLY
  18. # define NS_REG_MAX 95
  19. # define NS_REG_OFF 32
  20. # define NS_REGCHAR_CLAMP(c) ((c) > NS_REG_MAX)
  21. #else
  22. # define NS_REG_MAX 256
  23. # define NS_REG_OFF 0
  24. # define NS_REGCHAR_CLAMP(c) 0
  25. #endif /* !DC_COMPLY */
  26. #include "config.h"
  27. #include "ns.c"
  28. #define REGCHAR() \
  29. do \
  30. { \
  31. if (!(i + 1 < len)) \
  32. { break; } \
  33. c = eval[i + 1]; \
  34. ++i; \
  35. } while (0)
  36. #define REGCLAMP() \
  37. if (c -= NS_REG_OFF, NS_REGCHAR_CLAMP(c)) \
  38. { \
  39. NS_REG_OOB(c, c); \
  40. break; \
  41. }
  42. #define AUTHOR "Emil Williams"
  43. #define VERSION_STRING "5"
  44. #define DC_EXIT (2 << 5)
  45. /* Command
  46. Note that I feel this should be ommited, as it doesn't serve
  47. any purpose except being able to run commands blindly.
  48. */
  49. static inline size_t
  50. commandn(char * command, size_t len)
  51. {
  52. size_t i = 0;
  53. char c;
  54. while ((command[i] != '\0' ||
  55. command[i] != '\n') &&
  56. i < len)
  57. { ++i; }
  58. c = command[i+1];
  59. command[i+1] = '\0';
  60. /* Why we're using system over anything else (direct quote):
  61. Will run the rest of the line as a SYSTEM command. */
  62. system(command);
  63. command[i+1] = c;
  64. return i;
  65. }
  66. static inline size_t
  67. command(char * command)
  68. { return commandn(command, strlen(command)); }
  69. /* File Helpers */
  70. static inline size_t
  71. frem(FILE * fp)
  72. {
  73. fseek(fp, 0, SEEK_END);
  74. return ftell(fp);
  75. }
  76. DC_EXPORT char *
  77. slurp(const char * fn, size_t * rlen)
  78. {
  79. FILE * fp = fopen(fn, "r");
  80. if (!fp)
  81. { PERROR_RETURN("fopen", NULL); }
  82. else
  83. {
  84. size_t len = frem(fp);
  85. char * buf = malloc(len + 1);
  86. rewind(fp);
  87. if (!buf)
  88. { PERROR_RETURN("malloc", NULL); }
  89. if (len != fread(buf, 1, len, fp))
  90. {
  91. free(buf);
  92. { PERROR_RETURN("fopen", NULL); }
  93. }
  94. *rlen = len;
  95. return buf;
  96. }
  97. }
  98. /* DC REPL and CLI->Eval */
  99. DC_EXPORT int
  100. dcevaln(ns_t * s, char * eval, size_t len)
  101. {
  102. size_t i;
  103. int comment = 0;
  104. int neg = 0;
  105. int lradix = g_iradix;
  106. uint8_t c = '\0';
  107. for (i = 0; i < len; ++i)
  108. {
  109. if (comment)
  110. {
  111. if (eval[i] == '\n')
  112. { comment = 0; }
  113. continue;
  114. }
  115. switch (eval[i])
  116. {
  117. case '\t': case '\v': case '\n': case '\r': case ' ':
  118. continue;
  119. case '_': neg = 1; continue;
  120. case '.':
  121. case 'A': case 'B': case 'C': case 'D': case 'E':
  122. case 'F':
  123. /* DC naturally respects numbers like A100 as their hexidecimal value, rather than respecting
  124. the base and dropping the number for an invalid numeric symbol. */
  125. #ifdef DC_COMPLY
  126. lradix = g_iradix;
  127. g_iradix = 16;
  128. #endif /* DC_COMPLY */
  129. /* fallthrough */
  130. case '0': case '1': case '2': case '3': case '4':
  131. case '5': case '6': case '7': case '8': case '9':
  132. i += ns_getnum(s, eval+i, len - i, g_iradix);
  133. if (neg)
  134. { mpf_neg(NS_PEEK(s), NS_PEEK(s)); }
  135. g_iradix = lradix;
  136. break;
  137. case '#': comment = 1; break;
  138. case '*': ns_mul(s); break;
  139. case '+': ns_add(s); break;
  140. case '-': ns_sub(s); break;
  141. case '/': ns_div(s); break;
  142. case '^': ns_exp(s); break;
  143. /* case '%': ns_mod(s); break; */
  144. /* case '~': ns_divrem(s); break; */
  145. /* case '|': modexp(s); break; */
  146. case 'c': ns_clear(s); break;
  147. case 'd': ns_dup(s); break;
  148. case 'f': ns_printline_all(s); break;
  149. case 'p': ns_printline_peek(s); break;
  150. case 'n': ns_print_peek(s); ns_pop(s); break;
  151. case 'q': return DC_EXIT;
  152. case 'r': ns_reverse(s); break;
  153. case 'R': ns_nrotate(s); break;
  154. case 'v': ns_sqrt(s); break;
  155. case 'z': ns_push_ui(s,s->top); break;
  156. case 's': REGCHAR(); REGCLAMP(); ns_reg_set(s, c); break;
  157. case 'l': REGCHAR(); REGCLAMP(); ns_reg_get(s, c); break;
  158. case 'S': REGCHAR(); REGCLAMP(); ns_reg_push(s, c); break;
  159. case 'L': REGCHAR(); REGCLAMP(); ns_reg_pop(s, c); break;
  160. case 'k': ns_pop_prec(s); break;
  161. case 'K': ns_push_prec(s); break;
  162. case 'i': ns_pop_iradix(s); break;
  163. case 'I': ns_push_iradix(s); break;
  164. case 'o': ns_pop_oradix(s); break;
  165. case 'O': ns_push_oradix(s); break;
  166. case ':': REGCHAR(); REGCLAMP(); ns_reg_push_index(s, c); break;
  167. case ';': REGCHAR(); REGCLAMP(); ns_reg_pop_index(s, c); break;
  168. case '!': i += command(eval + i + 1); break;
  169. #if 0
  170. case '?': /* take user input, and execute that
  171. as a slave to current level */ break;
  172. #endif /* 0 */
  173. /* New non-conflicting features not present in DC */
  174. case '@': ns_abs(s); break;
  175. case '"': ns_ceil(s); break;
  176. case '\'': ns_floor(s); break;
  177. #ifndef DC_COMPLY
  178. /*** CONFLICTION ***/
  179. case 'P': (void) ns_pop(s); break;
  180. #else
  181. case 'P':
  182. #endif /* !DC_COMPLY */
  183. /* Intended to be implemented */
  184. case 'Z': case 'X': case '?': case 'Q':
  185. default:
  186. if (31 < eval[i] &&
  187. eval[i] < 127)
  188. { fprintf(stderr, PROGN ": '%c' (%#o) unimplemented\n", (uint8_t) eval[i], (uint8_t) eval[i]); }
  189. else
  190. { fprintf(stderr, PROGN ": (%#o) unimplemented\n", (uint8_t) eval[i]); }
  191. }
  192. neg = 0;
  193. }
  194. return 0;
  195. }
  196. DC_EXPORT inline int
  197. dceval(ns_t * s, char * eval)
  198. {
  199. return dcevaln(s, eval, strlen(eval));
  200. }
  201. DC_EXPORT inline int
  202. dcfile(ns_t * s, char * fn)
  203. {
  204. int ret = 0;
  205. size_t sz;
  206. char * buf;
  207. buf = slurp(fn, &sz);
  208. if (!buf)
  209. { ret = 1; }
  210. else
  211. { ret += dcevaln(s, buf, sz); }
  212. free(buf);
  213. return ret;
  214. }
  215. /* DC CLI and ADDITIONAL INFORMATION */
  216. #ifndef OTHER_MAIN
  217. static inline void
  218. help(void)
  219. { fprintf(stderr,
  220. "Usage: " PROGN " [OPTION] [file ...]\n"
  221. "\t-e, --expression[=...] Evaluates an expression\n"
  222. "\t-f, --file[=...] Evaluates the contents of file\n"
  223. "\t-h, --help Displays this message and exits\n"
  224. "\t-V, --version Outputs version information and exits\n"); }
  225. static inline void
  226. version(void)
  227. { fprintf(stderr,
  228. PROGN " " VERSION_STRING "\n"
  229. "Copyright 2021, 2022, 2023 " AUTHOR "\n\n"
  230. PROGN " is free software: you can redistribute it and/or modify\n"
  231. "it under the terms of the GNU General Public License version 3 as\n"
  232. "published by the Free Software Foundation.\n\n"
  233. "See <https://www.gnu.org/licenses/gpl-3.0.txt>.\n"); }
  234. #define DC_EQOP(op,arg,off) \
  235. if (arg[off] == '=') \
  236. { op(s,arg+off+1); } \
  237. else if (arg[off] == '\0' && \
  238. argc > 1) \
  239. { \
  240. ret = op(s, argv[1]); \
  241. ++argv; --argc; \
  242. } \
  243. else \
  244. { goto help; }
  245. int
  246. main(int argc,
  247. char ** argv)
  248. {
  249. int ret = 0;
  250. ns_t sreal = (ns_t) {0, -1, NULL};
  251. ns_t * s = &sreal;
  252. mpf_set_default_prec(g_prec);
  253. ns_reg_init();
  254. if (!s)
  255. { ret = 1; }
  256. else if (argc > 1)
  257. {
  258. char * arg;
  259. while (++argv, --argc)
  260. {
  261. arg = *argv;
  262. if (arg[0] == '-')
  263. {
  264. if (arg[1] == '-')
  265. {
  266. if (strcmp(arg+2, "version") == 0)
  267. { goto version; }
  268. else if (strcmp(arg+2, "help") == 0)
  269. { goto help; }
  270. else if (strncmp(arg+2, "expression", 10) == 0)
  271. { DC_EQOP(dceval,arg,12) }
  272. else if (strncmp(arg+2, "file", 4) == 0)
  273. { DC_EQOP(dcfile,arg,6) }
  274. }
  275. else switch(arg[1])
  276. {
  277. case 'e': DC_EQOP(dceval,arg,2) break;
  278. case 'f': DC_EQOP(dcfile,arg,2) break;
  279. default:
  280. fprintf(stderr, PROGN ": invaild option -- '%c'\n", argv[0][1]);
  281. ret = 1;
  282. /* fallthrough */
  283. help: case 'h': help(); goto stop;
  284. version: case 'V': version(); goto stop;
  285. }
  286. }
  287. else
  288. { dcfile(s, *argv++); }
  289. }
  290. }
  291. else /* REPL */
  292. {
  293. char * input;
  294. while (!ret)
  295. {
  296. input = readline("");
  297. if (!input)
  298. {
  299. ret = 1;
  300. break;
  301. }
  302. add_history(input);
  303. ret = dceval(s,input);
  304. free(input);
  305. }
  306. clear_history();
  307. }
  308. stop:
  309. ns_reg_free();
  310. ns_free(s);
  311. return ret == DC_EXIT ? 0 : ret;
  312. }
  313. #endif /* !OTHER_MAIN */
  314. #undef REGCHAR
  315. #undef REGCLAMP