This commit is contained in:
Lewis Van Winkle
2017-04-03 13:07:48 -05:00
3 changed files with 149 additions and 18 deletions

View File

@@ -3,7 +3,7 @@
<img alt="TinyExpr logo" src="https://codeplea.com/public/content/tinyexpr_logo.png" align="right"/>
#TinyExpr
# 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
@@ -12,7 +12,7 @@ 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
## Features
- **ANSI C with no dependencies**.
- Single source file and header file.
@@ -25,12 +25,12 @@ the standard C math functions and runtime binding of variables.
- Easy to use and integrate with your code
- Thread-safe, provided that your *malloc* is.
##Building
## 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
## Short Example
Here is a minimal example to evaluate an expression at runtime.
@@ -40,7 +40,7 @@ Here is a minimal example to evaluate an expression at runtime.
```
##Usage
## Usage
TinyExpr defines only four functions:
@@ -51,7 +51,7 @@ TinyExpr defines only four functions:
void te_free(te_expr *expr);
```
##te_interp
## te_interp
```C
double te_interp(const char *expression, int *error);
```
@@ -72,7 +72,7 @@ of the parse error on failure, and set `*error` to 0 on success.
double c = te_interp("(5+5", &error); /* Returns NaN, error is set to 4. */
```
##te_compile, te_eval, te_free
## te_compile, te_eval, te_free
```C
te_expr *te_compile(const char *expression, const te_variable *lookup, int lookup_len, int *error);
double te_eval(const te_expr *n);
@@ -117,7 +117,7 @@ After you're finished, make sure to call `te_free()`.
```
##Longer Example
## 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.
@@ -178,7 +178,7 @@ This produces the output:
5.000000
##Binding to Custom Functions
## Binding to Custom Functions
TinyExpr can also call to custom functions implemented in C. Here is a short example:
@@ -197,7 +197,7 @@ te_expr *n = te_compile("mysum(5, 6)", vars, 1, 0);
```
##How it works
## 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"`
@@ -216,7 +216,7 @@ and return the result of the expression.
`te_free()` should always be called when you're done with the compiled expression.
##Speed
## Speed
TinyExpr is pretty fast compared to C when the expression is short, when the
@@ -237,7 +237,7 @@ Here is some example performance numbers taken from the included
##Grammar
## Grammar
TinyExpr parses the following grammar:
@@ -262,33 +262,39 @@ notation (e.g. *1e3* for *1000*). A leading zero is not required (e.g. *.5*
for *0.5*)
##Functions supported
## 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).
In addition, the following C math functions are also supported:
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
## Compile-time options
By default, TinyExpr does exponentation from left to right. For example:
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 exponentation work from right to left, you need to
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:
@@ -300,7 +306,7 @@ 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
## Hints
- All functions/types start with the letters *te*.

87
test.c
View File

