diff q/expr.c @ 14:388074ff9474

Add fixed point code
author Daniel O'Connor <darius@dons.net.au>
date Tue, 25 Feb 2025 13:28:29 +1030
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/q/expr.c	Tue Feb 25 13:28:29 2025 +1030
@@ -0,0 +1,245 @@
+/* Project:  Small Expression Evaluator for Q library
+ * Author:   Richard James Howe
+ * License:  The Unlicense
+ * See: <https://en.wikipedia.org/wiki/Shunting-yard_algorithm> */
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "q.h"
+
+#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
+#define DEFAULT_STACK_SIZE      (64)
+
+enum { ASSOCIATE_NONE, ASSOCIATE_LEFT, ASSOCIATE_RIGHT, };
+enum { LEX_NUMBER, LEX_OPERATOR, LEX_END, };
+
+static void expr_delete(qexpr_t *e) {
+	if (!e)
+		return;
+	free(e->ops);
+	free(e->numbers);
+	for (size_t i = 0; i < e->vars_max; i++) {
+		free(e->vars[i]->name);
+		free(e->vars[i]);
+	}
+	free(e->vars);
+	free(e);
+}
+
+static qexpr_t *expr_new(size_t max) {
+	max = max ? max : 64;
+	qexpr_t *e = calloc(sizeof(*e), 1);
+	if (!e)
+		goto fail;
+	e->ops     = calloc(sizeof(*e->ops), max);
+	e->numbers = calloc(sizeof(*(e->numbers)), max);
+	if (!(e->ops) || !(e->numbers))
+		goto fail;
+	e->ops_max     = max;
+	e->numbers_max = max;
+	qexpr_init(e);
+	return e;
+fail:
+	expr_delete(e);
+	return NULL;
+}
+
+static qvariable_t *variable_lookup(qexpr_t *e, const char *name) {
+	assert(e);
+	for (size_t i = 0; i < e->vars_max; i++) {
+		qvariable_t *v = e->vars[i];
+		if (!strcmp(v->name, name))
+			return v;
+	}
+	return NULL;
+}
+
+static char *estrdup(const char *s) {
+	assert(s);
+	const size_t l = strlen(s) + 1;
+	char *r = malloc(l);
+	return memcpy(r, s, l);
+}
+
+static int variable_name_is_valid(const char *n) {
+	assert(n);
+	if (!isalpha(*n) && !(*n == '_'))
+		return 0;
+	for (n++; *n; n++)
+		if (!isalnum(*n) && !(*n == '_'))
+			return 0;
+	return 1;
+}
+
+static qvariable_t *variable_add(qexpr_t *e, const char *name, q_t value) {
+	assert(e);
+	assert(name);
+	qvariable_t *v = variable_lookup(e, name), **vs = e->vars;
+	if (v) {
+		v->value = value;
+		return v;
+	}
+	if (!variable_name_is_valid(name))
+		return NULL;
+	char *s = estrdup(name);
+	vs = realloc(e->vars, (e->vars_max + 1) * sizeof(*vs));
+	v = calloc(1, sizeof(*v));
+	if (!vs || !v || !s)
+		goto fail;
+	v->name = s;
+	v->value = value;
+	vs[e->vars_max++] = v;
+	e->vars = vs;
+	return v;
+fail:
+	free(v);
+	free(s);
+	free(vs);
+	if (vs != e->vars)
+		free(e->vars);
+	return NULL;
+}
+
+static inline int tests(FILE *out) {
+	assert(out);
+	int report = 0;
+	static const struct test {
+		int r;
+		q_t result;
+		const char *expr;
+	} tests[] = { // NB. Variables defined later.
+		{  -1,  QINT( 0),   ""            },
+		{  -1,  QINT( 0),   "("           },
+		{  -1,  QINT( 0),   ")"           },
+		{  -1,  QINT( 0),   "2**3"        },
+		{   0,  QINT( 0),   "0"           },
+		{   0,  QINT( 2),   "1+1"         },
+		{   0, -QINT( 1),   "-1"          },
+		{   0,  QINT( 1),   "--1"         },
+		{   0,  QINT(14),   "2+(3*4)"     },
+		{   0,  QINT(23),   "a+(b*5)"     },
+		{  -1,  QINT(14),   "(2+(3* 4)"   },
+		{  -1,  QINT(14),   "2+(3*4)("    },
+		{   0,  QINT(14),   "2+3*4"       },
+		{   0,  QINT( 0),   "  2==3 "     },
+		{   0,  QINT( 1),   "2 ==2"       },
+		{   0,  QINT( 1),   "2== (1+1)"   },
+		//{   0,  QINT( 8),   "2 pow 3"     },
+		//{  -1,  QINT( 0),   "2pow3"       },
+		{   0,  QINT(20),   "(2+3)*4"     },
+		{   0, -QINT( 4),   "(2+(-3))*4"  },
+		{  -1,  QINT( 0),   "1/0"         },
+		{  -1,  QINT( 0),   "1%0"         },
+		{   0,  QINT(50),   "100/2"       },
+		{   0,  QINT( 2),   "1--1",       },
+		{   0,  QINT( 0),   "1---1",      },
+	};
+
+	if (fputs("Running Built In Self Tests:\n", out) < 0)
+		report = -1;
+	const size_t length = sizeof (tests) / sizeof (tests[0]);
+	for (size_t i = 0; i < length; i++) {
+		qexpr_t *e = expr_new(64);
+		const struct test *test = &tests[i];
+		if (!e) {
+			(void)fprintf(out, "test failed (unable to allocate)\n");
+			report = -1;
+			goto end;
+		}
+
+		qvariable_t *v1 = variable_add(e, "a",  QINT(3));
+		qvariable_t *v2 = variable_add(e, "b",  QINT(4));
+		qvariable_t *v3 = variable_add(e, "c", -QINT(5));
+		if (!v1 || !v2 || !v3) {
+			(void)fprintf(out, "test failed (unable to assign variable)\n");
+			report = -1;
+			goto end;
+		}
+
+		const int r = qexpr(e, test->expr);
+		const q_t tos = e->numbers[0];
+		const int pass = (r == test->r) && (r != 0 || tos == test->result);
+		if (fprintf(out, "%s: r(%2d), eval(\"%s\") = %lg \n",
+				pass ? "   ok" : " FAIL", r, test->expr, (double)tos) < 0)
+			report = -1;
+		if (!pass) {
+			(void)fprintf(out, "\tExpected: r(%2d), %lg\n",
+				test->r, (double)(test->result));
+			report = -1;
+		}
+		expr_delete(e);
+	}
+end:
+	if (fprintf(out, "Tests Complete: %s\n", report == 0 ? "pass" : "FAIL") < 0)
+		report = -1;
+	return report;
+}
+
+static qexpr_t *expr_new_with_vars(size_t max) {
+	qexpr_t *e = expr_new(max);
+	if (!e) return NULL;
+	if (!variable_add(e, "whole", qinfo.whole)) goto fail;
+	if (!variable_add(e, "fractional", qinfo.fractional)) goto fail;
+	if (!variable_add(e, "bit", qinfo.bit)) goto fail;
+	if (!variable_add(e, "smallest", qinfo.min)) goto fail;
+	if (!variable_add(e, "biggest", qinfo.max)) goto fail;
+	if (!variable_add(e, "pi", qinfo.pi)) goto fail;
+	if (!variable_add(e, "e", qinfo.e)) goto fail;
+	if (!variable_add(e, "sqrt2", qinfo.sqrt2)) goto fail;
+	if (!variable_add(e, "sqrt3", qinfo.sqrt3)) goto fail;
+	if (!variable_add(e, "ln2", qinfo.ln2)) goto fail;
+	if (!variable_add(e, "ln10", qinfo.ln10)) goto fail;
+	return e;
+fail:
+	expr_delete(e);
+	return NULL;
+}
+
+static int usage(FILE *out, const char *arg0) {
+	assert(out);
+	assert(arg0);
+	return fprintf(out, "usage: %s expr\n", arg0);
+}
+
+int main(int argc, char *argv[]) {
+
+	if (argc == 1) {
+		if (usage(stderr, argv[0]) < 0) { return 1; }
+		return tests(stderr);
+	}
+
+	if (argc < 2) {
+		(void)fprintf(stderr, "usage: %s expr\n", argv[0]);
+		return 1;
+	}
+
+	int r = 0;
+
+	for (int i = 1; i < argc; i++) {
+		qexpr_t *e = expr_new_with_vars(0);
+		if (!e) {
+			(void)fprintf(stderr, "allocate failed\n");
+			r = 1;
+			break;
+		}
+		if (qexpr(e, argv[i]) == 0) {
+			char n[64 + 1] = { 0, };
+			qsprint(e->numbers[0], n, sizeof n);
+			if (printf("%s\n", n) < 0)
+				r = 1;
+			r = 0;
+		} else {
+			(void)fprintf(stderr, "error: %s\n", e->error_string);
+			r = 1;
+		}
+		expr_delete(e);
+	}
+	return r;
+}
+
+