summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/account.cc34
-rw-r--r--src/commodity.cc4
-rw-r--r--src/convert.cc7
-rw-r--r--src/expr.cc20
-rw-r--r--src/generate.cc8
-rw-r--r--src/interactive.cc4
-rw-r--r--src/interactive.h13
-rw-r--r--src/op.cc52
-rw-r--r--src/op.h1
-rw-r--r--src/parser.cc5
-rw-r--r--src/post.cc48
-rw-r--r--src/print.cc12
-rw-r--r--src/report.cc16
-rw-r--r--src/report.h1
-rw-r--r--src/scope.h11
-rw-r--r--src/value.cc31
-rw-r--r--src/value.h29
-rw-r--r--src/xact.cc38
18 files changed, 316 insertions, 18 deletions
diff --git a/src/account.cc b/src/account.cc
index 245d61fc..e02d21d7 100644
--- a/src/account.cc
+++ b/src/account.cc
@@ -239,6 +239,36 @@ namespace {
value_t get_parent(account_t& account) {
return value_t(static_cast<scope_t *>(account.parent));
}
+
+ value_t fn_any(call_scope_t& scope)
+ {
+ interactive_t args(scope, "X&X");
+
+ account_t& account(find_scope<account_t>(scope));
+ expr_t& expr(args.get<expr_t&>(0));
+
+ foreach (post_t * p, account.posts) {
+ bind_scope_t bound_scope(scope, *p);
+ if (expr.calc(bound_scope).to_boolean())
+ return true;
+ }
+ return false;
+ }
+
+ value_t fn_all(call_scope_t& scope)
+ {
+ interactive_t args(scope, "X&X");
+
+ account_t& account(find_scope<account_t>(scope));
+ expr_t& expr(args.get<expr_t&>(0));
+
+ foreach (post_t * p, account.posts) {
+ bind_scope_t bound_scope(scope, *p);
+ if (! expr.calc(bound_scope).to_boolean())
+ return false;
+ }
+ return true;
+ }
}
expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind,
@@ -255,6 +285,10 @@ expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(get_wrapper<&get_account>);
else if (name == "account_base")
return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
+ else if (name == "any")
+ return WRAP_FUNCTOR(&fn_any);
+ else if (name == "all")
+ return WRAP_FUNCTOR(&fn_all);
break;
case 'c':
diff --git a/src/commodity.cc b/src/commodity.cc
index e5f10e34..836a4269 100644
--- a/src/commodity.cc
+++ b/src/commodity.cc
@@ -428,7 +428,9 @@ namespace {
{
switch (buf[0]) {
case 'a':
- return std::strcmp(buf, "and") == 0;
+ return (std::strcmp(buf, "and") == 0 ||
+ std::strcmp(buf, "any") == 0 ||
+ std::strcmp(buf, "all") == 0);
case 'd':
return std::strcmp(buf, "div") == 0;
case 'e':
diff --git a/src/convert.cc b/src/convert.cc
index 6c02cff3..2e6da2f6 100644
--- a/src/convert.cc
+++ b/src/convert.cc
@@ -83,7 +83,7 @@ value_t convert_command(call_scope_t& scope)
}
}
- // Create a flat list o
+ // Create a flat list
xacts_list current_xacts(journal.xacts_begin(), journal.xacts_end());
// Read in the series of transactions from the CSV file
@@ -93,6 +93,11 @@ value_t convert_command(call_scope_t& scope)
csv_reader reader(data);
while (xact_t * xact = reader.read_xact(journal, bucket)) {
+ if (report.HANDLED(invert)) {
+ foreach (post_t * post, xact->posts)
+ post->amount.in_place_negate();
+ }
+
bool matched = false;
post_map_t::iterator i = post_map.find(- xact->posts.front()->amount);
if (i != post_map.end()) {
diff --git a/src/expr.cc b/src/expr.cc
index 79fb3611..f3a30de6 100644
--- a/src/expr.cc
+++ b/src/expr.cc
@@ -39,10 +39,26 @@ namespace ledger {
void expr_t::parse(std::istream& in, const parse_flags_t& flags,
const optional<string>& original_string)
{
- base_type::parse(in, flags, original_string);
-
parser_t parser;
+ istream_pos_type start_pos = in.tellg();
ptr = parser.parse(in, flags, original_string);
+ istream_pos_type end_pos = in.tellg();
+
+ if (original_string) {
+ set_text(*original_string);
+ }
+ else if (end_pos > start_pos) {
+ in.clear();
+ in.seekg(start_pos, std::ios::beg);
+ scoped_array<char> buf
+ (new char[static_cast<std::size_t>(end_pos - start_pos) + 1]);
+ in.read(buf.get(), end_pos - start_pos);
+ buf[end_pos - start_pos] = '\0';
+ set_text(buf.get());
+ }
+ else {
+ set_text("<stream>");
+ }
}
void expr_t::compile(scope_t& scope)
diff --git a/src/generate.cc b/src/generate.cc
index c1eb1d14..9b4c2ee7 100644
--- a/src/generate.cc
+++ b/src/generate.cc
@@ -171,10 +171,10 @@ void generate_posts_iterator::generate_commodity(std::ostream& out)
generate_string(buf, six_gen(), true);
comm = buf.str();
}
- while (comm == "h" || comm == "m" || comm == "s" ||
- comm == "and" || comm == "div" || comm == "false" ||
- comm == "or" || comm == "not" || comm == "true" ||
- comm == "if" || comm == "else");
+ while (comm == "h" || comm == "m" || comm == "s" || comm == "and" ||
+ comm == "any" || comm == "all" || comm == "div" ||
+ comm == "false" || comm == "or" || comm == "not" ||
+ comm == "true" || comm == "if" || comm == "else");
out << comm;
}
diff --git a/src/interactive.cc b/src/interactive.cc
index d0baf07a..9aeb5307 100644
--- a/src/interactive.cc
+++ b/src/interactive.cc
@@ -122,6 +122,10 @@ void interactive_t::verify_arguments() const
label = _("a scope");
wrong_arg = ! next_arg->is_scope();
break;
+ case 'X':
+ label = _("an expression");
+ wrong_arg = ! next_arg->is_expr();
+ break;
case 'S':
label = _("a sequence");
wrong_arg = false;
diff --git a/src/interactive.h b/src/interactive.h
index fbc4ffeb..04c23ae5 100644
--- a/src/interactive.h
+++ b/src/interactive.h
@@ -120,6 +120,19 @@ inline const value_t::sequence_t&
interactive_t::get<const value_t::sequence_t&>(std::size_t index) {
return value_at(index).as_sequence();
}
+template <>
+inline scope_t *
+interactive_t::get<scope_t *>(std::size_t index) {
+ return value_at(index).as_scope();
+}
+template <>
+inline expr_t& interactive_t::get<expr_t&>(std::size_t index) {
+ return value_at(index).as_expr_lval();
+}
+template <>
+inline const expr_t& interactive_t::get<const expr_t&>(std::size_t index) {
+ return value_at(index).as_expr();
+}
template <typename T>
class in_context_t : public interactive_t
diff --git a/src/op.cc b/src/op.cc
index 4020ecff..df222802 100644
--- a/src/op.cc
+++ b/src/op.cc
@@ -38,6 +38,37 @@
namespace ledger {
+namespace {
+ value_t split_cons_expr(expr_t::ptr_op_t op, scope_t& scope,
+ std::vector<expr_t>& exprs)
+ {
+ value_t seq;
+
+ if (op->kind == expr_t::op_t::O_CONS) {
+ exprs.push_back(expr_t(op->left(), &scope));
+ seq.push_back(value_t(exprs.back()));
+
+ expr_t::ptr_op_t next = op->right();
+ while (next) {
+ expr_t::ptr_op_t value_op;
+ if (next->kind == expr_t::op_t::O_CONS) {
+ value_op = next->left();
+ next = next->right();
+ } else {
+ value_op = next;
+ next = NULL;
+ }
+ exprs.push_back(expr_t(value_op, &scope));
+ seq.push_back(value_t(exprs.back()));
+ }
+ } else {
+ exprs.push_back(expr_t(op, &scope));
+ seq.push_back(value_t(exprs.back()));
+ }
+ return seq;
+ }
+}
+
expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth)
{
if (is_ident()) {
@@ -190,11 +221,24 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
_("Failed to lookup member '%1'") << right()->as_ident());
break;
- case O_CALL: {
+ case O_CALL:
+ case O_EXPAND: {
call_scope_t call_args(scope);
+ // When evaluating a macro call, these expressions have to live beyond the
+ // call to calc() below.
+ optional<std::vector<expr_t> > args_expr;
- if (has_right())
- call_args.set_args(right()->calc(scope, locus, depth + 1));
+ if (has_right()) {
+ if (kind == O_CALL) {
+ call_args.set_args(right()->calc(scope, locus, depth + 1));
+ } else {
+ // macros defer calculation to the callee
+ args_expr = std::vector<expr_t>();
+ call_args.set_args(split_cons_expr(right()->kind == O_SEQ ?
+ right()->left() : right(),
+ scope, *args_expr));
+ }
+ }
ptr_op_t func = left();
const string& name(func->as_ident());
@@ -592,6 +636,7 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const
break;
case O_CALL:
+ case O_EXPAND:
if (left() && left()->print(out, context))
found = true;
if (has_right()) {
@@ -663,6 +708,7 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const
case O_DEFINE: out << "O_DEFINE"; break;
case O_LOOKUP: out << "O_LOOKUP"; break;
case O_CALL: out << "O_CALL"; break;
+ case O_EXPAND: out << "O_EXPAND"; break;
case O_MATCH: out << "O_MATCH"; break;
case O_NOT: out << "O_NOT"; break;
diff --git a/src/op.h b/src/op.h
index 6a69527b..936c11f4 100644
--- a/src/op.h
+++ b/src/op.h
@@ -105,6 +105,7 @@ public:
O_DEFINE,
O_LOOKUP,
O_CALL,
+ O_EXPAND,
O_MATCH,
BINARY_OPERATORS,
diff --git a/src/parser.cc b/src/parser.cc
index 5bb06f84..e8e987cb 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -58,7 +58,10 @@ expr_t::parser_t::parse_value_term(std::istream& in,
// An identifier followed by ( represents a function call
tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
if (tok.kind == token_t::LPAREN) {
- ptr_op_t call_node(new op_t(op_t::O_CALL));
+ op_t::kind_t kind = op_t::O_CALL;
+ if (ident == "any" || ident == "all")
+ kind = op_t::O_EXPAND;
+ ptr_op_t call_node(new op_t(kind));
call_node->set_left(node);
node = call_node;
diff --git a/src/post.cc b/src/post.cc
index 41ae04dd..183fb901 100644
--- a/src/post.cc
+++ b/src/post.cc
@@ -291,6 +291,50 @@ namespace {
value_t get_wrapper(call_scope_t& scope) {
return (*Func)(find_scope<post_t>(scope));
}
+
+ value_t fn_any(call_scope_t& scope)
+ {
+ interactive_t args(scope, "X&X");
+
+ post_t& post(find_scope<post_t>(scope));
+ expr_t& expr(args.get<expr_t&>(0));
+
+ foreach (post_t * p, post.xact->posts) {
+ bind_scope_t bound_scope(scope, *p);
+ if (p == &post && args.has(1) &&
+ ! args.get<expr_t&>(1).calc(bound_scope).to_boolean()) {
+ // If the user specifies any(EXPR, false), and the context is a
+ // posting, then that posting isn't considered by the test.
+ ; // skip it
+ }
+ else if (expr.calc(bound_scope).to_boolean()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ value_t fn_all(call_scope_t& scope)
+ {
+ interactive_t args(scope, "X&X");
+
+ post_t& post(find_scope<post_t>(scope));
+ expr_t& expr(args.get<expr_t&>(0));
+
+ foreach (post_t * p, post.xact->posts) {
+ bind_scope_t bound_scope(scope, *p);
+ if (p == &post && args.has(1) &&
+ ! args.get<expr_t&>(1).calc(bound_scope).to_boolean()) {
+ // If the user specifies any(EXPR, false), and the context is a
+ // posting, then that posting isn't considered by the test.
+ ; // skip it
+ }
+ else if (! expr.calc(bound_scope).to_boolean()) {
+ return false;
+ }
+ }
+ return true;
+ }
}
expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
@@ -307,6 +351,10 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(get_account);
else if (name == "account_base")
return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
+ else if (name == "any")
+ return WRAP_FUNCTOR(&fn_any);
+ else if (name == "all")
+ return WRAP_FUNCTOR(&fn_all);
break;
case 'b':
diff --git a/src/print.cc b/src/print.cc
index eb102a87..34f5af51 100644
--- a/src/print.cc
+++ b/src/print.cc
@@ -140,9 +140,15 @@ namespace {
if (slip > 0)
out << string(slip, ' ');
- std::ostringstream amt_str;
- report.scrub(post->amount).print(amt_str, 12, -1, true);
- string amt = amt_str.str();
+ string amt;
+ if (post->amount_expr) {
+ amt = post->amount_expr->text();
+ } else {
+ std::ostringstream amt_str;
+ report.scrub(post->amount).print(amt_str, 12, -1, true);
+ amt = amt_str.str();
+ }
+
string trimmed_amt(amt);
trim_left(trimmed_amt);
int amt_slip = (static_cast<int>(amt.length()) -
diff --git a/src/report.cc b/src/report.cc
index 2189812b..1180c019 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -443,6 +443,20 @@ value_t report_t::fn_trim(call_scope_t& args)
}
}
+value_t report_t::fn_print(call_scope_t& args)
+{
+ std::ostream& out(output_stream);
+ bool first = true;
+ for (call_scope_t::iterator i = args.begin(); i != args.end(); i++) {
+ if (first)
+ first = false;
+ else
+ out << ' ';
+ (*i).print(out);
+ }
+ return true;
+}
+
value_t report_t::scrub(value_t val)
{
value_t temp(val.strip_annotations(what_to_keep()));
@@ -1117,6 +1131,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return MAKE_FUNCTOR(report_t::fn_percent);
else if (is_eq(p, "price"))
return MAKE_FUNCTOR(report_t::fn_price);
+ else if (is_eq(p, "print"))
+ return MAKE_FUNCTOR(report_t::fn_print);
break;
case 'q':
diff --git a/src/report.h b/src/report.h
index 50071940..6b10dbcc 100644
--- a/src/report.h
+++ b/src/report.h
@@ -144,6 +144,7 @@ public:
value_t fn_is_seq(call_scope_t& scope);
value_t fn_strip(call_scope_t& scope);
value_t fn_trim(call_scope_t& scope);
+ value_t fn_print(call_scope_t& scope);
value_t scrub(value_t val);
value_t fn_scrub(call_scope_t& scope);
value_t fn_quantity(call_scope_t& scope);
diff --git a/src/scope.h b/src/scope.h
index 675fa4e1..30ba6823 100644
--- a/src/scope.h
+++ b/src/scope.h
@@ -241,6 +241,17 @@ public:
args.pop_back();
}
+ typedef value_t::sequence_t::iterator iterator;
+
+ value_t::sequence_t::iterator begin() {
+ return args.begin();
+ }
+ value_t::sequence_t::iterator end() {
+ return args.end();
+ }
+
+ typedef value_t::sequence_t::const_iterator const_iterator;
+
value_t::sequence_t::const_iterator begin() const {
return args.begin();
}
diff --git a/src/value.cc b/src/value.cc
index 54798162..ce9b0e6a 100644
--- a/src/value.cc
+++ b/src/value.cc
@@ -36,6 +36,7 @@
#include "annotate.h"
#include "pool.h"
#include "unistring.h" // for justify()
+#include "op.h"
namespace ledger {
@@ -115,6 +116,8 @@ value_t::operator bool() const
return false;
case SCOPE:
return as_scope() != NULL;
+ case EXPR:
+ return as_expr();
default:
break;
}
@@ -140,6 +143,12 @@ void value_t::set_type(type_t new_type)
}
}
+void value_t::set_expr(const expr_t& val)
+{
+ set_type(EXPR);
+ storage->data = new expr_t(val);
+}
+
bool value_t::to_boolean() const
{
if (is_boolean()) {
@@ -1272,6 +1281,8 @@ bool value_t::is_realzero() const
case SCOPE:
return as_scope() == NULL;
+ case EXPR:
+ return ! as_expr();
default:
throw_(value_error, _("Cannot determine if %1 is really zero") << label());
@@ -1301,6 +1312,8 @@ bool value_t::is_zero() const
case SCOPE:
return as_scope() == NULL;
+ case EXPR:
+ return ! as_expr();
default:
throw_(value_error, _("Cannot determine if %1 is zero") << label());
@@ -1565,6 +1578,7 @@ value_t value_t::strip_annotations(const keep_details_t& what_to_keep) const
case STRING:
case MASK:
case SCOPE:
+ case EXPR:
return *this;
case SEQUENCE: {
@@ -1673,7 +1687,15 @@ void value_t::print(std::ostream& out,
}
case SCOPE:
- out << "<SCOPE>";
+ out << "<#SCOPE>";
+ break;
+ case EXPR:
+ out << "<#EXPR ";
+ if (as_expr())
+ as_expr().print(out);
+ else
+ out << "null";
+ out << ">";
break;
default:
@@ -1743,6 +1765,12 @@ void value_t::dump(std::ostream& out, const bool relaxed) const
case SCOPE:
out << as_scope();
break;
+ case EXPR:
+ if (as_expr())
+ as_expr().dump(out);
+ else
+ out << "null";
+ break;
case SEQUENCE: {
out << '(';
@@ -1855,6 +1883,7 @@ void to_xml(std::ostream& out, const value_t& value)
}
case value_t::SCOPE:
+ case value_t::EXPR:
default:
assert(false);
break;
diff --git a/src/value.h b/src/value.h
index 1c1d8b6c..3252ed65 100644
--- a/src/value.h
+++ b/src/value.h
@@ -57,6 +57,7 @@ namespace ledger {
DECLARE_EXCEPTION(value_error, std::runtime_error);
class scope_t;
+class expr_t;
/**
* @class value_t
@@ -108,7 +109,8 @@ public:
STRING, // a string object
MASK, // a regular expression mask
SEQUENCE, // a vector of value_t objects
- SCOPE // a pointer to a scope
+ SCOPE, // a pointer to a scope
+ EXPR // a pointer to a value expression
};
private:
@@ -135,7 +137,8 @@ private:
string, // STRING
mask_t, // MASK
sequence_t *, // SEQUENCE
- scope_t * // SCOPE
+ scope_t *, // SCOPE
+ expr_t * // EXPR
> data;
type_t type;
@@ -351,6 +354,10 @@ public:
TRACE_CTOR(value_t, "scope_t *");
set_scope(item);
}
+ explicit value_t(const expr_t& item) {
+ TRACE_CTOR(value_t, "const expr_t&");
+ set_expr(item);
+ }
/**
* Destructor. This does not do anything, because the intrusive_ptr
@@ -723,6 +730,22 @@ public:
}
/**
+ * Dealing with expr pointers.
+ */
+ bool is_expr() const {
+ return is_type(EXPR);
+ }
+ expr_t& as_expr_lval() const {
+ VERIFY(is_expr());
+ return *boost::get<expr_t *>(storage->data);
+ }
+ const expr_t& as_expr() const {
+ VERIFY(is_expr());
+ return *boost::get<expr_t *>(storage->data);
+ }
+ void set_expr(const expr_t& val);
+
+ /**
* Data conversion methods. These methods convert a value object to
* its underlying type, where possible. If not possible, an
* exception is thrown.
@@ -908,6 +931,8 @@ public:
return _("a sequence");
case SCOPE:
return _("a scope");
+ case EXPR:
+ return _("a expr");
default:
assert(false);
break;
diff --git a/src/xact.cc b/src/xact.cc
index 1a022387..344f66ea 100644
--- a/src/xact.cc
+++ b/src/xact.cc
@@ -36,6 +36,7 @@
#include "account.h"
#include "journal.h"
#include "pool.h"
+#include "interactive.h"
namespace ledger {
@@ -483,6 +484,36 @@ namespace {
value_t get_wrapper(call_scope_t& scope) {
return (*Func)(find_scope<xact_t>(scope));
}
+
+ value_t fn_any(call_scope_t& scope)
+ {
+ interactive_t args(scope, "X&X");
+
+ post_t& post(find_scope<post_t>(scope));
+ expr_t& expr(args.get<expr_t&>(0));
+
+ foreach (post_t * p, post.xact->posts) {
+ bind_scope_t bound_scope(scope, *p);
+ if (expr.calc(bound_scope).to_boolean())
+ return true;
+ }
+ return false;
+ }
+
+ value_t fn_all(call_scope_t& scope)
+ {
+ interactive_t args(scope, "X&X");
+
+ post_t& post(find_scope<post_t>(scope));
+ expr_t& expr(args.get<expr_t&>(0));
+
+ foreach (post_t * p, post.xact->posts) {
+ bind_scope_t bound_scope(scope, *p);
+ if (! expr.calc(bound_scope).to_boolean())
+ return false;
+ }
+ return true;
+ }
}
expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind,
@@ -492,6 +523,13 @@ expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind,
return item_t::lookup(kind, name);
switch (name[0]) {
+ case 'a':
+ if (name == "any")
+ return WRAP_FUNCTOR(&fn_any);
+ else if (name == "all")
+ return WRAP_FUNCTOR(&fn_all);
+ break;
+
case 'c':
if (name == "code")
return WRAP_FUNCTOR(get_wrapper<&get_code>);