diff options
Diffstat (limited to 'Source/Core/InputCommon/ControlReference')
6 files changed, 1208 insertions, 323 deletions
diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp index cfc6fa658e..af7536547c 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.cpp +++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp @@ -25,12 +25,12 @@ bool ControlReference::InputGateOn() // Updates a controlreference's binded devices/controls // need to call this to re-bind a control reference after changing its expression // -void ControlReference::UpdateReference(const ciface::Core::DeviceContainer& devices, - const ciface::Core::DeviceQualifier& default_device) +void ControlReference::UpdateReference(ciface::ExpressionParser::ControlEnvironment& env) { - ControlFinder finder(devices, default_device, IsInput()); if (m_parsed_expression) - m_parsed_expression->UpdateReferences(finder); + { + m_parsed_expression->UpdateReferences(env); + } } int ControlReference::BoundCount() const @@ -54,7 +54,9 @@ std::string ControlReference::GetExpression() const void ControlReference::SetExpression(std::string expr) { m_expression = std::move(expr); - std::tie(m_parse_status, m_parsed_expression) = ParseExpression(m_expression); + auto parse_result = ParseExpression(m_expression); + m_parse_status = parse_result.status; + m_parsed_expression = std::move(parse_result.expr); } ControlReference::ControlReference() : range(1), m_parsed_expression(nullptr) diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.h b/Source/Core/InputCommon/ControlReference/ControlReference.h index 83fa288676..3f2c205501 100644 --- a/Source/Core/InputCommon/ControlReference/ControlReference.h +++ b/Source/Core/InputCommon/ControlReference/ControlReference.h @@ -30,8 +30,7 @@ public: int BoundCount() const; ciface::ExpressionParser::ParseStatus GetParseStatus() const; - void UpdateReference(const ciface::Core::DeviceContainer& devices, - const ciface::Core::DeviceQualifier& default_device); + void UpdateReference(ciface::ExpressionParser::ControlEnvironment& env); std::string GetExpression() const; void SetExpression(std::string expr); diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index c864b22a20..00afc5e036 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -2,222 +2,201 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include <algorithm> #include <cassert> +#include <cmath> #include <iostream> -#include <map> #include <memory> +#include <regex> #include <string> +#include <utility> #include <vector> +#include "Common/Common.h" #include "Common/StringUtil.h" + #include "InputCommon/ControlReference/ExpressionParser.h" +#include "InputCommon/ControlReference/FunctionExpression.h" namespace ciface::ExpressionParser { using namespace ciface::Core; -enum TokenType -{ - TOK_DISCARD, - TOK_INVALID, - TOK_EOF, - TOK_LPAREN, - TOK_RPAREN, - TOK_AND, - TOK_OR, - TOK_NOT, - TOK_ADD, - TOK_CONTROL, -}; +Token::Token(TokenType type_) : type(type_) +{ +} -inline std::string OpName(TokenType op) +Token::Token(TokenType type_, std::string data_) : type(type_), data(std::move(data_)) { - switch (op) - { - case TOK_AND: - return "And"; - case TOK_OR: - return "Or"; - case TOK_NOT: - return "Not"; - case TOK_ADD: - return "Add"; - default: - assert(false); - return ""; - } } -class Token +bool Token::IsBinaryOperator() const { -public: - TokenType type; - ControlQualifier qualifier; + return type >= TOK_BINARY_OPS_BEGIN && type < TOK_BINARY_OPS_END; +} - Token(TokenType type_) : type(type_) {} - Token(TokenType type_, ControlQualifier qualifier_) : type(type_), qualifier(qualifier_) {} - 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_NOT: - return "!"; - case TOK_ADD: - return "+"; - case TOK_CONTROL: - return "Device(" + (std::string)qualifier + ")"; - case TOK_INVALID: - break; - } +Lexer::Lexer(std::string expr_) : expr(std::move(expr_)) +{ + it = expr.begin(); +} - return "Invalid"; - } -}; +std::string Lexer::FetchDelimString(char delim) +{ + const std::string result = FetchCharsWhile([delim](char c) { return c != delim; }); + if (it != expr.end()) + ++it; + return result; +} -class Lexer +std::string Lexer::FetchWordChars() { -public: - std::string expr; - std::string::iterator it; + // Valid word characters: + std::regex rx(R"([a-z\d_])", std::regex_constants::icase); - Lexer(const std::string& expr_) : expr(expr_) { it = expr.begin(); } - bool FetchBacktickString(std::string& value, char otherDelim = 0) - { - value = ""; - while (it != expr.end()) - { - char c = *it; - ++it; - if (c == '`') - return false; - if (c > 0 && c == otherDelim) - return true; - value += c; - } - return false; - } + return FetchCharsWhile([&rx](char c) { return std::regex_match(std::string(1, c), rx); }); +} - Token GetFullyQualifiedControl() - { - ControlQualifier qualifier; - std::string value; +Token Lexer::GetDelimitedLiteral() +{ + return Token(TOK_LITERAL, FetchDelimString('\'')); +} - if (FetchBacktickString(value, ':')) - { - // Found colon, this is the device name - qualifier.has_device = true; - qualifier.device_qualifier.FromString(value); - FetchBacktickString(value); - } +Token Lexer::GetVariable() +{ + return Token(TOK_VARIABLE, FetchWordChars()); +} - qualifier.control_name = value; +Token Lexer::GetFullyQualifiedControl() +{ + return Token(TOK_CONTROL, FetchDelimString('`')); +} - return Token(TOK_CONTROL, qualifier); - } +Token Lexer::GetBareword(char first_char) +{ + return Token(TOK_BAREWORD, first_char + FetchWordChars()); +} - Token GetBarewordsControl(char c) - { - std::string name; - name += c; +Token Lexer::GetRealLiteral(char first_char) +{ + std::string value; + value += first_char; + value += FetchCharsWhile([](char c) { return isdigit(c, std::locale::classic()) || ('.' == c); }); - while (it != expr.end()) - { - c = *it; - if (!isalpha(c)) - break; - name += c; - ++it; - } + if (std::regex_match(value, std::regex(R"(\d+(\.\d+)?)"))) + return Token(TOK_LITERAL, value); - ControlQualifier qualifier; - qualifier.control_name = name; - return Token(TOK_CONTROL, qualifier); - } + return Token(TOK_INVALID); +} - Token NextToken() - { - if (it == expr.end()) - return Token(TOK_EOF); +Token Lexer::PeekToken() +{ + const auto old_it = it; + const auto tok = NextToken(); + it = old_it; + return tok; +} - 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 Token(TOK_NOT); - case '+': - return Token(TOK_ADD); - case '`': - return GetFullyQualifiedControl(); - default: - if (isalpha(c)) - return GetBarewordsControl(c); - else - return Token(TOK_INVALID); - } +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_WHITESPACE); + case '(': + return Token(TOK_LPAREN); + case ')': + return Token(TOK_RPAREN); + case '&': + return Token(TOK_AND); + case '|': + return Token(TOK_OR); + case '!': + return Token(TOK_NOT); + 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 GetBareword(c); + else if (isdigit(c, std::locale::classic())) + return GetRealLiteral(c); + else + return Token(TOK_INVALID); } +} - ParseStatus Tokenize(std::vector<Token>& tokens) +ParseStatus Lexer::Tokenize(std::vector<Token>& 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(); + // 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; - } - tokens.push_back(tok); + tok.type = TOK_COMMENT; + tok.string_length = end_of_comment + 4; - if (tok.type == TOK_EOF) - break; + it = expr.begin() + end_of_comment + 2; } - return ParseStatus::Successful; + + tokens.push_back(tok); + + if (tok.type == TOK_INVALID) + return ParseStatus::SyntaxError; + + if (tok.type == TOK_EOF) + break; } -}; + return ParseStatus::Successful; +} 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 + // Keep a shared_ptr to the device so the control pointer doesn't become invalid. std::shared_ptr<Device> 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 @@ -226,20 +205,25 @@ 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<std::string>(qualifier) + "`"; } + +private: + ControlQualifier qualifier; + Device::Input* input = nullptr; + Device::Output* output = nullptr; }; class BinaryExpression : public Expression @@ -257,16 +241,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 std::min(lhsValue + rhsValue, 1.0); + return lhs->GetValue() + rhs->GetValue(); + case TOK_SUB: + return lhs->GetValue() - rhs->GetValue(); + case TOK_MUL: + return lhs->GetValue() * rhs->GetValue(); + case TOK_DIV: + { + const ControlState result = lhs->GetValue() / rhs->GetValue(); + return std::isinf(result) ? 0.0 : result; + } + case TOK_MOD: + { + const ControlState result = std::fmod(lhs->GetValue(), rhs->GetValue()); + return std::isnan(result) ? 0.0 : result; + } + case TOK_ASSIGN: + { + lhs->SetValue(rhs->GetValue()); + return lhs->GetValue(); + } + case TOK_LTHAN: + return lhs->GetValue() < rhs->GetValue(); + case TOK_GTHAN: + return lhs->GetValue() > rhs->GetValue(); + case TOK_COMMA: + { + // Eval and discard lhs: + lhs->GetValue(); + return rhs->GetValue(); + } default: assert(false); return 0; @@ -286,57 +297,73 @@ public: return lhs->CountNumControls() + rhs->CountNumControls(); } - void UpdateReferences(ControlFinder& finder) override + void UpdateReferences(ControlEnvironment& env) override { - lhs->UpdateReferences(finder); - rhs->UpdateReferences(finder); - } - - operator std::string() const override - { - return OpName(op) + "(" + (std::string)(*lhs) + ", " + (std::string)(*rhs) + ")"; + lhs->UpdateReferences(env); + rhs->UpdateReferences(env); } }; -class UnaryExpression : public Expression +class LiteralExpression : public Expression { public: - TokenType op; - std::unique_ptr<Expression> inner; - - UnaryExpression(TokenType op_, std::unique_ptr<Expression>&& inner_) - : op(op_), inner(std::move(inner_)) + void SetValue(ControlState) override { + // Do nothing. } - ControlState GetValue() const override + + int CountNumControls() const override { return 1; } + + void UpdateReferences(ControlEnvironment&) override { - ControlState value = inner->GetValue(); - switch (op) - { - case TOK_NOT: - return 1.0 - value; - default: - assert(false); - return 0; - } + // Nothing needed. } - void SetValue(ControlState value) override - { - switch (op) - { - case TOK_NOT: - inner->SetValue(1.0 - value); - break; +protected: + virtual std::string GetName() const = 0; +}; - default: - assert(false); - } +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: + const ControlState m_value{}; +}; + +ParseResult MakeLiteralExpression(Token token) +{ + ControlState val{}; + if (TryParse(token.data, &val)) + return ParseResult::MakeSuccessfulResult(std::make_unique<LiteralReal>(val)); + else + return ParseResult::MakeErrorResult(token, _trans("Invalid literal.")); +} + +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); } - 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) + ")"; } +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 @@ -353,16 +380,10 @@ public: void SetValue(ControlState value) override { GetActiveChild()->SetValue(value); } int CountNumControls() const override { return GetActiveChild()->CountNumControls(); } - operator std::string() const override + void UpdateReferences(ControlEnvironment& env) override { - return "Coalesce(" + static_cast<std::string>(*m_lhs) + ", " + - static_cast<std::string>(*m_rhs) + ')'; - } - - void UpdateReferences(ControlFinder& finder) override - { - m_lhs->UpdateReferences(finder); - m_rhs->UpdateReferences(finder); + m_lhs->UpdateReferences(env); + m_rhs->UpdateReferences(env); } private: @@ -375,7 +396,7 @@ private: std::unique_ptr<Expression> m_rhs; }; -std::shared_ptr<Device> ControlFinder::FindDevice(ControlQualifier qualifier) const +std::shared_ptr<Device> ControlEnvironment::FindDevice(ControlQualifier qualifier) const { if (qualifier.has_device) return container.FindDevice(qualifier.device_qualifier); @@ -383,150 +404,303 @@ std::shared_ptr<Device> 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> 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); } -struct ParseResult +Device::Output* ControlEnvironment::FindOutput(ControlQualifier qualifier) const { - ParseResult(ParseStatus status_, std::unique_ptr<Expression>&& expr_ = {}) - : status(status_), expr(std::move(expr_)) - { - } + const std::shared_ptr<Device> device = FindDevice(qualifier); + if (!device) + return nullptr; - ParseStatus status; - std::unique_ptr<Expression> expr; -}; + return device->FindOutput(qualifier.control_name); +} + +ControlState* ControlEnvironment::GetVariablePtr(const std::string& name) +{ + return &m_variables[name]; +} + +ParseResult ParseResult::MakeEmptyResult() +{ + ParseResult result; + result.status = ParseStatus::EmptyExpression; + return result; +} + +ParseResult ParseResult::MakeSuccessfulResult(std::unique_ptr<Expression>&& 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<Token> tokens_) : tokens(tokens_) { m_it = tokens.begin(); } - ParseResult Parse() { return Toplevel(); } + explicit Parser(const std::vector<Token>& 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 ParseResult::MakeErrorResult(Peek(), _trans("Expected EOF.")); + } private: - std::vector<Token> tokens; - std::vector<Token>::iterator m_it; + const std::vector<Token>& tokens; + std::vector<Token>::const_iterator m_it; + + Token Chew() + { + const Token tok = Peek(); + if (TOK_EOF != tok.type) + ++m_it; + return tok; + } - Token Chew() { return *m_it++; } Token Peek() { return *m_it; } + bool Expects(TokenType type) { Token tok = Chew(); return tok.type == type; } - ParseResult Atom() + ParseResult ParseFunctionArguments(const std::string_view& func_name, + std::unique_ptr<FunctionExpression>&& func, + const Token& func_tok) { - Token tok = Chew(); - switch (tok.type) + std::vector<std::unique_ptr<Expression>> args; + + if (TOK_LPAREN != Peek().type) { - case TOK_CONTROL: - return {ParseStatus::Successful, std::make_unique<ControlExpression>(tok.qualifier)}; - case TOK_LPAREN: - return Paren(); - default: - return {ParseStatus::SyntaxError}; + // Single argument with no parens (useful for unary ! function) + const auto tok = Chew(); + auto arg = ParseAtom(tok); + if (ParseStatus::Successful != arg.status) + return arg; + + args.emplace_back(std::move(arg.expr)); } - } + else + { + // Chew the L-Paren + Chew(); - bool IsUnaryExpression(TokenType type) - { - switch (type) + // 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.")); + }; + } + } + + const auto argument_validation = func->SetArguments(std::move(args)); + + if (std::holds_alternative<FunctionExpression::ExpectedArguments>(argument_validation)) { - case TOK_NOT: - return true; - default: - return false; + const auto text = std::string(func_name) + '(' + + std::get<FunctionExpression::ExpectedArguments>(argument_validation).text + + ')'; + + return ParseResult::MakeErrorResult(func_tok, _trans("Expected arguments: " + text)); } + + return ParseResult::MakeSuccessfulResult(std::move(func)); } - ParseResult Unary() + ParseResult ParseAtom(const Token& tok) { - if (IsUnaryExpression(Peek().type)) + switch (tok.type) { - Token tok = Chew(); - ParseResult result = Atom(); - if (result.status == ParseStatus::SyntaxError) - return result; - return {ParseStatus::Successful, - std::make_unique<UnaryExpression>(tok.type, std::move(result.expr))}; - } + case TOK_BAREWORD: + { + auto func = MakeFunctionExpression(tok.data); + + if (!func) + { + // Invalid function, interpret this as a bareword control. + Token control_tok(tok); + control_tok.type = TOK_CONTROL; + return ParseAtom(control_tok); + } - return Atom(); + return ParseFunctionArguments(tok.data, std::move(func), tok); + } + case TOK_CONTROL: + { + ControlQualifier cq; + cq.FromString(tok.data); + return ParseResult::MakeSuccessfulResult(std::make_unique<ControlExpression>(cq)); + } + case TOK_NOT: + { + return ParseFunctionArguments("not", MakeFunctionExpression("not"), tok); + } + case TOK_LITERAL: + { + return MakeLiteralExpression(tok); + } + case TOK_VARIABLE: + { + return ParseResult::MakeSuccessfulResult(std::make_unique<VariableExpression>(tok.data)); + } + case TOK_LPAREN: + { + return ParseParens(); + } + case TOK_SUB: + { + // An atom was expected but we got a subtraction symbol. + // Interpret it as a unary minus function. + return ParseFunctionArguments("minus", MakeFunctionExpression("minus"), tok); + } + default: + { + return ParseResult::MakeErrorResult(tok, _trans("Expected start of expression.")); + } + } } - bool IsBinaryToken(TokenType type) + static int BinaryOperatorPrecedence(TokenType type) { 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: - case TOK_ADD: - return true; + return 5; + case TOK_ASSIGN: + return 6; + case TOK_COMMA: + return 7; default: - return false; + assert(false); + return 0; } } - ParseResult Binary() + ParseResult ParseBinary(int precedence = 999) { - ParseResult result = Unary(); - if (result.status == ParseStatus::SyntaxError) - return result; + ParseResult lhs = ParseAtom(Chew()); + + if (lhs.status == ParseStatus::SyntaxError) + return lhs; + + std::unique_ptr<Expression> expr = std::move(lhs.expr); - std::unique_ptr<Expression> expr = std::move(result.expr); - while (IsBinaryToken(Peek().type)) + // TODO: handle LTR/RTL associativity? + while (Peek().IsBinaryOperator() && BinaryOperatorPrecedence(Peek().type) < precedence) { - Token tok = Chew(); - ParseResult unary_result = Unary(); - if (unary_result.status == ParseStatus::SyntaxError) + const Token tok = Chew(); + ParseResult rhs = ParseBinary(BinaryOperatorPrecedence(tok.type)); + if (rhs.status == ParseStatus::SyntaxError) { - return unary_result; + return rhs; } - expr = std::make_unique<BinaryExpression>(tok.type, std::move(expr), - std::move(unary_result.expr)); + expr = std::make_unique<BinaryExpression>(tok.type, std::move(expr), std::move(rhs.expr)); } - return {ParseStatus::Successful, std::move(expr)}; + return ParseResult::MakeSuccessfulResult(std::move(expr)); } - ParseResult Paren() + ParseResult ParseParens() { // lparen already chewed - ParseResult result = Toplevel(); + ParseResult result = ParseToplevel(); 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; } - ParseResult Toplevel() { return Binary(); } -}; + ParseResult ParseToplevel() { return ParseBinary(); } +}; // namespace ExpressionParser + +ParseResult ParseTokens(const std::vector<Token>& tokens) +{ + return Parser(tokens).Parse(); +} static ParseResult ParseComplexExpression(const std::string& str) { Lexer l(str); std::vector<Token> 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(); + RemoveInertTokens(&tokens); + return ParseTokens(tokens); +} + +void RemoveInertTokens(std::vector<Token>* 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<Expression> ParseBarewordExpression(const std::string& str) @@ -538,21 +712,24 @@ static std::unique_ptr<Expression> ParseBarewordExpression(const std::string& st return std::make_unique<ControlExpression>(qualifier); } -std::pair<ParseStatus, std::unique_ptr<Expression>> 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<CoalesceExpression>(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<CoalesceExpression>(std::move(bareword_expr), + std::move(complex_result.expr)); + return complex_result; } } // namespace ciface::ExpressionParser diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.h b/Source/Core/InputCommon/ControlReference/ExpressionParser.h index 56c0340f49..cab6096c0e 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.h +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.h @@ -4,13 +4,102 @@ #pragma once +#include <map> #include <memory> +#include <optional> #include <string> -#include <utility> + #include "InputCommon/ControllerInterface/Device.h" namespace ciface::ExpressionParser { +enum TokenType +{ + TOK_WHITESPACE, + TOK_INVALID, + TOK_EOF, + TOK_LPAREN, + TOK_RPAREN, + TOK_NOT, + TOK_CONTROL, + TOK_LITERAL, + TOK_VARIABLE, + TOK_BAREWORD, + TOK_COMMENT, + // 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, +}; + +class Token +{ +public: + TokenType type; + std::string data; + + // Position in the input string: + std::size_t string_position = 0; + std::size_t string_length = 0; + + explicit Token(TokenType type_); + Token(TokenType type_, std::string data_); + + bool IsBinaryOperator() const; +}; + +enum class ParseStatus +{ + Successful, + SyntaxError, + EmptyExpression, +}; + +class Lexer +{ +public: + std::string expr; + std::string::iterator it; + + explicit Lexer(std::string expr_); + + ParseStatus Tokenize(std::vector<Token>& tokens); + +private: + template <typename F> + std::string FetchCharsWhile(F&& func) + { + std::string value; + while (it != expr.end() && func(*it)) + { + value += *it; + ++it; + } + return value; + } + + std::string FetchDelimString(char delim); + std::string FetchWordChars(); + Token GetDelimitedLiteral(); + Token GetVariable(); + Token GetFullyQualifiedControl(); + Token GetBareword(char c); + Token GetRealLiteral(char c); + + Token PeekToken(); + Token NextToken(); +}; + class ControlQualifier { public: @@ -19,6 +108,7 @@ public: std::string control_name; ControlQualifier() : has_device(false) {} + operator std::string() const { if (has_device) @@ -26,23 +116,45 @@ public: else return control_name; } + + void FromString(const std::string& str) + { + const auto col_pos = str.find_last_of(':'); + + has_device = (str.npos != col_pos); + if (has_device) + { + device_qualifier.FromString(str.substr(0, col_pos)); + control_name = str.substr(col_pos + 1); + } + else + { + device_qualifier.FromString(""); + control_name = str; + } + } }; -class ControlFinder +class ControlEnvironment { public: - ControlFinder(const Core::DeviceContainer& container_, const Core::DeviceQualifier& default_, - const bool is_input_) - : container(container_), default_device(default_), is_input(is_input_) + using VariableContainer = std::map<std::string, ControlState>; + + ControlEnvironment(const Core::DeviceContainer& container_, const Core::DeviceQualifier& default_, + VariableContainer& vars) + : m_variables(vars), container(container_), default_device(default_) { } + std::shared_ptr<Core::Device> FindDevice(ControlQualifier qualifier) const; - Core::Device::Control* FindControl(ControlQualifier qualifier) const; + Core::Device::Input* FindInput(ControlQualifier qualifier) const; + Core::Device::Output* FindOutput(ControlQualifier qualifier) const; + ControlState* GetVariablePtr(const std::string& name); private: + VariableContainer& m_variables; const Core::DeviceContainer& container; const Core::DeviceQualifier& default_device; - bool is_input; }; class Expression @@ -52,16 +164,30 @@ public: virtual ControlState GetValue() const = 0; virtual void SetValue(ControlState state) = 0; virtual int CountNumControls() const = 0; - virtual void UpdateReferences(ControlFinder& finder) = 0; - virtual operator std::string() const = 0; + virtual void UpdateReferences(ControlEnvironment& finder) = 0; }; -enum class ParseStatus +class ParseResult { - Successful, - SyntaxError, - EmptyExpression, +public: + static ParseResult MakeEmptyResult(); + static ParseResult MakeSuccessfulResult(std::unique_ptr<Expression>&& expr); + static ParseResult MakeErrorResult(Token token, std::string description); + + ParseStatus status; + std::unique_ptr<Expression> expr; + + // Used for parse errors: + // TODO: This should probably be moved elsewhere: + std::optional<Token> token; + std::optional<std::string> description; + +private: + ParseResult() = default; }; -std::pair<ParseStatus, std::unique_ptr<Expression>> ParseExpression(const std::string& expr); +ParseResult ParseExpression(const std::string& expr); +ParseResult ParseTokens(const std::vector<Token>& tokens); +void RemoveInertTokens(std::vector<Token>* tokens); + } // namespace ciface::ExpressionParser diff --git a/Source/Core/InputCommon/ControlReference/FunctionExpression.cpp b/Source/Core/InputCommon/ControlReference/FunctionExpression.cpp new file mode 100644 index 0000000000..18b98c5e19 --- /dev/null +++ b/Source/Core/InputCommon/ControlReference/FunctionExpression.cpp @@ -0,0 +1,526 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <chrono> +#include <cmath> + +#include "InputCommon/ControlReference/FunctionExpression.h" + +namespace ciface +{ +namespace ExpressionParser +{ +constexpr int LOOP_MAX_REPS = 10000; +constexpr ControlState CONDITION_THRESHOLD = 0.5; + +using Clock = std::chrono::steady_clock; +using FSec = std::chrono::duration<ControlState>; + +// usage: toggle(toggle_state_input, [clear_state_input]) +class ToggleExpression : public FunctionExpression +{ +private: + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + // Optional 2nd argument for clearing state: + if (1 == args.size() || 2 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"toggle_state_input, [clear_state_input]"}; + } + + 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; + } + + if (2 == GetArgCount() && GetArg(1).GetValue() > CONDITION_THRESHOLD) + { + m_state = false; + } + + return m_state; + } + + mutable bool m_released{}; + mutable bool m_state{}; +}; + +// usage: not(expression) +class NotExpression : public FunctionExpression +{ +private: + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (1 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"expression"}; + } + + ControlState GetValue() const override { return 1.0 - GetArg(0).GetValue(); } + void SetValue(ControlState value) override { GetArg(0).SetValue(1.0 - value); } +}; + +// usage: sin(expression) +class SinExpression : public FunctionExpression +{ +private: + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (1 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"expression"}; + } + + ControlState GetValue() const override { return std::sin(GetArg(0).GetValue()); } +}; + +// usage: timer(seconds) +class TimerExpression : public FunctionExpression +{ +private: + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (1 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"seconds"}; + } + + ControlState GetValue() const override + { + const auto now = Clock::now(); + const auto elapsed = now - m_start_time; + + const ControlState val = GetArg(0).GetValue(); + + ControlState progress = std::chrono::duration_cast<FSec>(elapsed).count() / val; + + if (std::isinf(progress) || progress < 0.0) + { + // User configured a non-positive 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<Clock::duration>(FSec(val * reset_count)); + progress -= reset_count; + } + + return progress; + } + +private: + mutable Clock::time_point m_start_time = Clock::now(); +}; + +// usage: if(condition, true_expression, false_expression) +class IfExpression : public FunctionExpression +{ +private: + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (3 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"condition, true_expression, false_expression"}; + } + + ControlState GetValue() const override + { + return (GetArg(0).GetValue() > CONDITION_THRESHOLD) ? GetArg(1).GetValue() : + GetArg(2).GetValue(); + } +}; + +// usage: minus(expression) +class UnaryMinusExpression : public FunctionExpression +{ +private: + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (1 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"expression"}; + } + + ControlState GetValue() const override + { + // Subtraction for clarity: + return 0.0 - GetArg(0).GetValue(); + } +}; + +// usage: deadzone(input, amount) +class DeadzoneExpression : public FunctionExpression +{ + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (2 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"input, amount"}; + } + + ControlState GetValue() const override + { + const ControlState val = GetArg(0).GetValue(); + const ControlState deadzone = GetArg(1).GetValue(); + return std::copysign(std::max(0.0, std::abs(val) - deadzone) / (1.0 - deadzone), val); + } +}; + +// usage: smooth(input, seconds_up, seconds_down = seconds_up) +// seconds is seconds to change from 0.0 to 1.0 +class SmoothExpression : public FunctionExpression +{ + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (2 == args.size() || 3 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"input, seconds_up, seconds_down = seconds_up"}; + } + + ControlState GetValue() const override + { + const auto now = Clock::now(); + const auto elapsed = now - m_last_update; + m_last_update = now; + + const ControlState desired_value = GetArg(0).GetValue(); + + const ControlState smooth_up = GetArg(1).GetValue(); + const ControlState smooth_down = (3 == GetArgCount() ? GetArg(2).GetValue() : smooth_up); + + const ControlState smooth = (desired_value < m_value) ? smooth_down : smooth_up; + const ControlState max_move = std::chrono::duration_cast<FSec>(elapsed).count() / smooth; + + if (std::isinf(max_move)) + { + m_value = desired_value; + } + else + { + const ControlState diff = desired_value - m_value; + m_value += std::copysign(std::min(max_move, std::abs(diff)), diff); + } + + return m_value; + } + +private: + mutable ControlState m_value = 0.0; + mutable Clock::time_point m_last_update = Clock::now(); +}; + +// usage: hold(input, seconds) +class HoldExpression : public FunctionExpression +{ + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (2 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"input, seconds"}; + } + + ControlState GetValue() const override + { + const auto now = Clock::now(); + + const ControlState input = GetArg(0).GetValue(); + + if (input < CONDITION_THRESHOLD) + { + m_state = false; + m_start_time = Clock::now(); + } + else if (!m_state) + { + const auto hold_time = now - m_start_time; + + if (std::chrono::duration_cast<FSec>(hold_time).count() >= GetArg(1).GetValue()) + m_state = true; + } + + return m_state; + } + +private: + mutable bool m_state = false; + mutable Clock::time_point m_start_time = Clock::now(); +}; + +// usage: tap(input, seconds, taps=2) +class TapExpression : public FunctionExpression +{ + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (2 == args.size() || 3 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"input, seconds, taps = 2"}; + } + + ControlState GetValue() const override + { + const auto now = Clock::now(); + + const auto elapsed = std::chrono::duration_cast<FSec>(now - m_start_time).count(); + + const ControlState input = GetArg(0).GetValue(); + const ControlState seconds = GetArg(1).GetValue(); + + const bool is_time_up = elapsed > seconds; + + const u32 desired_taps = (3 == GetArgCount()) ? u32(GetArg(2).GetValue() + 0.5) : 2; + + if (input < CONDITION_THRESHOLD) + { + m_released = true; + + if (m_taps > 0 && is_time_up) + { + m_taps = 0; + } + } + else + { + if (m_released) + { + if (!m_taps) + { + m_start_time = now; + } + + ++m_taps; + m_released = false; + } + + return desired_taps == m_taps; + } + + return 0.0; + } + +private: + mutable bool m_released = true; + mutable u32 m_taps = 0; + mutable Clock::time_point m_start_time = Clock::now(); +}; + +// usage: relative(input, speed, [max_abs_value, [shared_state]]) +// speed is max movement per second +class RelativeExpression : public FunctionExpression +{ + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (args.size() >= 2 && args.size() <= 4) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"input, speed, [max_abs_value, [shared_state]]"}; + } + + ControlState GetValue() const override + { + // There is a lot of funky math in this function but it allows for a variety of uses: + // + // e.g. A single mapping with a relatively adjusted value between 0.0 and 1.0 + // Potentially useful for a trigger input + // relative(`Up` - `Down`, 2.0) + // + // e.g. A value with two mappings (such as analog stick Up/Down) + // The shared state allows the two mappings to work together. + // This mapping (for up) returns a value clamped between 0.0 and 1.0 + // relative(`Up`, 2.0, 1.0, $y) + // This mapping (for down) returns the negative value clamped between 0.0 and 1.0 + // (Adjustments created by `Down` are applied negatively to the shared state) + // relative(`Down`, 2.0, -1.0, $y) + + const auto now = Clock::now(); + + if (GetArgCount() >= 4) + m_state = GetArg(3).GetValue(); + + const auto elapsed = std::chrono::duration_cast<FSec>(now - m_last_update).count(); + m_last_update = now; + + const ControlState input = GetArg(0).GetValue(); + const ControlState speed = GetArg(1).GetValue(); + + const ControlState max_abs_value = (GetArgCount() >= 3) ? GetArg(2).GetValue() : 1.0; + + const ControlState max_move = input * elapsed * speed; + const ControlState diff_from_zero = std::abs(0.0 - m_state); + const ControlState diff_from_max = std::abs(max_abs_value - m_state); + + m_state += std::min(std::max(max_move, -diff_from_zero), diff_from_max) * + std::copysign(1.0, max_abs_value); + + if (GetArgCount() >= 4) + const_cast<Expression&>(GetArg(3)).SetValue(m_state); + + return std::max(0.0, m_state * std::copysign(1.0, max_abs_value)); + } + +private: + mutable ControlState m_state = 0.0; + mutable Clock::time_point m_last_update = Clock::now(); +}; + +// usage: pulse(input, seconds) +class PulseExpression : public FunctionExpression +{ + ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) override + { + if (2 == args.size()) + return ArgumentsAreValid{}; + else + return ExpectedArguments{"input, seconds"}; + } + + ControlState GetValue() const override + { + const auto now = Clock::now(); + + const ControlState input = GetArg(0).GetValue(); + + if (input < CONDITION_THRESHOLD) + { + m_released = true; + } + else if (m_released) + { + m_released = false; + + const auto seconds = std::chrono::duration_cast<Clock::duration>(FSec(GetArg(1).GetValue())); + + if (m_state) + { + m_release_time += seconds; + } + else + { + m_state = true; + m_release_time = now + seconds; + } + } + + if (m_state && now >= m_release_time) + { + m_state = false; + } + + return m_state; + } + +private: + mutable bool m_released = false; + mutable bool m_state = false; + mutable Clock::time_point m_release_time = Clock::now(); +}; + +std::unique_ptr<FunctionExpression> MakeFunctionExpression(std::string name) +{ + if ("not" == name) + return std::make_unique<NotExpression>(); + else if ("if" == name) + return std::make_unique<IfExpression>(); + else if ("sin" == name) + return std::make_unique<SinExpression>(); + else if ("timer" == name) + return std::make_unique<TimerExpression>(); + else if ("toggle" == name) + return std::make_unique<ToggleExpression>(); + else if ("minus" == name) + return std::make_unique<UnaryMinusExpression>(); + else if ("deadzone" == name) + return std::make_unique<DeadzoneExpression>(); + else if ("smooth" == name) + return std::make_unique<SmoothExpression>(); + else if ("hold" == name) + return std::make_unique<HoldExpression>(); + else if ("tap" == name) + return std::make_unique<TapExpression>(); + else if ("relative" == name) + return std::make_unique<RelativeExpression>(); + else if ("pulse" == name) + return std::make_unique<PulseExpression>(); + else + return nullptr; +} + +int FunctionExpression::CountNumControls() const +{ + int result = 0; + + for (auto& arg : m_args) + result += arg->CountNumControls(); + + return result; +} + +void FunctionExpression::UpdateReferences(ControlEnvironment& env) +{ + for (auto& arg : m_args) + arg->UpdateReferences(env); +} + +FunctionExpression::ArgumentValidation +FunctionExpression::SetArguments(std::vector<std::unique_ptr<Expression>>&& args) +{ + m_args = std::move(args); + + return ValidateArguments(m_args); +} + +Expression& FunctionExpression::GetArg(u32 number) +{ + return *m_args[number]; +} + +const Expression& FunctionExpression::GetArg(u32 number) const +{ + return *m_args[number]; +} + +u32 FunctionExpression::GetArgCount() const +{ + return u32(m_args.size()); +} + +void FunctionExpression::SetValue(ControlState) +{ +} + +} // namespace ExpressionParser +} // namespace ciface diff --git a/Source/Core/InputCommon/ControlReference/FunctionExpression.h b/Source/Core/InputCommon/ControlReference/FunctionExpression.h new file mode 100644 index 0000000000..f5332a0d25 --- /dev/null +++ b/Source/Core/InputCommon/ControlReference/FunctionExpression.h @@ -0,0 +1,55 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> +#include <variant> +#include <vector> + +#include "InputCommon/ControlReference/ExpressionParser.h" +#include "InputCommon/ControlReference/FunctionExpression.h" + +namespace ciface +{ +namespace ExpressionParser +{ +class FunctionExpression : public Expression +{ +public: + struct ArgumentsAreValid + { + }; + + struct ExpectedArguments + { + std::string text; + }; + + using ArgumentValidation = std::variant<ArgumentsAreValid, ExpectedArguments>; + + int CountNumControls() const override; + void UpdateReferences(ControlEnvironment& env) override; + + ArgumentValidation SetArguments(std::vector<std::unique_ptr<Expression>>&& args); + + void SetValue(ControlState value) override; + +protected: + virtual ArgumentValidation + ValidateArguments(const std::vector<std::unique_ptr<Expression>>& args) = 0; + + Expression& GetArg(u32 number); + const Expression& GetArg(u32 number) const; + u32 GetArgCount() const; + +private: + std::vector<std::unique_ptr<Expression>> m_args; +}; + +std::unique_ptr<FunctionExpression> MakeFunctionExpression(std::string name); + +} // namespace ExpressionParser +} // namespace ciface |
