diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..665ae06 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +TINYEXPR - Tiny recursive descent parser and evaluation engine in C + +Copyright (c) 2015, 2016 Lewis Van Winkle + +http://CodePlea.com + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgement in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3ed925f --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +CC = gcc +CCFLAGS = -ansi -Wall -Wshadow -O2 $(EXTRAS) + + +all: test bench example example2 + + +test: test.o tinyexpr.o + $(CC) $(CCFLAGS) -o $@ $^ + ./$@ + + +bench: benchmark.o tinyexpr.o + $(CC) $(CCFLAGS) -o $@ $^ + +example: example.o tinyexpr.o + $(CC) $(CCFLAGS) -o $@ $^ + +example2: example2.o tinyexpr.o + $(CC) $(CCFLAGS) -o $@ $^ + +.c.o: + $(CC) -c $(CCFLAGS) $< -o $@ + + +clean: + rm *.o + rm *.exe diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a19e6d --- /dev/null +++ b/README.md @@ -0,0 +1,240 @@ +#TINYEXPR + + +TINYEXPR is a very small recursive descent parser and evaluation engine for +math expressions. It's handy when you want to add the ability to evaluation +math expressions at runtime without adding a bunch of cruft to you project. + +In addition to the standard math operators and precedence, TINYEXPR also supports +the standard C math functions and runtime binding of variables. + +##Features + +- **ANSI C with no dependencies**. +- Single source file and header file. +- Simple and fast. +- Implements standard operators precedence. +- Exposes standard C math functions (sin, sqrt, ln, etc.). +- Can bind variables at eval-time. +- Released under the zlib license - free for nearly any use. +- Easy to use and integrate with your code +- Thread-safe, provided that your *malloc* is. + +##Short Example + +Here is a minimal example to evaluate an expression at runtime. + + #include "tinyexpr.h" + #include + + int main(int argc, char *argv[]) + { + const char *c = "sqrt(5^2+7^2+11^2+(8-2)^2)"; + double r = te_interp(c, 0); + printf("The expression:\n\t%s\nevaluates to:\n\t%f\n", c, r); + return 0; + } + + +That produces the following output: + + The expression: + sqrt(5^2+7^2+11^2+(8-2)^2) + evaluates to: + 15.198684 + + +##Longer Example + +Here is an example that will evaluate an expression passed in from the command +line. It also does error checking and binds the variables *x* and *y*. + + #include "tinyexpr.h" + #include + + int main(int argc, char *argv[]) + { + if (argc < 2) { + printf("Usage: example2 \"expression\"\n", argv[0]); + return 0; + } + + const char *expression = argv[1]; + printf("Evaluating:\n\t%s\n", expression); + + /* This shows an example where the variables + * x and y are bound at eval-time. */ + double x, y; + te_variable vars[] = {{"x", &x}, {"y", &y}}; + + /* This will compile the expression and check for errors. */ + int err; + te_expr *n = te_compile(expression, vars, 2, &err); + + if (!err) { + /* The variables can be changed here, and eval can be called as many + * times as you like. This is fairly efficient because the parsing has + * already been done. */ + x = 3; + y = 4; + const double r = te_eval(n); printf("Result:\n\t%f\n", r); } + else { + /* Show the user where the error is at. */ + printf("\t%*s^\nError near here", err-1, ""); + } + + /* te_free should always be called after te_compile. */ + te_free(n); + + return 0; + } + + +This produces the output: + + $ example2 "sqrt(x^2+y2)" + Evaluating: + sqrt(x^2+y2) + ^ + Error near here + + + $ example2 "sqrt(x^2+y^2)" + Evaluating: + sqrt(x^2+y^2) + Result: + 5.000000 + + +##Usage + +TINYEXPR defines only five functions: + + double te_interp(const char *expression, int *error); + te_expr *te_compile(const char *expression, const te_variable *lookup, int lookup_len, int *error); + double te_eval(te_expr *n); + void te_print(const te_expr *n); + void te_free(te_expr *n); + +**te_interp** takes an expression and immediately returns the result of it. If +an error pointer is passed in, *te_interp* will set it to 0 for success or +approximately the position of the error for failure. If you don't care about +errors, just pass in 0. + +**te_interp example:** + + double x = te_interp("5+5", 0); + +**te_compile** will compile an expression with unbound variables, which will +be suitable to evaluate later. **te_eval** can then be called on the compiled +expression repeatedly to evaluate it with different variable values. **te_free** +should be called after you're finished. + +**te_compile example:** + + double x, y; + te_variable vars[] = {{"x", &x}, {"y", &y}}; + + int err; + te_expr *expr = te_compile("sqrt(x^2+y^2)", vars, 2, &err); + + if (!err) { + x = 3; y = 4; + const double h1 = te_eval(expr); + + x = 5; y = 7; + const double h2 = te_eval(expr); + } + + te_free(expr); + +**te_print** will display some (possibly not so) useful debugging +information about the return value of *te_compile*. + + +##How it works + +**te_compile** uses a simple recursive descent parser to compile your +expression into a syntax tree. For example, the expression "sin x + 1/4" +parses as: + +![example syntax tree](doc/e1.png?raw=true) + +**te_compile** also automatically prunes constant branches. In this example, +the compiled expression returned by *te_compile* is: + +![example syntax tree](doc/e2.png?raw=true) + +**te_eval** will automatically load in any variables by their pointer, then evaluate +and return the result of the expression. + +**te_free** should always be called when you're done with the compiled expression. + + +##Speed + + +TINYEXPR is pretty fast compared to C when the expression is short, when the +expression does hard calculations (e.g. exponentiation), and when some of the +work can be simplified by *te_compile*. TINYEXPR is slow compared to C when the +expression is long and involves only basic arithmetic. + +Here is some example performance numbers taken from the included +*benchmark.c* program: + +| Expression | te_eval time | native C time | slowdown | +| ------------- |-------------| -----| +| sqrt(a^1.5+a^2.5) | 15,641 ms | 14,478 ms | 8% slower | +| a+5 | 765 ms | 563 ms | 36% slower | +| a+(5*2) | 765 ms | 563 ms | 36% slower | +| (a+5)*2 | 1422 ms | 563 ms | 153% slower | +| (1/(a+1)+2/(a+2)+3/(a+3)) | 5,516 ms | 1,266 ms | 336% slower | + + + +##Grammar + +TINYEXPR parses the following grammar: + + = {("+" | "-") } + = {("*" | "/" | "%") } + = {"^" } + = {("-" | "+")} + = | | | "(" ")" + +In addition, whitespace between tokens is ignored. + +Valid variable names are any combination of the lower case letters *a* through +*z*. Constants can be integers, decimal numbers, or in scientific notation +(e.g. *1e3* for *1000*). A leading zero is not required (e.g. *.5* for *0.5*) + + +##Functions supported + +TINYEXPR supports addition (+), subtraction/negation (-), multiplication (\*), +division (/), exponentiation (^) and modulus (%) with the normal operator +precedence (the one exception being that exponentiation is evaluated +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 + + +##Hints + +- All functions/types start with the letters *te*. + +- Remember to always call *te_free* on the result of *te_compile*, even if + there is an error. + +- If there is an error, you can usually still evaluate the first part of the + expression. This may or may not be useful to you. + +- To allow constant optimization, surround constant expressions in parentheses. + For example "x+(1+5)" will evaluate the "(1+5)" expression at compile time and + compile the entire expression as "x+6", saving a runtime calculation. The + parentheses are important, because TINYEXPR will not change the order of + evaluation. If you instead compiled "x+1+5" TINYEXPR will insist that "1" is + added to "x" first, and "5" is added the result second. + diff --git a/benchmark.c b/benchmark.c new file mode 100644 index 0000000..fa914fb --- /dev/null +++ b/benchmark.c @@ -0,0 +1,124 @@ +/* + * TINYEXPR - Tiny recursive descent parser and evaluation engine in C + * + * Copyright (c) 2015, 2016 Lewis Van Winkle + * + * http://CodePlea.com + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include +#include +#include +#include "tinyexpr.h" + + + +#define loops 10000 + + + + +void bench(const char *expr, te_fun1 func) { + int i, j; + volatile double d; + double tmp; + clock_t start; + + te_variable lk = {"a", &tmp}; + + printf("Expression: %s\n", expr); + + printf("native "); + start = clock(); + d = 0; + for (j = 0; j < loops; ++j) + for (i = 0; i < loops; ++i) { + tmp = i; + d += func(tmp); + } + const int nelapsed = (clock() - start) * 1000 / CLOCKS_PER_SEC; + + /*Million floats per second input.*/ + printf(" %.5g", d); + if (nelapsed) + printf("\t%5dms\t%5lumfps\n", nelapsed, loops * loops / nelapsed / 1000); + else + printf("\tinf\n"); + + + + + printf("interp "); + te_expr *n = te_compile(expr, &lk, 1, 0); + start = clock(); + d = 0; + for (j = 0; j < loops; ++j) + for (i = 0; i < loops; ++i) { + tmp = i; + d += te_eval(n); + } + const int eelapsed = (clock() - start) * 1000 / CLOCKS_PER_SEC; + te_free(n); + + /*Million floats per second input.*/ + printf(" %.5g", d); + if (eelapsed) + printf("\t%5dms\t%5lumfps\n", eelapsed, loops * loops / eelapsed / 1000); + else + printf("\tinf\n"); + + + printf("%.2f%% longer\n", (((double)eelapsed / nelapsed) - 1.0) * 100.0); + + + printf("\n"); +} + + +double a5(double a) { + return a+5; +} + +double a52(double a) { + return (a+5)*2; +} + +double a10(double a) { + return a+(5*2); +} + +double as(double a) { + return sqrt(pow(a, 1.5) + pow(a, 2.5)); +} + +double al(double a) { + return (1/(a+1)+2/(a+2)+3/(a+3)); +} + +int main(int argc, char *argv[]) +{ + + bench("sqrt(a^1.5+a^2.5)", as); + bench("a+5", a5); + bench("a+(5*2)", a10); + bench("(a+5)*2", a52); + bench("(1/(a+1)+2/(a+2)+3/(a+3))", al); + + return 0; +} diff --git a/doc/e1.dot b/doc/e1.dot new file mode 100644 index 0000000..6a631ab --- /dev/null +++ b/doc/e1.dot @@ -0,0 +1,8 @@ +digraph G { + "+" -> "sin"; + "+" -> div; + "sin" -> "x"; + div -> "1"; + div -> "4"; + div [label="÷"] +} diff --git a/doc/e1.png b/doc/e1.png new file mode 100644 index 0000000..7f7a450 Binary files /dev/null and b/doc/e1.png differ diff --git a/doc/e2.dot b/doc/e2.dot new file mode 100644 index 0000000..d606722 --- /dev/null +++ b/doc/e2.dot @@ -0,0 +1,5 @@ +digraph G { + "+" -> "sin"; + "+" -> "0.25"; + "sin" -> "x"; +} diff --git a/doc/e2.png b/doc/e2.png new file mode 100644 index 0000000..cebb1db Binary files /dev/null and b/doc/e2.png differ diff --git a/example.c b/example.c new file mode 100644 index 0000000..040093f --- /dev/null +++ b/example.c @@ -0,0 +1,10 @@ +#include "tinyexpr.h" +#include + +int main(int argc, char *argv[]) +{ + const char *c = "sqrt(5^2+7^2+11^2+(8-2)^2)"; + double r = te_interp(c, 0); + printf("The expression:\n\t%s\nevaluates to:\n\t%f\n", c, r); + return 0; +} diff --git a/example2.c b/example2.c new file mode 100644 index 0000000..b317f98 --- /dev/null +++ b/example2.c @@ -0,0 +1,39 @@ +#include "tinyexpr.h" +#include + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + printf("Usage: example2 \"expression\"\n", argv[0]); + return 0; + } + + const char *expression = argv[1]; + printf("Evaluating:\n\t%s\n", expression); + + /* This shows an example where the variables + * x and y are bound at eval-time. */ + double x, y; + te_variable vars[] = {{"x", &x}, {"y", &y}}; + + /* This will compile the expression and check for errors. */ + int err; + te_expr *n = te_compile(expression, vars, 2, &err); + + if (!err) { + /* The variables can be changed here, and eval can be called as many + * times as you like. This is fairly efficient because the parsing has + * already been done. */ + x = 3; + y = 4; + const double r = te_eval(n); printf("Result:\n\t%f\n", r); } + else { + /* Show the user where the error is at. */ + printf("\t%*s^\nError near here", err-1, ""); + } + + /* te_free should always be called after te_compile. */ + te_free(n); + + return 0; +} diff --git a/minctest.h b/minctest.h new file mode 100644 index 0000000..eb4e537 --- /dev/null +++ b/minctest.h @@ -0,0 +1,127 @@ +/* + * + * MINCTEST - Minimal C Test Library - 0.1 + * + * Copyright (c) 2014, 2015 Lewis Van Winkle + * + * http://CodePlea.com + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + */ + + + +/* + * MINCTEST - Minimal testing library for C + * + * + * Example: + * + * void test1() { + * lok('a' == 'a'); + * } + * + * void test2() { + * lequal(5, 6); + * lfequal(5.5, 5.6); + * } + * + * int main() { + * lrun("test1", test1); + * lrun("test2", test2); + * lresults(); + * return lfails != 0; + * } + * + * + * + * Hints: + * All functions/variables start with the letter 'l'. + * + */ + + +#ifndef __MINCTEST_H__ +#define __MINCTEST_H__ + +#include +#include +#include + + +/* How far apart can floats be before we consider them unequal. */ +#define LTEST_FLOAT_TOLERANCE 0.001 + + +/* Track the number of passes, fails. */ +/* NB this is made for all tests to be in one file. */ +static int ltests = 0; +static int lfails = 0; + + +/* Display the test results. */ +#define lresults() do {\ + if (lfails == 0) {\ + printf("ALL TESTS PASSED (%d/%d)\n", ltests, ltests);\ + } else {\ + printf("SOME TESTS FAILED (%d/%d)\n", ltests-lfails, ltests);\ + }\ +} while (0) + + +/* Run a test. Name can be any string to print out, test is the function name to call. */ +#define lrun(name, test) do {\ + const int ts = ltests;\ + const int fs = lfails;\ + const clock_t start = clock();\ + printf("\t%-14s", name);\ + test();\ + printf("pass:%2d fail:%2d %4dms\n",\ + (ltests-ts)-(lfails-fs), lfails-fs,\ + (int)((clock() - start) * 1000 / CLOCKS_PER_SEC));\ +} while (0) + + +/* Assert a true statement. */ +#define lok(test) do {\ + ++ltests;\ + if (!(test)) {\ + ++lfails;\ + printf("%s:%d error \n", __FILE__, __LINE__);\ + }} while (0) + + +/* Assert two integers are equal. */ +#define lequal(a, b) do {\ + ++ltests;\ + if ((a) != (b)) {\ + ++lfails;\ + printf("%s:%d (%d != %d)\n", __FILE__, __LINE__, (a), (b));\ + }} while (0) + + +/* Assert two floats are equal (Within LTEST_FLOAT_TOLERANCE). */ +#define lfequal(a, b) do {\ + ++ltests;\ + if (fabs((double)(a)-(double)(b)) > LTEST_FLOAT_TOLERANCE) {\ + ++lfails;\ + printf("%s:%d (%f != %f)\n", __FILE__, __LINE__, (double)(a), (double)(b));\ + }} while (0) + + +#endif /*__MINCTEST_H__*/ diff --git a/test.c b/test.c new file mode 100644 index 0000000..152a703 --- /dev/null +++ b/test.c @@ -0,0 +1,233 @@ +/* + * TINYEXPR - Tiny recursive descent parser and evaluation engine in C + * + * Copyright (c) 2015, 2016 Lewis Van Winkle + * + * http://CodePlea.com + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "tinyexpr.h" +#include +#include "minctest.h" + + +typedef struct { + const char *expr; + double answer; +} test_case; + + +test_case cases[] = { + {"1", 1}, + {"(1)", 1}, + + {"2+1", 2+1}, + {"(((2+(1))))", 2+1}, + {"3+2", 3+2}, + + {"3+2+4", 3+2+4}, + {"(3+2)+4", 3+2+4}, + {"3+(2+4)", 3+2+4}, + {"(3+2+4)", 3+2+4}, + + {"3*2*4", 3*2*4}, + {"(3*2)*4", 3*2*4}, + {"3*(2*4)", 3*2*4}, + {"(3*2*4)", 3*2*4}, + + {"3-2-4", 3-2-4}, + {"(3-2)-4", (3-2)-4}, + {"3-(2-4)", 3-(2-4)}, + {"(3-2-4)", 3-2-4}, + + {"3/2/4", 3.0/2.0/4.0}, + {"(3/2)/4", (3.0/2.0)/4.0}, + {"3/(2/4)", 3.0/(2.0/4.0)}, + {"(3/2/4)", 3.0/2.0/4.0}, + + {"(3*2/4)", 3.0*2.0/4.0}, + {"(3/2*4)", 3.0/2.0*4.0}, + {"3*(2/4)", 3.0*(2.0/4.0)}, + + {"asin sin .5", 0.5}, + {"sin asin .5", 0.5}, + {"ln exp .5", 0.5}, + {"exp ln .5", 0.5}, + + {"asin sin-.5", -0.5}, + {"asin sin-0.5", -0.5}, + {"asin sin -0.5", -0.5}, + {"asin (sin -0.5)", -0.5}, + {"asin (sin (-0.5))", -0.5}, + {"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}, + {"log1.0e3", 3}, + {"10^5*5e-5", 5}, + + {"100^.5+1", 11}, + {"100 ^.5+1", 11}, + {"100^+.5+1", 11}, + {"100^--.5+1", 11}, + {"100^---+-++---++-+-+-.5+1", 11}, + + {"100^-.5+1", 1.1}, + {"100^---.5+1", 1.1}, + {"100^+---.5+1", 1.1}, + {"1e2^+---.5e0+1e0", 1.1}, + {"--(1e2^(+(-(-(-.5e0))))+1e0)", 1.1}, + + {"sqrt 100 + 7", 17}, + {"sqrt 100 * 7", 70}, + {"sqrt (100 * 100)", 100}, + +}; + + +test_case errors[] = { + {"1+", 2}, + {"1)", 2}, + {"(1", 2}, + {"1**1", 3}, + {"1*2(+4", 4}, + {"1*2(1+4", 4}, + {"a+5", 1}, + {"A+5", 1}, + {"Aa+5", 1}, + {"1^^5", 3}, + {"1**5", 3}, + {"sin(cos5", 8}, +}; + + +void test1() { + int i; + for (i = 0; i < sizeof(cases) / sizeof(test_case); ++i) { + const char *expr = cases[i].expr; + const double answer = cases[i].answer; + + int err; + const double ev = te_interp(expr, &err); + lok(!err); + lfequal(ev, answer); + } +} + + +void test2() { + int i; + for (i = 0; i < sizeof(errors) / sizeof(test_case); ++i) { + const char *expr = errors[i].expr; + const int e = errors[i].answer; + + int err; + te_interp(expr, &err); + lequal(err, e); + } +} + + + +void test3() { + + double x, y; + te_variable lookup[] = {{"x", &x}, {"y", &y}}; + + int err; + + te_expr *expr1 = te_compile("cos x + sin y", lookup, 2, &err); + lok(!err); + + te_expr *expr2 = te_compile("x+x+x-y", lookup, 2, &err); + lok(!err); + + te_expr *expr3 = te_compile("x*y^3", lookup, 2, &err); + lok(!err); + + for (y = 2; y < 3; ++y) { + for (x = 0; x < 5; ++x) { + double ev = te_eval(expr1); + lfequal(ev, cos(x) + sin(y)); + + ev = te_eval(expr2); + lfequal(ev, x+x+x-y); + + ev = te_eval(expr3); + lfequal(ev, x*y*y*y); + } + } + + te_free(expr1); + te_free(expr2); + te_free(expr3); +} + + +#define cross_check(a, b) do {\ + expr = te_compile((a), &lookup, 1, &err);\ + lfequal(te_eval(expr), (b));\ + lok(!err);\ + te_free(expr);\ +}while(0) + +void test4() { + + double x; + te_variable lookup = {"x", &x}; + + int err; + te_expr *expr; + + for (x = -5; x < 5; x += .2) { + cross_check("abs x", fabs(x)); + cross_check("acos x", acos(x)); + cross_check("asin x", asin(x)); + cross_check("atan x", atan(x)); + cross_check("ceil x", ceil(x)); + cross_check("cos x", cos(x)); + cross_check("cosh x", cosh(x)); + cross_check("exp x", exp(x)); + cross_check("floor x", floor(x)); + cross_check("ln x", log(x)); + cross_check("log x", log10(x)); + cross_check("sin x", sin(x)); + cross_check("sinh x", sinh(x)); + cross_check("sqrt x", sqrt(x)); + cross_check("tan x", tan(x)); + cross_check("tanh x", tanh(x)); + } +} + + +int main(int argc, char *argv[]) +{ + lrun("Results", test1); + lrun("Syntax", test2); + lrun("Bind", test3); + lrun("Functions", test4); + lresults(); + + return lfails != 0; +} diff --git a/tinyexpr.c b/tinyexpr.c new file mode 100644 index 0000000..97be41b --- /dev/null +++ b/tinyexpr.c @@ -0,0 +1,406 @@ +/* + * TINYEXPR - Tiny recursive descent parser and evaluation engine in C + * + * Copyright (c) 2015, 2016 Lewis Van Winkle + * + * http://CodePlea.com + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "tinyexpr.h" +#include +#include +#include +#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}; + + + +typedef struct { + const char *start; + const char *next; + int type; + union {double value; te_fun1 f1; te_fun2 f2; const double *var;}; + + const te_variable *lookup; + int lookup_len; +} state; + + + + +static te_expr *new_expr(te_expr *l, te_expr *r) { + te_expr *ret = malloc(sizeof(te_expr)); + ret->left = l; + ret->right = r; + ret->bound = 0; + return ret; +} + + +void te_free(te_expr *n) { + if (n->left) te_free(n->left); + if (n->right) te_free(n->right); + free(n); +} + + +typedef struct { + const char *name; + te_fun1 f1; +} builtin; + + +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}, + {0} +}; + + +static const builtin *find_function(const char *name, int len) { + int imin = 0; + int imax = sizeof(functions) / sizeof(builtin) - 2; + + /*Binary search.*/ + while (imax >= imin) { + const int i = (imin + ((imax-imin)/2)); + int c = strncmp(name, functions[i].name, len); + if (!c) c = len - strlen(functions[i].name); + if (c == 0) { + return functions + i; + } else if (c > 0) { + imin = i + 1; + } else { + imax = i - 1; + } + } + + return 0; +} + + +static const double *find_var(const state *s, const char *name, int len) { + int i; + if (!s->lookup) return 0; + for (i = 0; i < s->lookup_len; ++i) { + if (strlen(s->lookup[i].name) == len && strncmp(name, s->lookup[i].name, len) == 0) { + return s->lookup[i].value; + } + } + return 0; +} + + + +static double add(double a, double b) {return a + b;} +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 mod(double a, double b) {return (long long)a % (long long)b;} +static double negate(double a) {return -a;} + + +void next_token(state *s) { + s->type = TOK_NULL; + + if (!*s->next){ + s->type = TOK_END; + return; + } + + do { + + /* Try reading a number. */ + if ((s->next[0] >= '0' && s->next[0] <= '9') || s->next[0] == '.') { + s->value = strtod(s->next, (char**)&s->next); + s->type = TOK_NUMBER; + } else { + /* Look for a variable or builtin function call. */ + 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++; + + const double *var = find_var(s, start, s->next - start); + if (var) { + s->type = TOK_VARIABLE; + s->var = var; + } else { + 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; + } + } + } + + } 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 = mod; break; + case '(': s->type = TOK_OPEN; break; + case ')': s->type = TOK_CLOSE; break; + case ' ': case '\t': case '\n': case '\r': break; + default: s->type = TOK_ERROR; break; + } + } + } + } while (s->type == TOK_NULL); +} + + +static te_expr *expr(state *s); +static te_expr *power(state *s); + +static te_expr *base(state *s) { + /* = | | | "(" ")" */ + te_expr *ret; + + switch (s->type) { + case TOK_NUMBER: + ret = new_expr(0, 0); + ret->value = s->value; + next_token(s); + break; + + case TOK_VARIABLE: + ret = new_expr(0, 0); + ret->bound = s->var; + next_token(s); + break; + + case TOK_FUNCTION1: + ret = new_expr(0, 0); + ret->f1 = s->f1; + next_token(s); + ret->left = power(s); + break; + + case TOK_OPEN: + next_token(s); + ret = expr(s); + if (s->type != TOK_CLOSE) { + s->type = TOK_ERROR; + } else { + next_token(s); + } + break; + + default: + ret = new_expr(0, 0); + s->type = TOK_ERROR; + ret->value = 1.0/0.0; + break; + } + + return ret; +} + + +static te_expr *power(state *s) { + /* = {("-" | "+")} */ + int sign = 1; + while (s->type == TOK_FUNCTION2 && (s->f2 == add || s->f2 == sub)) { + if (s->f2 == sub) sign = -sign; + next_token(s); + } + + te_expr *ret; + + if (sign == 1) { + ret = base(s); + } else { + ret = new_expr(base(s), 0); + ret->f1 = negate; + } + + return ret; +} + + +static te_expr *factor(state *s) { + /* = {"^" } */ + te_expr *ret = power(s); + + while (s->type == TOK_FUNCTION2 && (s->f2 == pow)) { + te_fun2 t = s->f2; + next_token(s); + ret = new_expr(ret, power(s)); + ret->f2 = t; + } + + return ret; +} + + +static te_expr *term(state *s) { + /* = {("*" | "/" | "%") } */ + te_expr *ret = factor(s); + + while (s->type == TOK_FUNCTION2 && (s->f2 == mul || s->f2 == divide || s->f2 == mod)) { + te_fun2 t = s->f2; + next_token(s); + ret = new_expr(ret, factor(s)); + ret->f2 = t; + } + + return ret; +} + + +static te_expr *expr(state *s) { + /* = {("+" | "-") } */ + te_expr *ret = term(s); + + while (s->type == TOK_FUNCTION2 && (s->f2 == add || s->f2 == sub)) { + te_fun2 t = s->f2; + next_token(s); + ret = new_expr(ret, term(s)); + ret->f2 = t; + } + + return ret; +} + + +double te_eval(te_expr *n) { + double ret; + + if (n->bound) { + ret = *n->bound; + } else if (n->left == 0 && n->right == 0) { + ret = n->value; + } else if (n->left && n->right == 0) { + ret = n->f1(te_eval(n->left)); + } else { + ret = n->f2(te_eval(n->left), te_eval(n->right)); + } + return ret; +} + + +static void optimize(te_expr *n) { + /* Evaluates as much as possible. */ + if (n->bound) return; + + if (n->left) optimize(n->left); + if (n->right) optimize(n->right); + + if (n->left && n->right) + { + if (n->left->left == 0 && n->left->right == 0 && n->right->left == 0 && n->right->right == 0 && n->right->bound == 0 && n->left->bound == 0) + { + const double r = n->f2(n->left->value, n->right->value); + free(n->left); free(n->right); + n->left = 0; n->right = 0; + n->value = r; + } + } else if (n->left && !n->right) { + if (n->left->left == 0 && n->left->right == 0 && n->left->bound == 0) { + const double r = n->f1(n->left->value); + free(n->left); + n->left = 0; + n->value = r; + } + } +} + + +te_expr *te_compile(const char *expression, const te_variable *lookup, int lookup_len, int *error) { + state s; + s.start = s.next = expression; + s.lookup = lookup; + s.lookup_len = lookup_len; + + next_token(&s); + te_expr *root = expr(&s); + + + if (s.type != TOK_END) { + if (error) *error = (s.next - s.start); + if (*error == 0) *error = 1; + } else { + optimize(root); + if (error) *error = 0; + } + + + return root; +} + + +double te_interp(const char *expression, int *error) { + te_expr *n = te_compile(expression, 0, 0, error); + double ret = te_eval(n); + free(n); + return ret; +} + + +static void pn (const te_expr *n, int depth) { + int i; + for (i = 0; i < depth; ++i) { + printf(" "); + } + + if (n->bound) { + printf("bound %p\n", n->bound); + } else if (n->left == 0 && n->right == 0) { + printf("%f\n", n->value); + } else if (n->left && n->right == 0) { + printf("f1 %p\n", n->left); + pn(n->left, depth+1); + } else { + printf("f2 %p %p\n", n->left, n->right); + pn(n->left, depth+1); + pn(n->right, depth+1); + } +} + + +void te_print(const te_expr *n) { + pn(n, 0); +} diff --git a/tinyexpr.h b/tinyexpr.h new file mode 100644 index 0000000..1df74d2 --- /dev/null +++ b/tinyexpr.h @@ -0,0 +1,74 @@ +/* + * TINYEXPR - Tiny recursive descent parser and evaluation engine in C + * + * Copyright (c) 2015, 2016 Lewis Van Winkle + * + * http://CodePlea.com + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef __TINYEXPR_H__ +#define __TINYEXPR_H__ + + + +typedef double (*te_fun1)(double); +typedef double (*te_fun2)(double, double); + + +typedef struct te_expr { + struct te_expr *left, *right; + union {double value; te_fun1 f1; te_fun2 f2;}; + const double *bound; +} te_expr; + + +typedef struct { + const char *name; + const double *value; +} te_variable; + + +/* Note on error handling: + * If the parser encounters an error, it will still return + * an expression up to that point (which may be worthless or useful. + * If the error pointer parameter is passed in and not null, the + * parser will set it to roughly the index of the error in the + * input expression. If there is no error, the parse sets + * the error pointer to 0. + */ + + +/* Parses the input expression, evaluates it, and frees it. */ +double te_interp(const char *expression, int *error); + +/* Parses the input expression and binds variables. */ +te_expr *te_compile(const char *expression, const te_variable *lookup, int lookup_len, int *error); + +/* Evaluates the expression. */ +double te_eval(te_expr *n); + +/* Prints debugging information on the syntax tree. */ +void te_print(const te_expr *n); + +/* Frees the expression. */ +void te_free(te_expr *n); + + + +#endif /*__TINYEXPR_H__*/