@@ -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' |