summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/ControlReference
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Core/InputCommon/ControlReference')
-rw-r--r--Source/Core/InputCommon/ControlReference/ControlReference.cpp12
-rw-r--r--Source/Core/InputCommon/ControlReference/ControlReference.h3
-rw-r--r--Source/Core/InputCommon/ControlReference/ExpressionParser.cpp781
-rw-r--r--Source/Core/InputCommon/ControlReference/ExpressionParser.h154
-rw-r--r--Source/Core/InputCommon/ControlReference/FunctionExpression.cpp526
-rw-r--r--Source/Core/InputCommon/ControlReference/FunctionExpression.h55
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