Add list literals to filters (#2103)

This commit is contained in:
Daniel 2020-11-01 07:33:46 -05:00 committed by GitHub
parent a27757e440
commit a9590ae292
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 161 additions and 12 deletions

View file

@ -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<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)
{
this->valid_ = false;

View file

@ -26,6 +26,7 @@ private:
ExpressionPtr parseParentheses();
ExpressionPtr parseCondition();
ExpressionPtr parseValue();
ExpressionPtr parseList();
void errorLog(const QString &text, bool expand = false);

View file

@ -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 == "-")

View file

@ -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

View file

@ -35,6 +35,12 @@ QString tokenTypeToInfoString(TokenType type)
return "<left parenthesis>";
case RP:
return "<right parenthesis>";
case LIST_START:
return "<list start>";
case LIST_END:
return "<list end>";
case COMMA:
return "<comma>";
case PLUS:
return "<plus>";
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(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))
{

View file

@ -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<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
{
public: