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:
Paul Beckingham 2011-07-17 12:04:38 -04:00
parent d66729adf3
commit e08d840ba1
6 changed files with 150 additions and 85 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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