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:
Paul Beckingham 2011-07-18 23:08:05 -04:00
parent dd75c1af1e
commit ab6e230f10
6 changed files with 184 additions and 71 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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