Click here to Skip to main content
15,885,537 members
Articles / Programming Languages / C#

An extensible math expression parser with plug-ins

Rate me:
Please Sign up or sign in to vote.
4.92/5 (147 votes)
13 Mar 2008CPOL51 min read 1.4M   29K   364  
Design and code for an extensible, maintainable, robust, and easy to use math parser.
#include "MTParserTestCases.h"
#include "MTParser.h"
#include <windows.h>
#include "MTParserExcepStrEng.h"
#include "MTTools.h"

#define NUMALGOPLUGINCLSID _T("{1F6C2D74-1023-4E0F-8A6B-DBA19E6585E3}")

#define ASSERTTRUE(expr, desc) {if( !(expr) ){MTSTRING msg = _T("AssertTrue failed: "); msg  += desc; msg+= _T(". Line:");msg+=MTTools::longToS(__LINE__); reportError(msg); }}

void MTParserTestCaseI::reportError(const MTSTRING &desc)
{
	MTSTRING msg = getName();
	msg += _T("-");
	msg += _T("Test failed: ");
	msg += desc;		
	msg += _T("\n\r");
	
	OutputDebugString(msg.c_str());
	m_success = false;

}

void MTParserTestCaseI::loadNumAlgoPlugin(MTParser *pParser)
{
	try
	{
		// load the numerical algorithms plug-in
		pParser->loadPlugin(NUMALGOPLUGINCLSID);
	}
	catch( MTParserException &e )
	{
		// we don't care if the info file is missing...
		if( lstrcmp(e.getException(0)->getData().getID(), MTLOCEXCEP_InfoFileOpenFailed)!=0)
		{
			MTSTRING msg = _T("Unit test definition error: ");
			msg += e.m_description.c_str();
			reportError(msg);
		}
	}
}

//************************************************
// MTMathExprTest

MTSTRING MTParserTestMathExpr::getName()
{
	return _T("Compiler");
}

bool MTParserTestMathExpr::doTests()
{	
	addTest(_T("X"), 0, false);					// Names are case sensitive	
	addTest(_T("x"), 0, true);	
	addTest(_T("y"), 0, true);	
			
	
	addTest(_T("1"), 1, true);					// constant expression
	addTest(_T("1+2"), 3, true);
	addTest(_T("1-2"), -1, true);
	addTest(_T("1--2"), 3, true);	
	addTest(_T("2*3"), 6, true);	
	addTest(_T("4/2"), 2, true);	
	addTest(_T("2^3"), 8, true);	
	addTest(_T("10%3"), 1, true);
	addTest(_T("10.34%20"), 10.34, true);			
	addTest(_T("avg(1,1-1+2,3,(2+2))"), 2.5, true);	
	addTest(_T("sum(1,2,3,2^2)"), 10, true);	
	addTest(_T("rand()*0"), 0, true);			// function with no argument	
	addTest(_T("min(1,2,3)"), 1, true);			// overloaded function	
	addTest(_T("min(-1,2,3,4)"), -1, true);		// overloaded function	
	addTest(_T("max(1,2,3)"), 3, true);			// overloaded function	
	addTest(_T("max(1,2,3,4)"), 4, true);		// overloaded function	
	addTest(_T("ceil(0.1)"), 1, true);			
	addTest(_T("floor(2.671)"), 2, true);
	addTest(_T("1.456==1.456"), 1, true);
	addTest(_T("-1.456==-1.456"), 1, true);
	addTest(_T("-1.456==-1.4567"), 0, true);
	addTest(_T("fact(0)"), 1, true);			
	addTest(_T("fact(4)"), 24, true);	
	addTest(_T("3.45 >= 3.45"), 1, true);			
	addTest(_T("3.5 >= 3.45"), 1, true);			
	addTest(_T("3.45 > 3.45"), 0, true);			
	addTest(_T("3.45 <= 3.45"), 1, true);			
	addTest(_T("3.45 <= 3.5"), 1, true);			
	addTest(_T("3.45 < 3.5"), 1, true);			
	addTest(_T("3.5 < 3.45"), 0, true);				
	addTest(_T("3.5 > 3.45"), 1, true);		
	addTest(_T("if(!(3.45 > 3.45),1,0)"), 1, true);			
	addTest(_T("if(!(3.45 >= 3.45),1,0)"), 0, true);
	addTest(_T("12.45 != 12.451"), 1, true);			
	addTest(_T("12.45 != 12.45"), 0, true);			
	addTest(_T("0 | 0"), 0, true);			
	addTest(_T("0 | 1.34"), 1, true);	
	addTest(_T("0 & 0"), 0, true);			
	addTest(_T("0 & 1"), 0, true);			
	addTest(_T("1 & 1"), 1, true);			
	addTest(_T("10 <= rand(10,20) <= 20 "), 1, true);	
	addTest(_T("sqrt(36)"), 6, true);			
	addTest(_T("x+y+sin(0)/2^3-40.9*2"), -81.8, true);	
	addTest(_T("min(sin(x),2)"), 0, true);
	addTest(_T("max(100.98,101)"), 101, true);	
	addTest(_T("round(2.3)"), 2, true);			
	addTest(_T("round(2.5)"), 3, true);			
	addTest(_T("round(-2.3)"), -2, true);			
	addTest(_T("round(-2.5)"), -3, true);	
	addTest(_T("!0*5"), 5, true);
	
	addTest(_T("min(1,0)"), 0, true);			
	addTest(_T("min(sin(ali),2)"), 0, false);	// undefined variable "ali"
	addTest(_T("foo(sin(x),2)"), 0, false);		// undefined function "foo"	
	addTest(_T("hex(ff)"), 255, true);			// conversion function
	addTest(_T("hex(ff)+2"), 257, true);		// conversion function
	addTest(_T("min(hex(A)*2,2)"), 2, true);	// conversion function
	addTest(_T("1+(hex(ff))"), 256, true);		
	addTest(_T("bin(100)"), 4, true);		
	addTest(_T("bin(102)"), 4, false);			// 2 is an invalid boolean digit
	addTest(_T("hex(fz)"), 255, false);			// 'z': invalid string argument for the conversion function

	// unary minus/add op
	addTest(_T("-1"),-1, true);			
	addTest(_T("--1"), 1, true);			
	addTest(_T("---1"), -1, true);	
	addTest(_T("-(1)"), -1, true);	
	addTest(_T("-(1+2)"), -3, true);	
	addTest(_T("--(1)"), 1, true);	
	addTest(_T("1+-2"), -1, true);	
	addTest(_T("1+-(((1+2)))"), -2, true);		
	addTest(_T("1++2"), 3, true);	
	addTest(_T("++2"), 2, true);	
	addTest(_T("+++2"), 2, true);	
	addTest(_T("(+2)"), 2, true);	
	addTest(_T("-+2"), -2, true);	

	// brackets
	addTest(_T("-(1)+3"), 2, true);	
	addTest(_T("(2+3"), 2, false);				// missing closing bracket
	addTest(_T("(2+3-(3/2)"), 2, false);		// missing closing bracket
	addTest(_T("3/2)"), 2, false);				// missing opening bracket
	addTest(_T("(1)-2"), -1, true);	
	addTest(_T("sin(((1-1)))"), 0, true);		// tricky inside brackets
	addTest(_T("-(((-2)))+3"), 5, true);	
	addTest(_T("avg((2),4)"), 3, true);	
	addTest(_T("max(1+(2,2))"), 3, false);		// missing closing bracket before arg separator	
	addTest(_T("((((100.34))))"), 100.34, true);		// expression inside brackets
	addTest(_T("(1+((2+(1))))"), 4, true);		// expression inside brackets	
	addTest(_T("1+()"), 0, false);				// () syntax is invalid	
	addTest(_T("()"), 0, false);				// () syntax is invalid

	// argument separator
	addTest(_T("avg(2,,2)"), 0, false);			// useless ","
	addTest(_T("avg(2,2,)"), 0, false);			// useless "," at the end	
	addTest(_T("avg(,2,2)"), 0, false);			// useless "," at the beginning	
	addTest(_T("2,2"), 0, false);				// useless "," outside a function
	addTest(_T("(2*2,2)"), 0, false);			// useless "," outside a function
	
	// bad syntax	
	addTest(_T("sin(1,2)"), 0, false);			// too many arguments
	addTest(_T("sin()"), 0, false);				// not enough argument
	addTest(_T("1+2/4+"), 0, false);			// expression cannot end with an operator
	addTest(_T("sin(+))"), 0, false);			// missing + arguments
	addTest(_T("sin(-)"), 0, false);			// missing - arguments
	addTest(_T("(2+3)2"), 0, false);			// the * operator is not implicit
	addTest(_T("5.1.2+3"), 0, false);			// 5.1.2 is not a valid number

	addTest(_T("isNaN(NaN)"), 1, true);		
	addTest(_T("isNaN(0)"), 0, true);		
	addTest(_T("isNaN(1/0)"), 0, true);			// a division by zero doesn't give a NaN result

	addTest(_T("isFinite(1/0)"), 0, true);		
	addTest(_T("isFinite(sqrt(-1))"), 0, true);		
	addTest(_T("isFinite(32)"), 1, true);		


	// funtions that use custom compilers
	addTest(_T("round(abs(solve(x^2,x,36)))"), 6, true);		
	addTest(_T("round(abs(solve(x^2,x,36,0)))"), 6, true);	
	addTest(_T("round(abs(solve(x^2,x,36,0, 0.0001)))"), 6, true);		
	addTest(_T("round(abs(solve(x^2,x,36,0, 0.0001, 10)))"), 6, true);		
	addTest(_T("round(abs(solve(x^2,x,36,0, 0.0001, min(100,10))))"), 6, true);		
	addTest(_T("round(abs(solve(x^2,x,36,0, 1/1000, 10^2)))"), 6, true);			
	addTest(_T("solve(x^2+o,x,36)"), 6, false);					// undefined variable o
	addTest(_T("solve(x^2,x,g+)"), 6, false);					// bad syntax "g+"		
	addTest(_T("solve(x^2,x,36,0,sin())"), 6, false);			// bad syntax "sin()"
	addTest(_T("solve(x^2,x,36,0,0.01, min(x,)"), 6, false);	// missing closing bracket
	addTest(_T("round(derivate(x^2,x,6))"), 12, true);		
	addTest(_T("round(derivate(derivate(x^2,x,p),p,10))"), 2, true);		// second order derivative
	addTest(_T("round(derivate(x^2,x,1)"), 0, false);						// missing closing bracket
	addTest(_T("round(derivate(x^2,NaN,1))"), 0, false);						// pi is a constant!
	addTest(_T("round(derivate(x^2,x,pi+)"), 0, false);						// invalid point
	addTest(_T("round(derivate(x^2,x,)"), 0, false);						// empty argument
	addTest(_T("round(trapezoid(2*x,x,0,6))"), 36, true);		
	addTest(_T("round(trapezoid(2*x,x,6,0))"), 36, true);		
	addTest(_T("round(trapezoid(2*x,x,-3,3, 0.01))"), 0, true);		
	addTest(_T("round(trapezoid(2*x,x,-6,0))"), -36, true);		
	addTest(_T("round(trapezoid(2*x,x,0,6, 0.01))"), 36, true);		
	addTest(_T("round(trapezoid(2*x,x,0,6, 0.001))"), 36, true);		
	addTest(_T("round(trapezoid(2*x,x,0,6, ())"), 36, false);				// missing closing bracket	
	addTest(_T("round(trapezoid(2*x,x,trapezoid(2*x,x,0,5),0, 0.001))"), 625, true);		
	
	// operator precedences
	addTest(_T("-1+2*5"), 9, true);		// (-1)+2*5
	addTest(_T("1+2*5"), 11, true);		// 1+(2*5)	
	addTest(_T("2>1+2"), 0, true);		// 2>(1+2)
	addTest(_T("2<1==1"), 0, true);		// 2<(1==1)
	addTest(_T("2>4 | 4+2>3"), 1, true);		// (2>4) | ((4+2)>3)


	return runTest();
}


void MTParserTestMathExpr::addTest(MTSTRING expr, MTDOUBLE result, bool valid)
{
	TESTCASE tc;
	tc.expr = expr;
	tc.expectedResult = result;
	tc.valid = valid;

	m_testCases.push_back(tc);

}

bool MTParserTestMathExpr::runTest()
{
	MTParser parserBase;
	MTParser parser;
	parser = parserBase;	// this allows to catch error on spawn methods

	int nbTests = m_testCases.size();	
	MTDOUBLE x,y;
	x=0;
	y=0;

	loadNumAlgoPlugin(&parser);	
	
	try
	{
		

		parser.defineVar(_T("x"), &x);		
		parser.defineVar(_T("y"), &y);

		parser.evaluate();		// no expression, but this should not crash the parser
	}
	catch( MTException &e )
	{
		
		MTSTRING msg = _T("Unit test definition error: ");
		msg += e.m_description.c_str();
		reportError(msg);		
		return false;
	}


	int t;
	for( t=0; t<nbTests; t++ )
	{
		try
		{
			MTDOUBLE val = parser.evaluate(m_testCases[t].expr.c_str());

			if(!m_testCases[t].valid )
			{
				MTSTRING msg;
				msg += m_testCases[t].expr;
				msg += _T(", test no.");
				msg += MTTools::longToS(t);
				msg += _T(".  Reason->expected failed");					
				
				reportError(msg);
			}	  

			
			if( val != m_testCases[t].expectedResult )
			{
				MTSTRING msg;
				msg += m_testCases[t].expr;
				msg += _T(", test no.");
				msg += MTTools::longToS(t);				
				msg += _T(".  Reason->bad result");					
				
				reportError(msg);	
				
			}
			

		}
		catch( MTException &e)
		{
			if( m_testCases[t].valid )
			{
				MTSTRING msg;
				msg += m_testCases[t].expr;
				msg += _T(", test no.");
				msg += MTTools::longToS(t);				
				msg += _T(".  Reason->");		
				msg += e.m_description.c_str();
				
				reportError(msg);
			}

		}
	}

	return m_success;
}


//************************************************
// MTParserTestGetUsedVars

MTSTRING MTParserTestGetUsedVars::getName()
{
	return _T("GetUsedVars");
}


bool MTParserTestGetUsedVars::doTests()
{
	MTParser parser;

	MTDOUBLE x,y;	

	try
	{
		// test with explicit variable definition...
		parser.defineVar(_T("x"), &x);
		parser.defineVar(_T("y"), &y);

		parser.evaluate(_T("x+1"));
		
		ASSERTTRUE(parser.getNbUsedVars()==1, _T("only the x variable is defined"))

		if( parser.getNbUsedVars()==1 )
		{
			ASSERTTRUE(lstrcmp(parser.getUsedVar(0).c_str(), _T("x"))==0, _T("the define variable'name should be x"))
		}

		parser.evaluate(_T("x+y"));
		
		ASSERTTRUE(parser.getNbUsedVars()==2, _T("the x and the y variables are defined"))

		if( parser.getNbUsedVars()==2 )
		{
			ASSERTTRUE(lstrcmp(parser.getUsedVar(0).c_str(), _T("x"))==0, _T("the first defined variable'name should be x"))
			ASSERTTRUE(lstrcmp(parser.getUsedVar(1).c_str(), _T("y"))==0, _T("the second defined variable'name should be y"))
		}

		parser.evaluate(_T("1+2"));
		
		ASSERTTRUE(parser.getNbUsedVars()==0, _T("no defined variable"))


		parser.undefineAllVars();


		// test with the auto variable definition feature
		parser.enableAutoVarDefinition(true);	

		parser.evaluate(_T("x+1"));
		
		ASSERTTRUE(parser.getNbUsedVars()==1, _T("only the x variable is defined"))

		if( parser.getNbUsedVars()==1 )
		{
			ASSERTTRUE(lstrcmp(parser.getUsedVar(0).c_str(), _T("x"))==0, _T("the define variable'name should be x"))
		}

		parser.evaluate(_T("x+y"));
		
		ASSERTTRUE(parser.getNbUsedVars()==2, _T("the x and the y variables are defined"))

		if( parser.getNbUsedVars()==2 )
		{
			ASSERTTRUE(lstrcmp(parser.getUsedVar(0).c_str(), _T("x"))==0, _T("the first defined variable'name should be x"))
			ASSERTTRUE(lstrcmp(parser.getUsedVar(1).c_str(), _T("y"))==0, _T("the second defined variable'name should be y"))
		}

		parser.evaluate(_T("1+2"));
		
		ASSERTTRUE(parser.getNbUsedVars()==0, _T("no defined variable"))
	}
	catch(MTException &e)
	{
		MTSTRING msg = _T("Exception: ");
		msg += e.m_description.c_str();
		reportError(msg);
	}

	return m_success;

}

//************************************************
// MTParserTestParserInit

MTSTRING MTParserTestParserInit::getName()
{
	return _T("Parser initialization");
}

bool MTParserTestParserInit::doTests()
{
	MTParser parser1, parser2;

	try
	{

		MTSyntax syntax;
		syntax = parser1.getSyntax();
		syntax.argumentSeparator = ';';
		syntax.decimalPoint = ',';
		parser1.setSyntax(syntax);

		MTDOUBLE x;
		x = 1;

		parser1.defineVar(_T("x"), &x);

		MTDOUBLE c1 = 10;
		parser1.defineConst(_T("c1"), c1);

		parser1.enableAutoVarDefinition(true);

		parser1.evaluate(_T("3+2+x"));

		parser2 = parser1;		

		ASSERTTRUE(parser2.getNbUsedVars()==1, _T("one defined variable: x"))
		
		if( parser2.getNbUsedVars() == 1 )
		{
			ASSERTTRUE(lstrcmp(parser2.getUsedVar(0).c_str(), _T("x"))==0, _T("the define variable'name should be x"))
		}

		ASSERTTRUE(parser2.isAutoVarDefinitionEnabled(), _T("the auto variable definition feature is on"))

		ASSERTTRUE(parser2.getNbDefinedConsts()==2, _T("Two defined const"))
		ASSERTTRUE(parser2.evaluate() == parser1.evaluate(), _T("parser1 and parser2 should evaluate to the same value"))

		
	}
	catch( MTException &e )
	{
		MTSTRING msg = _T("Exception: ");
		msg += e.m_description.c_str();
		reportError(msg);
	}

	return m_success;
}



//************************************************
// MTParserTestVarDef

MTSTRING MTParserTestVarDef::getName()
{
	return _T("Variable definition");
}

