Expressions

- Many operators implemented
- DOM::get partially implemented
This commit is contained in:
Paul Beckingham 2011-06-17 01:00:03 -04:00
parent 4fca40fc69
commit c77c6f172f
6 changed files with 315 additions and 101 deletions

View file

@ -37,6 +37,7 @@
#include <ViewText.h> #include <ViewText.h>
#include <text.h> #include <text.h>
#include <util.h> #include <util.h>
#include <i18n.h>
#include <Arguments.h> #include <Arguments.h>
extern Context context; extern Context context;
@ -111,11 +112,11 @@ static struct
{ "!=", 9, 'b', 1, 'l' }, // Inequal { "!=", 9, 'b', 1, 'l' }, // Inequal
{ "=", 9, 'b', 1, 'l' }, // Equal { "=", 9, 'b', 1, 'l' }, // Equal
{ "^", 16, 'b', 1, 'r' }, // Exponent // { "^", 16, 'b', 1, 'r' }, // Exponent
{ ">", 10, 'b', 1, 'l' }, // Greater than { ">", 10, 'b', 1, 'l' }, // Greater than
{ "~", 9, 'b', 1, 'l' }, // Regex match { "~", 9, 'b', 1, 'l' }, // Regex match
{ "!", 15, 'u', 1, 'r' }, // Not { "!", 15, 'u', 1, 'r' }, // Not
{ "-", 15, 'u', 1, 'r' }, // Unary minus // { "-", 15, 'u', 1, 'r' }, // Unary minus
{ "*", 13, 'b', 1, 'l' }, // Multiplication { "*", 13, 'b', 1, 'l' }, // Multiplication
{ "/", 13, 'b', 1, 'l' }, // Division { "/", 13, 'b', 1, 'l' }, // Division
{ "%", 13, 'b', 1, 'l' }, // Modulus { "%", 13, 'b', 1, 'l' }, // Modulus
@ -149,8 +150,11 @@ void Arguments::capture (int argc, const char** argv)
{ {
for (int i = 0; i < argc; ++i) for (int i = 0; i < argc; ++i)
{ {
// The "i != 0" guarantees that argv[0] does not get split, because it may
// be an absolute path, and Expression::expand_tokens would make a dog's
// dinner out of it.
std::vector <std::string> parts; std::vector <std::string> parts;
if (is_multipart (argv[i], parts)) if (is_multipart (argv[i], parts) && i != 0)
{ {
std::vector <std::string>::iterator part; std::vector <std::string>::iterator part;
for (part = parts.begin (); part != parts.end (); ++part) for (part = parts.begin (); part != parts.end (); ++part)
@ -270,22 +274,16 @@ void Arguments::categorize ()
} }
// rc:<file> // rc:<file>
// Note: This doesn't break a sequence chain.
else if (arg->first.substr (0, 3) == "rc:") else if (arg->first.substr (0, 3) == "rc:")
{ {
found_non_sequence = true;
if (found_sequence)
found_something_after_sequence = true;
arg->second = "rc"; arg->second = "rc";
} }
// rc.<name>:<value> // rc.<name>:<value>
// Note: This doesn't break a sequence chain.
else if (arg->first.substr (0, 3) == "rc.") else if (arg->first.substr (0, 3) == "rc.")
{ {
found_non_sequence = true;
if (found_sequence)
found_something_after_sequence = true;
arg->second = "override"; arg->second = "override";
} }
@ -447,7 +445,7 @@ void Arguments::categorize ()
found_sequence) found_sequence)
{ {
// TODO Invoke the info command. // TODO Invoke the info command.
// std::cout << STRING_ASSUME_INFO << "\n"; std::cout << STRING_ASSUME_INFO << "\n";
// parseCmd.command = "info"; // parseCmd.command = "info";
} }
} }
@ -925,10 +923,28 @@ bool Arguments::is_attribute (const std::string& input, std::string& canonical)
// Guess at the full attribute name. // Guess at the full attribute name.
std::vector <std::string> candidates; std::vector <std::string> candidates;
for (unsigned i = 0; i < NUM_ATT_NAMES; ++i) for (unsigned i = 0; i < NUM_ATT_NAMES; ++i)
{
// Short-circuit: exact matches cause immediate return.
if (attributeNames[i] == input)
{
canonical = input;
return true;
}
candidates.push_back (attributeNames[i]); candidates.push_back (attributeNames[i]);
}
for (unsigned i = 0; i < NUM_MODIFIABLE_ATT_NAMES; ++i) for (unsigned i = 0; i < NUM_MODIFIABLE_ATT_NAMES; ++i)
{
// Short-circuit: exact matches cause immediate return.
if (modifiableAttributeNames[i] == input)
{
canonical = input;
return true;
}
candidates.push_back (modifiableAttributeNames[i]); candidates.push_back (modifiableAttributeNames[i]);
}
std::vector <std::string> matches; std::vector <std::string> matches;
autoComplete (input, candidates, matches); autoComplete (input, candidates, matches);
@ -948,7 +964,13 @@ bool Arguments::is_modifier (const std::string& input)
// Guess at the full attribute name. // Guess at the full attribute name.
std::vector <std::string> candidates; std::vector <std::string> candidates;
for (unsigned i = 0; i < NUM_MODIFIER_NAMES; ++i) for (unsigned i = 0; i < NUM_MODIFIER_NAMES; ++i)
{
// Short-circuit: exact matches cause immediate return.
if (modifierNames[i] == input)
return true;
candidates.push_back (modifierNames[i]); candidates.push_back (modifierNames[i]);
}
std::vector <std::string> matches; std::vector <std::string> matches;
autoComplete (input, candidates, matches); autoComplete (input, candidates, matches);

View file

@ -63,26 +63,6 @@ DOM::~DOM ()
// context.width // context.width
// context.height // context.height
// //
// <id>.<?>
// <id>.{entry,start,end,due,until,wait}
// <id>.{entry,start,end,due,until,wait}.year
// <id>.{entry,start,end,due,until,wait}.month
// <id>.{entry,start,end,due,until,wait}.day
// <id>.{entry,start,end,due,until,wait}.hour
// <id>.{entry,start,end,due,until,wait}.minute
// <id>.{entry,start,end,due,until,wait}.second
// <id>.description
// <id>.project
// <id>.priority
// <id>.parent
// <id>.status
// <id>.tags
// <id>.urgency
// <id>.recur
// <id>.depends
//
// <uuid>.<?>
//
// TODO report.<name>. <-- context.reports // TODO report.<name>. <-- context.reports
// TODO stats.<name> <-- context.stats // TODO stats.<name> <-- context.stats
// //
@ -93,8 +73,6 @@ const std::string DOM::get (const std::string& name)
{ {
int len = name.length (); int len = name.length ();
Nibbler n (name); Nibbler n (name);
int id;
std::string uuid;
// Primitives // Primitives
if (is_primitive (name)) if (is_primitive (name))
@ -137,30 +115,6 @@ const std::string DOM::get (const std::string& name)
throw format (STRING_DOM_UNREC, name); throw format (STRING_DOM_UNREC, name);
} }
// <id>.<name>
else if (n.getInt (id))
{
if (n.skip ('.'))
{
std::string ref;
n.getUntilEOS (ref);
if (ref == "description")
;
// TODO return task.get ("description");
}
}
// TODO <uuid>.<name>
else if (n.getUUID (uuid))
{
std::string attr;
if (n.skip ('.') &&
n.getUntilEOS (attr))
{
}
}
// TODO report. // TODO report.
// TODO stats.<name> // TODO stats.<name>
@ -205,6 +159,129 @@ const std::string DOM::get (const std::string& name)
return ""; return "";
} }
////////////////////////////////////////////////////////////////////////////////
// DOM Supported References:
//
// TODO <id>.{entry,start,end,due,until,wait}
// TODO <id>.description
// TODO <id>.project
// TODO <id>.priority
// TODO <id>.parent
// TODO <id>.status
// TODO <id>.tags
// TODO <id>.urgency
// TODO <id>.recur
// TODO <id>.depends
//
// TODO <uuid>.{entry,start,end,due,until,wait}
// TODO <uuid>.description
// TODO <uuid>.project
// TODO <uuid>.priority
// TODO <uuid>.parent
// TODO <uuid>.status
// TODO <uuid>.tags
// TODO <uuid>.urgency
// TODO <uuid>.recur
// TODO <uuid>.depends
//
// {entry,start,end,due,until,wait}
// description
// project
// priority
// parent
// status
// tags
// urgency
// recur
// depends
//
const std::string DOM::get (const std::string& name, Task& task)
{
Nibbler n (name);
int id;
std::string uuid;
// Primitives
if (is_primitive (name))
return name;
// <id>.<name>
else if (n.getInt (id))
{
if (n.skip ('.'))
{
// TODO Obtain task 'id' from TDB2.
std::string attr;
n.getUntilEOS (attr);
if (attr == "description") return task.get ("description");
else if (attr == "status") return task.get ("status");
else if (attr == "project") return task.get ("project");
else if (attr == "priority") return task.get ("priority");
else if (attr == "parent") return task.get ("parent");
else if (attr == "tags") return task.get ("tags");
else if (attr == "urgency") return format (task.urgency (), 4, 3);
else if (attr == "recur") return task.get ("recur");
else if (attr == "depends") return task.get ("depends");
else if (attr == "entry") return task.get ("entry");
else if (attr == "start") return task.get ("start");
else if (attr == "end") return task.get ("end");
else if (attr == "due") return task.get ("due");
else if (attr == "until") return task.get ("until");
else if (attr == "wait") return task.get ("wait");
}
}
// <uuid>.<name>
else if (n.getUUID (uuid))
{
if (n.skip ('.'))
{
// TODO Obtain task 'uuid' from TDB2.
std::string attr;
n.getUntilEOS (attr);
if (attr == "description") return task.get ("description");
else if (attr == "status") return task.get ("status");
else if (attr == "project") return task.get ("project");
else if (attr == "priority") return task.get ("priority");
else if (attr == "parent") return task.get ("parent");
else if (attr == "tags") return task.get ("tags");
else if (attr == "urgency") return format (task.urgency (), 4, 3);
else if (attr == "recur") return task.get ("recur");
else if (attr == "depends") return task.get ("depends");
else if (attr == "entry") return task.get ("entry");
else if (attr == "start") return task.get ("start");
else if (attr == "end") return task.get ("end");
else if (attr == "due") return task.get ("due");
else if (attr == "until") return task.get ("until");
else if (attr == "wait") return task.get ("wait");
}
}
// [<task>.] <name>
if (name == "description") return task.get ("description");
else if (name == "status") return task.get ("status");
else if (name == "project") return task.get ("project");
else if (name == "priority") return task.get ("priority");
else if (name == "parent") return task.get ("parent");
else if (name == "tags") return task.get ("tags");
else if (name == "urgency") return format (task.urgency (), 4, 3);
else if (name == "recur") return task.get ("recur");
else if (name == "depends") return task.get ("depends");
else if (name == "entry") return task.get ("entry");
else if (name == "start") return task.get ("start");
else if (name == "end") return task.get ("end");
else if (name == "due") return task.get ("due");
else if (name == "until") return task.get ("until");
else if (name == "wait") return task.get ("wait");
// Delegate to the context-free version of DOM::get.
return this->get (name);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void DOM::set (const std::string& name, const std::string& value) void DOM::set (const std::string& name, const std::string& value)
{ {

View file

@ -29,6 +29,7 @@
#define L10N // Localization complete. #define L10N // Localization complete.
#include <string> #include <string>
#include <Task.h>
#include <time.h> #include <time.h>
class DOM class DOM
@ -38,6 +39,7 @@ public:
~DOM (); ~DOM ();
const std::string get (const std::string&); const std::string get (const std::string&);
const std::string get (const std::string&, Task&);
void set (const std::string&, const std::string&); void set (const std::string&, const std::string&);
private: private:

View file

@ -46,12 +46,6 @@ Expression::Expression (Arguments& arguments)
{ {
_args.dump ("Expression::Expression"); _args.dump ("Expression::Expression");
bool new_style = is_new_style () && context.config.getBoolean ("expressions");
if (new_style)
context.debug ("Filter --> new");
else
context.debug ("Filter --> old");
expand_sequence (); expand_sequence ();
implicit_and (); implicit_and ();
expand_tag (); expand_tag ();
@ -84,108 +78,231 @@ bool Expression::eval (Task& task)
{ {
if (arg->second == "op") if (arg->second == "op")
{ {
// We already know it's an operator, but we need to know more. Or do we?
/*
char type;
int precedence;
char associativity;
Arguments::is_operator (arg->first, type, precedence, associativity);
*/
// TODO Need helpers that pop, and error out if necessary. // TODO Need helpers that pop, and error out if necessary.
if (arg->first == "+") if (arg->first == "and")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ()); Variant right (value_stack.back ());
value_stack.pop_back (); value_stack.pop_back ();
Variant left (value_stack.back ()); Variant left (value_stack.back ());
value_stack.pop_back (); value_stack.pop_back ();
context.debug ("eval left=" + left.format ()); left = left && right;
context.debug ("eval right=" + right.format ());
left = left + right;
context.debug ("eval + --> " + left.format ());
value_stack.push_back (left); value_stack.push_back (left);
} }
else if (arg->first == "and")
{
}
else if (arg->first == "xor") else if (arg->first == "xor")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ());
value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = (left && !right) || (!left && right);
value_stack.push_back (left);
} }
else if (arg->first == "or") else if (arg->first == "or")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ());
value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = left || right;
value_stack.push_back (left);
} }
else if (arg->first == "<=") else if (arg->first == "<=")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ());
value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = left <= right;
value_stack.push_back (left);
} }
else if (arg->first == ">=") else if (arg->first == ">=")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ());
value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = left >= right;
value_stack.push_back (left);
} }
else if (arg->first == "!~") else if (arg->first == "!~")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
// TODO
/*
if left == "description" then it really means description or annotations or project.
if left == "tags" then it just means tags.
*/
} }
else if (arg->first == "!=") else if (arg->first == "!=")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ());
value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = left != right;
value_stack.push_back (left);
} }
else if (arg->first == "=") else if (arg->first == "=")
{ {
} if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
else if (arg->first == "^") Variant right (value_stack.back ());
{ value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = left == right;
value_stack.push_back (left);
} }
else if (arg->first == ">") else if (arg->first == ">")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ());
value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = left > right;
value_stack.push_back (left);
} }
else if (arg->first == "~") else if (arg->first == "~")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
// TODO
/*
if left == "description" then it really means description or annotations or project.
if left == "tags" then it just means tags.
*/
} }
else if (arg->first == "!") else if (arg->first == "!")
{ {
if (value_stack.size () < 1)
throw std::string ("Error: Insufficient operands.");
Variant left (value_stack.back ());
value_stack.pop_back ();
left = ! left;
value_stack.push_back (left);
} }
else if (arg->first == "*") else if (arg->first == "*")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ());
value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = left * right;
value_stack.push_back (left);
} }
else if (arg->first == "/") else if (arg->first == "/")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ());
value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = left / right;
value_stack.push_back (left);
} }
else if (arg->first == "%") else if (arg->first == "%")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
// TODO Implement modulus.
} }
else if (arg->first == "+") else if (arg->first == "+")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ());
value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = left + right;
value_stack.push_back (left);
} }
else if (arg->first == "-") else if (arg->first == "-")
{ {
if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
Variant right (value_stack.back ());
value_stack.pop_back ();
Variant left (value_stack.back ());
value_stack.pop_back ();
left = left - right;
value_stack.push_back (left);
} }
else if (arg->first == "<") else if (arg->first == "<")
{ {
} if (value_stack.size () < 2)
throw std::string ("Error: Insufficient operands.");
else if (arg->first == "(") Variant right (value_stack.back ());
{ value_stack.pop_back ();
} Variant left (value_stack.back ());
value_stack.pop_back ();
else if (arg->first == ")") left = left < right;
{ value_stack.push_back (left);
} }
else else
@ -196,7 +313,7 @@ bool Expression::eval (Task& task)
else else
{ {
if (arg->second == "lvalue") if (arg->second == "lvalue")
value_stack.push_back (Variant (context.dom.get (arg->first))); value_stack.push_back (Variant (context.dom.get (arg->first, task)));
else if (arg->second == "int") else if (arg->second == "int")
value_stack.push_back (Variant ((int) strtol (arg->first.c_str (), NULL, 10))); value_stack.push_back (Variant ((int) strtol (arg->first.c_str (), NULL, 10)));
@ -212,12 +329,11 @@ bool Expression::eval (Task& task)
else else
throw std::string ("Error: Expression::eval unrecognized operand '") + + "'."; throw std::string ("Error: Expression::eval unrecognized operand '") + + "'.";
} }
} }
// Coerce stack element to boolean. // Coerce stack element to boolean.
Variant result (value_stack.back ()); Variant result (value_stack.back ());
context.debug ("eval result=" + result.format ()); //context.debug ("eval result=" + result.format ());
value_stack.pop_back (); value_stack.pop_back ();
bool pass_fail = result.boolean (); bool pass_fail = result.boolean ();

View file

@ -53,6 +53,9 @@ private:
bool is_new_style (); bool is_new_style ();
private:
private: private:
Arguments _args; Arguments _args;
}; };

View file

@ -92,7 +92,7 @@ int CmdCustom::execute (std::string& output)
context.tdb.commit (); context.tdb.commit ();
context.tdb.unlock (); context.tdb.unlock ();
//////////////////////////////////// // Filter.
Arguments f = context.args.extract_read_only_filter (); Arguments f = context.args.extract_read_only_filter ();
Expression e (f); Expression e (f);
@ -102,12 +102,6 @@ int CmdCustom::execute (std::string& output)
if (e.eval (*task)) if (e.eval (*task))
filtered.push_back (*task); filtered.push_back (*task);
std::cout << "# tasks=" << tasks.size () << "\n"
<< "# filtered=" << filtered.size () << "\n";
//return 0;
////////////////////////////////////
// Sort the tasks. // Sort the tasks.
std::vector <int> sequence; std::vector <int> sequence;
for (unsigned int i = 0; i < filtered.size (); ++i) for (unsigned int i = 0; i < filtered.size (); ++i)