Compiling Mathematical Expressions with muparser and asmjit
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.
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 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
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
![]() |
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! |
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 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
![]() |
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 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
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
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
The parser is extensible with different kinds of operators: prefix operators, infix operators and binary operators.
a! = a*(a-1)...*2*1
). Another application for postfix operators is their use as multipliers
that can be used for implementing units.
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
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:
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
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 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 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");
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:
mecGetErrorMsg()
- returns the error message. mecGetExpr()
- returns the current expression (if an expression is set) mecGetErrorToken()
- returns the token associated with the error (if applicable) mecGetErrorPos()
- returns the current position in the expression (if applicable) mecGetErrorCode()
- returns the error code. 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
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: