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
char* data types as arguments of callback functions. Later, I changed the implementation of the muParser a little to support the pointer data type.
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.
First of all, a new type was introduced and
#define MUP_BASETYPE long long // Must be the highest sized data type possible
typedef MUP_BASETYPE arg_type;
(Also, I just renamed
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:
inline val_type VAL_TYPE(arg_type at)
vt = 0;
memcpy(&vt, &at, sizeof(val_type));
inline arg_type ARG_TYPE(val_type vt)
at = 0;
memcpy(&at, &vt, sizeof(val_type));
These two functions become handy when you want to convert
arg_type when you want the value to act as a pointer, and then again,
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:
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
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.
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:
if(dlg.DoModal() == IDOK && m_arFilterStmts.GetSize())
mu::val_type dResult = 0.0;
int nRowCount = m_listData.GetCount();
BOOL bFilterCriterionMatched = 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;
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
mu::arg_type CMuFilterExprParser::LIKE(const TCHAR* pszText,
mu::arg_type pDataSource, mu::arg_type lParam)
CString strText = pszText;
ReplaceChar((TCHAR*)(LPCTSTR)strText, cQuoteReplaceChar, _T('"'));
IMuDataSource* pDS = (IMuDataSource*)pDataSource;
mu::val_type lRes = 0;
if(pDataSource != NULL)
if(pDS->GetValue(lParam, str) == S_OK)
int res = ((WildCmp((LPCTSTR)strText, (LPCTSTR)str)) ? 1 : 0);
lRes = res;
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)
And of course, the above mentioned
parser.SetDataSource(this) is the dialog itself.
So the main idea to use the class
CMuFilterExpreParser as a filter expression parser is follows:
- Implement the
IMuDataSource in the GUI which would act as the source giving data/value.
- Set the Data Source in the parser.
- 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.
Definitely acknowledgements go to iberg for such a wonderful parser which can be extended to do much more than just simple mathematical expression parsing.
- Article uploaded: 19 August, 2010.