mirror of
https://github.com/overte-org/overte.git
synced 2025-04-08 16:32:38 +02:00
Merge pull request #6660 from hyperlogic/tony/anim-expressions
AnimExpression: boolean expression parser and evaluator
This commit is contained in:
commit
ec27c75332
8 changed files with 1135 additions and 16 deletions
676
libraries/animation/src/AnimExpression.cpp
Normal file
676
libraries/animation/src/AnimExpression.cpp
Normal file
|
@ -0,0 +1,676 @@
|
|||
//
|
||||
// AnimExpression.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on 11/1/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <StreamUtils.h>
|
||||
#include <QRegExp>
|
||||
|
||||
#include "AnimExpression.h"
|
||||
#include "AnimationLogging.h"
|
||||
|
||||
AnimExpression::AnimExpression(const QString& str) :
|
||||
_expression(str) {
|
||||
auto iter = str.begin();
|
||||
parseExpr(_expression, iter);
|
||||
while(!_tokenStack.empty()) {
|
||||
_tokenStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Tokenizer
|
||||
//
|
||||
|
||||
void AnimExpression::unconsumeToken(const Token& token) {
|
||||
_tokenStack.push(token);
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeToken(const QString& str, QString::const_iterator& iter) const {
|
||||
if (!_tokenStack.empty()) {
|
||||
Token top = _tokenStack.top();
|
||||
_tokenStack.pop();
|
||||
return top;
|
||||
} else {
|
||||
while (iter != str.end()) {
|
||||
if (iter->isSpace()) {
|
||||
++iter;
|
||||
} else if (iter->isLetter()) {
|
||||
return consumeIdentifier(str, iter);
|
||||
} else if (iter->isDigit()) {
|
||||
return consumeNumber(str, iter);
|
||||
} else {
|
||||
switch (iter->unicode()) {
|
||||
case '&': return consumeAnd(str, iter);
|
||||
case '|': return consumeOr(str, iter);
|
||||
case '>': return consumeGreaterThan(str, iter);
|
||||
case '<': return consumeLessThan(str, iter);
|
||||
case '(': ++iter; return Token(Token::LeftParen);
|
||||
case ')': ++iter; return Token(Token::RightParen);
|
||||
case '!': return consumeNot(str, iter);
|
||||
case '-': ++iter; return Token(Token::Minus);
|
||||
case '+': ++iter; return Token(Token::Plus);
|
||||
case '*': ++iter; return Token(Token::Multiply);
|
||||
case '/': ++iter; return Token(Token::Divide);
|
||||
case '%': ++iter; return Token(Token::Modulus);
|
||||
case ',': ++iter; return Token(Token::Comma);
|
||||
default:
|
||||
qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin());
|
||||
return Token(Token::Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Token(Token::End);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeIdentifier(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->isLetter());
|
||||
auto begin = iter;
|
||||
while ((iter->isLetter() || iter->isDigit()) && iter != str.end()) {
|
||||
++iter;
|
||||
}
|
||||
int pos = (int)(begin - str.begin());
|
||||
int len = (int)(iter - begin);
|
||||
|
||||
QStringRef stringRef(const_cast<const QString*>(&str), pos, len);
|
||||
if (stringRef == "true") {
|
||||
return Token(true);
|
||||
} else if (stringRef == "false") {
|
||||
return Token(false);
|
||||
} else {
|
||||
return Token(stringRef);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: not very efficient or accruate, but it's close enough for now.
|
||||
static float computeFractionalPart(int fractionalPart)
|
||||
{
|
||||
float frac = (float)fractionalPart;
|
||||
while (fractionalPart) {
|
||||
fractionalPart /= 10;
|
||||
frac /= 10.0f;
|
||||
}
|
||||
return frac;
|
||||
}
|
||||
|
||||
static float computeFloat(int whole, int fraction) {
|
||||
return (float)whole + computeFractionalPart(fraction);
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeNumber(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->isDigit());
|
||||
auto begin = iter;
|
||||
while (iter->isDigit() && iter != str.end()) {
|
||||
++iter;
|
||||
}
|
||||
|
||||
// parse whole integer part
|
||||
int pos = (int)(begin - str.begin());
|
||||
int len = (int)(iter - begin);
|
||||
QString sub = QStringRef(const_cast<const QString*>(&str), pos, len).toString();
|
||||
int whole = sub.toInt();
|
||||
|
||||
// parse optional fractional part
|
||||
if (iter->unicode() == '.') {
|
||||
iter++;
|
||||
auto begin = iter;
|
||||
while (iter->isDigit() && iter != str.end()) {
|
||||
++iter;
|
||||
}
|
||||
|
||||
int pos = (int)(begin - str.begin());
|
||||
int len = (int)(iter - begin);
|
||||
QString sub = QStringRef(const_cast<const QString*>(&str), pos, len).toString();
|
||||
int fraction = sub.toInt();
|
||||
|
||||
return Token(computeFloat(whole, fraction));
|
||||
|
||||
} else {
|
||||
return Token(whole);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeAnd(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->unicode() == '&');
|
||||
iter++;
|
||||
if (iter->unicode() == '&') {
|
||||
iter++;
|
||||
return Token(Token::And);
|
||||
} else {
|
||||
qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin());
|
||||
return Token(Token::Error);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeOr(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->unicode() == '|');
|
||||
iter++;
|
||||
if (iter->unicode() == '|') {
|
||||
iter++;
|
||||
return Token(Token::Or);
|
||||
} else {
|
||||
qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin());
|
||||
return Token(Token::Error);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeGreaterThan(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->unicode() == '>');
|
||||
iter++;
|
||||
if (iter->unicode() == '=') {
|
||||
iter++;
|
||||
return Token(Token::GreaterThanEqual);
|
||||
} else {
|
||||
return Token(Token::GreaterThan);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeLessThan(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->unicode() == '<');
|
||||
iter++;
|
||||
if (iter->unicode() == '=') {
|
||||
iter++;
|
||||
return Token(Token::LessThanEqual);
|
||||
} else {
|
||||
return Token(Token::LessThan);
|
||||
}
|
||||
}
|
||||
|
||||
AnimExpression::Token AnimExpression::consumeNot(const QString& str, QString::const_iterator& iter) const {
|
||||
assert(iter != str.end());
|
||||
assert(iter->unicode() == '!');
|
||||
iter++;
|
||||
if (iter->unicode() == '=') {
|
||||
iter++;
|
||||
return Token(Token::NotEqual);
|
||||
} else {
|
||||
return Token(Token::Not);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Parser
|
||||
//
|
||||
|
||||
/*
|
||||
Expr → Term Expr'
|
||||
Expr' → '||' Term Expr'
|
||||
| ε
|
||||
Term → Unary Term'
|
||||
Term' → '&&' Unary Term'
|
||||
| ε
|
||||
Unary → '!' Unary
|
||||
| Factor
|
||||
Factor → INT
|
||||
| BOOL
|
||||
| FLOAT
|
||||
| IDENTIFIER
|
||||
| '(' Expr ')'
|
||||
*/
|
||||
|
||||
// Expr → Term 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;
|
||||
}
|
||||
|
||||
// Expr' → '||' Term Expr' | ε
|
||||
bool AnimExpression::parseExprPrime(const QString& str, QString::const_iterator& iter) {
|
||||
auto token = consumeToken(str, iter);
|
||||
if (token.type == Token::Or) {
|
||||
if (!parseTerm(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
if (!parseExprPrime(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
_opCodes.push_back(OpCode {OpCode::Or});
|
||||
return true;
|
||||
} else {
|
||||
unconsumeToken(token);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Term → Unary Term'
|
||||
bool AnimExpression::parseTerm(const QString& str, QString::const_iterator& iter) {
|
||||
if (!parseUnary(str, iter)) {
|
||||
return false;
|
||||
}
|
||||
if (!parseTermPrime(str, iter)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Term' → '&&' Unary Term' | ε
|
||||
bool AnimExpression::parseTermPrime(const QString& str, QString::const_iterator& iter) {
|
||||
auto token = consumeToken(str, iter);
|
||||
if (token.type == Token::And) {
|
||||
if (!parseUnary(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
if (!parseTermPrime(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
_opCodes.push_back(OpCode {OpCode::And});
|
||||
return true;
|
||||
} else {
|
||||
unconsumeToken(token);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Unary → '!' Unary | Factor
|
||||
bool AnimExpression::parseUnary(const QString& str, QString::const_iterator& iter) {
|
||||
|
||||
auto token = consumeToken(str, iter);
|
||||
if (token.type == Token::Not) {
|
||||
if (!parseUnary(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
_opCodes.push_back(OpCode {OpCode::Not});
|
||||
return true;
|
||||
}
|
||||
unconsumeToken(token);
|
||||
|
||||
return parseFactor(str, iter);
|
||||
}
|
||||
|
||||
|
||||
// Factor → INT | BOOL | FLOAT | IDENTIFIER | '(' Expr ')'
|
||||
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});
|
||||
return true;
|
||||
} else if (token.type == Token::Bool) {
|
||||
_opCodes.push_back(OpCode {(bool)token.intVal});
|
||||
return true;
|
||||
} else if (token.type == Token::Float) {
|
||||
_opCodes.push_back(OpCode {token.floatVal});
|
||||
return true;
|
||||
} else if (token.type == Token::Identifier) {
|
||||
_opCodes.push_back(OpCode {token.strVal});
|
||||
return true;
|
||||
} else if (token.type == Token::LeftParen) {
|
||||
if (!parseExpr(str, iter)) {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
auto nextToken = consumeToken(str, iter);
|
||||
if (nextToken.type != Token::RightParen) {
|
||||
unconsumeToken(nextToken);
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
unconsumeToken(token);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Evaluator
|
||||
//
|
||||
|
||||
AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const {
|
||||
std::stack<OpCode> stack;
|
||||
for (auto& opCode : _opCodes) {
|
||||
switch (opCode.type) {
|
||||
case OpCode::Identifier:
|
||||
case OpCode::Int:
|
||||
case OpCode::Float:
|
||||
case OpCode::Bool:
|
||||
stack.push(opCode);
|
||||
break;
|
||||
case OpCode::And: evalAnd(map, stack); break;
|
||||
case OpCode::Or: evalOr(map, stack); break;
|
||||
case OpCode::GreaterThan: evalGreaterThan(map, stack); break;
|
||||
case OpCode::GreaterThanEqual: evalGreaterThanEqual(map, stack); break;
|
||||
case OpCode::LessThan: evalLessThan(map, stack); break;
|
||||
case OpCode::LessThanEqual: evalLessThanEqual(map, stack); break;
|
||||
case OpCode::Equal: evalEqual(map, stack); break;
|
||||
case OpCode::NotEqual: evalNotEqual(map, stack); break;
|
||||
case OpCode::Not: evalNot(map, stack); break;
|
||||
case OpCode::Subtract: evalSubtract(map, stack); break;
|
||||
case OpCode::Add: evalAdd(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::UnaryMinus: evalUnaryMinus(map, stack); break;
|
||||
}
|
||||
}
|
||||
return coerseToValue(map, stack.top());
|
||||
}
|
||||
|
||||
#define POP_BOOL(NAME) \
|
||||
const OpCode& NAME##_temp = stack.top(); \
|
||||
bool NAME = NAME##_temp.coerceBool(map); \
|
||||
stack.pop()
|
||||
|
||||
#define PUSH(EXPR) \
|
||||
stack.push(OpCode {(EXPR)})
|
||||
|
||||
void AnimExpression::evalAnd(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
POP_BOOL(lhs);
|
||||
POP_BOOL(rhs);
|
||||
PUSH(lhs && rhs);
|
||||
}
|
||||
|
||||
void AnimExpression::evalOr(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
POP_BOOL(lhs);
|
||||
POP_BOOL(rhs);
|
||||
PUSH(lhs || rhs);
|
||||
}
|
||||
|
||||
void AnimExpression::evalGreaterThan(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalGreaterThanEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalLessThan(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalLessThanEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalNotEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(false);
|
||||
}
|
||||
|
||||
void AnimExpression::evalNot(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
POP_BOOL(rhs);
|
||||
PUSH(!rhs);
|
||||
}
|
||||
|
||||
void AnimExpression::evalSubtract(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(0.0f);
|
||||
}
|
||||
|
||||
void AnimExpression::add(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::add(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::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 {
|
||||
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 rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH(0.0f);
|
||||
}
|
||||
|
||||
void AnimExpression::evalModulus(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode lhs = stack.top(); stack.pop();
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
// TODO:
|
||||
PUSH((int)0);
|
||||
}
|
||||
|
||||
void AnimExpression::evalUnaryMinus(const AnimVariantMap& map, std::stack<OpCode>& stack) const {
|
||||
OpCode rhs = stack.top(); stack.pop();
|
||||
|
||||
switch (rhs.type) {
|
||||
case OpCode::Identifier: {
|
||||
const AnimVariant& var = map.get(rhs.strVal);
|
||||
switch (var.getType()) {
|
||||
case AnimVariant::Type::Bool:
|
||||
qCWarning(animation) << "AnimExpression: type missmatch for unary minus, expected a number not a bool";
|
||||
// interpret this as boolean not.
|
||||
PUSH(!var.getBool());
|
||||
break;
|
||||
case AnimVariant::Type::Int:
|
||||
PUSH(-var.getInt());
|
||||
break;
|
||||
case AnimVariant::Type::Float:
|
||||
PUSH(-var.getFloat());
|
||||
break;
|
||||
default:
|
||||
// TODO: Vec3, Quat are unsupported
|
||||
assert(false);
|
||||
PUSH(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
case OpCode::Int:
|
||||
PUSH(-rhs.intVal);
|
||||
break;
|
||||
case OpCode::Float:
|
||||
PUSH(-rhs.floatVal);
|
||||
break;
|
||||
case OpCode::Bool:
|
||||
qCWarning(animation) << "AnimExpression: type missmatch for unary minus, expected a number not a bool";
|
||||
// interpret this as boolean not.
|
||||
PUSH(!rhs.coerceBool(map));
|
||||
break;
|
||||
default:
|
||||
qCCritical(animation) << "AnimExpression: ERRROR for unary minus, expected a number, type = " << rhs.type;
|
||||
assert(false);
|
||||
PUSH(false);
|
||||
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:
|
||||
return OpCode((bool)var.getBool());
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
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();
|
||||
}
|
||||
#endif
|
158
libraries/animation/src/AnimExpression.h
Normal file
158
libraries/animation/src/AnimExpression.h
Normal file
|
@ -0,0 +1,158 @@
|
|||
//
|
||||
// AnimExpression.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 11/1/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimExpression
|
||||
#define hifi_AnimExpression
|
||||
|
||||
#include <QString>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#include "AnimVariant.h"
|
||||
|
||||
class AnimExpression {
|
||||
public:
|
||||
friend class AnimTests;
|
||||
AnimExpression(const QString& str);
|
||||
protected:
|
||||
struct Token {
|
||||
enum Type {
|
||||
End = 0,
|
||||
Identifier,
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
And,
|
||||
Or,
|
||||
GreaterThan,
|
||||
GreaterThanEqual,
|
||||
LessThan,
|
||||
LessThanEqual,
|
||||
Equal,
|
||||
NotEqual,
|
||||
LeftParen,
|
||||
RightParen,
|
||||
Not,
|
||||
Minus,
|
||||
Plus,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulus,
|
||||
Comma,
|
||||
Error
|
||||
};
|
||||
Token(Type type) : type {type} {}
|
||||
Token(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {}
|
||||
explicit Token(int val) : type {Type::Int}, intVal {val} {}
|
||||
explicit Token(bool val) : type {Type::Bool}, intVal {val} {}
|
||||
explicit Token(float val) : type {Type::Float}, floatVal {val} {}
|
||||
Type type {End};
|
||||
QString strVal;
|
||||
int intVal {0};
|
||||
float floatVal {0.0f};
|
||||
};
|
||||
|
||||
struct OpCode {
|
||||
enum Type {
|
||||
Identifier,
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
And,
|
||||
Or,
|
||||
GreaterThan,
|
||||
GreaterThanEqual,
|
||||
LessThan,
|
||||
LessThanEqual,
|
||||
Equal,
|
||||
NotEqual,
|
||||
Not,
|
||||
Subtract,
|
||||
Add,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulus,
|
||||
UnaryMinus
|
||||
};
|
||||
OpCode(Type type) : type {type} {}
|
||||
explicit OpCode(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {}
|
||||
explicit OpCode(const QString& str) : type {Type::Identifier}, strVal {str} {}
|
||||
explicit OpCode(int val) : type {Type::Int}, intVal {val} {}
|
||||
explicit OpCode(bool val) : type {Type::Bool}, intVal {(int)val} {}
|
||||
explicit OpCode(float val) : type {Type::Float}, floatVal {val} {}
|
||||
|
||||
bool coerceBool(const AnimVariantMap& map) const {
|
||||
if (type == Int || type == Bool) {
|
||||
return intVal != 0;
|
||||
} else if (type == Identifier) {
|
||||
return map.lookup(strVal, false);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Type type {Int};
|
||||
QString strVal;
|
||||
int intVal {0};
|
||||
float floatVal {0.0f};
|
||||
};
|
||||
|
||||
void unconsumeToken(const Token& token);
|
||||
Token consumeToken(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeIdentifier(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeNumber(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeAnd(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeOr(const QString& str, QString::const_iterator& iter) const;
|
||||
Token consumeGreaterThan(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;
|
||||
|
||||
bool parseExpr(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 parseUnary(const QString& str, QString::const_iterator& iter);
|
||||
bool parseFactor(const QString& str, QString::const_iterator& iter);
|
||||
|
||||
OpCode evaluate(const AnimVariantMap& map) const;
|
||||
void evalAnd(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalOr(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalGreaterThan(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalGreaterThanEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalLessThan(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalLessThanEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalEqual(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
void evalNotEqual(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 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 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 evalUnaryMinus(const AnimVariantMap& map, std::stack<OpCode>& stack) const;
|
||||
|
||||
OpCode coerseToValue(const AnimVariantMap& map, const OpCode& opCode) const;
|
||||
|
||||
QString _expression;
|
||||
mutable std::stack<Token> _tokenStack; // TODO: remove, only needed during parsing
|
||||
std::vector<OpCode> _opCodes;
|
||||
|
||||
#ifndef NDEBUG
|
||||
void dumpOpCodes() const;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -15,6 +15,8 @@
|
|||
#include <RegisteredMetaTypes.h>
|
||||
#include "AnimVariant.h" // which has AnimVariant/AnimVariantMap
|
||||
|
||||
const AnimVariant AnimVariant::False = AnimVariant();
|
||||
|
||||
QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const {
|
||||
if (QThread::currentThread() != engine->thread()) {
|
||||
qCWarning(animation) << "Cannot create Javacript object from non-script thread" << QThread::currentThread();
|
||||
|
|
|
@ -34,6 +34,8 @@ public:
|
|||
NumTypes
|
||||
};
|
||||
|
||||
static const AnimVariant False;
|
||||
|
||||
AnimVariant() : _type(Type::Bool) { memset(&_val, 0, sizeof(_val)); }
|
||||
AnimVariant(bool value) : _type(Type::Bool) { _val.boolVal = value; }
|
||||
AnimVariant(int value) : _type(Type::Int) { _val.intVal = value; }
|
||||
|
@ -57,13 +59,50 @@ public:
|
|||
void setQuat(const glm::quat& value) { assert(_type == Type::Quat); *reinterpret_cast<glm::quat*>(&_val) = value; }
|
||||
void setString(const QString& value) { assert(_type == Type::String); _stringVal = value; }
|
||||
|
||||
bool getBool() const { assert(_type == Type::Bool); return _val.boolVal; }
|
||||
int getInt() const { assert(_type == Type::Int || _type == Type::Float); return _type == Type::Float ? (int)_val.floats[0] : _val.intVal; }
|
||||
float getFloat() const { assert(_type == Type::Float || _type == Type::Int); return _type == Type::Int ? (float)_val.intVal : _val.floats[0]; }
|
||||
|
||||
const glm::vec3& getVec3() const { assert(_type == Type::Vec3); return *reinterpret_cast<const glm::vec3*>(&_val); }
|
||||
const glm::quat& getQuat() const { assert(_type == Type::Quat); return *reinterpret_cast<const glm::quat*>(&_val); }
|
||||
const QString& getString() const { assert(_type == Type::String); return _stringVal; }
|
||||
bool getBool() const {
|
||||
if (_type == Type::Bool) {
|
||||
return _val.boolVal;
|
||||
} else if (_type == Type::Int) {
|
||||
return _val.intVal != 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int getInt() const {
|
||||
if (_type == Type::Int) {
|
||||
return _val.intVal;
|
||||
} else if (_type == Type::Float) {
|
||||
return (int)_val.floats[0];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
float getFloat() const {
|
||||
if (_type == Type::Float) {
|
||||
return _val.floats[0];
|
||||
} else if (_type == Type::Int) {
|
||||
return (float)_val.intVal;
|
||||
} else {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
const glm::vec3& getVec3() const {
|
||||
if (_type == Type::Vec3) {
|
||||
return *reinterpret_cast<const glm::vec3*>(&_val);
|
||||
} else {
|
||||
return Vectors::ZERO;
|
||||
}
|
||||
}
|
||||
const glm::quat& getQuat() const {
|
||||
if (_type == Type::Quat) {
|
||||
return *reinterpret_cast<const glm::quat*>(&_val);
|
||||
} else {
|
||||
return Quaternions::IDENTITY;
|
||||
}
|
||||
}
|
||||
const QString& getString() const {
|
||||
return _stringVal;
|
||||
}
|
||||
|
||||
protected:
|
||||
Type _type;
|
||||
|
@ -71,7 +110,7 @@ protected:
|
|||
union {
|
||||
bool boolVal;
|
||||
int intVal;
|
||||
float floats[16];
|
||||
float floats[4];
|
||||
} _val;
|
||||
};
|
||||
|
||||
|
@ -172,6 +211,15 @@ public:
|
|||
void clearMap() { _map.clear(); }
|
||||
bool hasKey(const QString& key) const { return _map.find(key) != _map.end(); }
|
||||
|
||||
const AnimVariant& get(const QString& key) const {
|
||||
auto iter = _map.find(key);
|
||||
if (iter != _map.end()) {
|
||||
return iter->second;
|
||||
} else {
|
||||
return AnimVariant::False;
|
||||
}
|
||||
}
|
||||
|
||||
// Answer a Plain Old Javascript Object (for the given engine) all of our values set as properties.
|
||||
QScriptValue animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const;
|
||||
// Side-effect us with the value of object's own properties. (No inherited properties.)
|
||||
|
|
|
@ -82,6 +82,7 @@ public:
|
|||
static const vec3& RIGHT;
|
||||
static const vec3& UP;
|
||||
static const vec3& FRONT;
|
||||
static const vec3 ZERO4;
|
||||
};
|
||||
|
||||
// These pack/unpack functions are designed to start specific known types in as efficient a manner
|
||||
|
|
|
@ -274,7 +274,7 @@ struct ByteData {
|
|||
|
||||
QTextStream & operator << (QTextStream& stream, const ByteData & wrapper) {
|
||||
// Print bytes as hex
|
||||
stream << QByteArray::fromRawData(wrapper.data, wrapper.length).toHex();
|
||||
stream << QByteArray::fromRawData(wrapper.data, (int)wrapper.length).toHex();
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
|
|
@ -8,12 +8,13 @@
|
|||
//
|
||||
|
||||
#include "AnimTests.h"
|
||||
#include "AnimNodeLoader.h"
|
||||
#include "AnimClip.h"
|
||||
#include "AnimBlendLinear.h"
|
||||
#include "AnimationLogging.h"
|
||||
#include "AnimVariant.h"
|
||||
#include "AnimUtil.h"
|
||||
#include <AnimNodeLoader.h>
|
||||
#include <AnimClip.h>
|
||||
#include <AnimBlendLinear.h>
|
||||
#include <AnimationLogging.h>
|
||||
#include <AnimVariant.h>
|
||||
#include <AnimExpression.h>
|
||||
#include <AnimUtil.h>
|
||||
|
||||
#include <../QTestExtensions.h>
|
||||
|
||||
|
@ -315,7 +316,6 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram
|
|||
triggers.clear();
|
||||
}
|
||||
|
||||
|
||||
void AnimTests::testAnimPose() {
|
||||
const float PI = (float)M_PI;
|
||||
const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
|
@ -394,3 +394,234 @@ void AnimTests::testAnimPose() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimTests::testExpressionTokenizer() {
|
||||
QString str = "(10 + x) >= 20.1 && (y != !z)";
|
||||
AnimExpression e("x");
|
||||
auto iter = str.cbegin();
|
||||
AnimExpression::Token token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::LeftParen);
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::Int);
|
||||
QVERIFY(token.intVal == 10);
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::Plus);
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::Identifier);
|
||||
QVERIFY(token.strVal == "x");
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::RightParen);
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::GreaterThanEqual);
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::Float);
|
||||
QVERIFY(fabsf(token.floatVal - 20.1f) < 0.0001f);
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::And);
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::LeftParen);
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::Identifier);
|
||||
QVERIFY(token.strVal == "y");
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::NotEqual);
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::Not);
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::Identifier);
|
||||
QVERIFY(token.strVal == "z");
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::RightParen);
|
||||
token = e.consumeToken(str, iter);
|
||||
|
||||
str = "true";
|
||||
iter = str.cbegin();
|
||||
token = e.consumeToken(str, iter);
|
||||
QVERIFY(token.type == AnimExpression::Token::Bool);
|
||||
QVERIFY(token.intVal == (int)true);
|
||||
}
|
||||
|
||||
void AnimTests::testExpressionParser() {
|
||||
|
||||
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 e("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("(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("((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) {
|
||||
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier);
|
||||
QVERIFY(e._opCodes[0].strVal == "twenty");
|
||||
}
|
||||
|
||||
e = AnimExpression("true || false");
|
||||
QVERIFY(e._opCodes.size() == 3);
|
||||
if (e._opCodes.size() == 3) {
|
||||
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[0].intVal == (int)true);
|
||||
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[1].intVal == (int)false);
|
||||
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Or);
|
||||
}
|
||||
|
||||
e = AnimExpression("true || false && true");
|
||||
QVERIFY(e._opCodes.size() == 5);
|
||||
if (e._opCodes.size() == 5) {
|
||||
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[0].intVal == (int)true);
|
||||
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[1].intVal == (int)false);
|
||||
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[2].intVal == (int)true);
|
||||
QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::And);
|
||||
QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::Or);
|
||||
}
|
||||
|
||||
e = AnimExpression("(true || false) && true");
|
||||
QVERIFY(e._opCodes.size() == 5);
|
||||
if (e._opCodes.size() == 5) {
|
||||
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[0].intVal == (int)true);
|
||||
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[1].intVal == (int)false);
|
||||
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Or);
|
||||
QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[3].intVal == (int)true);
|
||||
QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::And);
|
||||
}
|
||||
|
||||
e = AnimExpression("!(true || false) && true");
|
||||
QVERIFY(e._opCodes.size() == 6);
|
||||
if (e._opCodes.size() == 6) {
|
||||
QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[0].intVal == (int)true);
|
||||
QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[1].intVal == (int)false);
|
||||
QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Or);
|
||||
QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::Not);
|
||||
QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::Bool);
|
||||
QVERIFY(e._opCodes[4].intVal == (int)true);
|
||||
QVERIFY(e._opCodes[5].type == AnimExpression::OpCode::And);
|
||||
}
|
||||
}
|
||||
|
||||
#define TEST_BOOL_EXPR(EXPR) \
|
||||
result = AnimExpression( #EXPR ).evaluate(vars); \
|
||||
QVERIFY(result.type == AnimExpression::OpCode::Bool); \
|
||||
QVERIFY(result.intVal == (int)(EXPR))
|
||||
|
||||
void AnimTests::testExpressionEvaluator() {
|
||||
auto vars = AnimVariantMap();
|
||||
|
||||
bool f = false;
|
||||
bool t = true;
|
||||
int ten = 10;
|
||||
int twenty = 20;
|
||||
float five = 5.0f;
|
||||
float fourty = 40.0f;
|
||||
vars.set("f", f);
|
||||
vars.set("t", t);
|
||||
vars.set("ten", ten);
|
||||
vars.set("twenty", twenty);
|
||||
vars.set("five", five);
|
||||
vars.set("forty", fourty);
|
||||
|
||||
AnimExpression::OpCode result(AnimExpression::OpCode::Int);
|
||||
|
||||
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);
|
||||
|
||||
TEST_BOOL_EXPR(true);
|
||||
TEST_BOOL_EXPR(false);
|
||||
TEST_BOOL_EXPR(t);
|
||||
TEST_BOOL_EXPR(f);
|
||||
|
||||
TEST_BOOL_EXPR(true || false);
|
||||
TEST_BOOL_EXPR(true || true);
|
||||
TEST_BOOL_EXPR(false || false);
|
||||
TEST_BOOL_EXPR(false || true);
|
||||
|
||||
TEST_BOOL_EXPR(true && false);
|
||||
TEST_BOOL_EXPR(true && true);
|
||||
TEST_BOOL_EXPR(false && false);
|
||||
TEST_BOOL_EXPR(false && true);
|
||||
|
||||
TEST_BOOL_EXPR(true || false && true);
|
||||
TEST_BOOL_EXPR(true || false && false);
|
||||
TEST_BOOL_EXPR(true || true && true);
|
||||
TEST_BOOL_EXPR(true || true && false);
|
||||
TEST_BOOL_EXPR(false || false && true);
|
||||
TEST_BOOL_EXPR(false || false && false);
|
||||
TEST_BOOL_EXPR(false || true && true);
|
||||
TEST_BOOL_EXPR(false || true && false);
|
||||
|
||||
TEST_BOOL_EXPR(true && false || true);
|
||||
TEST_BOOL_EXPR(true && false || false);
|
||||
TEST_BOOL_EXPR(true && true || true);
|
||||
TEST_BOOL_EXPR(true && true || false);
|
||||
TEST_BOOL_EXPR(false && false || true);
|
||||
TEST_BOOL_EXPR(false && false || false);
|
||||
TEST_BOOL_EXPR(false && true || true);
|
||||
TEST_BOOL_EXPR(false && true || false);
|
||||
|
||||
TEST_BOOL_EXPR(t || false);
|
||||
TEST_BOOL_EXPR(t || true);
|
||||
TEST_BOOL_EXPR(f || false);
|
||||
TEST_BOOL_EXPR(f || true);
|
||||
|
||||
TEST_BOOL_EXPR(!true);
|
||||
TEST_BOOL_EXPR(!false);
|
||||
TEST_BOOL_EXPR(!true || true);
|
||||
|
||||
TEST_BOOL_EXPR(!true && !false || !true);
|
||||
TEST_BOOL_EXPR(!true && !false || true);
|
||||
TEST_BOOL_EXPR(!true && false || !true);
|
||||
TEST_BOOL_EXPR(!true && false || true);
|
||||
TEST_BOOL_EXPR(true && !false || !true);
|
||||
TEST_BOOL_EXPR(true && !false || true);
|
||||
TEST_BOOL_EXPR(true && false || !true);
|
||||
TEST_BOOL_EXPR(true && false || true);
|
||||
|
||||
TEST_BOOL_EXPR(!(true && f) || !t);
|
||||
TEST_BOOL_EXPR(!!!(t) && (!!f || true));
|
||||
TEST_BOOL_EXPR(!(true && f) && true);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,9 @@ private slots:
|
|||
void testVariant();
|
||||
void testAccumulateTime();
|
||||
void testAnimPose();
|
||||
void testExpressionTokenizer();
|
||||
void testExpressionParser();
|
||||
void testExpressionEvaluator();
|
||||
};
|
||||
|
||||
#endif // hifi_AnimTests_h
|
||||
|
|
Loading…
Reference in a new issue