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:
inline val_type VAL_TYPE(arg_type at)
{
val_type vt;
vt = 0;
memcpy(&vt, &at, sizeof(val_type));
return vt;
}
inline arg_type ARG_TYPE(val_type vt)
{
arg_type at;
at = 0;
memcpy(&at, &vt, sizeof(val_type));
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:
struct CMuFilterStmt
{
int nOperatorIndex;
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)
{
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.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:
- 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.
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.