|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionMany applications require the parsing of mathematical expressions. The main objective of this project is to provide a fast and easy way of doing this. muParser is an extensible high performance math parser library. It is based on transforming an expression into a bytecode and pre-calculating constant parts of it. The math parser library is written in pure C++ and should compile on every standard-compliant compiler. I provide you with static libraries and a DLL version ready for implementation into your projects. The code has been tested using MS VC++ V7.1 and GCC V3.3.1. It reportedly works on BCB, too. I provide you with three demo projects in order to help you understand its usage. Additional information about this parser can be found at its project homepage. An online class documentation for the math parser is available, too. Table of contents
FeaturesThe following is a list of the features currently supported by the parser library. The primary objective is to keep it as extensible as possible whilst ensuring a maximum parsing speed. Extending the parser is mostly based on allowing a user to add custom callbacks, which require only an absolute minimum of code. For instance, you need exactly two lines of code to add a new function. But extending the parser may not be necessary at all since it comes with a powerful default implementation. Here is the (incomplete) list of features: Overview
The default implementationThis section gives an overview on the default features supported by the parser. The default implementation is defined in the class Built-in functionsThe following table gives an overview of the functions supported by the default implementation. It lists the function names, the number of arguments and a brief description.
Built-in binary operatorsThe following table lists the default binary operators supported by the parser:
Adding the parser to your projectsThis project comes as a library that allows you multiple choices for inclusion into your projects, depending only on your platform and your programming environment. The choices are: using static libraries, using a DLL or including the source code directly. The following table gives an overview of their applicability:
(X) means that I have not been able to test this. All languages that are able to use DLLs that export plain C functions should be able to use this parser. Using the static libraries is possible only when using an identical version of the STL, otherwise you need to recompile the libraries. (R) means recompiling the static libraries is necessary (Makefile for cygwin supplied with this project). Using the DLLUsing 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 functions as plain C style functions. Due to the wrapper being necessary, there is some overhead associated with using the dynamic library. 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. Using the static librariesWhen linking with the static libraries, you have the choice between different library versions. Using the default libraries is only possible when using MSVC 7.x since they depend on the STL version used in your project. The lib file necessary for your project depends on the threading model and debug mode. The following table gives you an overview of the static libraries:
In order to keep the archive small, I do not provide you with binaries for the libraries. Please create them yourself. Click on the ParserLib project and select the appropriate compilation setup (Release, Debug, Debug (MTD) or Debug (Multithread)). To link the library, select the right one for your application, depending on your project settings. Next, either add lines like the following to your application or drag and drop the library to your project resources. Make sure to use the correct version, otherwise you will get plenty of linker errors originating from different versions of the runtime library used within the same project. #if defined(_DEBUG)
#pragma comment(lib, "../ParserLib/muParserDbg.lib")
#else
#pragma comment(lib, "../ParserLib/muParser.lib")
#endif
The path might be different for your project, depending on where you put the library. Additionally, you have to include the file muParser.h in your source code. Finally, make sure the following header files are present in your project include path:
If you have trouble using the lib files, include the sources directly or use the DLL. Source code inclusionFor some platforms like cygwin/linux, including the source code is the most convenient way of using the parser. Due to the variety of Linux platforms and distributions out there, I can't provide you with a library for your specific platform/distribution. In other cases, it may be a convenient way to avoid linker conflicts originating from different versions of the runtime used by the parser and your project. In order to use the parser, simply make sure all header files listed in the previous section are in the include path. Additionally, make sure to add the following source code files to your project/makefile:
The parser class and all related classes reside in the namespace using namespace mu;
to your files, or reference all classes with their complete name. The parser interfaceThe following section gives an overview of the public parser member functions as well as of the functions exported by the DLL version of the parser. Parser initialization / deinitialization[DLL interface]Create a new instance handle. You can create as many different instance handles as you like. Each will internally reference a different parser object. When using the DLL, it is necessary to manually release any parser handle created by parser_handle hParser;
hParser = mupInit(); // Create a new handle
mupRelease(hParser); // Release an existing parser handle
[Parser class interface]Code for creating a new parser object. (In the case of dynamic allocation, use mu::Parser parser;
Setting the expression[DLL interface]Setting the expression when using the DLL requires a valid parser handle and a pointer to mupSetExpr(hParser, szLine);
See also: Example3/Example3.cpp. [Parser class interface]Setting the expression using the parser class requires a parser.SetExpr(line);
See also: Example1/Example1.cpp; Example2/WndMain.cpp; ParserLib/muParserTest.cpp. Evaluating an expressionExpression evaluation is done by calling the Internally, there are different evaluation functions. One for parsing from a string, and another for parsing from bytecode (and a third one used only if the expression can be simplified to a constant). Initially, [DLL interface]double fVal;
fVal = mupEval(hParser);
See also: Example3/Example3.cpp. [Parser class interface]double fVal;
try
{
fVal = parser.Eval();
}
catch (Parser::exception_type &e)
{
std::cout << e.GetMsg() << endl;
}
See also: Example1/Example1.cpp; Example2/WndMain.cpp. Defining identifier character setsSometimes it is necessary to change the character sets that are used for token identifiers in order to avoid conflicts. The parser uses three different character sets.
When using the default implementation [DLL interface]mupDefineNameChars(hParser,
"0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
mupDefineOprtChars(hParser,
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-*^/?<>=#!$%&|~'_");
mupDefineInfixOprtChars(hParser, "/+-*^?<>=#!$%&|~'_");
[Parser class interface]parser.DefineNameChars("0123456789"
"_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
parser.DefineOprtChars("abcdefghijklmno"
"pqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-*^/?<>=#!$%&|~'_");
parser.DefineInfixOprtChars("/+-*^?<>=#!$%&|~'_");
See also: ParserLib/muParser.cpp; ParserLib/muParserInt.cpp. Defining parser variablesCustom variables can be defined either explicitly in the code by using the Explicitly defining variablesExplicitly 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. [DLL interface]The first parameter is a valid parser handle, the second the variable name, and the third a pointer to the associated C++ variable. double fVal=0;
mupDefineVar(hParser, "a", &fVal);
See also: Example3/Example3.cpp. [Parser class interface]The first parameter is the variable name and the second a pointer to the associated C++ variable. double fVal=0;
parser.DefineVar("a", &fVal);
See also: Example1/Example1.cpp; Example2/WndMain.cpp; ParserLib/muParserTest.cpp. Implicit creation of new variablesImplicit declaration of new variables is only possible by setting a factory function. Implicit creation means that 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: double* (*facfun_type)(const char*)
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. double* AddVariable(const char *a_szName)
{
static double afValBuf[100];
static int iVal = 0;
std::cout << "Generating new variable \""
<< a_szName << "\" (slots left: "
<< 99-iVal << ")" << endl;
afValBuf[iVal++] = 0;
if (iVal>=99)
throw mu::Parser::exception_type("Variable buffer overflow.");
return &afValBuf[iVal];
}
See also: Example1/Example1.cpp. In order to add a variable factory, use the [DLL interface]mupSetVarFactory(hParser, AddVariable);
See also: Example3/Example3.cpp. [Parser class interface]parser.SetVarFactory(AddVariable);
See also: Example1/Example1.cpp. Defining parser constantsParser constants can either be values of type 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. [DLL interface]// Define value constants _pi
mupDefineConst(hParser, "_pi", (double)PARSER_CONST_PI);
// Define a string constant named strBuf
mupDefineStrConst("strBuf", "hello world");
See also: Example3/Example3.cpp. [Parser class interface]// Define value constant <CODE>_pi
parser.DefineConst("_pi", (double)PARSER_CONST_PI);
// Define a string constant named strBuf
parser.DefineStrConst("strBuf", "hello world");
See also: Example1/Example1.cpp; Example2/WndMain.cpp; ParserLib/muParserTest.cpp. Defining parser functionsThe parser allows the user to define a variety of different callback functions. Functions with a fixed number of up to five numeric arguments, functions with a variable number of numeric arguments, and functions taking a single string argument. In order to define a parser function, you need to specify its name, a pointer to a static callback function, and an optional flag indicating if the function is volatile. Volatile functions are functions that can not be optimized. The static callback functions must be either one of the following types: // For fixed number of arguments
double (*fun_type1)(double);
double (*fun_type2)(double, double);
double (*fun_type3)(double, double, double);
double (*fun_type4)(double, double, double, double);
double (*fun_type5)(double, double, double, double, double);
// for a variable number of arguments
// first arg: pointer to the arguments
// second arg: number of arguments
double (*multfun_type)(const double*, int);
// for a single string argument
double (*strfun_type1)(const char *);
[DLL interface]When using the DLL version, it is necessary to call a separate function for each type of callback. The following is a list of possible choices: // Add an unoptimizeable function
mupDefineStrFun(hParser, "StrFun", pStrCallback, false);
// Add an function with a fixed number of arguments
mupDefineFun1(hParser, "fun1", pCallback1, false);
mupDefineFun2(hParser, "fun2", pCallback2, false);
mupDefineFun3(hParser, "fun3", pCallback3, false);
mupDefineFun4(hParser, "fun4", pCallback4, false);
mupDefineFun5(hParser, "fun5", pCallback5, false);
// Define a function with variable number of arguments
mupDefineMultFun(hParser, "MultFun", pMultCallback);
See also: Example3.cpp. [Parser class interface]Defining callback functions by using the parser class directly is easier since there is only a single member function that is used for all kinds of callbacks. Since this member function is defined as a template internally, it automatically associates the right code to any given type of callback (as long as this type is listed above). parser.DefineFun("FunName", pCallback, false)
See also: Example1/Example1.cpp; ParserLib/muParser.cpp; ParserLib/muParserInt.cpp. Defining parser operatorsThe parser is extensible with different kinds of operators: prefix operators, infix operators and binary operators. Operators can be applied to numerical values only (not to string constants).
Unary operatorsBoth postfix and infix operators take callback functions of type double MyCallback(double fVal)
{
return fVal/1000.0;
}
For defining postfix operators and infix operators, you need a valid parser instance, an identifier string, and an optional third parameter marking the operator as volatile (non optimizable). [DLL interface]// Define an infix operator
mupDefineInfixOprt(hParser, "!", MyCallback);
// Define a postfix operators
mupDefinePostfixOprt(hParser, "M", MyCallback);
See also:Example3/Example3.cpp. [Parser class interface]// Define an infix operator
parser.DefineInfixOprt("!", MyCallback);
// Define a postfix operators
parser.DefinePostfixOprt("m", MyCallback);
See also:Example1/Example1.cpp; Example2/WndMain.cpp; muParserTest.cpp. Binary operatorsThis parser has 15 built-in binary operators. Sometimes it might be necessary to add additional custom binary operators. Examples are double pMyAddFun(double v1, double v2)
{
return v1+v2;
}
For the definition of binary operators, you need at least four parameters. The first is a valid parser handle, the second is the identifier of the operator, the third is the operator callback function, the fourth is the operator priority, and the optional fifth parameter is a flag of type [DLL interface]mupDefineOprt(hParser, "add", pMyAddFun, 0);
See also:Example3/Example3.cpp. [Parser class interface]parser.DefineOprt("add", pMyAddFun, 0);
See also:Example1/Example1.cpp; Example2/WndMain.cpp; muParserTest.cpp. The Priority value must be greater or equal than zero (lowest possible priority). It controls the operator precedence in the formula. For instance, if you want to calculate the formula Example A: Priority of 1 + 2 shl 1 => (1 + 2) shl 1
2 shl 1 + 1 => (s shl 1) + 1
Example B: Priority of 1 + 2 shl 1 => 1 + (2 shl 1)
2 shl 1 + 1 => (2 shl 1) + 1
If you encounter such conflicts or simply don't need the built-in operators, these can easily be deactivated using the // disable all built in operators
parser.EnableBuiltInOprt(false);
Querying parser variablesKeeping 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. [DLL interface]For querying the variables used in the expression, exchange // Get the number of variables
int iNumVar = mupGetVarNum(a_hParser);
// Query the variables
for (int i=0; i < iNumVar; ++i)
{
const char *szName = 0;
double *pVar = 0;
mupGetVar(a_hParser, i, &szName, &pVar);
std::cout << "Name: " << szName << " Address: [0x" << pVar << "]\n";
}
See also: Example3/Example3.cpp. [Parser class interface]For querying the expression variables, exchange // Get the map with the variables
mu::Parser::varmap_type variables = parser.GetVar();
cout << "Number: " << (int)variables.size() << "\n";
// Get the number of variables
mu::Parser::varmap_type::const_iterator item = variables.begin();
// Query the variables
for (; item!=variables.end(); ++item)
{
cout << "Name: " << item->first << " Address: [0x" << item->second << "]\n";
}
See also: Example1/Example1.cpp, Example2/WndMain.cpp. Querying parser constantsQuerying parser constants is similar to querying variables and expression variables. [DLL interface]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. int iNumVar = mupGetConstNum(a_hParser);
for (int i=0; i < iNumVar; ++i)
{
const char *szName = 0;
double fVal = 0;
mupGetConst(a_hParser, i, &szName, fVal);
std::cout << " " << szName << " = " << fVal << "\n";
}
See also: Example3/Example3.cpp. [Parser class interface]The parser class provides you with the mu::Parser::valmap_type cmap = parser.GetConst();
if (cmap.size())
{
mu::Parser::valmap_type::const_iterator item = cmap.begin();
for (; item!=cmap.end(); ++item)
cout << " " << item->first << " = " << item->second << "\n";
}
See also: Example1/Example1.cpp, Example2/WndMain.cpp. Setting custom value recognition callbacksThe parser default implementation (muParser.cpp) scans expressions only for floating point values. Custom value recognition callbacks can be used in order to implement support for binary, hexadecimal or octal numbers. These functions are called during the string parsing and allow the user to scan portions of the original expressions for values. Their callback functions must be of the following type: bool (*identfun_type)(const char_type*, int&, value_type&);
If the parser reaches a position during string parsing that could host a value token, it tries to interpret it as such. If that fails, the parser successively calls all internal value recognition callbacks in order to give them a chance to make sense out of what has been found. If all of them fail, the parser continues to check if it is a variable or another kind of token. In order to perform the task of value recognition, these functions take a The next code snippet shows a sample implementation of a function that reads and interprets binary values from the expression string. The code is taken from muParserInt.cpp, the implementation of a parser for integer numbers. Binary numbers must be preceded with a bool ParserInt::IsBinVal(const char_type *a_szExpr,
int &a_iPos, value_type &a_fVal)
{
if (a_szExpr[0]!='#')
return false;
unsigned iVal = 0, iBits = sizeof(iVal)*8;
for (unsigned i=0; (a_szExpr[i+1]=='0' ||
a_szExpr[i+1]=='1') && i<iBits; ++i)
iVal |= (int)(a_szExpr[i+1]=='1') << ((iBits-1)-i);
if (i==0)
return false;
if (i==iBits)
throw exception_type("Binary to integer conversion error (overflow).");
a_fVal = (unsigned)(iVal >> (iBits-i) );
a_iPos += i+1;
return true;
}
Once you have the callback, you must add it to the parser. This can be done with: [DLL interface]mupAddValIdent(hParser, IsBinVal);
See also: Example3/Example3.cpp. [Parser class interface]parser.AddValIdent(IsBinVal);
See also: ParserLib/muParserInt.cpp. Removing variables or constantsRemoving variables and constants can be done all at once using [DLL interface]// Remove all constants
mupClearConst(hParser);
// remove all variables
mupClearVar(hParser);
// remove a single variable by name
mupRemoveVar(hParser, "a");
[Parser class interface]// Remove all constants
parser.ClearConst();
// remove all variables
parser.ClearVar();
// remove a single variable by name
parser.RemoveVar("a");
Error handlingIn the case of an error, both parser class and the parser DLL provide similar methods for querying the information associated with the error. In the parser class, they are member functions of the associated exception class These functions are:
The following table lists the parser error codes. The first column contains the enumeration values as defined in the enumeration
[DLL interface]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 // Callback function for errors
void OnError()
{
cout << "Message: " << mupGetErrorMsg() << "\n";
cout << "Token: " << mupGetErrorToken() << "\n";
cout << "Position: " << mupGetErrorPos() << "\n";
cout << "Errc: " << mupGetErrorCode() << "\n";
}
...
// Set a callback for error handling
mupSetErrorHandler(OnError);
// The next function could raise an error
fVal = mupEval(hParser);
// Test for the error flag
if (!mupError()) cout << fVal << "\n";
See also: Example3/Example3.cpp. [Parser class interface]In the case of an error, the parser class raises an exception of type try
{
...
parser.Eval();
...
}
catch(mu::Parser::exception_type &e)
{
cout << "Message: " << e.GetMsg() << "\n";
cout << "Formula: " << e.GetExpr() << "\n";
cout << "Token: " << e.GetToken() << "\n";
cout << "Position: " << e.GetPos() << "\n";
cout << "Errc: " << e.GetCode() << "\n";
}
See also: Example1/Example1.cpp. Example codeIf you put all this together, you get the source code for a small application. The application defines a parser variable (" #include "muParser.h"
// Function callback
double MyFunction(double a_fVal)
{
return a_fVal*a_fVal;
}
// main program
int main(int argc, char* argv[])
{
using namespace mu;
try
{
double fVal = 1;
Parser p;
p.DefineVar("a", &fVal);
p.DefineFun("MyFunc", MyFunction);
p.SetFormula("MyFunc(a)*pi+min(10,a)");
std::cout << p.Eval() << endl;
}
catch (Parser::exception_type &e)
{
std::cout << e.GetMsg() << endl;
}
return 0;
}
BackgroundFirst step is the parsing of the expression string. During this stage, the parser locates all relevant tokens such as values, variables, functions, and operators. The tokens will be stored in two different stacks. One for values, and one for operators, functions and brackets. During the first parsing run, the bytecode will be generated. Bytecode code can be seen as a recording of stack operations. Once the parsing is done, the function pointer of the main parse routine will be set to the bytecode parsing function. If you change the expression after every call to Once the expression has been transformed into bytecode, token identification and syntax verification is no longer necessary because the formula has already been verified. This section will give you a short introduction of how the bytecode looks like. Following now is the bytecode for the formula Bytecode exampleThe next picture shows an example of the parser bytecode. It will give you an impression of how the internal representation of an expression looks like. The expression is stored in reverse Polish notation, constant parts have been precalculated before, pointers and values are stored inline.
Interpretation of the bytecode
BenchmarksFinally, I'd like to give you some benchmarks. The benchmarking was done on an Intel Pentium P-4 with 2.6 GHz, with a version compiled by using MSVC++ 7.1 (Standard edition). The diagram shows the number of evaluations per second vs. expression length. I compared both the static lib and the DLL version with two other parsers that are freely available on the net, are very fast and have a similar set of features. One of them is a commercial product.
A higher curve means better performance. Expressions were created randomly. They used only LicenseThis library is distributed as freeware. You are free to use it for both non-commercial and commercial use. In order to use it, you have to agree to the license text below. If you use the library, I consider it appropriate to give me credit at some place. This can either be the About dialog of your application or the documentation of your software. If you like this project, I encourage you to support it by:
Copyright (c) 2004, 2005 Ingo BergPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Related linksIf you found this article interesting, the following links might interest you: Programs using this parser
Other math parsers
Release notesRev 1.2: 14/04/2005First of all, the interface has changed so this version is not backwards compatible. After receiving a couple of questions about it, this version features support for user defined binary operators. Consequently, the built-in operators can now be turned off, thus you can deactivate them and write complete customized parser subclasses that only contain the functionality you want. Another new feature is the introduction of callback functions taking string arguments, implicit generation of variables, and the Assignment operator.
Rev 1.3: 09/07/2007The attachments contain two new archives. I renamed them to match the library name. muParser.zip is the new project archive and muParser_dll.zip is the new, pre-compiled library. The demo archive has been renamed to muParser_Demo.zip, but its contents have remained unchanged.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||