From 5078a63084121fa31d6d763dfa30ba5b0f99f040 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Wed, 6 Nov 2024 19:13:33 -0600 Subject: [PATCH] InputCommon: Add ternary conditional operator to input expressions. --- .../DolphinQt/Config/Mapping/IOWindow.cpp | 3 + .../ControlReference/ExpressionParser.cpp | 60 +++++++++++++++---- .../ControlReference/ExpressionParser.h | 2 + 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp index 9c9dc3e3b6..a43d91df09 100644 --- a/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp @@ -160,6 +160,8 @@ void ControlExpressionSyntaxHighlighter::highlightBlock(const QString&) case TokenType::TOK_LPAREN: case TokenType::TOK_RPAREN: case TokenType::TOK_COMMA: + case TokenType::TOK_QUESTION: + case TokenType::TOK_COLON: char_format = GetSpecialCharFormat(); break; case TokenType::TOK_LITERAL: @@ -290,6 +292,7 @@ void IOWindow::CreateMainLayout() m_operators_combo->addItem(tr("< Less-than")); m_operators_combo->addItem(tr("& And")); m_operators_combo->addItem(tr("^ Xor")); + m_operators_combo->addItem(tr("? Conditional")); } m_operators_combo->addItem(tr("| Or")); m_operators_combo->addItem(tr("$ User Variable")); diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp index b558d6b78f..e256eeeff5 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.cpp @@ -168,6 +168,10 @@ Token Lexer::NextToken() return Token(TOK_RPAREN); case '@': return Token(TOK_HOTKEY); + case '?': + return Token(TOK_QUESTION); + case ':': + return Token(TOK_COLON); case '&': return Token(TOK_AND); case '|': @@ -745,7 +749,7 @@ private: { // Read one argument. // Grab an expression, but stop at comma. - auto arg = ParseBinary(BinaryOperatorPrecedence(TOK_COMMA)); + auto arg = ParseInfixOperations(OperatorPrecedence(TOK_COMMA)); if (ParseStatus::Successful != arg.status) return arg; @@ -844,7 +848,7 @@ private: } } - static int BinaryOperatorPrecedence(TokenType type) + static constexpr int OperatorPrecedence(TokenType type = TOK_EOF) { switch (type) { @@ -865,16 +869,16 @@ private: case TOK_OR: return 6; case TOK_ASSIGN: + case TOK_QUESTION: return 7; case TOK_COMMA: return 8; default: - ASSERT(false); - return 0; + return 999; } } - ParseResult ParseBinary(int precedence = 999) + ParseResult ParseInfixOperations(int precedence = OperatorPrecedence()) { ParseResult lhs = ParseAtom(Chew()); @@ -884,18 +888,48 @@ private: std::unique_ptr expr = std::move(lhs.expr); // TODO: handle LTR/RTL associativity? - while (Peek().IsBinaryOperator() && BinaryOperatorPrecedence(Peek().type) < precedence) + while (true) { - const Token tok = Chew(); - ParseResult rhs = ParseBinary(BinaryOperatorPrecedence(tok.type)); - if (rhs.status == ParseStatus::SyntaxError) + const Token op = Peek(); + if (op.IsBinaryOperator() && OperatorPrecedence(op.type) < precedence) { - return rhs; + Chew(); + ParseResult rhs = ParseInfixOperations(OperatorPrecedence(op.type)); + if (rhs.status == ParseStatus::SyntaxError) + return rhs; + + expr = std::make_unique(op.type, std::move(expr), std::move(rhs.expr)); } + else if (op.type == TOK_QUESTION && OperatorPrecedence(TOK_QUESTION) <= precedence) + { + // Handle conditional operator: (a ? b : c) + Chew(); + auto true_result = ParseInfixOperations(OperatorPrecedence(op.type)); + if (true_result.status != ParseStatus::Successful) + return true_result; - expr = std::make_unique(tok.type, std::move(expr), std::move(rhs.expr)); + const Token should_be_colon = Chew(); + if (should_be_colon.type != TOK_COLON) + return ParseResult::MakeErrorResult(should_be_colon, + Common::GetStringT("Expected colon.")); + + auto false_result = ParseInfixOperations(OperatorPrecedence(op.type)); + if (false_result.status != ParseStatus::Successful) + return false_result; + + auto conditional = MakeFunctionExpression("if"); + std::vector> args; + args.emplace_back(std::move(expr)); + args.emplace_back(std::move(true_result.expr)); + args.emplace_back(std::move(false_result.expr)); + conditional->SetArguments(std::move(args)); + expr = std::move(conditional); + } + else + { + break; + } } - return ParseResult::MakeSuccessfulResult(std::move(expr)); } @@ -948,7 +982,7 @@ private: return ParseResult::MakeSuccessfulResult(std::make_unique(std::move(inputs))); } - ParseResult ParseToplevel() { return ParseBinary(); } + ParseResult ParseToplevel() { return ParseInfixOperations(); } }; // namespace ExpressionParser ParseResult ParseTokens(const std::vector& tokens) diff --git a/Source/Core/InputCommon/ControlReference/ExpressionParser.h b/Source/Core/InputCommon/ControlReference/ExpressionParser.h index bc39762127..4952915ced 100644 --- a/Source/Core/InputCommon/ControlReference/ExpressionParser.h +++ b/Source/Core/InputCommon/ControlReference/ExpressionParser.h @@ -26,6 +26,8 @@ enum TokenType TOK_BAREWORD, TOK_COMMENT, TOK_HOTKEY, + TOK_QUESTION, + TOK_COLON, // Binary Ops: TOK_BINARY_OPS_BEGIN, TOK_AND = TOK_BINARY_OPS_BEGIN,