From a509f56116d308c96e38898e07d3538f26a1b7af Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Tue, 11 Oct 2016 17:48:38 -0700 Subject: 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. --- .../ControlReference/ExpressionParser.cpp | 567 +++++++++++++++++++++ 1 file changed, 567 insertions(+) create mode 100644 Source/Core/InputCommon/ControlReference/ExpressionParser.cpp (limited to 'Source/Core/InputCommon/ControlReference/ExpressionParser.cpp') 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 +#include +#include +#include +#include +#include +#include + +#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& 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::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 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 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 = 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 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 tokens; + std::vector::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 = 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 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 = 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); +} +} +} -- cgit v1.2.3