279 lines
6.2 KiB
C
279 lines
6.2 KiB
C
/* 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;
|
|
}
|