mirror of
https://github.com/eledio-devices/thirdparty-tinyexpr.git
synced 2025-10-30 16:15:41 +01:00
Initial commit
This commit is contained in:
22
LICENSE.md
Normal file
22
LICENSE.md
Normal 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
28
Makefile
Normal 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
240
README.md
Normal 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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**te_compile** also automatically prunes constant branches. In this example,
|
||||||
|
the compiled expression returned by *te_compile* is:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**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
124
benchmark.c
Normal 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
8
doc/e1.dot
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
digraph G {
|
||||||
|
"+" -> "sin";
|
||||||
|
"+" -> div;
|
||||||
|
"sin" -> "x";
|
||||||
|
div -> "1";
|
||||||
|
div -> "4";
|
||||||
|
div [label="÷"]
|
||||||
|
}
|
||||||
BIN
doc/e1.png
Normal file
BIN
doc/e1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
5
doc/e2.dot
Normal file
5
doc/e2.dot
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
digraph G {
|
||||||
|
"+" -> "sin";
|
||||||
|
"+" -> "0.25";
|
||||||
|
"sin" -> "x";
|
||||||
|
}
|
||||||
BIN
doc/e2.png
Normal file
BIN
doc/e2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
10
example.c
Normal file
10
example.c
Normal 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
39
example2.c
Normal 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
127
minctest.h
Normal 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
233
test.c
Normal 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
406
tinyexpr.c
Normal 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
74
tinyexpr.h
Normal 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__*/
|
||||||
Reference in New Issue
Block a user