Advertisement

muparserSSE - A Math Expression Compiler

Compiling Mathematical Expressions with muparser and asmjit


The parser interface

The following section gives an overview over the DLL interface exposed by muParserSSE. The interface of this library is very similar to the interface of the original muParser DLL.

In order to use the library you need to add the DLL named muParserSSE32.dll and the header file named "muParserSSE.h" to your project. Using the DLL is the only way to use this parser with MSVC6 or languages other than C++. The DLL has an interface that exports all of its functions as plain C style functions. The following files are required:

Include the header file in your project and add the lib file to the project resources. If you are using the Borland compiler, it may be necessary either to create a new lib from the DLL using implib or to convert the lib file. For more details on using DLLs, consult your compiler manual.

Parser initialization / deinitialization

Before using the parser its necessary to create a new instance handle. You can create as many different instance handles as you like. Internally each handle will reference a different parser object (a different expression). After using the parser you should release any parser handle created by mecInit() with a call to mecRelease(handle).

#include "muParserSSE.h"

// ...

mecParserHandle_t hParser;
hParser = mecCreate();  // Create a new handle

// ...

mecRelease(hParser);    //Release an existing parser handle

Setting the expression

Setting the expression when using the DLL requires a valid parser handle and a pointer to null terminated string containing the expression.

const char szExpr = "sin(3*pi)";
mecSetExpr(hParser, szLine);

See also: example.c

Evaluating an expression

Unlike muparser, muParserSSE can't directly evaluate the expression. You have to compile the expression first. In order to compile the expression use the mecCompile function. It will return a pointer to the evaluation function. In order to evaluate the expression you must call this function.

mecEvalFun_t pFunEval = NULL;

// Compile the expression and get the pointer to the
// just in time compiled eval function
pFunEval = mecCompile(hParser);

// calculate the expression
float fVal = 0;
if (pFunEval!=NULL)
  fVal = pFunEval();

See also: example.c


warning It is crucial to know that the just in time compiled functions remains valid only as long as you do not change the expression or any variables. If you release the parser handle it gets invalid too. Accessing an invalid evaluation function will unevitably lead to a crash in your software! Just don't mess with the parser handle after having compiled the function!

Defining parser variables

Custom variables can be defined either explicitly in the code by using the DefineVar function or implicitly by the parser. Implicit declaration will call a variable factory function provided by the user. The parser is never the owner of its variables. So you must take care of their destruction in case of dynamic allocations. The general idea is to bind every parser variable to a C++ variable. For this reason, you have to make sure the C++ variable stays valid as long as you hav a parser object depending on it. Only variables of type float can be used as parser variables.

Explicitly defining variables

Explicitly in this context means you have to do add the variables manually in your application code. So you must know in advance which variables you intend to use. If this is not the case, have a look at the section on Implicit creation of new variables. In order to define variables use the mecDefineVar function. The first parameter is a valid parser handle, the second the variable name, and the third a pointer to the associated C++ variable.

float fVal=0;
mecDefineVar(hParser, "a", &fVal);

See also: example.c

warning Defining a variable will invalidate any existing compiled function so you need to recompile the function after defining new variables! It's important to understand that you should never use mecDefineVar for changing the value of an existing variable! Change the variable via the pointer submitted as the last parameter of mecDefineVar. The compiled function will access variables directly using their address!

Implicit creation of new variables

Implicit declaration of new variables is only possible by setting a factory function. Implicit creation means every time the parser finds an unknown token at a position where a variable could be located, it creates a new variable with that name automatically. The necessary factory function must be of type:

typedef mecFloat_t* (*mecFacFun_t)(const mecChar_t*, void*);

The following code is an example of a factory function. The example does not use dynamic allocation for the new variables although this would be possible too. But when using dynamic allocation, you must keep track of the variables allocated implicitly in order to free them later on.


// Factory function for creating new parser variables
// This could as well be a function performing database queries.

mecFloat_t* AddVariable(const mecChar_t* a_szName, void *pUserData)
{
  static mecFloat_t afValBuf[PARSER_MAXVARS];  // I don't want dynamic allocation here
  static int iVal = 0;                         // so i used this buffer

  printf("Generating new variable \"%s\" (slots left: %d)\n", a_szName, PARSER_MAXVARS-iVal);

  afValBuf[iVal] = 0;
  if (iVal>=PARSER_MAXVARS-1) 
  {
     printf("Variable buffer overflow.");
     return NULL;
  }

  return &afValBuf[iVal++];
}

See also: example.c

In order to add a variable factory, use the mecSetVarFactory functions. Without a variable factory, each undefined variable will cause an undefined token error. Factory functions can be used to query the values of newly created variables directly from a database.

mecSetVarFactory(hParser, AddVariable);

See also: example.c

Defining parser constants

Like variables constants have to be of type float. Originally muparser was using constants as a way to access their values faster in it's intermediate bytecode. In muparserSSE there is no performance gain from using constants but the function was kept for practical purposes. The names of user defined constants may contain only the following characters: 0-9, a-z, A-Z, _, and they may not start with a number. Violating this rule will raise a parser error.

// Define value constants _pi
mecDefineConst(hParser, "_pi", (float)PARSER_CONST_PI);  

See also: example.c

Defining parser functions

The parser allows using custom callback functions with up to 5 parameters. In order to define a parser callback function, you need to specify its name, a pointer to your static callback function, and an optional flag indicating if the function is volatile. Volatile functions are functions that should not be optimized since they may return different values even when fed with the same input (such as the rnd function). The static callback functions must be either one of the following types:

// function types for calculation
typedef mecFloat_t (*mecFun0_t)(); 
typedef mecFloat_t (*mecFun1_t)(mecFloat_t); 
typedef mecFloat_t (*mecFun2_t)(mecFloat_t, mecFloat_t); 
typedef mecFloat_t (*mecFun3_t)(mecFloat_t, mecFloat_t, mecFloat_t); 
typedef mecFloat_t (*mecFun4_t)(mecFloat_t, mecFloat_t, mecFloat_t, mecFloat_t); 
typedef mecFloat_t (*mecFun5_t)(mecFloat_t, mecFloat_t, mecFloat_t, mecFloat_t, mecFloat_t); 

The callback functions must be bound to the parser by using either one of the following functions:

// Add an function with a fixed number of arguments
mecDefineFun1(hParser, "fun1", pCallback1, false);             
mecDefineFun2(hParser, "fun2", pCallback2, false);             
mecDefineFun3(hParser, "fun3", pCallback3, false);             
mecDefineFun4(hParser, "fun4", pCallback4, false);             
mecDefineFun5(hParser, "fun5", pCallback5, false);             

See also: example.c

Defining parser operators

The parser is extensible with different kinds of operators: prefix operators, infix operators and binary operators.

Unary operators

Both postfix and infix operators take callback functions of type mecFun1_t like the following:

float MyCallback(float fVal) 
{
  return fVal/1000.0; 
}

For defining postfix operators and infix operators, you need a valid parser instance handle, an identifier string, and an optional third parameter marking the operator as volatile (non optimizable). In order to bind your callbacks to the parser use the mecDefineInfixOprt and mecDefinePostfixOprt functions:

// Define an infix operator
mecDefineInfixOprt(hParser, "!", MyCallback);

// Define a postfix operators
mecDefinePostfixOprt(hParser, "M", MyCallback);

See also: example.c

Binary operators

This parser has 13 built-in binary operators. Sometimes it might be necessary to have additional custom binary operators. Examples are shl or shr, the "shift left" and "shift right" operators for integer numbers. In order to add user defined operators, you need to assign a name, a callback function of type mecFun2_t, a priority for each new binary operator. You are not allowed to overload! Let's consider the following callback function which should be assigned to a binary operator:

float MyAddFun(float v1, float v2) 
{
  return v1+v2; 
}

For the definition of binary operators, you need at least six parameters:

  1. A valid parser handle
  2. A string used as the operator identifier
  3. A pointer to a callback function
  4. An integer value determining the operator priority
  5. The operator associativity which can be either one of the following constants:
    • mecOPRT_ASCT_LEFT
    • mecOPRT_ASCT_RIGHT
  6. An integer flag; If this flag is 1 the operator is assumed to be volatile.

Having defined a proper operator callback function, you can add the binary operator with the following code:

mecDefineOprt(hParser, "add", MyAddFun, 0, mecOPRT_ASCT_LEFT, 0);

See also: example.c

The Priority value must be greater or equal than zero (lowest possible priority). It controls the operator precedence in the expression. For instance, if you want to calculate the expression 1+2*3^4 in a mathematically correct sense, you have to make sure that addition has a lower priority than multiplication which in turn has a lower priority than the power operator. When adding custom binary operators the most likely cases are that you assign an operator with either a very low priority of 0 (like and, or, xor) or a high priority that is larger than 6 (the priority of the power operator ^). By assigning priority values already used by built-in operators, you might introduce unwanted side effects. To avoid this and make the order of calculation clear, you must use brackets in these cases. Otherwise, the order will be determined by the expression parsing direction which is from left to right.

Example A: Priority of shl equals priority of an addition; the order of the execution is from left to right.

1 + 2 shl 1 => (1 + 2) shl 1
2 shl 1 + 1 => (s shl 1) + 1

Example B: Priority of shl is higher than the that of addition; shl is executed first.

1 + 2 shl 1 => 1 + (2 shl 1)
2 shl 1 + 1 => (2 shl 1) + 1

Querying parser variables

Keeping track of all variables can be a difficult task. For simplification, the parser allows the user to query the variables defined in the parser. There are two different sets of variables that can be accessed:

Since the usage of the necessary commands is similar, the following example shows querying the parser variables only:

void ListExprVar(mecParserHandle_t a_hParser)
{
  mecInt_t iNumVar = mecGetVarNum(a_hParser),
           i = 0;

  if (iNumVar==0)
  {
    printf("Expression dos not contain variables\n");
    return;
  }

  printf("\nExpression variables:\n");
  printf("---------------------\n");
  printf("Expression: %s\n", mecGetExpr(a_hParser) );
  printf("Number: %d\n", iNumVar);
  
  for (i=0; i<iNumVar; ++i)
  {
    const mecChar_t* szName = 0;
    mecFloat_t* pVar = 0;

    mecGetVar(a_hParser, i, &szName, &pVar);
    printf("Name: %s   Address: [0x%x]\n", szName, (long long)pVar);
  }
}

See also: example.c

For querying the variables used in the expression, exchange mecGetVarNum(...) with mecGetExprVarNum(...) and mecGetVar(...) with mecGetExprVar(...). Due to the use of a temporary internal static buffer for storing the variable name in the DLL version, this DLL-function is not thread safe.

Querying parser constants

Querying parser constants is similar to querying variables and expression variables. Due to the use of a temporary internal static buffer for storing the variable name in the DLL version, this DLL-function is not thread safe. The following sample shows how to query parser constants:

void ListConst(mecParserHandle_t a_hParser)
{
  mecInt_t iNumVar = mecGetConstNum(a_hParser),
          i = 0;

  if (iNumVar==0)
  {
    printf("No constants defined\n");
    return;
  }

  printf("\nParser constants:\n");
  printf("---------------------\n");
  printf("Number: %d", iNumVar);

  for (i=0; i<iNumVar; ++i)
  {
    const mecChar_t* szName = 0;
    mecFloat_t fVal = 0;

    mecGetConst(a_hParser, i, &szName, &fVal);
    printf("  %s = %f\n", szName, fVal);
  }
}

See also: example.c

Removing variables or constants

Removing variables and constants can be done all at once using mecClearVar and mecClearConst. Additionally, variables can be removed by name using mecRemoveVar. Since the parser never owns the variables, you must take care of their release yourself if they were dynamically allocated. If you need to browse all the variables for that purpose, have a look at the chapter explaining how to query parser variables.

// Remove all constants
mecClearConst(hParser);

// remove all variables
mecClearVar(hParser);

// remove a single variable by name
mecRemoveVar(hParser, "a");

Error handling

In order to detect errors you can set an error handler as a callback function. The program will then automaticlly jump into the error handler in case of any problems. Once an error is detected you can use the following functions in order to get detailed information:

The following table lists the parser error codes. The first column contains the constant used for the error, the second column lists the numeric value assigned to this constant and the third column contains the error description.

See also: example.c

