# TinyExpr is two C files and four functions for runtime math

> TinyExpr is a recursive descent expression parser for C that compiles a math expression into a tree you evaluate repeatedly, with variables bound by pointer at evaluation time. It is C99, dependency free and zlib licensed, and its own README is candid about when it is slower than hand written C.

**codeplea/tinyexpr** — tiny recursive descent expression parser, compiler, and evaluation engine for math expressions

- Repository: https://github.com/codeplea/tinyexpr
- Website: https://codeplea.com/tinyexpr
- Stars: 1,937 · Forks: 313
- Language: C
- License: Zlib
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/codeplea-tinyexpr

## Two files, four functions, no build system required

The library is self-contained in tinyexpr.c and tinyexpr.h. There is nothing to install and nothing to link beyond the math library: the README says simply to add those two files to your project, and the Makefile links with -lm.

The public API is four functions, and the whole surface fits in one declaration block:

```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);
```

That is the design in one screen. If you need a formula evaluator in a firmware image, a game settings file or a spreadsheet-like field in a desktop tool, you copy two files and call one of these.

Language coverage is wider than arithmetic alone: standard operators with correct precedence, comparison operators (==, !=, <, <=, >, >=), logical operators (&&, ||, !), the standard C math functions such as sin, sqrt and ln, and user supplied functions and variables.

## te_interp is the shortcut, and it reports failure as NaN

The one line version compiles and evaluates in a single call:

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

The error handling is worth reading twice. On a parse error te_interp returns NaN, which means a caller that ignores the error pointer cannot distinguish a bad expression from a legitimate computation that produced NaN. If the error pointer is not 0, it receives the position of the parse error on failure and 0 on success:

```C
double c = te_interp("(5+5", &error); /* Returns NaN, error is set to 4. */
```

 Always pass a real error pointer if a human will ever see the input.

## Variables are pointers, so values change between evaluations

The compiled form is where the library earns its keep. You declare a te_variable array pairing a name with a pointer to a double, compile once against that array, and then evaluate as often as you like while the pointed-to values change:

```C
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);
```

With x set to 3 and y to 4 the evaluation returns 5, and with x set to 5 and y to 12 the same compiled expression returns 13. That is the whole point of splitting compile from evaluate: parsing is the expensive part and it happens once.

The tradeoff is that the pointers must stay valid for the lifetime of the compiled expression. Freeing or reallocating the doubles while a te_expr still refers to them is a dangling read, and the library has no way to warn you. Calling te_free when you are done is mandatory, as the README states plainly.

On failure te_compile returns 0, and you can pass 0 for the variable arguments to compile a constant expression.

## Calling your own C functions from an expression

A variable entry can hold a function pointer instead of a double pointer, and the arity is declared with a TE_FUNCTION marker:

```C
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);
```

So the same table that binds variables also extends the function namespace, and an expression can mix them. For a project with its own domain functions, that is the mechanism for exposing them without modifying the parser.

Note the error argument is 0 in that example. Fine when the expression is a constant you control, not fine for input from a user.

## Compile prunes constant branches before evaluation

The parser is a straightforward recursive descent one, and the README shows the tree it builds for the expression `sin x + 1/4`. That alone would be unremarkable, except for the next step: te_compile automatically prunes constant branches, so the compiled tree for that expression has the `1/4` already folded away before te_eval ever runs.

Variable loading happens inside te_eval, which dereferences the pointers from the te_variable table as it walks the tree. Constant folding at compile time is why the README can claim TinyExpr is fast in cases where some of the work can be simplified by te_compile, and it is a real advantage for a formula that mixes constants with a variable.

Where it is slow is equally documented: long expressions involving only basic arithmetic run slower than equivalent C, because every node in the tree is visited through pointers on each evaluation. The included benchmark.c program exists to measure exactly this, and the README's performance table is generated from it.

## The Makefile builds a smoke test in two configurations

The repository builds with plain gcc, no autotools, and the default target compiles and runs a list of programs:

```makefile
CC = gcc
CCFLAGS = -Wall -Wshadow -O2
LFLAGS = -lm
```

Two of the targets are worth knowing about. The smoke target compiles smoke.c with tinyexpr.c and immediately runs it, so a build failure shows up as a non-zero exit. The smoke_pr target rebuilds the same source with two defines, -DTE_POW_FROM_RIGHT and -DTE_NAT_LOG, which switch exponentiation to right-associative parsing and natural logarithm base respectively.

That is the only configuration surface in the project, and it changes parsing semantics rather than just arithmetic details. An expression containing ^ that means one thing in your default build means another in smoke_pr, so if you adopt these defines you must decide deliberately, per project, rather than by copying a build line.

The remaining targets build repl, an interactive read line REPL built from repl.c, a benchmark, and three examples. repl-readline is available when the readline library is present.

## Licence, versioning and where a full expression language wins

TinyExpr is released under the zlib license, described in the README as free for nearly any use, which in practice means permissive with attribution and no copyleft obligation on your own source.

Releases are infrequent and infrequent in a good way here: v1.1 and v1.1.1 both landed on 2026-08-06, and the last push was on 2026-10-08. The API is small enough that a stable 1.x line is plausible rather than aspirational, and the README does not describe a deprecation policy.

The realistic alternative for new work may be embedding a scripting language instead. TinyExpr gives you none of that. What it gives you is four functions, two files and no runtime dependency, which is exactly what you want in an embedded context and exactly what you do not want if your users will want to write something like a loop. Decide by asking whether your formulas need to be more than arithmetic; if they do, the scripting route costs more in binary size and less in your own code.

## Conclusion

Adopt TinyExpr when your C or C++ project needs user supplied formulas, scriptable config values or a calculator field, and the two file drop in matters more than raw speed. Do not use it to hot path numeric work, since the README states it is slower than plain C on long expressions with basic arithmetic. Verify first which of the four functions your call site needs, and if you compile constants yourself remember the Makefile smoke_pr target compiles with -DTE_POW_FROM_RIGHT and -DTE_NAT_LOG.

## FAQ

### How do I add TinyExpr to a C project?

Copy tinyexpr.c and tinyexpr.h into your project, include the header and link against the math library with -lm. There is nothing to install, and the README recommends exactly this rather than a package manager.

### What is the difference between te_interp and te_compile?

te_interp takes a string and immediately returns the result, returning NaN on a parse error. te_compile parses the expression into a tree that te_eval can run repeatedly, which is the efficient path when the same expression is evaluated many times.

### How do I add my own function to a TinyExpr expression?

Put the function pointer in a te_variable entry with the matching arity marker, such as TE_FUNCTION2 for a two argument function, then pass that table to te_compile. The README does this for a function called my_sum compiled as the expression mysum(5, 6).

### Is TinyExpr fast enough for production?

The README states TinyExpr is fast for short expressions, for hard calculations such as exponentiation, and where te_compile can simplify work, but slow compared to C for long expressions with only basic arithmetic. benchmark.c is included to measure this on your own hardware.

## Sources

- [codeplea/tinyexpr on GitHub](https://github.com/codeplea/tinyexpr)
- [License: Zlib](https://github.com/codeplea/tinyexpr/blob/master/LICENSE)
- [Project website](https://codeplea.com/tinyexpr)
- [README](https://github.com/codeplea/tinyexpr/blob/master/README.md)
- [Releases](https://github.com/codeplea/tinyexpr/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/codeplea-tinyexpr
