AnimExpression: support for parsing simple expressions

supports parens, binary +, -, / and *.
/ and * have higher precedence then + and -
This commit is contained in:
Anthony J. Thibault 2015-12-03 15:02:00 -08:00
parent 710ce7e639
commit 99223d0a3c
4 changed files with 417 additions and 131 deletions

View file

@ -17,9 +17,16 @@
AnimExpression::AnimExpression(const QString& str) : AnimExpression::AnimExpression(const QString& str) :
_expression(str) { _expression(str) {
auto iter = str.begin(); auto iter = str.begin();
parseExpression(_expression, iter); parseExpr(_expression, iter);
while(!_tokenStack.empty()) {
_tokenStack.pop();
}
} }
//
// Tokenizer
//
void AnimExpression::unconsumeToken(const Token& token) { void AnimExpression::unconsumeToken(const Token& token) {
_tokenStack.push(token); _tokenStack.push(token);
} }
@ -49,6 +56,7 @@ AnimExpression::Token AnimExpression::consumeToken(const QString& str, QString::
case '-': ++iter; return Token(Token::Minus); case '-': ++iter; return Token(Token::Minus);
case '+': ++iter; return Token(Token::Plus); case '+': ++iter; return Token(Token::Plus);
case '*': ++iter; return Token(Token::Multiply); case '*': ++iter; return Token(Token::Multiply);
case '/': ++iter; return Token(Token::Divide);
case '%': ++iter; return Token(Token::Modulus); case '%': ++iter; return Token(Token::Modulus);
case ',': ++iter; return Token(Token::Comma); case ',': ++iter; return Token(Token::Comma);
default: default:
@ -184,73 +192,137 @@ AnimExpression::Token AnimExpression::consumeNot(const QString& str, QString::co
} }
} }
bool AnimExpression::parseExpression(const QString& str, QString::const_iterator& iter) { //
// Parser
//
/*
Expr Term Expr'
Expr' + Term Expr'
| Term Expr'
| ε
Term Factor Term'
Term' * Term'
| / Term'
| ε
Factor INT
| FLOAT
| IDENTIFIER
| (Expr)
*/
bool AnimExpression::parseExpr(const QString& str, QString::const_iterator& iter) {
if (!parseTerm(str, iter)) {
return false;
}
if (!parseExprPrime(str, iter)) {
return false;
}
return true;
}
bool AnimExpression::parseExprPrime(const QString& str, QString::const_iterator& iter) {
auto token = consumeToken(str, iter); auto token = consumeToken(str, iter);
if (token.type == Token::Identifier) { if (token.type == Token::Plus) {
if (token.strVal == "true") { if (!parseTerm(str, iter)) {
_opCodes.push_back(OpCode {true}); unconsumeToken(token);
} else if (token.strVal == "false") { return false;
_opCodes.push_back(OpCode {false});
} else {
_opCodes.push_back(OpCode {token.strVal});
} }
if (!parseExprPrime(str, iter)) {
unconsumeToken(token);
return false;
}
_opCodes.push_back(OpCode {OpCode::Add});
return true; return true;
} else if (token.type == Token::Int) { } else if (token.type == Token::Minus) {
if (!parseTerm(str, iter)) {
unconsumeToken(token);
return false;
}
if (!parseExprPrime(str, iter)) {
unconsumeToken(token);
return false;
}
_opCodes.push_back(OpCode {OpCode::Subtract});
return true;
} else {
unconsumeToken(token);
return true;
}
}
bool AnimExpression::parseTerm(const QString& str, QString::const_iterator& iter) {
if (!parseFactor(str, iter)) {
return false;
}
if (!parseTermPrime(str, iter)) {
return false;
}
return true;
}
bool AnimExpression::parseTermPrime(const QString& str, QString::const_iterator& iter) {
auto token = consumeToken(str, iter);
if (token.type == Token::Multiply) {
if (!parseTerm(str, iter)) {
unconsumeToken(token);
return false;
}
if (!parseTermPrime(str, iter)) {
unconsumeToken(token);
return false;
}
_opCodes.push_back(OpCode {OpCode::Multiply});
return true;
} else if (token.type == Token::Divide) {
if (!parseTerm(str, iter)) {
unconsumeToken(token);
return false;
}
if (!parseTermPrime(str, iter)) {
unconsumeToken(token);
return false;
}
_opCodes.push_back(OpCode {OpCode::Divide});
return true;
} else {
unconsumeToken(token);
return true;
}
}
bool AnimExpression::parseFactor(const QString& str, QString::const_iterator& iter) {
auto token = consumeToken(str, iter);
if (token.type == Token::Int) {
_opCodes.push_back(OpCode {token.intVal}); _opCodes.push_back(OpCode {token.intVal});
return true; return true;
} else if (token.type == Token::Float) { } else if (token.type == Token::Float) {
_opCodes.push_back(OpCode {token.floatVal}); _opCodes.push_back(OpCode {token.floatVal});
return true; return true;
} else if (token.type == Token::Identifier) {
_opCodes.push_back(OpCode {token.strVal});
return true;
} else if (token.type == Token::LeftParen) { } else if (token.type == Token::LeftParen) {
if (parseUnaryExpression(str, iter)) { if (!parseExpr(str, iter)) {
token = consumeToken(str, iter); unconsumeToken(token);
if (token.type != Token::RightParen) {
qCCritical(animation) << "Error parsing expression, expected ')'";
return false;
} else {
return true;
}
} else {
return false; return false;
} }
auto nextToken = consumeToken(str, iter);
if (nextToken.type != Token::RightParen) {
unconsumeToken(nextToken);
unconsumeToken(token);
return false;
}
return true;
} else { } else {
unconsumeToken(token); unconsumeToken(token);
if (parseUnaryExpression(str, iter)) { return false;
return true;
} else {
qCCritical(animation) << "Error parsing expression";
return false;
}
} }
} }
bool AnimExpression::parseUnaryExpression(const QString& str, QString::const_iterator& iter) { //
auto token = consumeToken(str, iter); // Evaluator
if (token.type == Token::Plus) { // unary plus is a no op. //
if (parseExpression(str, iter)) {
return true;
} else {
return false;
}
} else if (token.type == Token::Minus) {
if (parseExpression(str, iter)) {
_opCodes.push_back(OpCode {OpCode::Minus});
return true;
} else {
return false;
}
} else if (token.type == Token::Not) {
if (parseExpression(str, iter)) {
_opCodes.push_back(OpCode {OpCode::Not});
return true;
} else {
return false;
}
} else {
unconsumeToken(token);
return parseExpression(str, iter);
}
}
AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const { AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const {
std::stack<OpCode> stack; std::stack<OpCode> stack;
@ -274,8 +346,9 @@ AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const
case OpCode::Subtract: evalSubtract(map, stack); break; case OpCode::Subtract: evalSubtract(map, stack); break;
case OpCode::Add: evalAdd(map, stack); break; case OpCode::Add: evalAdd(map, stack); break;
case OpCode::Multiply: evalMultiply(map, stack); break; case OpCode::Multiply: evalMultiply(map, stack); break;
case OpCode::Divide: evalDivide(map, stack); break;
case OpCode::Modulus: evalModulus(map, stack); break; case OpCode::Modulus: evalModulus(map, stack); break;
case OpCode::Minus: evalMinus(map, stack); break; case OpCode::UnaryMinus: evalUnaryMinus(map, stack); break;
} }
} }
return stack.top(); return stack.top();
@ -362,15 +435,107 @@ void AnimExpression::evalSubtract(const AnimVariantMap& map, std::stack<OpCode>&
PUSH(0.0f); PUSH(0.0f);
} }
void AnimExpression::evalAdd(const AnimVariantMap& map, std::stack<OpCode>& stack) const { void AnimExpression::add(int lhs, const OpCode& rhs, std::stack<OpCode>& stack) const {
OpCode lhs = stack.top(); stack.pop(); switch (rhs.type) {
OpCode rhs = stack.top(); stack.pop(); case OpCode::Bool:
case OpCode::Int:
PUSH(lhs + rhs.intVal);
break;
case OpCode::Float:
PUSH((float)lhs + rhs.floatVal);
break;
default:
PUSH(lhs);
}
}
// TODO: void AnimExpression::add(float lhs, const OpCode& rhs, std::stack<OpCode>& stack) const {
PUSH(0.0f); switch (rhs.type) {
case OpCode::Bool:
case OpCode::Int:
PUSH(lhs + (float)rhs.intVal);
break;
case OpCode::Float:
PUSH(lhs + rhs.floatVal);
break;
default:
PUSH(lhs);
}
}
void AnimExpression::evalAdd(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
OpCode lhs = coerseToValue(map, stack.top());
stack.pop();
OpCode rhs = coerseToValue(map, stack.top());
stack.pop();
switch (lhs.type) {
case OpCode::Bool:
add(lhs.intVal, rhs, stack);
break;
case OpCode::Int:
add(lhs.intVal, rhs, stack);
break;
case OpCode::Float:
add(lhs.floatVal, rhs, stack);
break;
default:
add(0, rhs, stack);
break;
}
} }
void AnimExpression::evalMultiply(const AnimVariantMap& map, std::stack<OpCode>& stack) const { void AnimExpression::evalMultiply(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
OpCode lhs = coerseToValue(map, stack.top());
stack.pop();
OpCode rhs = coerseToValue(map, stack.top());
stack.pop();
switch(lhs.type) {
case OpCode::Bool:
mul(lhs.intVal, rhs, stack);
break;
case OpCode::Int:
mul(lhs.intVal, rhs, stack);
break;
case OpCode::Float:
mul(lhs.floatVal, rhs, stack);
break;
default:
mul(0, rhs, stack);
break;
}
}
void AnimExpression::mul(int lhs, const OpCode& rhs, std::stack<OpCode>& stack) const {
switch (rhs.type) {
case OpCode::Bool:
case OpCode::Int:
PUSH(lhs * rhs.intVal);
break;
case OpCode::Float:
PUSH((float)lhs * rhs.floatVal);
break;
default:
PUSH(lhs);
}
}
void AnimExpression::mul(float lhs, const OpCode& rhs, std::stack<OpCode>& stack) const {
switch (rhs.type) {
case OpCode::Bool:
case OpCode::Int:
PUSH(lhs * (float)rhs.intVal);
break;
case OpCode::Float:
PUSH(lhs * rhs.floatVal);
break;
default:
PUSH(lhs);
}
}
void AnimExpression::evalDivide(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
OpCode lhs = stack.top(); stack.pop(); OpCode lhs = stack.top(); stack.pop();
OpCode rhs = stack.top(); stack.pop(); OpCode rhs = stack.top(); stack.pop();
@ -386,7 +551,7 @@ void AnimExpression::evalModulus(const AnimVariantMap& map, std::stack<OpCode>&
PUSH((int)0); PUSH((int)0);
} }
void AnimExpression::evalMinus(const AnimVariantMap& map, std::stack<OpCode>& stack) const { void AnimExpression::evalUnaryMinus(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
OpCode rhs = stack.top(); stack.pop(); OpCode rhs = stack.top(); stack.pop();
switch (rhs.type) { switch (rhs.type) {
@ -429,3 +594,69 @@ void AnimExpression::evalMinus(const AnimVariantMap& map, std::stack<OpCode>& st
break; break;
} }
} }
AnimExpression::OpCode AnimExpression::coerseToValue(const AnimVariantMap& map, const OpCode& opCode) const {
switch (opCode.type) {
case OpCode::Identifier:
{
const AnimVariant& var = map.get(opCode.strVal);
switch (var.getType()) {
case AnimVariant::Type::Bool:
qCWarning(animation) << "AnimExpression: type missmatch, expected a number not a bool";
return OpCode(0);
break;
case AnimVariant::Type::Int:
return OpCode(var.getInt());
break;
case AnimVariant::Type::Float:
return OpCode(var.getFloat());
break;
default:
// TODO: Vec3, Quat are unsupported
assert(false);
return OpCode(0);
break;
}
}
break;
case OpCode::Bool:
case OpCode::Int:
case OpCode::Float:
return opCode;
default:
qCCritical(animation) << "AnimExpression: ERROR expected a number, type = " << opCode.type;
assert(false);
return OpCode(0);
break;
}
}
void AnimExpression::dumpOpCodes() const {
QString tmp;
for (auto& op : _opCodes) {
switch (op.type) {
case OpCode::Identifier: tmp += QString(" %1").arg(op.strVal); break;
case OpCode::Bool: tmp += QString(" %1").arg(op.intVal ? "true" : "false"); break;
case OpCode::Int: tmp += QString(" %1").arg(op.intVal); break;
case OpCode::Float: tmp += QString(" %1").arg(op.floatVal); break;
case OpCode::And: tmp += " &&"; break;
case OpCode::Or: tmp += " ||"; break;
case OpCode::GreaterThan: tmp += " >"; break;
case OpCode::GreaterThanEqual: tmp += " >="; break;
case OpCode::LessThan: tmp += " <"; break;
case OpCode::LessThanEqual: tmp += " <="; break;
case OpCode::Equal: tmp += " =="; break;
case OpCode::NotEqual: tmp += " !="; break;
case OpCode::Not: tmp += " !"; break;
case OpCode::Subtract: tmp += " -"; break;
case OpCode::Add: tmp += " +"; break;
case OpCode::Multiply: tmp += " *"; break;
case OpCode::Divide: tmp += " /"; break;
case OpCode::Modulus: tmp += " %"; break;
case OpCode::UnaryMinus: tmp += " unary-"; break;
default: tmp += " ???"; break;
}
}
qCDebug(animation).nospace().noquote() << "opCodes =" << tmp;
qCDebug(animation).resetFormat();
}

View file

@ -43,6 +43,7 @@ protected:
Minus, Minus,
Plus, Plus,
Multiply, Multiply,
Divide,
Modulus, Modulus,
Comma, Comma,
Error Error
@ -75,8 +76,9 @@ protected:
Subtract, Subtract,
Add, Add,
Multiply, Multiply,
Divide,
Modulus, Modulus,
Minus UnaryMinus
}; };
OpCode(Type type) : type {type} {} OpCode(Type type) : type {type} {}
OpCode(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {} OpCode(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {}
@ -111,8 +113,11 @@ protected:
Token consumeLessThan(const QString& str, QString::const_iterator& iter) const; Token consumeLessThan(const QString& str, QString::const_iterator& iter) const;
Token consumeNot(const QString& str, QString::const_iterator& iter) const; Token consumeNot(const QString& str, QString::const_iterator& iter) const;
bool parseExpression(const QString& str, QString::const_iterator& iter); bool parseExpr(const QString& str, QString::const_iterator& iter);
bool parseUnaryExpression(const QString& str, QString::const_iterator& iter); bool parseExprPrime(const QString& str, QString::const_iterator& iter);
bool parseTerm(const QString& str, QString::const_iterator& iter);
bool parseTermPrime(const QString& str, QString::const_iterator& iter);
bool parseFactor(const QString& str, QString::const_iterator& iter);
OpCode evaluate(const AnimVariantMap& map) const; OpCode evaluate(const AnimVariantMap& map) const;
void evalAnd(const AnimVariantMap& map, std::stack<OpCode>& stack) const; void evalAnd(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
@ -126,13 +131,24 @@ protected:
void evalNot(const AnimVariantMap& map, std::stack<OpCode>& stack) const; void evalNot(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
void evalSubtract(const AnimVariantMap& map, std::stack<OpCode>& stack) const; void evalSubtract(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
void evalAdd(const AnimVariantMap& map, std::stack<OpCode>& stack) const; void evalAdd(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
void add(int lhs, const OpCode& rhs, std::stack<OpCode>& stack) const;
void add(float lhs, const OpCode& rhs, std::stack<OpCode>& stack) const;
void evalMultiply(const AnimVariantMap& map, std::stack<OpCode>& stack) const; void evalMultiply(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
void mul(int lhs, const OpCode& rhs, std::stack<OpCode>& stack) const;
void mul(float lhs, const OpCode& rhs, std::stack<OpCode>& stack) const;
void evalDivide(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
void evalModulus(const AnimVariantMap& map, std::stack<OpCode>& stack) const; void evalModulus(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
void evalMinus(const AnimVariantMap& map, std::stack<OpCode>& stack) const; void evalUnaryMinus(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
OpCode coerseToValue(const AnimVariantMap& map, const OpCode& opCode) const;
QString _expression; QString _expression;
mutable std::stack<Token> _tokenStack; // TODO: remove, only needed during parsing mutable std::stack<Token> _tokenStack; // TODO: remove, only needed during parsing
std::vector<OpCode> _opCodes; std::vector<OpCode> _opCodes;
#ifndef NDEBUG
void dumpOpCodes() const;
#endif
}; };
#endif #endif

View file

@ -366,82 +366,120 @@ void AnimTests::testExpressionParser() {
vars.set("five", (float)5.0f); vars.set("five", (float)5.0f);
vars.set("forty", (float)40.0f); vars.set("forty", (float)40.0f);
AnimExpression e("(!f)"); AnimExpression e("10");
QVERIFY(e._opCodes.size() == 2); QVERIFY(e._opCodes.size() == 1);
if (e._opCodes.size() == 2) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier);
QVERIFY(e._opCodes[0].strVal == "f");
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Not);
auto opCode = e.evaluate(vars);
QVERIFY(opCode.type == AnimExpression::OpCode::Bool);
QVERIFY((opCode.intVal != 0) == true);
}
e = AnimExpression("!!f");
QVERIFY(e._opCodes.size() == 3);
if (e._opCodes.size() == 3) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier);
QVERIFY(e._opCodes[0].strVal == "f");
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Not);
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Not);
auto opCode = e.evaluate(vars);
QVERIFY(opCode.type == AnimExpression::OpCode::Bool);
QVERIFY((opCode.intVal != 0) == false);
}
e = AnimExpression("!!(!f)");
QVERIFY(e._opCodes.size() == 4);
if (e._opCodes.size() == 4) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier);
QVERIFY(e._opCodes[0].strVal == "f");
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Not);
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Not);
QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::Not);
auto opCode = e.evaluate(vars);
QVERIFY(opCode.type == AnimExpression::OpCode::Bool);
QVERIFY((opCode.intVal != 0) == true);
}
/*
e = AnimExpression("f || !f");
QVERIFY(e._opCodes.size() == 4);
if (e._opCodes.size() == 4) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier);
QVERIFY(e._opCodes[0].strVal == "f");
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Identifier);
QVERIFY(e._opCodes[1].strVal == "f");
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Not);
QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::Or);
auto opCode = e.evaluate(vars);
QVERIFY(opCode.type == AnimExpression::OpCode::Bool);
QVERIFY((opCode.intVal != 0) == true);
}
*/
e = AnimExpression("-10");
QVERIFY(e._opCodes.size() == 2);
if (e._opCodes.size() == 1) { if (e._opCodes.size() == 1) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int); QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[0].intVal == 10); QVERIFY(e._opCodes[0].intVal == 10);
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Minus);
auto opCode = e.evaluate(vars);
QVERIFY(opCode.type == AnimExpression::OpCode::Int);
QVERIFY(opCode.intVal == 10);
} }
e = AnimExpression("-ten"); e = AnimExpression("(10)");
QVERIFY(e._opCodes.size() == 2); QVERIFY(e._opCodes.size() == 1);
if (e._opCodes.size() == 1) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[0].intVal == 10);
}
e = AnimExpression("((10))");
QVERIFY(e._opCodes.size() == 1);
if (e._opCodes.size() == 1) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[0].intVal == 10);
}
e = AnimExpression("12.5");
QVERIFY(e._opCodes.size() == 1);
if (e._opCodes.size() == 1) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Float);
QVERIFY(e._opCodes[0].floatVal == 12.5f);
}
e = AnimExpression("twenty");
QVERIFY(e._opCodes.size() == 1);
if (e._opCodes.size() == 1) { if (e._opCodes.size() == 1) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier); QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier);
QVERIFY(e._opCodes[0].strVal == "ten"); QVERIFY(e._opCodes[0].strVal == "twenty");
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Minus); }
auto opCode = e.evaluate(vars); e = AnimExpression("2 + 3");
QVERIFY(opCode.type == AnimExpression::OpCode::Int); QVERIFY(e._opCodes.size() == 3);
QVERIFY(opCode.intVal == 10); if (e._opCodes.size() == 3) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[0].intVal == 2);
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[1].intVal == 3);
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Add);
}
e = AnimExpression("2 + 3 * 10");
QVERIFY(e._opCodes.size() == 5);
if (e._opCodes.size() == 5) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[0].intVal == 2);
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[1].intVal == 3);
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[2].intVal == 10);
QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::Multiply);
QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::Add);
}
e = AnimExpression("(2 + 3) * 10");
QVERIFY(e._opCodes.size() == 5);
if (e._opCodes.size() == 5) {
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[0].intVal == 2);
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[1].intVal == 3);
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Add);
QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::Int);
QVERIFY(e._opCodes[3].intVal == 10);
QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::Multiply);
} }
} }
void AnimTests::testExpressionEvaluator() {
auto vars = AnimVariantMap();
vars.set("f", false);
vars.set("t", true);
vars.set("ten", (int)10);
vars.set("twenty", (int)20);
vars.set("five", (float)5.0f);
vars.set("forty", (float)40.0f);
AnimExpression::OpCode result = AnimExpression("10").evaluate(vars);
QVERIFY(result.type == AnimExpression::OpCode::Int);
QVERIFY(result.intVal == 10);
result = AnimExpression("(10)").evaluate(vars);
QVERIFY(result.type == AnimExpression::OpCode::Int);
QVERIFY(result.intVal == 10);
result = AnimExpression("2 + 3").evaluate(vars);
QVERIFY(result.type == AnimExpression::OpCode::Int);
QVERIFY(result.intVal == 2 + 3);
result = AnimExpression("2 + 3 * 5").evaluate(vars);
QVERIFY(result.type == AnimExpression::OpCode::Int);
QVERIFY(result.intVal == 2 + 3 * 5);
result = AnimExpression("(2 + 3) * 5").evaluate(vars);
QVERIFY(result.type == AnimExpression::OpCode::Int);
QVERIFY(result.intVal == (2 + 3) * 5);
result = AnimExpression("(2 + 3) * (5 + 3)").evaluate(vars);
QVERIFY(result.type == AnimExpression::OpCode::Int);
QVERIFY(result.intVal == (2 + 3) * (5 + 3));
result = AnimExpression("(ten + twenty) * 5").evaluate(vars);
QVERIFY(result.type == AnimExpression::OpCode::Int);
QVERIFY(result.intVal == (10 + 20) * 5);
result = AnimExpression("(ten + twenty) * 5.0").evaluate(vars);
QVERIFY(result.type == AnimExpression::OpCode::Float);
QVERIFY(result.floatVal == (10 + 20) * 5.0f);
result = AnimExpression("five * forty").evaluate(vars);
QVERIFY(result.type == AnimExpression::OpCode::Float);
QVERIFY(result.floatVal == 5.0f * 40.0f);
}

View file

@ -28,6 +28,7 @@ private slots:
void testAccumulateTime(); void testAccumulateTime();
void testExpressionTokenizer(); void testExpressionTokenizer();
void testExpressionParser(); void testExpressionParser();
void testExpressionEvaluator();
}; };
#endif // hifi_AnimTests_h #endif // hifi_AnimTests_h