|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionMathematical expressions are used by almost all applications. In fact, they are the foundation of programming languages. When you type a program, you are actually writing tons of math formulas. A program is static by nature, that is, it cannot be changed while executing. For example, a program written in C++ must be recompiled each time you change a single line of code. Hence, imagine that one math formula has to be modified in order to accommodate a change in the user environment. Do you like the idea of recompiling all your code just for one little formula change? There lies the usefulness of a math expression evaluator since it allows runtime formula evaluations and modifications without the need to change even one line of code. The trick is to handle math formulas as data instead of program instructions. The evaluator takes formulas as strings, compiles them on the fly, and computes the results that can be used transparently by your application. Mathematic software is a mature field, and there are many excellent libraries available at minimal fees. Providing enhanced math functions and algorithms is thus not the primary mission of this parser. Instead, the focus is on allowing existing code to be reused in the context of run-time formula evaluation. For this purpose, the parser design defines a framework where existing functions can elegantly be integrated. Plug-in is the privileged way of extending the parser and, if you’ve developed one, I invite you to send me your component. There are two parser versions: a native C++ library, and a COM component. The C++ library is highly extensible and fast, but its use is limited to the C++ language only. In counterpart, the COM component is easier to integrate in a project, and many programming languages can use it. COM plug-ins can be used to extend both the C++ library and the COM component. The article is structured as follows:
FeaturesHere are the main features:
A large feature list doesn’t necessary make a good parser. Quality is also very important. So, which qualities should a first-class math parser have? If you want to use it in a real-time application, then performance is a must. And since real-time applications often don’t use Windows, the parser must be portable to other platforms. If this is your case, then don’t lose your time with this parser, since it is not the “fastest parser in west” and nor is it portable. However, this parser has some very interesting qualities! A little performance has been traded for extensibility, code maintainability, ease of use, and robustness. Each of these qualities has a negative impact on performance, but it pays off other ways:
Don’t get me wrong: this parser is not perfect in regard to all the above qualities. But these qualities are what I care for and will continue to steer the parser toward. LicenseFree to use for non-commercial purposes. If you want to use this parser in a commercial product, please contact me with the following information:
In fact, even if you use this library in a personal project, I would really appreciate hearing some words from you! So feel free to share your experience. Design DiscussionThis section presents the overall design and the tradeoffs. Design PatternsThe following design patterns have been used: Façade, Prototype, Abstract Factory, and State. FaçadeDescription:
Consequences:
Collaborators:
PrototypeDescription:
Consequences:
Collaborators:
Abstract FactoryDescription:
Consequences:
Collaborators:
StateDescription:
Consequences:
Collaborators:
The parser designThe first diagram presents the composition of a math expression. The
The compiler responsibilities are:
The separation of the parser in three objects actually reduces the complexity of each object, and will ease the handling of special registrars or compilers. For example, we can naturally imagine a compiler that would do certain kinds of optimizations that the user can choose to use or not. There could have been another object called the evaluator. This object would be responsible to evaluate the expression result using the stack done by the compiler. The problem is that this would introduce one call indirection from the façade to the evaluator. Sadly, this indirection is too costly because the evaluate method is called repetitively, and thus each indirection time is multiplied thousand times.
The following class diagram shows how operators, functions, and variables are abstracted. At the bottom, we see the concrete operator and function classes. New operators and functions can be added simply by implementing the An alternate design could be to merge these two interfaces, but this could lead to bad function and operator implementations since operators are limited to two arguments and the precedence is fixed for functions. So in this case, I prefer to increase the design complexity to avoid coding error, by adding an interface. The
The next class diagram presents the math parser dependencies (the
Finally, the next class diagram presents the class relations during expression evaluations. There is no specific item interface (i.e.,
AlgorithmsThe Parsing AlgorithmBasically, this math parser allows you to use the following items in the definition of your math expressions: number values, constants, variables, operators, and functions. Basic. The math expression is then preprocessed, and a stack is built (note that this is a stack and not a tree) to speed up evaluations. This way, the big processing is done only once at the parsing time. This is a very common and efficient algorithm to parse math expressions, so I will not give a lot of details here. Here are the main ideas. To begin, you have two stacks: one temporary stack that will be used to store operators (namely the operator stack), and another stack that will contain the pre-processed expression that will be used later to do the evaluation (name it the second stack). The parsing is done from left to right. So, if you get a number, you put it immediately on the second stack. You do the same thing for all subsequent numbers. Now, if you get an operator (name it If precedence(curOp) > precedence(lastOp) then
push curOp on the operator stack
Else
While precedence(curOp) <= precedence(lastOp)
pop lastOp
push lastOp on the second stack
push curOp on the operator stack
EndIf
At the end, pop all remaining operators and push them on the second stack. The goal of all this is to put high precedence operators at the bottom of the stack in order that they be evaluated first. Precedence here means the mathematical operator precedence. So, + and - have equal precedence, and * has greater precedence than +… Also, In more technical terms, this algorithm is commonly called the shunting yard algorithm or simply an operator-precedence parsing algorithm. Its purpose is to transform an expression string using the infix notation to the reverse Polish form. The infix notation is what we, humans, commonly use to write mathematical expressions (for example, x+2*2), and the reverse Polish notation is what a computer can better understand because there is no need to worry about operator precedence and brackets. An example of the latter form is presented in the following section. The Evaluation AlgorithmGiven the expression 2^3+4*5, applying the parsing algorithm gives the following stack (column 1):
(Popped items are highlighted) The evaluation algorithm pops the first item and evaluates it. In this case, the + operator is picked (#1). The addition operator needs two arguments, so it pops a first argument that is the * operator (#2). Again, the multiplication operator needs two arguments and pops a first argument that is 5 and a second argument that is 4 (#3). The * operator can now be evaluated, and returns 20 that becomes the first argument for the + operator. Next, the addition operator pops a second argument that is ^ (#4). The ^ operator then pops two arguments, 3 and 2 (#5), and returns 8. Finally, the + operator returns 28, that is the sum of 8 and 20. The pseudocode is: double Evaluate()
item = first item on the stack
if item is a value
return the value
else
return item->Evaluate()
endif
double ItemXYZ::Evaluate(stack)
return fctXYZ(stack->pop(),stack->pop(), …)
double Pop()
if the next item on the stack is a value
return the value
else
return next item->Evaluate()
endif
Again, this is the basic algorithm. Actually, it is used in the parsing step only, and a faster, iterative algorithm is used in the evaluation step. Using the C++ Library and the COM ComponentThis section explains how to accomplish the most common tasks with the MTParser C++ library and the COM component. C++ code will be used to show how to use the C++ library, while, for the sake of diversity, VB6 and C# code will be used for the COM component. Include the Parser in your ProjectBefore you begin evaluating math formulas, you have to integrate the parser with your application. [C++]Select the Unicode or the ANSI String FormatYou have to choose! If you want to follow internationalization guidelines, you should use Unicode. You indicate your choice in the “UnicodeANSIDefs.h” file. What this file does is to define the right string manipulation functions depending on the string format you choose. For example, in the library code, instead of using Other Compilation Flags
These flags are defined, by default, in the MTParserPublic.h file. Compile the LibraryIf you don’t have the .lib files, then you will have to compile the library by yourself. You’ll be able to compile the library with Visual Studio 6, .NET 2003, and .NET 2005. Note: Be sure to compile with the same run-time library version (for example, multithreaded DLL) as in your project. The following list gives a short description of each project:
If you only use the basic features, you only need to compile the MTParserLib project. Visual Studio 6Go to Project Settings|C/C++|Code generation, and then “Use run-time library”. If you compile the library with a different run-time library than in your project, then you will get link errors stating that there are multiple symbol definitions. With Visual Studio 6, you will be able to compile the following projects:
Visual Studio .NET 2003, 2005Go to the project property pages, and then to C/C++|Code Generation, and then “Runtime library”. Usually, you will use Multi-threaded Debug DLL in debug mode, and Multi-threaded DLL in release. With Visual Studio .NET, you will be able to compile all projects except the VB6 example. Include the Proper Header Files in your ProjectThe only header files you’ll need to include are MTParser.h and MTParserLocalizer.h, if you use the localization features. The math parser class is #include "../MTParserLib/MTParser.h" #include "../MTParserLib/MTParserLocalizer.h" Note: I assume that the library is located in a directory beside your project. For example, if your project is in c:\project\MyProject\, then the math parser library is in c:\project\MTParserLib\. Link the Library to Your ProjectThere are four library versions, one for each mix of Debug/Release and ANSI/Unicode. You’ll find the .lib files in the lib directory under MTParserLib. You can add the proper library to your project link settings, or use the following pre-compiler command: #ifdef _DEBUG
#ifdef _UNICODE
#pragma comment(lib, "../MTParserLib/lib/MTParserUd.lib")
#else
#pragma comment(lib, "../MTParserLib/lib/MTParserd.lib")
#endif
#else
#ifdef _UNICODE
#pragma comment(lib, "../MTParserLib/lib/MTParserU.lib")
#else
#pragma comment(lib, "../MTParserLib/lib/MTParser.lib")
#endif
#endif
The above code automatically links with the proper library version depending on your project settings. I urge you to use the release version when benchmarking your project since it is really faster. [VB6]The only thing you have to do is to add the MTParser component in your project references. To do so in Visual Basic 6.0, go to Project|References|Browse and then browse for the MTParserCOM.dll file. The most common problem is to forget to register the COM file in your system. To do so manually, use the regsvr32.exe application located in your Windows\System32 directory. Run “regsvr32 MTParserCOM.dll”. [C#]The only thing you have to do is to add the MTParser component in your project references. To do so in Visual Studio .NET 2003, go to Project|Add Reference|Browse and then browse for the MTParserCOM.dll file. As stated in the VB6 section above, make sure to register the COM file. To use the parser namespace add the following line: using MTPARSERCOMLib;
Create a Parser ObjectYou can create a new object and configure it from scratch, or you can copy an existing object configuration. Initialize the Object Using the Default ConfigurationThis is the basic way of creating a new parser object. The default configuration consists of the default operators and functions, and uses the dot (.) as the decimal point character and the coma (,) as the function argument separator character. [C++]Call the empty constructor: MTParser parser; // default configuration
[VB6]Create a new object as usual: Dim parser As New MTParser
[C#]Create a new object as usual: MTParser parser = new MTParser();
Initialize the Object Using Another Object ConfigurationWhen you want to use multiple parser objects, it is more convenient to configure only one object and then create copies of it than having to configure them all. All the object state is duplicated, which includes: current math formula, variables, constants, custom functions, custom operators, and the syntax. This duplication is essential to ensure that the new object will continue to function properly although the original object is destroyed. [C++]There are two ways to configure a new object using another object configuration. The first way is to use the copy constructor, and the second is to call the assignment operator: MTParser parserTemplate;
// Configure the template object…
parserTemplate.enableAutoVarDefinition(true);
// Using the copy-constructor to configure a new object
MTParser parser2(parserTemplate);
// Using the assignment operator to configure an object
MTParser parser3;
parser3 = parser2;
[VB6]Dim parserTemplate As New MTParser
Dim parser As New MTParser
'Configure the template object…
parserTemplate.autoVarDefinitionEnabled = True
' Using the copy method to re-configure an object
parser.Copy parserTemplate
[C#]MTParser parserTemplate = new MTParser();
MTParser parser = new MTParser();
// Configure the exprTemplate object…
parserTemplate.autoVarDefinitionEnabled = 1;
// Using the copy method to re-configure an object
parser.Copy(exprTemplate);
Evaluate Mathematical ExpressionsThis is the main purpose of this library! If you want to compute the result of a simple expression like “2+2”, or of any expression of arbitrary complexity and length, like “pi*min(x+y+sin(z)/2^3-40.9988*2, avg(y,x*10,3,5))”, then this section shows you how it works. Evaluate an Expression and Get the Result ImmediatelyThis is the easiest and the more direct way of evaluating an expression. [C++]MTParser parser;
MTDOUBLE result = parser.evaluate(_T("2+10*2"));
[VB6]Dim parser As New MTParser
Result = parser.Evaluate("2+10*2")
You handle errors the standard way by using the “ [C#]MTParser parser = new MTParser();
double result = parser.evaluate("2+10*2");
Evaluate an Expression Multiple TimesIf you want to evaluate an expression multiple times, with different variable values, the performance will be increased if you first compile the expression and then evaluate the expression. The following code evaluates 1000 times the same expression: [C++]There are two ways, one using a loop, and a second using a batch evaluate. In C++, both methods give the same performance but the second method is better when you already have a bunch of variable values to process. The reason is that variables' values will automatically be pulled from a vector. The following code evaluates an expression multiple times in a loop: MTDOUBLE x;
MTParser parser;
parser.defineVar(_T("x"), &x);
parser.compile(_T("x+2*sin(x)"));
for( int t=0; t < 1000; t++ )
{
x = t; // new variable value
MTDOUBLE result = parser.evaluate();
}
The following code evaluates an expression multiple times in one parser call: unsigned int nbEvals = 1000;
// Create variable object
MTDoubleVector *pX = new MTDoubleVector(_T("x"));
// Defines variables in the parser. pX is now owned by the parser.
MTParser parser;
parser.defineVar(pX);
// Allocate memory for variable values and results
MTDOUBLE *pXVector = new MTDOUBLE[nbEvals];
MTDOUBLE *pResults = new MTDOUBLE[nbEvals];
// Add your code to fill the value vector…
// Assign variable values
pX->setValues(pXVector, nbEvals);
// Compile the expression only once
parser.compile(_T("x+2*sin(x)"));
// Evaluate the expression for all variable value in one function call
parser.evaluateBatch(nbEvals, pResults);
[VB6]The following code evaluates an expression multiple times in a loop: Dim x As New MTDouble
Dim parser As New MTParser
x.Create "x", 1
parser.DefineVar x
parser.Compile "x+y*sin(z)"
For i = 1 To 1000
x.Value = i ' new variable value
result = parser.EvaluateCompiled()
Next i
The following code evaluates an expression multiple times but only does one function call. Note the special VB6 functions setValueVectorVB6 and evaluateCompiledBatchVB6. Two versions of these methods are needed to accommodate VB6 and .Net. Dim parser As New MTParser
Dim x As New MTDoubleVector
Dim y As New MTDoubleVector
Dim z As New MTDoubleVector
x.Create ("x")
y.Create ("y")
z.Create ("z")
parser.defineVar x
parser.defineVar y
parser.defineVar z
' Compile the expression only once
parser.compile "x+2*sin(x)"
' Generate random variable values...
Dim nbEvals As Long
nbEvals = 800000
Dim xval() As Double
ReDim xval(nbEvals) As Double
' Add your code to fill the value vector…
' Set values...
x.setValueVectorVB6 xval
' this will contain all the results after evaluations
Dim results() As Double
ReDim results(nbEvals) As Double
' Evaluate the expression for all
' variable value in one function call
parser.evaluateCompiledBatchVB6 nbEvals, results
[C#]Like in C++, there are two ways: one using a loop, and a second using a batch evaluate. But unlike in C++, the second method is really faster (as much as 40 times faster!) because COM calls are very slow. The following code evaluates an expression multiple times in a loop: int nbEvals = 1000;
MTDouble x = new MTDouble();
x.create("x", 0.0);
MTParser parser = new MTParser();
parser.defineVar(x as IMTVariable);
parser.compile("x+2*sin(x)");
for( int t=0; t < 1000; t++ )
{
x.value = t; // new variable value
double result = parser.evaluateCompiled();
}
The following code evaluates an expression multiple times but only does one function call: MTDoubleVector x = new MTDoubleVector();
x.create("x");
MTParser parser = new MTParser();
parser.defineVar(x as IMTVariable);
// Allocate memory for variable values and results
double[] xval = new double[nbEvals];
double[] results = new double[nbEvals];
// Add your code to fill the value vector…
// Assign variable values
x.setValueVector(xval);
// Compile the expression only once
parser.compile("x+2*sin(x)");
// Evaluate the expression for all
// variable value in one function call
parser.evaluateCompiledBatch(nbEvals, results);
Manage VariablesA variable is a special symbol that is replaced by a value when evaluated, but unlike a constant, its value can be changed. It can be used to make parameterized formulas like “3t+10”, where t is the time variable. Define a VariableBefore you can use a variable in an expression, unless you use the automatic variable definition feature, you have to define it. Otherwise, the parser will not be able to recognize the variable and an exception will be thrown. [C++]The principle is to link a variable name in the parser to a real variable value in your program. In C++, this link is established through the use of a variable pointer. The following code example defines a variable named “ MTDOUBLE myVar;
MTParser parser;
parser.defineVar(_T("x"), &myVar);
When evaluating an expression containing a variable defined this way, the value is obtained automatically through the variable pointer. This method avoids you to have to set the variable value manually by calling a method like “ myVar = 10;
[VB6]The following code defines a variable named “ Dim x As New MTDouble
Dim parser As New MTParser
x.Create "x", 1
parser.defineVar x
The x.Value = 10 ' set the variable value
val = x.Value ' get the variable value
[C#]The following code defines a variable named “ MTDouble x = new MTDouble();
MTParser parser = new MTParser();
x.create("x", 0);
parser.defineVar(x as IMTVariable);
The x.value = 10 // set the variable value
double val = x.value // get the variable value
Define Only the Used VariablesWhen the number of possible variables is very huge, defining them all up front, just in case only some were used in one expression, is not very optimised. Instead, you can ask the parser which variables are used and were not already defined. This lets you define only the needed variables. By default, the parser throws an exception if it encounters an undefined variable. To change this behaviour, you have to enable the automatic variable definition feature. [C++]The easiest way is letting the parser define the variables using default values, and then you redefine them with the desired variable objects. The following code shows how it works: MTParser parser;
parser.enableAutoVarDefinition(true);
parser.compile(_T("x+y+z"));
double *pVars = new double[parser.getNbUsedVars()];
for( unsigned int t=0; t < parser.getNbUsedVars(); t++ )
{
parser.redefineVar(parser.getUsedVar(t).c_str(), &pVars[t]);
}
First, you enable the automatic variable definition feature by calling the The factory design pattern fits perfectly here. It could be used to obtain variable objects or memory pointers associated with each variable. In fact, this design pattern is already implemented and the next section describes how it works. If you provide a variable factory, it will be called when a variable needs to be defined. To create a variable factory, you have to implement the The following code shows how to enable the automatic variable definition feature and to set a variable factory: class MyVariable : public MTVariableI { public: virtual const MTCHAR* getSymbol(){ return _T("symbol"); } virtual MTDOUBLE evaluate(unsigned int nbArgs, const MTDOUBLE *pArg){ return 1.32213; } virtual MTVariableI* spawn() throw(MTParserException){ return new MyVariable(); } }; class MyVarFactory : public MTVariableFactoryI { public: virtual MTVariableI* create(const MTCHAR *symbol) { return new MyVariable(); } virtual MTVariableFactoryI* spawn(){ return new MyVarFactory (); } }; parser.enableAutoVarDefinition(true, new MyVarFactory()); The “ [VB6]The following code compiles an expression without defining variables beforehand, and then redefines each used variable: Dim parser As New MTParser
parser.autoVarDefinitionEnabled = 1
parser.compile "x+y+z"
Dim vars() As MTDouble
ReDim vars(0 To parser.getNbUsedVars() - 1) As MTDouble
For t = 0 To parser.getNbUsedVars - 1
Dim symbol As String
symbol = parser.getUsedVar(t)
Set vars(t) = New MTDouble
vars(t).Create symbol, 0
parser.redefineVar vars(t)
Next t
[C#]The following code compiles an expression without defining variables beforehand, and then redefines each used variable: MTParser parser = new MTParser();
parser.autoVarDefinitionEnabled = 1;
parser.compile("x+y+z");
MTDouble[] vars = new MTDouble[parser.getNbUsedVars()];
for(int t=0; t<parser.getNbUsedVars(); t++)
{
vars[t] = new MTDouble();
vars[t].create(parser.getUsedVar(t), t);
parser.redefineVar(vars[t] as IMTVariable);
}
Extend the ParserThe parser language can be extended by defining custom constants, functions, and operators. Define a ConstantA constant is a special symbol that is replaced by a value when evaluated. Using constants instead of values helps to make math formulas clearer, especially for well-known values. The parser can also do optimizations since it knows that these values are constants. Examples of constants are pi (3.14159…) and e (2.71…). To define a constant, use the [C++]MTParser parser; parser.defineConst(_T("pi"), 3.14159265359); [VB6]Dim parser As New MTParser
parser.defineConst "pi", 3.14159265359
[C#]MTParser parser = new MTParser();
parser.defineConst("pi", 3.14159265359);
Define a Custom Function or OperatorC++ Library and COM using .Net OnlyOperators and functions have been completely abstracted so that you can add your own at runtime and without having to modify the math parser code. This generalization simplifies the math parser a lot by eliminating all [C++]The following code is all that is needed to define the summation function: class SumFct : public MTFunctionI
{
virtual MTSTRING getSymbol(){return _T("sum"); }
virtual MTSTRING getHelpString(){ return _T("sum(v1,v2,v3,...)"); }
virtual MTSTRING getDescription()
{ return _T("Return the sum of a set of values"); }
virtual int getNbArgs(){ return c_NbArgUndefined; }
virtual MTDOUBLE evaluate(unsigned int nbArgs, const MTDOUBLE *pArg)
{
MTDOUBLE val = 0;
for( unsigned int t=0; t < nbArgs; t++ )
{
val += pArg[t];
}
return val;
}
virtual MTFunctionI* spawn(){ return new SumFct(); }
};
The After creating your custom operator and function classes, you can tell the parser to use them using the following code: try
{
parser.defineOp(new MyOp());
…
parser.defineFunc(new MyFct());
…
}
catch( MTParserException &e ){}
To define an operator, you call the Operator and Function OverloadingThis feature allows you to define multiple functions with the same name but with different number of arguments. For example, the Random function can take zero or two arguments: // Random with zero argument
class RandFct : public MTFunctionI
{
virtual MTSTRING getSymbol(){return _T("rand"); }
virtual MTSTRING getHelpString(){ return _T("rand()"); }
virtual MTSTRING getDescription()
{ return _T("Random value between 0 and 1"); }
virtual int getNbArgs(){ return 0; }
virtual bool isConstant(){ return false; }
virtual MTDOUBLE evaluate(unsigned int nbArgs, const MTDOUBLE *pArg)
{
return rand()/(double)RAND_MAX;
}
virtual MTFunctionI* spawn(){ return new RandFct(); }
};
// Random with two arguments
class RandMinMaxFct : public MTFunctionI
{
virtual MTSTRING getSymbol(){return _T("rand"); }
virtual MTSTRING getHelpString(){ return _T("rand(min, max)"); }
virtual MTSTRING getDescription()
{ return _T("Random value between min and max"); }
virtual int getNbArgs(){ return 2; }
virtual bool isConstant(){ return false; }
virtual MTDOUBLE evaluate(unsigned int nbArgs, const MTDOUBLE *pArg)
{
return pArg[0]+(rand()/(double)RAND_MAX)*(pArg[1]-pArg[0]);
}
virtual MTFunctionI* spawn(){ return new RandMinMaxFct(); }
};
The first Random function returns a value between 0 and 1. The second, more specialized function, allows the user to specify a range for the random value. Another function overloading use is for performance optimization. Examples are the [C#]When you can’t (or don’t have the courage) to develop your function in c++ or in a plug-in, your other option is to implement the IMTFunction COM interface. The performance will be about 30% less in average. But I agree with you, this is really much easier! Following is the code for a sum function taking an undefined number of arguments. As you see, multiple public class MySumFunction : IMTFunction
{
public double evaluate0()
{
throw new Exception("The method or operation is not implemented.");
}
public double evaluate1(double arg)
{
return arg;
}
public double evaluate2(double arg, double arg2)
{
return arg + arg2;
}
public double evaluate3(double arg, double arg2, double arg3)
{
return arg + arg2 + arg3;
}
public double evaluate(Array pArgs)
{
double sum = 0;
for (int t = 0; t < pArgs.Length; t++)
{
sum += (double)pArgs.GetValue(t);
}
return sum;
}
public string getDescription()
{
return "Computes the sum of many values";
}
public string getHelpString()
{
return "slowsum(x,y,z,...)";
}
public int getNbArgs()
{
return -1;
}
public string getSymbol()
{
return "slowsum";
}
}
The following code define your function: MTParser parser = new MTParser(); parser.defineFunc(new MySumFunction()); Define a Macro FunctionA macro function is helpful to simplify the writing of frequently used expressions. For example, if you often use the Euclidean distance defined as To define a macro, use the [C++]MTParser parser;
parser.defineMacro(_T("euc(x,y)"), _T("sqrt(x^2+y^2)") ,
_T("Euclidean distance"));
[VB6]Dim parser As New MTParser
parser.defineMacro "euc(x,y) ", "sqrt(x^2+y^2)" , "Euclidean distance"
[C#]MTParser parser = new MTParser();
parser.defineMacro( "euc(x,y)", "sqrt(x^2+y^2)"), "Euclidean distance");
Load a Plug-inLoad functions, operators, and constants at run-time. The [C++]MTParser parser;
parser.loadPlugin(_T("{4C639DCD-2043-42DC-9132-4B5C730855D6}"));
[VB6]Dim parser As New MTParser
parser.loadPlugin "{4C639DCD-2043-42DC-9132-4B5C730855D6}"
[C#]MTParser parser = new MTParser();
parser.loadPlugin("{4C639DCD-2043-42DC-9132-4B5C730855D6}");
Load All Available Plug-insThe XML info file associated with each plug-in contains the plug-in CLSID. So, the parser can automatically look for info files and discover their CLSID. This is a very flexible way of handling plug-ins since you can add and remove plug-ins without modifying your program. The following code example loads all plug-ins located in the current directory: [C++]MTParser parser;
MTSTRING directory = _T("./");
MTSTRING pluginFileSearchPattern = _T("*.xml");
parser.loadAllPlugins(directory.c_str(), pluginFileSearchPattern.c_str());
[VB6]Dim parser As New MTParser
parser.loadAllPlugins App.Path, "*.xml"
[C#]MTParser parser = new MTParser();
string directory = System.AppDomain.CurrentDomain.BaseDirectory;
parser.loadAllPlugins( directory, "*.xml");
Localize the ParserMany things need to be localized: error messages, item documentation (for example, function descriptions), and expression syntax (for example, decimal point and argument separator characters). Initialize the LocalizerThe localizer is a singleton (unique instance) object helping in multiple tasks of localization. Generally speaking, it contains localized strings for error messages and item documentation. So when you need a localized string, just ask the localizer. Before using the localizer, you have to tell it which locale to use and where to find localized information. Locale strings follow a standard like the ISO two-letter language identifier. For example, English is “en” and French is “fr”. Localized information is stored in XML files (the same files coming with plug-ins). An XML file contains one section for each locale. Next is an example of XML file: <?xml version="1.0" encoding="utf-8" ?>
<LibraryInfo schema="2" type="static" data1="" data2="" version="3">
<Resource LCID="en">
<function id="sin" symbol="sin" args="x" argDescs="" description="Sine" />
<function id="asin" symbol="asin" args="x"
argDescs="" description="Arcsine" />
<function id="sinh" symbol="sinh" args="x"
argDescs="" description="Hyperbolic sine" />
…
<operator id="+" symbol="+" args="x,y"
description="Add two numbers" />
<operator id="minus" symbol="-" args="x,y"
description="Substract two numbers" />
<operator id="unaryMinus" symbol="-" args="x"
description="Unary minus" />
…
<exception id="MTDEFEXCEP_SyntaxArgDecConflict"
description="The argument separator character
and the decimal point character
are the same"/>
<exception id="MTDEFEXCEP_SyntaxArgVarConflict"
description="The argument separator character and one of
the variable name delimiter
characters are the same"/>
<exception id="MTDEFEXCEP_SyntaxDecVarConflict"
description="The decimal point character and one of the
variable name delimiter
characters are the same"/>
…
</Resource>
<Resource LCID="fr">
<function id="sin" symbol="sin" args="x"
argDescs="" description="Sinus" />
<function id="asin" symbol="asin" args="x"
argDescs="" description="Arc sinus" />
<function id="sinh" symbol="sinh" args="x"
argDescs="" description="Hyperbolique sinus" />
…
<operator id="+" symbol="+" args="x,y"
description="Additionne deux nombres" />
<operator id="minus" symbol="-" args="x,y"
description="Soustrait deux nombres" />
<operator id="unaryMinus" symbol="-" args="x"
description="Soustraction unaire" />
…
<exception id="MTDEFEXCEP_SyntaxArgDecConflict"
description="Les caractères de séparation d'arguments
et de point décimal sont les mêmes"/>
<exception id="MTDEFEXCEP_SyntaxArgVarConflict"
description="Les caractères de séparation d'arguments
et de délimiteurs de noms de variables sont les mêmes"/>
<exception id="MTDEFEXCEP_SyntaxDecVarConflict"
description="Les caractères de point décimal et de
délimiteurs de noms de variables sont les mêmes"/>
…
</Resour | |||||||||||||||||||||||||||||