CLI2: Handle empty parentheses expressions

Since taskwarrior is a CLI tool, it is likely that it is interacted with
programmatically. As such, expressions that a human would not type, but
are syntactically correct are bound to occur. In particular, task
currently is not able to handle empty parentheses expressions:

    task +PENDING '(' ')'

This is due to "and" operator being injected between +PENDING (which
translates to '( status = pending )' and '('.

Modify the insertJunctions to not insert the 'and' operator between two
sub-expressions if one of them is an empty parentheses expression.

Closes #1896.
This commit is contained in:
Tomas Babej 2021-01-30 21:06:53 -05:00
parent c6fdb7e590
commit 456dfdc8cf
2 changed files with 47 additions and 4 deletions

View file

@ -1940,6 +1940,40 @@ void CLI2::desugarFilterPlainArgs ()
}
}
////////////////////////////////////////////////////////////////////////////////
// Detects if the bracket at iterator it is a start or end of an empty paren expression
// Examples:
// ( status = pending ) ( )
// ^
// it -----| => true
//
// ( status = pending ) ( project = Home )
// ^
// it -----| => false
bool CLI2::isEmptyParenExpression (std::vector<A2>::iterator it, bool forward /* = true */) const
{
int open = 0;
int closed = 0;
for (auto a = it; a != (forward ? _args.end (): _args.begin()); (forward ? ++a: --a))
{
if (a->attribute("raw") == "(")
open++;
else if (a->attribute("raw") == ")")
closed++;
else
// Encountering a non-paren token means there is something between parenthees
return false;
// Getting balanced parentheses means we have an empty paren expression
if (open == closed && open != 0)
return true;
}
// Should not end here.
return false;
}
////////////////////////////////////////////////////////////////////////////////
// Two consecutive FILTER, non-OP arguments that are not "(" or ")" need an
// "and" operator inserted between them.
@ -1966,10 +2000,18 @@ void CLI2::insertJunctions ()
// Insert AND between terms.
else if (a != prev)
{
if ((prev->_lextype != Lexer::Type::op && a->attribute ("raw") == "(") ||
(prev->_lextype != Lexer::Type::op && a->_lextype != Lexer::Type::op) ||
(prev->attribute ("raw") == ")" && a->_lextype != Lexer::Type::op) ||
(prev->attribute ("raw") == ")" && a->attribute ("raw") == "("))
if ((prev->_lextype != Lexer::Type::op &&
a->attribute ("raw") == "(" &&
! isEmptyParenExpression(a, true) ) ||
(prev->attribute ("raw") == ")" &&
a->_lextype != Lexer::Type::op &&
! isEmptyParenExpression(prev, false)) ||
(prev->attribute ("raw") == ")" &&
a->attribute ("raw") == "(" &&
! isEmptyParenExpression(a, true) &&
! isEmptyParenExpression(prev, false)) ||
(prev->_lextype != Lexer::Type::op &&
a->_lextype != Lexer::Type::op))
{
A2 opOr ("and", Lexer::Type::op);
opOr.tag ("FILTER");

View file

@ -99,6 +99,7 @@ private:
void findUUIDs ();
void insertIDExpr ();
void lexFilterArgs ();
bool isEmptyParenExpression (std::vector<A2>::iterator it, bool forward = true) const;
void desugarFilterPlainArgs ();
void insertJunctions ();
void defaultCommand ();