view 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 source

/* 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;
}