tiny recursive descent expression parser, compiler, and evaluation engine for math expressions

Related tags

Utilities tinyexpr
Overview

Build Status

TinyExpr logo

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

  • C99 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 add custom functions and variables easily.
  • 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.

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. */

Usage

TinyExpr defines only four functions:

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

te_interp

    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:

    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. */

te_compile, te_eval, te_free

    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:

    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.

    #include "tinyexpr.h"
    #include <stdio.h>

    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:

$ 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:

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 lower case letter followed by any combination of: lower case letters a through z, the digits 0 through 9, and underscore. 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, 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.

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.

Comments
  • Added arity greater than 2, and test cases.

    Added arity greater than 2, and test cases.

    I don't know what you think about the changes I made, please take a moment to consider merging them. It supports functions with arity from 0 to 7, and is somewhat easily scaled. You might guess this changes lots of things, so I understand if you are reluctant to merge it.

    opened by EvilPudding 27
  • Addition to benchmark suite

    Addition to benchmark suite

    I've added added TinyExpr to the following mathematical expression benchmark suite: https://github.com/ArashPartow/math-parser-benchmark-project

    The specific code can be found here: https://github.com/ArashPartow/math-parser-benchmark-project/blob/master/src/BenchTinyExpr.cpp

    If possible could you review the above and make sure, the library is being used correctly and that there's no unintended latencies being added.

    Here is an example run:

    Expression 92 of 205: "abs(sin(sqrt(a^2+b^2)))"; Progress: ############
    [01] ExprTk               ( 64.072 ns, 0.630283156548410717,  630283.156537625589407980)
    [02] muparserSSE          ( 68.056 ns, 0.630283117294311523,  630283.117294311523437500)
    [03] ExprTkFloat          ( 68.758 ns, 0.630283117294311523,  630283.117294311523437500)
    [04] atmsp 1.0.4          ( 74.571 ns, 0.630283156548410717,  630283.156537625589407980)
    [05] FParser 4.5          ( 74.707 ns, 0.630283156548410717,  630283.156537625589407980)
    [06] muparser 2.2.4       ( 81.762 ns, 0.630283156548410717,  630283.156537625589407980)
    [07] muparser 2.2.4 (omp) ( 83.188 ns, 0.630283156548410717,  630283.156537625589407980)
    [08] MTParser             (123.360 ns, 0.630283156548410717,  630283.156537625589407980)
    [09] MathExpr             (251.873 ns, 0.630283156548410717,  630283.156537625589407980)
    [10] TinyExpr             (295.522 ns, 0.630283156548410717,  630283.156537625589407980)
    [11] Lepton               (408.594 ns, 0.630283156548410717,  630283.156537625589407980)
    [12] muparserx            (533.038 ns, 0.630283156548410717,  630283.156537625589407980)
    

    During integration of TinyExpr issues relating to compiler diagnostics during compilation of library and bugs relating to expression parsing were found, here are some of the changes:

    1. https://github.com/ArashPartow/math-parser-benchmark-project/blob/master/tinyexpr/tinyexpr.c#L232
    2. https://github.com/ArashPartow/math-parser-benchmark-project/blob/master/tinyexpr/tinyexpr.h#L38
    3. https://github.com/ArashPartow/math-parser-benchmark-project/blob/master/tinyexpr/tinyexpr.h#L56

    When you get a chance, can you review the above changes and make sure they're correct, and don't add any unintended latencies or evaluation errors.


    There also seems to be general precedence and associativity issues with regards to the generated AST/evaluation. The following is one example of such problems:

    Expression 61 of 96: "-a^-b"; Progress: ############
    [01] muparserSSE          ( 68.395 ns, -0.810841679573059082, -6154.623031616210937500)
    [02] ExprTk               ( 71.411 ns, -0.810841732005176952, -6154.623390711222782556)
    [03] atmsp 1.0.4          ( 72.180 ns, -0.810841732005176952, -6154.623390711222782556)
    [04] ExprTkFloat          ( 79.174 ns, -0.810841679573059082, -6154.623031616210937500)
    [05] MathExpr             ( 90.595 ns, -0.810841732005176952, -6154.623390711222782556)
    [06] FParser 4.5          ( 96.818 ns, -0.810841732005177063, -6154.623390711222782556)
    [07] muparser 2.2.4       (108.624 ns, -0.810841732005176952, -6154.623390711222782556)
    [08] muparser 2.2.4 (omp) (109.714 ns, -0.810841732005176952, -6154.623390711222782556)
    [09] Lepton               (362.571 ns, -0.810841732005176952, -6154.623390711222782556)
    [10] muparserx            (397.474 ns, -0.810841732005176952, -6154.623390711222782556)
    DNQ List
    [01] TinyExpr             ( 85.975 ns, 0.000000000000000000, 0.000000000000000000)
    

    In the following the call to log seems to have a different meaning to the one commonly used in programming languages (log is generally known as the natural logarithm base e (aka ln), where as TinyExpr seems to implement it as log base 10):

    Expression 170 of 205: "10^log(3+b)"; Progress: ############
    [01] ExprTk         ( 86.773 ns,44.530608078220737411, 35146523.457089543342590332)
    [02] muparserSSE    ( 97.832 ns,44.530609130859375000, 35146521.568298339843750000)
    [03] ExprTkFloat    ( 99.054 ns,44.530609130859375000, 35146521.568298339843750000)
    [04] MTParser       (102.149 ns,44.530608078220737411, 35146523.457089543342590332)
    [05] muparser 2.2.4 (104.093 ns,44.530608078220737411, 35146523.457089543342590332)
    [06] atmsp 1.0.4    (104.925 ns,44.530608078220737411, 35146523.457089543342590332)
    [07] muparser 2.2.4 (105.696 ns,44.530608078220737411, 35146523.457089543342590332)
    [08] FParser 4.5    (118.495 ns,44.530608078220751622, 35146523.457089543342590332)
    [09] MathExpr       (127.893 ns,44.530608078220737411, 35146523.457089543342590332)
    [10] Lepton         (296.503 ns,44.530608078220737411, 35146523.457089543342590332)
    [11] muparserx      (418.354 ns,44.530608078220737411, 35146523.457089543342590332)
    DNQ List
    [01] TinyExpr       (117.324 ns, 5.200000000000001066, 4650000.000055111944675446)
    

    Here's a list of only some of the problematic expressions, though running all the benchmarks results in a great deal more:

    (0.1*a+1)*a+1.1-sin(a)-log(a)/a*3/4 - incorrect result
    -(-b^2^3)+b^6    - incorrect result
    -a^(-b)          - incorrect result
    -a^+b            - incorrect result
    -a^-b            - incorrect result
    -a^-b+1.1        - incorrect result
    -a^-b/1.1        - incorrect result
    -a^2^3-a^8       - incorrect result
    -b^2^3-b^6       - incorrect result
    +a^+2^+3-a^+8    - incorrect result
    10^log(3+b)      - incorrect result
    a-(e^(log(7+b))) - incorrect result
    a^-2^-3-1/a^1/8  - incorrect result
    a^-2^3-1/a^8     - incorrect result
    a^2.2^3.3        - incorrect result
    a^2.2^3.3-a^13.48946876053338489126547 - incorrect result
    a^2.2^3.3^1.1    - incorrect result
    a^2^3            - incorrect result
    a^2^3-a^8        - incorrect result
    e^log(7*a)       - incorrect result
    
    opened by ArashPartow 17
  • Benchmarking

    Benchmarking

    What would you think of me adding the possibility to compile the benchmarking tool with support for GNU's libmatheval, and other languages commonly used to solve expressions in C like perl and python? It wouldn't bring the libraries with the program, so there is no problem about size. It would just add the possibility to test them in case the user has them installed.

    opened by EvilPudding 14
  • Using uint64_t instead of double

    Using uint64_t instead of double

    Hi,

    I have been thinking about using your library with uint64_t instead of doubles and only use it for integer expressions (so things like sin(x) will not work, this is fine)

    I wonder if you see any issues with that or that it should work fine to replace all floats with uint64_t?

    opened by emoon 12
  • power

    power

    I have a project where I solve mathematical expressions using OpenCL, but as as a fallback to devices that do not support GPU acceleration, I'm using this library, but there is one problem, the difference in syntax of the power function.

    In OpenCL, which is a subset of C99, there is no '^', so every time I use 'a ^ b' in the expression, I would have to parse it and substitute for 'pow(a, b)', and that could be a complicated expression to parse. And because there is no 'pow' in this lib, I would have to do the reverse if I decided to stick to only using 'pow'. This made me want to either add pow to the builtin functions, or something even better; I would like to suggest adding the capability to define custom functions to the library. The only problem with this would be that right now, builtin functions are constant, so there would be two options:

    1. Change all functions to one dynamic array.
      • Pros: Only needs one binary search.
      • Cons: There would need be an init call to fill the builtin functions, or the first call to any function would have to check if the functions were initialized.
    2. Keep the constant builtin array, and one dynamic array for custom functions.
      • Pros: No need for initialization; Possibility to override builtin functions by searching the custom functions before searching the builtin ones.
      • Cons: Two binary searches would have to be performed.

    The syntax for it would probably look something like this:

    te_func("pow", my_power_function);
    

    Then, when using it in the expression, it would count the number of arguments being used, and assume that the function receives that number of arguments, and cast it to '(double)(*func)(double, double)' The only problem with that approach would be that if the user uses a wrong number of arguments by mistake, the result would be unexpected, and depend on the C implementation.

    An alternative would be to explicitly state the number of arguments:

    te_func("pow", my_power_function, 2);
    

    Making it so it's cast to the right type, and add the possibility to return an error if the user calls it with a wrong number of arguments.

    opened by EvilPudding 12
  • Bytecode interpreter?

    Bytecode interpreter?

    TinyExpr is currently an AST walking interpreter. Interpreting bytecode might be a little faster. If you want competitive speed then JITing is the way to go but then this library will nolonger be tiny, simple or cross-platform. I haven't done any benchmarks with a mock bytecode interpreter but it might be worthwhile. I'd be glad to implement it myself (it sounds like a fun challenge!).

    I'll do some benchmarks and see if this makes sense.

    opened by indianakernick 10
  • Undefined reference to 'fabs' on arduino ide 1.8

    Undefined reference to 'fabs' on arduino ide 1.8

    Hi. Thanks for the wonderful library.

    I am trying to include your library of one of my projects. When compiling, it returns an error.

    #include "tinyexpr.h"
    
    void setup() {
      // put your setup code here, to run once:
      te_interp("5*5", 0);
    }
    
    void loop() {
      // put your main code here, to run repeatedly:
    
    }
    
    /tmp/cce6imJi.ltrans0.ltrans.o:(.rodata+0x2): undefined reference to `fabs'
    collect2: error: ld returned 1 exit status
    exit status 1
    Error compiling for board Arduino/Genuino Uno.
    
    

    I tried to compile simple arduino program using only the math library and it works.

    #include <math.h>
    
    void setup() {
      // put your setup code here, to run once:
      fabs(-3);
    }
    
    void loop() {
      // put your main code here, to run repeatedly:
    
    }
    
    
    
    Sketch uses 444 bytes (1%) of program storage space. Maximum is 32256 bytes.
    Global variables use 9 bytes (0%) of dynamic memory, leaving 2039 bytes for local variables. Maximum is 2048 bytes.
    

    I love to use your library on embedded systems. I hope you can fix the issue. Thanks.

    opened by ricxsar 10
  • Missing CMake build system support

    Missing CMake build system support

    Hi.

    I think it would be nice to have the option to build with CMake. A lot of new projects (including mine) make use of it. Please, see my PR, thanks: #26

    opened by jansvoboda11 9
  • Handle malloc failures

    Handle malloc failures

    This PR adds handling for malloc failures. I don't quite know how one would add sane test cases to cover it, but I did test it manually by replaceing malloc with a malloc implementation which returns NULL 10% of the time, and all of the existing test cases run fine, with no memory errors or memory leaks. The only problem was one place in smoke.c which uses the return value of te_interp without checking for errors.

    The approach to error checking is the most basic one; just add a whole bunch of if (ret == NULL) { ...; return NULL; } everywhere. It's not the prettiest, but it works. The alternative would be to setjmp at the beginning of te_compile, then longjmp out of new_expr. It would probably be faster, but setjmp and longjmp is honestly really scary, with a lot of space for accidentally stepping into undefined behavior (for example, all variables which are modified between the setjmp and the longjmp must be volatile). A longjmp-based approach would probably be faster though.


    The reason behind doing this isn't just that I think malloc failures should be handled in general. I also think this is a possible approach for custom allocators such as allocators which allocate from a pool of static memory. Here's how I imagine a viable path to statically allocated tinyexpr:

    • Once malloc failures are handled gracefully, add TE_MALLOC and TE_FREE macros which default to malloc and free but can be overwritten at compile time.
    • I can then add -DTE_MALLOC=static_te_malloc -DTE_FREE=static_te_free to my tinyexpr.c compile options.
    • I can then define those functions like this in my own code:
    static _Alignas(8) char buffer[1024];
    static size_t allocs = 0;
    static size_t offset = 0;
    
    void *static_te_malloc(size_t count) {
        while (count % 8 != 0) count += 1; // Align
        if (offset + count >= sizeof(buffer)) {
            return NULL; // This will be handled properly by tinyexpr
        }
    
        void *ptr = buffer + offset;
        offset += count;
        allocs += 1;
        return ptr;
    }
    
    void static_te_free(void *ptr) {
        if (ptr == NULL) return;
        allocs -= 1;
        if (allocs == 0) {
            offset = 0; // All memory freed
        }
    }
    

    I personally find this to be a cleaner, simpler and more flexible solution than adding explicit support for using a static memory pool to tinyexpr itself.

    I decided to not include those macros in this PR, because it's kind of orthogonal, and I think handling malloc failures has value regardless of whether or not you agree with my idea for supporting custom allocators.


    Obviously, adding extra ifs everywhere has a performance cost (though only at te_compile time, not te_eval time). Here are times I got from running hyperfine ./bench.orig ./bench.new (where bench.new is the benchmarking suite with this patch):

    Benchmark #1: ./bench.orig
      Time (mean ± σ):     17.170 s ±  0.178 s    [User: 17.164 s, System: 0.004 s]
      Range (min … max):   16.818 s … 17.485 s    10 runs
    
    Benchmark #2: ./bench.new
      Time (mean ± σ):     18.342 s ±  1.088 s    [User: 18.341 s, System: 0.001 s]
      Range (min … max):   17.937 s … 21.431 s    10 runs
    
    Summary
      './bench.orig' ran
        1.07 ± 0.06 times faster than './bench.new'
    

    Here's the output from the unmodified benchmark suite on my machine:

    martin@ubun ~/src/tinyexpr master $ ./bench.orig
    Expression: a+5
    native  5.0045e+11        260ms   384mfps
    interp  5.0045e+11        621ms   161mfps
    138.85% longer
    
    Expression: 5+a+5
    native  5.0095e+11        224ms   446mfps
    interp  5.0095e+11        984ms   101mfps
    339.29% longer
    
    Expression: abs(a+5)
    native  5.0045e+11        222ms   450mfps
    interp  5.0045e+11        798ms   125mfps
    259.46% longer
    
    Expression: sqrt(a^1.5+a^2.5)
    native  4.4443e+12       3323ms    30mfps
    interp  4.4443e+12       4696ms    21mfps
    41.32% longer
    
    Expression: a+(5*2)
    native  5.0095e+11        223ms   448mfps
    interp  5.0095e+11        637ms   156mfps
    185.65% longer
    
    Expression: (a+5)*2
    native  1.0009e+12        225ms   444mfps
    interp  1.0009e+12        981ms   101mfps
    336.00% longer
    
    Expression: (1/(a+1)+2/(a+2)+3/(a+3))
    native  5.2226e+05        297ms   336mfps
    interp  5.2226e+05       3241ms    30mfps
    991.25% longer
    

    And the benchmark suite with this patch:

    martin@ubun ~/src/tinyexpr master $ ./bench.new
    Expression: a+5
    native  5.0045e+11        259ms   386mfps
    interp  5.0045e+11        698ms   143mfps
    169.50% longer
    
    Expression: 5+a+5
    native  5.0095e+11        223ms   448mfps
    interp  5.0095e+11       1169ms    85mfps
    424.22% longer
    
    Expression: abs(a+5)
    native  5.0045e+11        223ms   448mfps
    interp  5.0045e+11        892ms   112mfps
    300.00% longer
    
    Expression: sqrt(a^1.5+a^2.5)
    native  4.4443e+12       3340ms    29mfps
    interp  4.4443e+12       4671ms    21mfps
    39.85% longer
    
    Expression: a+(5*2)
    native  5.0095e+11        224ms   446mfps
    interp  5.0095e+11        758ms   131mfps
    238.39% longer
    
    Expression: (a+5)*2
    native  1.0009e+12        223ms   448mfps
    interp  1.0009e+12       1150ms    86mfps
    415.70% longer
    
    Expression: (1/(a+1)+2/(a+2)+3/(a+3))
    native  5.2226e+05        327ms   305mfps
    interp  5.2226e+05       3834ms    26mfps
    1072.48% longer
    
    opened by mortie 8
  • Invalid conversion compiler warnings

    Invalid conversion compiler warnings

    te_expr ret = malloc(size); 88 31 D:\ ...\Mywork\Cpp\Starter\tinyexpr.c [Error] invalid conversion from 'void' to 'te_expr*' [-fpermissive] case TE_FUNCTION7: case TE_CLOSURE7: te_free(n->parameters[6]); / Falls through. / 102 69 D:\ ...\Mywork\Cpp\Starter\tinyexpr.c [Error] invalid conversion from 'void*' to 'te_expr*' [-fpermissive] ... 183 1 D:\ ...\Mywork\Cpp\Starter\tinyexpr.c [Error] invalid conversion from 'double ()(double)' to 'const void' [-fpermissive]

    Call code:

    int main()
    {
    	std::cout << "Input arithmetic calculation: \n";
    	std::string calc;
    	std::cin >> calc;
        double result = te_interp("calc", 0);
        std::cout << "Result = ";
        std::cout << result << '\n';
        return 0;
    }
    
    opened by kornkaobat 8
  • Add support for combinations and permutations

    Add support for combinations and permutations

    nCr and nPr, while achievable with factorials (see #14), would be nice to have as "helpers".

    double npr(double n, double r) {
      return factorial(n) / factorial(n - r);
    }
    
    double ncr(double n, double r) {
      return factorial(n) / (factorial(r) * factorial(n - r));
    }
    

    As with factorials, some checks are required to ensure that people don't get unexpected results from negative or fractional numbers.

    enhancement 
    opened by silversquirl 8
  • Add vcpkg installation instructions

    Add vcpkg installation instructions

    Tinyexpr is available as a port in vcpkg, a C++ library manager that simplifies installation for tinyexpr and other project dependencies. Documenting the install process here will help users get started by providing a single set of commands to build tinyexpr, ready to be included in their projects.

    We also test whether our library ports build in various configurations (dynamic, static) on various platforms (OSX, Linux, Windows: x86, x64, UWP, ARM) to keep a wide coverage for users.

    I'm a maintainer for vcpkg, and here is what the port script looks like. We try to keep the library maintained as close as possible to the original library.

    opened by MonicaLiu0311 0
  • Use flexible array notation for te_expr

    Use flexible array notation for te_expr

    Newer gcc gets confused when malloc stores a bunch of memory in a one-element array. C99 "flexible array" notation achieves the same effect in a more standard way.

    opened by roblatham00 0
  • array bounds warning with gcc-11

    array bounds warning with gcc-11

    When I build tinyexpr with gcc-11 I get warnings about array subscripts exceeding bounds:

    %   gcc --version
    gcc (Ubuntu 11.2.0-7ubuntu2) 11.2.0
    
    gcc -Wall -Wshadow -O2 -o smoke smoke.c tinyexpr.c -lm
    tinyexpr.c: In function ‘base’:
    tinyexpr.c:321:16: warning: array subscript ‘te_expr[0]’ is partly outside array bounds of ‘unsigned char[16]’ [-Warray-bounds]
      321 |             ret->bound = s->bound;
          |                ^~
    tinyexpr.c:90:20: note: referencing an object of size 16 allocated by ‘malloc’
       90 |     te_expr *ret = malloc(size);
          |                    ^~~~~~~~~~~~
    

    Reproducing is easy: I checked out tinyexpr from github and typed "make"

    opened by roblatham00 0
  • warning: ISO C forbids conversion of function pointer to object pointer type

    warning: ISO C forbids conversion of function pointer to object pointer type

    I think that this warning arises because some CPU architectures have different memory bus sizes for code memory and data memory. Your code uses a single pointer to point to either functions or data with a second flag to indicate what the pointer means. The use of a single pointer type (void *) causes the compiler warning.

    I tried just doing simple type-casting e.g. on line 161:

    {"abs",   (void *)fabs,   TE_FUNCTION1 | TE_FLAG_PURE, 0},
    

    But that didn't work.

    Maybe a union would work? Not sure. Or you'd need to have two pointers in te_variable.

    As far as I know the code still works but it creates lots of warnings.

    Thanks for any help with this.

    opened by stcmeh 0
  • Nan checks for ncr and fac

    Nan checks for ncr and fac

    Added nan check for ncr and fac functions. Now both will return nan, when given nan as parameter.

    This should resolve #62

    Also added .gitignore with the artifacts from make

    opened by juntuu 0
Fast regular expression grep for source code with incremental index updates

Fast regular expression grep for source code with incremental index updates

Arseny Kapoulkine 261 Dec 28, 2022
Simple and lightweight pathname parser for C. This module helps to parse dirname, basename, filename and file extension .

Path Module For C File name and extension parsing functionality are removed because it's difficult to distinguish between a hidden dir (ex: .git) and

Prajwal Chapagain 3 Feb 25, 2022
A tiny programming language that transpiles to C, C++, Java, TypeScript, Python, C#, Swift, Lua and WebAssembly 🚀

A tiny programming language that transpiles to C, C++, Java, TypeScript, Python, C#, Swift, Lua and WebAssembly ??

Lingdong Huang 587 Jan 7, 2023
Very fast Markdown parser and HTML generator implemented in WebAssembly, based on md4c

Very fast Markdown parser and HTML generator implemented in WebAssembly, based on md4c

Rasmus 1.3k Dec 24, 2022
Parser for argv that works similarly to getopt

About Most command-line programs have to parse options, so there are a lot of different solutions to this problem. Some offer many features, while oth

Jørgen Ibsen 157 Dec 22, 2022
Simple .INI file parser in C, good for embedded systems

inih (INI Not Invented Here) inih (INI Not Invented Here) is a simple .INI file parser written in C. It's only a couple of pages of code, and it was d

Ben Hoyt 1.9k Jan 2, 2023
ini file parser

Iniparser 4 I - Overview This modules offers parsing of ini files from the C level. See a complete documentation in HTML format, from this directory o

Nicolas D 845 Jan 1, 2023
Small configuration file parser library for C.

libConfuse Introduction Documentation Examples Build & Install Origin & References Introduction libConfuse is a configuration file parser library writ

null 419 Dec 14, 2022
Universal configuration library parser

LIBUCL Table of Contents generated with DocToc Introduction Basic structure Improvements to the json notation General syntax sugar Automatic arrays cr

Vsevolod Stakhov 1.5k Dec 28, 2022
MiniCalculator with a simple parser.

MiniCalculator with a simple parser. This is a homework-expanded project. To learn something about parser and basic theory of programmi

GZTime 8 Oct 9, 2021
A simple YAML parser which produces a Node Tree Object representation of YAML Documents

A simple YAML parser which produces a Node Tree Object representation of YAML Documents and includes a find method to locate individual Nodes within the parsed Node Tree.

Timothy Rule 2 Sep 18, 2022
A PE parser written as an exercise to study the PE file structure.

Description A PE parser written as an exercise to study the PE file structure. It parses the following parts of PE32 and PE32+ files: DOS Header Rich

Ahmed Hesham 22 Nov 18, 2022
A markdown parser for tree-sitter

tree-sitter-markdown A markdown parser for tree-sitter Progress: Leaf blocks Thematic breaks ATX headings Setext headings Indented code blocks Fenced

Matthias Deiml 227 Jan 7, 2023
A TreeSitter parser for the Neorg File Format

NFF TreeSitter Parser A TreeSitter grammar for Neorg. Available Commands Command Result yarn installs needed dependencies (only do if you don't have t

Neorg 63 Dec 7, 2022
A Template Engine for Modern C++

Inja is a template engine for modern C++, loosely inspired by jinja for python. It has an easy and yet powerful template syntax with all variables, lo

pantor 1.2k Jan 8, 2023
CE-Plugin - 📃 Support Version Cheat Engine 6.5~Higher

?? Support Version Cheat Engine 6.5~Higher ?? Preview ❄️ Reference & Thanks Cheat Engine[Debugger with plugin] Unicorn[CPU emulator framework] Capston

kanren3 1 Jul 25, 2022
A Header-Only Engine that tries to use SFML in a deeper level

⚙️ SFML-Low-Level-Engine ⚙️ A header-only library that tries to use SFML at a deeper level ?? Instalation Download the source code and put the GLD fol

!Gustavo! 4 Aug 27, 2021
libddwaf is Datadog's implementation of a WAF engine

Datadog's WAF libddwaf is Datadog's implementation of a WAF engine, with a goal of low performance and memory overhead, and embeddability in a wide va

Datadog, Inc. 14 Jan 3, 2023
This library support run-time type casting faster than dynamic_cast ( similar to unreal engine's CastTo )

Fast Runtime Type Casting This library give you C++ Fast Runtime Type Casting faster than dynamic_cast ( similar to Unreal Engine's CastTo, IsChildOf

SungJinKang 7 Jun 11, 2022