@@ -0,0 +1 @@ | |||
*.o |
@@ -0,0 +1 @@ | |||
Public Domain. |
@@ -0,0 +1,47 @@ | |||
.POSIX: # Just kidding, use GNU Make | |||
CP := cp -f | |||
CC := cc | |||
CFLAGS := -std=c99 -Wall -Wextra -Wpedantic | |||
LDFLAGS := -lgmp -lreadline | |||
OBJ := dc.o | |||
ifeq ($(DEBUG),1) | |||
CFLAGS += -Og -g | |||
else | |||
CFLAGS += -O3 -funroll-loops -fomit-frame-pointer | |||
endif | |||
ifdef SAN | |||
CFLAGS += -fsanitize=$(SAN) | |||
endif | |||
ifeq ($(FOR_HUMANS),1) | |||
CPPFLAGS += -DFOR_HUMANS | |||
endif | |||
ifeq ($(DC_COMPLY),1) | |||
CPPFLAGS += -DDC_COMPLY | |||
endif | |||
ifdef PROGN | |||
CPPFLAGS += -DPROGN=\"$(PROGN)\" | |||
else | |||
PROGN := dc | |||
endif | |||
%.o: %.c | |||
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< | |||
$(PROGN): $(OBJ) | |||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $< $(LDFLAGS) | |||
config.h: config.mk.h | |||
$(CP) $< $@ | |||
dc.o: config.h ns.c | |||
clean: | |||
$(RM) $(OBJ) $(PROGN) |
@@ -0,0 +1,162 @@ | |||
-- DC -- | |||
desktop calculator implementation | |||
Copyright 2022, 2023 Emil Williams, see LICENSE | |||
GMP based DC. Uses readline. No limitations, either. | |||
-- Notes -- | |||
Note that much of the dc(1) man page is applicable, and is the formal root of | |||
this project. It is partly abidded in the featureset, of course this program is | |||
not GNU dc or a POSIX implementation* but it closely aligns to the manual page | |||
as a primary source of how DC should generally operate. | |||
Diviations exist, and this should be either treated as a bug, or a feature | |||
depending on the case regarding it. If some feature incorrectly matches dc(1) | |||
and deliberate incompatibility is not stated then it is a bug. See section | |||
Incompats | |||
Warning : Numbers are purely base 10 and hexadecimal is not implemented in any | |||
way but I've Ensured there are no hinderences with A-F with my | |||
conflicting character set. | |||
* There is no POSIX definition of DC, currently. There is a POSIX defition of | |||
BC, however. A formal proposal of DC should likely be a reduced subset of | |||
GNU/DC, with all normal printing and stack operations, along with registers, | |||
strings, and params, with the miscellaneous features and requirements for | |||
handling the REPL, files and STDIN all in the same or limited way (such that | |||
this program would be complicit) for the CLI. Of course this would come with | |||
the specification of multiple precision. | |||
This section is just a brief airing and goalset of what the codebase can do. | |||
-- 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, and is not limited | |||
v n1's square root | |||
Note that |, % and ~ have been omitted due to being 'pointless'. | |||
They may be added later with mpf_t to mpz_t conversion. | |||
- Stack -- | |||
c clear | |||
d duplicate | |||
r reverse n1 and n2 | |||
z pushes top | |||
-- Regs -- | |||
any character from \0 to \377 [256] is a valid register and check config for | |||
stack max r being any character as stated. | |||
sr sets a popped value from the primary stack onto r | |||
lr gets a value from r and push that onto the stack | |||
Sr pushes a popped value unto the registers stack | |||
Lr pops a value unto the main stack | |||
;r pops two values from the the primary stack, such and pushes n2 at index n1 | |||
:r pops a value and pushes that index unto the primary stack | |||
Note that a sr value is not destroyed by Sr. This is technically an | |||
incompatibility but I don't see a reason to maintain such abnormality. | |||
-- Params -- | |||
Push / Pop | |||
I / i set input radix | |||
K / k set the precision (of everything, including registers) | |||
-- Misc. -- | |||
q quit | |||
# a comment. | |||
-- Incompats -- | |||
- FIXME on i and input radix | |||
A-F notation will refer to the numbers 10-15, | |||
however the Input Radix must be already set higher | |||
than these symbols as mpf_set_str does not respect these symbols normally. | |||
- FIXME on k and precision settings | |||
the expression: 128k2 3/ ran under this program and GNU DC preduce different outputs, | |||
GNU: .66666666666666666666666666666666666666666666666666666666666666666666\ | |||
666666666666666666666666666666666666666666666666666666666666 | |||
CACT: 0.6666666666666666666666666666666666666667 | |||
128 refers to apparently a 128 digits of precision, rather than the traditional number of bits. | |||
And it rounds up... | |||
- DISCREPANCY we print a prefixing zero on fractions 1 < & > -1 | |||
- FIXME not very meaningful o | |||
o has no effect on output. | |||
- FIXME readline does not take kindly to input | |||
demo: | |||
20S | |||
L | |||
p | |||
# loads 20 into register 012's stack | |||
# loads that back into the main stack | |||
# prints top of stack | |||
does not have the same effect as when ran as ours, infact, the S call would | |||
preduce a clamping error at the CLI REGCHAR level. | |||
I believe this is an issue with how input is read, thus an issue with how readline filters it's returned string. | |||
- WARN registers without DC_COMPLY | |||
Are reduced to a smaller subset and only normal ascii characters are respected (' ' - '~') | |||
Only 95 registers out of a naturally presumed 256 registers as stated by DC's man page. | |||
- INCOMPAT readline respects DEL sometimes | |||
% echo '^?20' | ./dc | |||
% echo '^?20' | dc | |||
This is the equivalent of UB in your terminal emulator. | |||
MINOR INCOMPAT FIXME dc prints characters greater than 126 (~) in octal under the format PROGN ": %04o unimplemented\n" | |||
-- TODO -- | |||
Key: | |||
- Partial work, nothing tangable | |||
~ Partial completion | |||
? Experimental stage completion | |||
* Completed with confidence | |||
No work or consideration has been done | |||
X Incompatible completed | |||
[*] dynamic stack allocation | |||
[*] A-F hexadecimal w/ compat | |||
[X] Params | |||
[*] :; | |||
[*] registers | |||
[-] digit counts | |||
[-] cyclical nrotate R | |||
[ ] strings/macros | |||
-- Findings -- | |||
GNU dc has leaks but it has two orders of magnitude less total space allocated. |
@@ -0,0 +1,217 @@ | |||
-- DC -- | |||
desktop calculator implementation | |||
Copyright 2022, 2023 Emil Williams, see LICENSE | |||
GMP based DC. Uses readline. No limitations, either. | |||
-- Summary -- | |||
DC is a reverse-polish desk calculator which supports unlimited precision arithmetic. Normally DC reads from the | |||
standard input; if any command arguments are given to it, they are filenames, and DC reads and executes the contents of | |||
the files instead of reading from standard input. | |||
A reverse-polish calculator stores numbers on a stack. Entering a number pushes it on the stack. Arithmetic operations | |||
pop arguments off the stack and push the results. | |||
To enter a number in DC, type the digits, with an optional decimal point. To enter a negative number, begin the number | |||
with `_'. `-' cannot be used for this, as it is a binary operator for subtraction instead. To enter two numbers in | |||
succession, separate them with spaces or newlines. These have no meaning as direct commands. Exponential notation is not | |||
supported. | |||
A-F are respected as values under the circumstance that the input radix is higher than their given number. | |||
-- Invocation -- | |||
DC may be invoked with the following command-line options: | |||
`-e expr' | |||
`-e=expr' | |||
`--expression expr' | |||
`--expression=expr' | |||
Evaluates a DC expression. | |||
`-f file' | |||
`-f=file' | |||
`--file file' | |||
`--file=file' | |||
Reads and evaluates expressions from a file. | |||
`-h' | |||
`--help' | |||
Print a usage message summarizing the command-line options, then exits. | |||
`-V' | |||
`--version' | |||
Print the current version for DC, then exits. | |||
The envoriment is reset after every command-line entry. | |||
-- Printing Commands -- | |||
`p' | |||
Prints the value on the top of the stack, without altering the stack. A newline is printed after the value. | |||
`n' (Supported GNU extension) | |||
Prints the value on the top of the stack, popping it off, and does not print a newline after. | |||
`f' | |||
Prints the entire contents of the stack. This is a good command to use if you are lost or want to figure out what the | |||
effect of some command has been. | |||
-- Arithmetic -- | |||
Arithmetic | |||
`+' | |||
Pops two values off the stack, adds them, and pushes the result. The precision of the result is determined only by the | |||
values of the arguments, and is enough to be exact. | |||
`-' | |||
Pops two values, subtracts the first one popped from the second one popped, and pushes the result. | |||
`*' | |||
Pops two values, multiplies them, and pushes the result. | |||
`/' | |||
Pops two values, divides the second one popped from the first one popped, and pushes the result. | |||
`%' | |||
Pops two values, computes the remainder of the division that the `/' command would do, and pushes that. The value | |||
computed is the same as that computed by the sequence Sd dld/ Ld*- . | |||
`~' (Supported GNU extension) | |||
Pops two values, divides the second one popped from the first one popped. The quotient is pushed first, and the | |||
remainder is pushed next.(The sequence SdSn lnld/ LnLd% could also accomplish this function, with slightly different | |||
error checking.) | |||
`^' | |||
Pops two values and exponentiates, using the first value popped as the exponent and the second popped as the base. The | |||
fraction part of the exponent is ignored. | |||
FIXME reread and rephrase truness of secondary expression always working or not. | |||
`|' (Supported GNU extension) | |||
Pops three values and computes a modular exponentiation. The first value popped is used as the reduction modulus; this | |||
value must be a non-zero number, and the result may not be accurate if the modulus is not an integer. The second popped | |||
is used as the exponent; this value must be a non-negative number, and any fractional part of this exponent will be | |||
ignored. The third value popped is the base which gets exponentiated, which should be an integer. For small integers | |||
this is like the sequence Sm^Lm%, but, unlike ^, this command will work with arbritrarily large exponents. | |||
`v' | |||
Pops one value, computes its square root, and pushes that. | |||
Most arithmetic operations are affected by the precision value, which you can set with the `k' command. the default | |||
precision value is stated within config.h and is usually 128, this refers to bit complexity and not a number of digits. | |||
-- Additional Mathematical Functions -- | |||
(These are all my extensions.) | |||
`@' | |||
Pops one value, computes its absolute value, and pushes that. | |||
`\"' | |||
Pops one value, ceils the value, and pushes that. | |||
`'' | |||
Pops one value, floors the value, and pushes that. | |||
-- Stack Control -- | |||
`c' | |||
Clears the stack, rendering it empty. | |||
`d' | |||
Duplicates the value on the top of the stack, pushing another copy of it. Thus, `4d*p' computes 4 squared and prints | |||
it. | |||
`r' (Supported GNU extension) | |||
Reverses the order of (swaps) the top two values on the stack. | |||
`R' (Supported GNU extension) | |||
Rotates the top N items in a cyclical order, negatives do this in reverse. | |||
-- Registers -- | |||
Under DC_COMPLY, DC provides at least 256 memory registers, each named by a single character. You can store a number in | |||
a register and retrieve it later. Without DC_COMPLY there are only 95 registers, being any character between ' ' and '~' | |||
(inclusive.) | |||
`sr' | |||
Pop the value off the top of the stack and store it into register r. | |||
`lr' | |||
Copy the value in register r, and push it onto the stack. This does not alter the contents of r. Each register also | |||
contains its own stack. The current register value is the top of the register's stack. | |||
`Sr' | |||
Pop the value off the top of the (main) stack and push it onto the stack of register r. The previous value of the | |||
register becomes inaccessible. | |||
`Lr' | |||
Pop the value off the top of register r's stack and push it onto the main stack. The previous value in register r's | |||
stack, if any, is now accessible via the `lr' command. | |||
-- Params -- | |||
NOTE THAT INCOMPATIBILITIES EXIST WITHIN SOME OF THESE FEATURES, ESPECIALLY THE OUTPUT RADIX, WHICH DOES NOTHING. | |||
DC has three parameters that control its operation: the precision, the input radix, and the output radix. The precision | |||
specifies the number of fraction digits to keep in the result of most arithmetic operations. The input radix controls the | |||
interpretation of numbers typed in; all numbers typed in use this radix. The output radix is used for printing numbers. | |||
The input and output radices are separate parameters; you can make them unequal, which can be useful or confusing. The | |||
input radix must be between 2 and 16 inclusive. The output radix must be at least 2. The precision must be zero or | |||
greater. The precision is always measured in decimal digits, regardless of the current input or output radix. | |||
`i' | |||
Pops the value off the top of the stack and uses it to set the input radix. | |||
`o' | |||
Pops the value off the top of the stack and uses it to set the output radix. | |||
`k' | |||
Pops the value off the top of the stack and uses it to set the precision. | |||
`I' | |||
Pushes the current input radix on the stack. | |||
`O' | |||
Pushes the current output radix on the stack. | |||
`K' | |||
Pushes the current precision on the stack. | |||
-- Status Inquiry -- | |||
`Z' | |||
Pops a value off the stack, calculates the number of digits it has and pushes that number. | |||
`X' | |||
Pops a value off the stack, calculates the number of fraction digits it has, and pushes that number. | |||
`z' | |||
Pushes the current stack depth: the number of objects on the stack before the execution of the `z' command. | |||
-- Misc -- | |||
`!' | |||
Will run the rest of the line as a system command. Note that parsing of the !<, !=, and !> commands take precidence, so | |||
if you want to run a command starting with <, =, or > you will need to add a space after the !. | |||
`#' (Supported GNU extension) | |||
Will interpret the rest of the line as a comment. | |||
`:r' | |||
Will pop the top two values off of the stack. The old second-to-top value will be stored in the array r, indexed by the | |||
old top-of-stack value. | |||
`;r' | |||
Pops the top-of-stack and uses it as an index into the array r. The selected value is then pushed onto the stack. | |||
-- COMPILING -- | |||
This project uses GNU Make. | |||
Building options: | |||
DEBUG[=1] | |||
Enables the debugging detail. | |||
SAN[=...] | |||
Adds a given sanitization. |
@@ -0,0 +1,30 @@ | |||
/* config.h - Alter this file as you may need. */ | |||
/* alters features to match closer to GNU dc, incompats exist */ | |||
/* #define DC_COMPLY */ | |||
/* Reverses the output of f */ | |||
/* #define FOR_HUMANS */ | |||
/* The default stack size. | |||
The suggested value is 4096 (2 11^p). */ | |||
#define NS_DEFAULT_SIZE 0 | |||
/* The default size of registers, multiply by sizeof mpf_t * NS_REGISTER_MAX. | |||
The suggested value is 512 (2 9^p). */ | |||
#define NS_REG_SIZE 0 | |||
/* The default bit precision of numbers. | |||
The high value is 4096 (2 11^p). | |||
The lower the cheaper on memory the per-number. */ | |||
# define NS_DEFAULT_PREC 128 | |||
/* The default formatting for all number outputs. */ | |||
#define NS_FORMAT_DEC "%.Ff\n" | |||
#define NS_FORMAT NS_FORMAT_DEC | |||
/* Unless PROGN is already defined, define the programs name. | |||
Must be known and constant and compile time or it'll seem very inconsistent. */ | |||
#ifndef PROGN | |||
# define PROGN "dc" | |||
#endif |
@@ -0,0 +1,30 @@ | |||
/* config.h - Alter this file as you may need. */ | |||
/* alters features to match closer to GNU dc, incompats exist */ | |||
/* #define DC_COMPLY */ | |||
/* Reverses the output of f */ | |||
/* #define FOR_HUMANS */ | |||
/* The default stack size. | |||
The suggested value is 4096 (2 11^p). */ | |||
#define NS_DEFAULT_SIZE 0 | |||
/* The default size of registers, multiply by sizeof mpf_t * NS_REGISTER_MAX. | |||
The suggested value is 512 (2 9^p). */ | |||
#define NS_REG_SIZE 0 | |||
/* The default bit precision of numbers. | |||
The high value is 4096 (2 11^p). | |||
The lower the cheaper on memory the per-number. */ | |||
# define NS_DEFAULT_PREC 128 | |||
/* The default formatting for all number outputs. */ | |||
#define NS_FORMAT_DEC "%.Ff\n" | |||
#define NS_FORMAT NS_FORMAT_DEC | |||
/* Unless PROGN is already defined, define the programs name. | |||
Must be known and constant and compile time or it'll seem very inconsistent. */ | |||
#ifndef PROGN | |||
# define PROGN "dc" | |||
#endif |
@@ -0,0 +1,348 @@ | |||
/* 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 |
@@ -0,0 +1,545 @@ | |||
/* ns.c - Number Stack */ | |||
#define NS_PRINTLINE_FORMAT NS_PRINT_FORMAT "\n" | |||
/* Easy access functions */ | |||
#define NS_PEEK(s) s->num[s->top] | |||
#define NS_PEEKN(s,n) s->num[s->top + n] | |||
#define NS_PEEKDOT(s) s.num[s.top] | |||
/* Handling of the stack max size and expanding */ | |||
#define NS_EXPAND(s,n) n >= s->max ? !ns_expand(s,n) : 1 | |||
#define NS_EXPANDDOT(s,n) n >= s.max ? !ns_expand(&s,n) : 1 | |||
#define NS_LTMAX(s,n) s->top + n >= s->max ? !ns_expand(s,s->max+n) : 1 | |||
#define NS_LTMAXDOT(s,n) s.top + n >= s.max ? !ns_expand(&s,s.max+n) : 1 | |||
/* Debugging and Stack Output */ | |||
#define NS_DIV0(fail) do { fprintf(stderr, PROGN ": divide by zero\n"); return fail; } while (0) | |||
#define NS_REG_UNDERFLOW(fail, c) do { fprintf(stderr, PROGN ": stack register '%c' (%04d) is empty\n", (char) c, c); return fail; } while (0) | |||
#ifndef DC_COMPLY | |||
#define NS_OVERFLOW(fail) do { fprintf(stderr, PROGN ": overflow\n"); return fail; } while (0) | |||
#define NS_UNDERFLOW(fail) do { fprintf(stderr, PROGN ": underflow\n"); return fail; } while (0) | |||
#define NS_REG_OOB(fail, c) fprintf(stderr, PROGN ": register '%c' (%04d) is out of bounds\n", (char) c, c) | |||
#else | |||
#define NS_OVERFLOW(fail) do { fprintf(stderr, PROGN ": at top of stack\n"); return fail; } while (0) | |||
#define NS_UNDERFLOW(fail) do { fprintf(stderr, PROGN ": stack empty\n"); return fail; } while (0) | |||
#define NS_REG_OOB(fail, c) __builtin_unreachable() | |||
#endif /* !DC_COMPLY */ | |||
#define PERROR_RETURN(str,ret) do { perror(str); return ret; } while (1) | |||
#define NS_RETURN(ret) do { perror(PROGN); return ret; } while (1) | |||
/* #define NS_EXPAND(s,n) \ */ | |||
/* if (s->top + n >= s->max) \ */ | |||
/* { ns_expand(s,s->max+n); } */ | |||
#define NS_OP(fn, op) \ | |||
DC_EXPORT int \ | |||
fn(ns_t * s) \ | |||
{ \ | |||
if (s->top > 0) \ | |||
{ \ | |||
--s->top; \ | |||
op(NS_PEEK(s), \ | |||
NS_PEEK(s), NS_PEEKN(s,1)); \ | |||
return 0; \ | |||
} \ | |||
else \ | |||
{ NS_UNDERFLOW(1); } \ | |||
} | |||
#define NS_OP_ONE(fn, op) \ | |||
DC_EXPORT int \ | |||
fn(ns_t * s) \ | |||
{ \ | |||
if (s->top > -1) \ | |||
{ \ | |||
op(NS_PEEK(s), \ | |||
NS_PEEK(s)); \ | |||
return 0; \ | |||
} \ | |||
else \ | |||
{ NS_UNDERFLOW(1); } \ | |||
} | |||
#define NS_OP_DIV(fn, op) \ | |||
DC_EXPORT int fn(ns_t * s) \ | |||
{ \ | |||
if (s->top > 0) \ | |||
{ \ | |||
if (!(mpf_cmp_ui(NS_PEEK(s), 0) || \ | |||
mpf_cmp_ui(NS_PEEKN(s,-1), 0))) \ | |||
{ NS_DIV0(2); } \ | |||
else \ | |||
{ \ | |||
--s->top; \ | |||
op(NS_PEEK(s), \ | |||
NS_PEEK(s), NS_PEEKN(s,1)); \ | |||
return 0; \ | |||
} \ | |||
} \ | |||
else \ | |||
{ NS_UNDERFLOW(1); } \ | |||
} | |||
typedef struct | |||
{ | |||
ssize_t max; | |||
ssize_t top; | |||
mpf_t * num; | |||
} ns_t; /* number stack */ | |||
DC_EXPORT_VAR mp_bitcnt_t g_prec = NS_DEFAULT_PREC; | |||
DC_EXPORT_VAR int g_iradix = 10; | |||
DC_EXPORT_VAR int g_oradix = 10; | |||
/* Data handling */ | |||
DC_EXPORT void | |||
ns_free(ns_t * s) | |||
{ | |||
ssize_t i; | |||
/* fprintf(stderr, "%p: Freeing %ld blocks\n", (void *) s, s->max); */ | |||
for (i = 0; i < s->max; ++i) | |||
{ | |||
mpf_clear(s->num[i]); | |||
} | |||
free(s->num); | |||
/* free(s); */ | |||
} | |||
/* Growth */ | |||
DC_EXPORT int | |||
ns_expand(ns_t * s, ssize_t newmax) | |||
{ | |||
/* fprintf(stderr, "%p: Expanding to %ld blocks\n", (void *) s, newmax); */ | |||
s->num = realloc(s->num, newmax * sizeof(mpf_t)); | |||
if (!s->num) | |||
{ NS_RETURN(1); } | |||
else | |||
{ | |||
ssize_t i; | |||
for (i = s->max; i < newmax; ++i) | |||
{ mpf_init(s->num[i]); } | |||
s->max = newmax; | |||
return 0; | |||
} | |||
} | |||
/* Stack */ | |||
#define NS_PUSH(fn, op, type) \ | |||
DC_EXPORT int \ | |||
fn(ns_t * s, type val) \ | |||
{ \ | |||
if (NS_LTMAX(s,1)) \ | |||
{ \ | |||
++s->top; \ | |||
op(NS_PEEK(s), val); \ | |||
return 0; \ | |||
} \ | |||
NS_OVERFLOW(1); \ | |||
} | |||
NS_PUSH(ns_push, mpf_set, mpf_t) | |||
NS_PUSH(ns_push_ui, mpf_set_ui, unsigned int) | |||
DC_EXPORT mpf_t * | |||
ns_pop(ns_t * s) | |||
{ | |||
if (s->top > -1) | |||
{ return &s->num[s->top--]; } | |||
else | |||
{ return NULL; } | |||
} | |||
DC_EXPORT inline int | |||
ns_dup(ns_t * s) | |||
{ | |||
if (NS_LTMAX(s,1)) | |||
{ return ns_push(s, NS_PEEK(s)); } | |||
return 1; | |||
} | |||
DC_EXPORT inline void | |||
ns_clear(ns_t * s) | |||
{ s->top = -1; } | |||
DC_EXPORT inline int | |||
ns_reverse(ns_t * s) | |||
{ | |||
if (s->top > 0) | |||
{ | |||
mpf_swap(NS_PEEK(s), NS_PEEKN(s,-1)); | |||
return 0; | |||
} | |||
else | |||
{ return 1; } | |||
} | |||
/* nrotate = x - 1 | |||
top - n swapped with peek -x */ | |||
DC_EXPORT int | |||
ns_nrotate(ns_t * s) | |||
{ | |||
mpf_t * val = ns_pop(s); | |||
if (!val) | |||
{ NS_UNDERFLOW(1); } | |||
else | |||
{ | |||
signed long v = mpf_get_si(*val); | |||
long i = -v; | |||
const long l = -v; | |||
while (++i < v) | |||
{ | |||
mpf_swap(NS_PEEKN(s,i), NS_PEEKN(s,l)); | |||
} | |||
} | |||
return 0; | |||
} | |||
/* Registers */ | |||
ns_t g_reg[NS_REG_MAX]; | |||
void | |||
ns_reg_init(void) | |||
{ | |||
size_t i; | |||
for (i = 0; i < NS_REG_MAX; ++i) | |||
{ | |||
g_reg[i].top = -1; | |||
g_reg[i].max = 0; | |||
} | |||
} | |||
void | |||
ns_reg_free(void) | |||
{ | |||
size_t i; | |||
for (i = 0; i < NS_REG_MAX; ++i) | |||
{ ns_free(&g_reg[i]); } | |||
} | |||
DC_EXPORT int | |||
ns_reg_set(ns_t * s, int c) | |||
{ | |||
mpf_t * val = ns_pop(s); | |||
if (!val) | |||
{ NS_REG_UNDERFLOW(1, c); } | |||
if (g_reg[c].top > -1) | |||
{ mpf_set(NS_PEEKDOT(g_reg[c]), *val); } | |||
else | |||
{ ns_push(&g_reg[c], *val); } | |||
return 0; | |||
} | |||
DC_EXPORT int | |||
ns_reg_get(ns_t * s, int c) | |||
{ | |||
mpf_t * val = ns_pop(&g_reg[c]); | |||
if (!val) | |||
{ NS_REG_UNDERFLOW(1, c); } | |||
if (NS_LTMAX(s,1)) | |||
{ ns_push(s, *val); } | |||
return 0; | |||
} | |||
DC_EXPORT int | |||
ns_reg_push(ns_t * s, int c) | |||
{ | |||
mpf_t * val = ns_pop(s); | |||
if (!val) | |||
{ NS_REG_UNDERFLOW(1, c); } | |||
if (NS_LTMAXDOT(g_reg[c],1)) | |||
{ ns_push(&g_reg[c], *val); } | |||
return 0; | |||
} | |||
DC_EXPORT int | |||
ns_reg_pop(ns_t * s, int c) | |||
{ | |||
mpf_t * val = ns_pop(&g_reg[c]); | |||
if (!val) | |||
{ NS_REG_UNDERFLOW(1, c); } | |||
if (NS_LTMAX(s,1)) | |||
{ ns_push(s, *val); } | |||
return 0; | |||
} | |||
DC_EXPORT int | |||
ns_reg_push_index(ns_t * s, int c) | |||
{ | |||
mpf_t * val, * ip; | |||
ip = ns_pop(s); | |||
val = ns_pop(s); | |||
if (!ip || !val) | |||
{ NS_REG_UNDERFLOW(1, c); } | |||
else | |||
{ | |||
ssize_t i = mpf_get_si(*ip); | |||
if (NS_EXPANDDOT(g_reg[c],i+1)) | |||
{ | |||
mpf_set(g_reg[c].num[i], *val); | |||
return 0; | |||
} | |||
else return 1; | |||
} | |||
} | |||
DC_EXPORT int | |||
ns_reg_pop_index(ns_t * s, int c) | |||
{ | |||
mpf_t * ip = ns_pop(s); | |||
if (!ip) | |||
{ NS_REG_UNDERFLOW(1, c); } | |||
else | |||
{ | |||
ssize_t i; | |||
i = mpf_get_ui(*ip); | |||
if (NS_LTMAX(s,1) && NS_EXPANDDOT(g_reg[c],i+1)) | |||
{ | |||
ns_push(s, g_reg[c].num[i]); | |||
return 0; | |||
} | |||
else return 1; | |||
} | |||
} | |||
/* Printing */ | |||
DC_EXPORT void | |||
ns_printline_all(ns_t * s) | |||
{ | |||
ssize_t i; | |||
#ifdef FOR_HUMANS | |||
for (i = 0; i <= s->top; ++i) | |||
#else | |||
for (i = s->top; i >= 0; --i) | |||
#endif /* FOR_HUMANS */ | |||
{ gmp_printf(NS_FORMAT, s->num[i]); } | |||
} | |||
DC_EXPORT inline void | |||
ns_printline_peek(ns_t * s) | |||
{ | |||
if (s->top > -1) | |||
{ gmp_printf(NS_FORMAT, NS_PEEK(s)); } | |||
} | |||
DC_EXPORT inline int | |||
ns_print_peek(ns_t * s) | |||
{ | |||
if (s->top > -1) | |||
{ | |||
gmp_printf(NS_FORMAT, NS_PEEK(s)); | |||
return 0; | |||
} | |||
else | |||
{ return 1; } | |||
} | |||
#undef PRINT | |||
/* Numbers */ | |||
DC_EXPORT inline int | |||
ns_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': | |||
case 'A': case 'B': case 'C': case 'D': case 'E': | |||
case 'F': | |||
return 1; | |||
default: | |||
return 0; | |||
} | |||
} | |||
DC_EXPORT size_t | |||
ns_getnum(ns_t * s, char * eval, size_t len, int base) | |||
{ | |||
size_t i = 0; | |||
char t; | |||
while (ns_isnum(eval[i]) && i < len) { ++i; } | |||
if (!i) | |||
{ return 0; } | |||
if (NS_LTMAX(s,1)) | |||
{ | |||
++s->top; | |||
t = eval[i]; | |||
eval[i] = '\0'; | |||
mpf_set_str(NS_PEEK(s), eval, base); | |||
eval[i] = t; | |||
return i-1; | |||
} | |||
else | |||
{ NS_OVERFLOW(0); } | |||
} | |||
/* Digits */ | |||
#if 0 | |||
DC_EXPORT int | |||
ns_digit_last(ns_t * s) | |||
{ | |||
return 1; | |||
} | |||
DC_EXPORT int | |||
ns_digit(ns_t * s) | |||
{ | |||
return 1; | |||
} | |||
#endif /* 0 */ | |||
/* Arithmetic */ | |||
/* handles negative numbers and is multi-precision on both operands unlike mpf_exp_ui */ | |||
DC_EXPORT int | |||
ns_exp(ns_t * s) | |||
{ | |||
if (s->top > 0) | |||
{ | |||
if (!(mpf_cmp_ui(NS_PEEK(s), 0) || | |||
mpf_cmp_ui(s->num[s->top-1], 0))) | |||
{ fprintf(stderr, "dc: divide by 0\n"); return 2; } | |||
else | |||
{ | |||
mpf_t i; | |||
mpf_t mpf; | |||
if (mpf_cmp_ui(NS_PEEK(s), 0) == 0) | |||
{ mpf_set_ui(NS_PEEK(s), 1); return 0; } | |||
--s->top; | |||
mpf_inits(i, mpf, NULL); | |||
mpf_set(mpf, NS_PEEK(s)); | |||
mpf_set(i, s->num[s->top + 1]); | |||
mpf_ceil(i, i); | |||
mpf_sub_ui(i, i, 1); | |||
if (mpf_cmp_ui(i, 0) > 0) | |||
{ | |||
for (; mpf_cmp_ui(i, 0); mpf_sub_ui(i, i, 1)) | |||
{ mpf_mul(NS_PEEK(s), NS_PEEK(s), mpf); } | |||
} | |||
else | |||
{ | |||
for (; mpf_cmp_ui(i, 0); mpf_add_ui(i, i, 1)) | |||
{ mpf_div(NS_PEEK(s), NS_PEEK(s), mpf); } | |||
} | |||
mpf_clears(i, mpf, NULL); | |||
return 0; | |||
} | |||
} | |||
NS_UNDERFLOW(1); | |||
} | |||
NS_OP(ns_add, mpf_add) | |||
NS_OP(ns_sub, mpf_sub) | |||
NS_OP(ns_mul, mpf_mul) | |||
NS_OP_ONE(ns_abs, mpf_abs) | |||
NS_OP_ONE(ns_sqrt, mpf_sqrt) | |||
NS_OP_ONE(ns_floor, mpf_floor) | |||
NS_OP_ONE(ns_ceil, mpf_ceil) | |||
NS_OP_DIV(ns_div, mpf_div) | |||
/* Params */ | |||
DC_EXPORT int | |||
ns_pop_prec(ns_t * s) | |||
{ | |||
ssize_t i, f; | |||
mpf_t * mpf = ns_pop(s); | |||
if (!mpf) | |||
{ NS_UNDERFLOW(1); } | |||
else | |||
{ | |||
size_t prec = mpf_get_ui(*mpf); | |||
/* fprintf(stderr, "new prec: %ld\n", prec); */ | |||
g_prec = prec; | |||
for (i = 0; i < s->max; ++i) | |||
{ mpf_set_prec(s->num[i], prec); } | |||
for (f = NS_REG_OFF; f < NS_REG_MAX; ++f) | |||
{ | |||
for (i = 0; i < g_reg[f].max; ++i) | |||
{ mpf_set_prec(g_reg[f].num[i], prec); } | |||
} | |||
return 0; | |||
} | |||
} | |||
DC_EXPORT int | |||
ns_push_prec(ns_t * s) | |||
{ return ns_push_ui(s, g_prec); } | |||
DC_EXPORT int | |||
ns_pop_iradix(ns_t * s) | |||
{ | |||
mpf_t * mpf = ns_pop(s); | |||
if (!mpf) | |||
{ NS_UNDERFLOW(1); } | |||
else | |||
{ | |||
int x = (int) mpf_get_ui(*mpf); | |||
if (2 < x && x < INT_MAX - 1) | |||
{ fprintf(stderr, PROGN ": input base must be a number between 2 and %d (inclusive)\n", INT_MAX - 1); } | |||
return 0; | |||
} | |||
} | |||
DC_EXPORT int | |||
ns_push_iradix(ns_t * s) | |||
{ return ns_push_ui(s, g_iradix); } | |||
DC_EXPORT int | |||
ns_pop_oradix(ns_t * s) | |||
{ | |||
mpf_t * mpf = ns_pop(s); | |||
if (!mpf) | |||
{ NS_UNDERFLOW(1); } | |||
else | |||
{ | |||
g_oradix = (int) mpf_get_ui(*mpf); | |||
return 0; | |||
} | |||
} | |||
DC_EXPORT int | |||
ns_push_oradix(ns_t * s) | |||
{ return ns_push_ui(s, g_oradix); } | |||
/* Test Functions */ | |||
#if 0 | |||
/* It probably shouldn't return -1 but I'm not worried about this function */ | |||
DC_EXPORT int | |||
ns_ndup(ns_t * s) | |||
{ | |||
int ret = 0; | |||
mpf_t dupn; | |||
if (!ns_pop(s)) | |||
{ NS_UNDERFLOW(-1); } | |||
for (mpf_set(dupn, NS_PEEKN(s,1)); | |||
!ret && | |||
mpf_cmp_ui(dupn,1); | |||
mpf_sub_ui(dupn,dupn,1)) | |||
{ ret = ns_dup(s); } | |||
return ret; | |||
} | |||
#endif /* 0 */ | |||
/* Definition Cleanup */ | |||
/* #undef NS_OP */ | |||
/* #undef NS_OP_ONE */ | |||
/* #undef NS_OP_DIV */ | |||
/* #undef NS_OVERFLOW */ | |||
/* #undef NS_UNDERFLOW */ | |||
/* #undef NS_REG_UNDERFLOW */ | |||
/* #undef NS_DIV0 */ |
@@ -0,0 +1 @@ | |||
Public Domain. |
@@ -0,0 +1,24 @@ | |||
.POSIX: # Just kidding, use GNU Make | |||
CC := cc | |||
CFLAGS := -std=c99 -Wall -Wextra -Wpedantic | |||
LDFLAGS := -lm -lgmp -lreadline | |||
OBJ := dc.o | |||
ifeq ($(debug),1) | |||
CFLAGS += -Og -g | |||
else | |||
CFLAGS += -O3 -funroll-loops -fomit-frame-pointer | |||
endif | |||
%.o: %.c | |||
$(CC) $(CFLAGS) -c -o $@ $< | |||
dc: $(OBJ) | |||
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) | |||
dc.o: ns.c arith.c slurp.c | |||
clean: | |||
$(RM) $(OBJ) dc |
@@ -0,0 +1 @@ | |||
90 120+p |
@@ -0,0 +1,104 @@ | |||
#define ARITH(fn, op) \ | |||
static inline int \ | |||
fn(ns_t * s) \ | |||
{ \ | |||
if (s->top > 0) \ | |||
{ \ | |||
--s->top; \ | |||
op(NS_PEEK(s), \ | |||
NS_PEEK(s), s->num[s->top+1]); \ | |||
return 0; \ | |||
} \ | |||
else \ | |||
{ \ | |||
fprintf(stderr, "dc: underflow\n"); \ | |||
return 1; \ | |||
} \ | |||
} | |||
#define ARITH_ONE(fn, op) \ | |||
static inline int \ | |||
fn(ns_t * s) \ | |||
{ \ | |||
if (s->top > -1) \ | |||
{ \ | |||
op(NS_PEEK(s), \ | |||
NS_PEEK(s)); \ | |||
return 0; \ | |||
} \ | |||
else \ | |||
{ \ | |||
fprintf(stderr, "dc: underflow\n"); \ | |||
return 1; \ | |||
} \ | |||
} | |||
#define ARITH_DIV(fn, op) \ | |||
static inline int fn(ns_t * s) \ | |||
{ \ | |||
if (s->top > 0) \ | |||
{ \ | |||
if (!(mpf_cmp_ui(NS_PEEK(s), 0) || \ | |||
mpf_cmp_ui(s->num[s->top-1], 0))) \ | |||
{ fprintf(stderr, "dc: divide by 0\n"); return 2; } \ | |||
else \ | |||
{ \ | |||
--s->top; op(NS_PEEK(s), NS_PEEK(s), s->num[s->top+1]); \ | |||
return 0; \ | |||
} \ | |||
} \ | |||
else \ | |||
{ fprintf(stderr, "dc: underflow\n"); return 1; } \ | |||
} | |||
/* handles negative numbers and is multi-presision unlike mpf_exp_ui */ | |||
static inline int ns_exp(ns_t * s) | |||
{ | |||
if (s->top > 0) | |||
{ | |||
if (!(mpf_cmp_ui(NS_PEEK(s), 0) || | |||
mpf_cmp_ui(s->num[s->top-1], 0))) | |||
{ fprintf(stderr, "dc: divide by 0\n"); return 2; } | |||
else | |||
{ | |||
mpf_t i; | |||
mpf_t mpf; | |||
if (mpf_cmp_ui(NS_PEEK(s), 0) == 0) | |||
{ mpf_set_ui(NS_PEEK(s), 1); return 0; } | |||
--s->top; | |||
mpf_inits(i, mpf, NULL); | |||
mpf_set(mpf, NS_PEEK(s)); | |||
mpf_set(i, s->num[s->top + 1]); | |||
mpf_ceil(i, i); | |||
mpf_sub_ui(i, i, 1); | |||
if (mpf_cmp_ui(i, 0) > 0) | |||
{ | |||
for (; mpf_cmp_ui(i, 0); mpf_sub_ui(i, i, 1)) | |||
{ mpf_mul(NS_PEEK(s), NS_PEEK(s), mpf); } | |||
} | |||
else | |||
{ | |||
for (; mpf_cmp_ui(i, 0); mpf_add_ui(i, i, 1)) | |||
{ mpf_div(NS_PEEK(s), NS_PEEK(s), mpf); } | |||
} | |||
mpf_clears(i, mpf, NULL); | |||
return 0; | |||
} | |||
{ | |||
} | |||
} | |||
else | |||
{ fprintf(stderr, "dc: underflow\n"); return 1; } | |||
__builtin_unreachable(); | |||
} | |||
ARITH(ns_add, mpf_add) | |||
ARITH(ns_sub, mpf_sub) | |||
ARITH(ns_mul, mpf_mul) | |||
ARITH_DIV(ns_div, mpf_div) | |||
ARITH_DIV(ns_mod, mpf_div) | |||
ARITH_ONE(ns_abs, mpf_abs) | |||
ARITH_ONE(ns_sqrt, mpf_sqrt) | |||
ARITH_ONE(ns_floor, mpf_floor) | |||
ARITH_ONE(ns_ceil, mpf_ceil) |
@@ -0,0 +1,278 @@ | |||
/* 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; | |||
} |
@@ -0,0 +1,165 @@ | |||
#define NS_DEFAULT (2 << 11) /* 4096 */ | |||
#define MAX_PREC 4 | |||
#define PRINT_FORMAT "%.Ff" | |||
#define PRINTLINE_FORMAT PRINT_FORMAT "\n" | |||
#define PERROR_RETURN(str,ret) do { perror(str); return ret; } while (1) | |||
#define NS_EXPAND(s) \ | |||
do \ | |||
{ \ | |||
if (s->top + 2 >= (ssize_t) s->max) \ | |||
{ \ | |||
if (ns_expand(s, s->max << 2)) \ | |||
{ return 1; } \ | |||
if (!s->num) \ | |||
{ return 1; } \ | |||
} \ | |||
++s->top; \ | |||
} while (0) | |||
#define NS_DEINT(s)\ | |||
#define NS_PEEK(s) s->num[s->top] | |||
#define NS_PEEKN(s,n) s->num[s->top + n] | |||
typedef struct | |||
{ | |||
ssize_t max; | |||
ssize_t top; /* top < NS_MAX */ | |||
mpf_t * num; | |||
} ns_t; /* number stack */ | |||
static ns_t * | |||
ns_init(ssize_t max) | |||
{ | |||
ssize_t i; | |||
ns_t * s = (ns_t *) malloc(sizeof(ns_t)); | |||
if (!s) | |||
{ PERROR_RETURN(PROGN, NULL); } | |||
s->num = malloc(max * sizeof(mpf_t)); | |||
if (!s->num) | |||
{ PERROR_RETURN(PROGN, NULL); } | |||
s->max = max; | |||
s->top = -1; | |||
for (i = 0; i < s->max; ++i) | |||
{ mpf_init(s->num[i]); } | |||
return s; | |||
} | |||
static void | |||
ns_free(ns_t * s) | |||
{ | |||
ssize_t i; | |||
/* fprintf(stderr, "Freeing %ld blocks\n", s->max); */ | |||
for (i = 0; i < s->max; ++i) | |||
{ mpf_clear(s->num[i]); } | |||
free(s->num); | |||
free(s); | |||
} | |||
int | |||
ns_expand(ns_t * s, ssize_t newmax) | |||
{ | |||
/* fprintf(stderr, "Expanding to %ld blocks\n", newmax); */ | |||
assert(newmax > s->max); | |||
s->num = realloc(s->num, (size_t) newmax * sizeof(mpf_t)); | |||
if (!s->num) | |||
{ PERROR_RETURN(PROGN, 1); } | |||
else | |||
{ | |||
ssize_t i; | |||
for (i = s->max; i < newmax; ++i) | |||
{ mpf_init(s->num[i]); } | |||
s->max = newmax; | |||
return 0; | |||
} | |||
} | |||
static int | |||
ns_push(ns_t * s, mpf_t val) | |||
{ | |||
NS_EXPAND(s); | |||
mpf_set(NS_PEEK(s), val); | |||
return 0; | |||
} | |||
static mpf_t * | |||
ns_pop(ns_t * s) | |||
{ | |||
if (s->top > -1) | |||
{ return &s->num[s->top--]; } | |||
else | |||
{ | |||
fprintf(stderr, PROGN ": underflow\n"); | |||
return NULL; | |||
} | |||
} | |||
static void | |||
ns_printline_all(ns_t * s) | |||
{ | |||
ssize_t i; | |||
#ifdef FOR_HUMANS | |||
for (i = 0; i <= s->top; ++i) | |||
#else | |||
for (i = s->top; i >= 0; --i) | |||
#endif /* FOR_HUMANS */ | |||
{ gmp_printf(PRINTLINE_FORMAT, s->num[i]); } | |||
} | |||
static inline void | |||
ns_printline_peek(ns_t * s) | |||
{ | |||
if (s->top > -1) | |||
{ gmp_printf(PRINTLINE_FORMAT, NS_PEEK(s)); } | |||
} | |||
static inline int | |||
ns_print_peek(ns_t * s) | |||
{ | |||
if (s->top > -1) | |||
{ | |||
gmp_printf(PRINT_FORMAT, NS_PEEK(s)); | |||
return 0; | |||
} | |||
else | |||
{ return 1; } | |||
} | |||
static inline void | |||
ns_clear(ns_t * s) | |||
{ s->top = -1; } | |||
static inline int | |||
ns_reverse(ns_t * s) | |||
{ if (s->top > 0) | |||
{ | |||
mpf_swap(NS_PEEK(s), s->num[s->top-1]); | |||
return 0; | |||
} | |||
else | |||
{ return 1; } | |||
} | |||
static inline int | |||
ns_dup(ns_t * s) | |||
{ return ns_push(s, NS_PEEK(s)); } | |||
/* FIXME REMOVE TEST FUNCTION !!! */ | |||
static inline int | |||
ns_ndup(ns_t * s) | |||
{ | |||
int ret = 0; | |||
mpf_t dupn; | |||
if (ns_pop(s) == NULL) | |||
{ return 1; } | |||
for (mpf_set(dupn, NS_PEEKN(s,1)); | |||
!ret && | |||
mpf_cmp_ui(dupn,1); | |||
mpf_sub_ui(dupn,dupn,1)) | |||
{ ret += ns_dup(s); } | |||
return ret; | |||
} | |||
#include "arith.c" |
@@ -0,0 +1,26 @@ | |||
static inline size_t | |||
frem(FILE * fp) | |||
{ | |||
fseek(fp, 0, SEEK_END); | |||
return ftell(fp); | |||
} | |||
static char * | |||
slurp(const char * fn, size_t * rlen) | |||
{ | |||
size_t len; | |||
FILE * fp = fopen(fn, "r"); | |||
char * buf; | |||
if (!fp) | |||
{ PERROR_RETURN("fopen", NULL); } | |||
if (!(buf = (char *) malloc((len = frem(fp))))) | |||
{ PERROR_RETURN("malloc", NULL); } | |||
rewind(fp); | |||
if (len != fread(buf, 1, len, fp)) | |||
{ | |||
free(buf); | |||
{ PERROR_RETURN("fopen", NULL); } | |||
} | |||
*rlen = len; | |||
return buf; | |||
} |
@@ -0,0 +1,17 @@ | |||
Project was a more of a exploration of the exact working behind a state | |||
machine and a exploration of memory and C, and making a 'real' program, | |||
along with a repairing of a former failure and loss of code. This project | |||
is not faster, nor comparable to traditional dc. | |||
I may one day rewrite this with strictly my needs in mind. I have left | |||
the code exactly as I had it the last time I've worked on it. I have | |||
also left a 'old' copy here, because I didn't have the heart to delete | |||
it either. I just decided to upload it, I didn't upload it initial | |||
because it did not surpass GNU dc. It's been a while, and I consider | |||
it done. Now that it's uploaded I can delete it from my disk. | |||
Made public domain. Some parts might be buggy. Consider this strictly | |||
an archive, no more will be done, this project is deadware and is not | |||
to be respected in any capacity, steal and besmirch as you wish. | |||
Email me if you want to call me dumb: emilwilliams@tuta.io |
@@ -0,0 +1 @@ | |||
20 |
@@ -0,0 +1 @@ | |||
3 40 ^p |
@@ -0,0 +1 @@ | |||
test `../dc -e '20 120+p'` = '140' |
@@ -0,0 +1 @@ | |||
30+p |
@@ -0,0 +1 @@ | |||
test `../dc -e '10 4/p'` = '2.5' |
@@ -0,0 +1 @@ | |||
test `../dc -e '20 120-p'` = '-100' |