Add list literals to filters (#2103)
This commit is contained in:
@@ -259,6 +259,10 @@ ExpressionPtr FilterParser::parseValue()
|
|||||||
{
|
{
|
||||||
return this->parseParentheses();
|
return this->parseParentheses();
|
||||||
}
|
}
|
||||||
|
else if (type == TokenType::LIST_START)
|
||||||
|
{
|
||||||
|
return this->parseList();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this->tokenizer_.next();
|
this->tokenizer_.next();
|
||||||
@@ -275,6 +279,48 @@ ExpressionPtr FilterParser::parseValue()
|
|||||||
return std::make_unique<ValueExpression>(0, TokenType::INT);
|
return std::make_unique<ValueExpression>(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<ListExpression>(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<ListExpression>(ExpressionList());
|
||||||
|
}
|
||||||
|
|
||||||
void FilterParser::errorLog(const QString &text, bool expand)
|
void FilterParser::errorLog(const QString &text, bool expand)
|
||||||
{
|
{
|
||||||
this->valid_ = false;
|
this->valid_ = false;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ private:
|
|||||||
ExpressionPtr parseParentheses();
|
ExpressionPtr parseParentheses();
|
||||||
ExpressionPtr parseCondition();
|
ExpressionPtr parseCondition();
|
||||||
ExpressionPtr parseValue();
|
ExpressionPtr parseValue();
|
||||||
|
ExpressionPtr parseList();
|
||||||
|
|
||||||
void errorLog(const QString &text, bool expand = false);
|
void errorLog(const QString &text, bool expand = false);
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,12 @@ TokenType Tokenizer::tokenize(const QString &text)
|
|||||||
return TokenType::LP;
|
return TokenType::LP;
|
||||||
else if (text == ")")
|
else if (text == ")")
|
||||||
return TokenType::RP;
|
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 == "+")
|
else if (text == "+")
|
||||||
return TokenType::PLUS;
|
return TokenType::PLUS;
|
||||||
else if (text == "-")
|
else if (text == "-")
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ static const QRegularExpression tokenRegex(
|
|||||||
QString("\\\"((\\\\\")|[^\\\"])*\\\"|") + // String literal
|
QString("\\\"((\\\\\")|[^\\\"])*\\\"|") + // String literal
|
||||||
QString("[\\w\\.]+|") + // Identifier or reserved keyword
|
QString("[\\w\\.]+|") + // Identifier or reserved keyword
|
||||||
QString("(<=?|>=?|!=?|==|\\|\\||&&|\\+|-|\\*|\\/|%)+|") + // Operator
|
QString("(<=?|>=?|!=?|==|\\|\\||&&|\\+|-|\\*|\\/|%)+|") + // Operator
|
||||||
QString("[\\(\\)]") // Parentheses
|
QString("[\\(\\)]|") + // Parentheses
|
||||||
|
QString("[{},]") // List
|
||||||
);
|
);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ QString tokenTypeToInfoString(TokenType type)
|
|||||||
return "<left parenthesis>";
|
return "<left parenthesis>";
|
||||||
case RP:
|
case RP:
|
||||||
return "<right parenthesis>";
|
return "<right parenthesis>";
|
||||||
|
case LIST_START:
|
||||||
|
return "<list start>";
|
||||||
|
case LIST_END:
|
||||||
|
return "<list end>";
|
||||||
|
case COMMA:
|
||||||
|
return "<comma>";
|
||||||
case PLUS:
|
case PLUS:
|
||||||
return "<plus>";
|
return "<plus>";
|
||||||
case MINUS:
|
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<QVariant> 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::BinaryOperation(TokenType op, ExpressionPtr left,
|
BinaryOperation::BinaryOperation(TokenType op, ExpressionPtr left,
|
||||||
@@ -212,6 +274,11 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
|||||||
return left.toMap().contains(right.toString());
|
return left.toMap().contains(right.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (left.type() == QVariant::Type::List)
|
||||||
|
{
|
||||||
|
return left.toList().contains(right);
|
||||||
|
}
|
||||||
|
|
||||||
if (left.canConvert(QMetaType::QString) &&
|
if (left.canConvert(QMetaType::QString) &&
|
||||||
right.canConvert(QMetaType::QString))
|
right.canConvert(QMetaType::QString))
|
||||||
{
|
{
|
||||||
@@ -230,6 +297,11 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
|||||||
Qt::CaseInsensitive);
|
Qt::CaseInsensitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (left.type() == QVariant::Type::List)
|
||||||
|
{
|
||||||
|
return left.toList().startsWith(right);
|
||||||
|
}
|
||||||
|
|
||||||
if (left.canConvert(QMetaType::QString) &&
|
if (left.canConvert(QMetaType::QString) &&
|
||||||
right.canConvert(QMetaType::QString))
|
right.canConvert(QMetaType::QString))
|
||||||
{
|
{
|
||||||
@@ -249,6 +321,11 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
|||||||
Qt::CaseInsensitive);
|
Qt::CaseInsensitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (left.type() == QVariant::Type::List)
|
||||||
|
{
|
||||||
|
return left.toList().endsWith(right);
|
||||||
|
}
|
||||||
|
|
||||||
if (left.canConvert(QMetaType::QString) &&
|
if (left.canConvert(QMetaType::QString) &&
|
||||||
right.canConvert(QMetaType::QString))
|
right.canConvert(QMetaType::QString))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,19 +14,22 @@ enum TokenType {
|
|||||||
OR = 2,
|
OR = 2,
|
||||||
LP = 3,
|
LP = 3,
|
||||||
RP = 4,
|
RP = 4,
|
||||||
CONTROL_END = 9,
|
LIST_START = 5,
|
||||||
|
LIST_END = 6,
|
||||||
|
COMMA = 7,
|
||||||
|
CONTROL_END = 19,
|
||||||
|
|
||||||
// binary operator
|
// binary operator
|
||||||
BINARY_START = 10,
|
BINARY_START = 20,
|
||||||
EQ = 11,
|
EQ = 21,
|
||||||
NEQ = 12,
|
NEQ = 22,
|
||||||
LT = 13,
|
LT = 23,
|
||||||
GT = 14,
|
GT = 24,
|
||||||
LTE = 15,
|
LTE = 25,
|
||||||
GTE = 16,
|
GTE = 26,
|
||||||
CONTAINS = 17,
|
CONTAINS = 27,
|
||||||
STARTS_WITH = 18,
|
STARTS_WITH = 28,
|
||||||
ENDS_WITH = 19,
|
ENDS_WITH = 29,
|
||||||
BINARY_END = 49,
|
BINARY_END = 49,
|
||||||
|
|
||||||
// unary operator
|
// unary operator
|
||||||
@@ -93,6 +96,21 @@ private:
|
|||||||
TokenType type_;
|
TokenType type_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using ExpressionList = std::vector<std::unique_ptr<Expression>>;
|
||||||
|
|
||||||
|
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
|
class BinaryOperation : public Expression
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
Reference in New Issue
Block a user