From 52a4995d6c15c05348e797e2f6deed86811f7b5b Mon Sep 17 00:00:00 2001 From: Lewis Van Winkle Date: Tue, 23 Feb 2016 15:18:44 -0600 Subject: [PATCH] Added comma operator, builtin functions of arity 0 and 2. --- README.md | 9 +++- test.c | 35 +++++++++++++-- tinyexpr.c | 127 +++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 132 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 6b0e17d..5e1361e 100644 --- a/README.md +++ b/README.md @@ -214,11 +214,12 @@ Here is some example performance numbers taken from the included TinyExpr parses the following grammar: + = {"," } = {("+" | "-") } = {("*" | "/" | "%") } = {"^" } = {("-" | "+")} - = | | | "(" ")" + = | | | "(" "," ")" | "(" ")" In addition, whitespace between tokens is ignored. @@ -236,7 +237,11 @@ left-to-right). In addition, the following C math functions are also supported: -- abs (calls to *fabs*), acos, asin, atan, ceil, cos, cosh, exp, floor, ln (calls to *log*), log (calls to *log10*), sin, sinh, sqrt, tan, tanh +- abs (calls to *fabs*), acos, asin, atan, atan2, ceil, cos, cosh, exp, floor, ln (calls to *log*), log (calls to *log10*), pow, sin, sinh, sqrt, tan, tanh + +Also, the following constants are available: + +- `pi`, `e` ##Hints diff --git a/test.c b/test.c index 07ad0bd..7674090 100644 --- a/test.c +++ b/test.c @@ -37,6 +37,10 @@ test_case cases[] = { {"1", 1}, {"(1)", 1}, + {"pi", 3.14159}, + {"atan(1)*4 - pi", 0}, + {"e", 2.71828}, + {"2+1", 2+1}, {"(((2+(1))))", 2+1}, {"3+2", 3+2}, @@ -78,13 +82,13 @@ test_case cases[] = { {"asin sin (-0.5)", -0.5}, {"(asin sin (-0.5))", -0.5}, - {"log1000", 3}, - {"log1e3", 3}, + {"log 1000", 3}, + {"log 1e3", 3}, {"log 1000", 3}, {"log 1e3", 3}, {"log(1000)", 3}, {"log(1e3)", 3}, - {"log1.0e3", 3}, + {"log 1.0e3", 3}, {"10^5*5e-5", 5}, {"100^.5+1", 11}, @@ -103,6 +107,23 @@ test_case cases[] = { {"sqrt 100 * 7", 70}, {"sqrt (100 * 100)", 100}, + {"1,2", 2}, + {"1,2,3", 3}, + {"(1,2),3", 3}, + {"1,(2,3)", 3}, + + {"2^2", 4}, + {"pow(2,2)", 4}, + + {"atan2(1,1)", 0.7854}, + {"atan2(1,2)", 0.4636}, + {"atan2(2,1)", 1.1071}, + {"atan2(3,4)", 0.6435}, + {"atan2(3+3,4*2)", 0.6435}, + {"atan2(3+3,(4*2))", 0.6435}, + {"atan2((3+3),4*2)", 0.6435}, + {"atan2((3+3),(4*2))", 0.6435}, + }; @@ -141,6 +162,10 @@ void test_results() { const double ev = te_interp(expr, &err); lok(!err); lfequal(ev, answer); + + if (err) { + printf("FAILED: %s (%d)\n", expr, err); + } } } @@ -160,6 +185,10 @@ void test_syntax() { lequal(err, e); lok(!n); + if (err != e) { + printf("FAILED: %s\n", expr); + } + const double k = te_interp(expr, 0); lok(k != k); } diff --git a/tinyexpr.c b/tinyexpr.c index 5a44685..1f254e0 100644 --- a/tinyexpr.c +++ b/tinyexpr.c @@ -29,7 +29,7 @@ #include -enum {TOK_NULL, TOK_END, TOK_OPEN, TOK_CLOSE, TOK_NUMBER, TOK_ADD, TOK_SUB, TOK_MUL, TOK_DIV, TOK_FUNCTION1, TOK_FUNCTION2, TOK_VARIABLE, TOK_ERROR}; +enum {TOK_NULL, TOK_END, TOK_OPEN, TOK_CLOSE, TOK_NUMBER, TOK_INFIX, TOK_FUNCTION1, TOK_FUNCTION2, TOK_VARIABLE, TOK_SEP, TOK_ERROR}; @@ -65,28 +65,36 @@ void te_free(te_expr *n) { typedef struct { const char *name; - te_fun1 f1; + union {const void *v; double *value; te_fun1 f1; te_fun2 f2;}; + int arity; } builtin; +static const double pi = 3.14159265358979323846; +static const double e = 2.71828182845904523536; + static const builtin functions[] = { /* must be in alphabetical order */ - {"abs", fabs}, - {"acos", acos}, - {"asin", asin}, - {"atan", atan}, - {"ceil", ceil}, - {"cos", cos}, - {"cosh", cosh}, - {"exp", exp}, - {"floor", floor}, - {"ln", log}, - {"log", log10}, - {"sin", sin}, - {"sinh", sinh}, - {"sqrt", sqrt}, - {"tan", tan}, - {"tanh", tanh}, + {"abs", {fabs}, 1}, + {"acos", {acos}, 1}, + {"asin", {asin}, 1}, + {"atan", {atan}, 1}, + {"atan2", {atan2}, 2}, + {"ceil", {ceil}, 1}, + {"cos", {cos}, 1}, + {"cosh", {cosh}, 1}, + {"e", {&e}, 0}, + {"exp", {exp}, 1}, + {"floor", {floor}, 1}, + {"ln", {log}, 1}, + {"log", {log10}, 1}, + {"pi", {&pi}, 0}, + {"pow", {pow}, 2}, + {"sin", {sin}, 1}, + {"sinh", {sinh}, 1}, + {"sqrt", {sqrt}, 1}, + {"tan", {tan}, 1}, + {"tanh", {tanh}, 1}, {0} }; @@ -131,6 +139,7 @@ static double sub(double a, double b) {return a - b;} static double mul(double a, double b) {return a * b;} static double divide(double a, double b) {return a / b;} static double negate(double a) {return -a;} +static double comma(double a, double b) {return b;} void next_token(state *s) { @@ -152,7 +161,7 @@ void next_token(state *s) { if (s->next[0] >= 'a' && s->next[0] <= 'z') { const char *start; start = s->next; - while (s->next[0] >= 'a' && s->next[0] <= 'z') s->next++; + while ((s->next[0] >= 'a' && s->next[0] <= 'z') || (s->next[0] >= '0' && s->next[0] <= '9')) s->next++; const double *var = find_var(s, start, s->next - start); if (var) { @@ -162,12 +171,20 @@ void next_token(state *s) { if (s->next - start > 15) { s->type = TOK_ERROR; } else { - s->type = TOK_FUNCTION1; const builtin *f = find_function(start, s->next - start); if (!f) { s->type = TOK_ERROR; } else { - s->f1 = f->f1; + if (f->arity == 0) { + s->type = TOK_NUMBER; + s->value = *f->value; + } else if (f->arity == 1) { + s->type = TOK_FUNCTION1; + s->f1 = f->f1; + } else if (f->arity == 2) { + s->type = TOK_FUNCTION2; + s->f2 = f->f2; + } } } } @@ -175,14 +192,15 @@ void next_token(state *s) { } else { /* Look for an operator or special character. */ switch (s->next++[0]) { - case '+': s->type = TOK_FUNCTION2; s->f2 = add; break; - case '-': s->type = TOK_FUNCTION2; s->f2 = sub; break; - case '*': s->type = TOK_FUNCTION2; s->f2 = mul; break; - case '/': s->type = TOK_FUNCTION2; s->f2 = divide; break; - case '^': s->type = TOK_FUNCTION2; s->f2 = pow; break; - case '%': s->type = TOK_FUNCTION2; s->f2 = fmod; break; + case '+': s->type = TOK_INFIX; s->f2 = add; break; + case '-': s->type = TOK_INFIX; s->f2 = sub; break; + case '*': s->type = TOK_INFIX; s->f2 = mul; break; + case '/': s->type = TOK_INFIX; s->f2 = divide; break; + case '^': s->type = TOK_INFIX; s->f2 = pow; break; + case '%': s->type = TOK_INFIX; s->f2 = fmod; break; case '(': s->type = TOK_OPEN; break; case ')': s->type = TOK_CLOSE; break; + case ',': s->type = TOK_SEP; break; case ' ': case '\t': case '\n': case '\r': break; default: s->type = TOK_ERROR; break; } @@ -192,11 +210,12 @@ void next_token(state *s) { } +static te_expr *list(state *s); static te_expr *expr(state *s); static te_expr *power(state *s); static te_expr *base(state *s) { - /* = | | | "(" ")" */ + /* = | | | "(" "," ")" | "(" ")" */ te_expr *ret; switch (s->type) { @@ -219,9 +238,35 @@ static te_expr *base(state *s) { ret->left = power(s); break; + case TOK_FUNCTION2: + ret = new_expr(0, 0); + ret->f2 = s->f2; + next_token(s); + + if (s->type != TOK_OPEN) { + s->type = TOK_ERROR; + } else { + next_token(s); + ret->left = expr(s); + + if (s->type != TOK_SEP) { + s->type = TOK_ERROR; + } else { + next_token(s); + ret->right = expr(s); + if (s->type != TOK_CLOSE) { + s->type = TOK_ERROR; + } else { + next_token(s); + } + } + } + + break; + case TOK_OPEN: next_token(s); - ret = expr(s); + ret = list(s); if (s->type != TOK_CLOSE) { s->type = TOK_ERROR; } else { @@ -243,7 +288,7 @@ static te_expr *base(state *s) { static te_expr *power(state *s) { /* = {("-" | "+")} */ int sign = 1; - while (s->type == TOK_FUNCTION2 && (s->f2 == add || s->f2 == sub)) { + while (s->type == TOK_INFIX && (s->f2 == add || s->f2 == sub)) { if (s->f2 == sub) sign = -sign; next_token(s); } @@ -265,7 +310,7 @@ static te_expr *factor(state *s) { /* = {"^" } */ te_expr *ret = power(s); - while (s->type == TOK_FUNCTION2 && (s->f2 == pow)) { + while (s->type == TOK_INFIX && (s->f2 == pow)) { te_fun2 t = s->f2; next_token(s); ret = new_expr(ret, power(s)); @@ -280,7 +325,7 @@ static te_expr *term(state *s) { /* = {("*" | "/" | "%") } */ te_expr *ret = factor(s); - while (s->type == TOK_FUNCTION2 && (s->f2 == mul || s->f2 == divide || s->f2 == fmod)) { + while (s->type == TOK_INFIX && (s->f2 == mul || s->f2 == divide || s->f2 == fmod)) { te_fun2 t = s->f2; next_token(s); ret = new_expr(ret, factor(s)); @@ -295,7 +340,7 @@ static te_expr *expr(state *s) { /* = {("+" | "-") } */ te_expr *ret = term(s); - while (s->type == TOK_FUNCTION2 && (s->f2 == add || s->f2 == sub)) { + while (s->type == TOK_INFIX && (s->f2 == add || s->f2 == sub)) { te_fun2 t = s->f2; next_token(s); ret = new_expr(ret, term(s)); @@ -306,6 +351,20 @@ static te_expr *expr(state *s) { } +static te_expr *list(state *s) { + /* = {"," } */ + te_expr *ret = expr(s); + + while (s->type == TOK_SEP) { + next_token(s); + ret = new_expr(ret, term(s)); + ret->f2 = comma; + } + + return ret; +} + + double te_eval(const te_expr *n) { double ret; @@ -356,7 +415,7 @@ te_expr *te_compile(const char *expression, const te_variable *variables, int va s.lookup_len = var_count; next_token(&s); - te_expr *root = expr(&s); + te_expr *root = list(&s); if (s.type != TOK_END) { te_free(root);