349 lines
8.8 KiB
C
349 lines
8.8 KiB
C
/* 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;
|
|
case '?': /* take user input, and execute that
|
|
as a slave to current level */ break;
|
|
/* 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
|