diff options
author | John Wiegley <johnw@newartisans.com> | 2010-05-08 02:06:06 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2010-05-08 02:06:06 -0400 |
commit | 4ec54b86f856c6e85446d065d2940b4244d71b8f (patch) | |
tree | 645c11578337991c7de83a4e3443a50b5c8a469a /src | |
parent | d5f8c3bc576d98266519911ec05a98cd586d49db (diff) | |
download | fork-ledger-4ec54b86f856c6e85446d065d2940b4244d71b8f.tar.gz fork-ledger-4ec54b86f856c6e85446d065d2940b4244d71b8f.tar.bz2 fork-ledger-4ec54b86f856c6e85446d065d2940b4244d71b8f.zip |
Added any() and all() value expression macros
any() matches an expression against every post in a transaction or
account, and returns true if any of them are true. all() tests if all
are true. For example:
ledger -l 'account =~ /Expense/ & any(account =~ /MasterCard/)' reg
This reports every posting affecting an Expense account (regex match),
but only if some other posting in the same transaction affects the
MasterCard account.
Both functions also take a second boolean argument. If it is false, the
"source" posting is not considered. For example:
ledger -l 'any(/x/, false)'
This matches any posting where a *different* posting in the same
transaction contains the letter 'x'.
Diffstat (limited to 'src')
-rw-r--r-- | src/account.cc | 34 | ||||
-rw-r--r-- | src/commodity.cc | 4 | ||||
-rw-r--r-- | src/generate.cc | 8 | ||||
-rw-r--r-- | src/parser.cc | 5 | ||||
-rw-r--r-- | src/post.cc | 48 | ||||
-rw-r--r-- | src/xact.cc | 38 |
6 files changed, 131 insertions, 6 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/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/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/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>); |