diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/account.cc | 34 | ||||
-rw-r--r-- | src/commodity.cc | 4 | ||||
-rw-r--r-- | src/convert.cc | 7 | ||||
-rw-r--r-- | src/expr.cc | 20 | ||||
-rw-r--r-- | src/generate.cc | 8 | ||||
-rw-r--r-- | src/interactive.cc | 4 | ||||
-rw-r--r-- | src/interactive.h | 13 | ||||
-rw-r--r-- | src/op.cc | 52 | ||||
-rw-r--r-- | src/op.h | 1 | ||||
-rw-r--r-- | src/parser.cc | 5 | ||||
-rw-r--r-- | src/post.cc | 48 | ||||
-rw-r--r-- | src/print.cc | 12 | ||||
-rw-r--r-- | src/report.cc | 16 | ||||
-rw-r--r-- | src/report.h | 1 | ||||
-rw-r--r-- | src/scope.h | 11 | ||||
-rw-r--r-- | src/value.cc | 31 | ||||
-rw-r--r-- | src/value.h | 29 | ||||
-rw-r--r-- | src/xact.cc | 38 |
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 @@ -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; @@ -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>); |