From f3192ca06de1007545bf6c3a49ddc83fb615a642 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 10:37:23 -0600 Subject: [PATCH 01/38] ExpressionParser: Add support for literals. --- .../ControlReference/ExpressionParser.cpp | 79 ++++++++++++++----- .../ControlReference/ExpressionParser.h | 18 +++++ 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index c864b22a20..81760aff2b 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -29,6 +29,7 @@ enum TokenType TOK_NOT, TOK_ADD, TOK_CONTROL, + TOK_LITERAL, }; inline std::string OpName(TokenType op) @@ -53,10 +54,10 @@ class Token { public: TokenType type; - ControlQualifier qualifier; + std::string data; Token(TokenType type_) : type(type_) {} - Token(TokenType type_, ControlQualifier qualifier_) : type(type_), qualifier(qualifier_) {} + Token(TokenType type_, std::string data_) : type(type_), data(std::move(data_)) {} operator std::string() const { switch (type) @@ -78,7 +79,9 @@ public: case TOK_ADD: return "+"; case TOK_CONTROL: - return "Device(" + (std::string)qualifier + ")"; + return "Device(" + data + ")"; + case TOK_LITERAL: + return '\'' + data + '\''; case TOK_INVALID: break; } @@ -94,38 +97,33 @@ public: std::string::iterator it; Lexer(const std::string& expr_) : expr(expr_) { it = expr.begin(); } - bool FetchBacktickString(std::string& value, char otherDelim = 0) + + bool FetchDelimString(std::string& value, char delim) { value = ""; while (it != expr.end()) { char c = *it; ++it; - if (c == '`') - return false; - if (c > 0 && c == otherDelim) + if (c == delim) return true; value += c; } return false; } + Token GetLiteral() + { + std::string value; + FetchDelimString(value, '\''); + return Token(TOK_LITERAL, value); + } + Token GetFullyQualifiedControl() { - ControlQualifier qualifier; std::string value; - - if (FetchBacktickString(value, ':')) - { - // Found colon, this is the device name - qualifier.has_device = true; - qualifier.device_qualifier.FromString(value); - FetchBacktickString(value); - } - - qualifier.control_name = value; - - return Token(TOK_CONTROL, qualifier); + FetchDelimString(value, '`'); + return Token(TOK_CONTROL, value); } Token GetBarewordsControl(char c) @@ -172,6 +170,8 @@ public: return Token(TOK_NOT); case '+': return Token(TOK_ADD); + case '\'': + return GetLiteral(); case '`': return GetFullyQualifiedControl(); default: @@ -339,6 +339,35 @@ public: operator std::string() const override { return OpName(op) + "(" + (std::string)(*inner) + ")"; } }; +class LiteralExpression : public Expression +{ +public: + explicit LiteralExpression(const std::string& str) + { + // If it fails to parse it will just be the default: 0.0 + TryParse(str, &m_value); + } + + ControlState GetValue() const override { return m_value; } + + void SetValue(ControlState value) override + { + // Do nothing. + } + + int CountNumControls() const override { return 1; } + + void UpdateReferences(ControlFinder&) override + { + // Nothing needed. + } + + operator std::string() const override { return '\'' + ValueToString(m_value) + '\''; } + +private: + ControlState m_value{}; +}; + // This class proxies all methods to its either left-hand child if it has bound controls, or its // right-hand child. Its intended use is for supporting old-style barewords expressions. class CoalesceExpression : public Expression @@ -430,7 +459,15 @@ private: switch (tok.type) { case TOK_CONTROL: - return {ParseStatus::Successful, std::make_unique(tok.qualifier)}; + { + ControlQualifier cq; + cq.FromString(tok.data); + return {ParseStatus::Successful, std::make_unique(cq)}; + } + case TOK_LITERAL: + { + return {ParseStatus::Successful, std::make_unique(tok.data)}; + } case TOK_LPAREN: return Paren(); default: diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.h b/Source/Core/InputCommon/ControlReference/ExpressionParser.h index 56c0340f49..0a42d90c62 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.h +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.h @@ -19,6 +19,7 @@ public: std::string control_name; ControlQualifier() : has_device(false) {} + operator std::string() const { if (has_device) @@ -26,6 +27,23 @@ public: else return control_name; } + + void FromString(const std::string& str) + { + const auto col_pos = str.find_last_of(':'); + + has_device = (str.npos != col_pos); + if (has_device) + { + device_qualifier.FromString(str.substr(0, col_pos)); + control_name = str.substr(col_pos + 1); + } + else + { + device_qualifier.FromString(""); + control_name = str; + } + } }; class ControlFinder From bf63f85d732db2fdab169823f2cf8a5fcbde0ec4 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 11:51:12 -0600 Subject: [PATCH 02/38] ExpressionParser: Add multiplication and division operators. (division by zero evaluates as zero). Don't clamp result of addition operator. Clamping will be done later. --- .../ControlReference/ExpressionParser.cpp | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 81760aff2b..2ca7efd98a 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -28,6 +28,8 @@ enum TokenType TOK_OR, TOK_NOT, TOK_ADD, + TOK_MUL, + TOK_DIV, TOK_CONTROL, TOK_LITERAL, }; @@ -44,6 +46,10 @@ inline std::string OpName(TokenType op) return "Not"; case TOK_ADD: return "Add"; + case TOK_MUL: + return "Mul"; + case TOK_DIV: + return "Div"; default: assert(false); return ""; @@ -78,6 +84,10 @@ public: return "!"; case TOK_ADD: return "+"; + case TOK_MUL: + return "*"; + case TOK_DIV: + return "/"; case TOK_CONTROL: return "Device(" + data + ")"; case TOK_LITERAL: @@ -170,6 +180,10 @@ public: return Token(TOK_NOT); case '+': return Token(TOK_ADD); + case '*': + return Token(TOK_MUL); + case '/': + return Token(TOK_DIV); case '\'': return GetLiteral(); case '`': @@ -266,7 +280,14 @@ public: case TOK_OR: return std::max(lhsValue, rhsValue); case TOK_ADD: - return std::min(lhsValue + rhsValue, 1.0); + return lhsValue + rhsValue; + case TOK_MUL: + return lhsValue * rhsValue; + case TOK_DIV: + { + const ControlState result = lhsValue / rhsValue; + return std::isinf(result) ? 0.0 : result; + } default: assert(false); return 0; @@ -508,6 +529,8 @@ private: case TOK_AND: case TOK_OR: case TOK_ADD: + case TOK_MUL: + case TOK_DIV: return true; default: return false; From a8f3e9585f98c91c2b6220b3ab6951484192266e Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 12:38:02 -0600 Subject: [PATCH 03/38] ExpressionParser: Expand ! symbol to allow for named unary functions. Added !toggle function which toggles on/off with each activation of its inner expression. --- .../ControlReference/ExpressionParser.cpp | 148 +++++++++++++----- 1 file changed, 106 insertions(+), 42 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 2ca7efd98a..6a59f5c19b 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -26,7 +27,7 @@ enum TokenType TOK_RPAREN, TOK_AND, TOK_OR, - TOK_NOT, + TOK_UNARY, TOK_ADD, TOK_MUL, TOK_DIV, @@ -42,8 +43,8 @@ inline std::string OpName(TokenType op) return "And"; case TOK_OR: return "Or"; - case TOK_NOT: - return "Not"; + case TOK_UNARY: + return "Unary"; case TOK_ADD: return "Add"; case TOK_MUL: @@ -80,8 +81,8 @@ public: return "&"; case TOK_OR: return "|"; - case TOK_NOT: - return "!"; + case TOK_UNARY: + return "!" + data; case TOK_ADD: return "+"; case TOK_MUL: @@ -122,6 +123,21 @@ public: return false; } + Token GetUnaryFunction() + { + std::string name; + + std::regex valid_name_char("[a-z0-9_]", std::regex_constants::icase); + + while (it != expr.end() && std::regex_match(std::string(1, *it), valid_name_char)) + { + name += *it; + ++it; + } + + return Token(TOK_UNARY, name); + } + Token GetLiteral() { std::string value; @@ -177,7 +193,7 @@ public: case '|': return Token(TOK_OR); case '!': - return Token(TOK_NOT); + return GetUnaryFunction(); case '+': return Token(TOK_ADD); case '*': @@ -322,44 +338,93 @@ public: class UnaryExpression : public Expression { public: - TokenType op; - std::unique_ptr inner; - - UnaryExpression(TokenType op_, std::unique_ptr&& inner_) - : op(op_), inner(std::move(inner_)) - { - } - ControlState GetValue() const override - { - ControlState value = inner->GetValue(); - switch (op) - { - case TOK_NOT: - return 1.0 - value; - default: - assert(false); - return 0; - } - } - - void SetValue(ControlState value) override - { - switch (op) - { - case TOK_NOT: - inner->SetValue(1.0 - value); - break; - - default: - assert(false); - } - } + UnaryExpression(std::unique_ptr&& inner_) : inner(std::move(inner_)) {} int CountNumControls() const override { return inner->CountNumControls(); } void UpdateReferences(ControlFinder& finder) override { inner->UpdateReferences(finder); } - operator std::string() const override { return OpName(op) + "(" + (std::string)(*inner) + ")"; } + + operator std::string() const override + { + return "!" + GetFuncName() + "(" + (std::string)(*inner) + ")"; + } + +protected: + virtual std::string GetFuncName() const = 0; + + std::unique_ptr inner; }; +// TODO: Return an oscillating value to make it apparent something was spelled wrong? +class UnaryUnknownExpression : public UnaryExpression +{ +public: + UnaryUnknownExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) + { + } + + ControlState GetValue() const override { return 0.0; } + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "Unknown"; } +}; + +class UnaryToggleExpression : public UnaryExpression +{ +public: + UnaryToggleExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) + { + } + + ControlState GetValue() const override + { + const ControlState inner_value = inner->GetValue(); + + if (inner_value < THRESHOLD) + { + m_released = true; + } + else if (m_released && inner_value > THRESHOLD) + { + m_released = false; + m_state ^= true; + } + + return m_state; + } + + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "Toggle"; } + +private: + static constexpr ControlState THRESHOLD = 0.5; + // eww: + mutable bool m_released{}; + mutable bool m_state{}; +}; + +class UnaryNotExpression : public UnaryExpression +{ +public: + UnaryNotExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) {} + + ControlState GetValue() const override { return 1.0 - inner->GetValue(); } + void SetValue(ControlState value) override { inner->SetValue(1.0 - value); } + std::string GetFuncName() const override { return ""; } +}; + +std::unique_ptr MakeUnaryExpression(std::string name, + std::unique_ptr&& inner_) +{ + // Case insensitive matching. + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + + if ("" == name) + return std::make_unique(std::move(inner_)); + else if ("toggle" == name) + return std::make_unique(std::move(inner_)); + else + return std::make_unique(std::move(inner_)); +} + class LiteralExpression : public Expression { public: @@ -500,7 +565,7 @@ private: { switch (type) { - case TOK_NOT: + case TOK_UNARY: return true; default: return false; @@ -515,8 +580,7 @@ private: ParseResult result = Atom(); if (result.status == ParseStatus::SyntaxError) return result; - return {ParseStatus::Successful, - std::make_unique(tok.type, std::move(result.expr))}; + return {ParseStatus::Successful, MakeUnaryExpression(tok.data, std::move(result.expr))}; } return Atom(); From 1efcf861ead76bbffb4ac22726d799975146707c Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 13:16:28 -0600 Subject: [PATCH 04/38] ExpressionParser: Add mod operator, sin function, and timer "constant" which can be used for auto-fire and oscillators. --- .../ControlReference/ExpressionParser.cpp | 92 ++++++++++++++++--- 1 file changed, 81 insertions(+), 11 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 6a59f5c19b..2de2650699 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include #include @@ -11,6 +13,7 @@ #include #include +#include "Common/MathUtil.h" #include "Common/StringUtil.h" #include "InputCommon/ControlReference/ExpressionParser.h" @@ -31,6 +34,7 @@ enum TokenType TOK_ADD, TOK_MUL, TOK_DIV, + TOK_MOD, TOK_CONTROL, TOK_LITERAL, }; @@ -51,6 +55,8 @@ inline std::string OpName(TokenType op) return "Mul"; case TOK_DIV: return "Div"; + case TOK_MOD: + return "Mod"; default: assert(false); return ""; @@ -89,6 +95,8 @@ public: return "*"; case TOK_DIV: return "/"; + case TOK_MOD: + return "%"; case TOK_CONTROL: return "Device(" + data + ")"; case TOK_LITERAL: @@ -200,6 +208,8 @@ public: return Token(TOK_MUL); case '/': return Token(TOK_DIV); + case '%': + return Token(TOK_MOD); case '\'': return GetLiteral(); case '`': @@ -304,6 +314,11 @@ public: const ControlState result = lhsValue / rhsValue; return std::isinf(result) ? 0.0 : result; } + case TOK_MOD: + { + const ControlState result = std::fmod(lhsValue, rhsValue); + return std::isnan(result) ? 0.0 : result; + } default: assert(false); return 0; @@ -411,6 +426,16 @@ public: std::string GetFuncName() const override { return ""; } }; +class UnarySinExpression : public UnaryExpression +{ +public: + UnarySinExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) {} + + ControlState GetValue() const override { return std::cos(inner->GetValue()); } + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "Sin"; } +}; + std::unique_ptr MakeUnaryExpression(std::string name, std::unique_ptr&& inner_) { @@ -421,6 +446,8 @@ std::unique_ptr MakeUnaryExpression(std::string name, return std::make_unique(std::move(inner_)); else if ("toggle" == name) return std::make_unique(std::move(inner_)); + else if ("sin" == name) + return std::make_unique(std::move(inner_)); else return std::make_unique(std::move(inner_)); } @@ -428,14 +455,6 @@ std::unique_ptr MakeUnaryExpression(std::string name, class LiteralExpression : public Expression { public: - explicit LiteralExpression(const std::string& str) - { - // If it fails to parse it will just be the default: 0.0 - TryParse(str, &m_value); - } - - ControlState GetValue() const override { return m_value; } - void SetValue(ControlState value) override { // Do nothing. @@ -448,12 +467,62 @@ public: // Nothing needed. } - operator std::string() const override { return '\'' + ValueToString(m_value) + '\''; } + operator std::string() const override { return '\'' + GetName() + '\''; } + +protected: + virtual std::string GetName() const = 0; +}; + +class LiteralReal : public LiteralExpression +{ +public: + LiteralReal(ControlState value) : m_value(value) {} + + ControlState GetValue() const override { return m_value; } + + std::string GetName() const override { return ValueToString(m_value); } private: - ControlState m_value{}; + const ControlState m_value{}; }; +// A +1.0 per second incrementing timer: +class LiteralTimer : public LiteralExpression +{ +public: + ControlState GetValue() const override + { + const auto ms = + std::chrono::duration_cast(Clock::now().time_since_epoch()); + // TODO: Will this roll over nicely? + return ms.count() / 1000.0; + } + + std::string GetName() const override { return "Timer"; } + +private: + using Clock = std::chrono::steady_clock; +}; + +std::unique_ptr MakeLiteralExpression(std::string name) +{ + // Case insensitive matching. + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + + // Check for named literals: + if ("timer" == name) + { + return std::make_unique(); + } + else + { + // Assume it's a Real. If TryParse fails we'll just get a Zero. + ControlState val{}; + TryParse(name, &val); + return std::make_unique(val); + } +} + // This class proxies all methods to its either left-hand child if it has bound controls, or its // right-hand child. Its intended use is for supporting old-style barewords expressions. class CoalesceExpression : public Expression @@ -552,7 +621,7 @@ private: } case TOK_LITERAL: { - return {ParseStatus::Successful, std::make_unique(tok.data)}; + return {ParseStatus::Successful, MakeLiteralExpression(tok.data)}; } case TOK_LPAREN: return Paren(); @@ -595,6 +664,7 @@ private: case TOK_ADD: case TOK_MUL: case TOK_DIV: + case TOK_MOD: return true; default: return false; From e896835f86e52051b1998a344549dfa8ba79847a Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 16:06:29 -0600 Subject: [PATCH 05/38] ExpressionParser: Renamed ControlFinder to ControlEnvironment. Added support for variables and assignment operator. ControlExpression objects now reference a matching input and output so the two can me mixed in any expression. (you can set rumble directly from inputs) --- .../Config/Mapping/MappingButton.cpp | 4 +- .../ControlReference/ControlReference.cpp | 8 +- .../ControlReference/ControlReference.h | 3 +- .../ControlReference/ExpressionParser.cpp | 124 ++++++++++++++---- .../ControlReference/ExpressionParser.h | 20 ++- .../ControllerEmu/ControllerEmu.cpp | 21 ++- .../InputCommon/ControllerEmu/ControllerEmu.h | 10 ++ 7 files changed, 144 insertions(+), 46 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index 435033c8c1..a9a142276f 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -100,7 +100,7 @@ void MappingButton::Detect() return; m_reference->SetExpression(expression.toStdString()); - m_parent->GetController()->UpdateReferences(g_controller_interface); + m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, m_reference); ConfigChanged(); m_parent->SaveSettings(); @@ -111,7 +111,7 @@ void MappingButton::Clear() m_reference->range = 100.0 / SLIDER_TICK_COUNT; m_reference->SetExpression(""); - m_parent->GetController()->UpdateReferences(g_controller_interface); + m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, m_reference); m_parent->SaveSettings(); ConfigChanged(); diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp index cfc6fa658e..c17cfb3c86 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.cpp +++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp @@ -25,12 +25,12 @@ bool ControlReference::InputGateOn() // Updates a controlreference's binded devices/controls // need to call this to re-bind a control reference after changing its expression // -void ControlReference::UpdateReference(const ciface::Core::DeviceContainer& devices, - const ciface::Core::DeviceQualifier& default_device) +void ControlReference::UpdateReference(ciface::ExpressionParser::ControlEnvironment& env) { - ControlFinder finder(devices, default_device, IsInput()); if (m_parsed_expression) - m_parsed_expression->UpdateReferences(finder); + { + m_parsed_expression->UpdateReferences(env); + } } int ControlReference::BoundCount() const diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.h b/Source/Core/InputCommon/ControlReference/ControlReference.h index 83fa288676..3f2c205501 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.h +++ b/Source/Core/InputCommon/ControlReference/ControlReference.h @@ -30,8 +30,7 @@ public: int BoundCount() const; ciface::ExpressionParser::ParseStatus GetParseStatus() const; - void UpdateReference(const ciface::Core::DeviceContainer& devices, - const ciface::Core::DeviceQualifier& default_device); + void UpdateReference(ciface::ExpressionParser::ControlEnvironment& env); std::string GetExpression() const; void SetExpression(std::string expr); diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 2de2650699..f375c465d7 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -35,8 +35,10 @@ enum TokenType TOK_MUL, TOK_DIV, TOK_MOD, + TOK_ASSIGN, TOK_CONTROL, TOK_LITERAL, + TOK_VARIABLE, }; inline std::string OpName(TokenType op) @@ -57,6 +59,10 @@ inline std::string OpName(TokenType op) return "Div"; case TOK_MOD: return "Mod"; + case TOK_ASSIGN: + return "Assign"; + case TOK_VARIABLE: + return "Var"; default: assert(false); return ""; @@ -97,10 +103,14 @@ public: return "/"; case TOK_MOD: return "%"; + case TOK_ASSIGN: + return "="; case TOK_CONTROL: return "Device(" + data + ")"; case TOK_LITERAL: return '\'' + data + '\''; + case TOK_VARIABLE: + return '$' + data; case TOK_INVALID: break; } @@ -131,21 +141,23 @@ public: return false; } - Token GetUnaryFunction() + std::string FetchWordChars() { - std::string name; + std::string word; std::regex valid_name_char("[a-z0-9_]", std::regex_constants::icase); while (it != expr.end() && std::regex_match(std::string(1, *it), valid_name_char)) { - name += *it; + word += *it; ++it; } - return Token(TOK_UNARY, name); + return word; } + Token GetUnaryFunction() { return Token(TOK_UNARY, FetchWordChars()); } + Token GetLiteral() { std::string value; @@ -153,6 +165,8 @@ public: return Token(TOK_LITERAL, value); } + Token GetVariable() { return Token(TOK_VARIABLE, FetchWordChars()); } + Token GetFullyQualifiedControl() { std::string value; @@ -210,8 +224,12 @@ public: return Token(TOK_DIV); case '%': return Token(TOK_MOD); + case '=': + return Token(TOK_ASSIGN); case '\'': return GetLiteral(); + case '$': + return GetVariable(); case '`': return GetFullyQualifiedControl(); default: @@ -249,15 +267,14 @@ public: class ControlExpression : public Expression { public: - ControlQualifier qualifier; - Device::Control* control = nullptr; // Keep a shared_ptr to the device so the control pointer doesn't become invalid + // TODO: This is causing devices to be destructed after backends are shutdown: std::shared_ptr m_device; explicit ControlExpression(ControlQualifier qualifier_) : qualifier(qualifier_) {} ControlState GetValue() const override { - if (!control) + if (!input) return 0.0; // Note: Inputs may return negative values in situations where opposing directions are @@ -266,20 +283,26 @@ public: // FYI: Clamping values greater than 1.0 is purposely not done to support unbounded values in // the future. (e.g. raw accelerometer/gyro data) - return std::max(0.0, control->ToInput()->GetState()); + return std::max(0.0, input->GetState()); } void SetValue(ControlState value) override { - if (control) - control->ToOutput()->SetState(value); + if (output) + output->SetState(value); } - int CountNumControls() const override { return control ? 1 : 0; } - void UpdateReferences(ControlFinder& finder) override + int CountNumControls() const override { return (input || output) ? 1 : 0; } + void UpdateReferences(ControlEnvironment& env) override { - m_device = finder.FindDevice(qualifier); - control = finder.FindControl(qualifier); + m_device = env.FindDevice(qualifier); + input = env.FindInput(qualifier); + output = env.FindOutput(qualifier); } operator std::string() const override { return "`" + static_cast(qualifier) + "`"; } + +private: + ControlQualifier qualifier; + Device::Input* input = nullptr; + Device::Output* output = nullptr; }; class BinaryExpression : public Expression @@ -319,6 +342,12 @@ public: const ControlState result = std::fmod(lhsValue, rhsValue); return std::isnan(result) ? 0.0 : result; } + case TOK_ASSIGN: + { + lhs->SetValue(rhsValue); + // TODO: Should this instead GetValue(lhs) ? + return rhsValue; + } default: assert(false); return 0; @@ -338,10 +367,10 @@ public: return lhs->CountNumControls() + rhs->CountNumControls(); } - void UpdateReferences(ControlFinder& finder) override + void UpdateReferences(ControlEnvironment& env) override { - lhs->UpdateReferences(finder); - rhs->UpdateReferences(finder); + lhs->UpdateReferences(env); + rhs->UpdateReferences(env); } operator std::string() const override @@ -356,7 +385,7 @@ public: UnaryExpression(std::unique_ptr&& inner_) : inner(std::move(inner_)) {} int CountNumControls() const override { return inner->CountNumControls(); } - void UpdateReferences(ControlFinder& finder) override { inner->UpdateReferences(finder); } + void UpdateReferences(ControlEnvironment& env) override { inner->UpdateReferences(env); } operator std::string() const override { @@ -462,7 +491,7 @@ public: int CountNumControls() const override { return 1; } - void UpdateReferences(ControlFinder&) override + void UpdateReferences(ControlEnvironment&) override { // Nothing needed. } @@ -523,6 +552,29 @@ std::unique_ptr MakeLiteralExpression(std::string name) } } +class VariableExpression : public Expression +{ +public: + VariableExpression(std::string name) : m_name(name) {} + + ControlState GetValue() const override { return *m_value_ptr; } + + void SetValue(ControlState value) override { *m_value_ptr = value; } + + int CountNumControls() const override { return 1; } + + void UpdateReferences(ControlEnvironment& env) override + { + m_value_ptr = env.GetVariablePtr(m_name); + } + + operator std::string() const override { return '$' + m_name; } + +protected: + const std::string m_name; + ControlState* m_value_ptr{}; +}; + // This class proxies all methods to its either left-hand child if it has bound controls, or its // right-hand child. Its intended use is for supporting old-style barewords expressions. class CoalesceExpression : public Expression @@ -543,10 +595,10 @@ public: static_cast(*m_rhs) + ')'; } - void UpdateReferences(ControlFinder& finder) override + void UpdateReferences(ControlEnvironment& env) override { - m_lhs->UpdateReferences(finder); - m_rhs->UpdateReferences(finder); + m_lhs->UpdateReferences(env); + m_rhs->UpdateReferences(env); } private: @@ -559,7 +611,7 @@ private: std::unique_ptr m_rhs; }; -std::shared_ptr ControlFinder::FindDevice(ControlQualifier qualifier) const +std::shared_ptr ControlEnvironment::FindDevice(ControlQualifier qualifier) const { if (qualifier.has_device) return container.FindDevice(qualifier.device_qualifier); @@ -567,16 +619,27 @@ std::shared_ptr ControlFinder::FindDevice(ControlQualifier qualifier) co return container.FindDevice(default_device); } -Device::Control* ControlFinder::FindControl(ControlQualifier qualifier) const +Device::Input* ControlEnvironment::FindInput(ControlQualifier qualifier) const { const std::shared_ptr device = FindDevice(qualifier); if (!device) return nullptr; - if (is_input) - return device->FindInput(qualifier.control_name); - else - return device->FindOutput(qualifier.control_name); + return device->FindInput(qualifier.control_name); +} + +Device::Output* ControlEnvironment::FindOutput(ControlQualifier qualifier) const +{ + const std::shared_ptr device = FindDevice(qualifier); + if (!device) + return nullptr; + + return device->FindOutput(qualifier.control_name); +} + +ControlState* ControlEnvironment::GetVariablePtr(const std::string& name) +{ + return &m_variables[name]; } struct ParseResult @@ -623,6 +686,10 @@ private: { return {ParseStatus::Successful, MakeLiteralExpression(tok.data)}; } + case TOK_VARIABLE: + { + return {ParseStatus::Successful, std::make_unique(tok.data)}; + } case TOK_LPAREN: return Paren(); default: @@ -665,6 +732,7 @@ private: case TOK_MUL: case TOK_DIV: case TOK_MOD: + case TOK_ASSIGN: return true; default: return false; diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.h b/Source/Core/InputCommon/ControlReference/ExpressionParser.h index 0a42d90c62..d9f7a5eb2b 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.h +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -46,21 +47,26 @@ public: } }; -class ControlFinder +class ControlEnvironment { public: - ControlFinder(const Core::DeviceContainer& container_, const Core::DeviceQualifier& default_, - const bool is_input_) - : container(container_), default_device(default_), is_input(is_input_) + using VariableContainer = std::map; + + ControlEnvironment(const Core::DeviceContainer& container_, const Core::DeviceQualifier& default_, + VariableContainer& vars) + : m_variables(vars), container(container_), default_device(default_) { } + std::shared_ptr FindDevice(ControlQualifier qualifier) const; - Core::Device::Control* FindControl(ControlQualifier qualifier) const; + Core::Device::Input* FindInput(ControlQualifier qualifier) const; + Core::Device::Output* FindOutput(ControlQualifier qualifier) const; + ControlState* GetVariablePtr(const std::string& name); private: + VariableContainer& m_variables; const Core::DeviceContainer& container; const Core::DeviceQualifier& default_device; - bool is_input; }; class Expression @@ -70,7 +76,7 @@ public: virtual ControlState GetValue() const = 0; virtual void SetValue(ControlState state) = 0; virtual int CountNumControls() const = 0; - virtual void UpdateReferences(ControlFinder& finder) = 0; + virtual void UpdateReferences(ControlEnvironment& finder) = 0; virtual operator std::string() const = 0; }; diff --git a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp index de0579a2b2..c5ff0705a2 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp @@ -38,23 +38,38 @@ std::unique_lock EmulatedController::GetStateLock() void EmulatedController::UpdateReferences(const ControllerInterface& devi) { - const auto lock = GetStateLock(); m_default_device_is_connected = devi.HasConnectedDevice(m_default_device); + ciface::ExpressionParser::ControlEnvironment env(devi, GetDefaultDevice(), m_expression_vars); + + UpdateReferences(env); +} + +void EmulatedController::UpdateReferences(ciface::ExpressionParser::ControlEnvironment& env) +{ + const auto lock = GetStateLock(); + for (auto& ctrlGroup : groups) { for (auto& control : ctrlGroup->controls) - control->control_ref.get()->UpdateReference(devi, GetDefaultDevice()); + control->control_ref->UpdateReference(env); // Attachments: if (ctrlGroup->type == GroupType::Attachments) { for (auto& attachment : static_cast(ctrlGroup.get())->GetAttachmentList()) - attachment->UpdateReferences(devi); + attachment->UpdateReferences(env); } } } +void EmulatedController::UpdateSingleControlReference(const ControllerInterface& devi, + ControlReference* ref) +{ + ciface::ExpressionParser::ControlEnvironment env(devi, GetDefaultDevice(), m_expression_vars); + ref->UpdateReference(env); +} + bool EmulatedController::IsDefaultDeviceConnected() const { return m_default_device_is_connected; diff --git a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h index bf2ff666fa..2611880ee9 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h +++ b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h @@ -13,6 +13,7 @@ #include "Common/Common.h" #include "Common/IniFile.h" +#include "InputCommon/ControlReference/ExpressionParser.h" #include "InputCommon/ControllerInterface/Device.h" class ControllerInterface; @@ -20,6 +21,8 @@ class ControllerInterface; const char* const named_directions[] = {_trans("Up"), _trans("Down"), _trans("Left"), _trans("Right")}; +class ControlReference; + namespace ControllerEmu { class ControlGroup; @@ -43,6 +46,7 @@ public: void SetDefaultDevice(ciface::Core::DeviceQualifier devq); void UpdateReferences(const ControllerInterface& devi); + void UpdateSingleControlReference(const ControllerInterface& devi, ControlReference* ref); // This returns a lock that should be held before calling State() on any control // references and GetState(), by extension. This prevents a race condition @@ -75,6 +79,12 @@ public: return T(std::lround((zero_value - neg_1_value) * input_value + zero_value)); } +protected: + // TODO: Wiimote attachment has its own member that isn't being used.. + ciface::ExpressionParser::ControlEnvironment::VariableContainer m_expression_vars; + + void UpdateReferences(ciface::ExpressionParser::ControlEnvironment& env); + private: ciface::Core::DeviceQualifier m_default_device; bool m_default_device_is_connected{false}; From 35e51ebbaa025bad87deb4668e0b32249dd62d77 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 16:08:54 -0600 Subject: [PATCH 06/38] ExpressionParser: Clear expression variables on UpdateReferences call. I don't know if this is most sensible. --- Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp index c5ff0705a2..2cf5edbb00 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp @@ -40,6 +40,9 @@ void EmulatedController::UpdateReferences(const ControllerInterface& devi) { m_default_device_is_connected = devi.HasConnectedDevice(m_default_device); + // Reset variables: + m_expression_vars.clear(); + ciface::ExpressionParser::ControlEnvironment env(devi, GetDefaultDevice(), m_expression_vars); UpdateReferences(env); From 718efce1dce86e18fb42c319627b5490cd1f8d94 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 16:11:42 -0600 Subject: [PATCH 07/38] ExpressionParser: Add less-than and greater-than operators. --- .../ControlReference/ExpressionParser.cpp | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index f375c465d7..7a114e95a0 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -36,6 +36,8 @@ enum TokenType TOK_DIV, TOK_MOD, TOK_ASSIGN, + TOK_LTHAN, + TOK_GTHAN, TOK_CONTROL, TOK_LITERAL, TOK_VARIABLE, @@ -61,6 +63,10 @@ inline std::string OpName(TokenType op) return "Mod"; case TOK_ASSIGN: return "Assign"; + case TOK_LTHAN: + return "LThan"; + case TOK_GTHAN: + return "GThan"; case TOK_VARIABLE: return "Var"; default: @@ -105,6 +111,10 @@ public: return "%"; case TOK_ASSIGN: return "="; + case TOK_LTHAN: + return "<"; + case TOK_GTHAN: + return ">"; case TOK_CONTROL: return "Device(" + data + ")"; case TOK_LITERAL: @@ -226,6 +236,10 @@ public: return Token(TOK_MOD); case '=': return Token(TOK_ASSIGN); + case '<': + return Token(TOK_LTHAN); + case '>': + return Token(TOK_GTHAN); case '\'': return GetLiteral(); case '$': @@ -348,6 +362,10 @@ public: // TODO: Should this instead GetValue(lhs) ? return rhsValue; } + case TOK_LTHAN: + return lhsValue < rhsValue; + case TOK_GTHAN: + return lhsValue > rhsValue; default: assert(false); return 0; @@ -733,6 +751,8 @@ private: case TOK_DIV: case TOK_MOD: case TOK_ASSIGN: + case TOK_LTHAN: + case TOK_GTHAN: return true; default: return false; From 58efc93ed4ff6ef3ba7c4c4116d0cef47c63700d Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 16:29:48 -0600 Subject: [PATCH 08/38] ExpressionParser: Conditional operator. A binary op that evals the rhs if lhs > 0.5 else 0.0. --- .../ControlReference/ExpressionParser.cpp | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 7a114e95a0..58cf84cbed 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -28,9 +28,14 @@ enum TokenType TOK_EOF, TOK_LPAREN, TOK_RPAREN, - TOK_AND, - TOK_OR, TOK_UNARY, + TOK_CONTROL, + TOK_LITERAL, + TOK_VARIABLE, + // Binary Ops: + TOK_BINARY_OPS_BEGIN, + TOK_AND = TOK_BINARY_OPS_BEGIN, + TOK_OR, TOK_ADD, TOK_MUL, TOK_DIV, @@ -38,9 +43,8 @@ enum TokenType TOK_ASSIGN, TOK_LTHAN, TOK_GTHAN, - TOK_CONTROL, - TOK_LITERAL, - TOK_VARIABLE, + TOK_COND, + TOK_BINARY_OPS_END, }; inline std::string OpName(TokenType op) @@ -67,6 +71,8 @@ inline std::string OpName(TokenType op) return "LThan"; case TOK_GTHAN: return "GThan"; + case TOK_COND: + return "Cond"; case TOK_VARIABLE: return "Var"; default: @@ -115,6 +121,8 @@ public: return "<"; case TOK_GTHAN: return ">"; + case TOK_COND: + return "?"; case TOK_CONTROL: return "Device(" + data + ")"; case TOK_LITERAL: @@ -240,6 +248,8 @@ public: return Token(TOK_LTHAN); case '>': return Token(TOK_GTHAN); + case '?': + return Token(TOK_COND); case '\'': return GetLiteral(); case '$': @@ -334,38 +344,43 @@ public: ControlState GetValue() const override { - ControlState lhsValue = lhs->GetValue(); - ControlState rhsValue = rhs->GetValue(); switch (op) { case TOK_AND: - return std::min(lhsValue, rhsValue); + return std::min(lhs->GetValue(), rhs->GetValue()); case TOK_OR: - return std::max(lhsValue, rhsValue); + return std::max(lhs->GetValue(), rhs->GetValue()); case TOK_ADD: - return lhsValue + rhsValue; + return lhs->GetValue() + rhs->GetValue(); case TOK_MUL: - return lhsValue * rhsValue; + return lhs->GetValue() * rhs->GetValue(); case TOK_DIV: { - const ControlState result = lhsValue / rhsValue; + const ControlState result = lhs->GetValue() / rhs->GetValue(); return std::isinf(result) ? 0.0 : result; } case TOK_MOD: { - const ControlState result = std::fmod(lhsValue, rhsValue); + const ControlState result = std::fmod(lhs->GetValue(), rhs->GetValue()); return std::isnan(result) ? 0.0 : result; } case TOK_ASSIGN: { - lhs->SetValue(rhsValue); - // TODO: Should this instead GetValue(lhs) ? - return rhsValue; + lhs->SetValue(rhs->GetValue()); + return lhs->GetValue(); } case TOK_LTHAN: - return lhsValue < rhsValue; + return lhs->GetValue() < rhs->GetValue(); case TOK_GTHAN: - return lhsValue > rhsValue; + return lhs->GetValue() > rhs->GetValue(); + case TOK_COND: + { + constexpr ControlState COND_THRESHOLD = 0.5; + if (lhs->GetValue() > COND_THRESHOLD) + return rhs->GetValue(); + else + return 0.0; + } default: assert(false); return 0; @@ -742,21 +757,7 @@ private: bool IsBinaryToken(TokenType type) { - switch (type) - { - case TOK_AND: - case TOK_OR: - case TOK_ADD: - case TOK_MUL: - case TOK_DIV: - case TOK_MOD: - case TOK_ASSIGN: - case TOK_LTHAN: - case TOK_GTHAN: - return true; - default: - return false; - } + return type >= TOK_BINARY_OPS_BEGIN && type < TOK_BINARY_OPS_END; } ParseResult Binary() From 2c89b6029809f720e9233b3510c625766f466529 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 16:35:52 -0600 Subject: [PATCH 09/38] ExpressionParser: cleanup. --- .../ControlReference/ExpressionParser.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 58cf84cbed..33cf0b00c6 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -106,7 +106,7 @@ public: case TOK_OR: return "|"; case TOK_UNARY: - return "!" + data; + return '!' + data; case TOK_ADD: return "+"; case TOK_MUL: @@ -124,7 +124,7 @@ public: case TOK_COND: return "?"; case TOK_CONTROL: - return "Device(" + data + ")"; + return "Device(" + data + ')'; case TOK_LITERAL: return '\'' + data + '\''; case TOK_VARIABLE: @@ -422,7 +422,7 @@ public: operator std::string() const override { - return "!" + GetFuncName() + "(" + (std::string)(*inner) + ")"; + return '!' + GetFuncName() + '(' + static_cast(*inner) + ')'; } protected: @@ -493,7 +493,7 @@ class UnarySinExpression : public UnaryExpression public: UnarySinExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) {} - ControlState GetValue() const override { return std::cos(inner->GetValue()); } + ControlState GetValue() const override { return std::sin(inner->GetValue()); } void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "Sin"; } }; @@ -502,9 +502,10 @@ std::unique_ptr MakeUnaryExpression(std::string name, std::unique_ptr&& inner_) { // Case insensitive matching. - std::transform(name.begin(), name.end(), name.begin(), ::tolower); + std::transform(name.begin(), name.end(), name.begin(), + [](char c) { return std::tolower(c, std::locale::classic()); }); - if ("" == name) + if (name.empty()) return std::make_unique(std::move(inner_)); else if ("toggle" == name) return std::make_unique(std::move(inner_)); @@ -569,7 +570,8 @@ private: std::unique_ptr MakeLiteralExpression(std::string name) { // Case insensitive matching. - std::transform(name.begin(), name.end(), name.begin(), ::tolower); + std::transform(name.begin(), name.end(), name.begin(), + [](char c) { return std::tolower(c, std::locale::classic()); }); // Check for named literals: if ("timer" == name) From 46c0ae7d1fd0eeca12f3406be8919a5fac3d6847 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 17:32:32 -0600 Subject: [PATCH 10/38] ExpressionParser: Add !while loop unary expression. Limited to 10000 reps to prevent infinite loops. Rhs is re-evaluated until it is < 0.5. Added comma operator, which behaves like it does in c++. Added subration operator. --- .../ControlReference/ExpressionParser.cpp | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 33cf0b00c6..4d68a090d8 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -37,6 +37,7 @@ enum TokenType TOK_AND = TOK_BINARY_OPS_BEGIN, TOK_OR, TOK_ADD, + TOK_SUB, TOK_MUL, TOK_DIV, TOK_MOD, @@ -44,6 +45,7 @@ enum TokenType TOK_LTHAN, TOK_GTHAN, TOK_COND, + TOK_COMMA, TOK_BINARY_OPS_END, }; @@ -59,6 +61,8 @@ inline std::string OpName(TokenType op) return "Unary"; case TOK_ADD: return "Add"; + case TOK_SUB: + return "Sub"; case TOK_MUL: return "Mul"; case TOK_DIV: @@ -73,6 +77,8 @@ inline std::string OpName(TokenType op) return "GThan"; case TOK_COND: return "Cond"; + case TOK_COMMA: + return "Comma"; case TOK_VARIABLE: return "Var"; default: @@ -109,6 +115,8 @@ public: return '!' + data; case TOK_ADD: return "+"; + case TOK_SUB: + return "-"; case TOK_MUL: return "*"; case TOK_DIV: @@ -123,6 +131,8 @@ public: return ">"; case TOK_COND: return "?"; + case TOK_COMMA: + return ","; case TOK_CONTROL: return "Device(" + data + ')'; case TOK_LITERAL: @@ -236,6 +246,8 @@ public: return GetUnaryFunction(); case '+': return Token(TOK_ADD); + case '-': + return Token(TOK_SUB); case '*': return Token(TOK_MUL); case '/': @@ -250,6 +262,8 @@ public: return Token(TOK_GTHAN); case '?': return Token(TOK_COND); + case ',': + return Token(TOK_COMMA); case '\'': return GetLiteral(); case '$': @@ -352,6 +366,8 @@ public: return std::max(lhs->GetValue(), rhs->GetValue()); case TOK_ADD: return lhs->GetValue() + rhs->GetValue(); + case TOK_SUB: + return lhs->GetValue() - rhs->GetValue(); case TOK_MUL: return lhs->GetValue() * rhs->GetValue(); case TOK_DIV: @@ -381,6 +397,12 @@ public: else return 0.0; } + case TOK_COMMA: + { + // Eval and discard lhs: + lhs->GetValue(); + return rhs->GetValue(); + } default: assert(false); return 0; @@ -498,6 +520,32 @@ public: std::string GetFuncName() const override { return "Sin"; } }; +class UnaryWhileExpression : public UnaryExpression +{ +public: + UnaryWhileExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) {} + + ControlState GetValue() const override + { + constexpr int MAX_REPS = 10000; + constexpr int COND_THRESHOLD = 0.5; + + // Returns 1.0 on successful loop, 0.0 on reps exceeded. Sensible? + + for (int i = 0; i != MAX_REPS; ++i) + { + const ControlState val = inner->GetValue(); + if (val < COND_THRESHOLD) + return 1.0; + } + + // Exceeded max reps: + return 0.0; + } + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "Sin"; } +}; + std::unique_ptr MakeUnaryExpression(std::string name, std::unique_ptr&& inner_) { @@ -511,6 +559,8 @@ std::unique_ptr MakeUnaryExpression(std::string name, return std::make_unique(std::move(inner_)); else if ("sin" == name) return std::make_unique(std::move(inner_)); + else if ("while" == name) + return std::make_unique(std::move(inner_)); else return std::make_unique(std::move(inner_)); } From fa75ab404f065670671a614c7839a3f963ef352b Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 19:50:20 -0600 Subject: [PATCH 11/38] ExpressionParser: operator precedence. --- .../ControlReference/ExpressionParser.cpp | 80 ++++++++++++------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 4d68a090d8..a5c4036167 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -139,7 +139,7 @@ public: return '\'' + data + '\''; case TOK_VARIABLE: return '$' + data; - case TOK_INVALID: + default: break; } @@ -528,7 +528,7 @@ public: ControlState GetValue() const override { constexpr int MAX_REPS = 10000; - constexpr int COND_THRESHOLD = 0.5; + constexpr ControlState COND_THRESHOLD = 0.5; // Returns 1.0 on successful loop, 0.0 on reps exceeded. Sensible? @@ -543,7 +543,7 @@ public: return 0.0; } void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "Sin"; } + std::string GetFuncName() const override { return "While"; } }; std::unique_ptr MakeUnaryExpression(std::string name, @@ -782,22 +782,13 @@ private: } } - bool IsUnaryExpression(TokenType type) - { - switch (type) - { - case TOK_UNARY: - return true; - default: - return false; - } - } + static bool IsUnaryExpression(TokenType type) { return TOK_UNARY == type; } ParseResult Unary() { if (IsUnaryExpression(Peek().type)) { - Token tok = Chew(); + const Token tok = Chew(); ParseResult result = Atom(); if (result.status == ParseStatus::SyntaxError) return result; @@ -807,29 +798,60 @@ private: return Atom(); } - bool IsBinaryToken(TokenType type) + static bool IsBinaryToken(TokenType type) { return type >= TOK_BINARY_OPS_BEGIN && type < TOK_BINARY_OPS_END; } - ParseResult Binary() + static int BinaryOperatorPrecedence(TokenType type) { - ParseResult result = Unary(); - if (result.status == ParseStatus::SyntaxError) - return result; - - std::unique_ptr expr = std::move(result.expr); - while (IsBinaryToken(Peek().type)) + switch (type) { - Token tok = Chew(); - ParseResult unary_result = Unary(); - if (unary_result.status == ParseStatus::SyntaxError) + case TOK_MUL: + case TOK_DIV: + case TOK_MOD: + return 1; + case TOK_ADD: + case TOK_SUB: + return 2; + case TOK_GTHAN: + case TOK_LTHAN: + return 3; + case TOK_AND: + return 4; + case TOK_OR: + return 5; + case TOK_COND: + case TOK_ASSIGN: + return 6; + case TOK_COMMA: + return 7; + default: + assert(false); + return 0; + } + } + + ParseResult Binary(int precedence = 999) + { + ParseResult lhs = Unary(); + + if (lhs.status == ParseStatus::SyntaxError) + return lhs; + + std::unique_ptr expr = std::move(lhs.expr); + + // TODO: handle LTR/RTL associativity? + while (IsBinaryToken(Peek().type) && BinaryOperatorPrecedence(Peek().type) < precedence) + { + const Token tok = Chew(); + ParseResult rhs = Binary(BinaryOperatorPrecedence(tok.type)); + if (rhs.status == ParseStatus::SyntaxError) { - return unary_result; + return rhs; } - expr = std::make_unique(tok.type, std::move(expr), - std::move(unary_result.expr)); + expr = std::make_unique(tok.type, std::move(expr), std::move(rhs.expr)); } return {ParseStatus::Successful, std::move(expr)}; @@ -851,7 +873,7 @@ private: } ParseResult Toplevel() { return Binary(); } -}; +}; // namespace ExpressionParser static ParseResult ParseComplexExpression(const std::string& str) { From 785eb144322f8d77c3697ccc30cf98fc231ed239 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 5 Jan 2019 13:43:39 -0600 Subject: [PATCH 12/38] ExpressionParser: Clean up string lexing and support numeric literals without tick delimiter: e.g. 0.75 --- .../ControlReference/ExpressionParser.cpp | 75 +++++++++---------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index a5c4036167..af3b9c6fd3 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -155,72 +156,62 @@ public: Lexer(const std::string& expr_) : expr(expr_) { it = expr.begin(); } - bool FetchDelimString(std::string& value, char delim) + template + std::string FetchCharsWhile(F&& func) { - value = ""; - while (it != expr.end()) + std::string value; + while (it != expr.end() && func(*it)) { - char c = *it; + value += *it; ++it; - if (c == delim) - return true; - value += c; } - return false; + return value; + } + + std::string FetchDelimString(char delim) + { + const std::string result = FetchCharsWhile([delim](char c) { return c != delim; }); + ++it; + return result; } std::string FetchWordChars() { - std::string word; + // Valid word characters: + std::regex rx("[a-z0-9_]", std::regex_constants::icase); - std::regex valid_name_char("[a-z0-9_]", std::regex_constants::icase); - - while (it != expr.end() && std::regex_match(std::string(1, *it), valid_name_char)) - { - word += *it; - ++it; - } - - return word; + return FetchCharsWhile([&rx](char c) { return std::regex_match(std::string(1, c), rx); }); } Token GetUnaryFunction() { return Token(TOK_UNARY, FetchWordChars()); } - Token GetLiteral() - { - std::string value; - FetchDelimString(value, '\''); - return Token(TOK_LITERAL, value); - } + Token GetDelimitedLiteral() { return Token(TOK_LITERAL, FetchDelimString('\'')); } Token GetVariable() { return Token(TOK_VARIABLE, FetchWordChars()); } - Token GetFullyQualifiedControl() - { - std::string value; - FetchDelimString(value, '`'); - return Token(TOK_CONTROL, value); - } + Token GetFullyQualifiedControl() { return Token(TOK_CONTROL, FetchDelimString('`')); } Token GetBarewordsControl(char c) { std::string name; name += c; - - while (it != expr.end()) - { - c = *it; - if (!isalpha(c)) - break; - name += c; - ++it; - } + name += FetchCharsWhile([](char c) { return std::isalpha(c, std::locale::classic()); }); ControlQualifier qualifier; qualifier.control_name = name; return Token(TOK_CONTROL, qualifier); } + Token GetRealLiteral(char c) + { + std::string value; + value += c; + value += + FetchCharsWhile([](char c) { return isdigit(c, std::locale::classic()) || ('.' == c); }); + + return Token(TOK_LITERAL, value); + } + Token NextToken() { if (it == expr.end()) @@ -265,14 +256,16 @@ public: case ',': return Token(TOK_COMMA); case '\'': - return GetLiteral(); + return GetDelimitedLiteral(); case '$': return GetVariable(); case '`': return GetFullyQualifiedControl(); default: - if (isalpha(c)) + if (isalpha(c, std::locale::classic())) return GetBarewordsControl(c); + else if (isdigit(c, std::locale::classic())) + return GetRealLiteral(c); else return Token(TOK_INVALID); } From 4dd078568b360198e0d24acf10fede8e23cdb378 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 5 Jan 2019 15:31:05 -0600 Subject: [PATCH 13/38] ExpressionParser: Replace the timer literal with a timer function that increases from 0.0 to 1.0 and resets after N seconds. e.g. (!timer 2.0) is a 2 second timer. Fixed parsing of unary expressions so things like (! ! 1.0) work. --- .../ControlReference/ExpressionParser.cpp | 118 ++++++++++-------- 1 file changed, 65 insertions(+), 53 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index af3b9c6fd3..f8024c328f 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -171,12 +171,17 @@ public: std::string FetchDelimString(char delim) { const std::string result = FetchCharsWhile([delim](char c) { return c != delim; }); - ++it; + if (it != expr.end()) + ++it; return result; } std::string FetchWordChars() { + // Words must start with a letter or underscore. + if (expr.end() == it || (!std::isalpha(*it, std::locale::classic()) && ('_' != *it))) + return ""; + // Valid word characters: std::regex rx("[a-z0-9_]", std::regex_constants::icase); @@ -513,6 +518,46 @@ public: std::string GetFuncName() const override { return "Sin"; } }; +class UnaryTimerExpression : public UnaryExpression +{ +public: + UnaryTimerExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) {} + + ControlState GetValue() const override + { + const auto now = Clock::now(); + const auto elapsed = now - m_start_time; + + using FSec = std::chrono::duration; + + const ControlState val = inner->GetValue(); + + ControlState progress = std::chrono::duration_cast(elapsed).count() / val; + + if (std::isinf(progress)) + { + // User configured a 0.0 length timer. Reset the timer and return 0.0. + progress = 0.0; + m_start_time = now; + } + else if (progress >= 1.0) + { + const ControlState reset_count = std::floor(progress); + + m_start_time += std::chrono::duration_cast(FSec(val * reset_count)); + progress -= reset_count; + } + + return progress; + } + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "Timer"; } + +private: + using Clock = std::chrono::steady_clock; + mutable Clock::time_point m_start_time = Clock::now(); +}; + class UnaryWhileExpression : public UnaryExpression { public: @@ -548,10 +593,12 @@ std::unique_ptr MakeUnaryExpression(std::string name, if (name.empty()) return std::make_unique(std::move(inner_)); - else if ("toggle" == name) - return std::make_unique(std::move(inner_)); else if ("sin" == name) return std::make_unique(std::move(inner_)); + else if ("timer" == name) + return std::make_unique(std::move(inner_)); + else if ("toggle" == name) + return std::make_unique(std::move(inner_)); else if ("while" == name) return std::make_unique(std::move(inner_)); else @@ -592,42 +639,12 @@ private: const ControlState m_value{}; }; -// A +1.0 per second incrementing timer: -class LiteralTimer : public LiteralExpression -{ -public: - ControlState GetValue() const override - { - const auto ms = - std::chrono::duration_cast(Clock::now().time_since_epoch()); - // TODO: Will this roll over nicely? - return ms.count() / 1000.0; - } - - std::string GetName() const override { return "Timer"; } - -private: - using Clock = std::chrono::steady_clock; -}; - std::unique_ptr MakeLiteralExpression(std::string name) { - // Case insensitive matching. - std::transform(name.begin(), name.end(), name.begin(), - [](char c) { return std::tolower(c, std::locale::classic()); }); - - // Check for named literals: - if ("timer" == name) - { - return std::make_unique(); - } - else - { - // Assume it's a Real. If TryParse fails we'll just get a Zero. - ControlState val{}; - TryParse(name, &val); - return std::make_unique(val); - } + // If TryParse fails we'll just get a Zero. + ControlState val{}; + TryParse(name, &val); + return std::make_unique(val); } class VariableExpression : public Expression @@ -751,9 +768,16 @@ private: ParseResult Atom() { - Token tok = Chew(); + const Token tok = Chew(); switch (tok.type) { + case TOK_UNARY: + { + ParseResult result = Atom(); + if (result.status == ParseStatus::SyntaxError) + return result; + return {ParseStatus::Successful, MakeUnaryExpression(tok.data, std::move(result.expr))}; + } case TOK_CONTROL: { ControlQualifier cq; @@ -769,7 +793,9 @@ private: return {ParseStatus::Successful, std::make_unique(tok.data)}; } case TOK_LPAREN: + { return Paren(); + } default: return {ParseStatus::SyntaxError}; } @@ -777,20 +803,6 @@ private: static bool IsUnaryExpression(TokenType type) { return TOK_UNARY == type; } - ParseResult Unary() - { - if (IsUnaryExpression(Peek().type)) - { - const Token tok = Chew(); - ParseResult result = Atom(); - if (result.status == ParseStatus::SyntaxError) - return result; - return {ParseStatus::Successful, MakeUnaryExpression(tok.data, std::move(result.expr))}; - } - - return Atom(); - } - static bool IsBinaryToken(TokenType type) { return type >= TOK_BINARY_OPS_BEGIN && type < TOK_BINARY_OPS_END; @@ -827,7 +839,7 @@ private: ParseResult Binary(int precedence = 999) { - ParseResult lhs = Unary(); + ParseResult lhs = Atom(); if (lhs.status == ParseStatus::SyntaxError) return lhs; From 7cf903a2091ecdb47f59a3699cbcf5b9517f74d9 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 6 Jan 2019 09:08:35 -0600 Subject: [PATCH 14/38] ExpressionParser: Suppport N-ary functions. Arguments are read LISP style. N atoms are read after the function name. Added "if" function and made the "while" function more sensible with an arity of 2. Removed the ugly binary conditional operator. --- .../ControlReference/ExpressionParser.cpp | 178 ++++++++++-------- 1 file changed, 99 insertions(+), 79 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index f8024c328f..fb461e7fa3 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -22,6 +22,9 @@ namespace ciface::ExpressionParser { using namespace ciface::Core; +constexpr int LOOP_MAX_REPS = 10000; +constexpr ControlState CONDITION_THRESHOLD = 0.5; + enum TokenType { TOK_DISCARD, @@ -29,7 +32,7 @@ enum TokenType TOK_EOF, TOK_LPAREN, TOK_RPAREN, - TOK_UNARY, + TOK_FUNCTION, TOK_CONTROL, TOK_LITERAL, TOK_VARIABLE, @@ -45,7 +48,6 @@ enum TokenType TOK_ASSIGN, TOK_LTHAN, TOK_GTHAN, - TOK_COND, TOK_COMMA, TOK_BINARY_OPS_END, }; @@ -58,8 +60,8 @@ inline std::string OpName(TokenType op) return "And"; case TOK_OR: return "Or"; - case TOK_UNARY: - return "Unary"; + case TOK_FUNCTION: + return "Function"; case TOK_ADD: return "Add"; case TOK_SUB: @@ -76,8 +78,6 @@ inline std::string OpName(TokenType op) return "LThan"; case TOK_GTHAN: return "GThan"; - case TOK_COND: - return "Cond"; case TOK_COMMA: return "Comma"; case TOK_VARIABLE: @@ -112,7 +112,7 @@ public: return "&"; case TOK_OR: return "|"; - case TOK_UNARY: + case TOK_FUNCTION: return '!' + data; case TOK_ADD: return "+"; @@ -130,8 +130,6 @@ public: return "<"; case TOK_GTHAN: return ">"; - case TOK_COND: - return "?"; case TOK_COMMA: return ","; case TOK_CONTROL: @@ -188,7 +186,7 @@ public: return FetchCharsWhile([&rx](char c) { return std::regex_match(std::string(1, c), rx); }); } - Token GetUnaryFunction() { return Token(TOK_UNARY, FetchWordChars()); } + Token GetFunction() { return Token(TOK_FUNCTION, FetchWordChars()); } Token GetDelimitedLiteral() { return Token(TOK_LITERAL, FetchDelimString('\'')); } @@ -239,7 +237,7 @@ public: case '|': return Token(TOK_OR); case '!': - return GetUnaryFunction(); + return GetFunction(); case '+': return Token(TOK_ADD); case '-': @@ -256,8 +254,6 @@ public: return Token(TOK_LTHAN); case '>': return Token(TOK_GTHAN); - case '?': - return Token(TOK_COND); case ',': return Token(TOK_COMMA); case '\'': @@ -387,14 +383,6 @@ public: return lhs->GetValue() < rhs->GetValue(); case TOK_GTHAN: return lhs->GetValue() > rhs->GetValue(); - case TOK_COND: - { - constexpr ControlState COND_THRESHOLD = 0.5; - if (lhs->GetValue() > COND_THRESHOLD) - return rhs->GetValue(); - else - return 0.0; - } case TOK_COMMA: { // Eval and discard lhs: @@ -432,54 +420,70 @@ public: } }; -class UnaryExpression : public Expression +class FunctionExpression : public Expression { public: - UnaryExpression(std::unique_ptr&& inner_) : inner(std::move(inner_)) {} + int CountNumControls() const override + { + int result = 0; - int CountNumControls() const override { return inner->CountNumControls(); } - void UpdateReferences(ControlEnvironment& env) override { inner->UpdateReferences(env); } + for (auto& arg : m_args) + result += arg->CountNumControls(); + + return result; + } + + void UpdateReferences(ControlEnvironment& env) override + { + for (auto& arg : m_args) + arg->UpdateReferences(env); + } operator std::string() const override { - return '!' + GetFuncName() + '(' + static_cast(*inner) + ')'; + std::string result = '!' + GetFuncName(); + + for (auto& arg : m_args) + result += ' ' + static_cast(*arg); + + return result; } + void AppendArg(std::unique_ptr arg) { m_args.emplace_back(std::move(arg)); } + + Expression& GetArg(u32 number) { return *m_args[number]; } + const Expression& GetArg(u32 number) const { return *m_args[number]; } + virtual int GetArity() const = 0; + protected: virtual std::string GetFuncName() const = 0; - std::unique_ptr inner; +private: + std::vector> m_args; }; // TODO: Return an oscillating value to make it apparent something was spelled wrong? -class UnaryUnknownExpression : public UnaryExpression +class UnknownFunctionExpression : public FunctionExpression { public: - UnaryUnknownExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) - { - } - ControlState GetValue() const override { return 0.0; } void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "Unknown"; } + int GetArity() const override { return 0; } }; -class UnaryToggleExpression : public UnaryExpression +class ToggleExpression : public FunctionExpression { public: - UnaryToggleExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) - { - } - ControlState GetValue() const override { - const ControlState inner_value = inner->GetValue(); + const ControlState inner_value = GetArg(0).GetValue(); - if (inner_value < THRESHOLD) + if (inner_value < CONDITION_THRESHOLD) { m_released = true; } - else if (m_released && inner_value > THRESHOLD) + else if (m_released && inner_value > CONDITION_THRESHOLD) { m_released = false; m_state ^= true; @@ -490,39 +494,34 @@ public: void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "Toggle"; } + int GetArity() const override { return 1; } private: - static constexpr ControlState THRESHOLD = 0.5; - // eww: mutable bool m_released{}; mutable bool m_state{}; }; -class UnaryNotExpression : public UnaryExpression +class NotExpression : public FunctionExpression { public: - UnaryNotExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) {} - - ControlState GetValue() const override { return 1.0 - inner->GetValue(); } - void SetValue(ControlState value) override { inner->SetValue(1.0 - value); } + ControlState GetValue() const override { return 1.0 - GetArg(0).GetValue(); } + void SetValue(ControlState value) override { GetArg(0).SetValue(1.0 - value); } std::string GetFuncName() const override { return ""; } + int GetArity() const override { return 1; } }; -class UnarySinExpression : public UnaryExpression +class SinExpression : public FunctionExpression { public: - UnarySinExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) {} - - ControlState GetValue() const override { return std::sin(inner->GetValue()); } + ControlState GetValue() const override { return std::sin(GetArg(0).GetValue()); } void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "Sin"; } + int GetArity() const override { return 1; } }; -class UnaryTimerExpression : public UnaryExpression +class TimerExpression : public FunctionExpression { public: - UnaryTimerExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) {} - ControlState GetValue() const override { const auto now = Clock::now(); @@ -530,7 +529,7 @@ public: using FSec = std::chrono::duration; - const ControlState val = inner->GetValue(); + const ControlState val = GetArg(0).GetValue(); ControlState progress = std::chrono::duration_cast(elapsed).count() / val; @@ -552,57 +551,74 @@ public: } void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "Timer"; } + int GetArity() const override { return 1; } private: using Clock = std::chrono::steady_clock; mutable Clock::time_point m_start_time = Clock::now(); }; -class UnaryWhileExpression : public UnaryExpression +class IfExpression : public FunctionExpression { public: - UnaryWhileExpression(std::unique_ptr&& inner_) : UnaryExpression(std::move(inner_)) {} - ControlState GetValue() const override { - constexpr int MAX_REPS = 10000; - constexpr ControlState COND_THRESHOLD = 0.5; + return (GetArg(0).GetValue() > CONDITION_THRESHOLD) ? GetArg(1).GetValue() : + GetArg(2).GetValue(); + } + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "If"; } + int GetArity() const override { return 3; } +}; + +class WhileExpression : public FunctionExpression +{ +public: + ControlState GetValue() const override + { // Returns 1.0 on successful loop, 0.0 on reps exceeded. Sensible? - for (int i = 0; i != MAX_REPS; ++i) + for (int i = 0; i != LOOP_MAX_REPS; ++i) { - const ControlState val = inner->GetValue(); - if (val < COND_THRESHOLD) + // Check condition of 1st argument: + const ControlState val = GetArg(0).GetValue(); + if (val < CONDITION_THRESHOLD) return 1.0; + + // Evaluate 2nd argument: + GetArg(1).GetValue(); } // Exceeded max reps: return 0.0; } + void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "While"; } + int GetArity() const override { return 2; } }; -std::unique_ptr MakeUnaryExpression(std::string name, - std::unique_ptr&& inner_) +std::unique_ptr MakeFunctionExpression(std::string name) { // Case insensitive matching. std::transform(name.begin(), name.end(), name.begin(), [](char c) { return std::tolower(c, std::locale::classic()); }); if (name.empty()) - return std::make_unique(std::move(inner_)); + return std::make_unique(); + else if ("if" == name) + return std::make_unique(); else if ("sin" == name) - return std::make_unique(std::move(inner_)); + return std::make_unique(); else if ("timer" == name) - return std::make_unique(std::move(inner_)); + return std::make_unique(); else if ("toggle" == name) - return std::make_unique(std::move(inner_)); + return std::make_unique(); else if ("while" == name) - return std::make_unique(std::move(inner_)); + return std::make_unique(); else - return std::make_unique(std::move(inner_)); + return std::make_unique(); } class LiteralExpression : public Expression @@ -771,12 +787,19 @@ private: const Token tok = Chew(); switch (tok.type) { - case TOK_UNARY: + case TOK_FUNCTION: { - ParseResult result = Atom(); - if (result.status == ParseStatus::SyntaxError) - return result; - return {ParseStatus::Successful, MakeUnaryExpression(tok.data, std::move(result.expr))}; + auto func = MakeFunctionExpression(tok.data); + int arity = func->GetArity(); + while (arity--) + { + auto arg = Atom(); + if (arg.status == ParseStatus::SyntaxError) + return arg; + + func->AppendArg(std::move(arg.expr)); + } + return {ParseStatus::Successful, std::move(func)}; } case TOK_CONTROL: { @@ -801,8 +824,6 @@ private: } } - static bool IsUnaryExpression(TokenType type) { return TOK_UNARY == type; } - static bool IsBinaryToken(TokenType type) { return type >= TOK_BINARY_OPS_BEGIN && type < TOK_BINARY_OPS_END; @@ -826,7 +847,6 @@ private: return 4; case TOK_OR: return 5; - case TOK_COND: case TOK_ASSIGN: return 6; case TOK_COMMA: From ccac3f1e495531f30764a1961a52f73131e09b9f Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 6 Jan 2019 10:03:21 -0600 Subject: [PATCH 15/38] ExpressionParser: Fix negative literals and support unary minus operator. --- .../ControlReference/ExpressionParser.cpp | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index fb461e7fa3..85e19da972 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -572,6 +572,20 @@ public: int GetArity() const override { return 3; } }; +class UnaryMinusExpression : public FunctionExpression +{ +public: + ControlState GetValue() const override + { + // Subtraction for clarity: + return 0.0 - GetArg(0).GetValue(); + } + + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "Minus"; } + int GetArity() const override { return 1; } +}; + class WhileExpression : public FunctionExpression { public: @@ -617,6 +631,8 @@ std::unique_ptr MakeFunctionExpression(std::string name) return std::make_unique(); else if ("while" == name) return std::make_unique(); + else if ("minus" == name) + return std::make_unique(); else return std::make_unique(); } @@ -782,9 +798,8 @@ private: return tok.type == type; } - ParseResult Atom() + ParseResult Atom(const Token& tok) { - const Token tok = Chew(); switch (tok.type) { case TOK_FUNCTION: @@ -793,7 +808,7 @@ private: int arity = func->GetArity(); while (arity--) { - auto arg = Atom(); + auto arg = Atom(Chew()); if (arg.status == ParseStatus::SyntaxError) return arg; @@ -819,6 +834,12 @@ private: { return Paren(); } + case TOK_SUB: + { + // An atom was expected but we got a subtraction symbol. + // Interpret it as a unary minus function. + return Atom(Token(TOK_FUNCTION, "minus")); + } default: return {ParseStatus::SyntaxError}; } @@ -859,7 +880,7 @@ private: ParseResult Binary(int precedence = 999) { - ParseResult lhs = Atom(); + ParseResult lhs = Atom(Chew()); if (lhs.status == ParseStatus::SyntaxError) return lhs; From 258832b1e89b62d582e42d23099570f03c407885 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 8 Jan 2019 18:26:36 -0600 Subject: [PATCH 16/38] ExpressionParser: Change function argument syntax to something more c++-like. --- .../ControlReference/ExpressionParser.cpp | 135 ++++++++++++++---- 1 file changed, 106 insertions(+), 29 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 85e19da972..3838a6a7e9 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -449,14 +449,19 @@ public: return result; } - void AppendArg(std::unique_ptr arg) { m_args.emplace_back(std::move(arg)); } + bool SetArguments(std::vector>&& args) + { + m_args = std::move(args); - Expression& GetArg(u32 number) { return *m_args[number]; } - const Expression& GetArg(u32 number) const { return *m_args[number]; } - virtual int GetArity() const = 0; + return ValidateArguments(m_args); + } protected: virtual std::string GetFuncName() const = 0; + virtual bool ValidateArguments(const std::vector>& args) = 0; + + Expression& GetArg(u32 number) { return *m_args[number]; } + const Expression& GetArg(u32 number) const { return *m_args[number]; } private: std::vector> m_args; @@ -465,16 +470,24 @@ private: // TODO: Return an oscillating value to make it apparent something was spelled wrong? class UnknownFunctionExpression : public FunctionExpression { -public: +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return false; + } ControlState GetValue() const override { return 0.0; } void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "Unknown"; } - int GetArity() const override { return 0; } }; class ToggleExpression : public FunctionExpression { -public: +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 1 == args.size(); + } + ControlState GetValue() const override { const ControlState inner_value = GetArg(0).GetValue(); @@ -494,34 +507,45 @@ public: void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "Toggle"; } - int GetArity() const override { return 1; } -private: mutable bool m_released{}; mutable bool m_state{}; }; class NotExpression : public FunctionExpression { -public: +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 1 == args.size(); + } + ControlState GetValue() const override { return 1.0 - GetArg(0).GetValue(); } void SetValue(ControlState value) override { GetArg(0).SetValue(1.0 - value); } std::string GetFuncName() const override { return ""; } - int GetArity() const override { return 1; } }; class SinExpression : public FunctionExpression { -public: +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 1 == args.size(); + } + ControlState GetValue() const override { return std::sin(GetArg(0).GetValue()); } void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "Sin"; } - int GetArity() const override { return 1; } }; class TimerExpression : public FunctionExpression { -public: +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 1 == args.size(); + } + ControlState GetValue() const override { const auto now = Clock::now(); @@ -551,7 +575,6 @@ public: } void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "Timer"; } - int GetArity() const override { return 1; } private: using Clock = std::chrono::steady_clock; @@ -560,7 +583,12 @@ private: class IfExpression : public FunctionExpression { -public: +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 3 == args.size(); + } + ControlState GetValue() const override { return (GetArg(0).GetValue() > CONDITION_THRESHOLD) ? GetArg(1).GetValue() : @@ -569,12 +597,16 @@ public: void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "If"; } - int GetArity() const override { return 3; } }; class UnaryMinusExpression : public FunctionExpression { -public: +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 1 == args.size(); + } + ControlState GetValue() const override { // Subtraction for clarity: @@ -583,12 +615,15 @@ public: void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "Minus"; } - int GetArity() const override { return 1; } }; class WhileExpression : public FunctionExpression { -public: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 2 == args.size(); + } + ControlState GetValue() const override { // Returns 1.0 on successful loop, 0.0 on reps exceeded. Sensible? @@ -610,7 +645,6 @@ public: void SetValue(ControlState value) override {} std::string GetFuncName() const override { return "While"; } - int GetArity() const override { return 2; } }; std::unique_ptr MakeFunctionExpression(std::string name) @@ -787,17 +821,61 @@ public: ParseResult Parse() { return Toplevel(); } private: + struct FunctionArguments + { + FunctionArguments(ParseStatus status_, std::vector>&& args_ = {}) + : status(status_), args(std::move(args_)) + { + } + + ParseStatus status; + std::vector> args; + }; + std::vector tokens; std::vector::iterator m_it; Token Chew() { return *m_it++; } Token Peek() { return *m_it; } + bool Expects(TokenType type) { Token tok = Chew(); return tok.type == type; } + FunctionArguments ParseFunctionArguments() + { + if (!Expects(TOK_LPAREN)) + return {ParseStatus::SyntaxError}; + + // Check for empty argument list: + if (TOK_RPAREN == Peek().type) + return {ParseStatus::Successful}; + + std::vector> args; + + while (true) + { + // Read one argument. + // Grab an expression, but stop at comma. + auto arg = Binary(BinaryOperatorPrecedence(TOK_COMMA)); + if (ParseStatus::Successful != arg.status) + return {ParseStatus::SyntaxError}; + + args.emplace_back(std::move(arg.expr)); + + // Right paren is the end of our arguments. + const Token tok = Chew(); + if (TOK_RPAREN == tok.type) + return {ParseStatus::Successful, std::move(args)}; + + // Comma before the next argument. + if (TOK_COMMA != tok.type) + return {ParseStatus::SyntaxError}; + } + } + ParseResult Atom(const Token& tok) { switch (tok.type) @@ -805,15 +883,14 @@ private: case TOK_FUNCTION: { auto func = MakeFunctionExpression(tok.data); - int arity = func->GetArity(); - while (arity--) - { - auto arg = Atom(Chew()); - if (arg.status == ParseStatus::SyntaxError) - return arg; + auto args = ParseFunctionArguments(); + + if (ParseStatus::Successful != args.status) + return {ParseStatus::SyntaxError}; + + if (!func->SetArguments(std::move(args.args))) + return {ParseStatus::SyntaxError}; - func->AppendArg(std::move(arg.expr)); - } return {ParseStatus::Successful, std::move(func)}; } case TOK_CONTROL: From 2b0297489fc2ab56a0f8aab1c39747f2e48c4429 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 8 Jan 2019 18:36:58 -0600 Subject: [PATCH 17/38] ExpressionParser: Rename some functions and return a syntax error on trailing tokens. --- .../ControlReference/ExpressionParser.cpp | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 3838a6a7e9..cff32c774c 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -818,7 +818,15 @@ class Parser { public: explicit Parser(std::vector tokens_) : tokens(tokens_) { m_it = tokens.begin(); } - ParseResult Parse() { return Toplevel(); } + ParseResult Parse() + { + ParseResult result = ParseToplevel(); + + if (Peek().type == TOK_EOF) + return result; + + return {ParseStatus::SyntaxError}; + } private: struct FunctionArguments @@ -859,7 +867,7 @@ private: { // Read one argument. // Grab an expression, but stop at comma. - auto arg = Binary(BinaryOperatorPrecedence(TOK_COMMA)); + auto arg = ParseBinary(BinaryOperatorPrecedence(TOK_COMMA)); if (ParseStatus::Successful != arg.status) return {ParseStatus::SyntaxError}; @@ -876,7 +884,7 @@ private: } } - ParseResult Atom(const Token& tok) + ParseResult ParseAtom(const Token& tok) { switch (tok.type) { @@ -909,13 +917,13 @@ private: } case TOK_LPAREN: { - return Paren(); + return ParseParens(); } case TOK_SUB: { // An atom was expected but we got a subtraction symbol. // Interpret it as a unary minus function. - return Atom(Token(TOK_FUNCTION, "minus")); + return ParseAtom(Token(TOK_FUNCTION, "minus")); } default: return {ParseStatus::SyntaxError}; @@ -955,9 +963,9 @@ private: } } - ParseResult Binary(int precedence = 999) + ParseResult ParseBinary(int precedence = 999) { - ParseResult lhs = Atom(Chew()); + ParseResult lhs = ParseAtom(Chew()); if (lhs.status == ParseStatus::SyntaxError) return lhs; @@ -968,7 +976,7 @@ private: while (IsBinaryToken(Peek().type) && BinaryOperatorPrecedence(Peek().type) < precedence) { const Token tok = Chew(); - ParseResult rhs = Binary(BinaryOperatorPrecedence(tok.type)); + ParseResult rhs = ParseBinary(BinaryOperatorPrecedence(tok.type)); if (rhs.status == ParseStatus::SyntaxError) { return rhs; @@ -980,10 +988,10 @@ private: return {ParseStatus::Successful, std::move(expr)}; } - ParseResult Paren() + ParseResult ParseParens() { // lparen already chewed - ParseResult result = Toplevel(); + ParseResult result = ParseToplevel(); if (result.status != ParseStatus::Successful) return result; @@ -995,7 +1003,7 @@ private: return result; } - ParseResult Toplevel() { return Binary(); } + ParseResult ParseToplevel() { return ParseBinary(); } }; // namespace ExpressionParser static ParseResult ParseComplexExpression(const std::string& str) From 2a377e35ed3f3afefdb5dce5474f2c5bb9c89a54 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 8 Jan 2019 20:34:24 -0600 Subject: [PATCH 18/38] ExpressionParser: Make function names case sensitive. --- .../ControlReference/ExpressionParser.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index cff32c774c..d845939cc3 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -477,7 +477,7 @@ private: } ControlState GetValue() const override { return 0.0; } void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "Unknown"; } + std::string GetFuncName() const override { return "unknown"; } }; class ToggleExpression : public FunctionExpression @@ -506,7 +506,7 @@ private: } void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "Toggle"; } + std::string GetFuncName() const override { return "toggle"; } mutable bool m_released{}; mutable bool m_state{}; @@ -535,7 +535,7 @@ private: ControlState GetValue() const override { return std::sin(GetArg(0).GetValue()); } void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "Sin"; } + std::string GetFuncName() const override { return "sin"; } }; class TimerExpression : public FunctionExpression @@ -574,7 +574,7 @@ private: return progress; } void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "Timer"; } + std::string GetFuncName() const override { return "timer"; } private: using Clock = std::chrono::steady_clock; @@ -596,7 +596,7 @@ private: } void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "If"; } + std::string GetFuncName() const override { return "if"; } }; class UnaryMinusExpression : public FunctionExpression @@ -614,7 +614,7 @@ private: } void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "Minus"; } + std::string GetFuncName() const override { return "minus"; } }; class WhileExpression : public FunctionExpression @@ -644,15 +644,11 @@ class WhileExpression : public FunctionExpression } void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "While"; } + std::string GetFuncName() const override { return "while"; } }; std::unique_ptr MakeFunctionExpression(std::string name) { - // Case insensitive matching. - std::transform(name.begin(), name.end(), name.begin(), - [](char c) { return std::tolower(c, std::locale::classic()); }); - if (name.empty()) return std::make_unique(); else if ("if" == name) From d4f9b8c4efe9ce48aac5b43487c5d6c5d5fbd83b Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 20 Jan 2019 17:44:01 -0600 Subject: [PATCH 19/38] ExpressionParser: Allow unary functions to be used without parens around the argument. e.g. !`Up` --- .../ControlReference/ExpressionParser.cpp | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index d845939cc3..1c9ea370ce 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -839,7 +839,14 @@ private: std::vector tokens; std::vector::iterator m_it; - Token Chew() { return *m_it++; } + Token Chew() + { + const Token tok = Peek(); + if (TOK_EOF != tok.type) + ++m_it; + return tok; + } + Token Peek() { return *m_it; } bool Expects(TokenType type) @@ -850,14 +857,28 @@ private: FunctionArguments ParseFunctionArguments() { - if (!Expects(TOK_LPAREN)) - return {ParseStatus::SyntaxError}; + std::vector> args; + + if (TOK_LPAREN != Peek().type) + { + // Single argument with no parens (useful for unary ! function) + auto arg = ParseAtom(Chew()); + if (ParseStatus::Successful != arg.status) + return {ParseStatus::SyntaxError}; + + args.emplace_back(std::move(arg.expr)); + return {ParseStatus::Successful, std::move(args)}; + } + + // Chew the L-Paren + Chew(); // Check for empty argument list: if (TOK_RPAREN == Peek().type) + { + Chew(); return {ParseStatus::Successful}; - - std::vector> args; + } while (true) { From fd07ae8cec5c77ac60788350610739e6ac094547 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 26 Jan 2019 12:17:30 -0600 Subject: [PATCH 20/38] ExpressionParser: Move FunctionExpression type definitions into another file. --- Source/Core/InputCommon/CMakeLists.txt | 2 + .../ControlReference/ExpressionParser.cpp | 258 +---------------- .../ControlReference/ExpressionParser.h | 2 +- .../ControlReference/FunctionExpression.cpp | 261 ++++++++++++++++++ .../ControlReference/FunctionExpression.h | 41 +++ Source/Core/InputCommon/InputCommon.vcxproj | 2 + .../InputCommon/InputCommon.vcxproj.filters | 6 + 7 files changed, 316 insertions(+), 256 deletions(-) create mode 100644 Source/Core/InputCommon/ControlReference/FunctionExpression.cpp create mode 100644 Source/Core/InputCommon/ControlReference/FunctionExpression.h diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index df8fa6423c..b47503a9ae 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -45,6 +45,8 @@ add_library(inputcommon ControlReference/ControlReference.h ControlReference/ExpressionParser.cpp ControlReference/ExpressionParser.h + ControlReference/FunctionExpression.cpp + ControlReference/FunctionExpression.h ) target_link_libraries(inputcommon PUBLIC diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 1c9ea370ce..b0704d3d8d 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -2,29 +2,24 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include #include -#include #include #include -#include -#include #include #include #include +#include #include -#include "Common/MathUtil.h" #include "Common/StringUtil.h" + #include "InputCommon/ControlReference/ExpressionParser.h" +#include "InputCommon/ControlReference/FunctionExpression.h" namespace ciface::ExpressionParser { using namespace ciface::Core; -constexpr int LOOP_MAX_REPS = 10000; -constexpr ControlState CONDITION_THRESHOLD = 0.5; - enum TokenType { TOK_DISCARD, @@ -420,253 +415,6 @@ public: } }; -class FunctionExpression : public Expression -{ -public: - int CountNumControls() const override - { - int result = 0; - - for (auto& arg : m_args) - result += arg->CountNumControls(); - - return result; - } - - void UpdateReferences(ControlEnvironment& env) override - { - for (auto& arg : m_args) - arg->UpdateReferences(env); - } - - operator std::string() const override - { - std::string result = '!' + GetFuncName(); - - for (auto& arg : m_args) - result += ' ' + static_cast(*arg); - - return result; - } - - bool SetArguments(std::vector>&& args) - { - m_args = std::move(args); - - return ValidateArguments(m_args); - } - -protected: - virtual std::string GetFuncName() const = 0; - virtual bool ValidateArguments(const std::vector>& args) = 0; - - Expression& GetArg(u32 number) { return *m_args[number]; } - const Expression& GetArg(u32 number) const { return *m_args[number]; } - -private: - std::vector> m_args; -}; - -// TODO: Return an oscillating value to make it apparent something was spelled wrong? -class UnknownFunctionExpression : public FunctionExpression -{ -private: - virtual bool ValidateArguments(const std::vector>& args) override - { - return false; - } - ControlState GetValue() const override { return 0.0; } - void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "unknown"; } -}; - -class ToggleExpression : public FunctionExpression -{ -private: - virtual bool ValidateArguments(const std::vector>& args) override - { - return 1 == args.size(); - } - - ControlState GetValue() const override - { - const ControlState inner_value = GetArg(0).GetValue(); - - if (inner_value < CONDITION_THRESHOLD) - { - m_released = true; - } - else if (m_released && inner_value > CONDITION_THRESHOLD) - { - m_released = false; - m_state ^= true; - } - - return m_state; - } - - void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "toggle"; } - - mutable bool m_released{}; - mutable bool m_state{}; -}; - -class NotExpression : public FunctionExpression -{ -private: - virtual bool ValidateArguments(const std::vector>& args) override - { - return 1 == args.size(); - } - - ControlState GetValue() const override { return 1.0 - GetArg(0).GetValue(); } - void SetValue(ControlState value) override { GetArg(0).SetValue(1.0 - value); } - std::string GetFuncName() const override { return ""; } -}; - -class SinExpression : public FunctionExpression -{ -private: - virtual bool ValidateArguments(const std::vector>& args) override - { - return 1 == args.size(); - } - - ControlState GetValue() const override { return std::sin(GetArg(0).GetValue()); } - void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "sin"; } -}; - -class TimerExpression : public FunctionExpression -{ -private: - virtual bool ValidateArguments(const std::vector>& args) override - { - return 1 == args.size(); - } - - ControlState GetValue() const override - { - const auto now = Clock::now(); - const auto elapsed = now - m_start_time; - - using FSec = std::chrono::duration; - - const ControlState val = GetArg(0).GetValue(); - - ControlState progress = std::chrono::duration_cast(elapsed).count() / val; - - if (std::isinf(progress)) - { - // User configured a 0.0 length timer. Reset the timer and return 0.0. - progress = 0.0; - m_start_time = now; - } - else if (progress >= 1.0) - { - const ControlState reset_count = std::floor(progress); - - m_start_time += std::chrono::duration_cast(FSec(val * reset_count)); - progress -= reset_count; - } - - return progress; - } - void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "timer"; } - -private: - using Clock = std::chrono::steady_clock; - mutable Clock::time_point m_start_time = Clock::now(); -}; - -class IfExpression : public FunctionExpression -{ -private: - virtual bool ValidateArguments(const std::vector>& args) override - { - return 3 == args.size(); - } - - ControlState GetValue() const override - { - return (GetArg(0).GetValue() > CONDITION_THRESHOLD) ? GetArg(1).GetValue() : - GetArg(2).GetValue(); - } - - void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "if"; } -}; - -class UnaryMinusExpression : public FunctionExpression -{ -private: - virtual bool ValidateArguments(const std::vector>& args) override - { - return 1 == args.size(); - } - - ControlState GetValue() const override - { - // Subtraction for clarity: - return 0.0 - GetArg(0).GetValue(); - } - - void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "minus"; } -}; - -class WhileExpression : public FunctionExpression -{ - virtual bool ValidateArguments(const std::vector>& args) override - { - return 2 == args.size(); - } - - ControlState GetValue() const override - { - // Returns 1.0 on successful loop, 0.0 on reps exceeded. Sensible? - - for (int i = 0; i != LOOP_MAX_REPS; ++i) - { - // Check condition of 1st argument: - const ControlState val = GetArg(0).GetValue(); - if (val < CONDITION_THRESHOLD) - return 1.0; - - // Evaluate 2nd argument: - GetArg(1).GetValue(); - } - - // Exceeded max reps: - return 0.0; - } - - void SetValue(ControlState value) override {} - std::string GetFuncName() const override { return "while"; } -}; - -std::unique_ptr MakeFunctionExpression(std::string name) -{ - if (name.empty()) - return std::make_unique(); - else if ("if" == name) - return std::make_unique(); - else if ("sin" == name) - return std::make_unique(); - else if ("timer" == name) - return std::make_unique(); - else if ("toggle" == name) - return std::make_unique(); - else if ("while" == name) - return std::make_unique(); - else if ("minus" == name) - return std::make_unique(); - else - return std::make_unique(); -} - class LiteralExpression : public Expression { public: diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.h b/Source/Core/InputCommon/ControlReference/ExpressionParser.h index d9f7a5eb2b..0cb84af5cd 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.h +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.h @@ -7,7 +7,7 @@ #include #include #include -#include + #include "InputCommon/ControllerInterface/Device.h" namespace ciface::ExpressionParser diff --git a/Source/Core/InputCommon/ControlReference/FunctionExpression.cpp b/Source/Core/InputCommon/ControlReference/FunctionExpression.cpp new file mode 100644 index 0000000000..15a66ec9d1 --- /dev/null +++ b/Source/Core/InputCommon/ControlReference/FunctionExpression.cpp @@ -0,0 +1,261 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include + +#include "InputCommon/ControlReference/FunctionExpression.h" + +namespace ciface +{ +namespace ExpressionParser +{ +constexpr int LOOP_MAX_REPS = 10000; +constexpr ControlState CONDITION_THRESHOLD = 0.5; + +// TODO: Return an oscillating value to make it apparent something was spelled wrong? +class UnknownFunctionExpression : public FunctionExpression +{ +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return false; + } + ControlState GetValue() const override { return 0.0; } + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "unknown"; } +}; + +class ToggleExpression : public FunctionExpression +{ +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 1 == args.size(); + } + + ControlState GetValue() const override + { + const ControlState inner_value = GetArg(0).GetValue(); + + if (inner_value < CONDITION_THRESHOLD) + { + m_released = true; + } + else if (m_released && inner_value > CONDITION_THRESHOLD) + { + m_released = false; + m_state ^= true; + } + + return m_state; + } + + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "toggle"; } + + mutable bool m_released{}; + mutable bool m_state{}; +}; + +class NotExpression : public FunctionExpression +{ +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 1 == args.size(); + } + + ControlState GetValue() const override { return 1.0 - GetArg(0).GetValue(); } + void SetValue(ControlState value) override { GetArg(0).SetValue(1.0 - value); } + std::string GetFuncName() const override { return ""; } +}; + +class SinExpression : public FunctionExpression +{ +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 1 == args.size(); + } + + ControlState GetValue() const override { return std::sin(GetArg(0).GetValue()); } + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "sin"; } +}; + +class TimerExpression : public FunctionExpression +{ +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 1 == args.size(); + } + + ControlState GetValue() const override + { + const auto now = Clock::now(); + const auto elapsed = now - m_start_time; + + using FSec = std::chrono::duration; + + const ControlState val = GetArg(0).GetValue(); + + ControlState progress = std::chrono::duration_cast(elapsed).count() / val; + + if (std::isinf(progress)) + { + // User configured a 0.0 length timer. Reset the timer and return 0.0. + progress = 0.0; + m_start_time = now; + } + else if (progress >= 1.0) + { + const ControlState reset_count = std::floor(progress); + + m_start_time += std::chrono::duration_cast(FSec(val * reset_count)); + progress -= reset_count; + } + + return progress; + } + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "timer"; } + +private: + using Clock = std::chrono::steady_clock; + mutable Clock::time_point m_start_time = Clock::now(); +}; + +class IfExpression : public FunctionExpression +{ +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 3 == args.size(); + } + + ControlState GetValue() const override + { + return (GetArg(0).GetValue() > CONDITION_THRESHOLD) ? GetArg(1).GetValue() : + GetArg(2).GetValue(); + } + + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "if"; } +}; + +class UnaryMinusExpression : public FunctionExpression +{ +private: + virtual bool ValidateArguments(const std::vector>& args) override + { + return 1 == args.size(); + } + + ControlState GetValue() const override + { + // Subtraction for clarity: + return 0.0 - GetArg(0).GetValue(); + } + + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "minus"; } +}; + +class WhileExpression : public FunctionExpression +{ + virtual bool ValidateArguments(const std::vector>& args) override + { + return 2 == args.size(); + } + + ControlState GetValue() const override + { + // Returns 1.0 on successful loop, 0.0 on reps exceeded. Sensible? + + for (int i = 0; i != LOOP_MAX_REPS; ++i) + { + // Check condition of 1st argument: + const ControlState val = GetArg(0).GetValue(); + if (val < CONDITION_THRESHOLD) + return 1.0; + + // Evaluate 2nd argument: + GetArg(1).GetValue(); + } + + // Exceeded max reps: + return 0.0; + } + + void SetValue(ControlState value) override {} + std::string GetFuncName() const override { return "while"; } +}; + +std::unique_ptr MakeFunctionExpression(std::string name) +{ + if (name.empty()) + return std::make_unique(); + else if ("if" == name) + return std::make_unique(); + else if ("sin" == name) + return std::make_unique(); + else if ("timer" == name) + return std::make_unique(); + else if ("toggle" == name) + return std::make_unique(); + else if ("while" == name) + return std::make_unique(); + else if ("minus" == name) + return std::make_unique(); + else + return std::make_unique(); +} + +int FunctionExpression::CountNumControls() const +{ + int result = 0; + + for (auto& arg : m_args) + result += arg->CountNumControls(); + + return result; +} + +void FunctionExpression::UpdateReferences(ControlEnvironment& env) +{ + for (auto& arg : m_args) + arg->UpdateReferences(env); +} + +FunctionExpression::operator std::string() const +{ + std::string result = '!' + GetFuncName(); + + for (auto& arg : m_args) + result += ' ' + static_cast(*arg); + + return result; +} + +bool FunctionExpression::SetArguments(std::vector>&& args) +{ + m_args = std::move(args); + + return ValidateArguments(m_args); +} + +Expression& FunctionExpression::GetArg(u32 number) +{ + return *m_args[number]; +} + +const Expression& FunctionExpression::GetArg(u32 number) const +{ + return *m_args[number]; +} + +} // namespace ExpressionParser +} // namespace ciface diff --git a/Source/Core/InputCommon/ControlReference/FunctionExpression.h b/Source/Core/InputCommon/ControlReference/FunctionExpression.h new file mode 100644 index 0000000000..8ac7e9800c --- /dev/null +++ b/Source/Core/InputCommon/ControlReference/FunctionExpression.h @@ -0,0 +1,41 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "InputCommon/ControlReference/ExpressionParser.h" +#include "InputCommon/ControlReference/FunctionExpression.h" + +namespace ciface +{ +namespace ExpressionParser +{ +class FunctionExpression : public Expression +{ +public: + int CountNumControls() const override; + void UpdateReferences(ControlEnvironment& env) override; + operator std::string() const override; + + bool SetArguments(std::vector>&& args); + +protected: + virtual std::string GetFuncName() const = 0; + virtual bool ValidateArguments(const std::vector>& args) = 0; + + Expression& GetArg(u32 number); + const Expression& GetArg(u32 number) const; + +private: + std::vector> m_args; +}; + +std::unique_ptr MakeFunctionExpression(std::string name); + +} // namespace ExpressionParser +} // namespace ciface diff --git a/Source/Core/InputCommon/InputCommon.vcxproj b/Source/Core/InputCommon/InputCommon.vcxproj index 8d0b0b3759..a22ad21394 100644 --- a/Source/Core/InputCommon/InputCommon.vcxproj +++ b/Source/Core/InputCommon/InputCommon.vcxproj @@ -64,6 +64,7 @@ +