I have made a implementation of the 'dc' command in toybox

Since dc isn't specified in POSIX or LSB, the things I have
compared it to and tested against are the busybox implementation 
of the command, and the GNU implementation.

My implementation includes most things from the busybox implementation missing 
a few things, 
and a lot of the GNU stuff that was easy to include.

The main things that my implementation can't do yet are arbitrary
precision math, input and output radixes, and stings, macros or conditionals.

I know that bc.c has a arbitrary precision library in it somewhere, 
But I haven't looked through the ~5700 lines of code
for the command to see how they got it to work or how it could be shared with 
dc yet
Everything that dc works with is just a long double as of now

Input and output radixes are much more trivial to get to work but much less 
important (Have less real use cases),
I know that strtoll and friends have a input radix argument but I haven't 
looked into it that much yet.

As for strings macros and conditionals, I was originally planning to put these 
in because 
it would turn what is essentially expr or $((shell math)) but more confusing, 
into a Turing complete language
with branching, loops, recursion, etc. The main problem with that is that it 
turns the simple 2 functions you
need to manipulate the stack in my implementation into at least 6 with separate 
error checking for every command
because a value on the stack can either store a string or number so _every_ 
command needs to make sure it operates
on the right one.

There are some bugs with it as well, mainly memory leaks I haven't diagnosed 
yet.
They only leak the commands given to it so the cases where it's actually 
apparent we should be freeing memory
is stuff like `yes p | dc` 

The other main bug I have found is that the part of the program 
that gets statements to process _only_ breaks statements with whitespace
so `2d+p` will not print 4 like it should and only adds 2 onto the stack.
From 60fda44c3fe4a880cac2dd59527560a238db78a3 Mon Sep 17 00:00:00 2001
From: Oliver Webb <aquahobby...@proton.me>
Date: Wed, 20 Sep 2023 01:01:39 -0500
Subject: [PATCH] A basic dc implementation

---
 toys/pending/dc.c | 218 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 218 insertions(+)
 create mode 100644 toys/pending/dc.c

