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

View file

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

View file

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

View file

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

View file

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

View file

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