From ec3dde1e2404cf50329f1d06cd0e65038674f2dc Mon Sep 17 00:00:00 2001 From: Lewis Van Winkle Date: Thu, 21 Jan 2016 14:36:59 -0600 Subject: [PATCH] Initial commit --- LICENSE.md | 22 +++ Makefile | 28 ++++ README.md | 240 +++++++++++++++++++++++++++++++ benchmark.c | 124 ++++++++++++++++ doc/e1.dot | 8 ++ doc/e1.png | Bin 0 -> 11883 bytes doc/e2.dot | 5 + doc/e2.png | Bin 0 -> 10032 bytes example.c | 10 ++ example2.c | 39 +++++ minctest.h | 127 ++++++++++++++++ test.c | 233 ++++++++++++++++++++++++++++++ tinyexpr.c | 406 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tinyexpr.h | 74 ++++++++++ 14 files changed, 1316 insertions(+) create mode 100644 LICENSE.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 benchmark.c create mode 100644 doc/e1.dot create mode 100644 doc/e1.png create mode 100644 doc/e2.dot create mode 100644 doc/e2.png create mode 100644 example.c create mode 100644 example2.c create mode 100644 minctest.h create mode 100644 test.c create mode 100644 tinyexpr.c create mode 100644 tinyexpr.h 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 0000000000000000000000000000000000000000..7f7a4500b173697057d247a96834cb88a0341fb1 GIT binary patch literal 11883 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 b36$TOm=n1+D}>?pI70QNx?-`sng9O){!(!~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cebb1dbc4fb3b0679fc4753c29a4509d54e805c6 GIT binary patch literal 10032 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 PIE1XEqD1*4gMj}7`r*d= literal 0 HcmV?d00001 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__*/