From a9590ae292e4724df5979dd17c5d15d3e14f1a35 Mon Sep 17 00:00:00 2001 From: Daniel <24928223+dnsge@users.noreply.github.com> Date: Sun, 1 Nov 2020 07:33:46 -0500 Subject: [PATCH] Add list literals to filters (#2103) --- .../filters/parser/FilterParser.cpp | 46 +++++++++++ .../filters/parser/FilterParser.hpp | 1 + src/controllers/filters/parser/Tokenizer.cpp | 6 ++ src/controllers/filters/parser/Tokenizer.hpp | 3 +- src/controllers/filters/parser/Types.cpp | 77 +++++++++++++++++++ src/controllers/filters/parser/Types.hpp | 40 +++++++--- 6 files changed, 161 insertions(+), 12 deletions(-) diff --git a/src/controllers/filters/parser/FilterParser.cpp b/src/controllers/filters/parser/FilterParser.cpp index 815766279..d79ea6a6c 100644 --- a/src/controllers/filters/parser/FilterParser.cpp +++ b/src/controllers/filters/parser/FilterParser.cpp @@ -259,6 +259,10 @@ ExpressionPtr FilterParser::parseValue() { return this->parseParentheses(); } + else if (type == TokenType::LIST_START) + { + return this->parseList(); + } else { this->tokenizer_.next(); @@ -275,6 +279,48 @@ ExpressionPtr FilterParser::parseValue() return std::make_unique(0, TokenType::INT); } +ExpressionPtr FilterParser::parseList() +{ + // Don't call .next() before calling this method + assert(this->tokenizer_.nextTokenType() == TokenType::LIST_START); + this->tokenizer_.next(); + + ExpressionList list; + bool first = true; + + while (this->tokenizer_.hasNext()) + { + if (this->tokenizer_.nextTokenType() == TokenType::LIST_END) + { + this->tokenizer_.next(); + return std::make_unique(std::move(list)); + } + else if (this->tokenizer_.nextTokenType() == TokenType::COMMA && !first) + { + this->tokenizer_.next(); + list.push_back(this->parseValue()); + first = false; + } + else if (first) + { + list.push_back(this->parseValue()); + first = false; + } + else + { + break; + } + } + + const auto message = + this->tokenizer_.hasNext() + ? QString("Missing closing list braces: got %1") + .arg(this->tokenizer_.preview()) + : "Missing closing list braces at end of statement"; + this->errorLog(message); + return std::make_unique(ExpressionList()); +} + void FilterParser::errorLog(const QString &text, bool expand) { this->valid_ = false; diff --git a/src/controllers/filters/parser/FilterParser.hpp b/src/controllers/filters/parser/FilterParser.hpp index e292f93da..0c144fae5 100644 --- a/src/controllers/filters/parser/FilterParser.hpp +++ b/src/controllers/filters/parser/FilterParser.hpp @@ -26,6 +26,7 @@ private: ExpressionPtr parseParentheses(); ExpressionPtr parseCondition(); ExpressionPtr parseValue(); + ExpressionPtr parseList(); void errorLog(const QString &text, bool expand = false); diff --git a/src/controllers/filters/parser/Tokenizer.cpp b/src/controllers/filters/parser/Tokenizer.cpp index 14da2bb79..08bc2b9f3 100644 --- a/src/controllers/filters/parser/Tokenizer.cpp +++ b/src/controllers/filters/parser/Tokenizer.cpp @@ -103,6 +103,12 @@ TokenType Tokenizer::tokenize(const QString &text) return TokenType::LP; else if (text == ")") return TokenType::RP; + else if (text == "{") + return TokenType::LIST_START; + else if (text == "}") + return TokenType::LIST_END; + else if (text == ",") + return TokenType::COMMA; else if (text == "+") return TokenType::PLUS; else if (text == "-") diff --git a/src/controllers/filters/parser/Tokenizer.hpp b/src/controllers/filters/parser/Tokenizer.hpp index 49aeff256..dcab024de 100644 --- a/src/controllers/filters/parser/Tokenizer.hpp +++ b/src/controllers/filters/parser/Tokenizer.hpp @@ -26,7 +26,8 @@ static const QRegularExpression tokenRegex( QString("\\\"((\\\\\")|[^\\\"])*\\\"|") + // String literal QString("[\\w\\.]+|") + // Identifier or reserved keyword QString("(<=?|>=?|!=?|==|\\|\\||&&|\\+|-|\\*|\\/|%)+|") + // Operator - QString("[\\(\\)]") // Parentheses + QString("[\\(\\)]|") + // Parentheses + QString("[{},]") // List ); // clang-format on diff --git a/src/controllers/filters/parser/Types.cpp b/src/controllers/filters/parser/Types.cpp index 0eec5f3f8..497c2f55e 100644 --- a/src/controllers/filters/parser/Types.cpp +++ b/src/controllers/filters/parser/Types.cpp @@ -35,6 +35,12 @@ QString tokenTypeToInfoString(TokenType type) return ""; case RP: return ""; + case LIST_START: + return ""; + case LIST_END: + return ""; + case COMMA: + return ""; case PLUS: return ""; case MINUS: @@ -117,6 +123,62 @@ QString ValueExpression::filterString() const } } +// ListExpression + +ListExpression::ListExpression(ExpressionList list) + : list_(std::move(list)){}; + +QVariant ListExpression::execute(const ContextMap &context) const +{ + QList results; + bool allStrings = true; + for (const auto &exp : this->list_) + { + auto res = exp->execute(context); + if (allStrings && res.type() != QVariant::Type::String) + { + allStrings = false; + } + results.append(res); + } + + // if everything is a string return a QStringList for case-insensitive comparison + if (allStrings) + { + QStringList strings; + strings.reserve(results.size()); + for (const auto &val : results) + { + strings << val.toString(); + } + return strings; + } + else + { + return results; + } +} + +QString ListExpression::debug() const +{ + QStringList debugs; + for (const auto &exp : this->list_) + { + debugs.append(exp->debug()); + } + return QString("{%1}").arg(debugs.join(", ")); +} + +QString ListExpression::filterString() const +{ + QStringList strings; + for (const auto &exp : this->list_) + { + strings.append(QString("(%1)").arg(exp->filterString())); + } + return QString("{%1}").arg(strings.join(", ")); +} + // BinaryOperation BinaryOperation::BinaryOperation(TokenType op, ExpressionPtr left, @@ -212,6 +274,11 @@ QVariant BinaryOperation::execute(const ContextMap &context) const return left.toMap().contains(right.toString()); } + if (left.type() == QVariant::Type::List) + { + return left.toList().contains(right); + } + if (left.canConvert(QMetaType::QString) && right.canConvert(QMetaType::QString)) { @@ -230,6 +297,11 @@ QVariant BinaryOperation::execute(const ContextMap &context) const Qt::CaseInsensitive); } + if (left.type() == QVariant::Type::List) + { + return left.toList().startsWith(right); + } + if (left.canConvert(QMetaType::QString) && right.canConvert(QMetaType::QString)) { @@ -249,6 +321,11 @@ QVariant BinaryOperation::execute(const ContextMap &context) const Qt::CaseInsensitive); } + if (left.type() == QVariant::Type::List) + { + return left.toList().endsWith(right); + } + if (left.canConvert(QMetaType::QString) && right.canConvert(QMetaType::QString)) { diff --git a/src/controllers/filters/parser/Types.hpp b/src/controllers/filters/parser/Types.hpp index ee2d481e0..12a2dcfa3 100644 --- a/src/controllers/filters/parser/Types.hpp +++ b/src/controllers/filters/parser/Types.hpp @@ -14,19 +14,22 @@ enum TokenType { OR = 2, LP = 3, RP = 4, - CONTROL_END = 9, + LIST_START = 5, + LIST_END = 6, + COMMA = 7, + CONTROL_END = 19, // binary operator - BINARY_START = 10, - EQ = 11, - NEQ = 12, - LT = 13, - GT = 14, - LTE = 15, - GTE = 16, - CONTAINS = 17, - STARTS_WITH = 18, - ENDS_WITH = 19, + BINARY_START = 20, + EQ = 21, + NEQ = 22, + LT = 23, + GT = 24, + LTE = 25, + GTE = 26, + CONTAINS = 27, + STARTS_WITH = 28, + ENDS_WITH = 29, BINARY_END = 49, // unary operator @@ -93,6 +96,21 @@ private: TokenType type_; }; +using ExpressionList = std::vector>; + +class ListExpression : public Expression +{ +public: + ListExpression(ExpressionList list); + + QVariant execute(const ContextMap &context) const override; + QString debug() const override; + QString filterString() const override; + +private: + ExpressionList list_; +}; + class BinaryOperation : public Expression { public: