+ add: complete copy of TinyExpr fork for NP3 adaption

This commit is contained in:
Rainer Kottenhoff 2018-08-10 21:38:25 +02:00
parent aa2883eeb3
commit b9d1fc0d64
14 changed files with 1409 additions and 0 deletions

7
tinyexpr/.travis.yml Normal file
View File

@ -0,0 +1,7 @@
language: c
compiler:
- clang
- gcc
script: make

10
tinyexpr/CONTRIBUTING Normal file
View File

@ -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.

33
tinyexpr/Makefile Normal file
View File

@ -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

319
tinyexpr/README.md Normal file
View File

@ -0,0 +1,319 @@
[![Build Status](https://travis-ci.org/codeplea/tinyexpr.svg?branch=master)](https://travis-ci.org/codeplea/tinyexpr)
<img alt="TinyExpr logo" src="https://codeplea.com/public/content/tinyexpr_logo.png" align="right"/>
# 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 <stdio.h>
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:
<list> = <expr> {"," <expr>}
<expr> = <term> {("+" | "-") <term>}
<term> = <factor> {("*" | "/" | "%") <factor>}
<factor> = <power> {"^" <power>}
<power> = {("-" | "+")} <base>
<base> = <constant>
| <variable>
| <function-0> {"(" ")"}
| <function-1> <power>
| <function-X> "(" <expr> {"," <expr>} ")"
| "(" <list> ")"
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.

125
tinyexpr/benchmark.c Normal file
View File

@ -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 <stdio.h>
#include <time.h>
#include <math.h>
#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;
}

8
tinyexpr/doc/e1.dot Normal file
View File

@ -0,0 +1,8 @@
digraph G {
"+" -> "sin";
"+" -> div;
"sin" -> "x";
div -> "1";
div -> "4";
div [label="&#247;"]
}

BIN
tinyexpr/doc/e1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

5
tinyexpr/doc/e2.dot Normal file
View File

@ -0,0 +1,5 @@
digraph G {
"+" -> "sin";
"+" -> "0.25";
"sin" -> "x";
}

BIN
tinyexpr/doc/e2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

10
tinyexpr/example.c Normal file
View File

@ -0,0 +1,10 @@
#include "tinyexpr.h"
#include <stdio.h>
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;
}

38
tinyexpr/example2.c Normal file
View File

@ -0,0 +1,38 @@
#include "tinyexpr.h"
#include <stdio.h>
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;
}

35
tinyexpr/example3.c Normal file
View File

@ -0,0 +1,35 @@
#include "tinyexpr.h"
#include <stdio.h>
/* 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;
}

128
tinyexpr/minctest.h Normal file
View File

@ -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 <stdio.h>
#include <math.h>
#include <time.h>
/* 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__*/

691
tinyexpr/test.c Normal file
View File

@ -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 <stdio.h>
#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;
}