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.

349 lines
8.8KB

  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. case '?': /* take user input, and execute that
  170. as a slave to current level */ break;
  171. /* New non-conflicting features not present in DC */
  172. case '@': ns_abs(s); break;
  173. case '"': ns_ceil(s); break;
  174. case '\'': ns_floor(s); break;
  175. #ifndef DC_COMPLY
  176. /*** CONFLICTION ***/
  177. case 'P': (void) ns_pop(s); break;
  178. #else
  179. case 'P':
  180. #endif /* !DC_COMPLY */
  181. /* Intended to be implemented */
  182. case 'Z': case 'X': case '?': case 'Q':
  183. default:
  184. if (31 < eval[i] &&
  185. eval[i] < 127)
  186. { fprintf(stderr, PROGN ": '%c' (%#o) unimplemented\n", (uint8_t) eval[i], (uint8_t) eval[i]); }
  187. else
  188. { fprintf(stderr, PROGN ": (%#o) unimplemented\n", (uint8_t) eval[i]); }
  189. }
  190. neg = 0;
  191. }
  192. return 0;
  193. }
  194. DC_EXPORT inline int
  195. dceval(ns_t * s, char * eval)
  196. {
  197. return dcevaln(s, eval, strlen(eval));
  198. }
  199. DC_EXPORT inline int
  200. dcfile(ns_t * s, char * fn)
  201. {
  202. int ret = 0;
  203. size_t sz;
  204. char * buf;
  205. buf = slurp(fn, &sz);
  206. if (!buf)
  207. { ret = 1; }
  208. else
  209. { ret += dcevaln(s, buf, sz); }
  210. free(buf);
  211. return ret;
  212. }
  213. /* DC CLI and ADDITIONAL INFORMATION */
  214. #ifndef OTHER_MAIN
  215. static inline void
  216. help(void)
  217. { fprintf(stderr,
  218. "Usage: " PROGN " [OPTION] [file ...]\n"
  219. "\t-e, --expression[=...] Evaluates an expression\n"
  220. "\t-f, --file[=...] Evaluates the contents of file\n"
  221. "\t-h, --help Displays this message and exits\n"
  222. "\t-V, --version Outputs version information and exits\n"); }
  223. static inline void
  224. version(void)
  225. { fprintf(stderr,
  226. PROGN " " VERSION_STRING "\n"
  227. "Copyright 2021, 2022, 2023 " AUTHOR "\n\n"
  228. PROGN " is free software: you can redistribute it and/or modify\n"
  229. "it under the terms of the GNU General Public License version 3 as\n"
  230. "published by the Free Software Foundation.\n\n"
  231. "See <https://www.gnu.org/licenses/gpl-3.0.txt>.\n"); }
  232. #define DC_EQOP(op,arg,off) \
  233. if (arg[off] == '=') \
  234. { op(s,arg+off+1); } \
  235. else if (arg[off] == '\0' && \
  236. argc > 1) \
  237. { \
  238. ret = op(s, argv[1]); \
  239. ++argv; --argc; \
  240. } \
  241. else \
  242. { goto help; }
  243. int
  244. main(int argc,
  245. char ** argv)
  246. {
  247. int ret = 0;
  248. ns_t sreal = (ns_t) {0, -1, NULL};
  249. ns_t * s = &sreal;
  250. mpf_set_default_prec(g_prec);
  251. ns_reg_init();
  252. if (!s)
  253. { ret = 1; }
  254. else if (argc > 1)
  255. {
  256. char * arg;
  257. while (++argv, --argc)
  258. {
  259. arg = *argv;
  260. if (arg[0] == '-')
  261. {
  262. if (arg[1] == '-')
  263. {
  264. if (strcmp(arg+2, "version") == 0)
  265. { goto version; }
  266. else if (strcmp(arg+2, "help") == 0)
  267. { goto help; }
  268. else if (strncmp(arg+2, "expression", 10) == 0)
  269. { DC_EQOP(dceval,arg,12) }
  270. else if (strncmp(arg+2, "file", 4) == 0)
  271. { DC_EQOP(dcfile,arg,6) }
  272. }
  273. else switch(arg[1])
  274. {
  275. case 'e': DC_EQOP(dceval,arg,2) break;
  276. case 'f': DC_EQOP(dcfile,arg,2) break;
  277. default:
  278. fprintf(stderr, PROGN ": invaild option -- '%c'\n", argv[0][1]);
  279. ret = 1;
  280. /* fallthrough */
  281. help: case 'h': help(); goto stop;
  282. version: case 'V': version(); goto stop;
  283. }
  284. }
  285. else
  286. { dcfile(s, *argv++); }
  287. }
  288. }
  289. else /* REPL */
  290. {
  291. char * input;
  292. while (!ret)
  293. {
  294. input = readline("");
  295. if (!input)
  296. {
  297. ret = 1;
  298. break;
  299. }
  300. add_history(input);
  301. ret = dceval(s,input);
  302. free(input);
  303. }
  304. clear_history();
  305. }
  306. stop:
  307. ns_reg_free();
  308. ns_free(s);
  309. return ret == DC_EXIT ? 0 : ret;
  310. }
  311. #endif /* !OTHER_MAIN */
  312. #undef REGCHAR
  313. #undef REGCLAMP