commit d29226268d199c3d730f30c36af1d9dc7a9facc8 Author: Emil Date: Sat Sep 23 17:26:21 2023 +0000 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5761abc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.o diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be3a1de --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +Public Domain. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..58fb977 --- /dev/null +++ b/Makefile @@ -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) diff --git a/README b/README new file mode 100644 index 0000000..793d87e --- /dev/null +++ b/README @@ -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. diff --git a/README.2 b/README.2 new file mode 100644 index 0000000..a5d69a7 --- /dev/null +++ b/README.2 @@ -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. diff --git a/config.h b/config.h new file mode 100644 index 0000000..90b2443 --- /dev/null +++ b/config.h @@ -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 diff --git a/config.mk.h b/config.mk.h new file mode 100644 index 0000000..90b2443 --- /dev/null +++ b/config.mk.h @@ -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 diff --git a/dc.c b/dc.c new file mode 100644 index 0000000..1021b29 --- /dev/null +++ b/dc.c @@ -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 +#include +#include +#include +#include +#include + +#include +#include + +#include + +#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 .\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 diff --git a/ns.c b/ns.c new file mode 100644 index 0000000..f98f182 --- /dev/null +++ b/ns.c @@ -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 */ diff --git a/old-dc/LICENSE b/old-dc/LICENSE new file mode 100644 index 0000000..be3a1de --- /dev/null +++ b/old-dc/LICENSE @@ -0,0 +1 @@ +Public Domain. diff --git a/old-dc/Makefile b/old-dc/Makefile new file mode 100644 index 0000000..bb6bb79 --- /dev/null +++ b/old-dc/Makefile @@ -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 diff --git a/old-dc/add.dc b/old-dc/add.dc new file mode 100644 index 0000000..cce2c9f --- /dev/null +++ b/old-dc/add.dc @@ -0,0 +1 @@ +90 120+p diff --git a/old-dc/arith.c b/old-dc/arith.c new file mode 100644 index 0000000..2dde5bd --- /dev/null +++ b/old-dc/arith.c @@ -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) diff --git a/old-dc/dc.c b/old-dc/dc.c new file mode 100644 index 0000000..d291707 --- /dev/null +++ b/old-dc/dc.c @@ -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 +#include +#include +#include +#include + +#include +#include + +#include + +#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 .\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; +} diff --git a/old-dc/ns.c b/old-dc/ns.c new file mode 100644 index 0000000..223362a --- /dev/null +++ b/old-dc/ns.c @@ -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" diff --git a/old-dc/slurp.c b/old-dc/slurp.c new file mode 100644 index 0000000..166e9a0 --- /dev/null +++ b/old-dc/slurp.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; +} diff --git a/retrospective b/retrospective new file mode 100644 index 0000000..af9f8f9 --- /dev/null +++ b/retrospective @@ -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 diff --git a/test/a.dc b/test/a.dc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/test/a.dc @@ -0,0 +1 @@ +20 diff --git a/test/add.dc b/test/add.dc new file mode 100644 index 0000000..cb8912c --- /dev/null +++ b/test/add.dc @@ -0,0 +1 @@ +3 40 ^p diff --git a/test/add.sh b/test/add.sh new file mode 100755 index 0000000..90ccf56 --- /dev/null +++ b/test/add.sh @@ -0,0 +1 @@ +test `../dc -e '20 120+p'` = '140' diff --git a/test/b.dc b/test/b.dc new file mode 100644 index 0000000..5421df2 --- /dev/null +++ b/test/b.dc @@ -0,0 +1 @@ +30+p diff --git a/test/div.sh b/test/div.sh new file mode 100755 index 0000000..e36b99a --- /dev/null +++ b/test/div.sh @@ -0,0 +1 @@ +test `../dc -e '10 4/p'` = '2.5' diff --git a/test/sub.sh b/test/sub.sh new file mode 100755 index 0000000..69bc41f --- /dev/null +++ b/test/sub.sh @@ -0,0 +1 @@ +test `../dc -e '20 120-p'` = '-100'