Added support for metadata and tagging, and made regexs a first-class type.

This commit is contained in:
John Wiegley 2009-02-01 22:10:32 -04:00
parent 7128fdb637
commit fb5428ce85
16 changed files with 415 additions and 123 deletions

View file

@ -37,6 +37,68 @@ namespace ledger {
bool item_t::use_effective_date = false;
bool item_t::has_tag(const string& tag) const
{
if (! metadata)
return false;
string_map::const_iterator i = metadata->find(tag);
return i != metadata->end();
}
optional<string> item_t::get_tag(const string& tag) const
{
if (metadata) {
string_map::const_iterator i = metadata->find(tag);
if (i != metadata->end())
return (*i).second;
}
return none;
}
void item_t::set_tag(const string& tag,
const optional<string>& value)
{
if (! metadata)
metadata = string_map();
DEBUG("item.meta", "Setting tag '" << tag << "' to value '"
<< (value ? *value : string("<none>")) << "'");
std::pair<string_map::iterator, bool> result
= metadata->insert(string_map::value_type(tag, value));
assert(result.second);
}
void item_t::parse_tags(const char * p)
{
if (! std::strchr(p, ':'))
return;
scoped_array<char> buf(new char[std::strlen(p) + 1]);
std::strcpy(buf.get(), p);
string tag;
for (char * q = std::strtok(buf.get(), " \t");
q;
q = std::strtok(NULL, " \t")) {
const std::size_t len = std::strlen(q);
if (! tag.empty()) {
set_tag(tag, string(p + (q - buf.get())));
break;
}
else if (q[0] == ':' && q[len - 1] == ':') { // a series of tags
for (char * r = std::strtok(q + 1, ":");
r;
r = std::strtok(NULL, ":"))
set_tag(r);
}
else if (q[len - 1] == ':') { // a metadata setting
tag = string(q, len - 1);
}
}
}
namespace {
value_t get_status(item_t& item) {
return long(item.state());
@ -59,7 +121,51 @@ namespace {
}
value_t get_note(item_t& item) {
return string_value(item.note ? *item.note : empty_string);
return item.note ? string_value(*item.note) : value_t(false);
}
value_t has_tag(call_scope_t& args) {
item_t& item(find_scope<item_t>(args));
if (! item.metadata)
return false;
IF_DEBUG("item.meta") {
foreach (const item_t::string_map::value_type& data, *item.metadata) {
*_log_stream << " Tag: " << data.first << "\n";
*_log_stream << "Value: ";
if (data.second)
*_log_stream << *data.second << "\n";
else
*_log_stream << "<none>\n";
}
}
value_t& arg(args[0]);
if (arg.is_string()) {
if (args.size() == 1)
return item.has_tag(args[0].as_string());
else if (optional<string> tag = item.get_tag(args[0].as_string()))
return args[1] == string_value(*tag);
}
else if (arg.is_mask()) {
foreach (const item_t::string_map::value_type& data, *item.metadata) {
if (arg.as_mask().match(data.first)) {
if (args.size() == 1)
return true;
else if (data.second && args[1] == string_value(*data.second))
return true;
}
}
}
return false;
}
value_t get_tag(call_scope_t& args) {
item_t& item(find_scope<item_t>(args));
if (optional<string> value = item.get_tag(args[0].as_string()))
return string_value(*value);
return false;
}
value_t get_beg_pos(item_t& item) {
@ -84,12 +190,37 @@ namespace {
}
}
value_t get_comment(item_t& item)
{
if (! item.note) {
return false;
} else {
std::ostringstream buf;
buf << "\n ;";
bool need_separator = false;
for (const char * p = item.note->c_str(); *p; p++) {
if (*p == '\n')
need_separator = true;
else {
if (need_separator) {
buf << "\n ;";
need_separator = false;
}
buf << *p;
}
}
return string_value(buf.str());
}
}
expr_t::ptr_op_t item_t::lookup(const string& name)
{
switch (name[0]) {
case 'c':
if (name == "cleared")
return WRAP_FUNCTOR(get_wrapper<&get_cleared>);
else if (name == "comment")
return WRAP_FUNCTOR(get_wrapper<&get_comment>);
break;
case 'd':
@ -97,6 +228,18 @@ expr_t::ptr_op_t item_t::lookup(const string& name)
return WRAP_FUNCTOR(get_wrapper<&get_date>);
break;
case 'h':
if (name == "has_tag")
return WRAP_FUNCTOR(ledger::has_tag);
else if (name == "has_meta")
return WRAP_FUNCTOR(ledger::has_tag);
break;
case 'm':
if (name == "meta")
return WRAP_FUNCTOR(ledger::get_tag);
break;
case 'n':
if (name == "note")
return WRAP_FUNCTOR(get_wrapper<&get_note>);
@ -112,6 +255,11 @@ expr_t::ptr_op_t item_t::lookup(const string& name)
return WRAP_FUNCTOR(get_wrapper<&get_status>);
break;
case 't':
if (name == "tag")
return WRAP_FUNCTOR(ledger::get_tag);
break;
case 'u':
if (name == "uncleared")
return WRAP_FUNCTOR(get_wrapper<&get_uncleared>);

View file

@ -72,6 +72,9 @@ public:
optional<date_t> _date_eff;
optional<string> note;
typedef std::map<string, optional<string> > string_map;
optional<string_map> metadata;
unsigned short src_idx;
istream_pos_type full_beg_pos;
std::size_t full_beg_line;
@ -122,6 +125,19 @@ public:
return ! (*this == entry);
}
virtual bool has_tag(const string& tag) const;
virtual optional<string> get_tag(const string& tag) const;
virtual void set_tag(const string& tag,
const optional<string>& value = none);
virtual void parse_tags(const char * p);
virtual void append_note(const char * p) {
if (note)
*note += p;
else
note = p;
}
virtual optional<date_t> actual_date() const {
return _date;
}
@ -147,6 +163,8 @@ public:
bool valid() const;
};
value_t get_comment(item_t& item);
} // namespace ledger
#endif // _ITEM_H

View file

@ -72,10 +72,18 @@ public:
mask_t& operator=(const string& other);
bool operator==(const mask_t& other) const {
return expr == other.expr;
}
bool match(const string& str) const {
return boost::regex_search(str, expr);
}
bool empty() const {
return expr.empty();
}
void read(const char *& data);
void write(std::ostream& out) const;
};

View file

@ -57,7 +57,9 @@ expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope)
return this;
ptr_op_t lhs(left()->compile(scope));
ptr_op_t rhs(has_right() ? right()->compile(scope) : ptr_op_t());
ptr_op_t rhs(has_right() ? (kind == O_LOOKUP ?
right() : right()->compile(scope)) :
ptr_op_t());
if (lhs == left() && (! rhs || rhs == right()))
return this;
@ -102,11 +104,6 @@ value_t expr_t::op_t::opcalc(scope_t& scope)
throw_(calc_error, "Unknown identifier '" << as_ident() << "'");
return left()->opcalc(scope);
case MASK:
throw_(calc_error,
"Regexs can only be used in a match; did you mean: account =~ /"
<< as_mask() << '/');
case FUNCTION: {
// Evaluating a FUNCTION is the same as calling it directly; this happens
// when certain functions-that-look-like-variables (such as "amount") are
@ -115,6 +112,27 @@ value_t expr_t::op_t::opcalc(scope_t& scope)
return as_function()(call_args);
}
case O_LOOKUP:
if (left()->kind == IDENT &&
left()->left() && left()->left()->kind == FUNCTION) {
call_scope_t call_args(scope);
if (value_t obj = left()->left()->as_function()(call_args)) {
if (obj.is_pointer()) {
scope_t& objscope(obj.as_ref_lval<scope_t>());
if (ptr_op_t member = objscope.lookup(right()->as_ident()))
return member->calc(objscope);
}
}
}
if (right()->kind != IDENT) {
throw_(calc_error,
"Right operand of . operator must be an identifier");
} else {
throw_(calc_error,
"Failed to lookup member '" << right()->as_ident() << "'");
}
break;
case O_CALL: {
call_scope_t call_args(scope);
@ -122,20 +140,19 @@ value_t expr_t::op_t::opcalc(scope_t& scope)
call_args.set_args(right()->opcalc(scope));
ptr_op_t func = left();
const string& name(func->as_ident());
assert(func->kind == IDENT);
func = func->left();
if (! func || func->kind != FUNCTION)
throw_(calc_error, "Calling non-function");
throw_(calc_error, "Calling non-function '" << name << "'");
return func->as_function()(call_args);
}
case O_MATCH:
if (! right()->is_mask())
if (! right()->is_value() || ! right()->as_value().is_mask())
throw_(calc_error, "Right-hand argument to match operator must be a regex");
return right()->as_mask().match(left()->opcalc(scope).to_string());
return right()->as_value().as_mask().match(left()->opcalc(scope).to_string());
case INDEX: {
const call_scope_t& args(downcast<const call_scope_t>(scope));
@ -237,10 +254,6 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const
out << as_ident();
break;
case MASK:
out << '/' << as_mask() << '/';
break;
case FUNCTION:
out << "<FUNCTION>";
break;
@ -370,6 +383,14 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const
found = true;
break;
case O_LOOKUP:
if (left() && left()->print(out, context))
found = true;
out << ".";
if (has_right() && right()->print(out, context))
found = true;
break;
case O_CALL:
if (left() && left()->print(out, context))
found = true;
@ -425,10 +446,6 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const
out << "IDENT: " << as_ident();
break;
case MASK:
out << "MASK: " << as_mask();
break;
case INDEX:
out << "INDEX: " << as_index();
break;
@ -437,27 +454,28 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const
out << "FUNCTION";
break;
case O_CALL: out << "O_CALL"; break;
case O_MATCH: out << "O_MATCH"; break;
case O_LOOKUP: out << "O_LOOKUP"; break;
case O_CALL: out << "O_CALL"; break;
case O_MATCH: out << "O_MATCH"; break;
case O_NOT: out << "O_NOT"; break;
case O_NEG: out << "O_NEG"; break;
case O_NOT: out << "O_NOT"; break;
case O_NEG: out << "O_NEG"; break;
case O_ADD: out << "O_ADD"; break;
case O_SUB: out << "O_SUB"; break;
case O_MUL: out << "O_MUL"; break;
case O_DIV: out << "O_DIV"; break;
case O_ADD: out << "O_ADD"; break;
case O_SUB: out << "O_SUB"; break;
case O_MUL: out << "O_MUL"; break;
case O_DIV: out << "O_DIV"; break;
case O_EQ: out << "O_EQ"; break;
case O_LT: out << "O_LT"; break;
case O_LTE: out << "O_LTE"; break;
case O_GT: out << "O_GT"; break;
case O_GTE: out << "O_GTE"; break;
case O_EQ: out << "O_EQ"; break;
case O_LT: out << "O_LT"; break;
case O_LTE: out << "O_LTE"; break;
case O_GT: out << "O_GT"; break;
case O_GTE: out << "O_GTE"; break;
case O_AND: out << "O_AND"; break;
case O_OR: out << "O_OR"; break;
case O_AND: out << "O_AND"; break;
case O_OR: out << "O_OR"; break;
case O_COMMA: out << "O_COMMA"; break;
case O_COMMA: out << "O_COMMA"; break;
case LAST:
default:
@ -508,12 +526,6 @@ void expr_t::op_t::read(const char *& data)
set_ident(temp);
break;
}
case MASK: {
mask_t temp;
temp.read(data);
set_mask(temp);
break;
}
case INDEX: {
long temp;
binary::read_long(data, temp);
@ -550,9 +562,6 @@ void expr_t::op_t::write(std::ostream& out) const
case IDENT:
binary::write_string(out, as_ident());
break;
case MASK:
as_mask().write(out);
break;
case INDEX:
binary::write_long(out, as_index());
break;

View file

@ -47,7 +47,6 @@
#define _OP_H
#include "expr.h"
#include "mask.h"
namespace ledger {
@ -71,7 +70,6 @@ private:
variant<std::size_t, // used by constant INDEX
value_t, // used by constant VALUE
string, // used by constant IDENT
mask_t, // used by constant MASK
function_t, // used by terminal FUNCTION
ptr_op_t> // used by all binary operators
data;
@ -81,7 +79,6 @@ public:
// Constants
VALUE,
IDENT,
MASK,
INDEX,
CONSTANTS,
@ -115,6 +112,7 @@ public:
O_COMMA,
O_LOOKUP,
O_CALL,
O_MATCH,
@ -193,27 +191,6 @@ public:
data = val;
}
bool is_mask() const {
if (kind == MASK) {
assert(data.type() == typeid(mask_t));
return true;
}
return false;
}
mask_t& as_mask_lval() {
assert(is_mask());
return boost::get<mask_t>(data);
}
const mask_t& as_mask() const {
return const_cast<op_t *>(this)->as_mask_lval();
}
void set_mask(const mask_t& val) {
data = val;
}
void set_mask(const string& expr) {
data = mask_t(expr);
}
bool is_function() const {
return kind == FUNCTION;
}

View file

@ -47,11 +47,6 @@ expr_t::parser_t::parse_value_term(std::istream& in,
node->set_value(tok.value);
break;
case token_t::MASK:
node = new op_t(op_t::MASK);
node->set_mask(tok.value.as_string());
break;
case token_t::IDENT: {
string ident = tok.value.as_string();
@ -88,6 +83,31 @@ expr_t::parser_t::parse_value_term(std::istream& in,
return node;
}
expr_t::ptr_op_t
expr_t::parser_t::parse_dot_expr(std::istream& in,
const parse_flags_t& tflags) const
{
ptr_op_t node(parse_value_term(in, tflags));
if (node && ! tflags.has_flags(PARSE_SINGLE)) {
token_t& tok = next_token(in, tflags);
if (tok.kind == token_t::DOT) {
ptr_op_t prev(node);
node = new op_t(op_t::O_LOOKUP);
node->set_left(prev);
node->set_right(parse_dot_expr(in, tflags));
if (! node->right())
throw_(parse_error,
tok.symbol << " operator not followed by argument");
} else {
push_token(tok);
}
}
return node;
}
expr_t::ptr_op_t
expr_t::parser_t::parse_unary_expr(std::istream& in,
const parse_flags_t& tflags) const
@ -98,7 +118,7 @@ expr_t::parser_t::parse_unary_expr(std::istream& in,
switch (tok.kind) {
case token_t::EXCLAM: {
ptr_op_t term(parse_value_term(in, tflags));
ptr_op_t term(parse_dot_expr(in, tflags));
if (! term)
throw_(parse_error,
tok.symbol << " operator not followed by argument");
@ -115,7 +135,7 @@ expr_t::parser_t::parse_unary_expr(std::istream& in,
}
case token_t::MINUS: {
ptr_op_t term(parse_value_term(in, tflags));
ptr_op_t term(parse_dot_expr(in, tflags));
if (! term)
throw_(parse_error,
tok.symbol << " operator not followed by argument");
@ -133,7 +153,7 @@ expr_t::parser_t::parse_unary_expr(std::istream& in,
default:
push_token(tok);
node = parse_value_term(in, tflags);
node = parse_dot_expr(in, tflags);
break;
}

View file

@ -83,6 +83,8 @@ private:
ptr_op_t parse_value_term(std::istream& in,
const parse_flags_t& flags) const;
ptr_op_t parse_dot_expr(std::istream& in,
const parse_flags_t& flags) const;
ptr_op_t parse_unary_expr(std::istream& in,
const parse_flags_t& flags) const;
ptr_op_t parse_mul_expr(std::istream& in,

View file

@ -67,27 +67,29 @@ string args_to_predicate_expr(value_t::sequence_t::const_iterator begin,
append_and = true;
}
if (arg == "desc" || arg == "DESC" ||
arg == "payee" || arg == "PAYEE") {
arg = string("@") + (*++begin).as_string();
}
else if (arg == "note" || arg == "NOTE") {
arg = string("&") + (*++begin).as_string();
}
else if (arg == "tag" || arg == "TAG" ||
arg == "meta" || arg == "META" ||
arg == "data" || arg == "DATA") {
arg = string("%") + (*++begin).as_string();
}
else if (arg == "expr" || arg == "EXPR") {
arg = string("=") + (*++begin).as_string();
value_t::sequence_t::const_iterator next = begin;
if (++next != end) {
if (arg == "desc" || arg == "DESC" ||
arg == "payee" || arg == "PAYEE") {
arg = string("@") + (*++begin).as_string();
}
else if (arg == "note" || arg == "NOTE") {
arg = string("&") + (*++begin).as_string();
}
else if (arg == "tag" || arg == "TAG" ||
arg == "meta" || arg == "META" ||
arg == "data" || arg == "DATA") {
arg = string("%") + (*++begin).as_string();
}
else if (arg == "expr" || arg == "EXPR") {
arg = string("=") + (*++begin).as_string();
}
}
if (parse_argument) {
bool in_prefix = true;
bool in_suffix = false;
bool found_specifier = false;
bool saw_tag_char = false;
bool no_final_slash = false;
only_parenthesis = true;
@ -122,15 +124,14 @@ string args_to_predicate_expr(value_t::sequence_t::const_iterator begin,
bool found_metadata = false;
for (const char *q = c; *q != '\0'; q++)
if (*q == '=') {
expr << "(metadata(\""
<< string(c + 1, q - c - 1) << "\") =~ /";
expr << "has_tag(/"
<< string(c + 1, q - c - 1) << "/, /";
found_metadata = true;
c = q;
break;
}
if (! found_metadata) {
expr << "(tag =~ /:";
saw_tag_char = true;
expr << "has_tag(/";
}
found_specifier = true;
consumed = true;
@ -151,9 +152,9 @@ string args_to_predicate_expr(value_t::sequence_t::const_iterator begin,
case ')':
if (! in_suffix) {
if (found_specifier) {
if (saw_tag_char)
expr << ':';
expr << "/)";
if (! no_final_slash)
expr << "/";
expr << ")";
}
in_suffix = true;
}
@ -171,8 +172,6 @@ string args_to_predicate_expr(value_t::sequence_t::const_iterator begin,
if (! in_suffix) {
if (found_specifier) {
if (saw_tag_char)
expr << ':';
if (! no_final_slash)
expr << "/";
expr << ")";

View file

@ -157,6 +157,8 @@ namespace {
(args_to_predicate_expr(args.value().as_sequence().begin(),
args.value().as_sequence().end()));
DEBUG("report.predicate", "Predicate = " << report.predicate);
(report.*report_method)(handler_ptr(handler));
return true;

View file

@ -84,7 +84,7 @@ session_t::session_t()
("%-.9D %-.35P %-.39A %22.108t %!22.132T\n%/"
"%48|%-.38A %22.108t %!22.132T\n"),
print_format
("%(date)%(cleared ? \" *\" : (uncleared ? \"\" : \" !\"))%(code ? \" (\" + code + \")\" : \"\") %(payee)\n %-34(account) %12(amount)\n%/ %-34(account) %12(amount)%(note ? \" ; \" + note : \"\")\n%/\n"),
("%(date)%(cleared ? \" *\" : (uncleared ? \"\" : \" !\"))%(code ? \" (\" + code + \")\" : \"\") %(payee)%(entry.comment | \"\")\n %-34(account) %12(amount)%(comment | \"\")\n%/ %-34(account) %12(amount)%(comment | \"\")\n%/\n"),
balance_format
("%20(display_total) %(depth_spacer)%-(partial_account)\n"),
equity_format

View file

@ -1068,7 +1068,8 @@ entry_t * textual_parser_t::instance_t::parse_entry(std::istream& in,
TRACE_START(entry_details, 1, "Time spent parsing entry details:");
istream_pos_type end_pos;
std::size_t beg_line = linenum;
std::size_t beg_line = linenum;
xact_t * last_xact = NULL;
while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
istream_pos_type beg_pos = in.tellg();
@ -1086,13 +1087,23 @@ entry_t * textual_parser_t::instance_t::parse_entry(std::istream& in,
end_pos += len + 1;
linenum++;
if (line[0] == ' ' || line[0] == '\t') {
char * p = skip_ws(line);
if (! *p)
break;
}
const char * p = skip_ws(line);
if (! *p)
break;
if (xact_t * xact = parse_xact(line, master, curr.get())) {
if (*p == ';') {
// This is an entry note, and possibly a metadata info tag
if (last_xact) {
last_xact->append_note(p + 1);
last_xact->append_note("\n");
last_xact->parse_tags(p + 1);
} else {
curr->append_note(p + 1);
curr->append_note("\n");
curr->parse_tags(p + 1);
}
}
else if (xact_t * xact = parse_xact(line, master, curr.get())) {
if ((state == item_t::CLEARED && xact->state() != item_t::CLEARED) ||
(state == item_t::PENDING && xact->state() == item_t::UNCLEARED))
xact->set_state(state);
@ -1105,6 +1116,7 @@ entry_t * textual_parser_t::instance_t::parse_entry(std::istream& in,
pos = end_pos;
curr->add_xact(xact);
last_xact = xact;
}
if (in.eof())

View file

@ -110,8 +110,7 @@ void expr_t::token_t::parse_ident(std::istream& in)
length = 0;
char c, buf[256];
READ_INTO_(in, buf, 255, c, length,
std::isalnum(c) || c == '_' || c == '.' || c == '-');
READ_INTO_(in, buf, 255, c, length, std::isalnum(c) || c == '_' || c == '-');
value.set_string(buf);
}
@ -280,8 +279,8 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
in.get(c);
length++;
kind = MASK;
value.set_string(buf);
kind = VALUE;
value.set_mask(buf);
}
break;
}
@ -334,6 +333,11 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
kind = GREATER;
break;
case '.':
in.get(c);
kind = DOT;
break;
case ',':
in.get(c);
kind = COMMA;

View file

@ -89,6 +89,7 @@ struct expr_t::token_t : public noncopyable
QUERY, // ?
COLON, // :
DOT, // .
COMMA, // ,
TOK_EOF,

View file

@ -76,6 +76,11 @@ value_t::storage_t& value_t::storage_t::operator=(const value_t::storage_t& rhs)
string(*reinterpret_cast<string *>(const_cast<char *>(rhs.data)));
break;
case MASK:
new(reinterpret_cast<mask_t *>(data))
mask_t(*reinterpret_cast<mask_t *>(const_cast<char *>(rhs.data)));
break;
case SEQUENCE:
*reinterpret_cast<sequence_t **>(data) =
new sequence_t(**reinterpret_cast<sequence_t **>
@ -107,6 +112,9 @@ void value_t::storage_t::destroy()
case STRING:
reinterpret_cast<string *>(data)->~string();
break;
case MASK:
reinterpret_cast<mask_t *>(data)->~mask_t();
break;
case SEQUENCE:
checked_delete(*reinterpret_cast<sequence_t **>(data));
break;
@ -134,7 +142,6 @@ void value_t::initialize()
false_value->type = BOOLEAN;
*reinterpret_cast<bool *>(false_value->data) = false;
#if 0
BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(bool));
BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(datetime_t));
BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(date_t));
@ -143,9 +150,9 @@ void value_t::initialize()
BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_t *));
BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_pair_t *));
BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(string));
BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(mask_t));
BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(sequence_t *));
BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(boost::any));
#endif
DEBUG_(std::setw(3) << std::right << sizeof(bool)
<< " sizeof(bool)");
@ -163,6 +170,8 @@ void value_t::initialize()
<< " sizeof(balance_pair_t *)");
DEBUG_(std::setw(3) << std::right << sizeof(string)
<< " sizeof(string)");
DEBUG_(std::setw(3) << std::right << sizeof(mask_t)
<< " sizeof(mask_t)");
DEBUG_(std::setw(3) << std::right << sizeof(sequence_t *)
<< " sizeof(sequence_t *)");
DEBUG_(std::setw(3) << std::right << sizeof(boost::any)
@ -203,6 +212,8 @@ value_t::operator bool() const
return as_balance_pair();
case STRING:
return ! as_string().empty();
case MASK:
return ! as_mask().empty();
case SEQUENCE:
return ! as_sequence().empty();
case POINTER:
@ -303,6 +314,17 @@ string value_t::to_string() const
}
}
mask_t value_t::to_mask() const
{
if (is_mask()) {
return as_mask();
} else {
value_t temp(*this);
temp.in_place_cast(MASK);
return temp.as_mask();
}
}
value_t::sequence_t value_t::to_sequence() const
{
if (is_sequence()) {
@ -922,6 +944,15 @@ bool value_t::is_equal_to(const value_t& val) const
case STRING:
if (val.is_string())
return as_string() == val.as_string();
else if (val.is_mask())
return val.as_mask().match(as_string());
break;
case MASK:
if (val.is_mask())
return as_mask() == val.as_mask();
else if (val.is_string())
return as_mask().match(val.as_string());
break;
case SEQUENCE:
@ -1257,6 +1288,12 @@ void value_t::in_place_not()
case BALANCE_PAIR:
set_boolean(! as_balance_pair());
return;
case STRING:
set_boolean(as_string().empty());
return;
case MASK:
set_boolean(as_mask().empty());
return;
default:
break;
}
@ -1283,6 +1320,8 @@ bool value_t::is_realzero() const
return as_balance_pair().is_realzero();
case STRING:
return as_string().empty();
case MASK:
return as_mask().empty();
case SEQUENCE:
return as_sequence().empty();
@ -1314,6 +1353,8 @@ bool value_t::is_zero() const
return as_balance_pair().is_zero();
case STRING:
return as_string().empty();
case MASK:
return as_mask().empty();
case SEQUENCE:
return as_sequence().empty();
@ -1505,6 +1546,7 @@ value_t value_t::strip_annotations(const bool keep_price,
case DATETIME:
case DATE:
case STRING:
case MASK:
case POINTER:
return *this;
@ -1625,6 +1667,10 @@ void value_t::dump(std::ostream& out, const int first_width,
out << as_string();
break;
case MASK:
out << as_mask();
break;
case POINTER:
out << boost::unsafe_any_cast<const void *>(&as_any_pointer());
break;
@ -1700,6 +1746,10 @@ void value_t::print(std::ostream& out, const bool relaxed) const
out << '"' << as_string() << '"';
break;
case MASK:
out << '/' << as_mask() << '/';
break;
case POINTER:
assert(false);
break;
@ -1801,7 +1851,7 @@ void value_t::write(std::ostream& out) const
break;
}
throw_(value_error, "Cannot read " << label() << " to a stream");
throw_(value_error, "Cannot write " << label() << " to a stream");
}
void value_t::write_xml(std::ostream& out, const int depth) const
@ -1846,6 +1896,11 @@ void value_t::write_xml(std::ostream& out, const int depth) const
<< as_string()
<< "</string>\n";
break;
case MASK:
out << xml_str("<mask>", depth + 1)
<< as_mask()
<< "</mask>\n";
break;
case SEQUENCE:
out << xml_str("<sequence>\n", depth + 1);
foreach (const value_t& v, as_sequence())

View file

@ -50,6 +50,7 @@
#define _VALUE_H
#include "balpair.h" // pulls in balance.h and amount.h
#include "mask.h"
namespace ledger {
@ -108,6 +109,7 @@ public:
BALANCE, // a ledger::balance_t
BALANCE_PAIR, // a ledger::balance_pair_t
STRING, // a string object
MASK, // a regular expression mask
SEQUENCE, // a vector of value_t objects
POINTER // an opaque pointer of any type
};
@ -299,6 +301,10 @@ public:
TRACE_CTOR(value_t, "const balance_pair_t&");
set_balance_pair(val);
}
value_t(const mask_t& val) {
TRACE_CTOR(value_t, "const mask_t&");
set_mask(val);
}
explicit value_t(const string& val, bool literal = false) {
TRACE_CTOR(value_t, "const string&, bool");
@ -477,6 +483,7 @@ public:
* is_balance()
* is_balance_pair()
* is_string()
* is_mask()
* is_sequence()
* is_pointer()
*
@ -649,6 +656,27 @@ public:
new(reinterpret_cast<string *>(storage->data)) string(val);
}
bool is_mask() const {
return is_type(MASK);
}
mask_t& as_mask_lval() {
assert(is_mask());
_dup();
return *reinterpret_cast<mask_t *>(storage->data);
}
const mask_t& as_mask() const {
assert(is_mask());
return *reinterpret_cast<mask_t *>(storage->data);
}
void set_mask(const string& val) {
set_type(MASK);
new(reinterpret_cast<mask_t *>(storage->data)) mask_t(val);
}
void set_mask(const mask_t& val) {
set_type(MASK);
new(reinterpret_cast<mask_t *>(storage->data)) mask_t(val);
}
bool is_sequence() const {
return is_type(SEQUENCE);
}
@ -731,6 +759,7 @@ public:
balance_t to_balance() const;
balance_pair_t to_balance_pair() const;
string to_string() const;
mask_t to_mask() const;
sequence_t to_sequence() const;
/**
@ -879,6 +908,8 @@ public:
return "a balance pair";
case STRING:
return "a string";
case MASK:
return "a regexp";
case SEQUENCE:
return "a sequence";
case POINTER:
@ -928,6 +959,10 @@ inline value_t string_value(const string& str) {
return value_t(str, true);
}
inline value_t mask_value(const string& str) {
return value_t(mask_t(str));
}
inline std::ostream& operator<<(std::ostream& out, const value_t& val) {
val.print(out, 12);
return out;

View file

@ -64,6 +64,10 @@ item_t::state_t xact_t::state() const
}
namespace {
value_t get_entry(xact_t& xact) {
return value_t(static_cast<scope_t *>(xact.entry));
}
value_t get_code(xact_t& xact) {
if (xact.entry->code)
return string_value(*xact.entry->code);
@ -172,6 +176,11 @@ expr_t::ptr_op_t xact_t::lookup(const string& name)
return WRAP_FUNCTOR(get_wrapper<&get_cost>);
break;
case 'e':
if (name == "entry")
return WRAP_FUNCTOR(get_wrapper<&get_entry>);
break;
case 'p':
if (name == "payee")
return WRAP_FUNCTOR(get_wrapper<&get_payee>);
@ -189,14 +198,7 @@ expr_t::ptr_op_t xact_t::lookup(const string& name)
break;
}
#if 0
// jww (2008-09-19): I don't think we can lookup in entry, because
// that means the functor returned would be expecting an entry to be
// passed to it, rather than a transaction.
return entry->lookup(name);
#else
return item_t::lookup(name);
#endif
}
bool xact_t::valid() const