summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/ControlReference
diff options
context:
space:
mode:
authorMichael Maltese <michaeljosephmaltese@gmail.com>2016-10-11 17:48:38 -0700
committerMichael Maltese <michaeljosephmaltese@gmail.com>2017-02-07 22:59:10 -0800
commita509f56116d308c96e38898e07d3538f26a1b7af (patch)
tree1df255d62c9057cb3b53869afab04e737e96f0e0 /Source/Core/InputCommon/ControlReference
parentf621a6af431e53a71890f916dd812ccf987ab429 (diff)
InputCommon: Extract ControlReference from ControllerInterface
Better separation of concerns. Relegates `ControllerInterface` to enumerating input controls, and the new `ControlReference` deals with combining inputs and configuration expression parsing.
Diffstat (limited to 'Source/Core/InputCommon/ControlReference')
-rw-r--r--Source/Core/InputCommon/ControlReference/ControlReference.cpp148
-rw-r--r--Source/Core/InputCommon/ControlReference/ControlReference.h79
-rw-r--r--Source/Core/InputCommon/ControlReference/ExpressionParser.cpp567
-rw-r--r--Source/Core/InputCommon/ControlReference/ExpressionParser.h72
4 files changed, 866 insertions, 0 deletions
diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.cpp b/Source/Core/InputCommon/ControlReference/ControlReference.cpp
new file mode 100644
index 0000000000..4268531668
--- /dev/null
+++ b/Source/Core/InputCommon/ControlReference/ControlReference.cpp
@@ -0,0 +1,148 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "Common/Thread.h"
+// For InputGateOn()
+// This is a bad layering violation, but it's the cleanest
+// place I could find to put it.
+#include "Core/ConfigManager.h"
+#include "Core/Host.h"
+
+#include "InputCommon/ControlReference/ControlReference.h"
+
+using namespace ciface::ExpressionParser;
+
+constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55;
+
+bool ControlReference::InputGateOn()
+{
+ return SConfig::GetInstance().m_BackgroundInput || Host_RendererHasFocus() || Host_UIHasFocus();
+}
+
+//
+// UpdateReference
+//
+// Updates a controlreference's binded devices/controls
+// need to call this to re-parse a control reference's expression after changing it
+//
+void ControlReference::UpdateReference(ciface::Core::DeviceContainer& devices,
+ const ciface::Core::DeviceQualifier& default_device)
+{
+ delete parsed_expression;
+ parsed_expression = nullptr;
+
+ ControlFinder finder(devices, default_device, is_input);
+ parse_error = ParseExpression(expression, finder, &parsed_expression);
+}
+
+//
+// InputReference :: State
+//
+// Gets the state of an input reference
+// override function for ControlReference::State ...
+//
+ControlState InputReference::State(const ControlState ignore)
+{
+ if (parsed_expression && InputGateOn())
+ return parsed_expression->GetValue() * range;
+ return 0.0;
+}
+
+//
+// OutputReference :: State
+//
+// Set the state of all binded outputs
+// overrides ControlReference::State .. combined them so I could make the GUI simple / inputs ==
+// same as outputs one list
+// I was lazy and it works so watever
+//
+ControlState OutputReference::State(const ControlState state)
+{
+ if (parsed_expression && InputGateOn())
+ parsed_expression->SetValue(state);
+ return 0.0;
+}
+
+//
+// InputReference :: Detect
+//
+// Wait for input on all binded devices
+// supports not detecting inputs that were held down at the time of Detect start,
+// which is useful for those crazy flightsticks that have certain buttons that are always held down
+// or some crazy axes or something
+// upon input, return pointer to detected Control
+// else return nullptr
+//
+ciface::Core::Device::Control* InputReference::Detect(const unsigned int ms,
+ ciface::Core::Device* const device)
+{
+ unsigned int time = 0;
+ std::vector<bool> states(device->Inputs().size());
+
+ if (device->Inputs().size() == 0)
+ return nullptr;
+
+ // get starting state of all inputs,
+ // so we can ignore those that were activated at time of Detect start
+ std::vector<ciface::Core::Device::Input *>::const_iterator i = device->Inputs().begin(),
+ e = device->Inputs().end();
+ for (std::vector<bool>::iterator state = states.begin(); i != e; ++i)
+ *state++ = ((*i)->GetState() > (1 - INPUT_DETECT_THRESHOLD));
+
+ while (time < ms)
+ {
+ device->UpdateInput();
+ i = device->Inputs().begin();
+ for (std::vector<bool>::iterator state = states.begin(); i != e; ++i, ++state)
+ {
+ // detected an input
+ if ((*i)->IsDetectable() && (*i)->GetState() > INPUT_DETECT_THRESHOLD)
+ {
+ // input was released at some point during Detect call
+ // return the detected input
+ if (false == *state)
+ return *i;
+ }
+ else if ((*i)->GetState() < (1 - INPUT_DETECT_THRESHOLD))
+ {
+ *state = false;
+ }
+ }
+ Common::SleepCurrentThread(10);
+ time += 10;
+ }
+
+ // no input was detected
+ return nullptr;
+}
+
+//
+// OutputReference :: Detect
+//
+// Totally different from the inputReference detect / I have them combined so it was simpler to make
+// the GUI.
+// The GUI doesn't know the difference between an input and an output / it's odd but I was lazy and
+// it was easy
+//
+// set all binded outputs to <range> power for x milliseconds return false
+//
+ciface::Core::Device::Control* OutputReference::Detect(const unsigned int ms,
+ ciface::Core::Device* const device)
+{
+ // ignore device
+
+ // don't hang if we don't even have any controls mapped
+ if (BoundCount() > 0)
+ {
+ State(1);
+ unsigned int slept = 0;
+
+ // this loop is to make stuff like flashing keyboard LEDs work
+ while (ms > (slept += 10))
+ Common::SleepCurrentThread(10);
+
+ State(0);
+ }
+ return nullptr;
+}
diff --git a/Source/Core/InputCommon/ControlReference/ControlReference.h b/Source/Core/InputCommon/ControlReference/ControlReference.h
new file mode 100644
index 0000000000..335675e5eb
--- /dev/null
+++ b/Source/Core/InputCommon/ControlReference/ControlReference.h
@@ -0,0 +1,79 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "InputCommon/ControlReference/ExpressionParser.h"
+#include "InputCommon/ControllerInterface/Device.h"
+
+// ControlReference
+//
+// These are what you create to actually use the inputs, InputReference or OutputReference.
+//
+// After being bound to devices and controls with UpdateReference,
+// each one can link to multiple devices and controls
+// when you change a ControlReference's expression,
+// you must use UpdateReference on it to rebind controls
+//
+
+class ControlReference
+{
+public:
+ static bool InputGateOn();
+
+ virtual ControlState State(const ControlState state = 0) = 0;
+ virtual ciface::Core::Device::Control* Detect(const unsigned int ms,
+ ciface::Core::Device* const device) = 0;
+
+ void UpdateReference(ciface::Core::DeviceContainer& devices,
+ const ciface::Core::DeviceQualifier& default_device);
+
+ ControlState range;
+ std::string expression;
+ const bool is_input;
+ ciface::ExpressionParser::ExpressionParseStatus parse_error;
+
+ virtual ~ControlReference() { delete parsed_expression; }
+ int BoundCount()
+ {
+ if (parsed_expression)
+ return parsed_expression->num_controls;
+ else
+ return 0;
+ }
+
+protected:
+ ControlReference(const bool _is_input) : range(1), is_input(_is_input), parsed_expression(nullptr)
+ {
+ }
+ ciface::ExpressionParser::Expression* parsed_expression;
+};
+
+//
+// InputReference
+//
+// Control reference for inputs
+//
+class InputReference : public ControlReference
+{
+public:
+ InputReference() : ControlReference(true) {}
+ ControlState State(const ControlState state) override;
+ ciface::Core::Device::Control* Detect(const unsigned int ms,
+ ciface::Core::Device* const device) override;
+};
+
+//
+// OutputReference
+//
+// Control reference for outputs
+//
+class OutputReference : public ControlReference
+{
+public:
+ OutputReference() : ControlReference(false) {}
+ ControlState State(const ControlState state) override;
+ ciface::Core::Device::Control* Detect(const unsigned int ms,
+ ciface::Core::Device* const device) override;
+};
diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp
new file mode 100644
index 0000000000..47fbd001a6
--- /dev/null
+++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp
@@ -0,0 +1,567 @@
+// Copyright 2013 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "InputCommon/ControlReference/ExpressionParser.h"
+
+using namespace ciface::Core;
+
+namespace ciface
+{
+namespace ExpressionParser
+{
+enum TokenType
+{
+ TOK_DISCARD,
+ TOK_INVALID,
+ TOK_EOF,
+ TOK_LPAREN,
+ TOK_RPAREN,
+ TOK_AND,
+ TOK_OR,
+ TOK_NOT,
+ TOK_ADD,
+ TOK_CONTROL,
+};
+
+inline std::string OpName(TokenType op)
+{
+ 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
+{
+public:
+ TokenType type;
+ ControlQualifier qualifier;
+
+ Token(TokenType type_) : type(type_) {}
+ Token(TokenType type_, ControlQualifier qualifier_) : type(type_), qualifier(qualifier_) {}
+ operator std::string()
+ {
+ 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;
+ }
+
+ return "Invalid";
+ }
+};
+
+class Lexer
+{
+public:
+ std::string expr;
+ std::string::iterator it;
+
+ 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;
+ }
+
+ Token GetFullyQualifiedControl()
+ {
+ ControlQualifier qualifier;
+ std::string value;
+
+ if (FetchBacktickString(value, ':'))
+ {
+ // Found colon, this is the device name
+ qualifier.has_device = true;
+ qualifier.device_qualifier.FromString(value);
+ FetchBacktickString(value);
+ }
+
+ qualifier.control_name = value;
+
+ return Token(TOK_CONTROL, qualifier);
+ }
+
+ Token GetBarewordsControl(char c)
+ {
+ std::string name;
+ name += c;
+
+ while (it != expr.end())
+ {
+ c = *it;
+ if (!isalpha(c))
+ break;
+ name += c;
+ ++it;
+ }
+
+ ControlQualifier qualifier;
+ qualifier.control_name = name;
+ return Token(TOK_CONTROL, qualifier);
+ }
+
+ Token NextToken()
+ {
+ if (it == expr.end())
+ return Token(TOK_EOF);
+
+ char c = *it++;
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ return Token(TOK_DISCARD);
+ case '(':
+ return Token(TOK_LPAREN);
+ case ')':
+ return Token(TOK_RPAREN);
+ case '&':
+ return Token(TOK_AND);
+ case '|':
+ return Token(TOK_OR);
+ case '!':
+ return Token(TOK_NOT);
+ case '+':
+ return Token(TOK_ADD);
+ case '`':
+ return GetFullyQualifiedControl();
+ default:
+ if (isalpha(c))
+ return GetBarewordsControl(c);
+ else
+ return Token(TOK_INVALID);
+ }
+ }
+
+ ExpressionParseStatus Tokenize(std::vector<Token>& tokens)
+ {
+ while (true)
+ {
+ Token tok = NextToken();
+
+ if (tok.type == TOK_DISCARD)
+ continue;
+
+ if (tok.type == TOK_INVALID)
+ {
+ tokens.clear();
+ return EXPRESSION_PARSE_SYNTAX_ERROR;
+ }
+
+ tokens.push_back(tok);
+
+ if (tok.type == TOK_EOF)
+ break;
+ }
+ return EXPRESSION_PARSE_SUCCESS;
+ }
+};
+
+class ExpressionNode
+{
+public:
+ virtual ~ExpressionNode() {}
+ virtual ControlState GetValue() { return 0; }
+ virtual void SetValue(ControlState state) {}
+ virtual int CountNumControls() { return 0; }
+ virtual operator std::string() { return ""; }
+};
+
+class DummyExpression : public ExpressionNode
+{
+public:
+ std::string name;
+
+ DummyExpression(const std::string& name_) : name(name_) {}
+ ControlState GetValue() override { return 0.0; }
+ void SetValue(ControlState value) override {}
+ int CountNumControls() override { return 0; }
+ operator std::string() override { return "`" + name + "`"; }
+};
+
+class ControlExpression : public ExpressionNode
+{
+public:
+ ControlQualifier qualifier;
+ Device::Control* control;
+
+ ControlExpression(ControlQualifier qualifier_, std::shared_ptr<Device> device,
+ Device::Control* control_)
+ : qualifier(qualifier_), control(control_), m_device(device)
+ {
+ }
+
+ ControlState GetValue() override { return control->ToInput()->GetState(); }
+ void SetValue(ControlState value) override { control->ToOutput()->SetState(value); }
+ int CountNumControls() override { return 1; }
+ operator std::string() override { return "`" + (std::string)qualifier + "`"; }
+private:
+ std::shared_ptr<Device> m_device;
+};
+
+class BinaryExpression : public ExpressionNode
+{
+public:
+ TokenType op;
+ ExpressionNode* lhs;
+ ExpressionNode* rhs;
+
+ BinaryExpression(TokenType op_, ExpressionNode* lhs_, ExpressionNode* rhs_)
+ : op(op_), lhs(lhs_), rhs(rhs_)
+ {
+ }
+ virtual ~BinaryExpression()
+ {
+ delete lhs;
+ delete rhs;
+ }
+
+ ControlState GetValue() override
+ {
+ ControlState lhsValue = lhs->GetValue();
+ ControlState rhsValue = rhs->GetValue();
+ switch (op)
+ {
+ case TOK_AND:
+ return std::min(lhsValue, rhsValue);
+ case TOK_OR:
+ return std::max(lhsValue, rhsValue);
+ case TOK_ADD:
+ return std::min(lhsValue + rhsValue, 1.0);
+ default:
+ assert(false);
+ return 0;
+ }
+ }
+
+ void SetValue(ControlState value) override
+ {
+ // Don't do anything special with the op we have.
+ // Treat "A & B" the same as "A | B".
+ lhs->SetValue(value);
+ rhs->SetValue(value);
+ }
+
+ int CountNumControls() override { return lhs->CountNumControls() + rhs->CountNumControls(); }
+ operator std::string() override
+ {
+ return OpName(op) + "(" + (std::string)(*lhs) + ", " + (std::string)(*rhs) + ")";
+ }
+};
+
+class UnaryExpression : public ExpressionNode
+{
+public:
+ TokenType op;
+ ExpressionNode* inner;
+
+ UnaryExpression(TokenType op_, ExpressionNode* inner_) : op(op_), inner(inner_) {}
+ virtual ~UnaryExpression() { delete inner; }
+ ControlState GetValue() override
+ {
+ ControlState value = inner->GetValue();
+ switch (op)
+ {
+ case TOK_NOT:
+ return 1.0 - value;
+ default:
+ assert(false);
+ return 0;
+ }
+ }
+
+ void SetValue(ControlState value) override
+ {
+ switch (op)
+ {
+ case TOK_NOT:
+ inner->SetValue(1.0 - value);
+ break;
+
+ default:
+ assert(false);
+ }
+ }
+
+ int CountNumControls() override { return inner->CountNumControls(); }
+ operator std::string() override { return OpName(op) + "(" + (std::string)(*inner) + ")"; }
+};
+
+std::shared_ptr<Device> ControlFinder::FindDevice(ControlQualifier qualifier)
+{
+ if (qualifier.has_device)
+ return container.FindDevice(qualifier.device_qualifier);
+ else
+ return container.FindDevice(default_device);
+}
+
+Device::Control* ControlFinder::FindControl(ControlQualifier qualifier)
+{
+ 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);
+}
+
+class Parser
+{
+public:
+ Parser(std::vector<Token> tokens_, ControlFinder& finder_) : tokens(tokens_), finder(finder_)
+ {
+ m_it = tokens.begin();
+ }
+
+ ExpressionParseStatus Parse(Expression** expr_out)
+ {
+ ExpressionNode* node;
+ ExpressionParseStatus status = Toplevel(&node);
+ if (status != EXPRESSION_PARSE_SUCCESS)
+ return status;
+
+ *expr_out = new Expression(node);
+ return EXPRESSION_PARSE_SUCCESS;
+ }
+
+private:
+ std::vector<Token> tokens;
+ std::vector<Token>::iterator m_it;
+ ControlFinder& finder;
+
+ Token Chew() { return *m_it++; }
+ Token Peek() { return *m_it; }
+ bool Expects(TokenType type)
+ {
+ Token tok = Chew();
+ return tok.type == type;
+ }
+
+ ExpressionParseStatus Atom(ExpressionNode** expr_out)
+ {
+ Token tok = Chew();
+ switch (tok.type)
+ {
+ case TOK_CONTROL:
+ {
+ std::shared_ptr<Device> device = finder.FindDevice(tok.qualifier);
+ Device::Control* control = finder.FindControl(tok.qualifier);
+ if (control == nullptr)
+ {
+ *expr_out = new DummyExpression(tok.qualifier);
+ return EXPRESSION_PARSE_NO_DEVICE;
+ }
+
+ *expr_out = new ControlExpression(tok.qualifier, device, control);
+ return EXPRESSION_PARSE_SUCCESS;
+ }
+ case TOK_LPAREN:
+ return Paren(expr_out);
+ default:
+ return EXPRESSION_PARSE_SYNTAX_ERROR;
+ }
+ }
+
+ bool IsUnaryExpression(TokenType type)
+ {
+ switch (type)
+ {
+ case TOK_NOT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ ExpressionParseStatus Unary(ExpressionNode** expr_out)
+ {
+ if (IsUnaryExpression(Peek().type))
+ {
+ Token tok = Chew();
+ ExpressionNode* atom_expr;
+ ExpressionParseStatus status = Atom(&atom_expr);
+ if (status == EXPRESSION_PARSE_SYNTAX_ERROR)
+ return status;
+ *expr_out = new UnaryExpression(tok.type, atom_expr);
+ return EXPRESSION_PARSE_SUCCESS;
+ }
+
+ return Atom(expr_out);
+ }
+
+ bool IsBinaryToken(TokenType type)
+ {
+ switch (type)
+ {
+ case TOK_AND:
+ case TOK_OR:
+ case TOK_ADD:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ ExpressionParseStatus Binary(ExpressionNode** expr_out)
+ {
+ ExpressionParseStatus status = Unary(expr_out);
+ if (status == EXPRESSION_PARSE_SYNTAX_ERROR)
+ return status;
+
+ while (IsBinaryToken(Peek().type))
+ {
+ Token tok = Chew();
+ ExpressionNode* unary_expr;
+ status = Unary(&unary_expr);
+ if (status == EXPRESSION_PARSE_SYNTAX_ERROR)
+ {
+ delete *expr_out;
+ return status;
+ }
+
+ *expr_out = new BinaryExpression(tok.type, *expr_out, unary_expr);
+ }
+
+ return EXPRESSION_PARSE_SUCCESS;
+ }
+
+ ExpressionParseStatus Paren(ExpressionNode** expr_out)
+ {
+ ExpressionParseStatus status;
+
+ // lparen already chewed
+ if ((status = Toplevel(expr_out)) != EXPRESSION_PARSE_SUCCESS)
+ return status;
+
+ if (!Expects(TOK_RPAREN))
+ {
+ delete *expr_out;
+ return EXPRESSION_PARSE_SYNTAX_ERROR;
+ }
+
+ return EXPRESSION_PARSE_SUCCESS;
+ }
+
+ ExpressionParseStatus Toplevel(ExpressionNode** expr_out) { return Binary(expr_out); }
+};
+
+ControlState Expression::GetValue()
+{
+ return node->GetValue();
+}
+
+void Expression::SetValue(ControlState value)
+{
+ node->SetValue(value);
+}
+
+Expression::Expression(ExpressionNode* node_)
+{
+ node = node_;
+ num_controls = node->CountNumControls();
+}
+
+Expression::~Expression()
+{
+ delete node;
+}
+
+static ExpressionParseStatus ParseExpressionInner(const std::string& str, ControlFinder& finder,
+ Expression** expr_out)
+{
+ ExpressionParseStatus status;
+ Expression* expr;
+ *expr_out = nullptr;
+
+ if (str == "")
+ return EXPRESSION_PARSE_SUCCESS;
+
+ Lexer l(str);
+ std::vector<Token> tokens;
+ status = l.Tokenize(tokens);
+ if (status != EXPRESSION_PARSE_SUCCESS)
+ return status;
+
+ Parser p(tokens, finder);
+ status = p.Parse(&expr);
+ if (status != EXPRESSION_PARSE_SUCCESS)
+ return status;
+
+ *expr_out = expr;
+ return EXPRESSION_PARSE_SUCCESS;
+}
+
+ExpressionParseStatus ParseExpression(const std::string& str, ControlFinder& finder,
+ Expression** expr_out)
+{
+ // Add compatibility with old simple expressions, which are simple
+ // barewords control names.
+
+ ControlQualifier qualifier;
+ qualifier.control_name = str;
+ qualifier.has_device = false;
+
+ std::shared_ptr<Device> device = finder.FindDevice(qualifier);
+ Device::Control* control = finder.FindControl(qualifier);
+ if (control)
+ {
+ *expr_out = new Expression(new ControlExpression(qualifier, device, control));
+ return EXPRESSION_PARSE_SUCCESS;
+ }
+
+ return ParseExpressionInner(str, finder, expr_out);
+}
+}
+}
diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.h b/Source/Core/InputCommon/ControlReference/ExpressionParser.h
new file mode 100644
index 0000000000..2c8f5a793f
--- /dev/null
+++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.h
@@ -0,0 +1,72 @@
+// Copyright 2013 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include "InputCommon/ControllerInterface/Device.h"
+
+namespace ciface
+{
+namespace ExpressionParser
+{
+class ControlQualifier
+{
+public:
+ bool has_device;
+ Core::DeviceQualifier device_qualifier;
+ std::string control_name;
+
+ ControlQualifier() : has_device(false) {}
+ operator std::string()
+ {
+ if (has_device)
+ return device_qualifier.ToString() + ":" + control_name;
+ else
+ return control_name;
+ }
+};
+
+class ControlFinder
+{
+public:
+ ControlFinder(const Core::DeviceContainer& container_, const Core::DeviceQualifier& default_,
+ const bool is_input_)
+ : container(container_), default_device(default_), is_input(is_input_)
+ {
+ }
+ std::shared_ptr<Core::Device> FindDevice(ControlQualifier qualifier);
+ Core::Device::Control* FindControl(ControlQualifier qualifier);
+
+private:
+ const Core::DeviceContainer& container;
+ const Core::DeviceQualifier& default_device;
+ bool is_input;
+};
+
+class ExpressionNode;
+class Expression
+{
+public:
+ Expression() : node(nullptr) {}
+ Expression(ExpressionNode* node);
+ ~Expression();
+ ControlState GetValue();
+ void SetValue(ControlState state);
+ int num_controls;
+ ExpressionNode* node;
+};
+
+enum ExpressionParseStatus
+{
+ EXPRESSION_PARSE_SUCCESS = 0,
+ EXPRESSION_PARSE_SYNTAX_ERROR,
+ EXPRESSION_PARSE_NO_DEVICE,
+};
+
+ExpressionParseStatus ParseExpression(const std::string& expr, ControlFinder& finder,
+ Expression** expr_out);
+}
+}