Tiny recursive descent expression parser, compiler, and evaluation engine for math expressions; Lewis Van Winkle (2016).
npm install tinyexpr.c
sh
$ npm i tinyexpr.c
`
And then include tinyexpr.h as follows:
`c
// main.c
#define TINYEXPR_IMPLEMENTATION
#include
int main() { / ... / }
`
Finally, compile while adding the path node_modules/tinyexpr.c to your compiler's include paths.
`bash
$ clang -I./node_modules/tinyexpr.c main.c # or, use gcc
$ gcc -I./node_modules/tinyexpr.c main.c
`
You may also use a simpler approach with the cpoach tool, which automatically adds the necessary include paths of all the installed dependencies for your project.
`bash
$ cpoach clang main.c # or, use gcc
$ cpoach gcc main.c
`
Short Example
Here is a minimal example to evaluate an expression at runtime.
`C
#include "tinyexpr.h"
printf("%f\n", te_interp("55", 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);
`
$3
`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. /
`
$3
`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 third arguments as 0.
Give te_eval() a te_expr* from te_compile(). te_eval() will evaluate the expression
using the current variable values.
After you're finished, make sure to call te_free().
Example usage:
`C
double x, y;
/ Store variable names and pointers. /
te_variable vars[] = {{"x", &x}, {"y", &y}};
int err;
/ Compile the expression with variables. /
te_expr *expr = te_compile("sqrt(x^2+y^2)", vars, 2, &err);
if (expr) {
x = 3; y = 4;
const double h1 = te_eval(expr); / Returns 5. /
x = 5; y = 12;
const double h2 = te_eval(expr); / Returns 13. /
te_free(expr);
} else {
printf("Parse error at %d\n", err);
}
`
Longer Example
Here is a complete example that will evaluate an expression passed in from the command
line. It also does error checking and binds the variables x and y to 3 and 4, respectively.
`C
#include "tinyexpr.h"
#include
int main(int argc, char *argv[])
{
if (argc < 2) {
printf("Usage: example2 \"expression\"\n");
return 0;
}
const char *expression = argv[1];
printf("Evaluating:\n\t%s\n", expression);
/* This shows an example where the variables
x and y are bound at eval-time. /
double x, y;
te_variable vars[] = {{"x", &x}, {"y", &y}};
/ This will compile the expression and check for errors. /
int err;
te_expr *n = te_compile(expression, vars, 2, &err);
if (n) {
/* The variables can be changed here, and eval can be called as many
* times as you like. This is fairly efficient because the parsing has
already been done. /
x = 3; y = 4;
const double r = te_eval(n); printf("Result:\n\t%f\n", r);
te_free(n);
} else {
/ Show the user where the error is at. /
printf("\t%*s^\nError near here", err-1, "");
}
return 0;
}
`
This produces the output:
`bash
$ 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
te_compile() also automatically prunes constant branches. In this example,
the compiled expression returned by te_compile() would become:
!example syntax tree
te_eval() will automatically load in any variables by their pointer, and then evaluate
and return the result of the expression.
te_free() should always be called when you're done with the compiled expression.
Speed
TinyExpr is pretty fast compared to C when the expression is short, when the
expression does hard calculations (e.g. exponentiation), and when some of the
work can be simplified by te_compile(). TinyExpr is slow compared to C when the
expression is long and involves only basic arithmetic.
Here is some example performance numbers taken from the included
benchmark.c program:
| Expression | te_eval time | native C time | slowdown |
| :------------- |-------------:| -----:|----:|
| sqrt(a^1.5+a^2.5) | 15,641 ms | 14,478 ms | 8% slower |
| a+5 | 765 ms | 563 ms | 36% slower |
| a+(5*2) | 765 ms | 563 ms | 36% slower |
| (a+5)*2 | 1422 ms | 563 ms | 153% slower |
| (1/(a+1)+2/(a+2)+3/(a+3)) | 5,516 ms | 1,266 ms | 336% slower |
Grammar
TinyExpr parses the following grammar:
= {"," }
= {("+" | "-") }
= {("*" | "/" | "%") }
= {"^" }
= {("-" | "+")}
=
|
| {"(" ")"}
|
| "(" {"," } ")"
| "(" ")"
In addition, whitespace between tokens is ignored.
Valid variable names consist of a letter followed by any combination of:
letters, the digits 0 through 9, and underscore. Constants can be integers
or floating-point numbers, and can be in decimal, hexadecimal (e.g., 0x57CEF7),
or 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`.