@@ -213,6 +213,13 @@ void test_nans() {
"1%0",
"1%(1%0)",
"(1%0)%1",
"fac(-1)",
"ncr(2, 4)",
"ncr(-2, 4)",
"ncr(2, -4)",
"npr(2, 4)",
"npr(-2, 4)",
"npr(2, -4)",
};
int i;
@@ -234,6 +241,40 @@ void test_nans() {
}
void test_infs() {
const char *infs[] = {
"1/0",
"log(0)",
"pow(2,10000000)",
"fac(300)",
"ncr(300,100)",
"ncr(300000,100)",
"ncr(300000,100)*8",
"npr(3,2)*ncr(300000,100)",
"npr(100,90)",
"npr(30,25)",
};
int i;
for (i = 0; i < sizeof(infs) / sizeof(const char *); ++i) {
const char *expr = infs[i];
int err;
const double r = te_interp(expr, &err);
lequal(err, 0);
lok(r == r + 1);
te_expr *n = te_compile(expr, 0, 0, &err);
lok(n);
lequal(err, 0);
const double c = te_eval(n);
lok(c == c + 1);
te_free(n);
}
}
void test_variables() {
double x, y, test;
@@ -587,17 +628,63 @@ void test_pow() {
}
void test_combinatorics() {
test_case cases[] = {
{"fac(0)", 1},
{"fac(0.2)", 1},
{"fac(1)", 1},
{"fac(2)", 2},
{"fac(3)", 6},
{"fac(4.8)", 24},
{"fac(10)", 3628800},
{"ncr(0,0)", 1},
{"ncr(10,1)", 10},
{"ncr(10,0)", 1},
{"ncr(10,10)", 1},
{"ncr(16,7)", 11440},
{"ncr(16,9)", 11440},
{"ncr(100,95)", 75287520},
{"npr(0,0)", 1},
{"npr(10,1)", 10},
{"npr(10,0)", 1},
{"npr(10,10)", 3628800},
{"npr(20,5)", 1860480},
{"npr(100,4)", 94109400},
};
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);
if (err) {
printf("FAILED: %s (%d)\n", expr, err);
}
}
}
int main(int argc, char *argv[])
{
lrun("Results", test_results);
lrun("Syntax", test_syntax);
lrun("NaNs", test_nans);
lrun("INFs", test_infs);
lrun("Variables", test_variables);
lrun("Functions", test_functions);
lrun("Dynamic", test_dynamic);
lrun("Closure", test_closure);
lrun("Optimize", test_optimize);
lrun("Pow", test_pow);
lrun("Combinatorics", test_combinatorics);
lresults();
return lfails != 0;

View File

@@ -39,11 +39,17 @@ For log = natural log uncomment the next line. */
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#ifndef NAN
#define NAN (0.0/0.0)
#endif
#ifndef INFINITY
#define INFINITY (1.0/0.0)
#endif
typedef double (*te_fun2)(double, double);
enum {
@@ -113,6 +119,35 @@ void te_free(te_expr *n) {
static double pi() {return 3.14159265358979323846;}
static double e() {return 2.71828182845904523536;}
static double fac(double a) {/* simplest version of fac */
if (a < 0.0)
return NAN;
if (a > UINT_MAX)
return INFINITY;
unsigned int ua = (unsigned int)(a);
unsigned long int result = 1, i;
for (i = 1; i <= ua; i++) {
if (i > ULONG_MAX / result)
return INFINITY;
result *= i;
}
return (double)result;
}
static double ncr(double n, double r) {
if (n < 0.0 || r < 0.0 || n < r) return NAN;
if (n > UINT_MAX || r > UINT_MAX) return INFINITY;
unsigned long int un = (unsigned int)(n), ur = (unsigned int)(r), i;
unsigned long int result = 1;
if (ur > un / 2) ur = un - ur;
for (i = 1; i <= ur; i++) {
if (result > ULONG_MAX / (un - ur + i))
return INFINITY;
result *= un - ur + i;
result /= i;
}
return result;
}
static double npr(double n, double r) {return ncr(n, r) * fac(r);}
static const te_variable functions[] = {
/* must be in alphabetical order */
@@ -126,6 +161,7 @@ static const te_variable functions[] = {
{"cosh", cosh, TE_FUNCTION1 | TE_FLAG_PURE, 0},
{"e", e, TE_FUNCTION0 | TE_FLAG_PURE, 0},
{"exp", exp, TE_FUNCTION1 | TE_FLAG_PURE, 0},
{"fac", fac, TE_FUNCTION1 | TE_FLAG_PURE, 0},
{"floor", floor, TE_FUNCTION1 | TE_FLAG_PURE, 0},
{"ln", log, TE_FUNCTION1 | TE_FLAG_PURE, 0},
#ifdef TE_NAT_LOG
@@ -134,6 +170,8 @@ static const te_variable functions[] = {
{"log", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0},
#endif
{"log10", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0},
{"ncr", ncr, TE_FUNCTION2 | TE_FLAG_PURE, 0},
{"npr", npr, TE_FUNCTION2 | TE_FLAG_PURE, 0},
{"pi", pi, TE_FUNCTION0 | TE_FLAG_PURE, 0},
{"pow", pow, TE_FUNCTION2 | TE_FLAG_PURE, 0},
{"sin", sin, TE_FUNCTION1 | TE_FLAG_PURE, 0},