mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-07-07 20:06:36 +02:00
Expressions
- Implemented Nibbler::getWord. - Re-implemented Nibbler::getDOM. - Modified DOM addressed for context-based attributes from "due" to ".due", the help disambiguate DOM references in expressions. There is now a consistency: <id>.due Task-specific .due Contextual <uuid>.due General - Implemented associated unit tests.
This commit is contained in:
parent
dd75c1af1e
commit
ab6e230f10
6 changed files with 184 additions and 71 deletions
53
src/DOM.cpp
53
src/DOM.cpp
|
@ -82,10 +82,11 @@ const std::string DOM::get (const std::string& name)
|
||||||
|
|
||||||
int len = name.length ();
|
int len = name.length ();
|
||||||
Nibbler n (name);
|
Nibbler n (name);
|
||||||
|
std::string copy_name (name);
|
||||||
|
|
||||||
// Primitives
|
// Primitives
|
||||||
if (is_primitive (name))
|
if (is_literal (copy_name))
|
||||||
return /*_cache[name] =*/ name;
|
return /*_cache[name] =*/ copy_name;
|
||||||
|
|
||||||
// rc. --> context.config
|
// rc. --> context.config
|
||||||
else if (len > 3 &&
|
else if (len > 3 &&
|
||||||
|
@ -162,6 +163,7 @@ const std::string DOM::get (const std::string& name)
|
||||||
throw format (STRING_DOM_UNREC, name);
|
throw format (STRING_DOM_UNREC, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass-through.
|
||||||
return /*_cache[name] =*/ name;
|
return /*_cache[name] =*/ name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,16 +192,16 @@ const std::string DOM::get (const std::string& name)
|
||||||
// TODO <uuid>.recur
|
// TODO <uuid>.recur
|
||||||
// TODO <uuid>.depends
|
// TODO <uuid>.depends
|
||||||
//
|
//
|
||||||
// {entry,start,end,due,until,wait}
|
// {.entry,.start,.end,.due,.until,.wait}
|
||||||
// description
|
// .description
|
||||||
// project
|
// .project
|
||||||
// priority
|
// .priority
|
||||||
// parent
|
// .parent
|
||||||
// status
|
// .status
|
||||||
// tags
|
// .tags
|
||||||
// urgency
|
// .urgency
|
||||||
// recur
|
// .recur
|
||||||
// depends
|
// .depends
|
||||||
//
|
//
|
||||||
const std::string DOM::get (const std::string& name, const Task& task)
|
const std::string DOM::get (const std::string& name, const Task& task)
|
||||||
{
|
{
|
||||||
|
@ -215,8 +217,13 @@ const std::string DOM::get (const std::string& name, const Task& task)
|
||||||
std::string uuid;
|
std::string uuid;
|
||||||
|
|
||||||
// Primitives
|
// Primitives
|
||||||
if (is_primitive (name))
|
std::string copy_name (name);
|
||||||
return /*_cache[name] =*/ name;
|
if (is_literal (copy_name))
|
||||||
|
return /*_cache[name] =*/ copy_name;
|
||||||
|
|
||||||
|
// <attr>
|
||||||
|
else if (task.has (name))
|
||||||
|
return task.get (name);
|
||||||
|
|
||||||
// <id>.<name>
|
// <id>.<name>
|
||||||
else if (n.getInt (id))
|
else if (n.getInt (id))
|
||||||
|
@ -248,15 +255,17 @@ const std::string DOM::get (const std::string& name, const Task& task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [<task>.] <name>
|
// [<task>] .<name>
|
||||||
if (name == "id") return format (task.id);
|
if (name == ".id") return format (task.id);
|
||||||
else if (name == "urgency") return format (task.urgency_c (), 4, 3);
|
else if (name == ".urgency") return format (task.urgency_c (), 4, 3);
|
||||||
else return task.get (name);
|
else if (task.has (name.substr (1))) return task.get (name.substr (1));
|
||||||
|
|
||||||
// Delegate to the context-free version of DOM::get.
|
// Delegate to the context-free version of DOM::get.
|
||||||
return this->get (name);
|
return this->get (name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Need a context-specific DOM::set.
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
void DOM::set (const std::string& name, const std::string& value)
|
void DOM::set (const std::string& name, const std::string& value)
|
||||||
{
|
{
|
||||||
|
@ -275,7 +284,7 @@ void DOM::set (const std::string& name, const std::string& value)
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
bool DOM::is_primitive (const std::string& input)
|
bool DOM::is_literal (std::string& input)
|
||||||
{
|
{
|
||||||
std::string s;
|
std::string s;
|
||||||
double d;
|
double d;
|
||||||
|
@ -283,7 +292,11 @@ bool DOM::is_primitive (const std::string& input)
|
||||||
|
|
||||||
// Date?
|
// Date?
|
||||||
if (Date::valid (input, context.config.get ("dateformat")))
|
if (Date::valid (input, context.config.get ("dateformat")))
|
||||||
|
{
|
||||||
|
input = Date (input).toEpochString ();
|
||||||
|
std::cout << "# DOM::is_literal '" << input << "' --> date\n";
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Duration?
|
// Duration?
|
||||||
if (Duration::valid (input))
|
if (Duration::valid (input))
|
||||||
|
@ -309,7 +322,7 @@ bool DOM::is_primitive (const std::string& input)
|
||||||
if (n.getInt (i) && n.depleted ())
|
if (n.getInt (i) && n.depleted ())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// std::cout << "# DOM::is_primitive '" << input << "' --> unknown\n";
|
// std::cout << "# DOM::is_literal '" << input << "' --> unknown\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ public:
|
||||||
void set (const std::string&, const std::string&);
|
void set (const std::string&, const std::string&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool is_primitive (const std::string&);
|
bool is_literal (std::string&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map <std::string, std::string> _cache;
|
std::map <std::string, std::string> _cache;
|
||||||
|
|
|
@ -112,11 +112,11 @@ std::string Expression::evalExpression (const Task& task)
|
||||||
std::vector <Variant> value_stack;
|
std::vector <Variant> value_stack;
|
||||||
eval (task, value_stack);
|
eval (task, value_stack);
|
||||||
|
|
||||||
// Coerce stack element to boolean.
|
// Coerce stack element to string.
|
||||||
Variant result (value_stack.back ());
|
Variant result (value_stack.back ());
|
||||||
value_stack.pop_back ();
|
value_stack.pop_back ();
|
||||||
result.cast (Variant::v_string);
|
result.cast (Variant::v_string);
|
||||||
return result._string;
|
return context.dom.get (result._string, task);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -417,7 +417,7 @@ void Expression::eval (const Task& task, std::vector <Variant>& value_stack)
|
||||||
throw std::string ("Unsupported operator '") + arg->_first + "'.";
|
throw std::string ("Unsupported operator '") + arg->_first + "'.";
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's not an operator, it's either and lvalue or some form of rvalue.
|
// It's not an operator, it's an lvalue or some form of rvalue.
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Variant operand;
|
Variant operand;
|
||||||
|
@ -480,6 +480,7 @@ void Expression::create_variant (
|
||||||
else if (type == "rvalue" ||
|
else if (type == "rvalue" ||
|
||||||
type == "string" ||
|
type == "string" ||
|
||||||
type == "rx")
|
type == "rx")
|
||||||
|
// TODO Is unquoteText necessary?
|
||||||
variant = Variant (unquoteText (value));
|
variant = Variant (unquoteText (value));
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -651,7 +652,7 @@ void Expression::tokenize (
|
||||||
if (! n.getUntilWS (s))
|
if (! n.getUntilWS (s))
|
||||||
n.getUntilEOS (s);
|
n.getUntilEOS (s);
|
||||||
|
|
||||||
tokens.push_back (Triple (s, "?", category));
|
tokens.push_back (Triple (s, "string", category));
|
||||||
}
|
}
|
||||||
|
|
||||||
n.skipWS ();
|
n.skipWS ();
|
||||||
|
@ -1022,13 +1023,9 @@ void Expression::postfix ()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (op_stack.size ())
|
if (op_stack.size ())
|
||||||
{
|
|
||||||
op_stack.pop_back ();
|
op_stack.pop_back ();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
throw std::string ("Mismatched parentheses in expression");
|
throw std::string ("Mismatched parentheses in expression");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (Arguments::is_operator (arg->_first, type, precedence, associativity))
|
else if (Arguments::is_operator (arg->_first, type, precedence, associativity))
|
||||||
{
|
{
|
||||||
|
|
107
src/Nibbler.cpp
107
src/Nibbler.cpp
|
@ -34,6 +34,7 @@
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <Nibbler.h>
|
#include <Nibbler.h>
|
||||||
#include <Date.h>
|
#include <Date.h>
|
||||||
|
#include <text.h>
|
||||||
#include <RX.h>
|
#include <RX.h>
|
||||||
|
|
||||||
const char* c_digits = "0123456789";
|
const char* c_digits = "0123456789";
|
||||||
|
@ -920,37 +921,93 @@ bool Nibbler::getOneOf (
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
bool Nibbler::getDOM (std::string& found)
|
// [<number>|<uuid>|<word>|].<word>[.<word> ...]
|
||||||
|
bool Nibbler::getDOM (std::string& result)
|
||||||
{
|
{
|
||||||
std::string::size_type i = mCursor;
|
std::string::size_type i = mCursor;
|
||||||
std::string::size_type start = mCursor;
|
if (i < mLength)
|
||||||
|
|
||||||
while ( isdigit (mInput[i]) ||
|
|
||||||
mInput[i] == '.' ||
|
|
||||||
mInput[i] == '-' ||
|
|
||||||
mInput[i] == '_' ||
|
|
||||||
(! ispunct (mInput[i]) &&
|
|
||||||
! isspace (mInput[i])))
|
|
||||||
{
|
{
|
||||||
++i;
|
save ();
|
||||||
|
|
||||||
|
std::string left;
|
||||||
|
std::string right;
|
||||||
|
int number;
|
||||||
|
|
||||||
|
if (skip ('.') &&
|
||||||
|
getWord (right))
|
||||||
|
{
|
||||||
|
while (skip ('.') &&
|
||||||
|
getWord (right))
|
||||||
|
;
|
||||||
|
|
||||||
|
result = mInput.substr (i, mCursor - i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
restore ();
|
||||||
|
if (getWord (left) &&
|
||||||
|
skip ('.') &&
|
||||||
|
getWord (right))
|
||||||
|
{
|
||||||
|
while (skip ('.') &&
|
||||||
|
getWord (right))
|
||||||
|
;
|
||||||
|
|
||||||
|
result = mInput.substr (i, mCursor - i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
restore ();
|
||||||
|
if (getInt (number) &&
|
||||||
|
skip ('.') &&
|
||||||
|
getWord (right))
|
||||||
|
{
|
||||||
|
while (skip ('.') &&
|
||||||
|
getWord (right))
|
||||||
|
;
|
||||||
|
|
||||||
|
result = mInput.substr (i, mCursor - i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
restore ();
|
||||||
|
if (getUUID (left) &&
|
||||||
|
skip ('.') &&
|
||||||
|
getWord (right))
|
||||||
|
{
|
||||||
|
while (skip ('.') &&
|
||||||
|
getWord (right))
|
||||||
|
;
|
||||||
|
|
||||||
|
result = mInput.substr (i, mCursor - i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i > mCursor)
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// A word is a contiguous string of non-space, non-digit, non-punct characters.
|
||||||
|
bool Nibbler::getWord (std::string& result)
|
||||||
|
{
|
||||||
|
std::string::size_type i = mCursor;
|
||||||
|
|
||||||
|
if (i < mLength)
|
||||||
{
|
{
|
||||||
found = mInput.substr (start, i - start);
|
while (!isdigit (mInput[i]) &&
|
||||||
|
!ispunct (mInput[i]) &&
|
||||||
|
!isspace (mInput[i]))
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
// If found is simple a number, then it is not a DOM reference.
|
if (i > mCursor)
|
||||||
double d;
|
{
|
||||||
Nibbler exclusion (found);
|
result = mInput.substr (mCursor, i - mCursor);
|
||||||
if (exclusion.getNumber (d) && exclusion.depleted ())
|
mCursor = i;
|
||||||
return false;
|
return true;
|
||||||
|
}
|
||||||
int in;
|
|
||||||
if (exclusion.getInt (in) && exclusion.depleted ())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
mCursor = i;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -66,6 +66,7 @@ public:
|
||||||
bool getDate (const std::string&, time_t&);
|
bool getDate (const std::string&, time_t&);
|
||||||
bool getOneOf (const std::vector <std::string>&, std::string&);
|
bool getOneOf (const std::vector <std::string>&, std::string&);
|
||||||
bool getDOM (std::string&);
|
bool getDOM (std::string&);
|
||||||
|
bool getWord (std::string&);
|
||||||
|
|
||||||
bool skipN (const int quantity = 1);
|
bool skipN (const int quantity = 1);
|
||||||
bool skip (char);
|
bool skip (char);
|
||||||
|
|
|
@ -34,7 +34,7 @@ Context context;
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
int main (int argc, char** argv)
|
int main (int argc, char** argv)
|
||||||
{
|
{
|
||||||
UnitTest t (257);
|
UnitTest t (277);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -412,29 +412,74 @@ int main (int argc, char** argv)
|
||||||
|
|
||||||
// bool getDOM (std::string&);
|
// bool getDOM (std::string&);
|
||||||
t.diag ("Nibbler::getDOM");
|
t.diag ("Nibbler::getDOM");
|
||||||
n = Nibbler ("one one.two one.two.three 1.project a0-a0-a0.due");
|
|
||||||
t.ok (n.getDOM (s), "'one' getDOM -> ok");
|
// positive.
|
||||||
t.is (s, "one", "'one' getDOM -> 'one'");
|
n = Nibbler (".due ");
|
||||||
|
t.ok (n.getDOM (s), "'.due' getDOM -> ok");
|
||||||
|
t.is (s, ".due", "'.due' getDOM -> '.due'");
|
||||||
|
|
||||||
|
n = Nibbler ("123.due ");
|
||||||
|
t.ok (n.getDOM (s), "'123.due' getDOM -> ok");
|
||||||
|
t.is (s, "123.due", "'123.due' getDOM -> '123.due'");
|
||||||
|
|
||||||
|
n = Nibbler ("ebeeab00-ccf8-464b-8b58-f7f2d606edfb.due ");
|
||||||
|
t.ok (n.getDOM (s), "'ebeeab00-ccf8-464b-8b58-f7f2d606edfb.due' getDOM -> ok");
|
||||||
|
t.is (s, "ebeeab00-ccf8-464b-8b58-f7f2d606edfb.due",
|
||||||
|
"'ebeeab00-ccf8-464b-8b58-f7f2d606edfb.due' getDOM -> 'ebeeab00-ccf8-464b-8b58-f7f2d606edfb.due");
|
||||||
|
|
||||||
|
n = Nibbler ("rc.one.two.three.four.five.six.seven ");
|
||||||
|
t.ok (n.getDOM (s), "'rc.one.two.three.four.five.six.seven' getDOM -> 'rc.one.two.three.four.five.six.seven'");
|
||||||
|
t.is (s, "rc.one.two.three.four.five.six.seven",
|
||||||
|
"'rc.one.two.three.four.five.six.seven' getDOM -> 'rc.one.two.three.four.five.six.seven'");
|
||||||
|
|
||||||
|
// negative.
|
||||||
|
n = Nibbler ("1+2 ");
|
||||||
|
t.notok (n.getDOM (s), "'1+2' getDOM -> notok");
|
||||||
|
|
||||||
|
n = Nibbler ("..foo ");
|
||||||
|
t.notok (n.getDOM (s), "'..foo' getDOM -> notok");
|
||||||
|
|
||||||
|
// bool getWord (std::string&);
|
||||||
|
t.diag ("Nibbler::getWord");
|
||||||
|
n = Nibbler ("one two th3ee");
|
||||||
|
t.ok (n.getWord (s), "'one' getWord -> ok");
|
||||||
|
t.is (s, "one", "'one' getWord -> 'one'");
|
||||||
t.ok (n.skipWS (), "skipWS");
|
t.ok (n.skipWS (), "skipWS");
|
||||||
t.ok (n.getDOM (s), "'one.two' getDOM -> ok");
|
t.ok (n.getWord (s), "'two' getWord -> ok");
|
||||||
t.is (s, "one.two", "'one.two' getDOM -> ok");
|
t.is (s, "two", "'two' getWord -> 'two'");
|
||||||
t.ok (n.skipWS (), "skipWS");
|
t.ok (n.skipWS (), "skipWS");
|
||||||
t.ok (n.getDOM (s), "'one.two.three' getDOM -> ok");
|
t.ok (n.getWord (s), "'th' getWord -> ok");
|
||||||
t.is (s, "one.two.three", "'one.two.three' getDOM -> ok");
|
t.is (s, "th", "'th' getWord -> 'th'");
|
||||||
t.ok (n.skipWS (), "skipWS");
|
t.ok (n.skip ('3'), "skip(3)");
|
||||||
t.ok (n.getDOM (s), "'1.project' getDOM -> ok");
|
t.ok (n.getWord (s), "'ee' getWord -> ok");
|
||||||
t.is (s, "1.project", "'1.project' getDOM -> ok");
|
t.is (s, "ee", "'ee' getWord -> 'ee'");
|
||||||
t.ok (n.skipWS (), "skipWS");
|
|
||||||
t.ok (n.getDOM (s), "'a0-a0-a0.due' getDOM -> ok");
|
|
||||||
t.is (s, "a0-a0-a0.due", "'a0-a0-a0.due' getDOM -> ok");
|
|
||||||
t.ok (n.depleted (), "depleted");
|
t.ok (n.depleted (), "depleted");
|
||||||
|
|
||||||
|
t.diag ("Nibbler::getWord");
|
||||||
|
n = Nibbler ("one TWO,three,f ");
|
||||||
|
t.ok (n.getWord (s), "'one TWO,three,f ' getWord -> ok");
|
||||||
|
t.is (s, "one", " ' TWO,three,f ' getWord -> one");
|
||||||
|
t.ok (n.skipWS (), " 'TWO,three,f ' skipWS -> ok");
|
||||||
|
|
||||||
|
t.ok (n.getWord (s), " 'TWO,three,f ' getWord -> ok");
|
||||||
|
t.is (s, "TWO", " ',three,f ' getWord -> TWO");
|
||||||
|
t.ok (n.skip (','), " 'three,f ' skip , -> ok");
|
||||||
|
|
||||||
|
t.ok (n.getWord (s), " 'three,f ' getWord -> ok");
|
||||||
|
t.is (s, "three", " ',f ' getWord -> three");
|
||||||
|
t.ok (n.skip (','), " 'f ' skip , -> ok");
|
||||||
|
|
||||||
|
t.ok (n.getWord (s), " 'f ' getWord -> ok");
|
||||||
|
t.is (s, "f", " ' ' getWord -> f");
|
||||||
|
t.ok (n.skipWS (), " '' skip , -> ok");
|
||||||
|
t.ok (n.depleted (), " '' depleted -> true");
|
||||||
|
|
||||||
// bool getUntilEOL (std::string&);
|
// bool getUntilEOL (std::string&);
|
||||||
t.diag ("Nibbler::getUntilEOL");
|
t.diag ("Nibbler::getUntilEOL");
|
||||||
n = Nibbler ("one\ntwo");
|
n = Nibbler ("one\ntwo");
|
||||||
t.ok (n.getUntilEOL (s), "'one\\ntwo' : getUntilEOL () -> true");
|
t.ok (n.getUntilEOL (s), "'one\\ntwo' : getUntilEOL () -> true");
|
||||||
t.is (s, "one", "'one\\ntwo' : getUntilEOL () -> 'one'");
|
t.is (s, "one", "'one\\ntwo' : getUntilEOL () -> 'one'");
|
||||||
t.ok (n.skip ('\n'), " '\\ntwo' : skip ('\\n') -> true");
|
t.ok (n.skip ('\n'), " '\\ntwo' : skip ('\\n') -> true");
|
||||||
t.ok (n.getUntilEOL (s), " 'two' : getUntilEOL () -> true");
|
t.ok (n.getUntilEOL (s), " 'two' : getUntilEOL () -> true");
|
||||||
t.is (s, "two", " 'two' : getUntilEOL () -> 'two'");
|
t.is (s, "two", " 'two' : getUntilEOL () -> 'two'");
|
||||||
t.ok (n.depleted (), " '' : depleted () -> true");
|
t.ok (n.depleted (), " '' : depleted () -> true");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue