Advertisement

muparserx - Math Parser Library

A C++ Library for Parsing Expressions with Strings, Complex Numbers, Vectors, Matrices and more.


Defining custom functions

All custom functions must implement the ICallback interface. This interface provides the ICallback::Clone(), ICallback::Eval(...) and ICallback::GetDesc() member functions. An implementation of a user defined class may look like this:

class MySine : public mup::ICallback
{
  MySine()
    :ICallback(cmFUNC, "mysin", 1)
  {}

  virtual void Eval(ptr_val_type &ret, const ptr_val_type *a_pArg, int a_iArgc)
  {
    // Get the argument from the argument input vector
    float_type a = argv[0]->GetFloat();

    // The return value is passed by writing it to the reference ret
    *ret = sin(a);
  }

  const char_type* GetDesc() const
  {
    return "mysin(x) - A custom sine function";
  }

  IToken* Clone() const
  {
    return new MySine(*this);
  }
};

The mup::ICallback constructor takes three argument. The first determines the type of the callback. If you want to add a parser function use cmFUNC. The second is the function name and the last parameter is the number of arguments. The implementation of your callback has to be done in the Eval function. Eval takes three parameters. The first one is a reference to a smartpointer of type IValue holding the return value. The second one is a pointer to an array containing the function arguments. These arguments are also passed via a smartpointer. The last argument is the number of arguments. By using the member functions implemented by the IValue interface you can now easily access the values and perform the actual work. In this sample the first (and only) argument is interpreted as a double value, its sine is calculated and it is then written to the return value.

  float_type a = argv[0]->GetFloat();
  *ret = sin(a);
Please note that the parser leaves the type checking of the function arguments entirely to your implementation of ICallback::Eval. The menanism for implementing callback functions is always the same. Binary operators are treated like functions taking two arguments, unary operators like functions with a single argument. After implementing the logic you have to register it with muParserX by using the DefineFun member function:
ParserX p;
p.DefineFun(new MySine);
One important thing to note is that once submitted to DefineFun the parser takes ownership of the newly created pointer. You may not release it on your own!

Defining custom operators

Defining your own operators is very similar to defining new functions. You have to create a callback object and register it at the parser instance. The parser lets you add unary operators (both infix and prefix) as well as binary operators using the following member functions:

void DefineOprt(IOprtBin *a_pCallback)
void DefinePostfixOprt(IOprtPostfix *a_pCallback)
void DefineInfixOprt(IOprtInfix *a_pCallback)

Defining binary operators

As already mentioned binary operators are defined in a very similar manner as function definitions. The main difference is that you don't have to implement the ICallback interface directly but use it indirectly by implementing the IOprtBin interface which itself is derived from ICallback. IOprtBin is an Extension to ICallback able to handle precedence and associativity parameters needed by binary operators. Consequently you need additional parameters for the construction of binary operators. Namely operator precedence and operator associativity. Operator precedence is a rule used to clarify unambiguously which procedures should be performed first in a given mathematical expression. The precedence value for in muParserX is a simple integer value. The higher the value, the higher the priority of the operation. For instance if you want to compute the following expression properly:

2 + 3 * 4

Then you need to make sure that the addition has a lower precedence than the multiplication. So basically what you are calculating is

(3 * 4) + 2 = 14

In order to do this you can use the predefined precedence values as defined by muParserX in the enumerator EOprtPrecedence. This enumerator does not only contain precedence values for binary operators but also for unary operatory and the ternary if-then-else operator:

enum EOprtPrecedence
{
  prASSIGN       = -1,
  prIF_THEN_ELSE =  0,
  prLOGIC_OR     =  1,
  prLOGIC_AND    =  2,
  prBIT_OR       =  3,
  prBIT_AND      =  4, 
  prRELATIONAL1  =  5, // For "==", "!="
  prRELATIONAL2  =  6, // Relational operators "<", "<=", ">", ">="
  prSHIFT        =  7, // Shift operators "<<", ">>"
  prADD_SUB      =  8, // addition
  prMUL_DIV      =  9, // multiplication/division
  prPOW          = 10, // power operator priority (highest)
  prINFIX        =  9, // Signs have a higher priority than ADD_SUB, but lower than power operator
  prPOSTFIX      =  9  // Postfix operator priority (currently unused)
};
The operator associativity is a parameter that determines how operators of the same precedence are grouped in the absence of parentheses. It can either be right associative or left associative. For instance lets have a look at the following expression:
5^4^3^2
Depending on the operator associativity this expression could either be interpreted as
5^(4^(3^2))
which correctly implements the power operator as a right associative operator or alternatively one could also evaluate:
((5^4)^3)^2 
Which would (incorrectly) ignore the right associativity of the power operator and compute a different result. By putting this information together we can now implement a simple binary operator for the addition of noncomplex scalar values:
class OprtAdd : public IOprtBin    
{
public:
  OprtAdd::OprtAdd() 
    :IOprtBin(_T("+"), (int)prADD_SUB, oaLEFT) 
  {}

  virtual void Eval(ptr_val_type &ret, const ptr_val_type *a_pArg, int)
  {
    const IValue *arg1 = a_pArg[0].Get();
    const IValue *arg2 = a_pArg[1].Get();

    if (!arg1->IsNonComplexScalar())
      throw ParserError( ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg1->GetType(), 'f', 1)); 

    if (!arg2->IsNonComplexScalar())
      throw ParserError( ErrorContext(ecTYPE_CONFLICT_FUN, -1, GetIdent(), arg2->GetType(), 'f', 2)); 
      
    *ret = arg1->GetFloat() + arg2->GetFloat(); 
  }

  virtual const char_type* OprtAdd::GetDesc() const
  { 
    return _T("x+y - Addition for noncomplex values"); 
  }
  
  virtual IToken* OprtAdd::Clone() const
  { 
    return new OprtAdd(*this); 
  }
};

You might also like: