mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-26 15:47:19 +02:00
Expressions
- Broke out expression handling into two distinct flavors: filter and expression. Filters need lots of preparation and mapping, which includes implicit 'AND' operators, expansion of syntactic sugar like /pattern/ etc and evaluate to a Boolean. Expressions are simpler wiht less preparation, and evaluate to a Variant.
This commit is contained in:
parent
d66729adf3
commit
e08d840ba1
6 changed files with 150 additions and 85 deletions
|
@ -1136,7 +1136,7 @@ bool Arguments::is_expression (const std::string& input)
|
||||||
if (is_operator (*token))
|
if (is_operator (*token))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Look for cuddled operators.
|
// Look for bare or cuddled operators.
|
||||||
Lexer lexer (unquoted);
|
Lexer lexer (unquoted);
|
||||||
lexer.skipWhitespace (true);
|
lexer.skipWhitespace (true);
|
||||||
lexer.coalesceAlpha (true);
|
lexer.coalesceAlpha (true);
|
||||||
|
|
|
@ -43,21 +43,8 @@ extern Context context;
|
||||||
// Perform all the necessary steps prior to an eval call.
|
// Perform all the necessary steps prior to an eval call.
|
||||||
Expression::Expression (Arguments& arguments)
|
Expression::Expression (Arguments& arguments)
|
||||||
: _args (arguments)
|
: _args (arguments)
|
||||||
|
, _prepared (false)
|
||||||
{
|
{
|
||||||
if (_args.size ())
|
|
||||||
{
|
|
||||||
_args.dump ("Expression::Expression");
|
|
||||||
|
|
||||||
expand_sequence ();
|
|
||||||
implicit_and ();
|
|
||||||
expand_tag ();
|
|
||||||
expand_pattern ();
|
|
||||||
expand_attr ();
|
|
||||||
expand_attmod ();
|
|
||||||
expand_word ();
|
|
||||||
expand_tokens ();
|
|
||||||
postfix ();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -66,21 +53,84 @@ Expression::~Expression ()
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
bool Expression::eval (const Task& task)
|
bool Expression::evalFilter (const Task& task)
|
||||||
{
|
{
|
||||||
// If there are no elements in the filter, then all tasks pass.
|
|
||||||
if (_args.size () == 0)
|
if (_args.size () == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// There are elements in the filter, so the expression must be evaluated
|
if (!_prepared)
|
||||||
// against each task.
|
{
|
||||||
std::vector <Variant> value_stack;
|
if (_args.size ())
|
||||||
|
{
|
||||||
|
_args.dump ("Expression::evalFilter");
|
||||||
|
|
||||||
|
expand_sequence ();
|
||||||
|
implicit_and ();
|
||||||
|
expand_tag ();
|
||||||
|
expand_pattern ();
|
||||||
|
expand_attr ();
|
||||||
|
expand_attmod ();
|
||||||
|
expand_word ();
|
||||||
|
expand_tokens ();
|
||||||
|
postfix ();
|
||||||
|
}
|
||||||
|
|
||||||
|
_prepared = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the expression.
|
||||||
|
std::vector <Variant> value_stack;
|
||||||
|
eval (task, value_stack);
|
||||||
|
|
||||||
|
// Coerce stack element to boolean.
|
||||||
|
Variant result (value_stack.back ());
|
||||||
|
value_stack.pop_back ();
|
||||||
|
return result.boolean ();
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
std::string Expression::evalExpression (const Task& task)
|
||||||
|
{
|
||||||
|
if (_args.size () == 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
if (!_prepared)
|
||||||
|
{
|
||||||
|
if (_args.size ())
|
||||||
|
{
|
||||||
|
_args.dump ("Expression::evalFilter");
|
||||||
|
|
||||||
|
expand_sequence ();
|
||||||
|
implicit_and ();
|
||||||
|
expand_tag ();
|
||||||
|
expand_pattern ();
|
||||||
|
expand_attr ();
|
||||||
|
expand_attmod ();
|
||||||
|
expand_word ();
|
||||||
|
expand_tokens ();
|
||||||
|
postfix ();
|
||||||
|
}
|
||||||
|
|
||||||
|
_prepared = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the expression.
|
||||||
|
std::vector <Variant> value_stack;
|
||||||
|
eval (task, value_stack);
|
||||||
|
|
||||||
|
// Coerce stack element to boolean.
|
||||||
|
Variant result (value_stack.back ());
|
||||||
|
value_stack.pop_back ();
|
||||||
|
result.cast (Variant::v_string);
|
||||||
|
return result._string;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void Expression::eval (const Task& task, std::vector <Variant>& value_stack)
|
||||||
|
{
|
||||||
// Case sensitivity is configurable.
|
// Case sensitivity is configurable.
|
||||||
bool case_sensitive = context.config.getBoolean ("search.case.sensitive");
|
bool case_sensitive = context.config.getBoolean ("search.case.sensitive");
|
||||||
|
|
||||||
// TODO Build an on-demand regex cache.
|
|
||||||
|
|
||||||
std::vector <Triple>::const_iterator arg;
|
std::vector <Triple>::const_iterator arg;
|
||||||
for (arg = _args.begin (); arg != _args.end (); ++arg)
|
for (arg = _args.begin (); arg != _args.end (); ++arg)
|
||||||
{
|
{
|
||||||
|
@ -382,16 +432,9 @@ bool Expression::eval (const Task& task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coerce stack element to boolean.
|
|
||||||
Variant result (value_stack.back ());
|
|
||||||
value_stack.pop_back ();
|
|
||||||
bool pass_fail = result.boolean ();
|
|
||||||
|
|
||||||
// Check for stack remnants.
|
// Check for stack remnants.
|
||||||
if (value_stack.size ())
|
if (value_stack.size () != 1)
|
||||||
throw std::string ("Error: Expression::eval found extra items on the stack.");
|
throw std::string ("Error: Expression::eval found extra items on the stack.");
|
||||||
|
|
||||||
return pass_fail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -515,7 +558,7 @@ void Expression::expand_sequence ()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now insert the new sequence expression.
|
// Now insert the new sequence expression.
|
||||||
temp.push_back (Triple (sequence.str (), "exp", "seq"));
|
temp.push_back (Triple (sequence.str (), "", "seq"));
|
||||||
|
|
||||||
// Now copy everything after the last id/uuid.
|
// Now copy everything after the last id/uuid.
|
||||||
bool found_id = false;
|
bool found_id = false;
|
||||||
|
@ -533,8 +576,6 @@ void Expression::expand_sequence ()
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Nibble the whole bloody thing. Nuke it from orbit - it's the only way to be
|
|
||||||
// sure.
|
|
||||||
void Expression::expand_tokens ()
|
void Expression::expand_tokens ()
|
||||||
{
|
{
|
||||||
Arguments temp;
|
Arguments temp;
|
||||||
|
@ -543,70 +584,85 @@ void Expression::expand_tokens ()
|
||||||
// Get a list of all operators.
|
// Get a list of all operators.
|
||||||
std::vector <std::string> operators = Arguments::operator_list ();
|
std::vector <std::string> operators = Arguments::operator_list ();
|
||||||
|
|
||||||
|
// Look at all args.
|
||||||
|
std::vector <Triple>::iterator arg;
|
||||||
|
for (arg = _args.begin (); arg != _args.end (); ++arg)
|
||||||
|
{
|
||||||
|
if (arg->_third == "seq" ||
|
||||||
|
arg->_third == "exp")
|
||||||
|
{
|
||||||
|
tokenize (arg->_first, arg->_third, operators, temp);
|
||||||
|
delta = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
temp.push_back (*arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta)
|
||||||
|
{
|
||||||
|
_args.swap (temp);
|
||||||
|
_args.dump ("Expression::expand_tokens");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Nibble the whole bloody thing. Nuke it from orbit - it's the only way to be
|
||||||
|
// sure.
|
||||||
|
void Expression::tokenize (
|
||||||
|
const std::string& input,
|
||||||
|
const std::string& category,
|
||||||
|
std::vector <std::string>& operators,
|
||||||
|
Arguments& tokens)
|
||||||
|
{
|
||||||
// Date format, for both parsing and rendering.
|
// Date format, for both parsing and rendering.
|
||||||
std::string date_format = context.config.get ("dateformat");
|
std::string date_format = context.config.get ("dateformat");
|
||||||
|
|
||||||
|
// Nibble each arg token by token.
|
||||||
|
Nibbler n (input);
|
||||||
|
|
||||||
// Fake polymorphism.
|
// Fake polymorphism.
|
||||||
std::string s;
|
std::string s;
|
||||||
int i;
|
int i;
|
||||||
double d;
|
double d;
|
||||||
time_t t;
|
time_t t;
|
||||||
|
|
||||||
// Look at all args.
|
while (! n.depleted ())
|
||||||
std::vector <Triple>::iterator arg;
|
|
||||||
for (arg = _args.begin (); arg != _args.end (); ++arg)
|
|
||||||
{
|
{
|
||||||
if (arg->_second == "exp")
|
if (n.getQuoted ('"', s, true) ||
|
||||||
{
|
n.getQuoted ('\'', s, true))
|
||||||
// Nibble each arg token by token.
|
tokens.push_back (Triple (s, "string", category));
|
||||||
Nibbler n (arg->_first);
|
|
||||||
|
|
||||||
while (! n.depleted ())
|
else if (n.getQuoted ('/', s, true))
|
||||||
{
|
tokens.push_back (Triple (s, "pattern", category));
|
||||||
if (n.getQuoted ('"', s, true) ||
|
|
||||||
n.getQuoted ('\'', s, true))
|
|
||||||
temp.push_back (Triple (s, "string", arg->_third));
|
|
||||||
|
|
||||||
else if (n.getQuoted ('/', s, true))
|
else if (n.getOneOf (operators, s))
|
||||||
temp.push_back (Triple (s, "pattern", arg->_third));
|
tokens.push_back (Triple (s, "op", category));
|
||||||
|
|
||||||
else if (n.getOneOf (operators, s))
|
else if (n.getDOM (s))
|
||||||
temp.push_back (Triple (s, "op", arg->_third));
|
tokens.push_back (Triple (s, "lvalue", category));
|
||||||
|
|
||||||
else if (n.getDOM (s))
|
else if (n.getNumber (d))
|
||||||
temp.push_back (Triple (s, "lvalue", arg->_third));
|
tokens.push_back (Triple (format (d), "number", category));
|
||||||
|
|
||||||
else if (n.getNumber (d))
|
else if (n.getInt (i))
|
||||||
temp.push_back (Triple (format (d), "number", arg->_third));
|
tokens.push_back (Triple (format (i), "int", category));
|
||||||
|
|
||||||
else if (n.getInt (i))
|
else if (n.getDateISO (t))
|
||||||
temp.push_back (Triple (format (i), "int", arg->_third));
|
tokens.push_back (Triple (Date (t).toISO (), "date", category));
|
||||||
|
|
||||||
else if (n.getDateISO (t))
|
else if (n.getDate (date_format, t))
|
||||||
temp.push_back (Triple (Date (t).toISO (), "date", arg->_third));
|
tokens.push_back (Triple (Date (t).toString (date_format), "date", category));
|
||||||
|
|
||||||
else if (n.getDate (date_format, t))
|
|
||||||
temp.push_back (Triple (Date (t).toString (date_format), "date", arg->_third));
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (! n.getUntilWS (s))
|
|
||||||
n.getUntilEOS (s);
|
|
||||||
|
|
||||||
temp.push_back (Triple (s, "?", arg->_third));
|
|
||||||
}
|
|
||||||
|
|
||||||
n.skipWS ();
|
|
||||||
}
|
|
||||||
|
|
||||||
delta = true;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
temp.push_back (*arg);
|
{
|
||||||
}
|
if (! n.getUntilWS (s))
|
||||||
|
n.getUntilEOS (s);
|
||||||
|
|
||||||
_args.swap (temp);
|
tokens.push_back (Triple (s, "?", category));
|
||||||
_args.dump ("Expression::expand_tokens");
|
}
|
||||||
|
|
||||||
|
n.skipWS ();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -724,10 +780,11 @@ void Expression::expand_attr ()
|
||||||
{
|
{
|
||||||
if (arg->_third == "attr")
|
if (arg->_third == "attr")
|
||||||
{
|
{
|
||||||
// TODO Canonicalize 'name'.
|
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string value;
|
std::string value;
|
||||||
Arguments::extract_attr (arg->_first, name, value);
|
Arguments::extract_attr (arg->_first, name, value);
|
||||||
|
|
||||||
|
// Canonicalize 'name'.
|
||||||
Arguments::is_attribute (name, name);
|
Arguments::is_attribute (name, name);
|
||||||
|
|
||||||
// Always quote the value, so that empty values, or values containing spaces
|
// Always quote the value, so that empty values, or values containing spaces
|
||||||
|
@ -816,13 +873,13 @@ void Expression::expand_attmod ()
|
||||||
{
|
{
|
||||||
temp.push_back (Triple (name, "lvalue", arg->_third));
|
temp.push_back (Triple (name, "lvalue", arg->_third));
|
||||||
temp.push_back (Triple ("~", "op", arg->_third));
|
temp.push_back (Triple ("~", "op", arg->_third));
|
||||||
temp.push_back (Triple (value, "rvalue", arg->_third));
|
temp.push_back (Triple (value, "rx", arg->_third));
|
||||||
}
|
}
|
||||||
else if (mod == "hasnt")
|
else if (mod == "hasnt")
|
||||||
{
|
{
|
||||||
temp.push_back (Triple (name, "lvalue", arg->_third));
|
temp.push_back (Triple (name, "lvalue", arg->_third));
|
||||||
temp.push_back (Triple ("!~", "op", arg->_third));
|
temp.push_back (Triple ("!~", "op", arg->_third));
|
||||||
temp.push_back (Triple (value, "rvalue", arg->_third));
|
temp.push_back (Triple (value, "rx", arg->_third));
|
||||||
}
|
}
|
||||||
else if (mod == "startswith" || mod == "left")
|
else if (mod == "startswith" || mod == "left")
|
||||||
{
|
{
|
||||||
|
@ -879,7 +936,7 @@ void Expression::expand_word ()
|
||||||
{
|
{
|
||||||
temp.push_back (Triple ("description", "lvalue", arg->_third));
|
temp.push_back (Triple ("description", "lvalue", arg->_third));
|
||||||
temp.push_back (Triple ("~", "op", arg->_third));
|
temp.push_back (Triple ("~", "op", arg->_third));
|
||||||
temp.push_back (Triple ("\"" + arg->_first + "\"", "rvalue", arg->_third));
|
temp.push_back (Triple ("\"" + arg->_first + "\"", "string", arg->_third));
|
||||||
|
|
||||||
delta = true;
|
delta = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,9 @@ public:
|
||||||
Expression (Arguments&);
|
Expression (Arguments&);
|
||||||
~Expression ();
|
~Expression ();
|
||||||
bool eval (const Task&);
|
bool eval (const Task&);
|
||||||
|
bool evalFilter (const Task&);
|
||||||
|
std::string evalExpression (const Task&);
|
||||||
|
void eval (const Task&, std::vector <Variant>&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void expand_sequence ();
|
void expand_sequence ();
|
||||||
|
@ -53,6 +56,7 @@ private:
|
||||||
void expand_tokens ();
|
void expand_tokens ();
|
||||||
void postfix ();
|
void postfix ();
|
||||||
|
|
||||||
|
void tokenize (const std::string&, const std::string&, std::vector <std::string>&, Arguments&);
|
||||||
void create_variant (Variant&, const std::string&, const std::string&);
|
void create_variant (Variant&, const std::string&, const std::string&);
|
||||||
bool is_new_style ();
|
bool is_new_style ();
|
||||||
|
|
||||||
|
@ -62,6 +66,7 @@ private:
|
||||||
private:
|
private:
|
||||||
Arguments _args;
|
Arguments _args;
|
||||||
std::map <std::string, RX> _regexes;
|
std::map <std::string, RX> _regexes;
|
||||||
|
bool _prepared;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -69,8 +69,9 @@ int CmdAdd::execute (std::string& output)
|
||||||
if (context.verbose ("new-id"))
|
if (context.verbose ("new-id"))
|
||||||
output = format (STRING_CMD_ADD_FEEDBACK, context.tdb.nextId ()) + "\n";
|
output = format (STRING_CMD_ADD_FEEDBACK, context.tdb.nextId ()) + "\n";
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
context.footnote (onProjectChange (task));
|
context.footnote (onProjectChange (task));
|
||||||
|
*/
|
||||||
context.tdb2.commit ();
|
context.tdb2.commit ();
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,9 @@ int CmdLog::execute (std::string& output)
|
||||||
task.validate ();
|
task.validate ();
|
||||||
context.tdb2.add (task);
|
context.tdb2.add (task);
|
||||||
|
|
||||||
|
/*
|
||||||
context.footnote (onProjectChange (task));
|
context.footnote (onProjectChange (task));
|
||||||
|
*/
|
||||||
context.tdb2.commit ();
|
context.tdb2.commit ();
|
||||||
|
|
||||||
if (context.config.getBoolean ("echo.command"))
|
if (context.config.getBoolean ("echo.command"))
|
||||||
|
|
|
@ -279,7 +279,7 @@ void Command::filter (std::vector <Task>& input, std::vector <Task>& output)
|
||||||
|
|
||||||
std::vector <Task>::iterator task;
|
std::vector <Task>::iterator task;
|
||||||
for (task = input.begin (); task != input.end (); ++task)
|
for (task = input.begin (); task != input.end (); ++task)
|
||||||
if (e.eval (*task))
|
if (e.evalFilter (*task))
|
||||||
output.push_back (*task);
|
output.push_back (*task);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -306,14 +306,14 @@ void Command::filter (std::vector <Task>& output)
|
||||||
output.clear ();
|
output.clear ();
|
||||||
std::vector <Task>::const_iterator task;
|
std::vector <Task>::const_iterator task;
|
||||||
for (task = pending.begin (); task != pending.end (); ++task)
|
for (task = pending.begin (); task != pending.end (); ++task)
|
||||||
if (e.eval (*task))
|
if (e.evalFilter (*task))
|
||||||
output.push_back (*task);
|
output.push_back (*task);
|
||||||
|
|
||||||
if (! filter_shortcut (f))
|
if (! filter_shortcut (f))
|
||||||
{
|
{
|
||||||
const std::vector <Task>& completed = context.tdb2.completed.get_tasks (); // TODO Optional
|
const std::vector <Task>& completed = context.tdb2.completed.get_tasks (); // TODO Optional
|
||||||
for (task = completed.begin (); task != completed.end (); ++task)
|
for (task = completed.begin (); task != completed.end (); ++task)
|
||||||
if (e.eval (*task))
|
if (e.evalFilter (*task))
|
||||||
output.push_back (*task);
|
output.push_back (*task);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue