diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp new file mode 100644 index 0000000000..79004a72a6 --- /dev/null +++ b/libraries/animation/src/AnimExpression.cpp @@ -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 +#include + +#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(&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(&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(&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 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& stack) const { + POP_BOOL(lhs); + POP_BOOL(rhs); + PUSH(lhs && rhs); +} + +void AnimExpression::evalOr(const AnimVariantMap& map, std::stack& stack) const { + POP_BOOL(lhs); + POP_BOOL(rhs); + PUSH(lhs || rhs); +} + +void AnimExpression::evalGreaterThan(const AnimVariantMap& map, std::stack& 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& 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& 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& 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& 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& 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& stack) const { + POP_BOOL(rhs); + PUSH(!rhs); +} + +void AnimExpression::evalSubtract(const AnimVariantMap& map, std::stack& 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& 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& 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& 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& 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& 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& 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& 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& 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& 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 diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h new file mode 100644 index 0000000000..468217f5b3 --- /dev/null +++ b/libraries/animation/src/AnimExpression.h @@ -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 +#include +#include +#include +#include +#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& stack) const; + void evalOr(const AnimVariantMap& map, std::stack& stack) const; + void evalGreaterThan(const AnimVariantMap& map, std::stack& stack) const; + void evalGreaterThanEqual(const AnimVariantMap& map, std::stack& stack) const; + void evalLessThan(const AnimVariantMap& map, std::stack& stack) const; + void evalLessThanEqual(const AnimVariantMap& map, std::stack& stack) const; + void evalEqual(const AnimVariantMap& map, std::stack& stack) const; + void evalNotEqual(const AnimVariantMap& map, std::stack& stack) const; + void evalNot(const AnimVariantMap& map, std::stack& stack) const; + void evalSubtract(const AnimVariantMap& map, std::stack& stack) const; + void evalAdd(const AnimVariantMap& map, std::stack& stack) const; + void add(int lhs, const OpCode& rhs, std::stack& stack) const; + void add(float lhs, const OpCode& rhs, std::stack& stack) const; + void evalMultiply(const AnimVariantMap& map, std::stack& stack) const; + void mul(int lhs, const OpCode& rhs, std::stack& stack) const; + void mul(float lhs, const OpCode& rhs, std::stack& stack) const; + void evalDivide(const AnimVariantMap& map, std::stack& stack) const; + void evalModulus(const AnimVariantMap& map, std::stack& stack) const; + void evalUnaryMinus(const AnimVariantMap& map, std::stack& stack) const; + + OpCode coerseToValue(const AnimVariantMap& map, const OpCode& opCode) const; + + QString _expression; + mutable std::stack _tokenStack; // TODO: remove, only needed during parsing + std::vector _opCodes; + +#ifndef NDEBUG + void dumpOpCodes() const; +#endif +}; + +#endif + diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp index d57d1b3c34..911899f5a9 100644 --- a/libraries/animation/src/AnimVariant.cpp +++ b/libraries/animation/src/AnimVariant.cpp @@ -15,6 +15,8 @@ #include #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(); diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 0cd7fb29ac..531e2c4a2d 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -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(&_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(&_val); } - const glm::quat& getQuat() const { assert(_type == Type::Quat); return *reinterpret_cast(&_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(&_val); + } else { + return Vectors::ZERO; + } + } + const glm::quat& getQuat() const { + if (_type == Type::Quat) { + return *reinterpret_cast(&_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.) diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 25ded54e93..55458e98a2 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -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 diff --git a/tests/QTestExtensions.h b/tests/QTestExtensions.h index 16e51b41ee..b7b9795a9a 100644 --- a/tests/QTestExtensions.h +++ b/tests/QTestExtensions.h @@ -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; } diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 64db3f6154..6812bb63b6 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -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 +#include +#include +#include +#include +#include +#include #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); +} + + diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index a07217b91a..439793f21d 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -27,6 +27,9 @@ private slots: void testVariant(); void testAccumulateTime(); void testAnimPose(); + void testExpressionTokenizer(); + void testExpressionParser(); + void testExpressionEvaluator(); }; #endif // hifi_AnimTests_h