From 340096d45735da353de600f082ba8cd906eb08cb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sun, 1 Nov 2015 15:16:00 -0800 Subject: [PATCH 01/11] 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 From 7f0fc4f6eb5bc9a4f28f55c6641c516e32cddd81 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sun, 1 Nov 2015 15:51:57 -0800 Subject: [PATCH 02/11] Added limited floating point support --- libraries/animation/src/AnimExpression.cpp | 38 +++++++++++++++++++++- libraries/animation/src/AnimExpression.h | 1 + tests/animation/src/AnimTests.cpp | 6 ++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp index f2ec0426be..14b3e9da87 100644 --- a/libraries/animation/src/AnimExpression.cpp +++ b/libraries/animation/src/AnimExpression.cpp @@ -151,6 +151,21 @@ AnimExpression::Token AnimExpression::consumeIdentifier(const QString& str, QStr return Token(QStringRef(const_cast(&str), pos, len)); } +// 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()); @@ -158,10 +173,31 @@ AnimExpression::Token AnimExpression::consumeNumber(const QString& str, QString: 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(); - return Token(sub.toInt()); + 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 { diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index a69b13c286..25e1803721 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -50,6 +50,7 @@ protected: Token(Type type) : type(type) {} Token(const QStringRef& strRef) : type(Type::Identifier), strVal(strRef.toString()) {} Token(int val) : type(Type::LiteralInt), intVal(val) {} + Token(float val) : type(Type::LiteralFloat), floatVal(val) {} Type type = End; QString strVal; int intVal; diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 08f13b7ec7..5b6806ec09 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -325,7 +325,7 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram triggers.clear(); void AnimTests::testTokenizer() { - QString str = "(10 + x) >= 20 && (y != !z)"; + QString str = "(10 + x) >= 20.1 && (y != !z)"; AnimExpression e(""); auto iter = str.cbegin(); AnimExpression::Token token = e.consumeToken(str, iter); @@ -343,8 +343,8 @@ void AnimTests::testTokenizer() { 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); + QVERIFY(token.type == AnimExpression::Token::LiteralFloat); + QVERIFY(fabsf(token.floatVal - 20.1f) < 0.0001f); token = e.consumeToken(str, iter); QVERIFY(token.type == AnimExpression::Token::And); token = e.consumeToken(str, iter); From 4394083138744a47fd8f2a14c46e1fd008b38772 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sun, 1 Nov 2015 15:57:20 -0800 Subject: [PATCH 03/11] Added comma token --- libraries/animation/src/AnimExpression.cpp | 79 +--------------------- libraries/animation/src/AnimExpression.h | 4 +- 2 files changed, 2 insertions(+), 81 deletions(-) diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp index 14b3e9da87..fe056be81d 100644 --- a/libraries/animation/src/AnimExpression.cpp +++ b/libraries/animation/src/AnimExpression.cpp @@ -14,83 +14,6 @@ #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); @@ -130,6 +53,7 @@ AnimExpression::Token AnimExpression::consumeToken(const QString& str, QString:: case '+': ++iter; return Token(Token::Plus); case '*': ++iter; return Token(Token::Multiply); 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); @@ -261,4 +185,3 @@ AnimExpression::Token AnimExpression::consumeNot(const QString& str, QString::co return Token(Token::Not); } } - diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index 25e1803721..7e6c42f08a 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -45,6 +45,7 @@ protected: Plus, Multiply, Modulus, + Comma, Error }; Token(Type type) : type(type) {} @@ -58,9 +59,6 @@ protected: 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; From 32c40d37c02694326abfd784010fc4c328764251 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Sun, 1 Nov 2015 16:01:29 -0800 Subject: [PATCH 04/11] Removed vec literals tokens and renamed int and float token types --- libraries/animation/src/AnimExpression.h | 11 ++++------- tests/animation/src/AnimTests.cpp | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index 7e6c42f08a..7ba099ce2e 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -25,11 +25,8 @@ protected: enum Type { End = 0, Identifier, - LiteralInt, - LiteralFloat, - LiteralVec3, - LiteralVec4, - LiteralQuat, + Int, + Float, And, Or, GreaterThan, @@ -50,8 +47,8 @@ protected: }; Token(Type type) : type(type) {} Token(const QStringRef& strRef) : type(Type::Identifier), strVal(strRef.toString()) {} - Token(int val) : type(Type::LiteralInt), intVal(val) {} - Token(float val) : type(Type::LiteralFloat), floatVal(val) {} + Token(int val) : type(Type::Int), intVal(val) {} + Token(float val) : type(Type::Float), floatVal(val) {} Type type = End; QString strVal; int intVal; diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 5b6806ec09..44f9c05c22 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -331,7 +331,7 @@ void AnimTests::testTokenizer() { 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.type == AnimExpression::Token::Int); QVERIFY(token.intVal == 10); token = e.consumeToken(str, iter); QVERIFY(token.type == AnimExpression::Token::Plus); @@ -343,7 +343,7 @@ void AnimTests::testTokenizer() { token = e.consumeToken(str, iter); QVERIFY(token.type == AnimExpression::Token::GreaterThanEqual); token = e.consumeToken(str, iter); - QVERIFY(token.type == AnimExpression::Token::LiteralFloat); + 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); From a80ab0003c424e3c85ef36424a58e14f08f9b4c1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 2 Nov 2015 20:46:29 -0800 Subject: [PATCH 05/11] Removed vec literal values --- libraries/animation/src/AnimExpression.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index 7ba099ce2e..145350547b 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -12,7 +12,6 @@ #define hifi_AnimExpression #include -#include #include #include @@ -53,11 +52,7 @@ protected: QString strVal; int intVal; float floatVal; - glm::vec3 vec3Val; - glm::vec4 vec4Val; - glm::quat quatVal; }; - 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; @@ -67,6 +62,8 @@ protected: Token consumeLessThan(const QString& str, QString::const_iterator& iter) const; Token consumeNot(const QString& str, QString::const_iterator& iter) const; + bool parseExpression(const QString& str); + QString _expression; }; From 61d1dd00d1d1d949887f230a84ea1d31518ad334 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 4 Nov 2015 12:04:34 -0800 Subject: [PATCH 06/11] Fixes for Mac build and crash on startup For some reason the qt code gen is getting confused by the #if 0 in 3DConnextionClient.h, so I added a stub implementation. In Application.cpp the change of moving idle into paintGL had a sideeffect of calling OffscreenGlCanvas before it was initialized. To work around this I added a guard to prevent calling idle before all the gl windows/widgets have been initialized. (cherry picked from commit 69f1cfbcb9125aa9d449a5d01b3734bcd51309e8) --- interface/src/Application.cpp | 12 ++++++------ interface/src/Application.h | 3 ++- interface/src/devices/3DConnexionClient.h | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d31d9de4a0..556664ec10 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -940,14 +940,12 @@ void Application::initializeGL() { qCDebug(interfaceapp) << "Created Display Window."; // initialize glut for shape drawing; Qt apparently initializes it on OS X - #ifndef __APPLE__ - static bool isInitialized = false; - if (isInitialized) { + if (_isGLInitialized) { return; } else { - isInitialized = true; + _isGLInitialized = true; } - #endif + // Where the gpuContext is initialized and where the TRUE Backend is created and assigned gpu::Context::init(); _gpuContext = std::make_shared(); @@ -1059,7 +1057,9 @@ void Application::paintGL() { _lastFramesPerSecondUpdate = now; } - idle(now); + if (_isGLInitialized) { + idle(now); + } PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("paintGL"); diff --git a/interface/src/Application.h b/interface/src/Application.h index 212687c11e..5a5d5a015c 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -414,7 +414,7 @@ private: bool _dependencyManagerIsSetup; - OffscreenGlCanvas* _offscreenContext; + OffscreenGlCanvas* _offscreenContext {nullptr}; DisplayPluginPointer _displayPlugin; InputPluginList _activeInputPlugins; @@ -548,6 +548,7 @@ private: quint64 _lastSimsPerSecondUpdate = 0; bool _isForeground = true; // starts out assumed to be in foreground bool _inPaint = false; + bool _isGLInitialized {false}; }; #endif // hifi_Application_h diff --git a/interface/src/devices/3DConnexionClient.h b/interface/src/devices/3DConnexionClient.h index 03a43d4c64..b6fa6a37c3 100755 --- a/interface/src/devices/3DConnexionClient.h +++ b/interface/src/devices/3DConnexionClient.h @@ -220,4 +220,19 @@ public: #endif +#include +#include + +// stub +class ConnexionClient : public QObject { + Q_OBJECT +public: + static ConnexionClient& getInstance(); + void init() {}; + void destroy() {}; + bool Is3dmouseAttached() { return false; }; +public slots: + void toggleConnexion(bool shouldEnable) {}; +}; + #endif // defined(hifi_3DConnexionClient_h) From 04d8a598da5d8f2ee684619cfb7492349853fdcc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 4 Nov 2015 16:56:34 -0800 Subject: [PATCH 07/11] First step toward evaluation * added OpCodes * added first parser rules * removed mat4 support from AnimVariantMap --- libraries/animation/src/AnimExpression.cpp | 164 ++++++++++++++++----- libraries/animation/src/AnimExpression.h | 75 +++++++++- libraries/animation/src/AnimVariant.h | 74 ++++++---- libraries/shared/src/GLMHelpers.h | 1 + tests/animation/src/AnimTests.cpp | 34 +++-- tests/animation/src/AnimTests.h | 3 +- 6 files changed, 265 insertions(+), 86 deletions(-) diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp index fe056be81d..8807262028 100644 --- a/libraries/animation/src/AnimExpression.cpp +++ b/libraries/animation/src/AnimExpression.cpp @@ -16,58 +16,56 @@ AnimExpression::AnimExpression(const QString& str) : _expression(str) { - parseExpression(_expression); + auto iter = str.begin(); + parseExpression(_expression, iter); } -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()); +void AnimExpression::unconsumeToken(const Token& token) { + _tokenStack.push(token); } 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); - case ',': ++iter; return Token(Token::Comma); - default: - qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin()); - return Token(Token::Error); + 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::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); } - 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()) { + while ((iter->isLetter() || iter->isDigit()) && iter != str.end()) { ++iter; } int pos = (int)(begin - str.begin()); @@ -185,3 +183,93 @@ AnimExpression::Token AnimExpression::consumeNot(const QString& str, QString::co return Token(Token::Not); } } + +bool AnimExpression::parseExpression(const QString& str, QString::const_iterator& iter) { + auto token = consumeToken(str, iter); + if (token.type == Token::Identifier) { + if (token.strVal == "true") { + _opCodes.push_back(OpCode {true}); + } else if (token.strVal == "false") { + _opCodes.push_back(OpCode {false}); + } else { + _opCodes.push_back(OpCode {token.strVal}); + } + return true; + } else if (token.type == Token::Int) { + _opCodes.push_back(OpCode {token.intVal}); + return true; + } else if (token.type == Token::Float) { + _opCodes.push_back(OpCode {token.floatVal}); + return true; + } else if (token.type == Token::LeftParen) { + if (parseUnaryExpression(str, iter)) { + token = consumeToken(str, iter); + if (token.type != Token::RightParen) { + qCCritical(animation) << "Error parsing expression, expected ')'"; + return false; + } else { + return true; + } + } else { + return false; + } + } else { + qCCritical(animation) << "Error parsing expression, unexpected symbol"; + return false; + } +} + +bool AnimExpression::parseUnaryExpression(const QString& str, QString::const_iterator& iter) { + auto token = consumeToken(str, iter); + if (token.type == Token::Plus) { + if (parseExpression(str, iter)) { + _opCodes.push_back(OpCode {OpCode::UnaryPlus}); + return true; + } else { + return false; + } + } else if (token.type == Token::Minus) { + if (parseExpression(str, iter)) { + _opCodes.push_back(OpCode {OpCode::UnaryMinus}); + 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 { + std::stack stack; + for (auto& opCode : _opCodes) { + switch (opCode.type) { + case OpCode::Identifier: + case OpCode::Int: + case OpCode::Float: + stack.push(opCode); + break; + default: + switch (opCode.type) { + case OpCode::Not: + evalNot(map, stack); + break; + } + } + } + return stack.top(); +} + +void AnimExpression::evalNot(const AnimVariantMap& map, std::stack& stack) const { + bool lhs = stack.top().coerceBool(map); + stack.pop(); + stack.push(OpCode {!lhs}); +} diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index 145350547b..8d216ca412 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include "AnimVariant.h" class AnimExpression { public: @@ -44,15 +47,64 @@ protected: Comma, Error }; - Token(Type type) : type(type) {} - Token(const QStringRef& strRef) : type(Type::Identifier), strVal(strRef.toString()) {} - Token(int val) : type(Type::Int), intVal(val) {} - Token(float val) : type(Type::Float), floatVal(val) {} - Type type = End; + Token(Type type) : type {type} {} + Token(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {} + Token(int val) : type {Type::Int}, intVal {val} {} + Token(float val) : type {Type::Float}, floatVal {val} {} + Type type {End}; QString strVal; - int intVal; - float floatVal; + int intVal {0}; + float floatVal {0.0f}; }; + + struct OpCode { + enum Type { + Identifier, + Bool, + Int, + Float, + And, + Or, + GreaterThan, + GreaterThanEqual, + LessThan, + LessThanEqual, + Equal, + NotEqual, + LeftParen, + RightParen, + Not, + Minus, + Plus, + Multiply, + Modulus, + UnaryPlus, + UnaryMinus + }; + OpCode(Type type) : type {type} {} + OpCode(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {} + OpCode(const QString& str) : type {Type::Identifier}, strVal {str} {} + OpCode(int val) : type {Type::Int}, intVal {val} {} + OpCode(bool val) : type {Type::Bool}, intVal {(int)val} {} + 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; @@ -62,9 +114,16 @@ protected: Token consumeLessThan(const QString& str, QString::const_iterator& iter) const; Token consumeNot(const QString& str, QString::const_iterator& iter) const; - bool parseExpression(const QString& str); + bool parseExpression(const QString& str, QString::const_iterator& iter); + bool parseUnaryExpression(const QString& str, QString::const_iterator& iter); + + OpCode evaluate(const AnimVariantMap& map) const; + void evalNot(const AnimVariantMap& map, std::stack& stack) const; QString _expression; + mutable std::stack _tokenStack; + std::vector _opCodes; + }; #endif diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 0d7c657058..ff7794a16a 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -18,8 +18,9 @@ #include #include #include +#include +#include #include "AnimationLogging.h" -#include "StreamUtils.h" class AnimVariant { public: @@ -29,7 +30,6 @@ public: Float, Vec3, Quat, - Mat4, String, NumTypes }; @@ -40,7 +40,6 @@ public: AnimVariant(float value) : _type(Type::Float) { _val.floats[0] = value; } AnimVariant(const glm::vec3& value) : _type(Type::Vec3) { *reinterpret_cast(&_val) = value; } AnimVariant(const glm::quat& value) : _type(Type::Quat) { *reinterpret_cast(&_val) = value; } - AnimVariant(const glm::mat4& value) : _type(Type::Mat4) { *reinterpret_cast(&_val) = value; } AnimVariant(const QString& value) : _type(Type::String) { _stringVal = value; } bool isBool() const { return _type == Type::Bool; } @@ -48,7 +47,6 @@ public: bool isFloat() const { return _type == Type::Float; } bool isVec3() const { return _type == Type::Vec3; } bool isQuat() const { return _type == Type::Quat; } - bool isMat4() const { return _type == Type::Mat4; } bool isString() const { return _type == Type::String; } Type getType() const { return _type; } @@ -57,17 +55,52 @@ public: void setFloat(float value) { assert(_type == Type::Float); _val.floats[0] = value; } void setVec3(const glm::vec3& value) { assert(_type == Type::Vec3); *reinterpret_cast(&_val) = value; } void setQuat(const glm::quat& value) { assert(_type == Type::Quat); *reinterpret_cast(&_val) = value; } - void setMat4(const glm::mat4& value) { assert(_type == Type::Mat4); *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 glm::mat4& getMat4() const { assert(_type == Type::Mat4); 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; @@ -75,7 +108,7 @@ protected: union { bool boolVal; int intVal; - float floats[16]; + float floats[4]; } _val; }; @@ -130,15 +163,6 @@ public: } } - const glm::mat4& lookup(const QString& key, const glm::mat4& defaultValue) const { - if (key.isEmpty()) { - return defaultValue; - } else { - auto iter = _map.find(key); - return iter != _map.end() ? iter->second.getMat4() : defaultValue; - } - } - const QString& lookup(const QString& key, const QString& defaultValue) const { if (key.isEmpty()) { return defaultValue; @@ -153,7 +177,6 @@ public: void set(const QString& key, float value) { _map[key] = AnimVariant(value); } void set(const QString& key, const glm::vec3& value) { _map[key] = AnimVariant(value); } void set(const QString& key, const glm::quat& value) { _map[key] = AnimVariant(value); } - void set(const QString& key, const glm::mat4& value) { _map[key] = AnimVariant(value); } void set(const QString& key, const QString& value) { _map[key] = AnimVariant(value); } void unset(const QString& key) { _map.erase(key); } @@ -189,9 +212,6 @@ public: case AnimVariant::Type::Quat: qCDebug(animation) << " " << pair.first << "=" << pair.second.getQuat(); break; - case AnimVariant::Type::Mat4: - qCDebug(animation) << " " << pair.first << "=" << pair.second.getMat4(); - break; case AnimVariant::Type::String: qCDebug(animation) << " " << pair.first << "=" << pair.second.getString(); break; diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 9c1bbe23a4..e69d11629e 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -80,6 +80,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/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 44f9c05c22..94caed66f5 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -195,10 +195,7 @@ void AnimTests::testVariant() { auto floatVarNegative = AnimVariant(-1.0f); auto vec3Var = AnimVariant(glm::vec3(1.0f, -2.0f, 3.0f)); auto quatVar = AnimVariant(glm::quat(1.0f, 2.0f, -3.0f, 4.0f)); - auto mat4Var = AnimVariant(glm::mat4(glm::vec4(1.0f, 2.0f, 3.0f, 4.0f), - glm::vec4(5.0f, 6.0f, -7.0f, 8.0f), - glm::vec4(9.0f, 10.0f, 11.0f, 12.0f), - glm::vec4(13.0f, 14.0f, 15.0f, 16.0f))); + QVERIFY(defaultVar.isBool()); QVERIFY(defaultVar.getBool() == false); @@ -233,12 +230,6 @@ void AnimTests::testVariant() { QVERIFY(q.x == 2.0f); QVERIFY(q.y == -3.0f); QVERIFY(q.z == 4.0f); - - QVERIFY(mat4Var.isMat4()); - auto m = mat4Var.getMat4(); - QVERIFY(m[0].x == 1.0f); - QVERIFY(m[1].z == -7.0f); - QVERIFY(m[3].w == 16.0f); } void AnimTests::testAccumulateTime() { @@ -323,10 +314,11 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram QVERIFY(resultFrame == startFrame + 0.5f); QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop"); triggers.clear(); +} -void AnimTests::testTokenizer() { +void AnimTests::testExpressionTokenizer() { QString str = "(10 + x) >= 20.1 && (y != !z)"; - AnimExpression e(""); + AnimExpression e("x"); auto iter = str.cbegin(); AnimExpression::Token token = e.consumeToken(str, iter); QVERIFY(token.type == AnimExpression::Token::LeftParen); @@ -364,3 +356,21 @@ void AnimTests::testTokenizer() { token = e.consumeToken(str, iter); } +void AnimTests::testExpressionParser() { + QString str = "(!x)"; + AnimExpression e(str); + QVERIFY(e._opCodes.size() == 2); + if (e._opCodes.size() == 2) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier); + QVERIFY(e._opCodes[0].strVal == "x"); + QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Not); + } + + auto vars = AnimVariantMap(); + vars.set("x", false); + + auto opCode = e.evaluate(vars); + QVERIFY(opCode.type == AnimExpression::OpCode::Bool); + QVERIFY(opCode.coerceBool(vars) == true); +} + diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index bfea4eb086..94b3eddd25 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -26,7 +26,8 @@ private slots: void testLoader(); void testVariant(); void testAccumulateTime(); - void testTokenizer(); + void testExpressionTokenizer(); + void testExpressionParser(); }; #endif // hifi_AnimTests_h From 431a108c351431345d9aab4144ef55f80de84ed3 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 4 Nov 2015 20:13:17 -0800 Subject: [PATCH 08/11] Bugfixes to expression for !!x expressions Added stub eval methods. only boolean not, boolean and, boolean or and unary minus are implemented. --- libraries/animation/src/AnimExpression.cpp | 186 +++++++++++++++++++-- libraries/animation/src/AnimExpression.h | 25 ++- libraries/animation/src/AnimVariant.cpp | 2 + libraries/animation/src/AnimVariant.h | 11 ++ tests/animation/src/AnimTests.cpp | 87 +++++++++- 5 files changed, 280 insertions(+), 31 deletions(-) diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp index 8807262028..a03925f8f9 100644 --- a/libraries/animation/src/AnimExpression.cpp +++ b/libraries/animation/src/AnimExpression.cpp @@ -214,23 +214,27 @@ bool AnimExpression::parseExpression(const QString& str, QString::const_iterator return false; } } else { - qCCritical(animation) << "Error parsing expression, unexpected symbol"; - return false; + unconsumeToken(token); + if (parseUnaryExpression(str, iter)) { + 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); - if (token.type == Token::Plus) { + if (token.type == Token::Plus) { // unary plus is a no op. if (parseExpression(str, iter)) { - _opCodes.push_back(OpCode {OpCode::UnaryPlus}); return true; } else { return false; } } else if (token.type == Token::Minus) { if (parseExpression(str, iter)) { - _opCodes.push_back(OpCode {OpCode::UnaryMinus}); + _opCodes.push_back(OpCode {OpCode::Minus}); return true; } else { return false; @@ -255,21 +259,173 @@ AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const case OpCode::Identifier: case OpCode::Int: case OpCode::Float: + case OpCode::Bool: stack.push(opCode); break; - default: - switch (opCode.type) { - case OpCode::Not: - evalNot(map, stack); - 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::Modulus: evalModulus(map, stack); break; + case OpCode::Minus: evalMinus(map, stack); break; } } return stack.top(); } -void AnimExpression::evalNot(const AnimVariantMap& map, std::stack& stack) const { - bool lhs = stack.top().coerceBool(map); - stack.pop(); - stack.push(OpCode {!lhs}); +#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::evalAdd(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::evalMultiply(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::evalMinus(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; + } } diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index 8d216ca412..afada44e86 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -71,15 +71,12 @@ protected: LessThanEqual, Equal, NotEqual, - LeftParen, - RightParen, Not, - Minus, - Plus, + Subtract, + Add, Multiply, Modulus, - UnaryPlus, - UnaryMinus + Minus }; OpCode(Type type) : type {type} {} OpCode(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {} @@ -118,12 +115,24 @@ protected: bool parseUnaryExpression(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 evalMultiply(const AnimVariantMap& map, std::stack& stack) const; + void evalModulus(const AnimVariantMap& map, std::stack& stack) const; + void evalMinus(const AnimVariantMap& map, std::stack& stack) const; QString _expression; - mutable std::stack _tokenStack; + mutable std::stack _tokenStack; // TODO: remove, only needed during parsing std::vector _opCodes; - }; #endif diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp index 234e9cef09..cae6dce23d 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 ff7794a16a..b15b25f4e0 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; } @@ -186,6 +188,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/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 94caed66f5..58fd3a1d35 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -357,20 +357,91 @@ void AnimTests::testExpressionTokenizer() { } void AnimTests::testExpressionParser() { - QString str = "(!x)"; - AnimExpression e(str); + + 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("(!f)"); QVERIFY(e._opCodes.size() == 2); if (e._opCodes.size() == 2) { QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier); - QVERIFY(e._opCodes[0].strVal == "x"); + 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); } - auto vars = AnimVariantMap(); - vars.set("x", false); + 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.coerceBool(vars) == true); + 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) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int); + 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"); + QVERIFY(e._opCodes.size() == 2); + if (e._opCodes.size() == 1) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier); + QVERIFY(e._opCodes[0].strVal == "ten"); + QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Minus); + + auto opCode = e.evaluate(vars); + QVERIFY(opCode.type == AnimExpression::OpCode::Int); + QVERIFY(opCode.intVal == 10); + } } From 99223d0a3c8848fbef5d8ce16f834747c8efe2e5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 3 Dec 2015 15:02:00 -0800 Subject: [PATCH 09/11] AnimExpression: support for parsing simple expressions supports parens, binary +, -, / and *. / and * have higher precedence then + and - --- libraries/animation/src/AnimExpression.cpp | 349 +++++++++++++++++---- libraries/animation/src/AnimExpression.h | 24 +- tests/animation/src/AnimTests.cpp | 174 ++++++---- tests/animation/src/AnimTests.h | 1 + 4 files changed, 417 insertions(+), 131 deletions(-) diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp index a03925f8f9..b76aaff3f9 100644 --- a/libraries/animation/src/AnimExpression.cpp +++ b/libraries/animation/src/AnimExpression.cpp @@ -17,9 +17,16 @@ AnimExpression::AnimExpression(const QString& str) : _expression(str) { auto iter = str.begin(); - parseExpression(_expression, iter); + parseExpr(_expression, iter); + while(!_tokenStack.empty()) { + _tokenStack.pop(); + } } +// +// Tokenizer +// + void AnimExpression::unconsumeToken(const Token& 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::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: @@ -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); - if (token.type == Token::Identifier) { - if (token.strVal == "true") { - _opCodes.push_back(OpCode {true}); - } else if (token.strVal == "false") { - _opCodes.push_back(OpCode {false}); - } else { - _opCodes.push_back(OpCode {token.strVal}); + if (token.type == Token::Plus) { + if (!parseTerm(str, iter)) { + unconsumeToken(token); + return false; } + if (!parseExprPrime(str, iter)) { + unconsumeToken(token); + return false; + } + _opCodes.push_back(OpCode {OpCode::Add}); 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}); 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 (parseUnaryExpression(str, iter)) { - token = consumeToken(str, iter); - if (token.type != Token::RightParen) { - qCCritical(animation) << "Error parsing expression, expected ')'"; - return false; - } else { - return true; - } - } else { + 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); - if (parseUnaryExpression(str, iter)) { - return true; - } else { - qCCritical(animation) << "Error parsing expression"; - return false; - } + return false; } } -bool AnimExpression::parseUnaryExpression(const QString& str, QString::const_iterator& iter) { - auto token = consumeToken(str, iter); - 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); - } -} +// +// Evaluator +// AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const { std::stack stack; @@ -274,8 +346,9 @@ AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const 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::Minus: evalMinus(map, stack); break; + case OpCode::UnaryMinus: evalUnaryMinus(map, stack); break; } } return stack.top(); @@ -362,15 +435,107 @@ void AnimExpression::evalSubtract(const AnimVariantMap& map, std::stack& PUSH(0.0f); } -void AnimExpression::evalAdd(const AnimVariantMap& map, std::stack& stack) const { - OpCode lhs = stack.top(); stack.pop(); - OpCode rhs = stack.top(); stack.pop(); +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); + } +} - // TODO: - PUSH(0.0f); +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(); @@ -386,7 +551,7 @@ void AnimExpression::evalModulus(const AnimVariantMap& map, std::stack& PUSH((int)0); } -void AnimExpression::evalMinus(const AnimVariantMap& map, std::stack& stack) const { +void AnimExpression::evalUnaryMinus(const AnimVariantMap& map, std::stack& stack) const { OpCode rhs = stack.top(); stack.pop(); switch (rhs.type) { @@ -429,3 +594,69 @@ void AnimExpression::evalMinus(const AnimVariantMap& map, std::stack& st 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(); +} diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index afada44e86..6e204483d5 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -43,6 +43,7 @@ protected: Minus, Plus, Multiply, + Divide, Modulus, Comma, Error @@ -75,8 +76,9 @@ protected: Subtract, Add, Multiply, + Divide, Modulus, - Minus + UnaryMinus }; OpCode(Type type) : type {type} {} 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 consumeNot(const QString& str, QString::const_iterator& iter) const; - bool parseExpression(const QString& str, QString::const_iterator& iter); - bool parseUnaryExpression(const QString& str, QString::const_iterator& iter); + 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 parseFactor(const QString& str, QString::const_iterator& iter); OpCode evaluate(const AnimVariantMap& map) const; void evalAnd(const AnimVariantMap& map, std::stack& stack) const; @@ -126,13 +131,24 @@ protected: 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 evalMinus(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/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 58fd3a1d35..6765553a25 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -366,82 +366,120 @@ void AnimTests::testExpressionParser() { vars.set("five", (float)5.0f); vars.set("forty", (float)40.0f); - AnimExpression e("(!f)"); - QVERIFY(e._opCodes.size() == 2); - 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); + 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); - 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"); - QVERIFY(e._opCodes.size() == 2); + 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 == "ten"); - QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Minus); + QVERIFY(e._opCodes[0].strVal == "twenty"); + } - auto opCode = e.evaluate(vars); - QVERIFY(opCode.type == AnimExpression::OpCode::Int); - QVERIFY(opCode.intVal == 10); + e = AnimExpression("2 + 3"); + QVERIFY(e._opCodes.size() == 3); + 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); +} diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index 94b3eddd25..a70acc21f7 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -28,6 +28,7 @@ private slots: void testAccumulateTime(); void testExpressionTokenizer(); void testExpressionParser(); + void testExpressionEvaluator(); }; #endif // hifi_AnimTests_h From 22756d168b25af08f004d5b7b8de24dd59134db4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 15 Dec 2015 10:35:35 -0800 Subject: [PATCH 10/11] Changed grammar to support boolean and and or. --- libraries/animation/src/AnimExpression.cpp | 64 ++++++++++------------ libraries/animation/src/AnimExpression.h | 16 +++--- tests/QTestExtensions.h | 2 +- 3 files changed, 39 insertions(+), 43 deletions(-) diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp index b76aaff3f9..5fc1bc9b4f 100644 --- a/libraries/animation/src/AnimExpression.cpp +++ b/libraries/animation/src/AnimExpression.cpp @@ -78,7 +78,15 @@ AnimExpression::Token AnimExpression::consumeIdentifier(const QString& str, QStr } int pos = (int)(begin - str.begin()); int len = (int)(iter - begin); - return Token(QStringRef(const_cast(&str), pos, len)); + + 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. @@ -198,19 +206,19 @@ AnimExpression::Token AnimExpression::consumeNot(const QString& str, QString::co /* Expr → Term Expr' -Expr' → + Term Expr' - | – Term Expr' +Expr' → '||' Term Expr' | ε Term → Factor Term' -Term' → * Term' - | / Term' +Term' → '&&' Term' | ε Factor → INT + | BOOL | FLOAT | IDENTIFIER - | (Expr) + | '(' Expr ')' */ +// Expr → Term Expr' bool AnimExpression::parseExpr(const QString& str, QString::const_iterator& iter) { if (!parseTerm(str, iter)) { return false; @@ -221,9 +229,10 @@ bool AnimExpression::parseExpr(const QString& str, QString::const_iterator& iter return true; } +// Expr' → '||' Term Expr' | ε bool AnimExpression::parseExprPrime(const QString& str, QString::const_iterator& iter) { auto token = consumeToken(str, iter); - if (token.type == Token::Plus) { + if (token.type == Token::Or) { if (!parseTerm(str, iter)) { unconsumeToken(token); return false; @@ -232,18 +241,7 @@ bool AnimExpression::parseExprPrime(const QString& str, QString::const_iterator& unconsumeToken(token); return false; } - _opCodes.push_back(OpCode {OpCode::Add}); - return true; - } 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}); + _opCodes.push_back(OpCode {OpCode::Or}); return true; } else { unconsumeToken(token); @@ -251,6 +249,7 @@ bool AnimExpression::parseExprPrime(const QString& str, QString::const_iterator& } } +// Term → Factor Term' bool AnimExpression::parseTerm(const QString& str, QString::const_iterator& iter) { if (!parseFactor(str, iter)) { return false; @@ -261,9 +260,10 @@ bool AnimExpression::parseTerm(const QString& str, QString::const_iterator& iter return true; } +// Term' → '&&' Term' | ε bool AnimExpression::parseTermPrime(const QString& str, QString::const_iterator& iter) { auto token = consumeToken(str, iter); - if (token.type == Token::Multiply) { + if (token.type == Token::And) { if (!parseTerm(str, iter)) { unconsumeToken(token); return false; @@ -272,18 +272,7 @@ bool AnimExpression::parseTermPrime(const QString& str, QString::const_iterator& 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}); + _opCodes.push_back(OpCode {OpCode::And}); return true; } else { unconsumeToken(token); @@ -291,11 +280,15 @@ bool AnimExpression::parseTermPrime(const QString& str, QString::const_iterator& } } +// 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; @@ -351,7 +344,7 @@ AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const case OpCode::UnaryMinus: evalUnaryMinus(map, stack); break; } } - return stack.top(); + return coerseToValue(map, stack.top()); } #define POP_BOOL(NAME) \ @@ -602,8 +595,7 @@ AnimExpression::OpCode AnimExpression::coerseToValue(const AnimVariantMap& map, 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); + return OpCode((bool)var.getBool()); break; case AnimVariant::Type::Int: return OpCode(var.getInt()); @@ -631,6 +623,7 @@ AnimExpression::OpCode AnimExpression::coerseToValue(const AnimVariantMap& map, } } +#ifndef NDEBUG void AnimExpression::dumpOpCodes() const { QString tmp; for (auto& op : _opCodes) { @@ -660,3 +653,4 @@ void AnimExpression::dumpOpCodes() const { qCDebug(animation).nospace().noquote() << "opCodes =" << tmp; qCDebug(animation).resetFormat(); } +#endif diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index 6e204483d5..8a1961b326 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -27,6 +27,7 @@ protected: enum Type { End = 0, Identifier, + Bool, Int, Float, And, @@ -50,8 +51,9 @@ protected: }; Token(Type type) : type {type} {} Token(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {} - Token(int val) : type {Type::Int}, intVal {val} {} - Token(float val) : type {Type::Float}, floatVal {val} {} + 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}; @@ -81,11 +83,11 @@ protected: UnaryMinus }; OpCode(Type type) : type {type} {} - OpCode(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {} - OpCode(const QString& str) : type {Type::Identifier}, strVal {str} {} - OpCode(int val) : type {Type::Int}, intVal {val} {} - OpCode(bool val) : type {Type::Bool}, intVal {(int)val} {} - OpCode(float val) : type {Type::Float}, floatVal {val} {} + 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) { 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; } From ab85e2967a9bca036abac6b49c5199fba4d4e54c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 15 Dec 2015 13:18:30 -0800 Subject: [PATCH 11/11] AnimExpression: support for unary not. --- libraries/animation/src/AnimExpression.cpp | 32 +++++++-- libraries/animation/src/AnimExpression.h | 1 + tests/animation/src/AnimTests.cpp | 77 ++++++++-------------- 3 files changed, 53 insertions(+), 57 deletions(-) diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp index 5fc1bc9b4f..79004a72a6 100644 --- a/libraries/animation/src/AnimExpression.cpp +++ b/libraries/animation/src/AnimExpression.cpp @@ -208,9 +208,11 @@ AnimExpression::Token AnimExpression::consumeNot(const QString& str, QString::co Expr → Term Expr' Expr' → '||' Term Expr' | ε -Term → Factor Term' -Term' → '&&' Term' +Term → Unary Term' +Term' → '&&' Unary Term' | ε +Unary → '!' Unary + | Factor Factor → INT | BOOL | FLOAT @@ -249,9 +251,9 @@ bool AnimExpression::parseExprPrime(const QString& str, QString::const_iterator& } } -// Term → Factor Term' +// Term → Unary Term' bool AnimExpression::parseTerm(const QString& str, QString::const_iterator& iter) { - if (!parseFactor(str, iter)) { + if (!parseUnary(str, iter)) { return false; } if (!parseTermPrime(str, iter)) { @@ -260,11 +262,11 @@ bool AnimExpression::parseTerm(const QString& str, QString::const_iterator& iter return true; } -// Term' → '&&' Term' | ε +// Term' → '&&' Unary Term' | ε bool AnimExpression::parseTermPrime(const QString& str, QString::const_iterator& iter) { auto token = consumeToken(str, iter); if (token.type == Token::And) { - if (!parseTerm(str, iter)) { + if (!parseUnary(str, iter)) { unconsumeToken(token); return false; } @@ -280,6 +282,24 @@ bool AnimExpression::parseTermPrime(const QString& str, QString::const_iterator& } } +// 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); diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index 8a1961b326..468217f5b3 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -119,6 +119,7 @@ protected: 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; diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 63eb39dc48..6812bb63b6 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -522,43 +522,19 @@ void AnimTests::testExpressionParser() { QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::And); } - /* - e = AnimExpression("2 + 3"); - QVERIFY(e._opCodes.size() == 3); - 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("!(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); } - - 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); - } - */ } #define TEST_BOOL_EXPR(EXPR) \ @@ -630,23 +606,22 @@ void AnimTests::testExpressionEvaluator() { TEST_BOOL_EXPR(f || false); TEST_BOOL_EXPR(f || true); -/* - result = AnimExpression("(2 + 3) * (5 + 3)").evaluate(vars); - QVERIFY(result.type == AnimExpression::OpCode::Int); - QVERIFY(result.intVal == (2 + 3) * (5 + 3)); + TEST_BOOL_EXPR(!true); + TEST_BOOL_EXPR(!false); + TEST_BOOL_EXPR(!true || true); - result = AnimExpression("(ten + twenty) * 5").evaluate(vars); - QVERIFY(result.type == AnimExpression::OpCode::Int); - QVERIFY(result.intVal == (10 + 20) * 5); + 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); - 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); -*/ + TEST_BOOL_EXPR(!(true && f) || !t); + TEST_BOOL_EXPR(!!!(t) && (!!f || true)); + TEST_BOOL_EXPR(!(true && f) && true); }