diff --git a/tinyexpr/.travis.yml b/tinyexpr/.travis.yml new file mode 100644 index 000000000..d275c1a7b --- /dev/null +++ b/tinyexpr/.travis.yml @@ -0,0 +1,7 @@ +language: c + +compiler: + - clang + - gcc + +script: make diff --git a/tinyexpr/CONTRIBUTING b/tinyexpr/CONTRIBUTING new file mode 100644 index 000000000..f27fc96f8 --- /dev/null +++ b/tinyexpr/CONTRIBUTING @@ -0,0 +1,10 @@ +A core strength of TinyExpr is that it is small and simple. This makes it easy +to add new features. However, if we keep adding new features, it'll no longer +be small or simple. In other words, each new feature corrodes away at the core +strength of TinyExpr. + +If you want to add a new feature, and you expect me to merge it, please discuss +it with me before you go to that work. Open an issue at +https://github.com/codeplea/tinyexpr and let us know what you're proposing. + +Bug fixes are always welcome and appreciated, of course. diff --git a/tinyexpr/Makefile b/tinyexpr/Makefile new file mode 100644 index 000000000..cc9765c88 --- /dev/null +++ b/tinyexpr/Makefile @@ -0,0 +1,33 @@ +CCFLAGS = -ansi -Wall -Wshadow -O2 +LFLAGS = -lm + +.PHONY = all clean + +all: test test_pr bench example example2 example3 + + +test: test.c tinyexpr.c + $(CC) $(CCFLAGS) -o $@ $^ $(LFLAGS) + ./$@ + +test_pr: test.c tinyexpr.c + $(CC) $(CCFLAGS) -DTE_POW_FROM_RIGHT -DTE_NAT_LOG -o $@ $^ $(LFLAGS) + ./$@ + +bench: benchmark.o tinyexpr.o + $(CC) $(CCFLAGS) -o $@ $^ $(LFLAGS) + +example: example.o tinyexpr.o + $(CC) $(CCFLAGS) -o $@ $^ $(LFLAGS) + +example2: example2.o tinyexpr.o + $(CC) $(CCFLAGS) -o $@ $^ $(LFLAGS) + +example3: example3.o tinyexpr.o + $(CC) $(CCFLAGS) -o $@ $^ $(LFLAGS) + +.c.o: + $(CC) -c $(CCFLAGS) $< -o $@ + +clean: + rm -f *.o *.exe example example2 example3 bench test_pr test diff --git a/tinyexpr/README.md b/tinyexpr/README.md new file mode 100644 index 000000000..ad9f45a45 --- /dev/null +++ b/tinyexpr/README.md @@ -0,0 +1,319 @@ +[![Build Status](https://travis-ci.org/codeplea/tinyexpr.svg?branch=master)](https://travis-ci.org/codeplea/tinyexpr) + + +TinyExpr logo + +# 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 add custom functions and variables easily. +- 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. + +## Building + +TinyExpr is self-contained in two files: `tinyexpr.c` and `tinyexpr.h`. To use +TinyExpr, simply add those two files to your project. + +## Short Example + +Here is a minimal example to evaluate an expression at runtime. + +```C + #include "tinyexpr.h" + printf("%f\n", te_interp("5*5", 0)); /* Prints 25. */ +``` + + +## Usage + +TinyExpr defines only four functions: + +```C + double te_interp(const char *expression, int *error); + te_expr *te_compile(const char *expression, const te_variable *variables, int var_count, int *error); + double te_eval(const te_expr *expr); + void te_free(te_expr *expr); +``` + +## te_interp +```C + double te_interp(const char *expression, int *error); +``` + +`te_interp()` takes an expression and immediately returns the result of it. If there +is a parse error, `te_interp()` returns NaN. + +If the `error` pointer argument is not 0, then `te_interp()` will set `*error` to the position +of the parse error on failure, and set `*error` to 0 on success. + +**example usage:** + +```C + int error; + + double a = te_interp("(5+5)", 0); /* Returns 10. */ + double b = te_interp("(5+5)", &error); /* Returns 10, error is set to 0. */ + double c = te_interp("(5+5", &error); /* Returns NaN, error is set to 4. */ +``` + +## te_compile, te_eval, te_free +```C + te_expr *te_compile(const char *expression, const te_variable *lookup, int lookup_len, int *error); + double te_eval(const te_expr *n); + void te_free(te_expr *n); +``` + +Give `te_compile()` an expression with unbound variables and a list of +variable names and pointers. `te_compile()` will return a `te_expr*` which can +be evaluated later using `te_eval()`. On failure, `te_compile()` will return 0 +and optionally set the passed in `*error` to the location of the parse error. + +You may also compile expressions without variables by passing `te_compile()`'s second +and thrid arguments as 0. + +Give `te_eval()` a `te_expr*` from `te_compile()`. `te_eval()` will evaluate the expression +using the current variable values. + +After you're finished, make sure to call `te_free()`. + +**example usage:** + +```C + double x, y; + /* Store variable names and pointers. */ + te_variable vars[] = {{"x", &x}, {"y", &y}}; + + int err; + /* Compile the expression with variables. */ + te_expr *expr = te_compile("sqrt(x^2+y^2)", vars, 2, &err); + + if (expr) { + x = 3; y = 4; + const double h1 = te_eval(expr); /* Returns 5. */ + + x = 5; y = 12; + const double h2 = te_eval(expr); /* Returns 13. */ + + te_free(expr); + } else { + printf("Parse error at %d\n", err); + } + +``` + +## Longer Example + +Here is a complete example that will evaluate an expression passed in from the command +line. It also does error checking and binds the variables `x` and `y` to *3* and *4*, respectively. + +```C + #include "tinyexpr.h" + #include + + int main(int argc, char *argv[]) + { + if (argc < 2) { + printf("Usage: example2 \"expression\"\n"); + 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 (n) { + /* 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); + te_free(n); + } else { + /* Show the user where the error is at. */ + printf("\t%*s^\nError near here", err-1, ""); + } + + 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 + + +## Binding to Custom Functions + +TinyExpr can also call to custom functions implemented in C. Here is a short example: + +```C +double my_sum(double a, double b) { + /* Example C function that adds two numbers together. */ + return a + b; +} + +te_variable vars[] = { + {"mysum", my_sum, TE_FUNCTION2} /* TE_FUNCTION2 used because my_sum takes two arguments. */ +}; + +te_expr *n = te_compile("mysum(5, 6)", vars, 1, 0); + +``` + + +## 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()` would become: + +![example syntax tree](doc/e2.png?raw=true) + +`te_eval()` will automatically load in any variables by their pointer, and 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 consist of a lower case letter followed by any combination +of: lower case letters *a* through *z*, the digits *0* through *9*, and +underscore. 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, but this can be changed - see below). + +The following C math functions are also supported: + +- abs (calls to *fabs*), acos, asin, atan, atan2, ceil, cos, cosh, exp, floor, ln (calls to *log*), log (calls to *log10* by default, see below), log10, pow, sin, sinh, sqrt, tan, tanh + +The following functions are also built-in and provided by TinyExpr: + +- fac (factorials e.g. `fac 5` == 120) +- ncr (combinations e.g. `ncr(6,2)` == 15) +- npr (permutations e.g. `npr(6,2)` == 30) + +Also, the following constants are available: + +- `pi`, `e` + + +## Compile-time options + + +By default, TinyExpr does exponentiation from left to right. For example: + +`a^b^c == (a^b)^c` and `-a^b == (-a)^b` + +This is by design. It's the way that spreadsheets do it (e.g. Excel, Google Sheets). + + +If you would rather have exponentiation work from right to left, you need to +define `TE_POW_FROM_RIGHT` when compiling `tinyexpr.c`. There is a +commented-out define near the top of that file. With this option enabled, the +behaviour is: + +`a^b^c == a^(b^c)` and `-a^b == -(a^b)` + +That will match how many scripting languages do it (e.g. Python, Ruby). + +Also, if you'd like `log` to default to the natural log instead of `log10`, +then you can define `TE_NAT_LOG`. + +## Hints + +- All functions/types start with the letters *te*. + +- 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/tinyexpr/benchmark.c b/tinyexpr/benchmark.c new file mode 100644 index 000000000..31d18faf8 --- /dev/null +++ b/tinyexpr/benchmark.c @@ -0,0 +1,125 @@ +/* + * 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 + + + +typedef double (*function1)(double); + +void bench(const char *expr, function1 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%5dmfps\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%5dmfps\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/tinyexpr/doc/e1.dot b/tinyexpr/doc/e1.dot new file mode 100644 index 000000000..6a631aba6 --- /dev/null +++ b/tinyexpr/doc/e1.dot @@ -0,0 +1,8 @@ +digraph G { + "+" -> "sin"; + "+" -> div; + "sin" -> "x"; + div -> "1"; + div -> "4"; + div [label="÷"] +} diff --git a/tinyexpr/doc/e1.png b/tinyexpr/doc/e1.png new file mode 100644 index 000000000..7f7a4500b Binary files /dev/null and b/tinyexpr/doc/e1.png differ diff --git a/tinyexpr/doc/e2.dot b/tinyexpr/doc/e2.dot new file mode 100644 index 000000000..d6067228b --- /dev/null +++ b/tinyexpr/doc/e2.dot @@ -0,0 +1,5 @@ +digraph G { + "+" -> "sin"; + "+" -> "0.25"; + "sin" -> "x"; +} diff --git a/tinyexpr/doc/e2.png b/tinyexpr/doc/e2.png new file mode 100644 index 000000000..cebb1dbc4 Binary files /dev/null and b/tinyexpr/doc/e2.png differ diff --git a/tinyexpr/example.c b/tinyexpr/example.c new file mode 100644 index 000000000..040093fd2 --- /dev/null +++ b/tinyexpr/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/tinyexpr/example2.c b/tinyexpr/example2.c new file mode 100644 index 000000000..70d05d190 --- /dev/null +++ b/tinyexpr/example2.c @@ -0,0 +1,38 @@ +#include "tinyexpr.h" +#include + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + printf("Usage: example2 \"expression\"\n"); + 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 (n) { + /* 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); + + te_free(n); + } else { + /* Show the user where the error is at. */ + printf("\t%*s^\nError near here", err-1, ""); + } + + + return 0; +} diff --git a/tinyexpr/example3.c b/tinyexpr/example3.c new file mode 100644 index 000000000..80fe9da44 --- /dev/null +++ b/tinyexpr/example3.c @@ -0,0 +1,35 @@ +#include "tinyexpr.h" +#include + + +/* An example of calling a C function. */ +double my_sum(double a, double b) { + printf("Called C function with %f and %f.\n", a, b); + return a + b; +} + + +int main(int argc, char *argv[]) +{ + te_variable vars[] = { + {"mysum", my_sum, TE_FUNCTION2} + }; + + const char *expression = "mysum(5, 6)"; + printf("Evaluating:\n\t%s\n", expression); + + int err; + te_expr *n = te_compile(expression, vars, 1, &err); + + if (n) { + const double r = te_eval(n); + printf("Result:\n\t%f\n", r); + te_free(n); + } else { + /* Show the user where the error is at. */ + printf("\t%*s^\nError near here", err-1, ""); + } + + + return 0; +} diff --git a/tinyexpr/minctest.h b/tinyexpr/minctest.h new file mode 100644 index 000000000..6180de840 --- /dev/null +++ b/tinyexpr/minctest.h @@ -0,0 +1,128 @@ +/* + * + * 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;\ + const double __LF_COMPARE = fabs((double)(a)-(double)(b));\ + if (__LF_COMPARE > LTEST_FLOAT_TOLERANCE || (__LF_COMPARE != __LF_COMPARE)) {\ + ++lfails;\ + printf("%s:%d (%f != %f)\n", __FILE__, __LINE__, (double)(a), (double)(b));\ + }} while (0) + + +#endif /*__MINCTEST_H__*/ diff --git a/tinyexpr/test.c b/tinyexpr/test.c new file mode 100644 index 000000000..c772950a8 --- /dev/null +++ b/tinyexpr/test.c @@ -0,0 +1,691 @@ +/* + * 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; + +typedef struct { + const char *expr1; + const char *expr2; +} test_equ; + + + +void test_results() { + test_case cases[] = { + {"1", 1}, + {"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}, + + {"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}, + + {"log10 1000", 3}, + {"log10 1e3", 3}, + {"log10 1000", 3}, + {"log10 1e3", 3}, + {"log10(1000)", 3}, + {"log10(1e3)", 3}, + {"log10 1.0e3", 3}, + {"10^5*5e-5", 5}, + +#ifdef TE_NAT_LOG + {"log 1000", 6.9078}, + {"log e", 1}, + {"log (e^10)", 10}, +#else + {"log 1000", 3}, +#endif + + {"ln (e^10)", 10}, + {"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}, + + {"1,2", 2}, + {"1,2+1", 3}, + {"1+1,2+2,2+1", 3}, + {"1,2,3", 3}, + {"(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}, + + }; + + + 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); + + if (err) { + printf("FAILED: %s (%d)\n", expr, err); + } + } +} + + +void test_syntax() { + test_case errors[] = { + {"", 1}, + {"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}, + }; + + + 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; + const double r = te_interp(expr, &err); + lequal(err, e); + lok(r != r); + + te_expr *n = te_compile(expr, 0, 0, &err); + lequal(err, e); + lok(!n); + + if (err != e) { + printf("FAILED: %s\n", expr); + } + + const double k = te_interp(expr, 0); + lok(k != k); + } +} + + +void test_nans() { + + const char *nans[] = { + "0/0", + "1%0", + "1%(1%0)", + "(1%0)%1", + "fac(-1)", + "ncr(2, 4)", + "ncr(-2, 4)", + "ncr(2, -4)", + "npr(2, 4)", + "npr(-2, 4)", + "npr(2, -4)", + }; + + int i; + for (i = 0; i < sizeof(nans) / sizeof(const char *); ++i) { + const char *expr = nans[i]; + + int err; + const double r = te_interp(expr, &err); + lequal(err, 0); + lok(r != r); + + te_expr *n = te_compile(expr, 0, 0, &err); + lok(n); + lequal(err, 0); + const double c = te_eval(n); + lok(c != c); + te_free(n); + } +} + + +void test_infs() { + + const char *infs[] = { + "1/0", + "log(0)", + "pow(2,10000000)", + "fac(300)", + "ncr(300,100)", + "ncr(300000,100)", + "ncr(300000,100)*8", + "npr(3,2)*ncr(300000,100)", + "npr(100,90)", + "npr(30,25)", + }; + + int i; + for (i = 0; i < sizeof(infs) / sizeof(const char *); ++i) { + const char *expr = infs[i]; + + int err; + const double r = te_interp(expr, &err); + lequal(err, 0); + lok(r == r + 1); + + te_expr *n = te_compile(expr, 0, 0, &err); + lok(n); + lequal(err, 0); + const double c = te_eval(n); + lok(c == c + 1); + te_free(n); + } +} + + +void test_variables() { + + double x, y, test; + te_variable lookup[] = {{"x", &x}, {"y", &y}, {"te_st", &test}}; + + int err; + + te_expr *expr1 = te_compile("cos x + sin y", lookup, 2, &err); + lok(expr1); + lok(!err); + + te_expr *expr2 = te_compile("x+x+x-y", lookup, 2, &err); + lok(expr2); + lok(!err); + + te_expr *expr3 = te_compile("x*y^3", lookup, 2, &err); + lok(expr3); + lok(!err); + + te_expr *expr4 = te_compile("te_st+5", lookup, 3, &err); + lok(expr4); + lok(!err); + + for (y = 2; y < 3; ++y) { + for (x = 0; x < 5; ++x) { + double ev; + + 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); + + test = x; + ev = te_eval(expr4); + lfequal(ev, x+5); + } + } + + te_free(expr1); + te_free(expr2); + te_free(expr3); + te_free(expr4); + + + + te_expr *expr5 = te_compile("xx*y^3", lookup, 2, &err); + lok(!expr5); + lok(err); + + te_expr *expr6 = te_compile("tes", lookup, 3, &err); + lok(!expr6); + lok(err); + + te_expr *expr7 = te_compile("sinn x", lookup, 2, &err); + lok(!expr7); + lok(err); + + te_expr *expr8 = te_compile("si x", lookup, 2, &err); + lok(!expr8); + lok(err); +} + + + +#define cross_check(a, b) do {\ + if ((b)!=(b)) break;\ + expr = te_compile((a), lookup, 2, &err);\ + lfequal(te_eval(expr), (b));\ + lok(!err);\ + te_free(expr);\ +}while(0) + +void test_functions() { + + double x, y; + te_variable lookup[] = {{"x", &x}, {"y", &y}}; + + 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("log10 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)); + + for (y = -2; y < 2; y += .2) { + if (fabs(x) < 0.01) break; + cross_check("atan2(x,y)", atan2(x, y)); + cross_check("pow(x,y)", pow(x, y)); + } + } +} + + +double sum0() { + return 6; +} +double sum1(double a) { + return a * 2; +} +double sum2(double a, double b) { + return a + b; +} +double sum3(double a, double b, double c) { + return a + b + c; +} +double sum4(double a, double b, double c, double d) { + return a + b + c + d; +} +double sum5(double a, double b, double c, double d, double e) { + return a + b + c + d + e; +} +double sum6(double a, double b, double c, double d, double e, double f) { + return a + b + c + d + e + f; +} +double sum7(double a, double b, double c, double d, double e, double f, double g) { + return a + b + c + d + e + f + g; +} + + +void test_dynamic() { + + double x, f; + te_variable lookup[] = { + {"x", &x}, + {"f", &f}, + {"sum0", sum0, TE_FUNCTION0}, + {"sum1", sum1, TE_FUNCTION1}, + {"sum2", sum2, TE_FUNCTION2}, + {"sum3", sum3, TE_FUNCTION3}, + {"sum4", sum4, TE_FUNCTION4}, + {"sum5", sum5, TE_FUNCTION5}, + {"sum6", sum6, TE_FUNCTION6}, + {"sum7", sum7, TE_FUNCTION7}, + }; + + test_case cases[] = { + {"x", 2}, + {"f+x", 7}, + {"x+x", 4}, + {"x+f", 7}, + {"f+f", 10}, + {"f+sum0", 11}, + {"sum0+sum0", 12}, + {"sum0()+sum0", 12}, + {"sum0+sum0()", 12}, + {"sum0()+(0)+sum0()", 12}, + {"sum1 sum0", 12}, + {"sum1(sum0)", 12}, + {"sum1 f", 10}, + {"sum1 x", 4}, + {"sum2 (sum0, x)", 8}, + {"sum3 (sum0, x, 2)", 10}, + {"sum2(2,3)", 5}, + {"sum3(2,3,4)", 9}, + {"sum4(2,3,4,5)", 14}, + {"sum5(2,3,4,5,6)", 20}, + {"sum6(2,3,4,5,6,7)", 27}, + {"sum7(2,3,4,5,6,7,8)", 35}, + }; + + x = 2; + f = 5; + + 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; + te_expr *ex = te_compile(expr, lookup, sizeof(lookup)/sizeof(te_variable), &err); + lok(ex); + lfequal(te_eval(ex), answer); + te_free(ex); + } +} + + +double clo0(void *context) { + if (context) return *((double*)context) + 6; + return 6; +} +double clo1(void *context, double a) { + if (context) return *((double*)context) + a * 2; + return a * 2; +} +double clo2(void *context, double a, double b) { + if (context) return *((double*)context) + a + b; + return a + b; +} + +double cell(void *context, double a) { + double *c = context; + return c[(int)a]; +} + +void test_closure() { + + double extra; + double c[] = {5,6,7,8,9}; + + te_variable lookup[] = { + {"c0", clo0, TE_CLOSURE0, &extra}, + {"c1", clo1, TE_CLOSURE1, &extra}, + {"c2", clo2, TE_CLOSURE2, &extra}, + {"cell", cell, TE_CLOSURE1, c}, + }; + + test_case cases[] = { + {"c0", 6}, + {"c1 4", 8}, + {"c2 (10, 20)", 30}, + }; + + 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; + te_expr *ex = te_compile(expr, lookup, sizeof(lookup)/sizeof(te_variable), &err); + lok(ex); + + extra = 0; + lfequal(te_eval(ex), answer + extra); + + extra = 10; + lfequal(te_eval(ex), answer + extra); + + te_free(ex); + } + + + test_case cases2[] = { + {"cell 0", 5}, + {"cell 1", 6}, + {"cell 0 + cell 1", 11}, + {"cell 1 * cell 3 + cell 4", 57}, + }; + + for (i = 0; i < sizeof(cases2) / sizeof(test_case); ++i) { + const char *expr = cases2[i].expr; + const double answer = cases2[i].answer; + + int err; + te_expr *ex = te_compile(expr, lookup, sizeof(lookup)/sizeof(te_variable), &err); + lok(ex); + lfequal(te_eval(ex), answer); + te_free(ex); + } +} + +void test_optimize() { + + test_case cases[] = { + {"5+5", 10}, + {"pow(2,2)", 4}, + {"sqrt 100", 10}, + {"pi * 2", 6.2832}, + }; + + 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; + te_expr *ex = te_compile(expr, 0, 0, &err); + lok(ex); + + /* The answer should be know without + * even running eval. */ + lfequal(ex->value, answer); + lfequal(te_eval(ex), answer); + + te_free(ex); + } +} + +void test_pow() { +#ifdef TE_POW_FROM_RIGHT + test_equ cases[] = { + {"2^3^4", "2^(3^4)"}, + {"-2^2", "-(2^2)"}, + {"--2^2", "(2^2)"}, + {"---2^2", "-(2^2)"}, + {"-(2)^2", "-(2^2)"}, + {"-(2*1)^2", "-(2^2)"}, + {"-2^2", "-4"}, + {"2^1.1^1.2^1.3", "2^(1.1^(1.2^1.3))"}, + {"-a^b", "-(a^b)"}, + {"-a^-b", "-(a^-b)"} + }; +#else + test_equ cases[] = { + {"2^3^4", "(2^3)^4"}, + {"-2^2", "(-2)^2"}, + {"--2^2", "2^2"}, + {"---2^2", "(-2)^2"}, + {"-2^2", "4"}, + {"2^1.1^1.2^1.3", "((2^1.1)^1.2)^1.3"}, + {"-a^b", "(-a)^b"}, + {"-a^-b", "(-a)^(-b)"} + }; +#endif + + double a = 2, b = 3; + + te_variable lookup[] = { + {"a", &a}, + {"b", &b} + }; + + int i; + for (i = 0; i < sizeof(cases) / sizeof(test_equ); ++i) { + const char *expr1 = cases[i].expr1; + const char *expr2 = cases[i].expr2; + + te_expr *ex1 = te_compile(expr1, lookup, sizeof(lookup)/sizeof(te_variable), 0); + te_expr *ex2 = te_compile(expr2, lookup, sizeof(lookup)/sizeof(te_variable), 0); + + lok(ex1); + lok(ex2); + + double r1 = te_eval(ex1); + double r2 = te_eval(ex2); + + fflush(stdout); + lfequal(r1, r2); + + te_free(ex1); + te_free(ex2); + } + +} + +void test_combinatorics() { + test_case cases[] = { + {"fac(0)", 1}, + {"fac(0.2)", 1}, + {"fac(1)", 1}, + {"fac(2)", 2}, + {"fac(3)", 6}, + {"fac(4.8)", 24}, + {"fac(10)", 3628800}, + + {"ncr(0,0)", 1}, + {"ncr(10,1)", 10}, + {"ncr(10,0)", 1}, + {"ncr(10,10)", 1}, + {"ncr(16,7)", 11440}, + {"ncr(16,9)", 11440}, + {"ncr(100,95)", 75287520}, + + {"npr(0,0)", 1}, + {"npr(10,1)", 10}, + {"npr(10,0)", 1}, + {"npr(10,10)", 3628800}, + {"npr(20,5)", 1860480}, + {"npr(100,4)", 94109400}, + }; + + + 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); + + if (err) { + printf("FAILED: %s (%d)\n", expr, err); + } + } +} + + +int main(int argc, char *argv[]) +{ + lrun("Results", test_results); + lrun("Syntax", test_syntax); + lrun("NaNs", test_nans); + lrun("INFs", test_infs); + lrun("Variables", test_variables); + lrun("Functions", test_functions); + lrun("Dynamic", test_dynamic); + lrun("Closure", test_closure); + lrun("Optimize", test_optimize); + lrun("Pow", test_pow); + lrun("Combinatorics", test_combinatorics); + lresults(); + + return lfails != 0; +}