Initial commit

This commit is contained in:
Lewis Van Winkle
2016-01-21 14:36:59 -06:00
commit ec3dde1e24
14 changed files with 1316 additions and 0 deletions

22
LICENSE.md Normal file
View File

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

28
Makefile Normal file
View File

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

240
README.md Normal file
View File

@@ -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 <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;
}
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 <stdio.h>
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:
<expr> = <term> {("+" | "-") <term>}
<term> = <factor> {("*" | "/" | "%") <factor>}
<factor> = <power> {"^" <power>}
<power> = {("-" | "+")} <base>
<base> = <constant> | <variable> | <function> <power> | "(" <expr> ")"
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.

124
benchmark.c Normal file
View File

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

8
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
doc/e1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

5
doc/e2.dot Normal file
View File

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

BIN
doc/e2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

10
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;
}

39
example2.c Normal file
View File

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

127
minctest.h Normal file
View File

@@ -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 <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;\
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__*/

233
test.c Normal file
View File

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

406
tinyexpr.c Normal file
View File

@@ -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 <stdlib.h>
#include <math.h>
#include <string.h>
#include <stdio.h>
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) {
/* <base> = <constant> | <variable> | <function> <power> | "(" <expr> ")" */
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) {
/* <power> = {("-" | "+")} <base> */
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) {
/* <factor> = <power> {"^" <power>} */
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) {
/* <term> = <factor> {("*" | "/" | "%") <factor>} */
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) {
/* <expr> = <term> {("+" | "-") <term>} */
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);
}

74
tinyexpr.h Normal file
View File

@@ -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__*/