test: add tests for ExprParser, and fix two crashes.
parent
11adaf20b7
commit
d6e2ac8328
|
@ -682,6 +682,7 @@ ExprParser::Token ExprParser::Lex(std::string *error) {
|
||||||
Token t = Token::From();
|
Token t = Token::From();
|
||||||
char c = PeekChar();
|
char c = PeekChar();
|
||||||
if(isupper(c)) {
|
if(isupper(c)) {
|
||||||
|
std::string n = ReadWord();
|
||||||
t = Token::From(TokenType::OPERAND, Expr::Op::VARIABLE);
|
t = Token::From(TokenType::OPERAND, Expr::Op::VARIABLE);
|
||||||
} else if(isalpha(c)) {
|
} else if(isalpha(c)) {
|
||||||
std::string s = ReadWord();
|
std::string s = ReadWord();
|
||||||
|
@ -796,6 +797,7 @@ bool ExprParser::Reduce(std::string *error) {
|
||||||
switch(op.expr->op) {
|
switch(op.expr->op) {
|
||||||
case Expr::Op::NEGATE: e = e->Negate(); break;
|
case Expr::Op::NEGATE: e = e->Negate(); break;
|
||||||
case Expr::Op::SQRT: e = e->Sqrt(); break;
|
case Expr::Op::SQRT: e = e->Sqrt(); break;
|
||||||
|
case Expr::Op::SQUARE: e = e->Times(e); break;
|
||||||
case Expr::Op::SIN: e = e->Times(Expr::From(PI/180))->Sin(); break;
|
case Expr::Op::SIN: e = e->Times(Expr::From(PI/180))->Sin(); break;
|
||||||
case Expr::Op::COS: e = e->Times(Expr::From(PI/180))->Cos(); break;
|
case Expr::Op::COS: e = e->Times(Expr::From(PI/180))->Cos(); break;
|
||||||
case Expr::Op::ASIN: e = e->ASin()->Times(Expr::From(180/PI)); break;
|
case Expr::Op::ASIN: e = e->ASin()->Times(Expr::From(180/PI)); break;
|
||||||
|
@ -883,6 +885,10 @@ Expr *ExprParser::Parse(const char *input, std::string *error) {
|
||||||
return r.expr;
|
return r.expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expr *Expr::Parse(const char *input, std::string *error) {
|
||||||
|
return ExprParser::Parse(input, error);
|
||||||
|
}
|
||||||
|
|
||||||
Expr *Expr::From(const char *input, bool popUpError) {
|
Expr *Expr::From(const char *input, bool popUpError) {
|
||||||
std::string error;
|
std::string error;
|
||||||
Expr *e = ExprParser::Parse(input, &error);
|
Expr *e = ExprParser::Parse(input, &error);
|
||||||
|
|
|
@ -96,6 +96,7 @@ public:
|
||||||
Expr *DeepCopyWithParamsAsPointers(IdList<Param,hParam> *firstTry,
|
Expr *DeepCopyWithParamsAsPointers(IdList<Param,hParam> *firstTry,
|
||||||
IdList<Param,hParam> *thenTry) const;
|
IdList<Param,hParam> *thenTry) const;
|
||||||
|
|
||||||
|
static Expr *Parse(const char *input, std::string *error);
|
||||||
static Expr *From(const char *in, bool popUpError);
|
static Expr *From(const char *in, bool popUpError);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ endforeach()
|
||||||
|
|
||||||
set(testsuite_SOURCES
|
set(testsuite_SOURCES
|
||||||
harness.cpp
|
harness.cpp
|
||||||
|
core/expr/test.cpp
|
||||||
constraint/points_coincident/test.cpp
|
constraint/points_coincident/test.cpp
|
||||||
constraint/pt_pt_distance/test.cpp
|
constraint/pt_pt_distance/test.cpp
|
||||||
constraint/pt_plane_distance/test.cpp
|
constraint/pt_plane_distance/test.cpp
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
#include "harness.h"
|
||||||
|
|
||||||
|
#define CHECK_PARSE(var, expr) \
|
||||||
|
do { \
|
||||||
|
var = Expr::From(expr, false); \
|
||||||
|
CHECK_TRUE(var != NULL); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define CHECK_PARSE_ERR(expr, msg) \
|
||||||
|
do { \
|
||||||
|
std::string err; \
|
||||||
|
Expr *e = Expr::Parse(expr, &err); \
|
||||||
|
CHECK_TRUE(e == NULL); \
|
||||||
|
CHECK_TRUE(err.find(msg) != std::string::npos); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
TEST_CASE(constant) {
|
||||||
|
Expr *e;
|
||||||
|
CHECK_PARSE(e, "pi");
|
||||||
|
CHECK_EQ_EPS(e->Eval(), 3.1415926);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(literal) {
|
||||||
|
Expr *e;
|
||||||
|
CHECK_PARSE(e, "42");
|
||||||
|
CHECK_TRUE(e->Eval() == 42);
|
||||||
|
CHECK_PARSE(e, "42.5");
|
||||||
|
CHECK_TRUE(e->Eval() == 42.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(unary_ops) {
|
||||||
|
Expr *e;
|
||||||
|
CHECK_PARSE(e, "-10");
|
||||||
|
CHECK_TRUE(e->Eval() == -10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(binary_ops) {
|
||||||
|
Expr *e;
|
||||||
|
CHECK_PARSE(e, "1 + 2");
|
||||||
|
CHECK_TRUE(e->Eval() == 3);
|
||||||
|
CHECK_PARSE(e, "1 - 2");
|
||||||
|
CHECK_TRUE(e->Eval() == -1);
|
||||||
|
CHECK_PARSE(e, "3 * 4");
|
||||||
|
CHECK_TRUE(e->Eval() == 12);
|
||||||
|
CHECK_PARSE(e, "3 / 4");
|
||||||
|
CHECK_TRUE(e->Eval() == 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(parentheses) {
|
||||||
|
Expr *e;
|
||||||
|
CHECK_PARSE(e, "(1 + 2) * 3");
|
||||||
|
CHECK_TRUE(e->Eval() == 9);
|
||||||
|
CHECK_PARSE(e, "1 + (2 * 3)");
|
||||||
|
CHECK_TRUE(e->Eval() == 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(functions) {
|
||||||
|
Expr *e;
|
||||||
|
CHECK_PARSE(e, "sqrt(2)");
|
||||||
|
CHECK_EQ_EPS(e->Eval(), 1.414213);
|
||||||
|
CHECK_PARSE(e, "square(3)");
|
||||||
|
CHECK_EQ_EPS(e->Eval(), 9);
|
||||||
|
CHECK_PARSE(e, "sin(180)");
|
||||||
|
CHECK_EQ_EPS(e->Eval(), 0);
|
||||||
|
CHECK_PARSE(e, "sin(90)");
|
||||||
|
CHECK_EQ_EPS(e->Eval(), 1);
|
||||||
|
CHECK_PARSE(e, "cos(180)");
|
||||||
|
CHECK_EQ_EPS(e->Eval(), -1);
|
||||||
|
CHECK_PARSE(e, "asin(1)");
|
||||||
|
CHECK_EQ_EPS(e->Eval(), 90);
|
||||||
|
CHECK_PARSE(e, "acos(0)");
|
||||||
|
CHECK_EQ_EPS(e->Eval(), 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(variable) {
|
||||||
|
Expr *e;
|
||||||
|
CHECK_PARSE(e, "Var");
|
||||||
|
CHECK_TRUE(e->op == Expr::Op::VARIABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(precedence) {
|
||||||
|
Expr *e;
|
||||||
|
CHECK_PARSE(e, "2 + 3 * 4");
|
||||||
|
CHECK_TRUE(e->Eval() == 14);
|
||||||
|
CHECK_PARSE(e, "2 - 3 / 4");
|
||||||
|
CHECK_TRUE(e->Eval() == 1.25);
|
||||||
|
CHECK_PARSE(e, "-3 + 2");
|
||||||
|
CHECK_TRUE(e->Eval() == -1);
|
||||||
|
CHECK_PARSE(e, "2 + 3 - 4");
|
||||||
|
CHECK_TRUE(e->Eval() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(errors) {
|
||||||
|
CHECK_PARSE_ERR("\x01",
|
||||||
|
"Unexpected character");
|
||||||
|
CHECK_PARSE_ERR("notavar",
|
||||||
|
"'notavar' is not a valid variable, function or constant");
|
||||||
|
CHECK_PARSE_ERR("_",
|
||||||
|
"'_' is not a valid operator");
|
||||||
|
CHECK_PARSE_ERR("2 2",
|
||||||
|
"Expected an operator");
|
||||||
|
CHECK_PARSE_ERR("2 + +",
|
||||||
|
"Expected an operand");
|
||||||
|
CHECK_PARSE_ERR("( 2 + 2",
|
||||||
|
"Expected ')'");
|
||||||
|
}
|
|
@ -178,6 +178,18 @@ bool Test::Helper::CheckTrue(const char *file, int line, const char *expr, bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Test::Helper::CheckEqualEpsilon(const char *file, int line, const char *valueExpr,
|
||||||
|
double value, double reference) {
|
||||||
|
bool result = fabs(value - reference) < LENGTH_EPS;
|
||||||
|
if(!RecordCheck(result)) {
|
||||||
|
std::string msg = ssprintf("(%s) = %.4g ≉ %.4g", valueExpr, value, reference);
|
||||||
|
PrintFailure(file, line, msg);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Test::Helper::CheckLoad(const char *file, int line, const char *fixture) {
|
bool Test::Helper::CheckLoad(const char *file, int line, const char *fixture) {
|
||||||
std::string fixturePath = GetAssetPath(file, fixture);
|
std::string fixturePath = GetAssetPath(file, fixture);
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ public:
|
||||||
std::string mangle = "");
|
std::string mangle = "");
|
||||||
|
|
||||||
bool CheckTrue(const char *file, int line, const char *expr, bool result);
|
bool CheckTrue(const char *file, int line, const char *expr, bool result);
|
||||||
|
bool CheckEqualEpsilon(const char *file, int line, const char *valueExpr,
|
||||||
|
double value, double reference);
|
||||||
bool CheckLoad(const char *file, int line, const char *fixture);
|
bool CheckLoad(const char *file, int line, const char *fixture);
|
||||||
bool CheckSave(const char *file, int line, const char *reference);
|
bool CheckSave(const char *file, int line, const char *reference);
|
||||||
bool CheckRender(const char *file, int line, const char *fixture);
|
bool CheckRender(const char *file, int line, const char *fixture);
|
||||||
|
@ -51,6 +53,9 @@ using namespace SolveSpace;
|
||||||
|
|
||||||
#define CHECK_TRUE(cond) \
|
#define CHECK_TRUE(cond) \
|
||||||
do { if(!helper->CheckTrue(__FILE__, __LINE__, #cond, cond)) return; } while(0)
|
do { if(!helper->CheckTrue(__FILE__, __LINE__, #cond, cond)) return; } while(0)
|
||||||
|
#define CHECK_EQ_EPS(value, reference) \
|
||||||
|
do { if(!helper->CheckEqualEpsilon(__FILE__, __LINE__, \
|
||||||
|
#value, value, reference)) return; } while(0)
|
||||||
#define CHECK_LOAD(fixture) \
|
#define CHECK_LOAD(fixture) \
|
||||||
do { if(!helper->CheckLoad(__FILE__, __LINE__, fixture)) return; } while(0)
|
do { if(!helper->CheckLoad(__FILE__, __LINE__, fixture)) return; } while(0)
|
||||||
#define CHECK_SAVE(fixture) \
|
#define CHECK_SAVE(fixture) \
|
||||||
|
|
Loading…
Reference in New Issue