From 340096d45735da353de600f082ba8cd906eb08cb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sun, 1 Nov 2015 15:16:00 -0800 Subject: [PATCH] Initial version of AnimExpression class with minimal tokenizer --- libraries/animation/src/AnimExpression.cpp | 228 +++++++++++++++++++++ libraries/animation/src/AnimExpression.h | 78 +++++++ tests/animation/src/AnimTests.cpp | 53 ++++- tests/animation/src/AnimTests.h | 1 + 4 files changed, 354 insertions(+), 6 deletions(-) create mode 100644 libraries/animation/src/AnimExpression.cpp create mode 100644 libraries/animation/src/AnimExpression.h diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp new file mode 100644 index 0000000000..f2ec0426be --- /dev/null +++ b/libraries/animation/src/AnimExpression.cpp @@ -0,0 +1,228 @@ +// +// 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" + + +#ifndef NDEBUG +void AnimExpression::Token::dump() { + switch (type) { + case End: + qCDebug(animation) << " End"; + break; + case Identifier: + qCDebug(animation) << " Identifer =" << strVal; + break; + case LiteralInt: + qCDebug(animation) << " LiteralInt =" << intVal; + break; + case LiteralFloat: + qCDebug(animation) << " LiteralFloat" << floatVal; + break; + case LiteralVec3: + qCDebug(animation) << " LiteralVec3" << vec3Val; + break; + case LiteralVec4: + qCDebug(animation) << " LiteralVec4" << vec4Val; + break; + case LiteralQuat: + qCDebug(animation) << " LiteralQuat" << quatVal; + break; + case And: + qCDebug(animation) << " And"; + break; + case Or: + qCDebug(animation) << " Or"; + break; + case GreaterThan: + qCDebug(animation) << " GreaterThan"; + break; + case GreaterThanEqual: + qCDebug(animation) << " GreaterThanEqual"; + break; + case LessThan: + qCDebug(animation) << " LessThan"; + break; + case LessThanEqual: + qCDebug(animation) << " LessThanEqual"; + break; + case Equal: + qCDebug(animation) << " Equal"; + break; + case NotEqual: + qCDebug(animation) << " NotEqual"; + break; + case LeftParen: + qCDebug(animation) << " LeftParen"; + break; + case RightParen: + qCDebug(animation) << " RightParen"; + break; + case Not: + qCDebug(animation) << " Not"; + break; + case Minus: + qCDebug(animation) << " Minus"; + break; + case Plus: + qCDebug(animation) << " Plus"; + break; + case Multiply: + qCDebug(animation) << " Multiply"; + break; + case Modulus: + qCDebug(animation) << " Modulus"; + break; + case Error: + qCDebug(animation) << " Error"; + break; + } +} +#endif + +AnimExpression::AnimExpression(const QString& str) : + _expression(str) { + parseExpression(_expression); +} + +bool AnimExpression::parseExpression(const QString& str) { + Token token(Token::End); + auto iter = str.begin(); + do { + token = consumeToken(str, iter); + switch(token.type) { + case Token::Error: + case Token::End: + return false; + } + } while(iter != str.end()); +} + +AnimExpression::Token AnimExpression::consumeToken(const QString& str, QString::const_iterator& iter) const { + 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::Modulus); + 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 != str.end()) { + ++iter; + } + int pos = (int)(begin - str.begin()); + int len = (int)(iter - begin); + return Token(QStringRef(const_cast(&str), pos, len)); +} + +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; + } + int pos = (int)(begin - str.begin()); + int len = (int)(iter - begin); + QString sub = QStringRef(const_cast(&str), pos, len).toString(); + return Token(sub.toInt()); +} + +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); + } +} + diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h new file mode 100644 index 0000000000..a69b13c286 --- /dev/null +++ b/libraries/animation/src/AnimExpression.h @@ -0,0 +1,78 @@ +// +// 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 + +class AnimExpression { +public: + friend class AnimTests; + AnimExpression(const QString& str); +protected: + struct Token { + enum Type { + End = 0, + Identifier, + LiteralInt, + LiteralFloat, + LiteralVec3, + LiteralVec4, + LiteralQuat, + And, + Or, + GreaterThan, + GreaterThanEqual, + LessThan, + LessThanEqual, + Equal, + NotEqual, + LeftParen, + RightParen, + Not, + Minus, + Plus, + Multiply, + Modulus, + Error + }; + Token(Type type) : type(type) {} + Token(const QStringRef& strRef) : type(Type::Identifier), strVal(strRef.toString()) {} + Token(int val) : type(Type::LiteralInt), intVal(val) {} + Type type = End; + QString strVal; + int intVal; + float floatVal; + glm::vec3 vec3Val; + glm::vec4 vec4Val; + glm::quat quatVal; +#ifndef NDEBUG + void dump(); +#endif + }; + bool parseExpression(const QString& str); + 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; + + QString _expression; +}; + +#endif + diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 1b5bb4739a..08f13b7ec7 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> @@ -322,4 +323,44 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram QVERIFY(resultFrame == startFrame + 0.5f); QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop"); triggers.clear(); + +void AnimTests::testTokenizer() { + QString str = "(10 + x) >= 20 && (y != !z)"; + AnimExpression e(""); + 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::LiteralInt); + 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::LiteralInt); + QVERIFY(token.intVal == 20); + 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); } + diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index 7bd05369c7..bfea4eb086 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -26,6 +26,7 @@ private slots: void testLoader(); void testVariant(); void testAccumulateTime(); + void testTokenizer(); }; #endif // hifi_AnimTests_h