From f3192ca06de1007545bf6c3a49ddc83fb615a642 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 10:37:23 -0600 Subject: ExpressionParser: Add support for literals. --- .../ControlReference/ExpressionParser.cpp | 79 ++++++++++++++++------ 1 file changed, 58 insertions(+), 21 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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 GetFullyQualifiedControl() + Token GetLiteral() { - ControlQualifier qualifier; std::string value; + FetchDelimString(value, '\''); + return Token(TOK_LITERAL, 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); + Token GetFullyQualifiedControl() + { + std::string value; + 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: -- cgit v1.2.3 From bf63f85d732db2fdab169823f2cf8a5fcbde0ec4 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 11:51:12 -0600 Subject: 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(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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; -- cgit v1.2.3 From a8f3e9585f98c91c2b6220b3ab6951484192266e Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 12:38:02 -0600 Subject: 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 | 130 +++++++++++++++------ 1 file changed, 97 insertions(+), 33 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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; + 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 "!" + GetFuncName() + "(" + (std::string)(*inner) + ")"; + } + +protected: + virtual std::string GetFuncName() const = 0; + std::unique_ptr inner; +}; - UnaryExpression(TokenType op_, std::unique_ptr&& inner_) - : op(op_), inner(std::move(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 + + 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 value = inner->GetValue(); - switch (op) - { - case TOK_NOT: - return 1.0 - value; - default: - assert(false); - return 0; - } } - void SetValue(ControlState value) override + ControlState GetValue() const override { - switch (op) - { - case TOK_NOT: - inner->SetValue(1.0 - value); - break; + const ControlState inner_value = inner->GetValue(); - default: - assert(false); + if (inner_value < THRESHOLD) + { + m_released = true; + } + else if (m_released && inner_value > THRESHOLD) + { + m_released = false; + m_state ^= true; } + + return m_state; } - 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) + ")"; } + 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(); -- cgit v1.2.3 From 1efcf861ead76bbffb4ac22726d799975146707c Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 13:16:28 -0600 Subject: 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(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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; -- cgit v1.2.3 From e896835f86e52051b1998a344549dfa8ba79847a Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 16:06:29 -0600 Subject: 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) --- .../ControlReference/ExpressionParser.cpp | 124 ++++++++++++++++----- 1 file changed, 96 insertions(+), 28 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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; -- cgit v1.2.3 From 718efce1dce86e18fb42c319627b5490cd1f8d94 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 16:11:42 -0600 Subject: ExpressionParser: Add less-than and greater-than operators. --- .../ControlReference/ExpressionParser.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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; -- cgit v1.2.3 From 58efc93ed4ff6ef3ba7c4c4116d0cef47c63700d Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 16:29:48 -0600 Subject: 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(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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() -- cgit v1.2.3 From 2c89b6029809f720e9233b3510c625766f466529 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 16:35:52 -0600 Subject: ExpressionParser: cleanup. --- .../InputCommon/ControlReference/ExpressionParser.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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) -- cgit v1.2.3 From 46c0ae7d1fd0eeca12f3406be8919a5fac3d6847 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 17:32:32 -0600 Subject: 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(+) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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_)); } -- cgit v1.2.3 From fa75ab404f065670671a614c7839a3f963ef352b Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 30 Dec 2018 19:50:20 -0600 Subject: ExpressionParser: operator precedence. --- .../ControlReference/ExpressionParser.cpp | 78 ++++++++++++++-------- 1 file changed, 50 insertions(+), 28 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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; + switch (type) + { + 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(result.expr); - while (IsBinaryToken(Peek().type)) + std::unique_ptr expr = std::move(lhs.expr); + + // TODO: handle LTR/RTL associativity? + while (IsBinaryToken(Peek().type) && BinaryOperatorPrecedence(Peek().type) < precedence) { - Token tok = Chew(); - ParseResult unary_result = Unary(); - if (unary_result.status == ParseStatus::SyntaxError) + 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) { -- cgit v1.2.3 From 785eb144322f8d77c3697ccc30cf98fc231ed239 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 5 Jan 2019 13:43:39 -0600 Subject: 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(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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 FetchWordChars() + std::string FetchDelimString(char delim) { - std::string word; - - std::regex valid_name_char("[a-z0-9_]", std::regex_constants::icase); + const std::string result = FetchCharsWhile([delim](char c) { return c != delim; }); + ++it; + return result; + } - while (it != expr.end() && std::regex_match(std::string(1, *it), valid_name_char)) - { - word += *it; - ++it; - } + std::string FetchWordChars() + { + // Valid word characters: + std::regex rx("[a-z0-9_]", std::regex_constants::icase); - 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); } -- cgit v1.2.3 From 4dd078568b360198e0d24acf10fede8e23cdb378 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 5 Jan 2019 15:31:05 -0600 Subject: 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(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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; -- cgit v1.2.3 From 7cf903a2091ecdb47f59a3699cbcf5b9517f74d9 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 6 Jan 2019 09:08:35 -0600 Subject: 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(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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: -- cgit v1.2.3 From ccac3f1e495531f30764a1961a52f73131e09b9f Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 6 Jan 2019 10:03:21 -0600 Subject: ExpressionParser: Fix negative literals and support unary minus operator. --- .../ControlReference/ExpressionParser.cpp | 29 +++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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; -- cgit v1.2.3 From 258832b1e89b62d582e42d23099570f03c407885 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 8 Jan 2019 18:26:36 -0600 Subject: ExpressionParser: Change function argument syntax to something more c++-like. --- .../ControlReference/ExpressionParser.cpp | 135 ++++++++++++++++----- 1 file changed, 106 insertions(+), 29 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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: -- cgit v1.2.3 From 2b0297489fc2ab56a0f8aab1c39747f2e48c4429 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 8 Jan 2019 18:36:58 -0600 Subject: ExpressionParser: Rename some functions and return a syntax error on trailing tokens. --- .../ControlReference/ExpressionParser.cpp | 30 ++++++++++++++-------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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) -- cgit v1.2.3 From 2a377e35ed3f3afefdb5dce5474f2c5bb9c89a54 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 8 Jan 2019 20:34:24 -0600 Subject: ExpressionParser: Make function names case sensitive. --- .../InputCommon/ControlReference/ExpressionParser.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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) -- cgit v1.2.3 From d4f9b8c4efe9ce48aac5b43487c5d6c5d5fbd83b Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 20 Jan 2019 17:44:01 -0600 Subject: 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(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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) { -- cgit v1.2.3 From fd07ae8cec5c77ac60788350610739e6ac094547 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 26 Jan 2019 12:17:30 -0600 Subject: ExpressionParser: Move FunctionExpression type definitions into another file. --- .../ControlReference/ExpressionParser.cpp | 258 +-------------------- 1 file changed, 3 insertions(+), 255 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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: -- cgit v1.2.3 From c8b2188e1972f14aec24ac66908a641dbdbd106f Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 2 Mar 2019 10:10:26 -0600 Subject: DolphinQT: Add syntax highlighting from tokenizer data. --- .../ControlReference/ExpressionParser.cpp | 394 ++++++++++----------- 1 file changed, 181 insertions(+), 213 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index b0704d3d8d..08278154f4 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -20,33 +20,6 @@ namespace ciface::ExpressionParser { using namespace ciface::Core; -enum TokenType -{ - TOK_DISCARD, - TOK_INVALID, - TOK_EOF, - TOK_LPAREN, - TOK_RPAREN, - TOK_FUNCTION, - TOK_CONTROL, - TOK_LITERAL, - TOK_VARIABLE, - // Binary Ops: - TOK_BINARY_OPS_BEGIN, - TOK_AND = TOK_BINARY_OPS_BEGIN, - TOK_OR, - TOK_ADD, - TOK_SUB, - TOK_MUL, - TOK_DIV, - TOK_MOD, - TOK_ASSIGN, - TOK_LTHAN, - TOK_GTHAN, - TOK_COMMA, - TOK_BINARY_OPS_END, -}; - inline std::string OpName(TokenType op) { switch (op) @@ -83,213 +56,213 @@ inline std::string OpName(TokenType op) } } -class Token +Token::Token(TokenType type_) : type(type_) { -public: - TokenType type; - std::string data; - - Token(TokenType type_) : type(type_) {} - Token(TokenType type_, std::string data_) : type(type_), data(std::move(data_)) {} - operator std::string() const - { - switch (type) - { - case TOK_DISCARD: - return "Discard"; - case TOK_EOF: - return "EOF"; - case TOK_LPAREN: - return "("; - case TOK_RPAREN: - return ")"; - case TOK_AND: - return "&"; - case TOK_OR: - return "|"; - case TOK_FUNCTION: - return '!' + data; - case TOK_ADD: - return "+"; - case TOK_SUB: - return "-"; - case TOK_MUL: - return "*"; - case TOK_DIV: - return "/"; - case TOK_MOD: - return "%"; - case TOK_ASSIGN: - return "="; - case TOK_LTHAN: - return "<"; - case TOK_GTHAN: - return ">"; - case TOK_COMMA: - return ","; - case TOK_CONTROL: - return "Device(" + data + ')'; - case TOK_LITERAL: - return '\'' + data + '\''; - case TOK_VARIABLE: - return '$' + data; - default: - break; - } - - return "Invalid"; - } -}; +} -class Lexer +Token::Token(TokenType type_, std::string data_) : type(type_), data(std::move(data_)) { -public: - std::string expr; - std::string::iterator it; +} - Lexer(const std::string& expr_) : expr(expr_) { it = expr.begin(); } +bool Token::IsBinaryOperator() const +{ + return type >= TOK_BINARY_OPS_BEGIN && type < TOK_BINARY_OPS_END; +} - template - std::string FetchCharsWhile(F&& func) - { - std::string value; - while (it != expr.end() && func(*it)) - { - value += *it; - ++it; - } - return value; +Token::operator std::string() const +{ + switch (type) + { + case TOK_DISCARD: + return "Discard"; + case TOK_EOF: + return "EOF"; + case TOK_LPAREN: + return "("; + case TOK_RPAREN: + return ")"; + case TOK_AND: + return "&"; + case TOK_OR: + return "|"; + case TOK_FUNCTION: + return '!' + data; + case TOK_ADD: + return "+"; + case TOK_SUB: + return "-"; + case TOK_MUL: + return "*"; + case TOK_DIV: + return "/"; + case TOK_MOD: + return "%"; + case TOK_ASSIGN: + return "="; + case TOK_LTHAN: + return "<"; + case TOK_GTHAN: + return ">"; + case TOK_COMMA: + return ","; + case TOK_CONTROL: + return "Device(" + data + ')'; + case TOK_LITERAL: + return '\'' + data + '\''; + case TOK_VARIABLE: + return '$' + data; + default: + break; } - std::string FetchDelimString(char delim) - { - const std::string result = FetchCharsWhile([delim](char c) { return c != delim; }); - if (it != expr.end()) - ++it; - return result; - } + return "Invalid"; +} - std::string FetchWordChars() - { - // Words must start with a letter or underscore. - if (expr.end() == it || (!std::isalpha(*it, std::locale::classic()) && ('_' != *it))) - return ""; +Lexer::Lexer(const std::string& expr_) : expr(expr_) +{ + it = expr.begin(); +} - // Valid word characters: - std::regex rx("[a-z0-9_]", std::regex_constants::icase); +std::string Lexer::FetchDelimString(char delim) +{ + const std::string result = FetchCharsWhile([delim](char c) { return c != delim; }); + if (it != expr.end()) + ++it; + return result; +} - return FetchCharsWhile([&rx](char c) { return std::regex_match(std::string(1, c), rx); }); - } +std::string Lexer::FetchWordChars() +{ + // Words must start with a letter or underscore. + if (expr.end() == it || (!std::isalpha(*it, std::locale::classic()) && ('_' != *it))) + return ""; - Token GetFunction() { return Token(TOK_FUNCTION, FetchWordChars()); } + // Valid word characters: + std::regex rx("[a-z0-9_]", std::regex_constants::icase); - Token GetDelimitedLiteral() { return Token(TOK_LITERAL, FetchDelimString('\'')); } + return FetchCharsWhile([&rx](char c) { return std::regex_match(std::string(1, c), rx); }); +} - Token GetVariable() { return Token(TOK_VARIABLE, FetchWordChars()); } +Token Lexer::GetFunction() +{ + return Token(TOK_FUNCTION, FetchWordChars()); +} - Token GetFullyQualifiedControl() { return Token(TOK_CONTROL, FetchDelimString('`')); } +Token Lexer::GetDelimitedLiteral() +{ + return Token(TOK_LITERAL, FetchDelimString('\'')); +} - Token GetBarewordsControl(char c) - { - std::string name; - name += c; - name += FetchCharsWhile([](char c) { return std::isalpha(c, std::locale::classic()); }); +Token Lexer::GetVariable() +{ + return Token(TOK_VARIABLE, FetchWordChars()); +} - ControlQualifier qualifier; - qualifier.control_name = name; - return Token(TOK_CONTROL, qualifier); - } +Token Lexer::GetFullyQualifiedControl() +{ + return Token(TOK_CONTROL, FetchDelimString('`')); +} - Token GetRealLiteral(char c) - { - std::string value; - value += c; - value += - FetchCharsWhile([](char c) { return isdigit(c, std::locale::classic()) || ('.' == c); }); +Token Lexer::GetBarewordsControl(char c) +{ + std::string name; + name += c; + name += FetchCharsWhile([](char c) { return std::isalpha(c, std::locale::classic()); }); - return Token(TOK_LITERAL, value); - } + ControlQualifier qualifier; + qualifier.control_name = name; + return Token(TOK_CONTROL, qualifier); +} - Token NextToken() - { - if (it == expr.end()) - return Token(TOK_EOF); +Token Lexer::GetRealLiteral(char c) +{ + std::string value; + value += c; + value += FetchCharsWhile([](char c) { return isdigit(c, std::locale::classic()) || ('.' == c); }); - char c = *it++; - switch (c) - { - case ' ': - case '\t': - case '\n': - case '\r': - return Token(TOK_DISCARD); - case '(': - return Token(TOK_LPAREN); - case ')': - return Token(TOK_RPAREN); - case '&': - return Token(TOK_AND); - case '|': - return Token(TOK_OR); - case '!': - return GetFunction(); - case '+': - return Token(TOK_ADD); - case '-': - return Token(TOK_SUB); - case '*': - return Token(TOK_MUL); - case '/': - return Token(TOK_DIV); - case '%': - return Token(TOK_MOD); - case '=': - return Token(TOK_ASSIGN); - case '<': - return Token(TOK_LTHAN); - case '>': - return Token(TOK_GTHAN); - case ',': - return Token(TOK_COMMA); - case '\'': - return GetDelimitedLiteral(); - case '$': - return GetVariable(); - case '`': - return GetFullyQualifiedControl(); - default: - if (isalpha(c, std::locale::classic())) - return GetBarewordsControl(c); - else if (isdigit(c, std::locale::classic())) - return GetRealLiteral(c); - else - return Token(TOK_INVALID); - } + return Token(TOK_LITERAL, value); +} + +Token Lexer::NextToken() +{ + if (it == expr.end()) + return Token(TOK_EOF); + + char c = *it++; + switch (c) + { + case ' ': + case '\t': + case '\n': + case '\r': + return Token(TOK_DISCARD); + case '(': + return Token(TOK_LPAREN); + case ')': + return Token(TOK_RPAREN); + case '&': + return Token(TOK_AND); + case '|': + return Token(TOK_OR); + case '!': + return GetFunction(); + case '+': + return Token(TOK_ADD); + case '-': + return Token(TOK_SUB); + case '*': + return Token(TOK_MUL); + case '/': + return Token(TOK_DIV); + case '%': + return Token(TOK_MOD); + case '=': + return Token(TOK_ASSIGN); + case '<': + return Token(TOK_LTHAN); + case '>': + return Token(TOK_GTHAN); + case ',': + return Token(TOK_COMMA); + case '\'': + return GetDelimitedLiteral(); + case '$': + return GetVariable(); + case '`': + return GetFullyQualifiedControl(); + default: + if (isalpha(c, std::locale::classic())) + return GetBarewordsControl(c); + else if (isdigit(c, std::locale::classic())) + return GetRealLiteral(c); + else + return Token(TOK_INVALID); } +} - ParseStatus Tokenize(std::vector& tokens) +ParseStatus Lexer::Tokenize(std::vector& tokens) +{ + while (true) { - while (true) - { - Token tok = NextToken(); + const std::size_t string_position = it - expr.begin(); + Token tok = NextToken(); - if (tok.type == TOK_DISCARD) - continue; + tok.string_position = string_position; + tok.string_length = it - expr.begin(); - if (tok.type == TOK_INVALID) - { - tokens.clear(); - return ParseStatus::SyntaxError; - } + if (tok.type == TOK_DISCARD) + continue; - tokens.push_back(tok); + tokens.push_back(tok); - if (tok.type == TOK_EOF) - break; - } - return ParseStatus::Successful; + if (tok.type == TOK_INVALID) + return ParseStatus::SyntaxError; + + if (tok.type == TOK_EOF) + break; } -}; + return ParseStatus::Successful; +} class ControlExpression : public Expression { @@ -418,7 +391,7 @@ public: class LiteralExpression : public Expression { public: - void SetValue(ControlState value) override + void SetValue(ControlState) override { // Do nothing. } @@ -695,11 +668,6 @@ private: } } - static bool IsBinaryToken(TokenType type) - { - return type >= TOK_BINARY_OPS_BEGIN && type < TOK_BINARY_OPS_END; - } - static int BinaryOperatorPrecedence(TokenType type) { switch (type) @@ -738,7 +706,7 @@ private: std::unique_ptr expr = std::move(lhs.expr); // TODO: handle LTR/RTL associativity? - while (IsBinaryToken(Peek().type) && BinaryOperatorPrecedence(Peek().type) < precedence) + while (Peek().IsBinaryOperator() && BinaryOperatorPrecedence(Peek().type) < precedence) { const Token tok = Chew(); ParseResult rhs = ParseBinary(BinaryOperatorPrecedence(tok.type)); -- cgit v1.2.3 From ca7ce674500b7664ed049ac1019a2e40636187dc Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 2 Mar 2019 14:47:26 -0600 Subject: ExpressionParser/DolphinQt: Added parse results to UI. --- .../ControlReference/ExpressionParser.cpp | 145 +++++++++++++-------- 1 file changed, 94 insertions(+), 51 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 08278154f4..a686e8fcc4 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -11,6 +11,7 @@ #include #include +#include "Common/Common.h" #include "Common/StringUtil.h" #include "InputCommon/ControlReference/ExpressionParser.h" @@ -138,7 +139,7 @@ std::string Lexer::FetchWordChars() return ""; // Valid word characters: - std::regex rx("[a-z0-9_]", std::regex_constants::icase); + std::regex rx(R"([a-z\d_])", std::regex_constants::icase); return FetchCharsWhile([&rx](char c) { return std::regex_match(std::string(1, c), rx); }); } @@ -180,7 +181,10 @@ Token Lexer::GetRealLiteral(char c) value += c; value += FetchCharsWhile([](char c) { return isdigit(c, std::locale::classic()) || ('.' == c); }); - return Token(TOK_LITERAL, value); + if (std::regex_match(value, std::regex(R"(\d+(\.\d+)?)"))) + return Token(TOK_LITERAL, value); + + return Token(TOK_INVALID); } Token Lexer::NextToken() @@ -267,8 +271,7 @@ ParseStatus Lexer::Tokenize(std::vector& tokens) class ControlExpression : public Expression { public: - // 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: + // Keep a shared_ptr to the device so the control pointer doesn't become invalid. std::shared_ptr m_device; explicit ControlExpression(ControlQualifier qualifier_) : qualifier(qualifier_) {} @@ -384,7 +387,7 @@ public: operator std::string() const override { - return OpName(op) + "(" + (std::string)(*lhs) + ", " + (std::string)(*rhs) + ")"; + return OpName(op) + "(" + std::string(*lhs) + ", " + std::string(*rhs) + ")"; } }; @@ -422,12 +425,13 @@ private: const ControlState m_value{}; }; -std::unique_ptr MakeLiteralExpression(std::string name) +ParseResult MakeLiteralExpression(Token token) { - // If TryParse fails we'll just get a Zero. ControlState val{}; - TryParse(name, &val); - return std::make_unique(val); + if (TryParse(token.data, &val)) + return ParseResult::MakeSuccessfulResult(std::make_unique(val)); + else + return ParseResult::MakeErrorResult(token, _trans("Invalid literal.")); } class VariableExpression : public Expression @@ -520,45 +524,63 @@ ControlState* ControlEnvironment::GetVariablePtr(const std::string& name) return &m_variables[name]; } -struct ParseResult +ParseResult ParseResult::MakeEmptyResult() { - ParseResult(ParseStatus status_, std::unique_ptr&& expr_ = {}) - : status(status_), expr(std::move(expr_)) - { - } + ParseResult result; + result.status = ParseStatus::EmptyExpression; + return result; +} - ParseStatus status; - std::unique_ptr expr; -}; +ParseResult ParseResult::MakeSuccessfulResult(std::unique_ptr&& expr) +{ + ParseResult result; + result.status = ParseStatus::Successful; + result.expr = std::move(expr); + return result; +} + +ParseResult ParseResult::MakeErrorResult(Token token, std::string description) +{ + ParseResult result; + result.status = ParseStatus::SyntaxError; + result.token = std::move(token); + result.description = std::move(description); + return result; +} class Parser { public: - explicit Parser(std::vector tokens_) : tokens(tokens_) { m_it = tokens.begin(); } + explicit Parser(const std::vector& tokens_) : tokens(tokens_) { m_it = tokens.begin(); } ParseResult Parse() { ParseResult result = ParseToplevel(); + if (ParseStatus::Successful != result.status) + return result; + if (Peek().type == TOK_EOF) return result; - return {ParseStatus::SyntaxError}; + return ParseResult::MakeErrorResult(Peek(), _trans("Expected EOF.")); } private: struct FunctionArguments { - FunctionArguments(ParseStatus status_, std::vector>&& args_ = {}) - : status(status_), args(std::move(args_)) + FunctionArguments(ParseResult&& result_, std::vector>&& args_ = {}) + : result(std::move(result_)), args(std::move(args_)) { } - ParseStatus status; + // Note: expression member isn't being used. + ParseResult result; + std::vector> args; }; - std::vector tokens; - std::vector::iterator m_it; + const std::vector& tokens; + std::vector::const_iterator m_it; Token Chew() { @@ -585,10 +607,10 @@ private: // Single argument with no parens (useful for unary ! function) auto arg = ParseAtom(Chew()); if (ParseStatus::Successful != arg.status) - return {ParseStatus::SyntaxError}; + return {std::move(arg)}; args.emplace_back(std::move(arg.expr)); - return {ParseStatus::Successful, std::move(args)}; + return {ParseResult::MakeSuccessfulResult({}), std::move(args)}; } // Chew the L-Paren @@ -598,7 +620,7 @@ private: if (TOK_RPAREN == Peek().type) { Chew(); - return {ParseStatus::Successful}; + return {ParseResult::MakeSuccessfulResult({})}; } while (true) @@ -607,18 +629,18 @@ private: // Grab an expression, but stop at comma. auto arg = ParseBinary(BinaryOperatorPrecedence(TOK_COMMA)); if (ParseStatus::Successful != arg.status) - return {ParseStatus::SyntaxError}; + return {std::move(arg)}; 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)}; + return {ParseResult::MakeSuccessfulResult({}), std::move(args)}; // Comma before the next argument. if (TOK_COMMA != tok.type) - return {ParseStatus::SyntaxError}; + return {ParseResult::MakeErrorResult(tok, _trans("Expected comma."))}; } } @@ -629,29 +651,36 @@ private: case TOK_FUNCTION: { auto func = MakeFunctionExpression(tok.data); + + if (!func) + return ParseResult::MakeErrorResult(tok, _trans("Unknown function.")); + auto args = ParseFunctionArguments(); - if (ParseStatus::Successful != args.status) - return {ParseStatus::SyntaxError}; + if (ParseStatus::Successful != args.result.status) + return std::move(args.result); if (!func->SetArguments(std::move(args.args))) - return {ParseStatus::SyntaxError}; + { + // TODO: It would be nice to output how many arguments are expected. + return ParseResult::MakeErrorResult(tok, _trans("Wrong number of arguments.")); + } - return {ParseStatus::Successful, std::move(func)}; + return ParseResult::MakeSuccessfulResult(std::move(func)); } case TOK_CONTROL: { ControlQualifier cq; cq.FromString(tok.data); - return {ParseStatus::Successful, std::make_unique(cq)}; + return ParseResult::MakeSuccessfulResult(std::make_unique(cq)); } case TOK_LITERAL: { - return {ParseStatus::Successful, MakeLiteralExpression(tok.data)}; + return MakeLiteralExpression(tok); } case TOK_VARIABLE: { - return {ParseStatus::Successful, std::make_unique(tok.data)}; + return ParseResult::MakeSuccessfulResult(std::make_unique(tok.data)); } case TOK_LPAREN: { @@ -661,10 +690,15 @@ private: { // An atom was expected but we got a subtraction symbol. // Interpret it as a unary minus function. - return ParseAtom(Token(TOK_FUNCTION, "minus")); + + // Make sure to copy the existing string position values for proper error results. + Token func = tok; + func.type = TOK_FUNCTION; + func.data = "minus"; + return ParseAtom(std::move(func)); } default: - return {ParseStatus::SyntaxError}; + return ParseResult::MakeErrorResult(tok, _trans("Expected start of expression.")); } } @@ -718,7 +752,7 @@ private: expr = std::make_unique(tok.type, std::move(expr), std::move(rhs.expr)); } - return {ParseStatus::Successful, std::move(expr)}; + return ParseResult::MakeSuccessfulResult(std::move(expr)); } ParseResult ParseParens() @@ -728,9 +762,10 @@ private: if (result.status != ParseStatus::Successful) return result; - if (!Expects(TOK_RPAREN)) + const auto rparen = Chew(); + if (rparen.type != TOK_RPAREN) { - return {ParseStatus::SyntaxError}; + return ParseResult::MakeErrorResult(rparen, _trans("Expected closing paren.")); } return result; @@ -739,15 +774,20 @@ private: ParseResult ParseToplevel() { return ParseBinary(); } }; // namespace ExpressionParser +ParseResult ParseTokens(const std::vector& tokens) +{ + return Parser(tokens).Parse(); +} + static ParseResult ParseComplexExpression(const std::string& str) { Lexer l(str); std::vector tokens; - ParseStatus tokenize_status = l.Tokenize(tokens); + const ParseStatus tokenize_status = l.Tokenize(tokens); if (tokenize_status != ParseStatus::Successful) - return {tokenize_status}; + return ParseResult::MakeErrorResult(Token(TOK_INVALID), _trans("Tokenizing failed.")); - return Parser(std::move(tokens)).Parse(); + return ParseTokens(tokens); } static std::unique_ptr ParseBarewordExpression(const std::string& str) @@ -759,21 +799,24 @@ static std::unique_ptr ParseBarewordExpression(const std::string& st return std::make_unique(qualifier); } -std::pair> ParseExpression(const std::string& str) +ParseResult ParseExpression(const std::string& str) { if (StripSpaces(str).empty()) - return std::make_pair(ParseStatus::EmptyExpression, nullptr); + return ParseResult::MakeEmptyResult(); auto bareword_expr = ParseBarewordExpression(str); ParseResult complex_result = ParseComplexExpression(str); if (complex_result.status != ParseStatus::Successful) { - return std::make_pair(complex_result.status, std::move(bareword_expr)); + // This is a bit odd. + // Return the error status of the complex expression with the fallback barewords expression. + complex_result.expr = std::move(bareword_expr); + return complex_result; } - auto combined_expr = std::make_unique(std::move(bareword_expr), - std::move(complex_result.expr)); - return std::make_pair(complex_result.status, std::move(combined_expr)); + complex_result.expr = std::make_unique(std::move(bareword_expr), + std::move(complex_result.expr)); + return complex_result; } } // namespace ciface::ExpressionParser -- cgit v1.2.3 From b57178d246640270779d791b00d3a5b8fd7c13f2 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 4 Apr 2019 17:35:49 -0500 Subject: ExpressionParser: Remove ! character from function syntax. Remove unused serialization functions. --- .../ControlReference/ExpressionParser.cpp | 240 +++++---------------- 1 file changed, 59 insertions(+), 181 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index a686e8fcc4..f7937a19bd 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -21,42 +21,6 @@ namespace ciface::ExpressionParser { using namespace ciface::Core; -inline std::string OpName(TokenType op) -{ - switch (op) - { - case TOK_AND: - return "And"; - case TOK_OR: - return "Or"; - case TOK_FUNCTION: - return "Function"; - case TOK_ADD: - return "Add"; - case TOK_SUB: - return "Sub"; - case TOK_MUL: - return "Mul"; - case TOK_DIV: - return "Div"; - case TOK_MOD: - return "Mod"; - case TOK_ASSIGN: - return "Assign"; - case TOK_LTHAN: - return "LThan"; - case TOK_GTHAN: - return "GThan"; - case TOK_COMMA: - return "Comma"; - case TOK_VARIABLE: - return "Var"; - default: - assert(false); - return ""; - } -} - Token::Token(TokenType type_) : type(type_) { } @@ -70,55 +34,6 @@ bool Token::IsBinaryOperator() const return type >= TOK_BINARY_OPS_BEGIN && type < TOK_BINARY_OPS_END; } -Token::operator std::string() const -{ - switch (type) - { - case TOK_DISCARD: - return "Discard"; - case TOK_EOF: - return "EOF"; - case TOK_LPAREN: - return "("; - case TOK_RPAREN: - return ")"; - case TOK_AND: - return "&"; - case TOK_OR: - return "|"; - case TOK_FUNCTION: - return '!' + data; - case TOK_ADD: - return "+"; - case TOK_SUB: - return "-"; - case TOK_MUL: - return "*"; - case TOK_DIV: - return "/"; - case TOK_MOD: - return "%"; - case TOK_ASSIGN: - return "="; - case TOK_LTHAN: - return "<"; - case TOK_GTHAN: - return ">"; - case TOK_COMMA: - return ","; - case TOK_CONTROL: - return "Device(" + data + ')'; - case TOK_LITERAL: - return '\'' + data + '\''; - case TOK_VARIABLE: - return '$' + data; - default: - break; - } - - return "Invalid"; -} - Lexer::Lexer(const std::string& expr_) : expr(expr_) { it = expr.begin(); @@ -134,21 +49,12 @@ std::string Lexer::FetchDelimString(char delim) std::string Lexer::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(R"([a-z\d_])", std::regex_constants::icase); return FetchCharsWhile([&rx](char c) { return std::regex_match(std::string(1, c), rx); }); } -Token Lexer::GetFunction() -{ - return Token(TOK_FUNCTION, FetchWordChars()); -} - Token Lexer::GetDelimitedLiteral() { return Token(TOK_LITERAL, FetchDelimString('\'')); @@ -164,21 +70,15 @@ Token Lexer::GetFullyQualifiedControl() return Token(TOK_CONTROL, FetchDelimString('`')); } -Token Lexer::GetBarewordsControl(char c) +Token Lexer::GetBareword(char first_char) { - std::string name; - name += c; - name += FetchCharsWhile([](char c) { return std::isalpha(c, std::locale::classic()); }); - - ControlQualifier qualifier; - qualifier.control_name = name; - return Token(TOK_CONTROL, qualifier); + return Token(TOK_BAREWORD, first_char + FetchWordChars()); } -Token Lexer::GetRealLiteral(char c) +Token Lexer::GetRealLiteral(char first_char) { std::string value; - value += c; + value += first_char; value += FetchCharsWhile([](char c) { return isdigit(c, std::locale::classic()) || ('.' == c); }); if (std::regex_match(value, std::regex(R"(\d+(\.\d+)?)"))) @@ -209,7 +109,7 @@ Token Lexer::NextToken() case '|': return Token(TOK_OR); case '!': - return GetFunction(); + return Token(TOK_NOT); case '+': return Token(TOK_ADD); case '-': @@ -236,7 +136,7 @@ Token Lexer::NextToken() return GetFullyQualifiedControl(); default: if (isalpha(c, std::locale::classic())) - return GetBarewordsControl(c); + return GetBareword(c); else if (isdigit(c, std::locale::classic())) return GetRealLiteral(c); else @@ -300,7 +200,6 @@ public: input = env.FindInput(qualifier); output = env.FindOutput(qualifier); } - operator std::string() const override { return "`" + static_cast(qualifier) + "`"; } private: ControlQualifier qualifier; @@ -384,11 +283,6 @@ public: lhs->UpdateReferences(env); rhs->UpdateReferences(env); } - - operator std::string() const override - { - return OpName(op) + "(" + std::string(*lhs) + ", " + std::string(*rhs) + ")"; - } }; class LiteralExpression : public Expression @@ -406,8 +300,6 @@ public: // Nothing needed. } - operator std::string() const override { return '\'' + GetName() + '\''; } - protected: virtual std::string GetName() const = 0; }; @@ -450,8 +342,6 @@ public: 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{}; @@ -471,12 +361,6 @@ public: void SetValue(ControlState value) override { GetActiveChild()->SetValue(value); } int CountNumControls() const override { return GetActiveChild()->CountNumControls(); } - operator std::string() const override - { - return "Coalesce(" + static_cast(*m_lhs) + ", " + - static_cast(*m_rhs) + ')'; - } - void UpdateReferences(ControlEnvironment& env) override { m_lhs->UpdateReferences(env); @@ -566,19 +450,6 @@ public: } private: - struct FunctionArguments - { - FunctionArguments(ParseResult&& result_, std::vector>&& args_ = {}) - : result(std::move(result_)), args(std::move(args_)) - { - } - - // Note: expression member isn't being used. - ParseResult result; - - std::vector> args; - }; - const std::vector& tokens; std::vector::const_iterator m_it; @@ -598,75 +469,81 @@ private: return tok.type == type; } - FunctionArguments ParseFunctionArguments() + ParseResult ParseFunctionArguments(std::unique_ptr&& func, + const Token& func_tok) { std::vector> args; if (TOK_LPAREN != Peek().type) { // Single argument with no parens (useful for unary ! function) - auto arg = ParseAtom(Chew()); + const auto tok = Chew(); + auto arg = ParseAtom(tok); if (ParseStatus::Successful != arg.status) - return {std::move(arg)}; + return arg; args.emplace_back(std::move(arg.expr)); - return {ParseResult::MakeSuccessfulResult({}), std::move(args)}; } - - // Chew the L-Paren - Chew(); - - // Check for empty argument list: - if (TOK_RPAREN == Peek().type) + else { + // Chew the L-Paren Chew(); - return {ParseResult::MakeSuccessfulResult({})}; + + // Check for empty argument list: + if (TOK_RPAREN == Peek().type) + { + Chew(); + } + else + { + while (true) + { + // Read one argument. + // Grab an expression, but stop at comma. + auto arg = ParseBinary(BinaryOperatorPrecedence(TOK_COMMA)); + if (ParseStatus::Successful != arg.status) + return arg; + + args.emplace_back(std::move(arg.expr)); + + // Right paren is the end of our arguments. + const Token tok = Chew(); + if (TOK_RPAREN == tok.type) + break; + + // Comma before the next argument. + if (TOK_COMMA != tok.type) + return ParseResult::MakeErrorResult(tok, _trans("Expected comma.")); + }; + } } - while (true) + if (!func->SetArguments(std::move(args))) { - // Read one argument. - // Grab an expression, but stop at comma. - auto arg = ParseBinary(BinaryOperatorPrecedence(TOK_COMMA)); - if (ParseStatus::Successful != arg.status) - return {std::move(arg)}; - - 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 {ParseResult::MakeSuccessfulResult({}), std::move(args)}; - - // Comma before the next argument. - if (TOK_COMMA != tok.type) - return {ParseResult::MakeErrorResult(tok, _trans("Expected comma."))}; + // TODO: It would be nice to output how many arguments are expected. + return ParseResult::MakeErrorResult(func_tok, _trans("Wrong number of arguments.")); } + + return ParseResult::MakeSuccessfulResult(std::move(func)); } ParseResult ParseAtom(const Token& tok) { switch (tok.type) { - case TOK_FUNCTION: + case TOK_BAREWORD: { auto func = MakeFunctionExpression(tok.data); if (!func) - return ParseResult::MakeErrorResult(tok, _trans("Unknown function.")); - - auto args = ParseFunctionArguments(); - - if (ParseStatus::Successful != args.result.status) - return std::move(args.result); - - if (!func->SetArguments(std::move(args.args))) { - // TODO: It would be nice to output how many arguments are expected. - return ParseResult::MakeErrorResult(tok, _trans("Wrong number of arguments.")); + // Invalid function, interpret this as a bareword control. + Token control_tok(tok); + control_tok.type = TOK_CONTROL; + return ParseAtom(control_tok); } - return ParseResult::MakeSuccessfulResult(std::move(func)); + return ParseFunctionArguments(std::move(func), tok); } case TOK_CONTROL: { @@ -674,6 +551,10 @@ private: cq.FromString(tok.data); return ParseResult::MakeSuccessfulResult(std::make_unique(cq)); } + case TOK_NOT: + { + return ParseFunctionArguments(MakeFunctionExpression("not"), tok); + } case TOK_LITERAL: { return MakeLiteralExpression(tok); @@ -690,16 +571,13 @@ private: { // An atom was expected but we got a subtraction symbol. // Interpret it as a unary minus function. - - // Make sure to copy the existing string position values for proper error results. - Token func = tok; - func.type = TOK_FUNCTION; - func.data = "minus"; - return ParseAtom(std::move(func)); + return ParseFunctionArguments(MakeFunctionExpression("minus"), tok); } default: + { return ParseResult::MakeErrorResult(tok, _trans("Expected start of expression.")); } + } } static int BinaryOperatorPrecedence(TokenType type) -- cgit v1.2.3 From 4d41bd64c8c01884fa0c2a663cc76a696c30adbb Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 11 Oct 2019 19:38:18 -0500 Subject: ExpressionParser: Show error message with expected arguments. --- .../ControlReference/ExpressionParser.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index f7937a19bd..0d1d0fdbea 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -469,7 +469,8 @@ private: return tok.type == type; } - ParseResult ParseFunctionArguments(std::unique_ptr&& func, + ParseResult ParseFunctionArguments(const std::string_view& func_name, + std::unique_ptr&& func, const Token& func_tok) { std::vector> args; @@ -518,10 +519,15 @@ private: } } - if (!func->SetArguments(std::move(args))) + const auto argument_validation = func->SetArguments(std::move(args)); + + if (std::holds_alternative(argument_validation)) { - // TODO: It would be nice to output how many arguments are expected. - return ParseResult::MakeErrorResult(func_tok, _trans("Wrong number of arguments.")); + const auto text = std::string(func_name) + '(' + + std::get(argument_validation).text + + ')'; + + return ParseResult::MakeErrorResult(func_tok, _trans("Expected arguments: " + text)); } return ParseResult::MakeSuccessfulResult(std::move(func)); @@ -543,7 +549,7 @@ private: return ParseAtom(control_tok); } - return ParseFunctionArguments(std::move(func), tok); + return ParseFunctionArguments(tok.data, std::move(func), tok); } case TOK_CONTROL: { @@ -553,7 +559,7 @@ private: } case TOK_NOT: { - return ParseFunctionArguments(MakeFunctionExpression("not"), tok); + return ParseFunctionArguments("not", MakeFunctionExpression("not"), tok); } case TOK_LITERAL: { @@ -571,7 +577,7 @@ private: { // An atom was expected but we got a subtraction symbol. // Interpret it as a unary minus function. - return ParseFunctionArguments(MakeFunctionExpression("minus"), tok); + return ParseFunctionArguments("minus", MakeFunctionExpression("minus"), tok); } default: { -- cgit v1.2.3 From 72302d9c4224271a606f2782dec7dc725658b150 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 12 Oct 2019 11:41:02 -0500 Subject: ExpressionParser: Add support for /* */ style comments. --- .../ControlReference/ExpressionParser.cpp | 35 ++++++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 0d1d0fdbea..7e8e54b1ec 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -87,6 +87,14 @@ Token Lexer::GetRealLiteral(char first_char) return Token(TOK_INVALID); } +Token Lexer::PeekToken() +{ + const auto old_it = it; + const auto tok = NextToken(); + it = old_it; + return tok; +} + Token Lexer::NextToken() { if (it == expr.end()) @@ -99,7 +107,7 @@ Token Lexer::NextToken() case '\t': case '\n': case '\r': - return Token(TOK_DISCARD); + return Token(TOK_WHITESPACE); case '(': return Token(TOK_LPAREN); case ')': @@ -154,8 +162,19 @@ ParseStatus Lexer::Tokenize(std::vector& tokens) tok.string_position = string_position; tok.string_length = it - expr.begin(); - if (tok.type == TOK_DISCARD) - continue; + // Handle /* */ style comments. + if (tok.type == TOK_DIV && PeekToken().type == TOK_MUL) + { + const auto end_of_comment = expr.find("*/", it - expr.begin()); + + if (end_of_comment == std::string::npos) + return ParseStatus::SyntaxError; + + tok.type = TOK_COMMENT; + tok.string_length = end_of_comment + 4; + + it = expr.begin() + end_of_comment + 2; + } tokens.push_back(tok); @@ -671,9 +690,19 @@ static ParseResult ParseComplexExpression(const std::string& str) if (tokenize_status != ParseStatus::Successful) return ParseResult::MakeErrorResult(Token(TOK_INVALID), _trans("Tokenizing failed.")); + RemoveInertTokens(&tokens); return ParseTokens(tokens); } +void RemoveInertTokens(std::vector* tokens) +{ + tokens->erase(std::remove_if(tokens->begin(), tokens->end(), + [](const Token& tok) { + return tok.type == TOK_COMMENT || tok.type == TOK_WHITESPACE; + }), + tokens->end()); +} + static std::unique_ptr ParseBarewordExpression(const std::string& str) { ControlQualifier qualifier; -- cgit v1.2.3 From 7295458c11e1465c9e0a823620ea935396c4ea39 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 12 Oct 2019 12:28:19 -0500 Subject: ExpressionParser: Make Lexer ctor explicit and move argument. --- Source/Core/InputCommon/ControlReference/ExpressionParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index 7e8e54b1ec..00afc5e036 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -34,7 +34,7 @@ bool Token::IsBinaryOperator() const return type >= TOK_BINARY_OPS_BEGIN && type < TOK_BINARY_OPS_END; } -Lexer::Lexer(const std::string& expr_) : expr(expr_) +Lexer::Lexer(std::string expr_) : expr(std::move(expr_)) { it = expr.begin(); } -- cgit v1.2.3