From: Tomas Babej Date: Sat, 30 Jan 2021 17:50:58 -0500 Subject: Backport parser issues fixes This backports upstream PR #2405 and commit a5eee5f, which contain extra fixes and tests for issues with numeric project names (amongst others) which were being wrongly interpreted as expressions. Addresses upstream issues TW-{2388,2386,2257,1908,1896} --- diff --git a/src/CLI2.cpp b/src/CLI2.cpp index ec2bd6c7..c49eb4d7 100644 --- a/src/CLI2.cpp +++ b/src/CLI2.cpp @@ -1277,7 +1277,9 @@ void CLI2::desugarFilterAttributes () A2 op ("", Lexer::Type::op); op.tag ("FILTER"); - A2 rhs ("", values[0]._lextype); + // Attribute types that do not support evaluation should be interpreted + // as strings (currently this means that string attributes are not evaluated) + A2 rhs ("", evalSupported ? values[0]._lextype: Lexer::Type::string); rhs.tag ("FILTER"); // Special case for ':'. @@ -1371,7 +1373,7 @@ void CLI2::desugarFilterAttributes () // Do not modify this construct without full understanding. // Getting this wrong breaks a whole lot of filtering tests. - if (values.size () > 1 || evalSupported) + if (evalSupported) { for (auto& v : values) reconstructed.push_back (v); @@ -1946,6 +1948,41 @@ 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::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. // @@ -1971,10 +2008,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"); diff --git a/src/CLI2.h b/src/CLI2.h index f6b0ce94..025be4c8 100644 --- a/src/CLI2.h +++ b/src/CLI2.h @@ -99,6 +99,7 @@ private: void findUUIDs (); void insertIDExpr (); void lexFilterArgs (); + bool isEmptyParenExpression (std::vector::iterator it, bool forward = true) const; void desugarFilterPlainArgs (); void insertJunctions (); void defaultCommand (); diff --git a/src/columns/ColTypeString.cpp b/src/columns/ColTypeString.cpp index 37c1433d..4ef97578 100644 --- a/src/columns/ColTypeString.cpp +++ b/src/columns/ColTypeString.cpp @@ -58,7 +58,12 @@ void ColumnTypeString::modify (Task& task, const std::string& value) std::string domRef; Lexer::Type type; if (lexer.token (domRef, type) && - type == Lexer::Type::dom) + type == Lexer::Type::dom && + // Ensure 'value' contains only the DOM reference and no other tokens + // The lexer.token returns false for end-of-string. + // This works as long as all the DOM references we should support consist + // only of a single token. + lexer.token (domRef, type) == false) { Eval e; e.addSource (domSource);