bool MTParserTestVarDef::doTests()
{
	MTParser parser;
	MTDOUBLE x;

	// MTDEFEXCEP_VariableNotFound
	try
	{
		parser.getVarBySymbol(_T("x"));		
		ASSERTTRUE(false, _T("variable not defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ItemNotFound)==0, _T("variable not defined"))		
	}

	// MTDEFEXCEP_VarAlreadyDefined
	try
	{
		parser.defineVar(_T("x"), &x);
		parser.defineVar(_T("x"), &x);
		ASSERTTRUE(false, _T("variable already defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_VarAlreadyDefined)==0, _T("variable already defined"))		
	}

	// MTDEFEXCEP_VarNameNull
	try
	{
		parser.defineVar(_T(""), &x);		
		ASSERTTRUE(false, _T("null variable name, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_VarNameNull)==0, _T("null variable name"))		
	}

	// MTDEFEXCEP_VarNameSpace
	try
	{
		parser.defineVar(_T("v a r"), &x);		
		ASSERTTRUE(false, _T("variable name with spaces, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_VarNameSpace)==0, _T("variable name with spaces"))		
	}

	// MTDEFEXCEP_VarNameOnlyNum
	try
	{
		parser.defineVar(_T("123"), &x);		
		ASSERTTRUE(false, _T("number only variable name, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(),MTDEFEXCEP_VarNameOnlyNum)==0, _T("number only variable name"))		
	}

	// MTDEFEXCEP_VarNameConstConflict
	try
	{
		parser.defineConst(_T("c1"), 1);
		parser.defineVar(_T("c1"), &x);		
		ASSERTTRUE(false, _T("var name is the same as a defined constant, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_VarNameConstConflict)==0, _T("the variable name is the same as a defined constant"))		
	}

	// MTDEFEXCEP_VarNameSyntaxConflict
	try
	{
		parser.defineVar(_T("c,"), &x);		
		ASSERTTRUE(false, _T("variable name contains a syntax character, but no exception thrown"))	
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_VarNameSyntaxConflict)==0, _T("the variable name conatains a syntax character"))		
	}

	// may contain the decimal point character...
	try
	{
		parser.defineVar(_T("c."), &x);		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(false, _T("variable name should be able to contain the decimal point character"))	
	}	


	return m_success;
}

//************************************************
// MTParserTestSyntaxDef

MTSTRING MTParserTestSyntaxDef::getName()
{
	return _T("Syntax definition");
}

bool MTParserTestSyntaxDef::doTests()
{	
	MTParser parser;
	MTSyntax syntax;

	
	// MTDEFEXCEP_SyntaxArgDecConflict
	try
	{
		syntax = parser.getSyntax();
		syntax.argumentSeparator = syntax.decimalPoint;
		parser.setSyntax(syntax);
		ASSERTTRUE(false, _T("decimal character=arg separator character, and no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_SyntaxArgDecConflict)==0, _T("decimal character=arg separator character"))		
	}
	
	
	
	

	

	return m_success;

}

//************************************************
// MTParserTestConstDef

MTSTRING MTParserTestConstDef::getName()
{
	return _T("Constant definition");
}

bool MTParserTestConstDef::doTests()
{
	MTParser parser;
	MTDOUBLE x;

	// MTDEFEXCEP_ConstNotFound
	try
	{		
		MTSTRING name;
		parser.getConst(-1, name, x);		
		ASSERTTRUE(false, _T("constant not defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ItemNotFound)==0, _T("constant not defined"))		
	}

	// MTDEFEXCEP_ConstNotFound
	try
	{		
		MTSTRING name;
		parser.getConst(parser.getNbDefinedConsts(), name, x);		
		ASSERTTRUE(false, _T("constant not defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ItemNotFound)==0, _T("constant not defined"))		
	}

	// MTDEFEXCEP_ConstAlreadyDefined
	try
	{
		parser.defineConst(_T("c1"), 1);
		parser.defineConst(_T("c1"), 1);
		ASSERTTRUE(false, _T("constant already defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ConstAlreadyDefined)==0, _T("constant already defined"))		
	}

	// MTDEFEXCEP_ConstNameNull
	try
	{
		parser.defineConst(_T(""), 1);		
		ASSERTTRUE(false, _T("null constant name, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ConstNameNull)==0, _T("null constant name"))		
	}

	// MTDEFEXCEP_ConstNameSpace
	try
	{
		parser.defineConst(_T("c onst"), 1);		
		ASSERTTRUE(false, _T("constant name with spaces, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ConstNameSpace)==0, _T("constant name with spaces"))		
	}


	// MTDEFEXCEP_ConstNameOnlyNum
	try
	{
		parser.defineConst(_T("123"), 1);		
		ASSERTTRUE(false, _T("number only constant name, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ConstNameOnlyNum)==0, _T("number only constant name"))		
	}

	// MTDEFEXCEP_ConstNameSyntaxConflict (decimalPoint)
	try
	{
		MTSyntax syntax = parser.getSyntax();
		MTSTRING cName;
		cName += syntax.decimalPoint;
		cName += _T("c1");

		parser.defineConst(cName.c_str(), 1);		
			
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(false, _T("const name may contain a decimal point character"))		
	}

	// MTDEFEXCEP_ConstNameSyntaxConflict (argSeparator)
	try
	{
		MTSyntax syntax = parser.getSyntax();
		MTSTRING cName;
		cName += syntax.argumentSeparator;
		cName += _T("c1");

		parser.defineConst(cName.c_str(), 1);		
		ASSERTTRUE(false, _T("const name contains a syntax element, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ConstNameSyntaxConflict)==0, _T("const name contains a syntax element"))		
	}

	

	// MTDEFEXCEP_ConstNameOpConflict
	try
	{			
		MTSTRING cName;
		cName += parser.getOp(0)->getSymbol();
		cName += _T("c1");
		parser.defineConst(cName.c_str(), 1);		
		ASSERTTRUE(false, _T("const'name contains an operator symbol, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ConstNameOpConflict)==0, _T("const'name contains an operator symbol"))		
	}

	
	// MTDEFEXCEP_ConstNameVarConflict
	try
	{
		parser.defineVar(_T("c2"), &x);		
		parser.defineConst(_T("c2"), 1);
		
		ASSERTTRUE(false, _T("var name is the same as a defined constant, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ConstNameVarConflict)==0, _T("the variable name is the same as a defined constant"))		
	}

	return m_success;
}

//************************************************
// MTParserTestMacro

MTSTRING MTParserTestMacro::getName()
{
	return _T("Macro");
}

bool MTParserTestMacro::doTests()
{
	MTParser parser;
	double y =11;

	loadNumAlgoPlugin(&parser);	

	try
	{
		parser.defineMacro(	_T("macro(x, y, z)"),
							_T("2*x+y+cos(z)"),
							_T("")	);

		double res;
		res = parser.evaluate(_T("macro(1,0,0)"));
		ASSERTTRUE(res == 3, _T("Bad result"))

		res = parser.evaluate(_T("macro(1,1,0)"));

		ASSERTTRUE(res == 4, _T("Bad result"))

		
		parser.defineVar(_T("y"), &y);
		parser.defineMacro(	_T("macro2(p)"),
							_T("derivate(x^2,x,p)"),
							_T("")	);

		ASSERTTRUE(parser.evaluate(_T("round(macro2(10))")) == 20, _T("Bad result"))
		ASSERTTRUE(parser.evaluate(_T("round(macro2(y))")) == 22, _T("Bad result"))


		
		parser.defineMacro(	_T("macro3(pt)"),
							_T("derivate(macro2(x),x,pt)+macro(1,0,0)"),
							_T("")	);	// second order derivative
							
		ASSERTTRUE(parser.evaluate(_T("round(macro3(10))")) == 5, _T("Bad result"))

		parser.defineMacro(	_T("macro4(pt,y)"),
							_T("derivate(x^2+y^2,x,pt)+y"),
							_T("")	);	// second order derivative
							
		ASSERTTRUE(parser.evaluate(_T("round(macro4(y,1))")) == 23, _T("Bad result"))
		
	}
	catch( MTException &e)
	{
		reportError(_T("Unable to define the macro: ")+MTSTRING(e.m_description.c_str()));
	}

	// Void prototype
	try
	{
		parser.defineMacro(_T(""), _T(""), _T(""));
		ASSERTTRUE(false, _T("Expected fail"))
	}
	catch( MTException )
	{
		// ok!
	}

	// Void prototype
	try
	{
		parser.defineMacro(_T("()"), _T(""), _T(""));
		ASSERTTRUE(false, _T("Expected fail"))
	}
	catch( MTException )
	{
		// ok!
	}

	// Missing closing bracket
	try
	{
		parser.defineMacro(_T("macro4("), _T(""), _T(""));
		ASSERTTRUE(false, _T("Expected fail"))
	}
	catch( MTException )
	{
		// ok!
	}

	// useless argument separator
	try
	{
		parser.defineMacro(_T("macro4(x,,y)"), _T(""), _T(""));
		ASSERTTRUE(false, _T("Expected fail"))
	}
	catch( MTException )
	{
		// ok!
	}

	// useless argument separator
	try
	{
		parser.defineMacro(_T("macro4(x,)"), _T(""), _T(""));
		ASSERTTRUE(false, _T("Expected fail"))
	}
	catch( MTException )
	{
		// ok!
	}

	// Macro without argument
	try
	{
		parser.defineMacro(_T("macro4()"), _T("1+2"), _T(""));
		
	}
	catch( MTException )
	{
		ASSERTTRUE(false, _T("Unable to define a macro without argument!"))
	}

	// Undefined argument
	try
	{
		parser.defineMacro(_T("macro5(x)"), _T("x+y"), _T(""));	
		ASSERTTRUE(false, _T("Expected fail: undefined argument"))
	}
	catch( MTException )
	{
		// ok!
	}

	// Undefined argument with auto var on... should change nothing
	try
	{
		parser.enableAutoVarDefinition(true);
		parser.defineMacro(_T("macro5(x)"), _T("x+y"), _T(""));	
		ASSERTTRUE(false, _T("Expected fail: undefined argument"))
	}
	catch( MTException )
	{
		// ok!
	}

	return m_success;

}

//************************************************
// MTParserTestInternational

MTSTRING MTParserTestInternational::getName()
{
	return _T("International");
}

bool MTParserTestInternational::doTests()
{
	MTParser parser;

	loadNumAlgoPlugin(&parser);	

	try
	{
		// totally weird syntax...to avoid conflict with defined items
		MTSyntax syntax;
		syntax.argumentSeparator = '@';		
		syntax.decimalPoint = '?';

		parser.setSyntax(syntax);
		double x;
		parser.defineVar(_T("x"), &x);
		x = 1000;

		ASSERTTRUE(parser.evaluate(_T("round(1?2+2?4)"))==4, _T("Bad result"))
		ASSERTTRUE(parser.evaluate(_T("min(1?2+2?4 @ 100?9882 @ 3)"))==3, _T("Bad result"))		
		ASSERTTRUE(parser.evaluate(_T("round(abs(solve(x^2@x@36?2)))"))==6, _T("Bad result"))
		ASSERTTRUE(parser.evaluate(_T("(round(x+3?6))"))==1004, _T("Bad result"))
		
		try
		{
			parser.evaluate(_T("(3.23)"));	
			ASSERTTRUE(false, _T("the '.' character is not valid"))
		}
		catch( MTException ){};

		try
		{
			parser.evaluate(_T("(3 @ 23)"));	
			ASSERTTRUE(false, _T("Invalid use of an argument separator outside a function"))
		}
		catch( MTException ){};
	}
	catch( MTException &e)
	{		
		reportError(e.m_description.c_str());		
	}

	return m_success;

}

//************************************************
// MTParserTestConstExpr

MTSTRING MTParserTestConstExpr::getName()
{
	return _T("Constant Expressions");
}

bool MTParserTestConstExpr::doTests()
{
	MTParser parser;

	loadNumAlgoPlugin(&parser);	

	try
	{
		double x = 10;
		parser.defineVar(_T("x"), &x);

		parser.compile(_T("1+2+sin(10)"));
		ASSERTTRUE(parser.isConstant(), _T("Should be constant"))

		parser.compile(_T("x"));
		ASSERTTRUE(!parser.isConstant(), _T("Should not be constant"))

		parser.compile(_T("1+x"));
		ASSERTTRUE(!parser.isConstant(), _T("Should not be constant"))

		parser.compile(_T("solve(x^2,x,36)"));
		ASSERTTRUE(parser.isConstant(), _T("Should be constant"))

		parser.compile(_T("solve(x^2,x,x)"));
		ASSERTTRUE(!parser.isConstant(), _T("Should not be constant"))

		parser.compile(_T("derivate(x^2,x,36)"));
		ASSERTTRUE(parser.isConstant(), _T("Should be constant"))

		parser.compile(_T("derivate(x^2,x,x)"));
		ASSERTTRUE(!parser.isConstant(), _T("Should not be constant"))

		parser.compile(_T("trapezoid(x^2,x,36,0)"));
		ASSERTTRUE(parser.isConstant(), _T("Should be constant"))

		parser.compile(_T("trapezoid(x^2,x,x,0)"));
		ASSERTTRUE(!parser.isConstant(), _T("Should not be constant"))

		parser.compile(_T("rand()"));
		ASSERTTRUE(!parser.isConstant(), _T("Should not be constant"))

	}
	catch( MTException &e)
	{		
		reportError(e.m_description.c_str());		
	}

	return m_success;

}

//************************************************
// MTParserTestFunctionDef

MTSTRING MTParserTestFunctionDef::getName()
{
	return _T("Function definitions");
}

bool MTParserTestFunctionDef::doTests()
{
	MTParser parser;

	// MTDEFEXCEP_FuncNotFound
	try
	{			
		parser.getFunc(-1);		
		ASSERTTRUE(false, _T("function not defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ItemNotFound)==0, _T("function not defined"))		
	}

	// MTDEFEXCEP_FuncNotFound
	try
	{		
		MTSTRING name;
		parser.getFunc(parser.getNbDefinedFuncs());		
		ASSERTTRUE(false, _T("function not defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ItemNotFound)==0, _T("function not defined"))		
	}

	// MTDEFEXCEP_FuncAlreadyDefined
	try
	{
		MyFct *pFct = new MyFct();
		pFct->m_symbol = _T("sin");
		pFct->m_nbArgs = 1;
		parser.defineFunc(pFct);
		ASSERTTRUE(false, _T("function already defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_FuncAlreadyDefined)==0, _T("function already defined"))		
	}

	// MTDEFEXCEP_FuncNameNull
	try
	{
		MyFct *pFct = new MyFct();
		pFct->m_symbol = _T("");
		parser.defineFunc(pFct);		
		ASSERTTRUE(false, _T("null function name, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_FuncNameNull)==0, _T("null function name"))		
	}

	// MTDEFEXCEP_FuncNameSpace
	try
	{
		MyFct *pFct = new MyFct();
		pFct->m_symbol = _T("my fct");
		parser.defineFunc(pFct);		
		ASSERTTRUE(false, _T("function name with spaces, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_FuncNameSpace)==0, _T("function name with spaces"))		
	}

	// MTDEFEXCEP_FuncNameSyntaxConflict 
	try
	{		
		MyFct *pFct = new MyFct();
		pFct->m_symbol = _T("sin(");
		parser.defineFunc(pFct);	
		ASSERTTRUE(false, _T("function name with syntax element, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_FuncNameSyntaxConflict)==0, _T("function name contains a syntax element"))		
	}
	
	try
	{		
		MyFct *pFct = new MyFct();
		pFct->m_symbol = _T("sin.");
		parser.defineFunc(pFct);					
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(false, _T("function name may contain decimal point character"))		
	}	

	// MTDEFEXCEP_FuncNameOpConflict 
	try
	{		
		MyFct *pFct = new MyFct();
		pFct->m_symbol = _T("sin+");
		parser.defineFunc(pFct);	
		ASSERTTRUE(false, _T("function name with operator, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_FuncNameOpConflict)==0, _T("function name contains an operator name"))		
	}	

	// Function overloading
	try
	{		
		MyFct *pFct = new MyFct();
		pFct->m_symbol = _T("myFct1");
		pFct->m_nbArgs = 1;
		
		MyFct *pFct2 = new MyFct();
		pFct2->m_symbol = _T("myFct2");
		pFct2->m_nbArgs = 2;

		parser.defineFunc(pFct);	
		parser.defineFunc(pFct2);	

		ASSERTTRUE(parser.evaluate(_T("myFct1(1)+myFct2(1,1)")) == 2, _T("function overloading doesn't work"))		

	}
	catch(MTException &e)
	{
		ASSERTTRUE(false, _T("function overloading doesn't work: "+MTSTRING(e.m_description.c_str())))		
	}	


	return m_success;
}

//************************************************
// MTParserTestOperatorDef

MTSTRING MTParserTestOperatorDef::getName()
{
	return _T("Operator definitions");
}

bool MTParserTestOperatorDef::doTests()
{
	MTParser parser;

	// MTDEFEXCEP_OpNotFound
	try
	{			
		parser.getOp(-1);		
		ASSERTTRUE(false, _T("op not defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ItemNotFound)==0, _T("op not defined"))		
	}

	// MTDEFEXCEP_FuncNotFound
	try
	{		
		MTSTRING name;
		parser.getOp(parser.getNbDefinedOps());		
		ASSERTTRUE(false, _T("op not defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ItemNotFound)==0, _T("op not defined"))		
	}

	// MTDEFEXCEP_OpAlreadyDefined
	try
	{
		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("+");
		pOp->m_isUnary = false;
		pOp->m_prec = e_MTOpPrec_ADD;
		parser.defineOp(pOp);
		ASSERTTRUE(false, _T("operator already defined, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{		
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_OpAlreadyDefined)==0, _T("operator already defined"))		
	}

	// MTDEFEXCEP_OpNameNull
	try
	{
		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("");
		pOp->m_isUnary = false;
		pOp->m_prec = e_MTOpPrec_ADD;
		parser.defineOp(pOp);		
		ASSERTTRUE(false, _T("null op name, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_OpNameNull)==0, _T("null op name"))		
	}

	// MTDEFEXCEP_OpNameSpace
	try
	{
		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("op 1");
		pOp->m_isUnary = false;
		pOp->m_prec = e_MTOpPrec_ADD;
		parser.defineOp(pOp);		
		ASSERTTRUE(false, _T("op name with spaces, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_OpNameSpace)==0, _T("op name with spaces"))		
	}

	// operator name may contain decimal point character...
	try
	{		
		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("op.");
		pOp->m_isUnary = false;
		pOp->m_prec = e_MTOpPrec_ADD;
		parser.defineOp(pOp);				
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(false, _T("op name may contain decimal point character"))			
	}	

	// MTDEFEXCEP_OpNameSyntaxConflict 
	try
	{		
		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("op,");
		pOp->m_isUnary = false;
		pOp->m_prec = e_MTOpPrec_ADD;
		parser.defineOp(pOp);		
		ASSERTTRUE(false, _T("op name with syntax element, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_OpNameSyntaxConflict)==0, _T("op name contains a syntax element"))		
	}		

	// MTDEFEXCEP_OpNameSyntaxConflict 
	try
	{		
		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("op(");
		pOp->m_isUnary = false;
		pOp->m_prec = e_MTOpPrec_ADD;
		parser.defineOp(pOp);		
		ASSERTTRUE(false, _T("op name with syntax element, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_OpNameSyntaxConflict)==0, _T("op name contains a syntax element"))		
	}	

	// MTDEFEXCEP_OpPrecedence (>e_MTOpPrec_FCT)
	try
	{		
		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("op(");
		pOp->m_isUnary = false;
		pOp->m_prec = e_MTOpPrec_FCT;
		parser.defineOp(pOp);		
		ASSERTTRUE(false, _T("bad op precedence, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_OpPrecedence)==0, _T("bad op precedence"))		
	}	

	// MTDEFEXCEP_OpPrecedence < 0
	try
	{		
		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("op(");
		pOp->m_isUnary = false;
		pOp->m_prec = -1;
		parser.defineOp(pOp);		
		ASSERTTRUE(false, _T("bad op precedence, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_OpPrecedence)==0, _T("bad op precedence"))		
	}	

	// MTDEFEXCEP_FuncNameOpConflict 
	try
	{		
		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("sin");
		pOp->m_isUnary = false;
		pOp->m_prec = e_MTOpPrec_ADD;
		parser.defineOp(pOp);	
		ASSERTTRUE(false, _T("function name with operator, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_FuncNameOpConflict)==0, _T("function name contains an operator name"))		
	}		

	// MTDEFEXCEP_ConstNameOpConflict 
	try
	{		
		parser.defineConst(_T("myConst"), 10);

		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("myConst");
		pOp->m_isUnary = false;
		pOp->m_prec = e_MTOpPrec_ADD;
		parser.defineOp(pOp);	
		ASSERTTRUE(false, _T("constant name with operator, but no exception thrown"))		
	}
	catch(MTParserException &e)
	{
		ASSERTTRUE(lstrcmp(e.getException(0)->getData().getID(), MTDEFEXCEP_ConstNameOpConflict)==0, _T("constant name contains an operator name"))		
	}	

	// Operator overloading
	try
	{		
		MyOp *pOp = new MyOp();
		pOp->m_symbol = _T("myOp1");
		pOp->m_isUnary = false;
		pOp->m_prec = e_MTOpPrec_ADD;

		MyOp *pOp2 = new MyOp();
		pOp2->m_symbol = _T("myOp1");
		pOp2->m_isUnary = true;
		pOp2->m_prec = e_MTOpPrec_UNARY;
		
		parser.defineOp(pOp);	
		parser.defineOp(pOp2);	

		ASSERTTRUE(parser.evaluate(_T("1 myOp1 2")) == -1, _T("operator overloading doesn't work"))		
		ASSERTTRUE(parser.evaluate(_T("myOp12+3")) == 1, _T("unary op doesn't work"))		

	}
	catch(MTException &e)
	{
		ASSERTTRUE(false, _T("operator overloading doesn't work: "+MTSTRING(e.m_description.c_str())))		
	}	


	return m_success;
}

//************************************************
// MTParserTestAutoVarDef

MTSTRING MTParserTestAutoVarDef::getName()
{
	return _T("Auto Variable definition");
}

bool MTParserTestAutoVarDef::doTests()
{
	MTParser parser;

	// Validate that when the auto-var feature is off, variables are not
	// automatically defined and that an error is thrown
	try
	{		
		parser.enableAutoVarDefinition(false);
		parser.compile(_T("[myVar]"));
		ASSERTTRUE(false, _T("undefined variable, but no exception thrown"))		
	}
	catch(MTException)
	{		
	}


	// When the auto-var feature is on, undefined variables should be
	// automatically defined
	try
	{		
		parser.enableAutoVarDefinition(true);
		parser.compile(_T("myVar"));
		MTVariableI *pVar = parser.getVarBySymbol(_T("myVar"));		
	}
	catch(MTException)
	{	
		ASSERTTRUE(false, _T("the auto-var feature doesn't work!"))		
	}	

	return m_success;
}

//************************************************
// MTParserTestVoidExpr

MTSTRING MTParserTestVoidExpr::getName()
{
	return _T("Void expression");
}

bool MTParserTestVoidExpr::doTests()
{
	MTParser parser;
	
	// totally void expression should return NaN
	parser.compile(_T(""));
	ASSERTTRUE(parser.isNaN(), _T("void expression"));
	
	return m_success;
}

//************************************************
// MTParserTestUndefineVar

MTSTRING MTParserTestUndefineVar::getName()
{
	return _T("UndefineVar");
}

bool MTParserTestUndefineVar::doTests()
{
	MTParser parser;
	
	MTDOUBLE x,y,z;

	try
	{
		parser.defineVar(_T("x"), &x);
		parser.defineVar(_T("y"), &y);
		parser.defineVar(_T("z"), &z);
	}
	catch( MTException )
	{
		ASSERTTRUE(false, _T("Unable to define variables"))		
	}

	// undefine an undefined variable
	try
	{
		parser.undefineVar(_T("i"));		
		ASSERTTRUE(false, _T("Undefine an undefined variable but no exception"))		
	}
	catch( MTException )
	{
		// ok
	}

	// undefine a variable
	try
	{
		parser.undefineVar(_T("y"));		
		parser.getVarBySymbol(_T("x"));
		parser.getVarBySymbol(_T("z"));
	}
	catch( MTException )
	{
		ASSERTTRUE(false, _T("Error undefining y"))		
	}

	// undefine the same variable again...
	try
	{
		parser.undefineVar(_T("y"));				
		ASSERTTRUE(false, _T("Undefine an undefined variable but no exception"))		
	}
	catch( MTException )
	{
		// ok
	}

	// undefine another variable
	try
	{		
		parser.undefineVar(_T("x"));				
		parser.getVarBySymbol(_T("z"));
	}
	catch( MTException )
	{
		ASSERTTRUE(false, _T("Error undefining x"))		
	}

	// undefine all variables
	try
	{
		parser.undefineAllVars();	
		parser.defineVar(_T("x"), &x);
		parser.defineVar(_T("y"), &y);
		parser.defineVar(_T("z"), &z);
	}
	catch( MTException )
	{
		ASSERTTRUE(false, _T("Error undefining all variables"))		
	}	

	return m_success;
}

//************************************************
// MTParserTestUndefineConstant

MTSTRING MTParserTestUndefineConstant::getName()
{
	return _T("UndefineConstant");
}

bool MTParserTestUndefineConstant::doTests()
{
	MTParser parser;

	
	try
	{
		unsigned int initialCount = parser.getNbDefinedConsts();
		parser.defineConst(_T("myConst"), 1.0);
		parser.defineConst(_T("myConst2"), 2.0);
		parser.undefineConst(_T("myConst"));

		ASSERTTRUE(parser.getNbDefinedConsts() == initialCount+1, _T("bad constant count..."))
		ASSERTTRUE(parser.evaluate(_T("myConst2")) == 2.0, _T("bad result"))
	}
	catch( MTException e)
	{
		ASSERTTRUE(false, e.m_description.c_str())		
	}

	return m_success;

}


//************************************************
// MTParserTestUndefineFunc

MTSTRING MTParserTestUndefineFunc::getName()
{
	return _T("UndefineFunc");
}

bool MTParserTestUndefineFunc::doTests()
{
	MTParser parser;

	
	try
	{
		unsigned int initialCount = parser.getNbDefinedFuncs();	

		parser.undefineFuncsBySymbol(_T("max"));

		ASSERTTRUE(parser.getNbDefinedFuncs() == initialCount-3, _T("bad function count..."))		

		ASSERTTRUE(parser.evaluate(_T("min(3,2)")) == 2, _T("bad function result"))		

		ASSERTTRUE(parser.evaluate(_T("sqrt(9)")) == 3, _T("bad function result"))		
	}
	catch( MTException e)
	{
		ASSERTTRUE(false, e.m_description.c_str())		
	}

	return m_success;

}


//************************************************
// MTParserTestUndefineOp

MTSTRING MTParserTestUndefineOp::getName()
{
	return _T("UndefineOp");
}

bool MTParserTestUndefineOp::doTests()
{
	MTParser parser;
	
	try
	{
		unsigned int initialCount = parser.getNbDefinedOps();	

		parser.undefineOpsBySymbol(_T("-"));

		ASSERTTRUE(parser.getNbDefinedOps() == initialCount-2, _T("bad operator count..."))		

		ASSERTTRUE(parser.evaluate(_T("2+1")) == 3, _T("bad result"))		

		ASSERTTRUE(parser.evaluate(_T("2*4")) == 8, _T("bad result"))		
	}
	catch( MTException e)
	{
		ASSERTTRUE(false, e.m_description.c_str())		
	}

	return m_success;

}

//************************************************
// MTParserTestEvaluateBatch

MTSTRING MTParserTestEvaluateBatch::getName()
{
	return _T("EvaluateBatch");
}

bool MTParserTestEvaluateBatch::doTests()
{
	MTParser parser;
	
	// Create variable object
	MTDoubleVector *pX = new MTDoubleVector(_T("x"));
	MTDoubleVector *pY = new MTDoubleVector(_T("y"));

	// Now defines variables in the parser
	parser.defineVar(pX);
	parser.defineVar(pY);

	
	unsigned int nbEvals = 100;
	unsigned int t;
	
	// Allocate memory for variable values and results
	MTDOUBLE *pXVector = new MTDOUBLE[nbEvals];
	MTDOUBLE *pYVector = new MTDOUBLE[nbEvals];
	MTDOUBLE *pResults = new MTDOUBLE[nbEvals];
		
	// Generate variable values
	for(t=0; t<nbEvals; t++)
	{
		pXVector[t] = t;
		pYVector[t] = t*2;
	}	

	// Assign variable values
	pX->setValues(pXVector, nbEvals);
	pY->setValues(pYVector, nbEvals);

	try
	{		
		// Compile the expression only once
		parser.compile(_T("x"));
		
		// Evaluate the expression for all variable value in one function call
		parser.evaluateBatch(nbEvals, pResults);

		for(t=0; t<nbEvals; t++)
		{
			ASSERTTRUE(pResults[t]==pXVector[t], _T("bad result"))		
		}
		
		parser.compile(_T("x+y"));		
		parser.evaluateBatch(nbEvals, pResults);

		for(t=0; t<nbEvals; t++)
		{
			ASSERTTRUE(pResults[t]==(pXVector[t]+pYVector[t]), _T("bad result"))		
		}

		parser.compile(_T("x+y+x"));		
		parser.evaluateBatch(nbEvals, pResults);

		for(t=0; t<nbEvals; t++)
		{
			ASSERTTRUE(pResults[t]==(2*pXVector[t]+pYVector[t]), _T("bad result"))		
		}	
		

	}
	catch( MTException )
	{
		ASSERTTRUE(false, _T("Err!"))		
	}

	delete []pXVector;
	delete []pYVector;
	delete []pResults;

	return m_success;
}

//************************************************
// MTParserTestRefinedVar

MTSTRING MTParserTestRefinedVar::getName()
{
	return _T("Redefined Var");
}

bool MTParserTestRefinedVar::doTests()
{
	MTParser parser;

	loadNumAlgoPlugin(&parser);	

	try
	{
		MTDOUBLE x = 10;
		parser.defineVar(_T("x"), &x);

		ASSERTTRUE(parser.evaluate(_T("x"))==10, _T("Bad result"))		

		DummyVariable *pVar = new DummyVariable();
		pVar->m_symbol = _T("x");
		pVar->m_value = 11;

		parser.redefineVar(pVar);

		ASSERTTRUE(parser.evaluate()==11, _T("Bad result"))		

		// test the other redefineVar method		
		MTDOUBLE x2 = 12;
		parser.redefineVar(_T("x"), &x2);
		ASSERTTRUE(parser.evaluate()==12, _T("Bad result"))		

	}
	catch( MTParserException )
	{
		ASSERTTRUE(false, _T("Redefining a variable failed"))		
	}

	// Redefine an undefined variable... should fail
	try
	{
		DummyVariable *pUndefinedVar = new DummyVariable();
		pUndefinedVar->m_symbol = _T("y");

		parser.redefineVar(pUndefinedVar);		
		ASSERTTRUE(false, _T("Redefining an undefined variable should fail"))		
	}
	catch( MTParserException )
	{
		// ok!
	}

	// Redefine a variable that is not used in the current expression
	try
	{
		MTDOUBLE y = 0;
		parser.defineVar(_T("y"), &y);
		
		DummyVariable *pVar = new DummyVariable();
		pVar->m_symbol = _T("y");

		parser.redefineVar(pVar);				
	}
	catch( MTParserException )
	{
		ASSERTTRUE(false, _T("Redefining an unused variable failed"))		
	}

	try
	{
		MTDOUBLE x = 10;
		MTDOUBLE y = 1;

		parser.undefineAllVars();
		parser.defineVar(_T("x"), &x);
		parser.defineVar(_T("y"), &y);

		parser.compile(_T("x+round(derivate(x^2,x,y))"));

		DummyVariable *pVarX = new DummyVariable();
		pVarX->m_symbol = _T("x");
		pVarX->m_value = 2;

		DummyVariable *pVarY = new DummyVariable();
		pVarY->m_symbol = _T("y");
		pVarY->m_value = 20;

		parser.redefineVar(pVarX);

		ASSERTTRUE(parser.evaluate()==4, _T("Bad result"))		

		parser.redefineVar(pVarY);

		ASSERTTRUE(parser.evaluate()==42, _T("Bad result"))		
	}
	catch( MTParserException e)
	{
		ASSERTTRUE(false, _T("Complex test case failed"))		
	}
	
	return m_success;
}

//************************************************
// MTParserTestVariable

MTSTRING MTParserTestVariable::getName()
{
	return _T("Variable");
}

bool MTParserTestVariable::doTests()
{
	MTParser parser;
	MTDOUBLE value = 0;

	try
	{
		ASSERTTRUE(parser.getNbDefinedVars() == 0 , _T("no variable defined yet..."))

		parser.defineVar(_T("x"), &value);

		ASSERTTRUE(parser.getNbDefinedVars() == 1 , _T("1 var defined"))

		parser.defineVar(_T("y"), &value);

		ASSERTTRUE(parser.getNbDefinedVars() == 2 , _T("2 vars defined"))

		ASSERTTRUE(lstrcmp(parser.getVar(0)->getSymbol(), _T("x")) == 0, _T("symbol should be x"))

		parser.undefineVar(_T("x"));

		ASSERTTRUE(parser.getNbDefinedVars() == 1 , _T("1 var defined"))
		ASSERTTRUE(lstrcmp(parser.getVar(0)->getSymbol(), _T("y")) == 0, _T("symbol should be y"))

	}
	catch(MTParserException)
	{
		ASSERTTRUE(false, _T("Variable test case failed"))
	}

	return m_success;
}


//************************************************
// MTParserTestSuite


bool MTParserTestSuite::doTests()	
{	
	std::vector<MTParserTestCaseI*> tests;
	
	// *********** add test cases here ***********
	tests.push_back(new MTParserTestMathExpr());
	tests.push_back(new MTParserTestGetUsedVars());
	tests.push_back(new MTParserTestParserInit());	
	tests.push_back(new MTParserTestVarDef());
	tests.push_back(new MTParserTestSyntaxDef());
	tests.push_back(new MTParserTestConstDef());
	tests.push_back(new MTParserTestMacro());
	tests.push_back(new MTParserTestInternational());
	tests.push_back(new MTParserTestConstExpr());	
	tests.push_back(new MTParserTestFunctionDef());	
	tests.push_back(new MTParserTestOperatorDef());	
	tests.push_back(new MTParserTestAutoVarDef());
	tests.push_back(new MTParserTestVoidExpr());
	tests.push_back(new MTParserTestUndefineVar());
	tests.push_back(new MTParserTestEvaluateBatch());
	tests.push_back(new MTParserTestRefinedVar());
	tests.push_back(new MTParserTestVariable());
	tests.push_back(new MTParserTestUndefineConstant());
	tests.push_back(new MTParserTestUndefineFunc());
	tests.push_back(new MTParserTestUndefineOp());	
	
	
	// *************************************



	unsigned int t;

	bool success = true;
	for( t=0; t<tests.size(); t++ )
	{
		if(!tests[t]->doTests())
		{
			success=false;		
		}
	}

	for( t=0; t<tests.size(); t++ )
	{
		delete tests[t];
	}
	
	return success;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Canada Canada
Software Engineer working at a fun and smart startup company

Comments and Discussions