Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

iberg's muParser modified to support pointer data types

, 22 Aug 2010
Rate this:
Please Sign up or sign in to vote.
iberg's muParser supports mainly value_type (double); now it's been modified to support arg_type (longest possible data structure).

screenshot.JPG

Introduction

This article is the end result of associating GUI elements with iberg's muParser. The muParser is a fantastic mathematical expression parser - one of the major features of which is the support of callback functions inside a mathematical expression. The only problem I faced was having a pointer data type as an argument of the callback function. The muParser originally supported only the double and char* data types as arguments of callback functions. Later, I changed the implementation of the muParser a little to support the pointer data type.

Background

In one of my projects, I had to develop a filtering mechanism for items of a list view. Since I had the muParser (probably one of the fastest mathematical expression parsers around) on hand, I thought of using it to evaluate an expression and then filter the list items. Only problem was I had to support Equal, Greater Than, Less Than, and most importantly, LIKE type operation in the filter, and since muParser gives the option of callback functions inside the expression, I thought of taking advantage of this feature. But I wanted the implementation to be very generic, so I wanted an Interface Pointer as an argument of the callback functions. Therefore, I changed the value_type data type to arg_type (my own data type), which supported the largest possible value, which in my case was a pointer in a 32 bit machine. The main problem of value_type was it was a double value, and it's basically impossible to hold a pointer inside a double, because a lot of the time, double values are used for calculations, and a lot of the time, the "=" operator is used which changes the meaning of a pointer. To separate a pointer from a double in the middle of this huge code seemed a daunting task. So, I went the other way and used the largest possible type as the common type all through out, and whenever calculations were required, I changed the largest possible type to double to make sure the calculations were not effected.

The Modifications

First of all, a new type was introduced and typedef-ed to arg_type:

#define MUP_BASETYPE long long // Must be the highest sized data type possible 
typedef MUP_BASETYPE arg_type;

(Also, I just renamed value_type to val_type for my conveniences only.)

long long was good enough for me; for 64 bit machines, you have to define the data structure properly. The main idea is MUP_BASETYPE should be the highest sized data type available in the local machine.

Then, two new functions were introduced to convert from one type to the other:

// convert from argument type to value type
inline val_type VAL_TYPE(arg_type at)
{
    val_type vt;
    vt = 0;

    // if sizeof(val_type) (e.g sizeof(vt) = 4) is less
    // than size of argument type (e.g sizeof(at) = 8)
    // then we have to left shift 'at' by required amount (e.g at<<4) 
    memcpy(&vt, &at, sizeof(val_type));
    return vt;
}

// convert from value type to argument type
inline arg_type ARG_TYPE(val_type vt)
{
    arg_type at;
    at = 0;
    memcpy(&at, &vt, sizeof(val_type));

    // if sizeof(val_type) (e.g sizeof(vt) = 4) is less
    // than size of argument type (e.g sizeof(at) = 8)
    // then we have to right shift 'at'
    // by required amount (e.g at>>4) 
    return at;
}

These two functions become handy when you want to convert val_type to arg_type when you want the value to act as a pointer, and then again, arg_type to val_type when calculations are necessary after evaluating the expression.

You can search in the source code uploaded with the LUC tag to find the places were these conversions from one data type to another was made.

Filter Expression Parser

Since I had to implement a dialog such as Custom Autofilter, which you are seeing in the screenshot, I just introduced a new level of hierarchy at the parser level and defined a new class called CMuFilterExprParser which will automatically build an expression string to parse based on some filter statements.

To represent a filter statement (such as "value_in_listview LIKE 'item 0'"), a structure named CMuFilterStmt was defined. This structure is of the following form:

//! CMuFilterStmt structure definition
struct CMuFilterStmt
{
    //! Operator index
    int nOperatorIndex;

    //! String of filter value
    CString strFilterValue;

    CMuFilterStmt()
    {
        nOperatorIndex = -1;
        strFilterValue = _T("");
    }
};

As you see from above, the filter statement is nothing but a structure to contain an operator and a filter value (i.e., LIKE 'item 0' where LIKE is the operation and 'item 0' is the filter value).

And from an array of such filter statements, my muParser derived CMuFilterExprParser builds a complete expression string.

The main idea behind the forming of the final expression string is it will create a string of the following format, given an operator associated operation and a filter value:

Format(_T("(%s(\"%s\", pDataSourceAddr, lParam))"), pFunction, (LPCTSTR)strFilterValue);

pFunction is an operator associated function. An example can be, for the LIKE operator, I will have a "LIKE" function which is a user-defined callback invoked by muParser while parsing the expression.

pDataSourceAddr is a pointer to an interface IMuDataSource which will be passed as an argument of the callback function (invoked while parsing). This interface creates a bridge between the GUI and the parser.

Finally, I have a public member variable m_lParam inside the CMuFilterExprParser class which can be set to any value, and that value would be received in the callback as an argument.

The IMuDataSource interface only has a single pure virtual function to be implemented by the implementor class, which in most cases would be a GUI or GUI related class. Following is the only function of the interface:

virtual HRESULT GetValue(LPARAM lParam, CString& str)

This function will be invoked inside the callback such as the LIKE function, and the callback should pass the lParam value as a hint of what to be returned from the GUI while invoking IMuDataSource::GetValue(..). The value is returned in the form of a string, which is the second argument (the output argument) of the function.

Using the Code

An example should be good enough to clarify the main concept:

CMuFilterDlg dlg(m_arFilterStmts);
if(dlg.DoModal() == IDOK && m_arFilterStmts.GetSize())
{
 CMuFilterExprParser parser;
 parser.SetDataSource(this);  
 
 parser.BuildFilterExpr(m_arFilterStmts);
 mu::val_type dResult = 0.0;
 int nRowCount = m_listData.GetCount();  
 
 BOOL bFilterCriterionMatched = FALSE;
 
 m_listData.SetRedraw(FALSE);
 parser.m_lParam = (mu::arg_type)(nRowCount - 1);  
 for(int i = nRowCount - 1; i >= 0; i--)
 {
  dResult = parser.Eval();
  bFilterCriterionMatched = (dResult > 0.0) ? TRUE : FALSE;
  if(!bFilterCriterionMatched)
  {
   m_listData.DeleteString(i); 
  }
  
  parser.m_lParam--;
 }
 m_listData.SetRedraw(TRUE);
 m_listData.Invalidate();
}

Here, CMuFilterDlg is a dialog which takes the feedback from the user and populates the filter statements in an array called m_arFilterStmts. Now, as we traverse through a list of items in a listbox (in my original project, it was a list control), the value of m_lParam of the parser is set to the item index, and for each item, the filter statement is first evaluated, and if the result is positive, that item stays otherwise gets deleted (in my original scenario, the item was made hidden from the list control).

An example of an operator associated function like LIKE would make the whole process more clear, so you can know how the gap is bridged between the GUI and the parser:

First, the LIKE function is declared as such inside the constructor of CMuFilterExprParser:

DefineFun(_T("LIKE"), LIKE);
mu::arg_type CMuFilterExprParser::LIKE(const TCHAR* pszText, 
             mu::arg_type pDataSource, mu::arg_type lParam)
{
    //The lParam value is the value of parser.m_lParam set earlier
    // The pDataSource was set in the earlier example through parser.SetDataSource(this);
    CString strText = pszText;
    ReplaceChar((TCHAR*)(LPCTSTR)strText, cQuoteReplaceChar, _T('"'));
    IMuDataSource* pDS = (IMuDataSource*)pDataSource;
    mu::val_type lRes = 0;
    if(pDataSource != NULL)
    {
        CString str;
        if(pDS->GetValue(lParam, str) == S_OK)
        {   
            int res = ((WildCmp((LPCTSTR)strText, (LPCTSTR)str)) ? 1 : 0);
            lRes = res;
        }
    }
    return mu::ARG_TYPE(lRes);
}

I guess by now you have guessed how the GetValue was implemented and where. The dialog containing the listbox that implements the interface is as follows:

HRESULT CMuParserTestDlg::GetValue(LPARAM lParam, CString& str)
{
    // m_listData is the listbox inside the dialog having the data
    m_listData.GetText((int)lParam, str);
    return S_OK;
}

And of course, the above mentioned this in parser.SetDataSource(this) is the dialog itself.

So the main idea to use the class CMuFilterExpreParser as a filter expression parser is follows:

  1. Implement the IMuDataSource in the GUI which would act as the source giving data/value.
  2. Set the Data Source in the parser.
  3. Set a proper value to the m_lParam member of the parser before evaluating the filter expression through the Eval() API to get the desired result.

Points of Interest

The one thing I fully learned while doing this project was assigning a long/pointer to a double value (using the = operator) fully changes the representation of the just assigned pointer, inside the double value. This was the main reason for me to change the basic data type to long long from double in my project.

Acknowledgements

Definitely acknowledgements go to iberg for such a wonderful parser which can be extended to do much more than just simple mathematical expression parsing.

History

  • Article uploaded: 19 August, 2010.

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Mukit, Ataul
Chief Technology Officer Rational Technologies
Bangladesh Bangladesh
You don't learn patterns, you just code it.
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 Pinmembermanoj kumar choubey26-Feb-12 20:53 
Generalbroken link PinmemberPaul Heil19-Aug-10 8:54 
GeneralRe: broken link PinmemberMukit, Ataul19-Aug-10 17:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140926.1 | Last Updated 22 Aug 2010
Article Copyright 2010 by Mukit, Ataul
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid