Click here to Skip to main content
Click here to Skip to main content

C based Expression Evaluation Library

By , 9 Aug 2004
 

Introduction

ExprEval is a C based expression evaluation library designed to be fast and powerful. It is written in ANSI compliant C to be able to work with any C/C++ compiler.

How it works

ExprEval takes an expression entered as a string and parses it down into an expression tree. This tree, once parsed, can be evaluated over and over without the need to parse it again. Variables can still be changed by the application and/or the expression.

Fast variable access

Variable access is fast within the ExprEval library. At parse time, any variables found in the expression string are added to the variable list if they are not already there. Then the memory address of the variable is stored in the tree. This way, at evaluation time, the variable can be directly accessed without needing to lookup the variable using string comparisons. Functions are also provided to enable the application to do the same thing.

The expression string

The expression string consists of one or more expressions, each terminated with a semicolon. The expressions appear much like they would on paper. An expression can do assignments, basic math operators (+, -, *, /, negate), and even call functions.

Order of operators

The parser parses the expression in a way that the order of operators should work correctly. First, parenthesis are done, then multiplication and division, then addition and then subtraction.

Assignment

Assignments can occur about anywhere in an expression. If an assignment is inside a function parameter, the function solver must evaluate that parameter for the assignment to take place.

x = sin(y = 50);

Functions

The ExprEval library provides many default functions. Functions can take normal parameters and reference parameters, depending on the function solver. Normal parameters are not pre-evaluated by the library. Instead, the function solver needs to evaluate them. This means more work for the function solver, but also more power because the function solver can evaluate and execute the normal parameters in any order and any number of times. Reference parameters are passed to the function solver as addresses to the variable being referenced. Their main purpose is to enable the function solver to 'return' more than one value by changing the value of some other variables. Normal and reference parameters can be mixed together. The only thing that matters is that the normal parameters are in a certain order, and the reference parameters are in a certain order, defined by the function solver.

x = func1(a, b, c);
//Normal parameters
x = func2(&a, &b);
//Reference parameters referencing variables a and b
x = func3(1, 2, &a, 3, &b);
//Normal and reference parameters can be mixed
x = func3(1, &a, &b, 2, 3);
//This is the same as the above
x = func3(2, &b, &a, 3, 1);
//This is not the same

Custom functions

Custom functions can easily be added to the library. The functions are written in C, and then added to the function list.

Constants

The ExprEval library also provides many constants such as M_PI, M_E, etc.

Function, variable and constant lists

Function lists, variable lists, and constant lists are all stored separately from each other so that the developer can choose how to bind them to the expression objects. You can create multiple expressions objects which share the same variables or that have there own private variables or constants or functions or any mix and match.

Sample Usage

The following is a sample of how to use ExprEval:

#include "expreval.h"

...
...

/* Breaker function to break out of long expression functions
   such as the 'for' function */
int breaker(exprObj *o)
    {
    /* Return nonzero to break out */
    return need_to_stop;
    }

/* Custom function using macros */
EXPR_FUNCTIONSOLVER(my_func)
    {
    EXPRTYPE tmp;
    int err; /* Need to declare this variable to use macros */

    EXPR_REQUIREREFCOUNT(1); /* Need 1 reference parameter */
    EXPR_REQUIRECOUNT(1); /* Need 1 normal parameter */

    /* Evaluate the first normal parameter */
    EXPR_EVALNODE(0, tmp);

    /* Set the ref parameter to the value of the normal parameter */
    *refitems[0] = tmp;

    /* Set the return value */
    *val = tmp;

    /* No error occured */
    return EXPR_ERROR_NOERROR;
    }

void DoExpression(char *expr)
    {
    exprObj *e = NULL;
    exprFuncList *f = NULL;
    exprValList *v = NULL;
    exprValList *c = NULL;
    EXPRTYPE *e_x, *e_y; /* EXPRTYPE is typedef for double */
    EXPRTYPE dummy;
    double pos;

    /* Create function list */
    err = exprFuncListCreate(&f);
    if(err != EXPR_ERROR_NOERROR)
        goto error;

    /* Init function list with internal functions */
    err = exprFuncListInit(f);
    if(err != EXPR_ERROR_NOERROR)
        goto error;

    /* Add custom function */
    err = exprFuncListAdd(f, my_func, "myfunc", 1, 1, 1, 1);
    if(err != EXPR_ERROR_NOERROR)
        goto error;

    /* Create constant list */
    err = exprValListCreate(&c);
    if(err != EXPR_ERROR_NOERROR)
        goto error;

    /* Init constant list with internal constants */
    err = exprValListInit(c);
    if(err != EXPR_ERROR_NOERROR)
        goto error;

    /* Create variable list */
    err = exprValListCreate(&v);
    if(err != EXPR_ERROR_NOERROR)
        goto error;

    /* Add variables x and y */
    exprValListAdd(v, "x", 0.0);
    exprValListAdd(v, "y", 0.0);

    exprValListGetAddress(v, "x", &e_x);
    exprValListGetAddress(v, "y", &e_y);

    if(e_x == NULL || e_y = NULL)
        goto error;

    /* Create expression object */
    err = exprCreate(&e, f, v, c, NULL, breaker, 0);
    if(err != EXPR_ERROR_NOERROR)
        goto error;

    /* Parse expression */
    err = exprParse(e, expr);
    if(err != EXPR_ERROR_NOERROR)
        goto error;

    /* Enable soft errors */
    exprSetSoftErrors(e, 1);

    /* The expression should set the variable y based on x */
    for(pos = 0.0; pos < 10.0; pos += 0.1)
        {
        /* Directly access and set variable x */
        *e_x = (EXPRTYPE)pos;

        /* Evaluate the expression */
        exprEval(e, &dummy);

        /* Do something with the results */
        do_something(*e_x, *e_y);        
        }

    goto done;

error:
    /* Alert user of error */
    printf("An error occured\n");

done:
    /* Do cleanup */
    if(e)
        exprFree(e); 

    if(f)
        exprFuncListFree(f);

    if(v)
        exprValListFree(v);

    if(c)
        exprValListFree(c);

    return;
    }

Documentation and examples

The download contains a more complete documentation and reference for the ExprEval library as well as two examples. One is basically a speed test, and the other generates an image based on a math expression.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

bavander
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralAdditional Changesmemberbavander1 Jul '06 - 13:41 
I have made some additional changes to the library, and it is currently at version 2.6 The source can be downloaded on sourceforge.net/projects/expreval
 
The value list has a new function called exprValListAddAddress. This causes a value to be added to the list, but for it to use an external variable instead of the internal one. This can be used to have two different value lists literally share a value.
 

EXPRTYPE global;
 
exprValListAddAddress(vlist1, "global", &global);
exprValListAddAddress(vlist2, "global", &global);

 
This works because the expression tree stores the address directly to the number, so when each expression is parsed, exprValListGetAddress will return the address of 'global' instead of the internal address. Each expression can have a private variable list, but still share some variables.
 
Additionally, a function 'exprValListGetNext' has been added so that items in a value list can be enumerated. With this, you can for example use a value list as a global list, and have seperate value lists for each expression.
 

exprValList *globals;
exprValList *v1;
exprValList *v2;
char *name;
EXPRTYPE *addr;
void *cookie;
 
exprValListCreate(&globals);
exprValListAdd(globals, "g", 0.0);
/* Add additional items, maybe specified by the end user and not known at compile time */
 
exprValListCreate(&v1);
/* Add globals */
cookie = exprValListGetNext(globals, &name, NULL, &addr, NULL);
while(cookie)
{
exprValListAddAddress(v1, name, addr);
cookie = exprValListGetNext(globals, &name, NULL, &addr, cookie);
}

 
This is basically how it works. The value structure in the list has a pointer and a value. Using exprValListAdd sets the pointer to be NULL, and so the internal value is used. Using exprValListAddAddress sets the pointer to the address specified and it is used. For any operation (Set/Get/GetAddress/GetNext), if the internal pointer is NULL, it will use the internal value and return it's address, but if the internal pointer is not NULL, it will use the value pointed to and return that address instead.
 
For variables and constants, the expression tree does not store a pointer to the value structure but instead a pointer directly to the number. When an item is parsed and a name is encounted, exprValListGetAddress is used to find it. If it does not exist it is added using exprValListAdd (so it is an internal value) and the address is obtained again. For external values, exprValListGetAddress will return the added address, and this will be stored in the expression. For internal values, it will return the address of the value in the data structure.
 
In this way, two expressions' variables may be the same if they were added with exprValListAddAddress with the same address each, even if the expressions are using a different variable list. You must make sure that the values are valid as long as they are used by the expression in order to avoid any memory access violations.
GeneralChanges to ExprEvalmemberbavander29 Dec '05 - 18:54 
First off, I've made major changes to the library since I posted this. It is hosted at http://sourceforge.net/projects/expreval/
 

The documentation states that a C++ wrapper existed. It did in the older versions, but as I added more features to the project, I never updated the wrapper and discontinued it.
 
Also, there is a typedef in expreval.h that defines the type EXPRTYPE. This is the type of number used in the library and it defaults to 'double', meaning numbers are internally stored as double and all variables are also stored as double.
 

Reporting of error position in the expression.
 
It is possible to get the parse error position of the input text string using the exprGetErrorPosition function. This function reports the start and end index to the string in the format that it was passed in. This means, if the string is a multi-line expression, the newline characters are counted in the report as well.
 
Fast Variable Access
 
Fast variable access is now used internally and can be used by the application. A function exprValListGetAddress gets the memory address of a variable to directly access it. Internally, this is used instead of storing the name of the variable, which required a lookup with strcmp.
 
Variable Constants
 
Constants are now stored in the compiled expression tree as variables. The parser makes certian that no assignment occurs to a constant and that it is not passes as a reference parameter. By storing it like this, the application CAN change the value of a constant, but the expression can not.
 
Pass by Reference
 
It is now possible to pass variables by reference to a function solver (the function solver must support it though). This is done using the ampersand. The function solver recieves a list of memory addresses the the variables passed, and it can directly access and modify it. Normal and reference parameters can be mixed, but the order in each group is important.
 
my_func(a, b, c, &d, &e, &f)
 
is the same as
 
my_func(a, &d, b, c, &e, &f)
 
This feature is now used by the random number generator to allow multiple random 'streams' to exist independantly. The seed is stored in the variables and is passed to the function used to generate the nubmers.
 
seed1 = 103; seed2 = 207;
result1 = rand(&seed1); result2 = rand(&seed2);
 
These random numbers operate independantly. You can change seed2 without affecting the outcome of the functions using seed1.
 
Custom Functions
 
For those developing custom functions, there are some things to possibly consider.
 
The library is designed so a long evaluation can be easily aborted. This became important with the introduction of the 'for' function, which would allow a user to enter an infinite loop. In order to fix the problem, a breaker function was introduced. This is simply an application defined callback that returns zero or non-zero to abort or not. This breaker function is automatically called every so often by the library, and can be controlled with the exprSetBreakerCount function. This controls how often the exprEvalNode function gets called before it tests the breaker function. That is the function used to evaluate every node in the expression tree to solve the expression.

Basically, a fast function solver has no need to test the breaker function directly (exprGetBreakResult). It depends if a function solver takes a long time. If it takes a long time constantly calling exprEvalNode (or EXPR_EVALNODE macro), it does not need to check the breaker function (exprEvalNode will do it automatically every so often). But if it takes a long time doing some very intensive calculation, then it may need to call the breaker function periodically.
 

 
-- modified at 0:56 Friday 30th December, 2005
GeneralRe: Changes to ExprEvalmemberccheng.g.g3 Nov '08 - 18:02 
Good Codes,can you release some more detail about the "Token" objects?Thanks very much.
GeneralErrormembersteboond14 Dec '05 - 23:21 
where is the file *.hpp?
Questiondatatype of vaiablesmemberwaqqas_jabbar13 Dec '05 - 20:38 
Is there any way to specify the datatype of the variables?

 
waqqas

Generalgood code, got my 5!memberPhil. Invoker13 Nov '05 - 21:45 
I wonder why no one read and vote this article, this is a really useful thing.Smile | :)
Questionhuh? wheres the article?memberTheGlynn10 Aug '04 - 22:40 
Confused | :confused:

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 10 Aug 2004
Article Copyright 2004 by bavander
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid