This repository has been archived on 2024-03-02. You can view files and clone it, but cannot push or open issues or pull requests.
dc/dc.c
2023-09-23 17:26:21 +00:00

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