Constant Value Description
mecERR_UNEXPECTED_OPERATOR 0 Unexpected binary operator found
mecERR_UNASSIGNABLE_TOKEN 1 Token can't be identified
mecERR_UNEXPECTED_EOF 2 Unexpected end of formula (example: "2+sin(")
mecERR_UNEXPECTED_COMMA 3 An unexpected comma has been found (example: "1,23")
mecERR_UNEXPECTED_ARG 4 An unexpected argument has been found
mecERR_UNEXPECTED_VAL 5 An unexpected value token has been found
mecERR_UNEXPECTED_VAR 6 An unexpected variable token has been found
mecERR_UNEXPECTED_PARENS 7 Unexpected parenthesis, opening or closing
unused 8 - 10 unused
mecERR_MISSING_PARENS 11 Missing parens. (example: "3*sin(3")
mecERR_UNEXPECTED_FUN 12 Unexpected function found (example: "sin(8)cos(9)")
unused 13 unused
mecERR_TOO_MANY_PARAMS 14 Too many function parameters
mecERR_TOO_FEW_PARAMS 15 Too few function parameters (example: "ite(1<2,2)")
unused 16 - 17 unused
mecERR_INVALID_NAME 18 Invalid function, variable or constant name.
mecERR_BUILTIN_OVERLOAD 19 Trying to overload built-in operator
mecERR_INVALID_FUN_PTR 20 Invalid callback function pointer
mecERR_INVALID_VAR_PTR 21 Invalid variable pointer
mecERR_NAME_CONFLICT 22 Name conflict
mecERR_OPT_PRI 23 Invalid operator priority
mecERR_DOMAIN_ERROR 24 Catch division by zero, sqrt(-1), log(0) (currently unused)
mecERR_DIV_BY_ZERO 25 Division by zero (currently unused)
mecERR_GENERIC 26 Generic error
mecERR_INTERNAL_ERROR 27 Internal error of any kind.

Since dynamic libraries with functions exported in C-style can't throw exceptions, the DLL version provides the user with a callback mechanism to raise errors. Simply add a callback function that does the handling of errors. Additionally, you can query the error flag with mupError(). By calling this function, you will automatically reset the error flag!

// Callback function for errors
void OnError(mecParserHandle_t hParser)
{
  printf("\nError:\n");
  printf("------\n");
  printf("Message:  \"%s\"\n", mecGetErrorMsg(hParser));
  printf("Token:    \"%s\"\n", mecGetErrorToken(hParser));
  printf("Position: %d\n", mecGetErrorPos(hParser));
  printf("Errc:     %d\n", mecGetErrorCode(hParser));
}

// ...


// Set a callback for error handling
mecSetErrorHandler(OnError);


// If the next function raises an error the
// error handler is automatically called.
mecCompile(hParser);

// Before continuing you should test the error flag.
if (!mecError(hParser))
  printf("%f\n", fVal);

See also: example.c

Example code

The following snippet shows a the minimal code necessary to set up muparserSSE. The application defines a parser variable named "x" and then calculates the expression "1+sin(x)".

#include "muParserSSE.h"

void OnError(mecParserHandle_t hParser)
{
  printf("\nError:\n");
  printf("------\n");
  printf("Message:  \"%s\"\n", mecGetErrorMsg(hParser));
  printf("Token:    \"%s\"\n", mecGetErrorToken(hParser));
  printf("Position: %d\n", mecGetErrorPos(hParser));
  printf("Errc:     %d\n", mecGetErrorCode(hParser));
}

int main(int argc, char* argv[])
{
  mecParserHandle_t hParser = mecCreate();
  mecEvalFun_t pFunEval = NULL;

  mecSetErrorHandler(hParser, OnError);
  
  // Define parser variables and bind them to C++ variables
  float x = 1;
  mecDefineVar(hParser, "x", &x);

  // Set the expression
  mecSetExpr(hParser, "1+sin(x)");

  // Compile the expression and get the pointer to the
  // just in time compiled eval function
  pFunEval = mecCompile(hParser);
  if (pFunEval==NULL)
    return -1;

  // Finally calculate the expression
  fVal = pFunEval();
  printf("Result: %2.2f\n", fVal);
  return 0;
}

You might also like: