From e08d840ba1a8541b10c6835618fe56509932e986 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 17 Jul 2011 12:04:38 -0400 Subject: [PATCH] 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. --- src/Arguments.cpp | 2 +- src/Expression.cpp | 217 ++++++++++++++++++++++++--------------- src/Expression.h | 5 + src/commands/CmdAdd.cpp | 3 +- src/commands/CmdLog.cpp | 2 + src/commands/Command.cpp | 6 +- 6 files changed, 150 insertions(+), 85 deletions(-) diff --git a/src/Arguments.cpp b/src/Arguments.cpp index 6e6fa879f..d53225675 100644 --- a/src/Arguments.cpp +++ b/src/Arguments.cpp @@ -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); diff --git a/src/Expression.cpp b/src/Expression.cpp index 4b6d8114b..dbb74b0ab 100644 --- a/src/Expression.cpp +++ b/src/Expression.cpp @@ -43,21 +43,8 @@ extern Context context; // Perform all the necessary steps prior to an eval call. Expression::Expression (Arguments& 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) return true; - // There are elements in the filter, so the expression must be evaluated - // against each task. - std::vector value_stack; + 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 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 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 & value_stack) +{ // Case sensitivity is configurable. bool case_sensitive = context.config.getBoolean ("search.case.sensitive"); - // TODO Build an on-demand regex cache. - std::vector ::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 operators = Arguments::operator_list (); + // Look at all args. + std::vector ::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 & 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 ::iterator arg; - for (arg = _args.begin (); arg != _args.end (); ++arg) + while (! n.depleted ()) { - if (arg->_second == "exp") - { - // Nibble each arg token by token. - Nibbler n (arg->_first); + if (n.getQuoted ('"', s, true) || + n.getQuoted ('\'', s, true)) + tokens.push_back (Triple (s, "string", category)); - while (! n.depleted ()) - { - if (n.getQuoted ('"', s, true) || - n.getQuoted ('\'', s, true)) - temp.push_back (Triple (s, "string", arg->_third)); + else if (n.getQuoted ('/', s, true)) + tokens.push_back (Triple (s, "pattern", category)); - else if (n.getQuoted ('/', s, true)) - temp.push_back (Triple (s, "pattern", arg->_third)); + else if (n.getOneOf (operators, s)) + tokens.push_back (Triple (s, "op", category)); - else if (n.getOneOf (operators, s)) - temp.push_back (Triple (s, "op", arg->_third)); + else if (n.getDOM (s)) + tokens.push_back (Triple (s, "lvalue", category)); - else if (n.getDOM (s)) - temp.push_back (Triple (s, "lvalue", arg->_third)); + else if (n.getNumber (d)) + tokens.push_back (Triple (format (d), "number", category)); - else if (n.getNumber (d)) - temp.push_back (Triple (format (d), "number", arg->_third)); + else if (n.getInt (i)) + tokens.push_back (Triple (format (i), "int", category)); - else if (n.getInt (i)) - temp.push_back (Triple (format (i), "int", arg->_third)); + else if (n.getDateISO (t)) + tokens.push_back (Triple (Date (t).toISO (), "date", category)); - else if (n.getDateISO (t)) - temp.push_back (Triple (Date (t).toISO (), "date", arg->_third)); + else if (n.getDate (date_format, t)) + 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 - temp.push_back (*arg); - } + { + if (! n.getUntilWS (s)) + n.getUntilEOS (s); - _args.swap (temp); - _args.dump ("Expression::expand_tokens"); + tokens.push_back (Triple (s, "?", category)); + } + + n.skipWS (); + } } //////////////////////////////////////////////////////////////////////////////// @@ -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; } diff --git a/src/Expression.h b/src/Expression.h index b972a15c3..6b4237141 100644 --- a/src/Expression.h +++ b/src/Expression.h @@ -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 &); private: void expand_sequence (); @@ -53,6 +56,7 @@ private: void expand_tokens (); void postfix (); + void tokenize (const std::string&, const std::string&, std::vector &, 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 _regexes; + bool _prepared; }; #endif diff --git a/src/commands/CmdAdd.cpp b/src/commands/CmdAdd.cpp index 4356c0abd..396cdba76 100644 --- a/src/commands/CmdAdd.cpp +++ b/src/commands/CmdAdd.cpp @@ -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; } diff --git a/src/commands/CmdLog.cpp b/src/commands/CmdLog.cpp index e2c40b10e..c7a176a77 100644 --- a/src/commands/CmdLog.cpp +++ b/src/commands/CmdLog.cpp @@ -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")) diff --git a/src/commands/Command.cpp b/src/commands/Command.cpp index b4e87d0f3..ed019a499 100644 --- a/src/commands/Command.cpp +++ b/src/commands/Command.cpp @@ -279,7 +279,7 @@ void Command::filter (std::vector & input, std::vector & output) std::vector ::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 & output) output.clear (); std::vector ::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 & 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