From 737cb2354652d3f766934d6d2dd43ea189deb08f Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Wed, 24 Jun 2015 13:12:24 -0400 Subject: [PATCH] CLI2: Eliminated CLI - This is a large commit, as all the changes are centered around the elimination of CLI. - CLI is no longer compiled. - Context no longer maintains CLI + CLI2. - Filter now walks the parse tree and sends to Eval a std::vector > containing only args tagged with FILTER. - Filter more efficiently sets/unsets Eval::debug, by doing it less often. - The filterExpr.length() check is no longer meaningful, and instead the size of the std::vector above is used. - Filter::pendingOnly performs better analysis. - Filter::safety makes use of the std::vector size also. - Task::modify makes use of 'canonical' rather than 'name', which is a policy change, not a fix. --- src/CMakeLists.txt | 3 +- src/Context.cpp | 39 ++++------------- src/Context.h | 2 - src/Filter.cpp | 87 ++++++++++++++++++------------------- src/Filter.h | 2 +- src/Task.cpp | 22 +++------- src/commands/CmdContext.cpp | 2 +- 7 files changed, 61 insertions(+), 96 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d605b4e7c..381a603ba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,8 +5,7 @@ include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src/columns ${TASK_INCLUDE_DIRS}) -set (task_SRCS CLI.cpp CLI.h - CLI2.cpp CLI2.h +set (task_SRCS CLI2.cpp CLI2.h Color.cpp Color.h Config.cpp Config.h Context.cpp Context.h diff --git a/src/Context.cpp b/src/Context.cpp index 1d972b6cd..640e18bf4 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -160,12 +160,6 @@ int Context::initialize (int argc, const char** argv) if (cmd.first[0] == '_') cli2.entity ("helper", cmd.first); - - cli.entity ("cmd", cmd.first); - cli.entity ((cmd.second->read_only () ? "readcmd" : "writecmd"), cmd.first); - - if (cmd.first[0] == '_') - cli.entity ("helper", cmd.first); } //////////////////////////////////////////////////////////////////////////// @@ -180,11 +174,6 @@ int Context::initialize (int argc, const char** argv) cli2.entity ("pseudo", "limit"); - for (auto& col : columns) - cli.entity ("attribute", col.first); - - cli.entity ("pseudo", "limit"); - //////////////////////////////////////////////////////////////////////////// // // [5] Capture modifier and operator entities. @@ -200,15 +189,6 @@ int Context::initialize (int argc, const char** argv) for (auto& op : Eval::getBinaryOperators ()) cli2.entity ("binary_operator", op); - for (unsigned int i = 0; i < NUM_MODIFIER_NAMES; ++i) - cli.entity ("modifier", modifierNames[i]); - - for (auto& op : Eval::getOperators ()) - cli.entity ("operator", op); - - for (auto& op : Eval::getBinaryOperators ()) - cli.entity ("binary_operator", op); - //////////////////////////////////////////////////////////////////////////// // // [6] Complete the Context initialization. @@ -230,14 +210,12 @@ int Context::initialize (int argc, const char** argv) cli2.add (argv[i]); cli2.analyze (); - cli.initialize (argc, argv); - cli.analyze (true, true); // Extract a recomposed command line. bool foundDefault = false; bool foundAssumed = false; std::string combined; - for (auto& a : cli._args) + for (auto& a : cli2._args) { if (combined.length ()) combined += ' '; @@ -445,7 +423,7 @@ int Context::run () int Context::dispatch (std::string &out) { // Autocomplete args against keywords. - std::string command = cli.getCommand (); + std::string command = cli2.getCommand (); if (command != "") { updateXtermTitle (); @@ -473,6 +451,10 @@ int Context::dispatch (std::string &out) throw std::string (""); */ + // This is something that is only needed for write commands with no other + // filter processing. + cli2.prepareFilter (); + return c->execute (out); } @@ -644,7 +626,6 @@ void Context::getLimits (int& rows, int& lines) // easier, it has been decoupled from Context. void Context::staticInitialization () { - CLI::minimumMatchLength = config.getInteger ("abbreviation.minimum"); CLI2::minimumMatchLength = config.getInteger ("abbreviation.minimum"); Task::defaultProject = config.get ("default.project"); @@ -755,9 +736,9 @@ void Context::updateXtermTitle () std::string command = cli2.getCommand (); std::string title; - for (auto a = cli._args.begin (); a != cli._args.end (); ++a) + for (auto a = cli2._args.begin (); a != cli2._args.end (); ++a) { - if (a != cli._args.begin ()) + if (a != cli2._args.begin ()) title += ' '; title += a->attribute ("raw"); @@ -785,10 +766,6 @@ void Context::loadAliases () for (auto& i : config) if (i.first.substr (0, 6) == "alias.") cli2.alias (i.first.substr (6), i.second); - - for (auto& i : config) - if (i.first.substr (0, 6) == "alias.") - cli.alias (i.first.substr (6), i.second); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Context.h b/src/Context.h index 08370f0bb..a638db92f 100644 --- a/src/Context.h +++ b/src/Context.h @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -80,7 +79,6 @@ private: void propagateDebug (); public: - CLI cli; CLI2 cli2; std::string home_dir; File rc_file; diff --git a/src/Filter.cpp b/src/Filter.cpp index aebfbf535..a83aacbec 100644 --- a/src/Filter.cpp +++ b/src/Filter.cpp @@ -25,6 +25,7 @@ //////////////////////////////////////////////////////////////////////////////// #include +#include #include #include #include @@ -72,16 +73,13 @@ void Filter::subset (const std::vector & input, std::vector & output context.timer_filter.start (); _startCount = (int) input.size (); - context.cli2.prepareFilter (applyContext); - // TODO Need to replace CLI2::getFilter with something that just walks the - // the parse tree. No point in combining the parse tree into a string, - // only to lex it back into tokens for Eval. +// context.cli2.prepareFilter (applyContext); + std::vector > precompiled; + for (auto& a : context.cli2._args) + if (a.hasTag ("FILTER")) + precompiled.push_back (std::pair (a.getToken (), a._lextype)); - if (context.config.getInteger ("debug.parser") >= 1) - context.debug (context.cli.dump ("Filter::subset")); - - std::string filterExpr = context.cli.getFilter (applyContext); - if (filterExpr.length ()) + if (precompiled.size ()) { Eval eval; eval.ambiguity (false); @@ -91,8 +89,7 @@ void Filter::subset (const std::vector & input, std::vector & output // Debug output from Eval during compilation is useful. During evaluation // it is mostly noise. eval.debug (context.config.getInteger ("debug.parser") >= 2 ? true : false); - eval.compileExpression (filterExpr); - eval.debug (false); + eval.compileExpression (precompiled); for (auto& task : input) { @@ -104,6 +101,8 @@ void Filter::subset (const std::vector & input, std::vector & output if (var.get_bool ()) output.push_back (task); } + + eval.debug (false); } else output = input; @@ -119,17 +118,15 @@ void Filter::subset (std::vector & output, bool applyContext /* = true */) { context.timer_filter.start (); - context.cli2.prepareFilter (applyContext); - // TODO Need to replace CLI2::getFilter with something that just walks the - // the parse tree. No point in combining the parse tree into a string, - // only to lex it back into tokens for Eval. - - if (context.config.getInteger ("debug.parser") >= 1) - context.debug (context.cli.dump ("Filter::subset")); + std::vector > precompiled; + for (auto& a : context.cli2._args) + if (a.hasTag ("FILTER")) + precompiled.push_back (std::pair (a.getToken (), a._lextype)); + // Shortcut indicates that only pending.data needs to be loaded. bool shortcut = false; - std::string filterExpr = context.cli.getFilter (applyContext); - if (filterExpr.length ()) + + if (precompiled.size ()) { context.timer_filter.stop (); auto pending = context.tdb2.pending.get_tasks (); @@ -144,8 +141,7 @@ void Filter::subset (std::vector & output, bool applyContext /* = true */) // Debug output from Eval during compilation is useful. During evaluation // it is mostly noise. eval.debug (context.config.getInteger ("debug.parser") >= 2 ? true : false); - eval.compileExpression (filterExpr); - eval.debug (false); + eval.compileExpression (precompiled); output.clear (); for (auto& task : pending) @@ -154,9 +150,7 @@ void Filter::subset (std::vector & output, bool applyContext /* = true */) contextTask = task; Variant var; - eval.debug (context.config.getInteger ("debug.parser") >= 2 ? true : false); eval.evaluateCompiledExpression (var); - eval.debug (false); if (var.get_bool ()) output.push_back (task); } @@ -175,17 +169,17 @@ void Filter::subset (std::vector & output, bool applyContext /* = true */) contextTask = task; Variant var; - eval.debug (context.config.getInteger ("debug.parser") >= 2 ? true : false); eval.evaluateCompiledExpression (var); - eval.debug (false); if (var.get_bool ()) output.push_back (task); } } + + eval.debug (false); } else { - safety (); + safety (precompiled.size ()); context.timer_filter.stop (); for (auto& task : context.tdb2.pending.get_tasks ()) @@ -203,8 +197,9 @@ void Filter::subset (std::vector & output, bool applyContext /* = true */) } //////////////////////////////////////////////////////////////////////////////// -// If the filter contains the restriction "status:pending", as the first filter -// term, then completed.data does not need to be loaded. +// If the filter contains no 'or', 'xor' or 'not' operators, and only includes +// status values 'pending', 'waiting' or 'recurring', then the filter is +// guaranteed to only need data from pending.data. bool Filter::pendingOnly () { // To skip loading completed.data, there should be: @@ -217,26 +212,32 @@ bool Filter::pendingOnly () int countPending = 0; int countWaiting = 0; int countRecurring = 0; - int countId = 0; + int countId = (int) context.cli2._id_ranges.size (); + int countUUID = (int) context.cli2._uuid_list.size (); int countOr = 0; int countXor = 0; int countNot = 0; - for (auto& a : context.cli._args) + for (auto& a : context.cli2._args) { if (a.hasTag ("FILTER")) { - if (a.hasTag ("ID")) ++countId; - if (a.hasTag ("OP") && a.attribute ("raw") == "or") ++countOr; - if (a.hasTag ("OP") && a.attribute ("raw") == "xor") ++countXor; - if (a.hasTag ("OP") && a.attribute ("raw") == "not") ++countNot; - if (a.hasTag ("ATTRIBUTE") && a.attribute ("name") == "status") ++countStatus; - if ( a.attribute ("raw") == "pending") ++countPending; - if ( a.attribute ("raw") == "waiting") ++countWaiting; - if ( a.attribute ("raw") == "recurring") ++countRecurring; + std::string raw = a.attribute ("raw"); + std::string canonical = a.attribute ("canonical"); + + if (a._lextype == Lexer::Type::op && raw == "or") ++countOr; + if (a._lextype == Lexer::Type::op && raw == "xor") ++countXor; + if (a._lextype == Lexer::Type::op && raw == "not") ++countXor; + if (a._lextype == Lexer::Type::dom && canonical == "status") ++countStatus; + if ( raw == "pending") ++countPending; + if ( raw == "waiting") ++countPending; + if ( raw == "recurring") ++countPending; } } + if (countUUID) + return false; + if (countOr || countXor || countNot) return false; @@ -249,9 +250,7 @@ bool Filter::pendingOnly () } if (countId) - { return true; - } return false; } @@ -259,15 +258,15 @@ bool Filter::pendingOnly () //////////////////////////////////////////////////////////////////////////////// // Disaster avoidance mechanism. If a WRITECMD has no filter, then it can cause // all tasks to be modified. This is usually not intended. -void Filter::safety () +void Filter::safety (unsigned int terms) { - for (auto& a : context.cli._args) + for (auto& a : context.cli2._args) { if (a.hasTag ("CMD")) { if (a.hasTag ("WRITECMD")) { - if (context.cli.getFilter () == "") + if (terms) { if (! context.config.getBoolean ("allow.empty.filter")) throw std::string (STRING_TASK_SAFETY_ALLOW); diff --git a/src/Filter.h b/src/Filter.h index e2c4c3d44..194f40dc9 100644 --- a/src/Filter.h +++ b/src/Filter.h @@ -43,7 +43,7 @@ public: void subset (const std::vector &, std::vector &, bool applyContext = true); void subset (std::vector &, bool applyContext = true); bool pendingOnly (); - void safety (); + void safety (unsigned int); private: int _startCount; diff --git a/src/Task.cpp b/src/Task.cpp index 64f6f9003..c518684bd 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -1883,16 +1883,17 @@ void Task::modify (modType type, bool text_required /* = false */) context.debug ("Task::modify"); std::string label = " MODIFICATION "; + std::string text = ""; int modCount = 0; - for (auto& a : context.cli._args) + for (auto& a : context.cli2._args) { if (a.hasTag ("MODIFICATION")) { - if (a.hasTag ("ATTRIBUTE")) + if (a._lextype == Lexer::Type::pair) { - // 'name' is canonical. + // 'canonical' is the canonical name. Needs to be said. // 'value' requires eval. - std::string name = a.attribute ("name"); + std::string name = a.attribute ("canonical"); std::string value = a.attribute ("value"); if (value == "" || value == "''" || @@ -2078,7 +2079,7 @@ void Task::modify (modType type, bool text_required /* = false */) } // arg7 from='from' global='1' raw='/from/to/g' to='to' ORIGINAL SUBSTITUTION MODIFICATION - else if (a.hasTag ("SUBSTITUTION")) + else if (a._lextype == Lexer::Type::substitution) { context.debug (label + "substitute " + a.attribute ("raw")); substitute (a.attribute ("from"), @@ -2090,7 +2091,7 @@ void Task::modify (modType type, bool text_required /* = false */) // Tags need special handling because they are essentially a vector stored // in a single string, therefore Task::{add,remove}Tag must be called as // appropriate. - else if (a.hasTag ("TAG")) + else if (a._lextype == Lexer::Type::tag) { std::string tag = a.attribute ("name"); if (a.attribute ("sign") == "+") @@ -2108,15 +2109,6 @@ void Task::modify (modType type, bool text_required /* = false */) ++modCount; } - // WORD and TERMINATED args are accumulated. - else if (a.hasTag ("WORD") || - a.hasTag ("TERMINATED")) - { - if (text != "") - text += ' '; - text += a.attribute ("raw"); - } - // Unknown args are accumulated as though they were WORDs. else { diff --git a/src/commands/CmdContext.cpp b/src/commands/CmdContext.cpp index ee0880f5c..491aadf35 100644 --- a/src/commands/CmdContext.cpp +++ b/src/commands/CmdContext.cpp @@ -144,7 +144,7 @@ int CmdContext::defineContext (std::vector & words, std::stringstre try { // This result is not used, and is just to check validity. - context.cli.addRawFilter ("( " + value + " )"); + context.cli2.addFilter (value); filter.subset (pending, filtered); } catch (std::string exception)