mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-07 20:06:36 +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))
|
||||
return true;
|
||||
|
||||
// Look for cuddled operators.
|
||||
// Look for bare or cuddled operators.
|
||||
Lexer lexer (unquoted);
|
||||
lexer.skipWhitespace (true);
|
||||
lexer.coalesceAlpha (true);
|
||||
|
|
|
@ -43,10 +43,26 @@ extern Context context;
|
|||
// Perform all the necessary steps prior to an eval call.
|
||||
Expression::Expression (Arguments& arguments)
|
||||
: _args (arguments)
|
||||
, _prepared (false)
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
Expression::~Expression ()
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool Expression::evalFilter (const Task& task)
|
||||
{
|
||||
if (_args.size () == 0)
|
||||
return true;
|
||||
|
||||
if (!_prepared)
|
||||
{
|
||||
if (_args.size ())
|
||||
{
|
||||
_args.dump ("Expression::Expression");
|
||||
_args.dump ("Expression::evalFilter");
|
||||
|
||||
expand_sequence ();
|
||||
implicit_and ();
|
||||
|
@ -58,29 +74,63 @@ Expression::Expression (Arguments& arguments)
|
|||
expand_tokens ();
|
||||
postfix ();
|
||||
}
|
||||
|
||||
_prepared = true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
Expression::~Expression ()
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool Expression::eval (const Task& task)
|
||||
{
|
||||
// If there are no elements in the filter, then all tasks pass.
|
||||
if (_args.size () == 0)
|
||||
return true;
|
||||
|
||||
// There are elements in the filter, so the expression must be evaluated
|
||||
// against each task.
|
||||
// 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.
|
||||
bool case_sensitive = context.config.getBoolean ("search.case.sensitive");
|
||||
|
||||
// TODO Build an on-demand regex cache.
|
||||
|
||||
std::vector <Triple>::const_iterator 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.
|
||||
if (value_stack.size ())
|
||||
if (value_stack.size () != 1)
|
||||
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.
|
||||
temp.push_back (Triple (sequence.str (), "exp", "seq"));
|
||||
temp.push_back (Triple (sequence.str (), "", "seq"));
|
||||
|
||||
// Now copy everything after the last id/uuid.
|
||||
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 ()
|
||||
{
|
||||
Arguments temp;
|
||||
|
@ -543,70 +584,85 @@ void Expression::expand_tokens ()
|
|||
// Get a list of all operators.
|
||||
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.
|
||||
std::string date_format = context.config.get ("dateformat");
|
||||
|
||||
// Nibble each arg token by token.
|
||||
Nibbler n (input);
|
||||
|
||||
// Fake polymorphism.
|
||||
std::string s;
|
||||
int i;
|
||||
double d;
|
||||
time_t t;
|
||||
|
||||
// Look at all args.
|
||||
std::vector <Triple>::iterator arg;
|
||||
for (arg = _args.begin (); arg != _args.end (); ++arg)
|
||||
{
|
||||
if (arg->_second == "exp")
|
||||
{
|
||||
// Nibble each arg token by token.
|
||||
Nibbler n (arg->_first);
|
||||
|
||||
while (! n.depleted ())
|
||||
{
|
||||
if (n.getQuoted ('"', s, true) ||
|
||||
n.getQuoted ('\'', s, true))
|
||||
temp.push_back (Triple (s, "string", arg->_third));
|
||||
tokens.push_back (Triple (s, "string", category));
|
||||
|
||||
else if (n.getQuoted ('/', s, true))
|
||||
temp.push_back (Triple (s, "pattern", arg->_third));
|
||||
tokens.push_back (Triple (s, "pattern", category));
|
||||
|
||||
else if (n.getOneOf (operators, s))
|
||||
temp.push_back (Triple (s, "op", arg->_third));
|
||||
tokens.push_back (Triple (s, "op", category));
|
||||
|
||||
else if (n.getDOM (s))
|
||||
temp.push_back (Triple (s, "lvalue", arg->_third));
|
||||
tokens.push_back (Triple (s, "lvalue", category));
|
||||
|
||||
else if (n.getNumber (d))
|
||||
temp.push_back (Triple (format (d), "number", arg->_third));
|
||||
tokens.push_back (Triple (format (d), "number", category));
|
||||
|
||||
else if (n.getInt (i))
|
||||
temp.push_back (Triple (format (i), "int", arg->_third));
|
||||
tokens.push_back (Triple (format (i), "int", category));
|
||||
|
||||
else if (n.getDateISO (t))
|
||||
temp.push_back (Triple (Date (t).toISO (), "date", arg->_third));
|
||||
tokens.push_back (Triple (Date (t).toISO (), "date", category));
|
||||
|
||||
else if (n.getDate (date_format, t))
|
||||
temp.push_back (Triple (Date (t).toString (date_format), "date", arg->_third));
|
||||
tokens.push_back (Triple (Date (t).toString (date_format), "date", category));
|
||||
|
||||
else
|
||||
{
|
||||
if (! n.getUntilWS (s))
|
||||
n.getUntilEOS (s);
|
||||
|
||||
temp.push_back (Triple (s, "?", arg->_third));
|
||||
tokens.push_back (Triple (s, "?", category));
|
||||
}
|
||||
|
||||
n.skipWS ();
|
||||
}
|
||||
|
||||
delta = true;
|
||||
}
|
||||
else
|
||||
temp.push_back (*arg);
|
||||
}
|
||||
|
||||
_args.swap (temp);
|
||||
_args.dump ("Expression::expand_tokens");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -724,10 +780,11 @@ void Expression::expand_attr ()
|
|||
{
|
||||
if (arg->_third == "attr")
|
||||
{
|
||||
// TODO Canonicalize 'name'.
|
||||
std::string name;
|
||||
std::string value;
|
||||
Arguments::extract_attr (arg->_first, name, value);
|
||||
|
||||
// Canonicalize 'name'.
|
||||
Arguments::is_attribute (name, name);
|
||||
|
||||
// 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 ("~", "op", arg->_third));
|
||||
temp.push_back (Triple (value, "rvalue", arg->_third));
|
||||
temp.push_back (Triple (value, "rx", arg->_third));
|
||||
}
|
||||
else if (mod == "hasnt")
|
||||
{
|
||||
temp.push_back (Triple (name, "lvalue", 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")
|
||||
{
|
||||
|
@ -879,7 +936,7 @@ void Expression::expand_word ()
|
|||
{
|
||||
temp.push_back (Triple ("description", "lvalue", 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;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,9 @@ public:
|
|||
Expression (Arguments&);
|
||||
~Expression ();
|
||||
bool eval (const Task&);
|
||||
bool evalFilter (const Task&);
|
||||
std::string evalExpression (const Task&);
|
||||
void eval (const Task&, std::vector <Variant>&);
|
||||
|
||||
private:
|
||||
void expand_sequence ();
|
||||
|
@ -53,6 +56,7 @@ private:
|
|||
void expand_tokens ();
|
||||
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&);
|
||||
bool is_new_style ();
|
||||
|
||||
|
@ -62,6 +66,7 @@ private:
|
|||
private:
|
||||
Arguments _args;
|
||||
std::map <std::string, RX> _regexes;
|
||||
bool _prepared;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -69,8 +69,9 @@ int CmdAdd::execute (std::string& output)
|
|||
if (context.verbose ("new-id"))
|
||||
output = format (STRING_CMD_ADD_FEEDBACK, context.tdb.nextId ()) + "\n";
|
||||
*/
|
||||
|
||||
/*
|
||||
context.footnote (onProjectChange (task));
|
||||
*/
|
||||
context.tdb2.commit ();
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -78,7 +78,9 @@ int CmdLog::execute (std::string& output)
|
|||
task.validate ();
|
||||
context.tdb2.add (task);
|
||||
|
||||
/*
|
||||
context.footnote (onProjectChange (task));
|
||||
*/
|
||||
context.tdb2.commit ();
|
||||
|
||||
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;
|
||||
for (task = input.begin (); task != input.end (); ++task)
|
||||
if (e.eval (*task))
|
||||
if (e.evalFilter (*task))
|
||||
output.push_back (*task);
|
||||
}
|
||||
else
|
||||
|
@ -306,14 +306,14 @@ void Command::filter (std::vector <Task>& output)
|
|||
output.clear ();
|
||||
std::vector <Task>::const_iterator task;
|
||||
for (task = pending.begin (); task != pending.end (); ++task)
|
||||
if (e.eval (*task))
|
||||
if (e.evalFilter (*task))
|
||||
output.push_back (*task);
|
||||
|
||||
if (! filter_shortcut (f))
|
||||
{
|
||||
const std::vector <Task>& completed = context.tdb2.completed.get_tasks (); // TODO Optional
|
||||
for (task = completed.begin (); task != completed.end (); ++task)
|
||||
if (e.eval (*task))
|
||||
if (e.evalFilter (*task))
|
||||
output.push_back (*task);
|
||||
}
|
||||
else
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue