Added support for metadata and tagging, and made regexs a first-class type.
This commit is contained in:
parent
7128fdb637
commit
fb5428ce85
16 changed files with 415 additions and 123 deletions
150
src/item.cc
150
src/item.cc
|
|
@ -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>);
|
||||
|
|
|
|||
18
src/item.h
18
src/item.h
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
97
src/op.cc
97
src/op.cc
|
|
@ -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;
|
||||
|
|
|
|||
25
src/op.h
25
src/op.h
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 << ")";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
12
src/token.cc
12
src/token.cc
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ struct expr_t::token_t : public noncopyable
|
|||
QUERY, // ?
|
||||
COLON, // :
|
||||
|
||||
DOT, // .
|
||||
COMMA, // ,
|
||||
|
||||
TOK_EOF,
|
||||
|
|
|
|||
61
src/value.cc
61
src/value.cc
|
|
@ -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())
|
||||
|
|
|
|||
35
src/value.h
35
src/value.h
|
|
@ -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;
|
||||
|
|
|
|||
16
src/xact.cc
16
src/xact.cc
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue