XUtils

Tinyexpr

Tiny recursive-descent parser, compiler and evaluation engine for simple mathematical expressions. [`zlib`](https://directory.fsf.org/wiki/License:Zlib)


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.

    #include "tinyexpr.h"
    printf("%f\n", te_interp("5*5", 0)); /* Prints 25. */

Binding to Custom Functions

TinyExpr can also call to custom functions implemented in C. Here is a short example:

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:

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

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.


Articles

  • coming soon...