diff --git a/toys/pending/dc.c b/toys/pending/dc.c
new file mode 100644
index 00000000..0a22546a
--- /dev/null
+++ b/toys/pending/dc.c
@@ -0,0 +1,218 @@
+/* dc.c - desk calculator
+ *
+ * Copyright 2023 Oliver Webb <aquahobby...@proton.me>
+ *
+ * See https://linux.die.net/man/1/dc
+ * And https://busybox.net/downloads/BusyBox.html#dc
+
+USE_DC(NEWTOY(dc, "e:f:", TOYFLAG_USR|TOYFLAG_BIN))
+
+config DC
+	bool "dc"
+	default n
+	help
+		usage: dc [-e EXPRESSION] [-f FILE]
+		
+		A small RPN stack based calculator
+
+		-e EXPRESSION Evaluate EXPRESSION
+		-f FILE Evaluate FILE
+		If neither of these options are specified, dc reads from stdin
+
+		Command list
+
+		Stack control
+		NUM Pushes NUM onto the stack
+		d   Duplicates top value on the stack
+		c   Clears the stack
+		z   Pushes stack index onto the stack
+		r   Reverses the Placement of the top 2 items of the stack
+
+		Math Operators
+		+   Pops 2 items, pushes the sum of them
+		-   Pops 2 items, pushes the value of the second subtracted from the first
+		*   Pops 2 items, pushes the product of them
+		/   Pops 2 items, pushes the value of the second divided by the first
+		%   Pops 2 items, pushes the second modulo the first, Items treated as integers
+		~   Pops 2 items, pushes the quotient, then the remainder of the division
+		^   Pops 2 items, pushes the exponent of the second raised to the first
+		v   Pops 1 item,  pushes the sqare root of that item
+
+		Input and output
+		p   Prints the value of the top item, does not pop it
+		n   Prints the value of the top item without a newline, pops the item
+		f   Prints the value of all items on the stack
+		?   Evaluates a line read from stdin
+
+*/
+
+#define FOR_dc
+#include "toys.h"
+
+GLOBALS(
+		char *f;
+		char *e;
+
+		size_t stacki, stacksize;
+		long double *stack;
+)
+
+static int pushs(long double value)
+{
+  TT.stacki++;
+  if (TT.stacki >= TT.stacksize) {
+    TT.stacksize += 16;
+    TT.stack = realloc(TT.stack, TT.stacksize);
+  }
+  TT.stack[TT.stacki] = value;
+  return 0;
+}
+
+static long double pops(void)
+{
+  if (TT.stacki<1) return 0;
+  long double stackv = TT.stack[TT.stacki];
+  TT.stack[TT.stacki] = 0;
+  TT.stacki--;
+  return stackv;
+}
+
+static char *substring(char *str, size_t start, size_t stop)
+{
+  char *dest = strdup(str);
+  strncpy(dest, str+start, (stop-start)+1);
+  dest[(stop-start)+1] = '\0';
+  return dest;
+}
+
+static void evaluate_dc(char *expr)
+{
+  long double opta = 0, optb = 0;
+  char *word;
+  int in = 0, start = 0, k = 0, stop;
+
+  while (k<strlen(expr)) {
+    while (1) {
+      if ((k >= strlen(expr)) || (in && expr[k] == ' ')) {
+				in = 0;
+				stop = k;
+				word = substring(expr, start, stop);
+				break;
+			} else if (!in && expr[k] != ' ') {
+				in = 1;
+				start = k;
+      }
+      k++;
+    }
+
+    // Number Pushing
+		if (word[0] >= '0' && word[0] <= '9')
+      pushs(strtold(word, 0));
+		else if (TT.stacki < 1) {
+			error_msg("no stack");
+			break;
+		}
+    else switch (word[0]) {
+		// Stack Control
+    case 'c':
+			for (int i = 0; i<sizeof(*TT.stack)/sizeof(long double); i++)
+				TT.stack[i] = 0;
+			TT.stacki = 0;
+		break;
+
+    case 'd':
+			pushs((opta = pops()));
+			pushs(opta);
+		break;
+
+    case 'r':
+			opta = pops();
+			optb = pops();
+			pushs(optb);
+			pushs(opta);
+		break;
+
+    case 'z':
+			pushs((long double)TT.stacki);
+		break;
+
+		// Math Operators
+    case '*': pushs(pops()*pops()); break;
+    case '+': pushs(pops()+pops()); break;
+
+    case '-':
+			opta = pops();
+			optb = pops();
+			pushs(optb-opta);
+		break;
+
+		case '/':
+			opta = pops();
+			if (!(optb = pops())) {
+				error_msg("div by 0");
+			continue;
+			}
+			pushs(optb/opta);
+		break;
+
+    case '%':
+			optb = pops();
+			if (!(opta = pops())) {
+				error_msg("mod of 0");
+				continue;
+			}
+			pushs((long double)((int)opta%(int)optb));
+		break;
+
+    case 'v':
+			if ((opta = pops()) <= 0) {
+				error_msg("sqrt of negative");
+				continue;
+			}
+			pushs(sqrtl(opta));
+		break;
+
+    case '^':
+			pushs(powl(pops(), pops()));
+		break;
+
+		case '~':
+			if (!(opta = pops()) || !(optb = pops())) {
+				error_msg("div or mod on 0");
+				continue;
+			}
+			pushs(optb/opta);
+			pushs((long double)((int)optb%(int)opta));
+		break;
+
+		// I/O
+    case 'p': printf("%Lg\n", TT.stack[TT.stacki]); break;
+    case 'n': printf("%Lg", pops()); break;
+
+    case 'f':
+			int i = TT.stacki;
+			for (; i>0; i--) printf("%Lg\n", TT.stack[i]);
+		break;
+
+    case '?':
+			char *line;
+			evaluate_dc((line = xgetline(stdin)));
+			free(line);
+		break;
+
+    default:
+			error_msg("bad command '%s'", word);
+		break;
+    }
+  }
+}
+
+void dc_main(void)
+{
+  char *ln;
+  TT.stack = malloc((TT.stacksize = 32));
+	FILE *fin = (TT.f) ? xfopen(TT.f, "r") : stdin; 
+
+	if (TT.e) evaluate_dc(TT.e);
+  else while ((ln = xgetline(fin))) evaluate_dc(ln);
+}
-- 
2.34.1

_______________________________________________
Toybox mailing list
Toybox@lists.landley.net
http://lists.landley.net/listinfo.cgi/toybox-landley.net

Reply via email to