diff options
author | John Wiegley <johnw@newartisans.com> | 2012-04-26 16:39:25 -0500 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2012-04-26 16:39:25 -0500 |
commit | 64a9b42381c26baf24e58b40f50f0b253e551811 (patch) | |
tree | 5447a29dff64c3a8b7be8100a01bcb4a2d73b0bb /src | |
parent | 7cc550fc22357e2ded194d3e65287c6b3317f5ae (diff) | |
parent | b4407c10c0071365322b2963747bf42a57fd7304 (diff) | |
download | fork-ledger-64a9b42381c26baf24e58b40f50f0b253e551811.tar.gz fork-ledger-64a9b42381c26baf24e58b40f50f0b253e551811.tar.bz2 fork-ledger-64a9b42381c26baf24e58b40f50f0b253e551811.zip |
Merge branch 'release/v3.0.0-20120426'
Diffstat (limited to 'src')
-rw-r--r-- | src/account.cc | 66 | ||||
-rw-r--r-- | src/account.h | 44 | ||||
-rw-r--r-- | src/accum.cc | 5 | ||||
-rw-r--r-- | src/accum.h | 27 | ||||
-rw-r--r-- | src/amount.cc | 99 | ||||
-rw-r--r-- | src/amount.h | 37 | ||||
-rw-r--r-- | src/annotate.cc | 163 | ||||
-rw-r--r-- | src/annotate.h | 78 | ||||
-rw-r--r-- | src/archive.cc | 2 | ||||
-rw-r--r-- | src/archive.h | 2 | ||||
-rw-r--r-- | src/balance.cc | 143 | ||||
-rw-r--r-- | src/balance.h | 22 | ||||
-rw-r--r-- | src/chain.cc | 24 | ||||
-rw-r--r-- | src/chain.h | 5 | ||||
-rw-r--r-- | src/commodity.cc | 517 | ||||
-rw-r--r-- | src/commodity.h | 190 | ||||
-rw-r--r-- | src/compare.cc | 4 | ||||
-rw-r--r-- | src/compare.h | 2 | ||||
-rw-r--r-- | src/context.h | 162 | ||||
-rw-r--r-- | src/convert.cc | 101 | ||||
-rw-r--r-- | src/convert.h | 2 | ||||
-rw-r--r-- | src/csv.cc | 129 | ||||
-rw-r--r-- | src/csv.h | 40 | ||||
-rw-r--r-- | src/draft.cc | 31 | ||||
-rw-r--r-- | src/draft.h | 29 | ||||
-rw-r--r-- | src/emacs.cc | 2 | ||||
-rw-r--r-- | src/emacs.h | 2 | ||||
-rw-r--r-- | src/error.cc | 2 | ||||
-rw-r--r-- | src/error.h | 8 | ||||
-rw-r--r-- | src/expr.cc | 114 | ||||
-rw-r--r-- | src/expr.h | 136 | ||||
-rw-r--r-- | src/exprbase.h | 8 | ||||
-rw-r--r-- | src/filters.cc | 333 | ||||
-rw-r--r-- | src/filters.h | 107 | ||||
-rw-r--r-- | src/flags.h | 2 | ||||
-rw-r--r-- | src/format.cc | 289 | ||||
-rw-r--r-- | src/format.h | 11 | ||||
-rw-r--r-- | src/generate.cc | 25 | ||||
-rw-r--r-- | src/generate.h | 4 | ||||
-rw-r--r-- | src/global.cc | 39 | ||||
-rw-r--r-- | src/global.h | 15 | ||||
-rw-r--r-- | src/history.cc | 455 | ||||
-rw-r--r-- | src/history.h | 135 | ||||
-rw-r--r-- | src/item.cc | 89 | ||||
-rw-r--r-- | src/item.h | 53 | ||||
-rw-r--r-- | src/iterators.cc | 115 | ||||
-rw-r--r-- | src/iterators.h | 107 | ||||
-rw-r--r-- | src/journal.cc | 294 | ||||
-rw-r--r-- | src/journal.h | 83 | ||||
-rw-r--r-- | src/lookup.cc | 4 | ||||
-rw-r--r-- | src/lookup.h | 2 | ||||
-rw-r--r-- | src/main.cc | 34 | ||||
-rw-r--r-- | src/mask.cc | 4 | ||||
-rw-r--r-- | src/mask.h | 2 | ||||
-rw-r--r-- | src/op.cc | 589 | ||||
-rw-r--r-- | src/op.h | 68 | ||||
-rw-r--r-- | src/option.cc | 3 | ||||
-rw-r--r-- | src/option.h | 82 | ||||
-rw-r--r-- | src/org.cc | 6 | ||||
-rw-r--r-- | src/org.h | 2 | ||||
-rw-r--r-- | src/output.cc | 10 | ||||
-rw-r--r-- | src/output.h | 2 | ||||
-rw-r--r-- | src/parser.cc | 79 | ||||
-rw-r--r-- | src/parser.h | 4 | ||||
-rw-r--r-- | src/pool.cc | 267 | ||||
-rw-r--r-- | src/pool.h | 62 | ||||
-rw-r--r-- | src/post.cc | 123 | ||||
-rw-r--r-- | src/post.h | 47 | ||||
-rw-r--r-- | src/precmd.cc | 28 | ||||
-rw-r--r-- | src/precmd.h | 2 | ||||
-rw-r--r-- | src/predicate.h | 2 | ||||
-rw-r--r-- | src/print.cc | 112 | ||||
-rw-r--r-- | src/print.h | 2 | ||||
-rw-r--r-- | src/pstream.h | 11 | ||||
-rw-r--r-- | src/py_account.cc | 2 | ||||
-rw-r--r-- | src/py_amount.cc | 17 | ||||
-rw-r--r-- | src/py_balance.cc | 10 | ||||
-rw-r--r-- | src/py_commodity.cc | 80 | ||||
-rw-r--r-- | src/py_expr.cc | 2 | ||||
-rw-r--r-- | src/py_format.cc | 2 | ||||
-rw-r--r-- | src/py_item.cc | 30 | ||||
-rw-r--r-- | src/py_journal.cc | 111 | ||||
-rw-r--r-- | src/py_post.cc | 26 | ||||
-rw-r--r-- | src/py_session.cc | 76 | ||||
-rw-r--r-- | src/py_times.cc | 2 | ||||
-rw-r--r-- | src/py_utils.cc | 2 | ||||
-rw-r--r-- | src/py_value.cc | 19 | ||||
-rw-r--r-- | src/py_xact.cc | 15 | ||||
-rw-r--r-- | src/pyfstream.h | 10 | ||||
-rw-r--r-- | src/pyinterp.cc | 261 | ||||
-rw-r--r-- | src/pyinterp.h | 61 | ||||
-rw-r--r-- | src/pyledger.cc | 2 | ||||
-rw-r--r-- | src/pyutils.h | 2 | ||||
-rw-r--r-- | src/query.cc | 4 | ||||
-rw-r--r-- | src/query.h | 15 | ||||
-rw-r--r-- | src/quotes.cc | 4 | ||||
-rw-r--r-- | src/quotes.h | 4 | ||||
-rw-r--r-- | src/report.cc | 482 | ||||
-rw-r--r-- | src/report.h | 814 | ||||
-rw-r--r-- | src/scope.cc | 20 | ||||
-rw-r--r-- | src/scope.h | 102 | ||||
-rw-r--r-- | src/select.cc | 441 | ||||
-rw-r--r-- | src/select.h (renamed from src/predicate.cc) | 25 | ||||
-rw-r--r-- | src/series.h | 135 | ||||
-rw-r--r-- | src/session.cc | 123 | ||||
-rw-r--r-- | src/session.h | 45 | ||||
-rw-r--r-- | src/stats.cc | 2 | ||||
-rw-r--r-- | src/stats.h | 2 | ||||
-rw-r--r-- | src/stream.cc | 2 | ||||
-rw-r--r-- | src/stream.h | 2 | ||||
-rw-r--r-- | src/system.hh.in | 37 | ||||
-rw-r--r-- | src/temps.cc | 10 | ||||
-rw-r--r-- | src/temps.h | 9 | ||||
-rw-r--r-- | src/textual.cc | 1171 | ||||
-rw-r--r-- | src/timelog.cc | 113 | ||||
-rw-r--r-- | src/timelog.h | 15 | ||||
-rw-r--r-- | src/times.cc | 33 | ||||
-rw-r--r-- | src/times.h | 29 | ||||
-rw-r--r-- | src/token.cc | 7 | ||||
-rw-r--r-- | src/token.h | 2 | ||||
-rw-r--r-- | src/unistring.h | 6 | ||||
-rw-r--r-- | src/utils.cc | 135 | ||||
-rw-r--r-- | src/utils.h | 52 | ||||
-rw-r--r-- | src/value.cc | 166 | ||||
-rw-r--r-- | src/value.h | 48 | ||||
-rw-r--r-- | src/views.cc | 259 | ||||
-rw-r--r-- | src/views.h | 457 | ||||
-rw-r--r-- | src/xact.cc | 197 | ||||
-rw-r--r-- | src/xact.h | 63 | ||||
-rw-r--r-- | src/xml.cc | 3 | ||||
-rw-r--r-- | src/xml.h | 2 |
131 files changed, 7854 insertions, 3811 deletions
diff --git a/src/account.cc b/src/account.cc index e201be64..206e2350 100644 --- a/src/account.cc +++ b/src/account.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -89,9 +89,13 @@ account_t * account_t::find_account(const string& acct_name, if (has_flags(ACCOUNT_GENERATED)) account->add_flags(ACCOUNT_GENERATED); - std::pair<accounts_map::iterator, bool> result - = accounts.insert(accounts_map::value_type(first, account)); +#if defined(DEBUG_ON) + std::pair<accounts_map::iterator, bool> result = +#endif + accounts.insert(accounts_map::value_type(first, account)); +#if defined(DEBUG_ON) assert(result.second); +#endif } else { account = (*i).second; } @@ -137,7 +141,10 @@ void account_t::add_post(post_t * post) bool account_t::remove_post(post_t * post) { - assert(! posts.empty()); + // It's possible that 'post' wasn't yet in this account, but try to + // remove it anyway. This can happen if there is an error during + // parsing, when the posting knows what it's account is, but + // xact_t::finalize has not yet added that posting to the account. posts.remove(post); post->account = NULL; return true; @@ -243,6 +250,10 @@ namespace { return long(account.depth); } + value_t get_note(account_t& account) { + return account.note ? string_value(*account.note) : NULL_VALUE; + } + value_t ignore(account_t&) { return false; } @@ -279,6 +290,26 @@ namespace { return account.self_details().latest_cleared_post; } + value_t get_earliest(account_t& account) + { + return account.self_details().earliest_post; + } + value_t get_earliest_checkin(account_t& account) + { + return (! account.self_details().earliest_checkin.is_not_a_date_time() ? + value_t(account.self_details().earliest_checkin) : NULL_VALUE); + } + + value_t get_latest(account_t& account) + { + return account.self_details().latest_post; + } + value_t get_latest_checkout(account_t& account) + { + return (! account.self_details().latest_checkout.is_not_a_date_time() ? + value_t(account.self_details().latest_checkout) : NULL_VALUE); + } + template <value_t (*Func)(account_t&)> value_t get_wrapper(call_scope_t& args) { return (*Func)(args.context<account_t>()); @@ -351,6 +382,13 @@ expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(get_wrapper<&get_depth_spacer>); break; + case 'e': + if (fn_name == "earliest") + return WRAP_FUNCTOR(get_wrapper<&get_earliest>); + else if (fn_name == "earliest_checkin") + return WRAP_FUNCTOR(get_wrapper<&get_earliest_checkin>); + break; + case 'i': if (fn_name == "is_account") return WRAP_FUNCTOR(get_wrapper<&get_true>); @@ -359,15 +397,21 @@ expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind, break; case 'l': - if (fn_name == "latest_cleared") - return WRAP_FUNCTOR(get_wrapper<&get_latest_cleared>); - else if (fn_name[1] == '\0') + if (fn_name[1] == '\0') return WRAP_FUNCTOR(get_wrapper<&get_depth>); + else if (fn_name == "latest_cleared") + return WRAP_FUNCTOR(get_wrapper<&get_latest_cleared>); + else if (fn_name == "latest") + return WRAP_FUNCTOR(get_wrapper<&get_latest>); + else if (fn_name == "latest_checkout") + return WRAP_FUNCTOR(get_wrapper<&get_latest_checkout>); break; case 'n': if (fn_name[1] == '\0') return WRAP_FUNCTOR(get_wrapper<&get_subcount>); + else if (fn_name == "note") + return WRAP_FUNCTOR(get_wrapper<&get_note>); break; case 'p': @@ -613,6 +657,14 @@ void account_t::xdata_t::details_t::update(post_t& post, if (! is_valid(latest_post) || post.date() > latest_post) latest_post = post.date(); + if (post.checkin && (earliest_checkin.is_not_a_date_time() || + *post.checkin < earliest_checkin)) + earliest_checkin = *post.checkin; + + if (post.checkout && (latest_checkout.is_not_a_date_time() || + *post.checkout > latest_checkout)) + latest_checkout = *post.checkout; + if (post.state() == item_t::CLEARED) { posts_cleared_count++; diff --git a/src/account.h b/src/account.h index 7a632b35..c0e3e1f7 100644 --- a/src/account.h +++ b/src/account.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -67,15 +67,23 @@ public: unsigned short depth; accounts_map accounts; posts_list posts; + optional<expr_t> value_expr; mutable string _fullname; +#ifdef DOCUMENT_MODEL + mutable void * data; +#endif account_t(account_t * _parent = NULL, const string& _name = "", const optional<string>& _note = none) : supports_flags<>(), scope_t(), parent(_parent), name(_name), note(_note), - depth(static_cast<unsigned short>(parent ? parent->depth + 1 : 0)) { + depth(static_cast<unsigned short>(parent ? parent->depth + 1 : 0)) +#ifdef DOCUMENT_MODEL + , data(NULL) +#endif + { TRACE_CTOR(account_t, "account_t *, const string&, const string&"); } account_t(const account_t& other) @@ -84,10 +92,14 @@ public: name(other.name), note(other.note), depth(other.depth), - accounts(other.accounts) { + accounts(other.accounts) +#ifdef DOCUMENT_MODEL + , data(NULL) +#endif + { TRACE_CTOR(account_t, "copy"); } - ~account_t(); + virtual ~account_t(); virtual string description() { return string(_("account ")) + fullname(); @@ -169,6 +181,9 @@ public: date_t latest_post; date_t latest_cleared_post; + datetime_t earliest_checkin; + datetime_t latest_checkout; + std::set<path> filenames; std::set<string> accounts_referenced; std::set<string> payees_referenced; @@ -185,7 +200,26 @@ public: posts_cleared_count(0), posts_last_7_count(0), posts_last_30_count(0), - posts_this_month_count(0) {} + posts_this_month_count(0) { + TRACE_CTOR(account_t::xdata_t::details_t, ""); + } + // A copy copies nothing + details_t(const details_t&) + : calculated(false), + gathered(false), + + posts_count(0), + posts_virtuals_count(0), + posts_cleared_count(0), + posts_last_7_count(0), + posts_last_30_count(0), + posts_this_month_count(0) + { + TRACE_CTOR(account_t::xdata_t::details_t, "copy"); + } + ~details_t() throw() { + TRACE_DTOR(account_t::xdata_t::details_t); + } details_t& operator+=(const details_t& other); diff --git a/src/accum.cc b/src/accum.cc index 8f3d5185..3add051b 100644 --- a/src/accum.cc +++ b/src/accum.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -35,6 +35,9 @@ namespace ledger { +straccstream _accum; +std::ostringstream _accum_buffer; + std::streamsize straccbuf::xsputn(const char * s, std::streamsize num) { if (index == 0) { diff --git a/src/accum.h b/src/accum.h index 236a7714..628a6b36 100644 --- a/src/accum.h +++ b/src/accum.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -51,7 +51,12 @@ protected: std::string::size_type index; public: - straccbuf() : index(0) {} + straccbuf() : index(0) { + TRACE_CTOR(straccbuf, ""); + } + ~straccbuf() throw() { + TRACE_DTOR(straccbuf); + } protected: virtual std::streamsize xsputn(const char * s, std::streamsize num); @@ -66,8 +71,12 @@ protected: public: straccstream() : std::ostream(0) { + TRACE_CTOR(straccstream, ""); rdbuf(&buf); } + ~straccstream() throw() { + TRACE_DTOR(straccstream); + } void clear() { std::ostream::clear(); @@ -83,6 +92,20 @@ public: #define ACCUM(obj) (static_cast<const straccstream&>(obj).str()) +extern straccstream _accum; +extern std::ostringstream _accum_buffer; + +inline string str_helper_func() { + string buf = _accum_buffer.str(); + _accum_buffer.clear(); + _accum_buffer.str(""); + return buf; +} + +#define STR(msg) \ + ((_accum_buffer << ACCUM(_accum << msg)), \ + _accum.clear(), str_helper_func()) + } // namespace ledger #endif // _ACCUM_H diff --git a/src/amount.cc b/src/amount.cc index 85afc3d8..5e933215 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -63,16 +63,16 @@ struct amount_t::bigint_t : public supports_flags<> #define MP(bigint) ((bigint)->val) bigint_t() : prec(0), refc(1) { - TRACE_CTOR(bigint_t, ""); mpq_init(val); + TRACE_CTOR(bigint_t, ""); } bigint_t(const bigint_t& other) : supports_flags<>(static_cast<uint_least8_t> (other.flags() & ~BIGINT_BULK_ALLOC)), prec(other.prec), refc(1) { - TRACE_CTOR(bigint_t, "copy"); mpq_init(val); mpq_set(val, other.val); + TRACE_CTOR(bigint_t, "copy"); } ~bigint_t() { TRACE_DTOR(bigint_t); @@ -120,11 +120,13 @@ namespace { { char * buf = NULL; try { +#if defined(DEBUG_ON) IF_DEBUG("amount.convert") { char * tbuf = mpq_get_str(NULL, 10, quant); DEBUG("amount.convert", "Rational to convert = " << tbuf); std::free(tbuf); } +#endif // Convert the rational number to a floating-point, extending the // floating-point to a large enough size to get a precise answer. @@ -347,24 +349,24 @@ void amount_t::_release() amount_t::amount_t(const double val) : commodity_(NULL) { - TRACE_CTOR(amount_t, "const double"); quantity = new bigint_t; mpq_set_d(MP(quantity), val); quantity->prec = extend_by_digits; // an approximation + TRACE_CTOR(amount_t, "const double"); } amount_t::amount_t(const unsigned long val) : commodity_(NULL) { - TRACE_CTOR(amount_t, "const unsigned long"); quantity = new bigint_t; mpq_set_ui(MP(quantity), val, 1); + TRACE_CTOR(amount_t, "const unsigned long"); } amount_t::amount_t(const long val) : commodity_(NULL) { - TRACE_CTOR(amount_t, "const long"); quantity = new bigint_t; mpq_set_si(MP(quantity), val, 1); + TRACE_CTOR(amount_t, "const long"); } @@ -393,11 +395,11 @@ int amount_t::compare(const amount_t& amt) const throw_(amount_error, _("Cannot compare two uninitialized amounts")); } - if (has_commodity() && amt.has_commodity() && - commodity() != amt.commodity()) + if (has_commodity() && amt.has_commodity() && commodity() != amt.commodity()) { throw_(amount_error, - _("Cannot compare amounts with different commodities: %1 and %2") - << commodity().symbol() << amt.commodity().symbol()); + _("Cannot compare amounts with different commodities: '%1' and '%2'") + << commodity() << amt.commodity()); + } return mpq_cmp(MP(quantity), MP(amt.quantity)); } @@ -428,12 +430,11 @@ amount_t& amount_t::operator+=(const amount_t& amt) throw_(amount_error, _("Cannot add two uninitialized amounts")); } - if (has_commodity() && amt.has_commodity() && - commodity() != amt.commodity()) + if (has_commodity() && amt.has_commodity() && commodity() != amt.commodity()) { throw_(amount_error, - _("Adding amounts with different commodities: %1 != %2") - << (has_commodity() ? commodity().symbol() : _("NONE")) - << (amt.has_commodity() ? amt.commodity().symbol() : _("NONE"))); + _("Adding amounts with different commodities: '%1' != '%2'") + << commodity() << amt.commodity()); + } _dup(); @@ -459,12 +460,11 @@ amount_t& amount_t::operator-=(const amount_t& amt) throw_(amount_error, _("Cannot subtract two uninitialized amounts")); } - if (has_commodity() && amt.has_commodity() && - commodity() != amt.commodity()) + if (has_commodity() && amt.has_commodity() && commodity() != amt.commodity()) { throw_(amount_error, - _("Subtracting amounts with different commodities: %1 != %2") - << (has_commodity() ? commodity().symbol() : _("NONE")) - << (amt.has_commodity() ? amt.commodity().symbol() : _("NONE"))); + _("Subtracting amounts with different commodities: '%1' != '%2'") + << commodity() << amt.commodity()); + } _dup(); @@ -605,16 +605,13 @@ void amount_t::in_place_negate() } } -amount_t amount_t::inverted() const +void amount_t::in_place_invert() { if (! quantity) throw_(amount_error, _("Cannot invert an uninitialized amount")); - amount_t t(*this); - t._dup(); - mpq_inv(MP(t.quantity), MP(t.quantity)); - - return t; + _dup(); + mpq_inv(MP(quantity), MP(quantity)); } void amount_t::in_place_round() @@ -729,24 +726,24 @@ void amount_t::in_place_unreduce() } optional<amount_t> -amount_t::value(const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of) const +amount_t::value(const datetime_t& moment, + const commodity_t * in_terms_of) const { if (quantity) { #if defined(DEBUG_ON) - DEBUG("commodity.prices.find", + DEBUG("commodity.price.find", "amount_t::value of " << commodity().symbol()); - if (moment) - DEBUG("commodity.prices.find", - "amount_t::value: moment = " << *moment); + if (! moment.is_not_a_date_time()) + DEBUG("commodity.price.find", + "amount_t::value: moment = " << moment); if (in_terms_of) - DEBUG("commodity.prices.find", + DEBUG("commodity.price.find", "amount_t::value: in_terms_of = " << in_terms_of->symbol()); #endif if (has_commodity() && (in_terms_of || ! commodity().has_flags(COMMODITY_PRIMARY))) { optional<price_point_t> point; - optional<commodity_t&> comm(in_terms_of); + const commodity_t * comm(in_terms_of); if (has_annotation() && annotation().price) { if (annotation().has_flags(ANNOTATION_PRICE_FIXATED)) { @@ -756,14 +753,14 @@ amount_t::value(const optional<datetime_t>& moment, "amount_t::value: fixated price = " << point->price); } else if (! comm) { - comm = annotation().price->commodity(); + comm = annotation().price->commodity_ptr(); } } - if (! point) { - if (comm && commodity().referent() == comm->referent()) - return *this; + if (comm && commodity().referent() == comm->referent()) + return with_commodity(comm->referent()); + if (! point) { point = commodity().find_price(comm, moment); // Whether a price was found or not, check whether we should attempt @@ -788,7 +785,7 @@ amount_t::value(const optional<datetime_t>& moment, return none; } -amount_t amount_t::price() const +optional<amount_t> amount_t::price() const { if (has_annotation() && annotation().price) { amount_t tmp(*annotation().price); @@ -796,7 +793,7 @@ amount_t amount_t::price() const DEBUG("amount.price", "Returning price of " << *this << " = " << tmp); return tmp; } - return *this; + return none; } @@ -868,10 +865,10 @@ bool amount_t::fits_in_long() const return mpfr_fits_slong_p(tempf, GMP_RNDN); } -commodity_t& amount_t::commodity() const +commodity_t * amount_t::commodity_ptr() const { - return (has_commodity() ? - *commodity_ : *commodity_pool_t::current_pool->null_commodity); + return (commodity_ ? + commodity_ : commodity_pool_t::current_pool->null_commodity); } bool amount_t::has_commodity() const @@ -1030,12 +1027,12 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) } // Allocate memory for the amount's quantity value. We have to - // monitor the allocation in an auto_ptr because this function gets + // monitor the allocation in a unique_ptr because this function gets // called sometimes from amount_t's constructor; and if there is an // exeception thrown by any of the function calls after this point, // the destructor will never be called and the memory never freed. - std::auto_ptr<bigint_t> new_quantity; + unique_ptr<bigint_t> new_quantity; if (quantity) { if (quantity->refc > 1) @@ -1061,10 +1058,6 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (! commodity_) commodity_ = commodity_pool_t::current_pool->create(symbol); assert(commodity_); - - if (details) - commodity_ = - commodity_pool_t::current_pool->find_or_create(*commodity_, details); } // Quickly scan through and verify the correctness of the amount's use of @@ -1200,6 +1193,14 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (! flags.has_flags(PARSE_NO_REDUCE)) in_place_reduce(); // will not throw an exception + if (commodity_ && details) { + if (details.has_flags(ANNOTATION_PRICE_NOT_PER_UNIT)) { + assert(details.price); + *details.price /= this->abs(); + } + set_commodity(*commodity_pool_t::current_pool->find_or_create(*commodity_, details)); + } + VERIFY(valid()); return true; diff --git a/src/amount.h b/src/amount.h index f7e877a7..cd77a79a 100644 --- a/src/amount.h +++ b/src/amount.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -155,17 +155,17 @@ public: commodity_t::null_commodity. The number may be of infinite precision. */ explicit amount_t(const string& val) : quantity(NULL) { - TRACE_CTOR(amount_t, "const string&"); parse(val); + TRACE_CTOR(amount_t, "const string&"); } /** Parse a pointer to a C string as an (optionally commoditized) amount. If no commodity is present, the resulting commodity is \c commodity_t::null_commodity. The number may be of infinite precision. */ explicit amount_t(const char * val) : quantity(NULL) { - TRACE_CTOR(amount_t, "const char *"); assert(val); parse(val); + TRACE_CTOR(amount_t, "const char *"); } /*@}*/ @@ -195,21 +195,21 @@ public: same memory used by the original via reference counting. The \c amount_t::bigint_t class in amount.cc maintains the reference. */ amount_t(const amount_t& amt) : quantity(NULL) { - TRACE_CTOR(amount_t, "copy"); if (amt.quantity) _copy(amt); else commodity_ = NULL; + TRACE_CTOR(amount_t, "copy"); } /** Copy an amount object, applying the given commodity annotation details afterward. This is equivalent to doing a normal copy (@see amount_t(const amount_t&)) and then calling amount_t::annotate(). */ amount_t(const amount_t& amt, const annotation_t& details) : quantity(NULL) { - TRACE_CTOR(amount_t, "const amount_t&, const annotation_t&"); assert(amt.quantity); _copy(amt); annotate(details); + TRACE_CTOR(amount_t, "const amount_t&, const annotation_t&"); } /** Assign an amount object. This is like copying if the amount was null beforehand, otherwise the previous value's reference is must @@ -327,7 +327,12 @@ public: return *this; } - amount_t inverted() const; + amount_t inverted() const { + amount_t temp(*this); + temp.in_place_invert(); + return temp; + } + void in_place_invert(); /** Yields an amount whose display precision when output is truncated to the display precision of its commodity. This is normally the @@ -399,10 +404,10 @@ public: $100.00. */ optional<amount_t> - value(const optional<datetime_t>& moment = none, - const optional<commodity_t&>& in_terms_of = none) const; + value(const datetime_t& moment = datetime_t(), + const commodity_t * in_terms_of = NULL) const; - amount_t price() const; + optional<amount_t> price() const; /*@}*/ @@ -528,7 +533,10 @@ public: number() returns a commodity-less version of an amount. This is useful for accessing just the numeric portion of an amount. */ - commodity_t& commodity() const; + commodity_t * commodity_ptr() const; + commodity_t& commodity() const { + return *commodity_ptr(); + } bool has_commodity() const; void set_commodity(commodity_t& comm) { @@ -536,6 +544,15 @@ public: *this = 0L; commodity_ = &comm; } + amount_t with_commodity(const commodity_t& comm) const { + if (commodity_ == &comm) { + return *this; + } else { + amount_t tmp(*this); + tmp.set_commodity(const_cast<commodity_t&>(comm)); + return tmp; + } + } void clear_commodity() { commodity_ = NULL; } diff --git a/src/annotate.cc b/src/annotate.cc index 8ba46f4f..41e7a752 100644 --- a/src/annotate.cc +++ b/src/annotate.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -33,11 +33,51 @@ #include "amount.h" #include "commodity.h" +#include "expr.h" #include "annotate.h" #include "pool.h" namespace ledger { +bool annotation_t::operator<(const annotation_t& rhs) const +{ + if (! price && rhs.price) return true; + if (price && ! rhs.price) return false; + if (! date && rhs.date) return true; + if (date && ! rhs.date) return false; + if (! tag && rhs.tag) return true; + if (tag && ! rhs.tag) return false; + + if (! value_expr && rhs.value_expr) return true; + if (value_expr && ! rhs.value_expr) return false; + + if (price) { + if (price->commodity().symbol() < rhs.price->commodity().symbol()) + return true; + if (price->commodity().symbol() > rhs.price->commodity().symbol()) + return false; + + if (*price < *rhs.price) return true; + if (*price > *rhs.price) return false; + } + if (date) { + if (*date < *rhs.date) return true; + if (*date > *rhs.date) return false; + } + if (tag) { + if (*tag < *rhs.tag) return true; + if (*tag > *rhs.tag) return false; + } + if (value_expr) { + DEBUG("annotate.less", "Comparing (" << value_expr->text() + << ") < (" << rhs.value_expr->text()); + if (value_expr->text() < rhs.value_expr->text()) return true; + //if (value_expr->text() > rhs.value_expr->text()) return false; + } + + return false; +} + void annotation_t::parse(std::istream& in) { do { @@ -52,6 +92,12 @@ void annotation_t::parse(std::istream& in) throw_(amount_error, _("Commodity specifies more than one price")); in.get(c); + c = static_cast<char>(in.peek()); + if (c == '{') { + in.get(c); + add_flags(ANNOTATION_PRICE_NOT_PER_UNIT); + } + c = peek_next_nonws(in); if (c == '=') { in.get(c); @@ -59,10 +105,18 @@ void annotation_t::parse(std::istream& in) } READ_INTO(in, buf, 255, c, c != '}'); - if (c == '}') + if (c == '}') { in.get(c); - else - throw_(amount_error, _("Commodity price lacks closing brace")); + if (has_flags(ANNOTATION_PRICE_NOT_PER_UNIT)) { + c = static_cast<char>(in.peek()); + if (c != '}') + throw_(amount_error, _("Commodity lot price lacks double closing brace")); + else + in.get(c); + } + } else { + throw_(amount_error, _("Commodity lot price lacks closing brace")); + } amount_t temp; temp.parse(buf, PARSE_NO_MIGRATE); @@ -84,17 +138,46 @@ void annotation_t::parse(std::istream& in) date = parse_date(buf); } else if (c == '(') { - if (tag) - throw_(amount_error, _("Commodity specifies more than one tag")); - in.get(c); - READ_INTO(in, buf, 255, c, c != ')'); - if (c == ')') - in.get(c); - else - throw_(amount_error, _("Commodity tag lacks closing parenthesis")); + c = static_cast<char>(in.peek()); + if (c == '@') { + in.clear(); + in.seekg(pos, std::ios::beg); + break; + } + else if (c == '(') { + if (value_expr) + throw_(amount_error, + _("Commodity specifies more than one valuation expresion")); - tag = buf; + in.get(c); + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') { + in.get(c); + c = static_cast<char>(in.peek()); + if (c == ')') + in.get(c); + else + throw_(amount_error, + _("Commodity valuation expression lacks closing parentheses")); + } else { + throw_(amount_error, + _("Commodity valuation expression lacks closing parentheses")); + } + + value_expr = expr_t(buf); + } else { + if (tag) + throw_(amount_error, _("Commodity specifies more than one tag")); + + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') + in.get(c); + else + throw_(amount_error, _("Commodity tag lacks closing parenthesis")); + + tag = buf; + } } else { in.clear(); @@ -128,6 +211,9 @@ void annotation_t::print(std::ostream& out, bool keep_base, if (tag && (! no_computed_annotations || ! has_flags(ANNOTATION_TAG_CALCULATED))) out << " (" << *tag << ')'; + + if (value_expr && ! has_flags(ANNOTATION_VALUE_EXPR_CALCULATED)) + out << " ((" << *value_expr << "))"; } bool keep_details_t::keep_all(const commodity_t& comm) const @@ -157,6 +243,54 @@ bool annotated_commodity_t::operator==(const commodity_t& comm) const return true; } +optional<price_point_t> +annotated_commodity_t::find_price(const commodity_t * commodity, + const datetime_t& moment, + const datetime_t& oldest) const +{ + DEBUG("commodity.price.find", + "annotated_commodity_t::find_price(" << symbol() << ")"); + + datetime_t when; + if (! moment.is_not_a_date_time()) + when = moment; + else if (epoch) + when = *epoch; + else + when = CURRENT_TIME(); + + DEBUG("commodity.price.find", "reference time: " << when); + + const commodity_t * target = NULL; + if (commodity) + target = commodity; + + if (details.price) { + DEBUG("commodity.price.find", "price annotation: " << *details.price); + + if (details.has_flags(ANNOTATION_PRICE_FIXATED)) { + DEBUG("commodity.price.find", + "amount_t::value: fixated price = " << *details.price); + return price_point_t(when, *details.price); + } + else if (! target) { + DEBUG("commodity.price.find", "setting target commodity from price"); + target = details.price->commodity_ptr(); + } + } + +#if defined(DEBUG_ON) + if (target) + DEBUG("commodity.price.find", "target commodity: " << target->symbol()); +#endif + + if (details.value_expr) + return find_price_from_expr(const_cast<expr_t&>(*details.value_expr), + commodity, when); + + return commodity_t::find_price(target, when, oldest); +} + commodity_t& annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep) { @@ -192,8 +326,7 @@ annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep) if ((keep_price && details.price) || (keep_date && details.date) || - (keep_tag && details.tag)) - { + (keep_tag && details.tag)) { new_comm = pool().find_or_create (referent(), annotation_t(keep_price ? details.price : none, keep_date ? details.date : none, diff --git a/src/annotate.h b/src/annotate.h index b590ca45..37ee0685 100644 --- a/src/annotate.h +++ b/src/annotate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -46,29 +46,39 @@ #ifndef _ANNOTATE_H #define _ANNOTATE_H +#include "expr.h" + namespace ledger { struct annotation_t : public supports_flags<>, public equality_comparable<annotation_t> { -#define ANNOTATION_PRICE_CALCULATED 0x01 -#define ANNOTATION_PRICE_FIXATED 0x02 -#define ANNOTATION_DATE_CALCULATED 0x04 -#define ANNOTATION_TAG_CALCULATED 0x08 +#define ANNOTATION_PRICE_CALCULATED 0x01 +#define ANNOTATION_PRICE_FIXATED 0x02 +#define ANNOTATION_PRICE_NOT_PER_UNIT 0x04 +#define ANNOTATION_DATE_CALCULATED 0x08 +#define ANNOTATION_TAG_CALCULATED 0x10 +#define ANNOTATION_VALUE_EXPR_CALCULATED 0x20 optional<amount_t> price; optional<date_t> date; optional<string> tag; - - explicit annotation_t(const optional<amount_t>& _price = none, - const optional<date_t>& _date = none, - const optional<string>& _tag = none) - : supports_flags<>(), price(_price), date(_date), tag(_tag) { - TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string"); + optional<expr_t> value_expr; + + explicit annotation_t(const optional<amount_t>& _price = none, + const optional<date_t>& _date = none, + const optional<string>& _tag = none, + const optional<expr_t>& _value_expr = none) + : supports_flags<>(), price(_price), date(_date), tag(_tag), + value_expr(_value_expr) { + TRACE_CTOR(annotation_t, + "optional<amount_t> + date_t + string + expr_t"); } annotation_t(const annotation_t& other) : supports_flags<>(other.flags()), - price(other.price), date(other.date), tag(other.tag) { + price(other.price), date(other.date), tag(other.tag), + value_expr(other.value_expr) + { TRACE_CTOR(annotation_t, "copy"); } ~annotation_t() { @@ -76,17 +86,20 @@ struct annotation_t : public supports_flags<>, } operator bool() const { - return price || date || tag; + return price || date || tag || value_expr; } + bool operator<(const annotation_t& rhs) const; bool operator==(const annotation_t& rhs) const { return (price == rhs.price && - date == rhs.date && - tag == rhs.tag); + date == rhs.date && + tag == rhs.tag && + (value_expr && rhs.value_expr ? + value_expr->text() == rhs.value_expr->text() : + value_expr == rhs.value_expr)); } void parse(std::istream& in); - void print(std::ostream& out, bool keep_base = false, bool no_computed_annotations = false) const; @@ -132,6 +145,12 @@ inline void to_xml(std::ostream& out, const annotation_t& details) push_xml y(out, "tag"); out << y.guard(*details.tag); } + + if (details.value_expr) + { + push_xml y(out, "value-expr"); + out << y.guard(details.value_expr->text()); + } } struct keep_details_t @@ -207,8 +226,9 @@ protected: explicit annotated_commodity_t(commodity_t * _ptr, const annotation_t& _details) : commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) { - TRACE_CTOR(annotated_commodity_t, "commodity_t *, annotation_t"); annotated = true; + qualified_symbol = _ptr->qualified_symbol; + TRACE_CTOR(annotated_commodity_t, "commodity_t *, annotation_t"); } public: @@ -230,7 +250,31 @@ public: return *ptr; } + virtual optional<expr_t> value_expr() const { + if (details.value_expr) + return details.value_expr; + return commodity_t::value_expr(); + } + + optional<price_point_t> + virtual find_price(const commodity_t * commodity = NULL, + const datetime_t& moment = datetime_t(), + const datetime_t& oldest = datetime_t()) const; + virtual commodity_t& strip_annotations(const keep_details_t& what_to_keep); + + virtual void print(std::ostream& out, bool elide_quotes = false, + bool print_annotations = false) const { + if (print_annotations) { + std::ostringstream buf; + commodity_t::print(buf, elide_quotes); + write_annotations(buf); + out << buf.str(); + } else { + commodity_t::print(out, elide_quotes); + } + } + virtual void write_annotations(std::ostream& out, bool no_computed_annotations = false) const; diff --git a/src/archive.cc b/src/archive.cc index 28760512..72ec0419 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/archive.h b/src/archive.h index 1ebf3496..4ce5e0e7 100644 --- a/src/archive.h +++ b/src/archive.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/balance.cc b/src/balance.cc index 7ce9d994..ded3d38a 100644 --- a/src/balance.cc +++ b/src/balance.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -41,23 +41,23 @@ namespace ledger { balance_t::balance_t(const double val) { - TRACE_CTOR(balance_t, "const double"); amounts.insert (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); + TRACE_CTOR(balance_t, "const double"); } balance_t::balance_t(const unsigned long val) { - TRACE_CTOR(balance_t, "const unsigned long"); amounts.insert (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); + TRACE_CTOR(balance_t, "const unsigned long"); } balance_t::balance_t(const long val) { - TRACE_CTOR(balance_t, "const long"); amounts.insert (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); + TRACE_CTOR(balance_t, "const long"); } balance_t& balance_t::operator+=(const balance_t& bal) @@ -185,8 +185,8 @@ balance_t& balance_t::operator/=(const amount_t& amt) } optional<balance_t> -balance_t::value(const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of) const +balance_t::value(const datetime_t& moment, + const commodity_t * in_terms_of) const { balance_t temp; bool resolved = false; @@ -202,16 +202,6 @@ balance_t::value(const optional<datetime_t>& moment, return resolved ? temp : optional<balance_t>(); } -balance_t balance_t::price() const -{ - balance_t temp; - - foreach (const amounts_map::value_type& pair, amounts) - temp += pair.second.price(); - - return temp; -} - optional<amount_t> balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const { @@ -250,51 +240,100 @@ balance_t::strip_annotations(const keep_details_t& what_to_keep) const return temp; } -void balance_t::print(std::ostream& out, - const int first_width, - const int latter_width, - const uint_least8_t flags) const +void balance_t::map_sorted_amounts(function<void(const amount_t&)> fn) const { - bool first = true; - int lwidth = latter_width; + if (! amounts.empty()) { + if (amounts.size() == 1) { + const amount_t& amount((*amounts.begin()).second); + if (amount) + fn(amount); + } + else { + typedef std::vector<const amount_t *> amounts_array; + amounts_array sorted; - if (lwidth == -1) - lwidth = first_width; + foreach (const amounts_map::value_type& pair, amounts) + if (pair.second) + sorted.push_back(&pair.second); - typedef std::vector<const amount_t *> amounts_array; - amounts_array sorted; + std::stable_sort(sorted.begin(), sorted.end(), + commodity_t::compare_by_commodity()); - foreach (const amounts_map::value_type& pair, amounts) - if (pair.second) - sorted.push_back(&pair.second); + foreach (const amount_t * amount, sorted) + fn(*amount); + } + } +} - std::stable_sort(sorted.begin(), sorted.end(), - commodity_t::compare_by_commodity()); +namespace { + struct print_amount_from_balance + { + std::ostream& out; + bool& first; + int fwidth; + int lwidth; + uint_least8_t flags; + + explicit print_amount_from_balance(std::ostream& _out, + bool& _first, + int _fwidth, int _lwidth, + uint_least8_t _flags) + : out(_out), first(_first), fwidth(_fwidth), lwidth(_lwidth), + flags(_flags) { + TRACE_CTOR(print_amount_from_balance, + "ostream&, int, int, uint_least8_t"); + } + print_amount_from_balance(const print_amount_from_balance& other) + : out(other.out), first(other.first), fwidth(other.fwidth), + lwidth(other.lwidth), flags(other.flags) { + TRACE_CTOR(print_amount_from_balance, "copy"); + } + ~print_amount_from_balance() throw() { + TRACE_DTOR(print_amount_from_balance); + } - foreach (const amount_t * amount, sorted) { - int width; - if (! first) { - out << std::endl; - width = lwidth; - } else { - first = false; - width = first_width; + void operator()(const amount_t& amount) { + int width; + if (! first) { + out << std::endl; + width = lwidth; + } else { + first = false; + width = fwidth; + } + + std::ostringstream buf; + amount.print(buf, flags); + + justify(out, buf.str(), width, + flags & AMOUNT_PRINT_RIGHT_JUSTIFY, + flags & AMOUNT_PRINT_COLORIZE && amount.sign() < 0); } - std::ostringstream buf; - amount->print(buf, flags); - justify(out, buf.str(), width, flags & AMOUNT_PRINT_RIGHT_JUSTIFY, - flags & AMOUNT_PRINT_COLORIZE && amount->sign() < 0); - } + void close() { + out.width(fwidth); + if (flags & AMOUNT_PRINT_RIGHT_JUSTIFY) + out << std::right; + else + out << std::left; + out << 0; + } + }; +} - if (first) { - out.width(first_width); - if (flags & AMOUNT_PRINT_RIGHT_JUSTIFY) - out << std::right; - else - out << std::left; - out << 0; - } +void balance_t::print(std::ostream& out, + const int first_width, + const int latter_width, + const uint_least8_t flags) const +{ + bool first = true; + print_amount_from_balance + amount_printer(out, first, first_width, + latter_width == 1 ? first_width : latter_width, flags); + map_sorted_amounts(amount_printer); + + if (first) + amount_printer.close(); } void to_xml(std::ostream& out, const balance_t& bal) diff --git a/src/balance.h b/src/balance.h index ac22f3e7..704b4072 100644 --- a/src/balance.h +++ b/src/balance.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -108,26 +108,26 @@ public: TRACE_CTOR(balance_t, ""); } balance_t(const amount_t& amt) { - TRACE_CTOR(balance_t, "const amount_t&"); if (amt.is_null()) throw_(balance_error, _("Cannot initialize a balance from an uninitialized amount")); if (! amt.is_realzero()) amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); + TRACE_CTOR(balance_t, "const amount_t&"); } balance_t(const double val); balance_t(const unsigned long val); balance_t(const long val); explicit balance_t(const string& val) { - TRACE_CTOR(balance_t, "const string&"); amount_t temp(val); amounts.insert(amounts_map::value_type(&temp.commodity(), temp)); + TRACE_CTOR(balance_t, "const string&"); } explicit balance_t(const char * val) { - TRACE_CTOR(balance_t, "const char *"); amount_t temp(val); amounts.insert(amounts_map::value_type(&temp.commodity(), temp)); + TRACE_CTOR(balance_t, "const char *"); } /** @@ -384,10 +384,8 @@ public: } optional<balance_t> - value(const optional<datetime_t>& moment = none, - const optional<commodity_t&>& in_terms_of = none) const; - - balance_t price() const; + value(const datetime_t& moment = datetime_t(), + const commodity_t * in_terms_of = NULL) const; /** * Truth tests. An balance may be truth test in two ways: @@ -509,6 +507,14 @@ public: balance_t strip_annotations(const keep_details_t& what_to_keep) const; /** + * Iteration primitives. `map_sorted_amounts' allows one to visit + * each amount in balance in the proper order for displaying to the + * user. Mostly used by `print' and other routinse where the sort + * order of the amounts' commodities is significant. + */ + void map_sorted_amounts(function<void(const amount_t&)> fn) const; + + /** * Printing methods. A balance may be output to a stream using the * `print' method. There is also a global operator<< defined which * simply calls print for a balance on the given stream. There is diff --git a/src/chain.cc b/src/chain.cc index 450e3758..52d52f14 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -88,10 +88,9 @@ post_handler_ptr chain_pre_post_handlers(post_handler_ptr base_handler, predicate_t(report.HANDLER(forecast_while_).str(), report.what_to_keep()), report, - report.HANDLED(forecast_years_) ? - static_cast<std::size_t> - (report.HANDLER(forecast_years_).value.to_long()) : - 5UL); + (report.HANDLED(forecast_years_) ? + lexical_cast<std::size_t> + (report.HANDLER(forecast_years_).value) : 5UL)); forecast_handler->add_period_xacts(report.session.journal->period_xacts); handler.reset(forecast_handler); @@ -115,10 +114,13 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler, predicate_t only_predicate; display_filter_posts * display_filter = NULL; - assert(report.HANDLED(amount_)); expr_t& expr(report.HANDLER(amount_).expr); expr.set_context(&report); + report.HANDLER(total_).expr.set_context(&report); + report.HANDLER(display_amount_).expr.set_context(&report); + report.HANDLER(display_total_).expr.set_context(&report); + if (! for_accounts_report) { // Make sure only forecast postings which match are allowed through if (report.HANDLED(forecast_while_)) { @@ -134,9 +136,9 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler, handler.reset (new truncate_xacts(handler, report.HANDLED(head_) ? - report.HANDLER(head_).value.to_int() : 0, + lexical_cast<int>(report.HANDLER(head_).value) : 0, report.HANDLED(tail_) ? - report.HANDLER(tail_).value.to_int() : 0)); + lexical_cast<int>(report.HANDLER(tail_).value) : 0)); // display_filter_posts adds virtual posts to the list to account // for changes in value of commodities, which otherwise would affect @@ -205,7 +207,7 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler, // day_of_week_posts is like period_posts, except that it reports // all the posts that fall on each subsequent day of the week. if (report.HANDLED(equity)) - handler.reset(new posts_as_equity(handler, expr)); + handler.reset(new posts_as_equity(handler, report, expr)); else if (report.HANDLED(subtotal)) handler.reset(new subtotal_posts(handler, expr)); } @@ -217,13 +219,11 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler, // interval_posts groups posts together based on a time period, such as // weekly or monthly. - if (report.HANDLED(period_)) { + if (report.HANDLED(period_)) handler.reset(new interval_posts(handler, expr, report.HANDLER(period_).str(), report.HANDLED(exact), report.HANDLED(empty))); - handler.reset(new sort_posts(handler, "date")); - } if (report.HANDLED(date_)) handler.reset(new transfer_details(handler, transfer_details::SET_DATE, diff --git a/src/chain.h b/src/chain.h index 7bd76712..15ae12ba 100644 --- a/src/chain.h +++ b/src/chain.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -50,8 +50,9 @@ class post_t; class account_t; template <typename T> -struct item_handler : public noncopyable +class item_handler : public noncopyable { +protected: shared_ptr<item_handler> handler; public: diff --git a/src/commodity.cc b/src/commodity.cc index 5fd54d11..a72d85c8 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -35,430 +35,162 @@ #include "commodity.h" #include "annotate.h" #include "pool.h" +#include "scope.h" namespace ledger { bool commodity_t::decimal_comma_by_default = false; -void commodity_t::history_t::add_price(commodity_t& source, - const datetime_t& date, - const amount_t& price, - const bool reflexive) +void commodity_t::add_price(const datetime_t& date, const amount_t& price, + const bool reflexive) { - DEBUG("commodity.prices.add", "add_price to " << source - << (reflexive ? " (secondary)" : " (primary)") - << " : " << date << ", " << price); - - history_map::iterator i = prices.find(date); - if (i != prices.end()) { - (*i).second = price; - } else { - std::pair<history_map::iterator, bool> result - = prices.insert(history_map::value_type(date, price)); - assert(result.second); - } - if (reflexive) { - amount_t inverse = price.inverted(); - inverse.set_commodity(const_cast<commodity_t&>(source)); - price.commodity().add_price(date, inverse, false); + DEBUG("history.find", "Marking " + << price.commodity().symbol() << " as a primary commodity"); + price.commodity().add_flags(COMMODITY_PRIMARY); } else { - DEBUG("commodity.prices.add", - "marking commodity " << source.symbol() << " as primary"); - source.add_flags(COMMODITY_PRIMARY); + DEBUG("history.find", "Marking " << symbol() << " as a primary commodity"); + add_flags(COMMODITY_PRIMARY); } -} -bool commodity_t::history_t::remove_price(const datetime_t& date) -{ - DEBUG("commodity.prices.add", "remove_price: " << date); + DEBUG("history.find", "Adding price: " << symbol() + << " for " << price << " on " << date); - history_map::size_type n = prices.erase(date); - if (n > 0) - return true; - return false; + pool().commodity_price_history.add_price(referent(), date, price); + + base->price_map.clear(); // a price was added, invalid the map } -void commodity_t::varied_history_t:: - add_price(commodity_t& source, - const datetime_t& date, - const amount_t& price, - const bool reflexive) +void commodity_t::remove_price(const datetime_t& date, commodity_t& commodity) { - optional<history_t&> hist = history(price.commodity()); - if (! hist) { - std::pair<history_by_commodity_map::iterator, bool> result - = histories.insert(history_by_commodity_map::value_type - (&price.commodity(), history_t())); - assert(result.second); - - hist = (*result.first).second; - } - assert(hist); + pool().commodity_price_history.remove_price(referent(), commodity, date); - hist->add_price(source, date, price, reflexive); + DEBUG("history.find", "Removing price: " << symbol() << " on " << date); + + base->price_map.clear(); // a price was added, invalid the map } -bool commodity_t::varied_history_t::remove_price(const datetime_t& date, - commodity_t& comm) +void commodity_t::map_prices(function<void(datetime_t, const amount_t&)> fn, + const datetime_t& moment, + const datetime_t& _oldest, + bool bidirectionally) { - DEBUG("commodity.prices.add", "varied_remove_price: " << date << ", " << comm); + datetime_t when; + if (! moment.is_not_a_date_time()) + when = moment; + else if (epoch) + when = *epoch; + else + when = CURRENT_TIME(); - if (optional<history_t&> hist = history(comm)) - return hist->remove_price(date); - return false; + pool().commodity_price_history.map_prices(fn, referent(), when, _oldest, + bidirectionally); } optional<price_point_t> -commodity_t::history_t::find_price(const optional<datetime_t>& moment, - const optional<datetime_t>& oldest -#if defined(DEBUG_ON) - , const int indent -#endif - ) const +commodity_t::find_price_from_expr(expr_t& expr, const commodity_t * commodity, + const datetime_t& moment) const { - price_point_t point; - bool found = false; - -#if defined(DEBUG_ON) -#define DEBUG_INDENT(cat, indent) \ - do { \ - if (SHOW_DEBUG(cat)) \ - for (int _i = 0; _i < indent; _i++) \ - ledger::_log_buffer << " "; \ - } while (false) -#else -#define DEBUG_INDENT(cat, indent) -#endif - #if defined(DEBUG_ON) - DEBUG_INDENT("commodity.prices.find", indent); - if (moment) - DEBUG("commodity.prices.find", "find price nearest before or on: " << *moment); - else - DEBUG("commodity.prices.find", "find any price"); - - if (oldest) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "but no older than: " << *oldest); + if (SHOW_DEBUG("commodity.price.find")) { + ledger::_log_buffer << "valuation expr: "; + expr.dump(ledger::_log_buffer); + DEBUG("commodity.price.find", ""); } #endif + value_t result(expr.calc(*scope_t::default_scope)); - if (prices.size() == 0) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "there are no prices in this history"); - return none; - } - - if (! moment) { - history_map::const_reverse_iterator r = prices.rbegin(); - point.when = (*r).first; - point.price = (*r).second; - found = true; + if (is_expr(result)) { + value_t call_args; - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "using most recent price"); - } else { - history_map::const_iterator i = prices.upper_bound(*moment); - if (i == prices.end()) { - history_map::const_reverse_iterator r = prices.rbegin(); - point.when = (*r).first; - point.price = (*r).second; - found = true; - - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "using last price"); - } else { - point.when = (*i).first; - if (*moment < point.when) { - if (i != prices.begin()) { - --i; - point.when = (*i).first; - point.price = (*i).second; - found = true; - } - } else { - point.price = (*i).second; - found = true; - } + call_args.push_back(string_value(base_symbol())); + call_args.push_back(moment); + if (commodity) + call_args.push_back(string_value(commodity->symbol())); - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "using found price"); - } + result = as_expr(result)->call(call_args, *scope_t::default_scope); } - if (! found) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "could not find a price"); - return none; - } - else if (moment && point.when > *moment) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "price is too young "); - return none; - } - else if (oldest && point.when < *oldest) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "price is too old "); - return none; - } - else { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", - "returning price: " << point.when << ", " << point.price); - return point; - } + return price_point_t(moment, result.to_amount()); } optional<price_point_t> -commodity_t::varied_history_t::find_price(const commodity_t& source, - const optional<commodity_t&>& commodity, - const optional<datetime_t>& moment, - const optional<datetime_t>& oldest -#if defined(DEBUG_ON) - , const int indent -#endif - ) const +commodity_t::find_price(const commodity_t * commodity, + const datetime_t& moment, + const datetime_t& oldest) const { - optional<price_point_t> point; - optional<datetime_t> limit = oldest; - -#if defined(VERIFY_ON) - if (commodity) { - VERIFY(source != *commodity); - VERIFY(! commodity->has_annotation()); - VERIFY(source.referent() != commodity->referent()); - } -#endif - -#if defined(DEBUG_ON) - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "varied_find_price for: " << source); + DEBUG("commodity.price.find", "commodity_t::find_price(" << symbol() << ")"); - DEBUG_INDENT("commodity.prices.find", indent); + const commodity_t * target = NULL; if (commodity) - DEBUG("commodity.prices.find", "looking for: commodity '" << *commodity << "'"); - else - DEBUG("commodity.prices.find", "looking for: any commodity"); - - if (moment) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "time index: " << *moment); - } - - if (oldest) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "only consider prices younger than: " << *oldest); - } -#endif - - // Either we couldn't find a history for the target commodity, or we - // couldn't find a price. In either case, search all histories known - // to this commodity for a price which we can calculate in terms of - // the goal commodity. - price_point_t best; - bool found = false; - - foreach (const history_by_commodity_map::value_type& hist, histories) { - commodity_t& comm(*hist.first); - if (comm == source) - continue; - - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "searching for price via commodity '" << comm << "'"); - - point = hist.second.find_price(moment, limit -#if defined(DEBUG_ON) - , indent + 2 -#endif - ); - assert(! point || point->price.commodity() == comm); - - if (point) { - optional<price_point_t> xlat; + target = commodity; + else if (pool().default_commodity) + target = &*pool().default_commodity; - if (commodity && comm != *commodity) { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "looking for translation price"); - - xlat = comm.find_price(commodity, moment, limit, true -#if defined(DEBUG_ON) - , indent + 2 -#endif - ); - if (xlat) { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "found translated price " - << xlat->price << " from " << xlat->when); - - point->price = xlat->price * point->price; - if (xlat->when < point->when) { - point->when = xlat->when; - - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "adjusting date of result back to " << point->when); - } - } else { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "saw no translated price there"); - continue; - } - } - - assert(! commodity || point->price.commodity() == *commodity); - - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "saw a price there: " << point->price << " from " << point->when); + if (target && this == target) + return none; - if (! limit || point->when > *limit) { - limit = point->when; - best = *point; - found = true; + base_t::memoized_price_entry entry(moment, oldest, + commodity ? commodity : NULL); - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "search limit adjusted to " << *limit); - } - } else { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "saw no price there"); + DEBUG("commodity.price.find", "looking for memoized args: " + << (! moment.is_not_a_date_time() ? format_datetime(moment) : "NONE") << ", " + << (! oldest.is_not_a_date_time() ? format_datetime(oldest) : "NONE") << ", " + << (commodity ? commodity->symbol() : "NONE")); + { + base_t::memoized_price_map::iterator i = base->price_map.find(entry); + if (i != base->price_map.end()) { + DEBUG("commodity.price.find", "found! returning: " + << ((*i).second ? (*i).second->price : amount_t(0L))); + return (*i).second; } } - if (found) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.download", - "found price " << best.price << " from " << best.when); - return best; - } - return none; -} - -optional<commodity_t::history_t&> -commodity_t::varied_history_t::history(const optional<commodity_t&>& commodity) -{ - commodity_t * comm = NULL; - if (! commodity) { - if (histories.size() > 1) - return none; - comm = (*histories.begin()).first; - } else { - comm = &(*commodity); + datetime_t when; + if (! moment.is_not_a_date_time()) + when = moment; + else if (epoch) + when = *epoch; + else + when = CURRENT_TIME(); + + if (base->value_expr) + return find_price_from_expr(*base->value_expr, commodity, when); + + optional<price_point_t> + point(target ? + pool().commodity_price_history.find_price(referent(), *target, + when, oldest) : + pool().commodity_price_history.find_price(referent(), when, oldest)); + + // Record this price point in the memoization map + if (base->price_map.size() > base_t::max_price_map_size) { + DEBUG("history.find", + "price map has grown too large, clearing it by half"); + for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++) + base->price_map.erase(base->price_map.begin()); } - history_by_commodity_map::iterator i = histories.find(comm); - if (i != histories.end()) - return (*i).second; - - return none; -} - -optional<price_point_t> -commodity_t::find_price(const optional<commodity_t&>& commodity, - const optional<datetime_t>& moment, - const optional<datetime_t>& oldest, - const bool nested -#if defined(DEBUG_ON) - , const int indent -#endif - ) const -{ - if (! has_flags(COMMODITY_WALKED) && base->varied_history) { - optional<base_t::time_and_commodity_t> pair; -#if defined(VERIFY_ON) - optional<price_point_t> checkpoint; - bool found = false; -#endif - - if (! nested) { - pair = base_t::time_and_commodity_t - (base_t::optional_time_pair_t(moment, oldest), - commodity ? &(*commodity) : NULL); - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "looking for memoized args: " - << (moment ? format_datetime(*moment) : "NONE") << ", " - << (oldest ? format_datetime(*oldest) : "NONE") << ", " - << (commodity ? commodity->symbol() : "NONE")); - - base_t::memoized_price_map::iterator i = base->price_map.find(*pair); - if (i != base->price_map.end()) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "found! returning: " - << ((*i).second ? (*i).second->price : amount_t(0L))); -#if defined(VERIFY_ON) - IF_VERIFY() { - found = true; - checkpoint = (*i).second; - } else -#endif // defined(VERIFY_ON) - return (*i).second; - } - } - - optional<price_point_t> point; - - const_cast<commodity_t&>(*this).add_flags(COMMODITY_WALKED); - try { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "manually finding price..."); + DEBUG("history.find", + "remembered: " << (point ? point->price : amount_t(0L))); + base->price_map.insert(base_t::memoized_price_map::value_type(entry, point)); - point = base->varied_history->find_price(*this, commodity, - moment, oldest -#if defined(DEBUG_ON) - , indent -#endif - ); - } - catch (...) { - const_cast<commodity_t&>(*this).drop_flags(COMMODITY_WALKED); - throw; - } - const_cast<commodity_t&>(*this).drop_flags(COMMODITY_WALKED); - -#if defined(VERIFY_ON) - if (DO_VERIFY() && found) { - VERIFY(checkpoint == point); - return checkpoint; - } -#endif // defined(VERIFY_ON) - - if (! nested && pair) { - if (base->price_map.size() > base_t::max_price_map_size) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", - "price map has grown too large, clearing it by half"); - - for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++) - base->price_map.erase(base->price_map.begin()); - } - - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", - "remembered: " << (point ? point->price : amount_t(0L))); - base->price_map.insert - (base_t::memoized_price_map::value_type(*pair, point)); - } - return point; - } - return none; + return point; } optional<price_point_t> commodity_t::check_for_updated_price(const optional<price_point_t>& point, - const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of) + const datetime_t& moment, + const commodity_t* in_terms_of) { if (pool().get_quotes && ! has_flags(COMMODITY_NOMARKET)) { bool exceeds_leeway = true; if (point) { time_duration_t::sec_type seconds_diff; - if (moment) { - seconds_diff = (*moment - point->when).total_seconds(); - DEBUG("commodity.download", "moment = " << *moment); + if (! moment.is_not_a_date_time()) { + seconds_diff = (moment - point->when).total_seconds(); + DEBUG("commodity.download", "moment = " << moment); DEBUG("commodity.download", "slip.moment = " << seconds_diff); } else { seconds_diff = (TRUE_CURRENT_TIME() - point->when).total_seconds(); @@ -474,10 +206,10 @@ commodity_t::check_for_updated_price(const optional<price_point_t>& point, DEBUG("commodity.download", "attempting to download a more current quote..."); if (optional<price_point_t> quote = - pool().get_commodity_quote(*this, in_terms_of)) { + pool().get_commodity_quote(referent(), in_terms_of)) { if (! in_terms_of || (quote->price.has_commodity() && - quote->price.commodity() == *in_terms_of)) + quote->price.commodity_ptr() == in_terms_of)) return quote; } } @@ -485,6 +217,16 @@ commodity_t::check_for_updated_price(const optional<price_point_t>& point, return point; } +commodity_t& commodity_t::nail_down(const expr_t& expr) +{ + annotation_t new_details; + + new_details.value_expr = expr; + new_details.add_flags(ANNOTATION_VALUE_EXPR_CALCULATED); + + return *pool().find_or_create(symbol(), new_details); +} + commodity_t::operator bool() const { return this != pool().null_commodity; @@ -641,7 +383,7 @@ void commodity_t::parse_symbol(char *& p, string& symbol) throw_(amount_error, _("Failed to parse commodity")); } -void commodity_t::print(std::ostream& out, bool elide_quotes) const +void commodity_t::print(std::ostream& out, bool elide_quotes, bool) const { string sym = symbol(); if (elide_quotes && has_flags(COMMODITY_STYLE_SEPARATED) && @@ -740,6 +482,15 @@ bool commodity_t::compare_by_commodity::operator()(const amount_t * left, if (aleftcomm.details.tag && arightcomm.details.tag) return *aleftcomm.details.tag < *arightcomm.details.tag; + if (! aleftcomm.details.value_expr && arightcomm.details.value_expr) + return true; + if (aleftcomm.details.value_expr && ! arightcomm.details.value_expr) + return false; + + if (aleftcomm.details.value_expr && arightcomm.details.value_expr) + return (aleftcomm.details.value_expr->text() < + arightcomm.details.value_expr->text()); + assert(false); return true; } @@ -767,28 +518,6 @@ void to_xml(std::ostream& out, const commodity_t& comm, if (commodity_details) { if (comm.has_annotation()) to_xml(out, as_annotated_commodity(comm).details); - - if (comm.varied_history()) { - push_xml y(out, "varied-history"); - - foreach (const commodity_t::history_by_commodity_map::value_type& pair, - comm.varied_history()->histories) { - { - push_xml z(out, "symbol"); - out << y.guard(pair.first->symbol()); - } - { - push_xml z(out, "history"); - - foreach (const commodity_t::history_map::value_type& inner_pair, - pair.second.prices) { - push_xml w(out, "price-point"); - to_xml(out, inner_pair.first); - to_xml(out, inner_pair.second); - } - } - } - } } } diff --git a/src/commodity.h b/src/commodity.h index d7747b2a..bfbabe6b 100644 --- a/src/commodity.h +++ b/src/commodity.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -47,6 +47,8 @@ #ifndef _COMMODITY_H #define _COMMODITY_H +#include "expr.h" + namespace ledger { struct keep_details_t; @@ -85,78 +87,6 @@ class commodity_t : public delegates_flags<uint_least16_t>, public equality_comparable1<commodity_t, noncopyable> { -public: - typedef std::map<const datetime_t, amount_t> history_map; - - struct history_t - { - history_map prices; - - void add_price(commodity_t& source, - const datetime_t& date, - const amount_t& price, - const bool reflexive = true); - bool remove_price(const datetime_t& date); - - optional<price_point_t> - find_price(const optional<datetime_t>& moment = none, - const optional<datetime_t>& oldest = none -#if defined(DEBUG_ON) - , const int indent = 0 -#endif - ) const; - -#if defined(HAVE_BOOST_SERIALIZATION) - private: - /** Serialization. */ - - friend class boost::serialization::access; - - template<class Archive> - void serialize(Archive& ar, const unsigned int /* version */) { - ar & prices; - } -#endif // HAVE_BOOST_SERIALIZATION - }; - - typedef std::map<commodity_t *, history_t> history_by_commodity_map; - - struct varied_history_t - { - history_by_commodity_map histories; - - void add_price(commodity_t& source, - const datetime_t& date, - const amount_t& price, - const bool reflexive = true); - bool remove_price(const datetime_t& date, commodity_t& commodity); - - optional<price_point_t> - find_price(const commodity_t& source, - const optional<commodity_t&>& commodity = none, - const optional<datetime_t>& moment = none, - const optional<datetime_t>& oldest = none -#if defined(DEBUG_ON) - , const int indent = 0 -#endif - ) const; - - optional<history_t&> - history(const optional<commodity_t&>& commodity = none); - -#if defined(HAVE_BOOST_SERIALIZATION) - private: - /** Serialization. */ - - friend class boost::serialization::access; - - template<class Archive> - void serialize(Archive& ar, const unsigned int /* version */) { - ar & histories; - } -#endif // HAVE_BOOST_SERIALIZATION - }; - protected: friend class commodity_pool_t; friend class annotated_commodity_t; @@ -178,37 +108,34 @@ protected: #define COMMODITY_SAW_ANN_PRICE_FLOAT 0x400 #define COMMODITY_SAW_ANN_PRICE_FIXATED 0x800 - string symbol; - amount_t::precision_t precision; - optional<string> name; - optional<string> note; - optional<varied_history_t> varied_history; - optional<amount_t> smaller; - optional<amount_t> larger; - - typedef std::pair<optional<datetime_t>, - optional<datetime_t> > optional_time_pair_t; - typedef std::pair<optional_time_pair_t, - commodity_t *> time_and_commodity_t; - typedef std::map<time_and_commodity_t, + string symbol; + optional<std::size_t> graph_index; + amount_t::precision_t precision; + optional<string> name; + optional<string> note; + optional<amount_t> smaller; + optional<amount_t> larger; + optional<expr_t> value_expr; + + typedef tuple<datetime_t, datetime_t, + const commodity_t *> memoized_price_entry; + typedef std::map<memoized_price_entry, optional<price_point_t> > memoized_price_map; - static const std::size_t max_price_map_size = 16; + static const std::size_t max_price_map_size = 8; mutable memoized_price_map price_map; - mutable bool searched; - public: explicit base_t(const string& _symbol) : supports_flags<uint_least16_t> (commodity_t::decimal_comma_by_default ? static_cast<uint_least16_t>(COMMODITY_STYLE_DECIMAL_COMMA) : static_cast<uint_least16_t>(COMMODITY_STYLE_DEFAULTS)), - symbol(_symbol), precision(0), searched(false) { - TRACE_CTOR(base_t, "const string&"); + symbol(_symbol), precision(0) { + TRACE_CTOR(commodity_t::base_t, "const string&"); } virtual ~base_t() { - TRACE_DTOR(base_t); + TRACE_DTOR(commodity_t::base_t); } #if defined(HAVE_BOOST_SERIALIZATION) @@ -228,7 +155,6 @@ protected: ar & precision; ar & name; ar & note; - ar & varied_history; ar & smaller; ar & larger; } @@ -239,7 +165,6 @@ protected: commodity_pool_t * parent_; optional<string> qualified_symbol; - optional<string> mapping_key_; bool annotated; explicit commodity_t(commodity_pool_t * _parent, @@ -263,6 +188,9 @@ public: return comm == *this; return base.get() == comm.base.get(); } + bool operator==(const string& name) const { + return base_symbol() == name; + } static bool symbol_needs_quotes(const string& symbol); @@ -293,11 +221,11 @@ public: return qualified_symbol ? *qualified_symbol : base_symbol(); } - string mapping_key() const { - if (mapping_key_) - return *mapping_key_; - else - return base_symbol(); + optional<std::size_t> graph_index() const {; + return base->graph_index; + } + void set_graph_index(const optional<std::size_t>& arg = none) { + base->graph_index = arg; } optional<string> name() const { @@ -335,53 +263,37 @@ public: base->larger = arg; } - optional<varied_history_t&> varied_history() { - if (base->varied_history) - return *base->varied_history; - return none; + virtual optional<expr_t> value_expr() const { + return base->value_expr; } - optional<const varied_history_t&> varied_history() const { - if (base->varied_history) - return *base->varied_history; - return none; + void set_value_expr(const optional<expr_t>& expr = none) { + base->value_expr = expr; } - optional<history_t&> history(const optional<commodity_t&>& commodity); + void add_price(const datetime_t& date, const amount_t& price, + const bool reflexive = true); + void remove_price(const datetime_t& date, commodity_t& commodity); - // These methods provide a transparent pass-through to the underlying - // base->varied_history object. + void map_prices(function<void(datetime_t, const amount_t&)> fn, + const datetime_t& moment = datetime_t(), + const datetime_t& _oldest = datetime_t(), + bool bidirectionally = false); - void add_price(const datetime_t& date, const amount_t& price, - const bool reflexive = true) { - if (! base->varied_history) - base->varied_history = varied_history_t(); - base->varied_history->add_price(*this, date, price, reflexive); - DEBUG("commodity.prices.find", "Price added, clearing price_map"); - base->price_map.clear(); // a price was added, invalid the map - } - bool remove_price(const datetime_t& date, commodity_t& commodity) { - if (base->varied_history) { - base->varied_history->remove_price(date, commodity); - DEBUG("commodity.prices.find", "Price removed, clearing price_map"); - base->price_map.clear(); // a price was added, invalid the map - } - return false; - } + optional<price_point_t> + find_price_from_expr(expr_t& expr, const commodity_t * commodity, + const datetime_t& moment) const; optional<price_point_t> - find_price(const optional<commodity_t&>& commodity = none, - const optional<datetime_t>& moment = none, - const optional<datetime_t>& oldest = none, - const bool nested = false -#if defined(DEBUG_ON) - , const int indent = 0 -#endif - ) const; + virtual find_price(const commodity_t * commodity = NULL, + const datetime_t& moment = datetime_t(), + const datetime_t& oldest = datetime_t()) const; optional<price_point_t> check_for_updated_price(const optional<price_point_t>& point, - const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of); + const datetime_t& moment, + const commodity_t * in_terms_of); + + commodity_t& nail_down(const expr_t& expr); // Methods related to parsing, reading, writing, etc., the commodity // itself. @@ -394,7 +306,8 @@ public: return temp; } - void print(std::ostream& out, bool elide_quotes = false) const; + virtual void print(std::ostream& out, bool elide_quotes = false, + bool print_annotations = false) const; bool valid() const; struct compare_by_commodity { @@ -423,14 +336,13 @@ private: ar & base; ar & parent_; ar & qualified_symbol; - ar & mapping_key_; ar & annotated; } #endif // HAVE_BOOST_SERIALIZATION }; inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { - comm.print(out); + comm.print(out, false, true); return out; } diff --git a/src/compare.cc b/src/compare.cc index 12114c7d..e2a298c2 100644 --- a/src/compare.cc +++ b/src/compare.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -44,7 +44,7 @@ void push_sort_value(std::list<sort_value_t>& sort_values, if (node->kind == expr_t::op_t::O_CONS) { while (node && node->kind == expr_t::op_t::O_CONS) { push_sort_value(sort_values, node->left(), scope); - node = node->right(); + node = node->has_right() ? node->right() : NULL; } } else { bool inverted = false; diff --git a/src/compare.h b/src/compare.h index 0e7bf5e5..e1abbca1 100644 --- a/src/compare.h +++ b/src/compare.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/context.h b/src/context.h new file mode 100644 index 00000000..45bb9990 --- /dev/null +++ b/src/context.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @addtogroup data + */ + +/** + * @file context.h + * @author John Wiegley + * + * @ingroup data + */ +#ifndef _CONTEXT_H +#define _CONTEXT_H + +#include "utils.h" +#include "times.h" + +namespace ledger { + +class journal_t; +class account_t; +class scope_t; + +class parse_context_t +{ +public: + static const std::size_t MAX_LINE = 4096; + + shared_ptr<std::istream> stream; + + path pathname; + path current_directory; + journal_t * journal; + account_t * master; + scope_t * scope; + char linebuf[MAX_LINE + 1]; + istream_pos_type line_beg_pos; + istream_pos_type curr_pos; + std::size_t linenum; + std::size_t errors; + std::size_t count; + std::size_t sequence; + + explicit parse_context_t(const path& cwd) + : current_directory(cwd), master(NULL), scope(NULL), + linenum(0), errors(0), count(0), sequence(1) {} + + explicit parse_context_t(shared_ptr<std::istream> _stream, + const path& cwd) + : stream(_stream), current_directory(cwd), master(NULL), + scope(NULL), linenum(0), errors(0), count(0), sequence(1) {} + + parse_context_t(const parse_context_t& context) + : stream(context.stream), + pathname(context.pathname), + current_directory(context.current_directory), + journal(context.journal), + master(context.master), + scope(context.scope), + line_beg_pos(context.line_beg_pos), + curr_pos(context.curr_pos), + linenum(context.linenum), + errors(context.errors), + count(context.count), + sequence(context.sequence) { + std::memcpy(linebuf, context.linebuf, MAX_LINE); + } + + string location() const { + return file_context(pathname, linenum); + } + + void warning(const string& what) const { + warning_func(location() + what); + } +}; + +inline parse_context_t open_for_reading(const path& pathname, + const path& cwd) +{ + path filename = resolve_path(pathname); + + if (! exists(filename)) + throw_(std::runtime_error, + _("Cannot read journal file %1") << filename); + +#if BOOST_VERSION >= 104600 && BOOST_FILESYSTEM_VERSION >= 3 + path parent(filesystem::absolute(pathname, cwd).parent_path()); +#else + path parent(filesystem::complete(pathname, cwd).parent_path()); +#endif + shared_ptr<std::istream> stream(new ifstream(filename)); + parse_context_t context(stream, parent); + context.pathname = filename; + return context; +} + +class parse_context_stack_t +{ + std::list<parse_context_t> parsing_context; + +public: + void push() { + parsing_context.push_front(parse_context_t(filesystem::current_path())); + } + void push(shared_ptr<std::istream> stream, + const path& cwd = filesystem::current_path()) { + parsing_context.push_front(parse_context_t(stream, cwd)); + } + void push(const path& pathname, + const path& cwd = filesystem::current_path()) { + parsing_context.push_front(open_for_reading(pathname, cwd)); + } + + void push(const parse_context_t& context) { + parsing_context.push_front(context); + } + + void pop() { + assert(! parsing_context.empty()); + parsing_context.pop_front(); + } + + parse_context_t& get_current() { + assert(! parsing_context.empty()); + return parsing_context.front(); + } +}; + +} // namespace ledger + +#endif // _CONTEXT_H diff --git a/src/convert.cc b/src/convert.cc index 493fbb7a..e8ca241e 100644 --- a/src/convert.cc +++ b/src/convert.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -56,75 +56,50 @@ value_t convert_command(call_scope_t& args) account_t * bucket = journal.master->find_account(bucket_name); account_t * unknown = journal.master->find_account(_("Expenses:Unknown")); - // Make an amounts mapping for the account under consideration - - typedef std::map<value_t, std::list<post_t *> > post_map_t; - post_map_t post_map; - - xacts_iterator journal_iter(journal); - while (xact_t * xact = *journal_iter++) { - post_t * post = NULL; - xact_posts_iterator xact_iter(*xact); - while ((post = *xact_iter++) != NULL) { - if (post->account == bucket) - break; - } - if (post) { - post_map_t::iterator i = post_map.find(post->amount); - if (i == post_map.end()) { - std::list<post_t *> post_list; - post_list.push_back(post); - post_map.insert(post_map_t::value_type(post->amount, post_list)); - } else { - (*i).second.push_back(post); - } - } - } - // Create a flat list xacts_list current_xacts(journal.xacts_begin(), journal.xacts_end()); // Read in the series of transactions from the CSV file print_xacts formatter(report); - ifstream data(path(args.get<string>(0))); - csv_reader reader(data); + path csv_file_path(args.get<string>(0)); - while (xact_t * xact = reader.read_xact(journal, bucket)) { - if (report.HANDLED(invert)) { - foreach (post_t * post, xact->posts) - post->amount.in_place_negate(); - } + report.session.parsing_context.push(csv_file_path); + parse_context_t& context(report.session.parsing_context.get_current()); + context.journal = &journal; + context.master = bucket; - bool matched = false; - if (! xact->posts.front()->amount.is_null()) { - post_map_t::iterator i = post_map.find(- xact->posts.front()->amount); - if (i != post_map.end()) { - std::list<post_t *>& post_list((*i).second); - foreach (post_t * post, post_list) { - if (xact->code && post->xact->code && - *xact->code == *post->xact->code) { - matched = true; - break; - } - else if (xact->actual_date() == post->actual_date()) { - matched = true; - break; - } - } + csv_reader reader(context); + + try { + while (xact_t * xact = reader.read_xact(report.HANDLED(rich_data))) { + if (report.HANDLED(invert)) { + foreach (post_t * post, xact->posts) + post->amount.in_place_negate(); } - } - if (matched) { - DEBUG("convert.csv", "Ignored xact with code: " << *xact->code); - checked_delete(xact); // ignore it - } - else { + string ref = (xact->has_tag(_("UUID")) ? + xact->get_tag(_("UUID"))->to_string() : + sha1sum(reader.get_last_line())); + + checksum_map_t::const_iterator entry = journal.checksum_map.find(ref); + if (entry != journal.checksum_map.end()) { + INFO(file_context(reader.get_pathname(), + reader.get_linenum()) + << "Ignoring known UUID " << ref); + checked_delete(xact); // ignore it + continue; + } + + if (report.HANDLED(rich_data) && ! xact->has_tag(_("UUID"))) + xact->set_tag(_("UUID"), string_value(ref)); + if (xact->posts.front()->account == NULL) { - // jww (2010-03-07): Bind this logic to an option: --auto-match if (account_t * acct = - lookup_probable_account(xact->payee, current_xacts.rbegin(), - current_xacts.rend(), bucket).second) + (report.HANDLED(auto_match) ? + lookup_probable_account(xact->payee, current_xacts.rbegin(), + current_xacts.rend(), bucket).second : + NULL)) xact->posts.front()->account = acct; else xact->posts.front()->account = unknown; @@ -141,8 +116,16 @@ value_t convert_command(call_scope_t& args) formatter(*post); } } + formatter.flush(); + } + catch (const std::exception&) { + add_error_context(_("While parsing file %1") + << file_context(reader.get_pathname(), + reader.get_linenum())); + add_error_context(_("While parsing CSV line:")); + add_error_context(line_context(reader.get_last_line())); + throw; } - formatter.flush(); // If not, transform the payee according to regexps diff --git a/src/convert.h b/src/convert.h index 6d02f24a..de958108 100644 --- a/src/convert.h +++ b/src/convert.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -40,27 +40,27 @@ namespace ledger { -string csv_reader::read_field(std::istream& sin) +string csv_reader::read_field(std::istream& in) { string field; char c; - if (sin.peek() == '"' || sin.peek() == '|') { - sin.get(c); + if (in.peek() == '"' || in.peek() == '|') { + in.get(c); char x; - while (sin.good() && ! sin.eof()) { - sin.get(x); + while (in.good() && ! in.eof()) { + in.get(x); if (x == '\\') { - sin.get(x); + in.get(x); } - else if (x == '"' && sin.peek() == '"') { - sin.get(x); + else if (x == '"' && in.peek() == '"') { + in.get(x); } else if (x == c) { if (x == '|') - sin.unget(); - else if (sin.peek() == ',') - sin.get(c); + in.unget(); + else if (in.peek() == ',') + in.get(c); break; } if (x != '\0') @@ -68,36 +68,36 @@ string csv_reader::read_field(std::istream& sin) } } else { - while (sin.good() && ! sin.eof()) { - sin.get(c); - if (c == ',') - break; - if (c != '\0') - field += c; + while (in.good() && ! in.eof()) { + in.get(c); + if (in.good()) { + if (c == ',') + break; + if (c != '\0') + field += c; + } } } trim(field); return field; } -char * csv_reader::next_line(std::istream& sin) +char * csv_reader::next_line(std::istream& in) { - static char linebuf[MAX_LINE + 1]; + while (in.good() && ! in.eof() && in.peek() == '#') + in.getline(context.linebuf, parse_context_t::MAX_LINE); - while (sin.good() && ! sin.eof() && sin.peek() == '#') - sin.getline(linebuf, MAX_LINE); - - if (! sin.good() || sin.eof()) + if (! in.good() || in.eof()) return NULL; - sin.getline(linebuf, MAX_LINE); + in.getline(context.linebuf, parse_context_t::MAX_LINE); - return linebuf; + return context.linebuf; } -void csv_reader::read_index(std::istream& sin) +void csv_reader::read_index(std::istream& in) { - char * line = next_line(sin); + char * line = next_line(in); if (! line) return; @@ -109,8 +109,8 @@ void csv_reader::read_index(std::istream& sin) if (date_mask.match(field)) index.push_back(FIELD_DATE); - else if (date_eff_mask.match(field)) - index.push_back(FIELD_DATE_EFF); + else if (date_aux_mask.match(field)) + index.push_back(FIELD_DATE_AUX); else if (code_mask.match(field)) index.push_back(FIELD_CODE); else if (payee_mask.match(field)) @@ -130,60 +130,52 @@ void csv_reader::read_index(std::istream& sin) } } -xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket) +xact_t * csv_reader::read_xact(bool rich_data) { - restart: - char * line = next_line(in); + char * line = next_line(*context.stream.get()); if (! line || index.empty()) return NULL; + context.linenum++; std::istringstream instr(line); - std::auto_ptr<xact_t> xact(new xact_t); - std::auto_ptr<post_t> post(new post_t); + unique_ptr<xact_t> xact(new xact_t); + unique_ptr<post_t> post(new post_t); xact->set_state(item_t::CLEARED); xact->pos = position_t(); - xact->pos->pathname = "jww (2010-03-05): unknown"; - xact->pos->beg_pos = in.tellg(); - xact->pos->beg_line = 0; - xact->pos->sequence = 0; + xact->pos->pathname = context.pathname; + xact->pos->beg_pos = context.stream->tellg(); + xact->pos->beg_line = context.linenum; + xact->pos->sequence = context.sequence++; post->xact = xact.get(); -#if 0 post->pos = position_t(); - post->pos->pathname = pathname; - post->pos->beg_pos = line_beg_pos; - post->pos->beg_line = linenum; + post->pos->pathname = context.pathname; + post->pos->beg_pos = context.stream->tellg(); + post->pos->beg_line = context.linenum; post->pos->sequence = context.sequence++; -#endif post->set_state(item_t::CLEARED); post->account = NULL; std::vector<int>::size_type n = 0; amount_t amt; - string total; + string total; + string field; while (instr.good() && ! instr.eof()) { - string field = read_field(instr); + field = read_field(instr); switch (index[n]) { case FIELD_DATE: - if (field.empty()) - goto restart; - try { - xact->_date = parse_date(field); - } - catch (date_error&) { - goto restart; - } + xact->_date = parse_date(field); break; - case FIELD_DATE_EFF: - xact->_date_eff = parse_date(field); + case FIELD_DATE_AUX: + xact->_date_aux = parse_date(field); break; case FIELD_CODE: @@ -193,7 +185,7 @@ xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket) case FIELD_PAYEE: { bool found = false; - foreach (payee_mapping_t& value, journal.payee_mappings) { + foreach (payee_mapping_t& value, context.journal->payee_mappings) { DEBUG("csv.mappings", "Looking for payee mapping: " << value.first); if (value.first.match(field)) { xact->payee = value.second; @@ -243,16 +235,15 @@ xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket) n++; } -#if 0 - xact->set_tag(_("Imported"), - string(format_date(CURRENT_DATE(), FMT_WRITTEN))); - xact->set_tag(_("Original"), string(line)); - xact->set_tag(_("SHA1"), string(sha1sum(line))); -#endif + if (rich_data) { + xact->set_tag(_("Imported"), + string_value(format_date(CURRENT_DATE(), FMT_WRITTEN))); + xact->set_tag(_("CSV"), string_value(line)); + } // Translate the account name, if we have enough information to do so - foreach (account_mapping_t& value, journal.account_mappings) { + foreach (account_mapping_t& value, context.journal->account_mappings) { if (value.first.match(xact->payee)) { post->account = value.second; break; @@ -267,16 +258,14 @@ xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket) post->xact = xact.get(); -#if 0 post->pos = position_t(); - post->pos->pathname = pathname; - post->pos->beg_pos = line_beg_pos; - post->pos->beg_line = linenum; + post->pos->pathname = context.pathname; + post->pos->beg_pos = context.stream->tellg(); + post->pos->beg_line = context.linenum; post->pos->sequence = context.sequence++; -#endif post->set_state(item_t::CLEARED); - post->account = bucket; + post->account = context.master; if (! amt.is_null()) post->amount = - amt; @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -43,6 +43,7 @@ #define _CSV_H #include "value.h" +#include "context.h" namespace ledger { @@ -52,13 +53,11 @@ class account_t; class csv_reader { - static const std::size_t MAX_LINE = 1024; - - std::istream& in; + parse_context_t context; enum headers_t { FIELD_DATE = 0, - FIELD_DATE_EFF, + FIELD_DATE_AUX, FIELD_CODE, FIELD_PAYEE, FIELD_AMOUNT, @@ -70,7 +69,7 @@ class csv_reader }; mask_t date_mask; - mask_t date_eff_mask; + mask_t date_aux_mask; mask_t code_mask; mask_t payee_mask; mask_t amount_mask; @@ -80,29 +79,40 @@ class csv_reader std::vector<int> index; std::vector<string> names; - std::vector<string> fields; - - typedef std::map<string, string> string_map; public: - csv_reader(std::istream& _in) - : in(_in), + csv_reader(parse_context_t& _context) + : context(_context), date_mask("date"), - date_eff_mask("posted( ?date)?"), + date_aux_mask("posted( ?date)?"), code_mask("code"), payee_mask("(payee|desc(ription)?|title)"), amount_mask("amount"), cost_mask("cost"), total_mask("total"), note_mask("note") { - read_index(in); + read_index(*context.stream.get()); + TRACE_CTOR(csv_reader, "parse_context_t&"); + } + ~csv_reader() { + TRACE_DTOR(csv_reader); } + void read_index(std::istream& in); string read_field(std::istream& in); char * next_line(std::istream& in); - void read_index(std::istream& in); - xact_t * read_xact(journal_t& journal, account_t * bucket); + xact_t * read_xact(bool rich_data); + + const char * get_last_line() const { + return context.linebuf; + } + path get_pathname() const { + return context.pathname; + } + std::size_t get_linenum() const { + return context.linenum; + } }; } // namespace ledger diff --git a/src/draft.cc b/src/draft.cc index 2b8b45f7..43c214cb 100644 --- a/src/draft.cc +++ b/src/draft.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -67,10 +67,8 @@ void draft_t::xact_template_t::dump(std::ostream& out) const << std::endl; } else { foreach (const post_template_t& post, posts) { - straccstream accum; out << std::endl - << ACCUM(accum << _("[Posting \"%1\"]") - << (post.from ? _("from") : _("to"))) + << STR(_("[Posting \"%1\"]") << (post.from ? _("from") : _("to"))) << std::endl; if (post.account_mask) @@ -111,7 +109,14 @@ void draft_t::parse_args(const value_t& args) } else if (check_for_date && bool(weekday = string_to_day_of_week(what[0]))) { +#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif short dow = static_cast<short>(*weekday); +#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7 +#pragma GCC diagnostic pop +#endif date_t date = CURRENT_DATE() - date_duration(1); while (date.day_of_week() != dow) date -= date_duration(1); @@ -235,7 +240,7 @@ xact_t * draft_t::insert(journal_t& journal) throw std::runtime_error(_("'xact' command requires at least a payee")); xact_t * matching = NULL; - std::auto_ptr<xact_t> added(new xact_t); + unique_ptr<xact_t> added(new xact_t); if (xact_t * xact = lookup_probable_account(tmpl->payee_mask.str(), journal.xacts.rbegin(), @@ -318,7 +323,7 @@ xact_t * draft_t::insert(journal_t& journal) } foreach (xact_template_t::post_template_t& post, tmpl->posts) { - std::auto_ptr<post_t> new_post; + unique_ptr<post_t> new_post; commodity_t * found_commodity = NULL; @@ -502,7 +507,6 @@ value_t template_command(call_scope_t& args) out << std::endl << std::endl; draft_t draft(args.value()); - out << _("--- Transaction template ---") << std::endl; draft.dump(out); @@ -512,15 +516,16 @@ value_t template_command(call_scope_t& args) value_t xact_command(call_scope_t& args) { report_t& report(find_scope<report_t>(args)); - draft_t draft(args.value()); + draft_t draft(args.value()); - xact_t * new_xact = draft.insert(*report.session.journal.get()); + unique_ptr<xact_t> new_xact(draft.insert(*report.session.journal.get())); + if (new_xact.get()) { + // Only consider actual postings for the "xact" command + report.HANDLER(limit_).on("#xact", "actual"); - // Only consider actual postings for the "xact" command - report.HANDLER(limit_).on(string("#xact"), "actual"); + report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact.get()); + } - if (new_xact) - report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact); return true; } diff --git a/src/draft.h b/src/draft.h index 59039f77..9023e6da 100644 --- a/src/draft.h +++ b/src/draft.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -68,12 +68,31 @@ class draft_t : public expr_base_t<value_t> optional<string> cost_operator; optional<amount_t> cost; - post_template_t() : from(false) {} + post_template_t() : from(false) { + TRACE_CTOR(post_template_t, ""); + } + ~post_template_t() throw() { + TRACE_DTOR(post_template_t); + } }; std::list<post_template_t> posts; - xact_template_t() {} + xact_template_t() { + TRACE_CTOR(xact_template_t, ""); + } + xact_template_t(const xact_template_t& other) + : date(other.date), + code(other.code), + note(other.note), + payee_mask(other.payee_mask), + posts(other.posts) + { + TRACE_CTOR(xact_template_t, "copy"); + } + ~xact_template_t() throw() { + TRACE_DTOR(xact_template_t); + } void dump(std::ostream& out) const; }; @@ -82,11 +101,11 @@ class draft_t : public expr_base_t<value_t> public: draft_t(const value_t& args) : base_type() { - TRACE_CTOR(draft_t, "value_t"); if (! args.empty()) parse_args(args); + TRACE_CTOR(draft_t, "value_t"); } - virtual ~draft_t() { + virtual ~draft_t() throw() { TRACE_DTOR(draft_t); } diff --git a/src/emacs.cc b/src/emacs.cc index 5048a348..41c67cc6 100644 --- a/src/emacs.cc +++ b/src/emacs.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/emacs.h b/src/emacs.h index 97292728..a018ce68 100644 --- a/src/emacs.h +++ b/src/emacs.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/error.cc b/src/error.cc index 88adfbdb..4a16f4e3 100644 --- a/src/error.cc +++ b/src/error.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/error.h b/src/error.h index b9960b03..86d9de76 100644 --- a/src/error.h +++ b/src/error.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -101,6 +101,12 @@ string source_context(const path& file, virtual ~name() throw() {} \ } +struct error_count { + std::size_t count; + explicit error_count(std::size_t _count) : count(_count) {} + const char * what() const { return ""; } +}; + } // namespace ledger #endif // _ERROR_H diff --git a/src/expr.cc b/src/expr.cc index b3d4abcd..25967207 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -37,6 +37,59 @@ namespace ledger { +expr_t::expr_t() : base_type() +{ + TRACE_CTOR(expr_t, ""); +} + +expr_t::expr_t(const expr_t& other) : base_type(other), ptr(other.ptr) +{ + TRACE_CTOR(expr_t, "copy"); +} +expr_t::expr_t(ptr_op_t _ptr, scope_t * _context) + : base_type(_context), ptr(_ptr) +{ + TRACE_CTOR(expr_t, "const ptr_op_t&, scope_t *"); +} + +expr_t::expr_t(const string& _str, const parse_flags_t& flags) + : base_type() +{ + if (! _str.empty()) + parse(_str, flags); + TRACE_CTOR(expr_t, "string, parse_flags_t"); +} + +expr_t::expr_t(std::istream& in, const parse_flags_t& flags) + : base_type() +{ + parse(in, flags); + TRACE_CTOR(expr_t, "std::istream&, parse_flags_t"); +} + +expr_t::~expr_t() { + TRACE_DTOR(expr_t); +} + +expr_t& expr_t::operator=(const expr_t& _expr) +{ + if (this != &_expr) { + base_type::operator=(_expr); + ptr = _expr.ptr; + } + return *this; +} + +expr_t::operator bool() const throw() +{ + return ptr.get() != NULL; +} + +expr_t::ptr_op_t expr_t::get_op() throw() +{ + return ptr; +} + void expr_t::parse(std::istream& in, const parse_flags_t& flags, const optional<string>& original_string) { @@ -163,6 +216,65 @@ void expr_t::dump(std::ostream& out) const if (ptr) ptr->dump(out, 0); } +bool merged_expr_t::check_for_single_identifier(const string& expr) +{ + bool single_identifier = true; + for (const char * p = expr.c_str(); *p; ++p) + if (! std::isalnum(*p) || *p == '_') { + single_identifier = false; + break; + } + + if (single_identifier) { + set_base_expr(expr); + exprs.clear(); + return true; + } else { + return false; + } +} + +void merged_expr_t::compile(scope_t& scope) +{ + if (exprs.empty()) { + parse(base_expr); + } else { + std::ostringstream buf; + + buf << "__tmp_" << term << "=(" << term << "=(" << base_expr << ")"; + foreach (const string& expr, exprs) { + if (merge_operator == ";") + buf << merge_operator << term << "=" << expr; + else + buf << merge_operator << "(" << expr << ")"; + } + buf << ";" << term << ");__tmp_" << term; + + DEBUG("expr.merged.compile", "Compiled expr: " << buf.str()); + parse(buf.str()); + } + + expr_t::compile(scope); +} + +expr_t::ptr_op_t as_expr(const value_t& val) +{ + VERIFY(val.is_any()); + return val.as_any<expr_t::ptr_op_t>(); +} + +void set_expr(value_t& val, expr_t::ptr_op_t op) +{ + val.set_any(op); +} + +value_t expr_value(expr_t::ptr_op_t op) +{ + value_t temp; + temp.set_any(op); + return temp; +} + value_t source_command(call_scope_t& args) { std::istream * in = NULL; @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -58,53 +58,33 @@ public: typedef intrusive_ptr<op_t> ptr_op_t; typedef intrusive_ptr<const op_t> const_ptr_op_t; + enum check_expr_kind_t { + EXPR_GENERAL, + EXPR_ASSERTION, + EXPR_CHECK + }; + + typedef std::pair<expr_t, check_expr_kind_t> check_expr_pair; + typedef std::list<check_expr_pair> check_expr_list; + protected: ptr_op_t ptr; public: - expr_t() : base_type() { - TRACE_CTOR(expr_t, ""); - } - expr_t(const expr_t& other) - : base_type(other), ptr(other.ptr) { - TRACE_CTOR(expr_t, "copy"); - } - expr_t(ptr_op_t _ptr, scope_t * _context = NULL) - : base_type(_context), ptr(_ptr) { - TRACE_CTOR(expr_t, "const ptr_op_t&, scope_t *"); - } + expr_t(); + expr_t(const expr_t& other); + expr_t(ptr_op_t _ptr, scope_t * _context = NULL); - expr_t(const string& _str, const parse_flags_t& flags = PARSE_DEFAULT) - : base_type() { - TRACE_CTOR(expr_t, "string, parse_flags_t"); - if (! _str.empty()) - parse(_str, flags); - } - expr_t(std::istream& in, const parse_flags_t& flags = PARSE_DEFAULT) - : base_type() { - TRACE_CTOR(expr_t, "std::istream&, parse_flags_t"); - parse(in, flags); - } + expr_t(const string& _str, const parse_flags_t& flags = PARSE_DEFAULT); + expr_t(std::istream& in, const parse_flags_t& flags = PARSE_DEFAULT); - virtual ~expr_t() { - TRACE_DTOR(expr_t); - } + virtual ~expr_t(); - expr_t& operator=(const expr_t& _expr) { - if (this != &_expr) { - base_type::operator=(_expr); - ptr = _expr.ptr; - } - return *this; - } + expr_t& operator=(const expr_t& _expr); - virtual operator bool() const throw() { - return ptr.get() != NULL; - } + virtual operator bool() const throw(); - ptr_op_t get_op() throw() { - return ptr; - } + ptr_op_t get_op() throw(); void parse(const string& str, const parse_flags_t& flags = PARSE_DEFAULT) { std::istringstream stream(str); @@ -147,21 +127,73 @@ private: inline bool is_expr(const value_t& val) { return val.is_any() && val.as_any().type() == typeid(expr_t::ptr_op_t); } -inline expr_t::ptr_op_t as_expr(const value_t& val) { - VERIFY(val.is_any()); - return val.as_any<expr_t::ptr_op_t>(); -} -inline void set_expr(value_t& val, expr_t::ptr_op_t op) { - val.set_any(op); -} -inline value_t expr_value(expr_t::ptr_op_t op) { - value_t temp; - temp.set_any(op); - return temp; -} -class call_scope_t; +expr_t::ptr_op_t as_expr(const value_t& val); +void set_expr(value_t& val, expr_t::ptr_op_t op); +value_t expr_value(expr_t::ptr_op_t op); + +// A merged expression allows one to set an expression term, "foo", and +// a base expression, "bar", and then merge in later expressions that +// utilize foo. For example: +// +// foo: bar +// merge: foo * 10 +// merge: foo + 20 +// +// When this expression is finally compiled, the base and merged +// elements are written into this: +// +// __tmp=(foo=bar; foo=foo*10; foo=foo+20);__tmp +// +// This allows users to select flags like -O, -B or -I at any time, and +// also combine flags such as -V and -A. + +class merged_expr_t : public expr_t +{ +public: + string term; + string base_expr; + string merge_operator; + std::list<string> exprs; + + merged_expr_t(const string& _term, const string& expr, + const string& merge_op = ";") + : expr_t(), term(_term), base_expr(expr), merge_operator(merge_op) { + TRACE_CTOR(merged_expr_t, "string, string, string"); + } + virtual ~merged_expr_t() { + TRACE_DTOR(merged_expr_t); + } + + void set_term(const string& _term) { + term = _term; + } + void set_base_expr(const string& expr) { + base_expr = expr; + } + void set_merge_operator(const string& merge_op) { + merge_operator = merge_op; + } + + bool check_for_single_identifier(const string& expr); + + void prepend(const string& expr) { + if (! check_for_single_identifier(expr)) + exprs.push_front(expr); + } + void append(const string& expr) { + if (! check_for_single_identifier(expr)) + exprs.push_back(expr); + } + void remove(const string& expr) { + exprs.remove(expr); + } + + virtual void compile(scope_t& scope); +}; + +class call_scope_t; value_t source_command(call_scope_t& scope); } // namespace ledger diff --git a/src/exprbase.h b/src/exprbase.h index e0e2824f..bea25320 100644 --- a/src/exprbase.h +++ b/src/exprbase.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -113,7 +113,7 @@ public: return ! str.empty(); } - virtual string text() { + virtual string text() const throw() { return str; } void set_text(const string& txt) { @@ -163,7 +163,7 @@ public: } #endif // defined(DEBUG_ON) - DEBUG("expr.calc.when", "Compiling: " << str); + DEBUG("expr.compile", "Compiling: " << str); compile(scope); #if defined(DEBUG_ON) @@ -174,7 +174,7 @@ public: #endif // defined(DEBUG_ON) } - DEBUG("expr.calc.when", "Calculating: " << str); + DEBUG("expr.calc", "Calculating: " << str); return real_calc(scope); } diff --git a/src/filters.cc b/src/filters.cc index 331073eb..9589958c 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -37,6 +37,7 @@ #include "report.h" #include "compare.h" #include "pool.h" +#include "history.h" namespace ledger { @@ -262,6 +263,8 @@ void anonymize_posts::operator()(post_t& post) xact.payee = to_hex(message_digest); xact.note = none; + } else { + xact.journal = post.xact->journal; } std::list<string> account_names; @@ -337,9 +340,10 @@ namespace { const bool act_date_p = true, const value_t& total = value_t(), const bool direct_amount = false, - const bool mark_visited = false) + const bool mark_visited = false, + const bool bidir_link = true) { - post_t& post = temps.create_post(*xact, account); + post_t& post = temps.create_post(*xact, account, bidir_link); post.add_flags(ITEM_GENERATED); // If the account for this post is all virtual, then report the post as @@ -509,8 +513,8 @@ display_filter_posts::display_filter_posts(post_handler_ptr handler, display_total_expr(report.HANDLER(display_total_).expr), show_rounding(_show_rounding) { - TRACE_CTOR(display_filter_posts, "post_handler_ptr, report_t&, bool"); create_accounts(); + TRACE_CTOR(display_filter_posts, "post_handler_ptr, report_t&, bool"); } bool display_filter_posts::output_rounding(post_t& post) @@ -519,7 +523,8 @@ bool display_filter_posts::output_rounding(post_t& post) value_t new_display_total; if (show_rounding) { - new_display_total = display_total_expr.calc(bound_scope); + new_display_total = (display_total_expr.calc(bound_scope) + .strip_annotations(report.what_to_keep())); DEBUG("filters.changed_value.rounding", "rounding.new_display_total = " << new_display_total); @@ -536,7 +541,8 @@ bool display_filter_posts::output_rounding(post_t& post) return true; } - if (value_t repriced_amount = display_amount_expr.calc(bound_scope)) { + if (value_t repriced_amount = (display_amount_expr.calc(bound_scope) + .strip_annotations(report.what_to_keep()))) { if (! last_display_total.is_null()) { DEBUG("filters.changed_value.rounding", "rounding.repriced_amount = " << repriced_amount); @@ -561,7 +567,9 @@ bool display_filter_posts::output_rounding(post_t& post) /* date= */ date_t(), /* act_date_p= */ true, /* total= */ precise_display_total, - /* direct_amount= */ true); + /* direct_amount= */ true, + /* mark_visited= */ false, + /* bidir_link= */ false); } } if (show_rounding) @@ -590,13 +598,11 @@ changed_value_posts::changed_value_posts report.HANDLER(display_total_).expr), display_total_expr(report.HANDLER(display_total_).expr), changed_values_only(report.HANDLED(revalued_only)), + historical_prices_only(report.HANDLED(historical)), for_accounts_report(_for_accounts_report), show_unrealized(_show_unrealized), last_post(NULL), display_filter(_display_filter) { - TRACE_CTOR(changed_value_posts, - "post_handler_ptr, report_t&, bool, bool, display_filter_posts *"); - string gains_equity_account_name; if (report.HANDLED(unrealized_gains_)) gains_equity_account_name = report.HANDLER(unrealized_gains_).str(); @@ -616,14 +622,19 @@ changed_value_posts::changed_value_posts losses_equity_account->add_flags(ACCOUNT_GENERATED); create_accounts(); + + TRACE_CTOR(changed_value_posts, + "post_handler_ptr, report_t&, bool, bool, display_filter_posts *"); } void changed_value_posts::flush() { if (last_post && last_post->date() <= report.terminus.date()) { - if (! for_accounts_report) - output_intermediate_prices(*last_post, report.terminus.date()); - output_revaluation(*last_post, report.terminus.date()); + if (! historical_prices_only) { + if (! for_accounts_report) + output_intermediate_prices(*last_post, report.terminus.date()); + output_revaluation(*last_post, report.terminus.date()); + } last_post = NULL; } item_handler<post_t>::flush(); @@ -688,6 +699,19 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date) } } +namespace { + struct insert_prices_in_map { + price_map_t& all_prices; + + insert_prices_in_map(price_map_t& _all_prices) + : all_prices(_all_prices) {} + + void operator()(datetime_t& date, const amount_t& price) { + all_prices.insert(price_map_t::value_type(date, price)); + } + }; +} + void changed_value_posts::output_intermediate_prices(post_t& post, const date_t& current) { @@ -754,36 +778,19 @@ void changed_value_posts::output_intermediate_prices(post_t& post, // fall through... case value_t::BALANCE: { - commodity_t::history_map all_prices; + price_map_t all_prices; foreach (const balance_t::amounts_map::value_type& amt_comm, - display_total.as_balance().amounts) { - if (optional<commodity_t::varied_history_t&> hist = - amt_comm.first->varied_history()) { - foreach - (const commodity_t::history_by_commodity_map::value_type& comm_hist, - hist->histories) { - foreach (const commodity_t::history_map::value_type& price, - comm_hist.second.prices) { - if (price.first.date() > post.value_date() && - price.first.date() < current) { - DEBUG("filters.revalued", post.value_date() << " < " - << price.first.date() << " < " << current); - DEBUG("filters.revalued", "inserting " - << price.second << " at " << price.first.date()); - all_prices.insert(price); - } - } - } - } - } + display_total.as_balance().amounts) + amt_comm.first->map_prices(insert_prices_in_map(all_prices), + datetime_t(current), + datetime_t(post.value_date()), true); // Choose the last price from each day as the price to use typedef std::map<const date_t, bool> date_map; date_map pricing_dates; - BOOST_REVERSE_FOREACH - (const commodity_t::history_map::value_type& price, all_prices) { + BOOST_REVERSE_FOREACH(const price_map_t::value_type& price, all_prices) { // This insert will fail if a later price has already been inserted // for that date. DEBUG("filters.revalued", @@ -808,7 +815,7 @@ void changed_value_posts::output_intermediate_prices(post_t& post, void changed_value_posts::operator()(post_t& post) { if (last_post) { - if (! for_accounts_report) + if (! for_accounts_report && ! historical_prices_only) output_intermediate_prices(*last_post, post.value_date()); output_revaluation(*last_post, post.value_date()); } @@ -836,10 +843,17 @@ void subtotal_posts::report_subtotal(const char * spec_fmt, foreach (post_t * post, component_posts) { date_t date = post->date(); date_t value_date = post->value_date(); +#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif if (! range_start || date < *range_start) range_start = date; if (! range_finish || value_date > *range_finish) range_finish = value_date; +#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7 +#pragma GCC diagnostic pop +#endif } } component_posts.clear(); @@ -879,15 +893,39 @@ void subtotal_posts::operator()(post_t& post) account_t * acct = post.reported_account(); assert(acct); +#if 0 + // jww (2012-04-06): The problem with doing this early is that + // fn_display_amount will recalculate this again. For example, if you + // use --invert, it will invert both here and in the display amount, + // effectively negating it. + bind_scope_t bound_scope(*amount_expr.get_context(), post); + value_t amount(amount_expr.calc(bound_scope)); +#else + value_t amount(post.amount); +#endif + + post.xdata().compound_value = amount; + post.xdata().add_flags(POST_EXT_COMPOUND); + values_map::iterator i = values.find(acct->fullname()); if (i == values.end()) { - value_t temp; - post.add_to_value(temp, amount_expr); - std::pair<values_map::iterator, bool> result - = values.insert(values_pair(acct->fullname(), acct_value_t(acct, temp))); +#if defined(DEBUG_ON) + std::pair<values_map::iterator, bool> result = +#endif + values.insert(values_pair + (acct->fullname(), + acct_value_t(acct, amount, post.has_flags(POST_VIRTUAL), + post.has_flags(POST_MUST_BALANCE)))); +#if defined(DEBUG_ON) assert(result.second); +#endif } else { - post.add_to_value((*i).second.value, amount_expr); + if (post.has_flags(POST_VIRTUAL) != (*i).second.is_virtual) + throw_(std::logic_error, + _("'equity' cannot accept virtual and " + "non-virtual postings to the same account")); + + add_or_set_value((*i).second.value, amount); } // If the account for this post is all virtual, mark it as @@ -904,55 +942,139 @@ void subtotal_posts::operator()(post_t& post) void interval_posts::report_subtotal(const date_interval_t& ival) { - if (last_post && ival) { - if (exact_periods) - subtotal_posts::report_subtotal(); - else - subtotal_posts::report_subtotal(NULL, ival); - } + if (exact_periods) + subtotal_posts::report_subtotal(); + else + subtotal_posts::report_subtotal(NULL, ival); +} - last_post = NULL; +namespace { + struct sort_posts_by_date { + bool operator()(post_t * left, post_t * right) const { + return left->date() < right->date(); + } + }; } void interval_posts::operator()(post_t& post) { - if (! interval.find_period(post.date())) - return; + // If there is a duration (such as weekly), we must generate the + // report in two passes. Otherwise, we only have to check whether the + // post falls within the reporting period. if (interval.duration) { - if (last_interval && interval != last_interval) { - report_subtotal(last_interval); + all_posts.push_back(&post); + } + else if (interval.find_period(post.date())) { + item_handler<post_t>::operator()(post); + } +} - if (generate_empty_posts) { - for (++last_interval; interval != last_interval; ++last_interval) { - // Generate a null posting, so the intervening periods can be - // seen when -E is used, or if the calculated amount ends up being - // non-zero - xact_t& null_xact = temps.create_xact(); - null_xact._date = last_interval.inclusive_end(); +void interval_posts::flush() +{ + if (! interval.duration) { + item_handler<post_t>::flush(); + return; + } - post_t& null_post = temps.create_post(null_xact, empty_account); - null_post.add_flags(POST_CALCULATED); - null_post.amount = 0L; + // Sort all the postings we saw by date ascending + std::stable_sort(all_posts.begin(), all_posts.end(), + sort_posts_by_date()); - last_post = &null_post; - subtotal_posts::operator()(null_post); + // Determine the beginning interval by using the earliest post + if (! interval.find_period(all_posts.front()->date())) + throw_(std::logic_error, _("Failed to find period for interval report")); - report_subtotal(last_interval); - } - assert(interval == last_interval); - } else { - last_interval = interval; - } + // Walk the interval forward reporting all posts within each one + // before moving on, until we reach the end of all_posts + bool saw_posts = false; + for (std::deque<post_t *>::iterator i = all_posts.begin(); + i != all_posts.end(); ) { + post_t * post(*i); + + DEBUG("filters.interval", + "Considering post " << post->date() << " = " << post->amount); +#if defined(DEBUG_ON) + DEBUG("filters.interval", "interval is:"); + debug_interval(interval); +#endif + assert(! interval.finish || post->date() < *interval.finish); + + if (interval.within_period(post->date())) { + DEBUG("filters.interval", "Calling subtotal_posts::operator()"); + subtotal_posts::operator()(*post); + ++i; + saw_posts = true; } else { - last_interval = interval; + if (saw_posts) { + DEBUG("filters.interval", + "Calling subtotal_posts::report_subtotal()"); + report_subtotal(interval); + saw_posts = false; + } + else if (generate_empty_posts) { + // Generate a null posting, so the intervening periods can be + // seen when -E is used, or if the calculated amount ends up + // being non-zero + xact_t& null_xact = temps.create_xact(); + null_xact._date = interval.inclusive_end(); + + post_t& null_post = temps.create_post(null_xact, empty_account); + null_post.add_flags(POST_CALCULATED); + null_post.amount = 0L; + + subtotal_posts::operator()(null_post); + report_subtotal(interval); + } + + DEBUG("filters.interval", "Advancing interval"); + ++interval; } - subtotal_posts::operator()(post); - } else { - item_handler<post_t>::operator()(post); } - last_post = &post; + // If the last postings weren't reported, do so now. + if (saw_posts) { + DEBUG("filters.interval", + "Calling subtotal_posts::report_subtotal() at end"); + report_subtotal(interval); + } + + // Tell our parent class to flush + subtotal_posts::flush(); +} + +namespace { + struct create_post_from_amount + { + post_handler_ptr handler; + xact_t& xact; + account_t& balance_account; + temporaries_t& temps; + + explicit create_post_from_amount(post_handler_ptr _handler, + xact_t& _xact, + account_t& _balance_account, + temporaries_t& _temps) + : handler(_handler), xact(_xact), + balance_account(_balance_account), temps(_temps) { + TRACE_CTOR(create_post_from_amount, + "post_handler_ptr, xact_t&, account_t&, temporaries_t&"); + } + create_post_from_amount(const create_post_from_amount& other) + : handler(other.handler), xact(other.xact), + balance_account(other.balance_account), temps(other.temps) { + TRACE_CTOR(create_post_from_amount, "copy"); + } + ~create_post_from_amount() throw() { + TRACE_DTOR(create_post_from_amount); + } + + void operator()(const amount_t& amount) { + post_t& balance_post = temps.create_post(xact, &balance_account); + balance_post.amount = - amount; + (*handler)(balance_post); + } + }; } void posts_as_equity::report_subtotal() @@ -971,40 +1093,47 @@ void posts_as_equity::report_subtotal() value_t total = 0L; foreach (values_map::value_type& pair, values) { - if (pair.second.value.is_balance()) { - foreach (const balance_t::amounts_map::value_type& amount_pair, - pair.second.value.as_balance().amounts) - handle_value(/* value= */ amount_pair.second, + value_t value(pair.second.value.strip_annotations(report.what_to_keep())); + if (! value.is_zero()) { + if (value.is_balance()) { + foreach (const balance_t::amounts_map::value_type& amount_pair, + value.as_balance_lval().amounts) { + if (! amount_pair.second.is_zero()) + handle_value(/* value= */ amount_pair.second, + /* account= */ pair.second.account, + /* xact= */ &xact, + /* temps= */ temps, + /* handler= */ handler, + /* date= */ finish, + /* act_date_p= */ false); + } + } else { + handle_value(/* value= */ value.to_amount(), /* account= */ pair.second.account, /* xact= */ &xact, /* temps= */ temps, /* handler= */ handler, /* date= */ finish, /* act_date_p= */ false); - } else { - handle_value(/* value= */ pair.second.value, - /* account= */ pair.second.account, - /* xact= */ &xact, - /* temps= */ temps, - /* handler= */ handler, - /* date= */ finish, - /* act_date_p= */ false); + } } - total += pair.second.value; + + if (! pair.second.is_virtual || pair.second.must_balance) + total += value; } values.clear(); - if (total.is_balance()) { - foreach (const balance_t::amounts_map::value_type& pair, - total.as_balance().amounts) { - post_t& balance_post = temps.create_post(xact, balance_account); - balance_post.amount = - pair.second; - (*handler)(balance_post); - } - } else { - post_t& balance_post = temps.create_post(xact, balance_account); - balance_post.amount = - total.to_amount(); - (*handler)(balance_post); + // This last part isn't really needed, since an Equity:Opening + // Balances posting with a null amount will automatically balance with + // all the other postings generated. But it does make the full + // balancing amount clearer to the user. + if (! total.is_zero()) { + create_post_from_amount post_creator(handler, xact, + *balance_account, temps); + if (total.is_balance()) + total.as_balance_lval().map_sorted_amounts(post_creator); + else + post_creator(total.to_amount()); } } @@ -1346,8 +1475,6 @@ inject_posts::inject_posts(post_handler_ptr handler, account_t * master) : item_handler<post_t>(handler) { - TRACE_CTOR(inject_posts, "post_handler_ptr, string, account_t *"); - scoped_array<char> buf(new char[tag_list.length() + 1]); std::strcpy(buf.get(), tag_list.c_str()); @@ -1364,6 +1491,8 @@ inject_posts::inject_posts(post_handler_ptr handler, tags_list.push_back (tags_list_pair(q, tag_mapping_pair(account, tag_injected_set()))); } + + TRACE_CTOR(inject_posts, "post_handler_ptr, string, account_t *"); } void inject_posts::operator()(post_t& post) diff --git a/src/filters.h b/src/filters.h index e7a2eefa..1dad8852 100644 --- a/src/filters.h +++ b/src/filters.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -65,18 +65,18 @@ protected: value_to_posts_map posts_map; post_handler_ptr post_chain; report_t& report; - expr_t group_by_expr; + expr_t& group_by_expr; custom_flusher_t preflush_func; optional<custom_flusher_t> postflush_func; public: post_splitter(post_handler_ptr _post_chain, report_t& _report, - expr_t _group_by_expr) + expr_t& _group_by_expr) : post_chain(_post_chain), report(_report), group_by_expr(_group_by_expr) { + preflush_func = bind(&post_splitter::print_title, this, _1); TRACE_CTOR(post_splitter, "scope_t&, post_handler_ptr, expr_t"); - preflush_func = bind(&post_splitter::print_title, this, _1); } virtual ~post_splitter() { TRACE_DTOR(post_splitter); @@ -154,9 +154,7 @@ class pass_down_posts : public item_handler<post_t> public: pass_down_posts(post_handler_ptr handler, Iterator& iter) : item_handler<post_t>(handler) { - TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator"); - - while (post_t * post = *iter++) { + while (post_t * post = *iter) { try { item_handler<post_t>::operator()(*post); } @@ -164,9 +162,12 @@ public: add_error_context(item_context(*post, _("While handling posting"))); throw; } + iter.increment(); } item_handler<post_t>::flush(); + + TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator"); } virtual ~pass_down_posts() { @@ -372,6 +373,7 @@ public: } virtual ~anonymize_posts() { TRACE_DTOR(anonymize_posts); + handler.reset(); } void render_commodity(amount_t& amt); @@ -446,11 +448,12 @@ public: only_predicate(_only_predicate), count(0), last_xact(NULL), last_post(NULL), only_collapse_if_zero(_only_collapse_if_zero), report(_report) { - TRACE_CTOR(collapse_posts, "post_handler_ptr, ..."); create_accounts(); + TRACE_CTOR(collapse_posts, "post_handler_ptr, ..."); } virtual ~collapse_posts() { TRACE_DTOR(collapse_posts); + handler.reset(); } void create_accounts() { @@ -496,8 +499,7 @@ public: const bool _also_matching = false) : item_handler<post_t>(handler), also_matching(_also_matching) { - TRACE_CTOR(related_posts, - "post_handler_ptr, const bool"); + TRACE_CTOR(related_posts, "post_handler_ptr, const bool"); } virtual ~related_posts() throw() { TRACE_DTOR(related_posts); @@ -521,8 +523,8 @@ class display_filter_posts : public item_handler<post_t> // later in the chain. report_t& report; - expr_t display_amount_expr; - expr_t display_total_expr; + expr_t& display_amount_expr; + expr_t& display_total_expr; bool show_rounding; value_t last_display_total; temporaries_t temps; @@ -539,6 +541,7 @@ public: virtual ~display_filter_posts() { TRACE_DTOR(display_filter_posts); + handler.reset(); } void create_accounts() { @@ -569,9 +572,10 @@ class changed_value_posts : public item_handler<post_t> // later in the chain. report_t& report; - expr_t total_expr; - expr_t display_total_expr; + expr_t& total_expr; + expr_t& display_total_expr; bool changed_values_only; + bool historical_prices_only; bool for_accounts_report; bool show_unrealized; post_t * last_post; @@ -595,6 +599,7 @@ public: virtual ~changed_value_posts() { TRACE_DTOR(changed_value_posts); + handler.reset(); } void create_accounts() { @@ -635,15 +640,22 @@ protected: public: account_t * account; value_t value; + bool is_virtual; + bool must_balance; - acct_value_t(account_t * a) : account(a) { - TRACE_CTOR(acct_value_t, "account_t *"); + acct_value_t(account_t * a, bool _is_virtual = false, + bool _must_balance = false) + : account(a), is_virtual(_is_virtual), must_balance(_must_balance) { + TRACE_CTOR(acct_value_t, "account_t *, bool, bool"); } - acct_value_t(account_t * a, value_t& v) : account(a), value(v) { - TRACE_CTOR(acct_value_t, "account_t *, value_t&"); + acct_value_t(account_t * a, value_t& v, bool _is_virtual = false, + bool _must_balance = false) + : account(a), value(v), is_virtual(_is_virtual), + must_balance(_must_balance) { + TRACE_CTOR(acct_value_t, "account_t *, value_t&, bool, bool"); } acct_value_t(const acct_value_t& av) - : account(av.account), value(av.value) { + : account(av.account), value(av.value), is_virtual(av.is_virtual) { TRACE_CTOR(acct_value_t, "copy"); } ~acct_value_t() throw() { @@ -655,11 +667,11 @@ protected: typedef std::pair<string, acct_value_t> values_pair; protected: - expr_t& amount_expr; - values_map values; - optional<string> date_format; - temporaries_t temps; - std::list<post_t *> component_posts; + expr_t& amount_expr; + values_map values; + optional<string> date_format; + temporaries_t temps; + std::deque<post_t *> component_posts; public: subtotal_posts(post_handler_ptr handler, expr_t& _amount_expr, @@ -671,6 +683,7 @@ public: } virtual ~subtotal_posts() { TRACE_DTOR(subtotal_posts); + handler.reset(); } void report_subtotal(const char * spec_fmt = NULL, @@ -697,12 +710,12 @@ class interval_posts : public subtotal_posts { date_interval_t start_interval; date_interval_t interval; - date_interval_t last_interval; - post_t * last_post; account_t * empty_account; bool exact_periods; bool generate_empty_posts; + std::deque<post_t *> all_posts; + interval_posts(); public: @@ -713,12 +726,11 @@ public: bool _exact_periods = false, bool _generate_empty_posts = false) : subtotal_posts(_handler, amount_expr), start_interval(_interval), - interval(start_interval), last_post(NULL), - exact_periods(_exact_periods), + interval(start_interval), exact_periods(_exact_periods), generate_empty_posts(_generate_empty_posts) { + create_accounts(); TRACE_CTOR(interval_posts, "post_handler_ptr, expr_t&, date_interval_t, bool, bool"); - create_accounts(); } virtual ~interval_posts() throw() { TRACE_DTOR(interval_posts); @@ -728,21 +740,27 @@ public: empty_account = &temps.create_account(_("<None>")); } - void report_subtotal(const date_interval_t& interval); + void report_subtotal(const date_interval_t& ival); - virtual void flush() { - if (last_post && interval.duration) { - if (interval.is_valid()) - report_subtotal(interval); - subtotal_posts::flush(); - } +#if defined(DEBUG_ON) + void debug_interval(const date_interval_t& ival) { + if (ival.start) + DEBUG("filters.interval", "start = " << *ival.start); + else + DEBUG("filters.interval", "no start"); + + if (ival.finish) + DEBUG("filters.interval", "finish = " << *ival.finish); + else + DEBUG("filters.interval", "no finish"); } +#endif + virtual void operator()(post_t& post); + virtual void flush(); virtual void clear() { - interval = start_interval; - last_interval = date_interval_t(); - last_post = NULL; + interval = start_interval; subtotal_posts::clear(); create_accounts(); @@ -751,6 +769,7 @@ public: class posts_as_equity : public subtotal_posts { + report_t& report; post_t * last_post; account_t * equity_account; account_t * balance_account; @@ -758,10 +777,11 @@ class posts_as_equity : public subtotal_posts posts_as_equity(); public: - posts_as_equity(post_handler_ptr _handler, expr_t& amount_expr) - : subtotal_posts(_handler, amount_expr) { - TRACE_CTOR(posts_as_equity, "post_handler_ptr, expr_t&"); + posts_as_equity(post_handler_ptr _handler, report_t& _report, + expr_t& amount_expr) + : subtotal_posts(_handler, amount_expr), report(_report) { create_accounts(); + TRACE_CTOR(posts_as_equity, "post_handler_ptr, expr_t&"); } virtual ~posts_as_equity() throw() { TRACE_DTOR(posts_as_equity); @@ -844,6 +864,7 @@ public: } virtual ~transfer_details() { TRACE_DTOR(transfer_details); + handler.reset(); } virtual void operator()(post_t& post); @@ -903,6 +924,7 @@ public: virtual ~generate_posts() { TRACE_DTOR(generate_posts); + handler.reset(); } void add_period_xacts(period_xacts_list& period_xacts); @@ -990,6 +1012,7 @@ class inject_posts : public item_handler<post_t> virtual ~inject_posts() throw() { TRACE_DTOR(inject_posts); + handler.reset(); } virtual void operator()(post_t& post); diff --git a/src/flags.h b/src/flags.h index 09b7eec4..e2046c08 100644 --- a/src/flags.h +++ b/src/flags.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/format.cc b/src/format.cc index 93e7ea08..8c3cbc14 100644 --- a/src/format.cc +++ b/src/format.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -70,6 +70,27 @@ void format_t::element_t::dump(std::ostream& out) const } namespace { + struct format_mapping_t { + char letter; + const char * expr; + } single_letter_mappings[] = { + { 'd', "date" }, + { 'S', "filename" }, + { 'B', "beg_pos" }, + { 'b', "beg_line" }, + { 'E', "end_pos" }, + { 'e', "end_line" }, + { 'X', "cleared" }, + { 'Y', "xact.cleared" }, + { 'C', "code" }, + { 'P', "payee" }, + { 'a', "account.name" }, + { 'A', "account" }, + { 't', "justify(scrub(display_amount), $min, $max, $left, color)" }, + { 'T', "justify(scrub(display_total), $min, $max, $left, color)" }, + { 'N', "note" }, + }; + expr_t parse_single_expression(const char *& p, bool single_expr = true) { string temp(p); @@ -92,12 +113,19 @@ namespace { } return expr; } + + inline expr_t::ptr_op_t ident_node(const string& ident) + { + expr_t::ptr_op_t node(new expr_t::op_t(expr_t::op_t::IDENT)); + node->set_ident(ident); + return node; + } } format_t::element_t * format_t::parse_elements(const string& fmt, const optional<format_t&>& tmpl) { - std::auto_ptr<element_t> result; + unique_ptr<element_t> result; element_t * current = NULL; @@ -172,122 +200,170 @@ format_t::element_t * format_t::parse_elements(const string& fmt, current->min_width = current->max_width; } - switch (*p) { - case '%': - current->type = element_t::STRING; - current->data = string("%"); - break; + if (std::isalpha(*p)) { + bool found = false; + for (std::size_t i = 0; i < (sizeof(single_letter_mappings) / + sizeof(format_mapping_t)); i++) { + if (*p == single_letter_mappings[i].letter) { + std::ostringstream expr; + for (const char * ptr = single_letter_mappings[i].expr; *ptr; ){ + if (*ptr == '$') { + const char * beg = ++ptr; + while (*ptr && std::isalpha(*ptr)) + ++ptr; + string::size_type klen = static_cast<string::size_type>(ptr - beg); + string keyword(beg, 0, klen); + if (keyword == "min") + expr << (current->min_width > 0 ? + static_cast<int>(current->min_width) : -1); + else if (keyword == "max") + expr << (current->max_width > 0 ? + static_cast<int>(current->max_width) : -1); + else if (keyword == "left") + expr << (current->has_flags(ELEMENT_ALIGN_LEFT) ? "false" : "true"); +#if defined(DEBUG_ON) + else + assert("Unrecognized format substitution keyword" == NULL); +#endif + } else { + expr << *ptr++; + } + } + current->type = element_t::EXPR; + current->data = expr_t(expr.str()); + found = true; + break; + } + } + if (! found) + throw_(format_error, _("Unrecognized formatting character: %1") << *p); + } else { + switch (*p) { + case '%': + current->type = element_t::STRING; + current->data = string("%"); + break; - case '$': { - if (! tmpl) - throw_(format_error, _("Prior field reference, but no template")); + case '$': { + if (! tmpl) + throw_(format_error, _("Prior field reference, but no template")); - p++; - if (*p == '0' || (! std::isdigit(*p) && - *p != 'A' && *p != 'B' && *p != 'C' && - *p != 'D' && *p != 'E' && *p != 'F')) - throw_(format_error, _("%$ field reference must be a digit from 1-9")); + p++; + if (*p == '0' || (! std::isdigit(*p) && + *p != 'A' && *p != 'B' && *p != 'C' && + *p != 'D' && *p != 'E' && *p != 'F')) + throw_(format_error, _("%$ field reference must be a digit from 1-9")); - int index = std::isdigit(*p) ? *p - '0' : (*p - 'A' + 10); - element_t * tmpl_elem = tmpl->elements.get(); + int index = std::isdigit(*p) ? *p - '0' : (*p - 'A' + 10); + element_t * tmpl_elem = tmpl->elements.get(); - for (int i = 1; i < index && tmpl_elem; i++) { - tmpl_elem = tmpl_elem->next.get(); - while (tmpl_elem && tmpl_elem->type != element_t::EXPR) + for (int i = 1; i < index && tmpl_elem; i++) { tmpl_elem = tmpl_elem->next.get(); - } + while (tmpl_elem && tmpl_elem->type != element_t::EXPR) + tmpl_elem = tmpl_elem->next.get(); + } - if (! tmpl_elem) - throw_(format_error, _("%$ reference to a non-existent prior field")); + if (! tmpl_elem) + throw_(format_error, _("%$ reference to a non-existent prior field")); - *current = *tmpl_elem; - break; - } + *current = *tmpl_elem; + break; + } - case '(': - case '{': { - bool format_amount = *p == '{'; - if (format_amount) p++; + case '(': + case '{': { + bool format_amount = *p == '{'; - current->type = element_t::EXPR; - current->data = parse_single_expression(p, ! format_amount); + current->type = element_t::EXPR; + current->data = parse_single_expression(p); - // Wrap the subexpression in calls to justify and scrub - if (format_amount) { - if (! *p || *(p + 1) != '}') - throw_(format_error, _("Expected closing brace")); - else - p++; + // Wrap the subexpression in calls to justify and scrub + if (! format_amount) + break; expr_t::ptr_op_t op = boost::get<expr_t>(current->data).get_op(); - expr_t::ptr_op_t amount_op; - expr_t::ptr_op_t colorize_op; - if (op->kind == expr_t::op_t::O_CONS) { - amount_op = op->left(); - colorize_op = op->right(); - } else { - amount_op = op; + expr_t::ptr_op_t call2_node(new expr_t::op_t(expr_t::op_t::O_CALL)); + { + call2_node->set_left(ident_node("justify")); + + { + expr_t::ptr_op_t args3_node(new expr_t::op_t(expr_t::op_t::O_CONS)); + { + { + expr_t::ptr_op_t call1_node(new expr_t::op_t(expr_t::op_t::O_CALL)); + { + call1_node->set_left(ident_node("scrub")); + call1_node->set_right(op->kind == expr_t::op_t::O_CONS ? op->left() : op); + } + + args3_node->set_left(call1_node); + } + + expr_t::ptr_op_t args2_node(new expr_t::op_t(expr_t::op_t::O_CONS)); + { + { + expr_t::ptr_op_t arg1_node(new expr_t::op_t(expr_t::op_t::VALUE)); + arg1_node->set_value(current->min_width > 0 ? + long(current->min_width) : -1); + + args2_node->set_left(arg1_node); + } + + { + expr_t::ptr_op_t args1_node(new expr_t::op_t(expr_t::op_t::O_CONS)); + { + { + expr_t::ptr_op_t arg2_node(new expr_t::op_t(expr_t::op_t::VALUE)); + arg2_node->set_value(current->max_width > 0 ? + long(current->max_width) : -1); + + args1_node->set_left(arg2_node); + } + + { + expr_t::ptr_op_t arg3_node(new expr_t::op_t(expr_t::op_t::VALUE)); + arg3_node->set_value(! current->has_flags(ELEMENT_ALIGN_LEFT)); + + args1_node->set_right(arg3_node); + } + } + + args2_node->set_right(args1_node); + } + + args3_node->set_right(args2_node); + } + } + + call2_node->set_right(args3_node); + } } - expr_t::ptr_op_t scrub_node(new expr_t::op_t(expr_t::op_t::IDENT)); - scrub_node->set_ident("scrub"); - - expr_t::ptr_op_t call1_node(new expr_t::op_t(expr_t::op_t::O_CALL)); - call1_node->set_left(scrub_node); - call1_node->set_right(amount_op); - - expr_t::ptr_op_t arg1_node(new expr_t::op_t(expr_t::op_t::VALUE)); - expr_t::ptr_op_t arg2_node(new expr_t::op_t(expr_t::op_t::VALUE)); - expr_t::ptr_op_t arg3_node(new expr_t::op_t(expr_t::op_t::VALUE)); - - arg1_node->set_value(current->min_width > 0 ? - long(current->min_width) : -1); - arg2_node->set_value(current->max_width > 0 ? - long(current->max_width) : -1); - arg3_node->set_value(! current->has_flags(ELEMENT_ALIGN_LEFT)); - current->min_width = 0; current->max_width = 0; - expr_t::ptr_op_t args1_node(new expr_t::op_t(expr_t::op_t::O_CONS)); - args1_node->set_left(arg2_node); - args1_node->set_right(arg3_node); - - expr_t::ptr_op_t args2_node(new expr_t::op_t(expr_t::op_t::O_CONS)); - args2_node->set_left(arg1_node); - args2_node->set_right(args1_node); - - expr_t::ptr_op_t args3_node(new expr_t::op_t(expr_t::op_t::O_CONS)); - args3_node->set_left(call1_node); - args3_node->set_right(args2_node); - - expr_t::ptr_op_t seq1_node(new expr_t::op_t(expr_t::op_t::O_SEQ)); - seq1_node->set_left(args3_node); - - expr_t::ptr_op_t justify_node(new expr_t::op_t(expr_t::op_t::IDENT)); - justify_node->set_ident("justify"); - - expr_t::ptr_op_t call2_node(new expr_t::op_t(expr_t::op_t::O_CALL)); - call2_node->set_left(justify_node); - call2_node->set_right(seq1_node); - string prev_expr = boost::get<expr_t>(current->data).text(); - if (colorize_op) { - expr_t::ptr_op_t ansify_if_node(new expr_t::op_t(expr_t::op_t::IDENT)); - ansify_if_node->set_ident("ansify_if"); - - expr_t::ptr_op_t args4_node(new expr_t::op_t(expr_t::op_t::O_CONS)); - args4_node->set_left(call2_node); - args4_node->set_right(colorize_op); - - expr_t::ptr_op_t seq2_node(new expr_t::op_t(expr_t::op_t::O_SEQ)); - seq2_node->set_left(args4_node); + expr_t::ptr_op_t colorize_op; + if (op->kind == expr_t::op_t::O_CONS) + colorize_op = op->has_right() ? op->right() : NULL; + if (colorize_op) { expr_t::ptr_op_t call3_node(new expr_t::op_t(expr_t::op_t::O_CALL)); - call3_node->set_left(ansify_if_node); - call3_node->set_right(seq2_node); + { + call3_node->set_left(ident_node("ansify_if")); + + { + expr_t::ptr_op_t args4_node(new expr_t::op_t(expr_t::op_t::O_CONS)); + { + args4_node->set_left(call2_node); // from above + args4_node->set_right(colorize_op); + } + + call3_node->set_right(args4_node); + } + } current->data = expr_t(call3_node); } else { @@ -295,12 +371,12 @@ format_t::element_t * format_t::parse_elements(const string& fmt, } boost::get<expr_t>(current->data).set_text(prev_expr); + break; } - break; - } - default: - throw_(format_error, _("Unrecognized formatting character: %1") << *p); + default: + throw_(format_error, _("Unrecognized formatting character: %1") << *p); + } } } @@ -342,7 +418,6 @@ string format_t::real_calc(scope_t& scope) case element_t::EXPR: { expr_t& expr(boost::get<expr_t>(elem->data)); try { - expr.compile(scope); value_t value; @@ -524,6 +599,7 @@ string format_t::truncate(const unistring& ustr, index = 0; #endif std::size_t counter = lens.size(); + std::list<string>::iterator x = parts.begin(); for (std::list<std::size_t>::iterator i = lens.begin(); i != lens.end(); i++) { @@ -553,12 +629,21 @@ string format_t::truncate(const unistring& ustr, if (adjust > 0) { DEBUG("format.abbrev", "Reducing segment " << ++index << " by " << adjust << " chars"); + while (std::isspace((*x)[*i - adjust - 1]) && adjust < *i) { + DEBUG("format.abbrev", + "Segment ends in whitespace, adjusting down"); + ++adjust; + } (*i) -= adjust; DEBUG("format.abbrev", "Segment " << index << " is now " << *i << " chars wide"); - overflow -= adjust; + if (adjust > overflow) + overflow = 0; + else + overflow -= adjust; DEBUG("format.abbrev", "Overflow is now " << overflow << " chars"); } + ++x; } DEBUG("format.abbrev", "Overflow ending this time at " << overflow << " chars"); diff --git a/src/format.h b/src/format.h index f30b8184..cc48bdda 100644 --- a/src/format.h +++ b/src/format.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -51,11 +51,11 @@ class unistring; DECLARE_EXCEPTION(format_error, std::runtime_error); -class format_t : public expr_base_t<string> +class format_t : public expr_base_t<string>, public noncopyable { typedef expr_base_t<string> base_type; - struct element_t : public supports_flags<> + struct element_t : public supports_flags<>, public noncopyable { #define ELEMENT_ALIGN_LEFT 0x01 @@ -74,9 +74,6 @@ class format_t : public expr_base_t<string> ~element_t() throw() { TRACE_DTOR(element_t); } - element_t(const element_t& elem) : supports_flags<>() { - *this = elem; - } element_t& operator=(const element_t& elem) { if (this != &elem) { @@ -128,9 +125,9 @@ public: } format_t(const string& _str, scope_t * context = NULL) : base_type(context) { - TRACE_CTOR(format_t, "const string&"); if (! _str.empty()) parse_format(_str); + TRACE_CTOR(format_t, "const string&"); } virtual ~format_t() { TRACE_DTOR(format_t); diff --git a/src/generate.cc b/src/generate.cc index 185e23e7..8769c99c 100644 --- a/src/generate.cc +++ b/src/generate.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -63,16 +63,15 @@ generate_posts_iterator::generate_posts_iterator neg_number_range(-10000, -1), neg_number_gen(rnd_gen, neg_number_range), pos_number_range(1, 10000), pos_number_gen(rnd_gen, pos_number_range) { - TRACE_CTOR(generate_posts_iterator, "bool"); - std::ostringstream next_date_buf; generate_date(next_date_buf); next_date = parse_date(next_date_buf.str()); - std::ostringstream next_eff_date_buf; - generate_date(next_eff_date_buf); - next_eff_date = parse_date(next_eff_date_buf.str()); + std::ostringstream next_aux_date_buf; + generate_date(next_aux_date_buf); + next_aux_date = parse_date(next_aux_date_buf.str()); + TRACE_CTOR(generate_posts_iterator, "bool"); } void generate_posts_iterator::generate_string(std::ostream& out, int len, @@ -326,8 +325,8 @@ void generate_posts_iterator::generate_xact(std::ostream& out) next_date += gregorian::days(six_gen()); if (truth_gen()) { out << '='; - out << format_date(next_eff_date, FMT_WRITTEN); - next_eff_date += gregorian::days(six_gen()); + out << format_date(next_aux_date, FMT_WRITTEN); + next_aux_date += gregorian::days(six_gen()); } out << ' '; @@ -360,9 +359,15 @@ void generate_posts_iterator::increment() DEBUG("generate.post", "The post we intend to parse:\n" << buf.str()); - std::istringstream in(buf.str()); try { - if (session.journal->parse(in, session) != 0) { + shared_ptr<std::istringstream> in(new std::istringstream(buf.str())); + + parse_context_stack_t parsing_context; + parsing_context.push(in); + parsing_context.get_current().journal = session.journal.get(); + parsing_context.get_current().scope = &session; + + if (session.journal->read(parsing_context) != 0) { VERIFY(session.journal->xacts.back()->valid()); posts.reset(*session.journal->xacts.back()); post = *posts++; diff --git a/src/generate.h b/src/generate.h index 47abcd94..1b22004b 100644 --- a/src/generate.h +++ b/src/generate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -57,7 +57,7 @@ class generate_posts_iterator std::size_t quantity; bool allow_invalid; date_t next_date; - date_t next_eff_date; + date_t next_aux_date; mt19937 rnd_gen; diff --git a/src/global.cc b/src/global.cc index c0698376..6dc8d150 100644 --- a/src/global.cc +++ b/src/global.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -47,8 +47,6 @@ static bool args_only = false; global_scope_t::global_scope_t(char ** envp) { - TRACE_CTOR(global_scope_t, ""); - epoch = CURRENT_TIME(); #if defined(HAVE_BOOST_PYTHON) @@ -68,8 +66,9 @@ global_scope_t::global_scope_t(char ** envp) // a GUI were calling into Ledger it would have one session object per // open document, with a separate report_t object for each report it // generated. - report_stack.push_front(new report_t(session())); + report_stack.push_front(new report_t(*session_ptr)); scope_t::default_scope = &report(); + scope_t::empty_scope = &empty_scope; // Read the user's options, in the following order: // @@ -88,6 +87,8 @@ global_scope_t::global_scope_t(char ** envp) } else { session().HANDLER(price_db_).off(); } + + TRACE_CTOR(global_scope_t, ""); } global_scope_t::~global_scope_t() @@ -112,9 +113,12 @@ void global_scope_t::read_init() if (exists(init_file)) { TRACE_START(init, 1, "Read initialization file"); - ifstream init(init_file); + parse_context_stack_t parsing_context; + parsing_context.push(init_file); + parsing_context.get_current().journal = session().journal.get(); + parsing_context.get_current().scope = &report(); - if (session().journal->read(init_file, NULL, &report()) > 0 || + if (session().journal->read(parsing_context) > 0 || session().journal->auto_xacts.size() > 0 || session().journal->period_xacts.size() > 0) { throw_(parse_error, _("Transactions found in initialization file '%1'") @@ -189,7 +193,7 @@ void global_scope_t::execute_command(strings_list args, bool at_repl) is_precommand = true; // If it is not a pre-command, then parse the user's ledger data at this - // time if not done alreday (i.e., if not at a REPL). Then patch up the + // time if not done already (i.e., if not at a REPL). Then patch up the // report options based on the command verb. if (! is_precommand) { @@ -268,6 +272,7 @@ void global_scope_t::report_options(report_t& report, std::ostream& out) HANDLER(trace_).report(out); HANDLER(verbose).report(out); HANDLER(verify).report(out); + HANDLER(verify_memory).report(out); out << std::endl << "[Session scope options]" << std::endl; report.session.report_options(out); @@ -311,6 +316,7 @@ option_t<global_scope_t> * global_scope_t::lookup_option(const char * p) case 'v': OPT_(verbose); else OPT(verify); + else OPT(verify_memory); else OPT(version); break; } @@ -448,29 +454,36 @@ void handle_debug_options(int argc, char * argv[]) if (std::strcmp(argv[i], "--args-only") == 0) { args_only = true; } + else if (std::strcmp(argv[i], "--verify-memory") == 0) { +#if defined(VERIFY_ON) + verify_enabled = true; + + _log_level = LOG_DEBUG; + _log_category = "memory\\.counts"; +#endif + } else if (std::strcmp(argv[i], "--verify") == 0) { #if defined(VERIFY_ON) - verify_enabled = true; // global in utils.h + verify_enabled = true; #endif } else if (std::strcmp(argv[i], "--verbose") == 0 || std::strcmp(argv[i], "-v") == 0) { #if defined(LOGGING_ON) - _log_level = LOG_INFO; // global in utils.h + _log_level = LOG_INFO; #endif } else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { #if defined(DEBUG_ON) - _log_level = LOG_DEBUG; // global in utils.h - _log_category = argv[i + 1]; // global in utils.h + _log_level = LOG_DEBUG; + _log_category = argv[i + 1]; i++; #endif } else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) { #if defined(TRACING_ON) - _log_level = LOG_TRACE; // global in utils.h + _log_level = LOG_TRACE; try { - // global in utils.h _trace_level = boost::lexical_cast<uint8_t>(argv[i + 1]); } catch (const boost::bad_lexical_cast&) { diff --git a/src/global.h b/src/global.h index 6504230d..5786bb89 100644 --- a/src/global.h +++ b/src/global.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -50,11 +50,17 @@ class global_scope_t : public noncopyable, public scope_t { shared_ptr<session_t> session_ptr; ptr_list<report_t> report_stack; + empty_scope_t empty_scope; public: global_scope_t(char ** envp); ~global_scope_t(); + void quick_close() { + if (! report_stack.empty()) + report_stack.front().quick_close(); + } + virtual string description() { return _("global scope"); } @@ -82,6 +88,7 @@ public: void pop_report() { assert(! report_stack.empty()); report_stack.pop_front(); + // There should always be the "default report" waiting on the stack. assert(! report_stack.empty()); scope_t::default_scope = &report(); @@ -113,7 +120,7 @@ public: out << "Ledger " << ledger::version << _(", the command-line accounting tool"); out << - _("\n\nCopyright (c) 2003-2010, John Wiegley. All rights reserved.\n\n\ + _("\n\nCopyright (c) 2003-2012, John Wiegley. All rights reserved.\n\n\ This program is made available under the terms of the BSD Public License.\n\ See LICENSE file included with the distribution for details and disclaimer."); out << std::endl; @@ -139,7 +146,6 @@ See LICENSE file included with the distribution for details and disclaimer."); OPTION__ (global_scope_t, init_file_, // -i - CTOR(global_scope_t, init_file_) { if (const char * home_var = std::getenv("HOME")) on(none, (path(home_var) / ".ledgerrc").string()); @@ -152,10 +158,11 @@ See LICENSE file included with the distribution for details and disclaimer."); OPTION(global_scope_t, trace_); OPTION(global_scope_t, verbose); OPTION(global_scope_t, verify); + OPTION(global_scope_t, verify_memory); OPTION_(global_scope_t, version, DO() { // -v parent->show_version_info(std::cout); - throw int(0); // exit immediately + throw error_count(0); // exit immediately }); }; diff --git a/src/history.cc b/src/history.cc new file mode 100644 index 00000000..d94ec647 --- /dev/null +++ b/src/history.cc @@ -0,0 +1,455 @@ +/* + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <system.hh> + +#include "history.h" + +template <typename T> +struct f_max : public std::binary_function<T, T, bool> { + T operator()(const T& x, const T& y) const { + return std::max(x, y); + } +}; + +namespace ledger { + +template <typename EdgeWeightMap, + typename PricePointMap, + typename PriceRatioMap> +class recent_edge_weight +{ +public: + EdgeWeightMap weight; + PricePointMap price_point; + PriceRatioMap ratios; + + datetime_t reftime; + datetime_t oldest; + + recent_edge_weight() { } + recent_edge_weight(EdgeWeightMap _weight, + PricePointMap _price_point, + PriceRatioMap _ratios, + const datetime_t& _reftime, + const datetime_t& _oldest = datetime_t()) + : weight(_weight), price_point(_price_point), ratios(_ratios), + reftime(_reftime), oldest(_oldest) { } + + template <typename Edge> + bool operator()(const Edge& e) const + { +#if defined(DEBUG_ON) + DEBUG("history.find", " reftime = " << reftime); + if (! oldest.is_not_a_date_time()) { + DEBUG("history.find", " oldest = " << oldest); + } +#endif + + const price_map_t& prices(get(ratios, e)); + if (prices.empty()) { + DEBUG("history.find", " prices map is empty for this edge"); + return false; + } + + price_map_t::const_iterator low = prices.upper_bound(reftime); + if (low != prices.end() && low == prices.begin()) { + DEBUG("history.find", " don't use this edge"); + return false; + } else { + --low; + assert(((*low).first <= reftime)); + + if (! oldest.is_not_a_date_time() && (*low).first < oldest) { + DEBUG("history.find", " edge is out of range"); + return false; + } + + long secs = (reftime - (*low).first).total_seconds(); + assert(secs >= 0); + + put(weight, e, secs); + put(price_point, e, price_point_t((*low).first, (*low).second)); + + DEBUG("history.find", " using edge at price point " + << (*low).first << " " << (*low).second); + return true; + } + } +}; + +typedef filtered_graph + <commodity_history_t::Graph, + recent_edge_weight<commodity_history_t::EdgeWeightMap, + commodity_history_t::PricePointMap, + commodity_history_t::PriceRatioMap> > FGraph; + +typedef property_map<FGraph, vertex_name_t>::type FNameMap; +typedef property_map<FGraph, vertex_index_t>::type FIndexMap; +typedef iterator_property_map + <commodity_history_t::vertex_descriptor*, FIndexMap, + commodity_history_t::vertex_descriptor, + commodity_history_t::vertex_descriptor&> FPredecessorMap; +typedef iterator_property_map<long*, FIndexMap, long, long&> FDistanceMap; + +void commodity_history_t::add_commodity(commodity_t& comm) +{ + if (! comm.graph_index()) { + comm.set_graph_index(num_vertices(price_graph)); + add_vertex(/* vertex_name= */ &comm, price_graph); + } +} + +void commodity_history_t::add_price(const commodity_t& source, + const datetime_t& when, + const amount_t& price) +{ + assert(source != price.commodity()); + + vertex_descriptor sv = vertex(*source.graph_index(), price_graph); + vertex_descriptor tv = vertex(*price.commodity().graph_index(), price_graph); + + std::pair<edge_descriptor, bool> e1 = edge(sv, tv, price_graph); + if (! e1.second) + e1 = add_edge(sv, tv, price_graph); + + price_map_t& prices(get(ratiomap, e1.first)); + + std::pair<price_map_t::iterator, bool> result = + prices.insert(price_map_t::value_type(when, price)); + if (! result.second) { + // There is already an entry for this moment, so update it + (*result.first).second = price; + } +} + +void commodity_history_t::remove_price(const commodity_t& source, + const commodity_t& target, + const datetime_t& date) +{ + assert(source != target); + + vertex_descriptor sv = vertex(*source.graph_index(), price_graph); + vertex_descriptor tv = vertex(*target.graph_index(), price_graph); + + std::pair<Graph::edge_descriptor, bool> e1 = edge(sv, tv, price_graph); + if (e1.second) { + price_map_t& prices(get(ratiomap, e1.first)); + + // jww (2012-03-04): If it fails, should we give a warning? + prices.erase(date); + + if (prices.empty()) + remove_edge(e1.first, price_graph); + } +} + +void commodity_history_t::map_prices(function<void(datetime_t, + const amount_t&)> fn, + const commodity_t& source, + const datetime_t& moment, + const datetime_t& oldest, + bool bidirectionally) +{ + DEBUG("history.map", "Mapping prices for source commodity: " << source); + + vertex_descriptor sv = vertex(*source.graph_index(), price_graph); + + FGraph fg(price_graph, + recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> + (get(edge_weight, price_graph), pricemap, ratiomap, + moment, oldest)); + + FNameMap namemap(get(vertex_name, fg)); + + graph_traits<FGraph>::adjacency_iterator f_vi, f_vend; + for (tie(f_vi, f_vend) = adjacent_vertices(sv, fg); f_vi != f_vend; ++f_vi) { + std::pair<Graph::edge_descriptor, bool> edgePair = edge(sv, *f_vi, fg); + Graph::edge_descriptor edge = edgePair.first; + + const price_map_t& prices(get(ratiomap, edge)); + + foreach (const price_map_t::value_type& pair, prices) { + const datetime_t& when(pair.first); + + DEBUG("history.map", "Price " << pair.second << " on " << when); + + if ((oldest.is_not_a_date_time() || when >= oldest) && when <= moment) { + if (pair.second.commodity() == source) { + if (bidirectionally) { + amount_t price(pair.second); + price.in_place_invert(); + if (source == *get(namemap, sv)) + price.set_commodity(const_cast<commodity_t&>(*get(namemap, *f_vi))); + else + price.set_commodity(const_cast<commodity_t&>(*get(namemap, sv))); + DEBUG("history.map", "Inverted price is " << price); + DEBUG("history.map", "fn(" << when << ", " << price << ")"); + fn(when, price); + } + } else { + DEBUG("history.map", "fn(" << when << ", " << pair.second << ")"); + fn(when, pair.second); + } + } + } + } +} + +optional<price_point_t> +commodity_history_t::find_price(const commodity_t& source, + const datetime_t& moment, + const datetime_t& oldest) +{ + vertex_descriptor sv = vertex(*source.graph_index(), price_graph); + + FGraph fg(price_graph, + recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> + (get(edge_weight, price_graph), pricemap, ratiomap, + moment, oldest)); + + FNameMap namemap(get(vertex_name, fg)); + + DEBUG("history.find", "sv commodity = " << get(namemap, sv)->symbol()); +#if defined(DEBUG_ON) + if (source.has_flags(COMMODITY_PRIMARY)) + DEBUG("history.find", "sv commodity is primary"); +#endif + DEBUG("history.find", "tv commodity = none "); + + datetime_t most_recent = moment; + amount_t price; + + graph_traits<FGraph>::adjacency_iterator f_vi, f_vend; + for (tie(f_vi, f_vend) = adjacent_vertices(sv, fg); f_vi != f_vend; ++f_vi) { + std::pair<Graph::edge_descriptor, bool> edgePair = edge(sv, *f_vi, fg); + Graph::edge_descriptor edge = edgePair.first; + + DEBUG("history.find", "u commodity = " << get(namemap, sv)->symbol()); + DEBUG("history.find", "v commodity = " << get(namemap, *f_vi)->symbol()); + + const price_point_t& point(get(pricemap, edge)); + + if (price.is_null() || point.when > most_recent) { + most_recent = point.when; + price = point.price; + } + + DEBUG("history.find", "price was = " << price.unrounded()); + + if (price.commodity() == source) { + price.in_place_invert(); + if (source == *get(namemap, sv)) + price.set_commodity(const_cast<commodity_t&>(*get(namemap, *f_vi))); + else + price.set_commodity(const_cast<commodity_t&>(*get(namemap, sv))); + } + + DEBUG("history.find", "price is = " << price.unrounded()); + } + + if (price.is_null()) { + DEBUG("history.find", "there is no final price"); + return none; + } else { + DEBUG("history.find", "final price is = " << price.unrounded()); + return price_point_t(most_recent, price); + } +} + +optional<price_point_t> +commodity_history_t::find_price(const commodity_t& source, + const commodity_t& target, + const datetime_t& moment, + const datetime_t& oldest) +{ + assert(source != target); + + vertex_descriptor sv = vertex(*source.graph_index(), price_graph); + vertex_descriptor tv = vertex(*target.graph_index(), price_graph); + + FGraph fg(price_graph, + recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> + (get(edge_weight, price_graph), pricemap, ratiomap, + moment, oldest)); + + FNameMap namemap(get(vertex_name, fg)); + + DEBUG("history.find", "sv commodity = " << get(namemap, sv)->symbol()); + DEBUG("history.find", "tv commodity = " << get(namemap, tv)->symbol()); + + std::size_t vector_len(num_vertices(fg)); + std::vector<vertex_descriptor> predecessors(vector_len); + std::vector<long> distances(vector_len); + + FPredecessorMap predecessorMap(&predecessors[0]); + FDistanceMap distanceMap(&distances[0]); + + dijkstra_shortest_paths(fg, /* start= */ sv, + predecessor_map(predecessorMap) + .distance_map(distanceMap) + .distance_combine(f_max<long>())); + + // Extract the shortest path and performance the calculations + datetime_t least_recent = moment; + amount_t price; + + const commodity_t * last_target = ⌖ + +#if defined(REVERSE_PREDECESSOR_MAP) + typedef tuple<const commodity_t *, const commodity_t *, + const price_point_t *> results_tuple; + std::vector<results_tuple> results; + bool results_reversed = false; +#endif + + vertex_descriptor v = tv; + for (vertex_descriptor u = predecessorMap[v]; + u != v; + v = u, u = predecessorMap[v]) + { + std::pair<Graph::edge_descriptor, bool> edgePair_uv = edge(u, v, fg); + std::pair<Graph::edge_descriptor, bool> edgePair_vu = edge(v, u, fg); + + Graph::edge_descriptor edge_uv = edgePair_uv.first; + Graph::edge_descriptor edge_vu = edgePair_vu.first; + + const price_point_t& point_uv(get(pricemap, edge_uv)); + const price_point_t& point_vu(get(pricemap, edge_vu)); + + const price_point_t& point(point_vu.when > point_uv.when ? + point_vu : point_uv); + + const commodity_t * u_comm = get(namemap, u); + const commodity_t * v_comm = get(namemap, v); + +#if defined(REVERSE_PREDECESSOR_MAP) + if (v == tv && u_comm != last_target && v_comm != last_target) + results_reversed = true; + + results.push_back(results_tuple(u_comm, v_comm, &point)); + } + + if (results_reversed) + std::reverse(results.begin(), results.end()); + + foreach (const results_tuple& edge, results) { + const commodity_t * u_comm = edge.get<0>(); + const commodity_t * v_comm = edge.get<1>(); + const price_point_t& point(*edge.get<2>()); +#else + assert(u_comm == last_target || v_comm == last_target); +#endif + + bool first_run = false; + if (price.is_null()) { + least_recent = point.when; + first_run = true; + } + else if (point.when < least_recent) { + least_recent = point.when; + } + + DEBUG("history.find", "u commodity = " << u_comm->symbol()); + DEBUG("history.find", "v commodity = " << v_comm->symbol()); + DEBUG("history.find", "last target = " << last_target->symbol()); + + // Determine which direction we are converting in + amount_t pprice(point.price); + DEBUG("history.find", "pprice = " << pprice.unrounded()); + + if (! first_run) { + DEBUG("history.find", "price was = " << price.unrounded()); + if (pprice.commodity_ptr() != last_target) + price *= pprice.inverted(); + else + price *= pprice; + } + else if (pprice.commodity_ptr() != last_target) { + price = pprice.inverted(); + } + else { + price = pprice; + } + DEBUG("history.find", "price is = " << price.unrounded()); + + if (last_target == v_comm) + last_target = u_comm; + else + last_target = v_comm; + + DEBUG("history.find", "last target now = " << last_target->symbol()); + } + + if (price.is_null()) { + DEBUG("history.find", "there is no final price"); + return none; + } else { + price.set_commodity(const_cast<commodity_t&>(target)); + DEBUG("history.find", "final price is = " << price.unrounded()); + + return price_point_t(least_recent, price); + } +} + +template <class Name> +class label_writer { +public: + label_writer(Name _name) : name(_name) { + TRACE_CTOR(label_writer<Name>, "Name"); + } + ~label_writer() throw() { + TRACE_DTOR(label_writer<Name>); + } + + template <class VertexOrEdge> + void operator()(std::ostream& out, const VertexOrEdge& v) const { + out << "[label=\"" << name[v]->symbol() << "\"]"; + } + +private: + Name name; +}; + +void commodity_history_t::print_map(std::ostream& out, const datetime_t& moment) +{ + if (moment.is_not_a_date_time()) { + write_graphviz(out, price_graph, + label_writer<NameMap>(get(vertex_name, price_graph))); + } else { + FGraph fg(price_graph, + recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> + (get(edge_weight, price_graph), pricemap, ratiomap, moment)); + write_graphviz(out, fg, label_writer<FNameMap>(get(vertex_name, fg))); + } +} + +} // namespace ledger diff --git a/src/history.h b/src/history.h new file mode 100644 index 00000000..af0d90f9 --- /dev/null +++ b/src/history.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @addtogroup math + */ + +/** + * @file history.h + * @author John Wiegley + * + * @ingroup math + * + * @brief Types for managing commodity historys + * + * Long. + */ +#ifndef _HISTORY_H +#define _HISTORY_H + +#include "amount.h" +#include "commodity.h" + +namespace boost { + enum edge_price_point_t { edge_price_point }; + enum edge_price_ratio_t { edge_price_ratio }; + BOOST_INSTALL_PROPERTY(edge, price_point); + BOOST_INSTALL_PROPERTY(edge, price_ratio); +} + +namespace ledger { + +typedef std::map<datetime_t, amount_t> price_map_t; + +class commodity_history_t : public noncopyable +{ +public: + typedef adjacency_list + <vecS, // Store all edges in a vector + vecS, // Store all vertices in a vector + undirectedS, // Relations are both ways + + // All vertices are commodities + property<vertex_name_t, const commodity_t *, + property<vertex_index_t, std::size_t> >, + + // All edges are weights computed as the absolute difference between + // the reference time of a search and a known price point. A + // filtered_graph is used to select the recent price point to the + // reference time before performing the search. + property<edge_weight_t, long, + property<edge_price_ratio_t, price_map_t, + property<edge_price_point_t, price_point_t> > >, + + // Graph itself has a std::string name + property<graph_name_t, std::string> + > Graph; + + Graph price_graph; + + typedef graph_traits<Graph>::vertex_descriptor vertex_descriptor; + typedef graph_traits<Graph>::edge_descriptor edge_descriptor; + + typedef property_map<Graph, vertex_name_t>::type NameMap; + typedef property_map<Graph, edge_weight_t>::type EdgeWeightMap; + typedef property_map<Graph, edge_price_point_t>::type PricePointMap; + typedef property_map<Graph, edge_price_ratio_t>::type PriceRatioMap; + + PricePointMap pricemap; + PriceRatioMap ratiomap; + + commodity_history_t() + : pricemap(get(edge_price_point, price_graph)), + ratiomap(get(edge_price_ratio, price_graph)) {} + + void add_commodity(commodity_t& comm); + + void add_price(const commodity_t& source, + const datetime_t& when, + const amount_t& price); + void remove_price(const commodity_t& source, + const commodity_t& target, + const datetime_t& date); + + void map_prices(function<void(datetime_t, const amount_t&)> fn, + const commodity_t& source, + const datetime_t& moment, + const datetime_t& _oldest = datetime_t(), + bool bidirectionally = false); + + optional<price_point_t> + find_price(const commodity_t& source, + const datetime_t& moment, + const datetime_t& oldest = datetime_t()); + + optional<price_point_t> + find_price(const commodity_t& source, + const commodity_t& target, + const datetime_t& moment, + const datetime_t& oldest = datetime_t()); + + void print_map(std::ostream& out, const datetime_t& moment = datetime_t()); +}; + +} // namespace ledger + +#endif // _HISTORY_H diff --git a/src/item.cc b/src/item.cc index 056aa04c..17d46652 100644 --- a/src/item.cc +++ b/src/item.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -35,7 +35,7 @@ namespace ledger { -bool item_t::use_effective_date = false; +bool item_t::use_aux_date = false; bool item_t::has_tag(const string& tag, bool) const { @@ -72,7 +72,7 @@ bool item_t::has_tag(const mask_t& tag_mask, return false; } - optional<value_t> item_t::get_tag(const string& tag, bool) const +optional<value_t> item_t::get_tag(const string& tag, bool) const { DEBUG("item.meta", "Getting item tag: " << tag); if (metadata) { @@ -103,6 +103,16 @@ optional<value_t> item_t::get_tag(const mask_t& tag_mask, return none; } +namespace { + struct CaseInsensitiveKeyCompare + : public std::binary_function<string, string, bool> + { + bool operator()(const string& s1, const string& s2) const { + return boost::algorithm::ilexicographical_compare(s1, s2); + } + }; +} + item_t::string_map::iterator item_t::set_tag(const string& tag, const optional<value_t>& value, @@ -111,15 +121,14 @@ item_t::set_tag(const string& tag, assert(! tag.empty()); if (! metadata) - metadata = string_map(); + metadata = string_map(CaseInsensitiveKeyCompare()); DEBUG("item.meta", "Setting tag '" << tag << "' to value '" << (value ? *value : string_value("<none>")) << "'"); optional<value_t> data = value; - if (data && - (data->is_null() || - (data->is_string() && data->as_string().empty()))) + if (data && (data->is_null() || + (data->is_string() && data->as_string().empty()))) data = none; string_map::iterator i = metadata->find(tag); @@ -150,7 +159,7 @@ void item_t::parse_tags(const char * p, if (char * pp = std::strchr(buf, '=')) { *pp++ = '\0'; - _date_eff = parse_date(pp); + _date_aux = parse_date(pp); } if (buf[0]) _date = parse_date(buf); @@ -172,19 +181,7 @@ void item_t::parse_tags(const char * p, q = std::strtok(NULL, " \t")) { const string::size_type len = std::strlen(q); if (len < 2) continue; - if (! tag.empty()) { - string_map::iterator i; - string field(p + (q - buf.get())); - if (by_value) { - bind_scope_t bound_scope(scope, *this); - i = set_tag(tag, expr_t(field).calc(bound_scope), overwrite_existing); - } else { - i = set_tag(tag, string_value(field), overwrite_existing); - } - (*i).second.second = true; - break; - } - else if (q[0] == ':' && q[len - 1] == ':') { // a series of tags + if (q[0] == ':' && q[len - 1] == ':') { // a series of tags for (char * r = std::strtok(q + 1, ":"); r; r = std::strtok(NULL, ":")) { @@ -199,6 +196,18 @@ void item_t::parse_tags(const char * p, index = 2; } tag = string(q, len - index); + + string_map::iterator i; + string field(p + len + index); + trim(field); + if (by_value) { + bind_scope_t bound_scope(scope, *this); + i = set_tag(tag, expr_t(field).calc(bound_scope), overwrite_existing); + } else { + i = set_tag(tag, string_value(field), overwrite_existing); + } + (*i).second.second = true; + break; } first = false; } @@ -239,12 +248,12 @@ namespace { value_t get_date(item_t& item) { return item.date(); } - value_t get_actual_date(item_t& item) { - return item.actual_date(); + value_t get_primary_date(item_t& item) { + return item.primary_date(); } - value_t get_effective_date(item_t& item) { - if (optional<date_t> effective = item.effective_date()) - return *effective; + value_t get_aux_date(item_t& item) { + if (optional<date_t> aux_date = item.aux_date()) + return *aux_date; return NULL_VALUE; } value_t get_note(item_t& item) { @@ -338,7 +347,10 @@ namespace { } value_t get_seq(item_t& item) { - return item.pos ? long(item.pos->sequence) : 0L; + return long(item.seq()); + } + value_t get_id(item_t& item) { + return string_value(item.id()); } value_t get_addr(item_t& item) { @@ -386,6 +398,13 @@ value_t get_comment(item_t& item) } } +void item_t::define(const symbol_t::kind_t, const string& name, + expr_t::ptr_op_t def) +{ + bind_scope_t bound_scope(*scope_t::default_scope, *this); + set_tag(name, def->calc(bound_scope)); +} + expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind, const string& name) { @@ -397,9 +416,11 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind, if (name == "actual") return WRAP_FUNCTOR(get_wrapper<&get_actual>); else if (name == "actual_date") - return WRAP_FUNCTOR(get_wrapper<&get_actual_date>); + return WRAP_FUNCTOR(get_wrapper<&get_primary_date>); else if (name == "addr") return WRAP_FUNCTOR(get_wrapper<&get_addr>); + else if (name == "aux_date") + return WRAP_FUNCTOR(get_wrapper<&get_aux_date>); break; case 'b': @@ -429,7 +450,7 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind, else if (name == "end_pos") return WRAP_FUNCTOR(get_wrapper<&get_end_pos>); else if (name == "effective_date") - return WRAP_FUNCTOR(get_wrapper<&get_effective_date>); + return WRAP_FUNCTOR(get_wrapper<&get_aux_date>); break; case 'f': @@ -447,6 +468,8 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind, case 'i': if (name == "is_account") return WRAP_FUNCTOR(get_wrapper<&ignore>); + else if (name == "id") + return WRAP_FUNCTOR(get_wrapper<&get_id>); break; case 'm': @@ -464,10 +487,12 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(get_wrapper<&get_pending>); else if (name == "parent") return WRAP_FUNCTOR(get_wrapper<&ignore>); + else if (name == "primary_date") + return WRAP_FUNCTOR(get_wrapper<&get_primary_date>); break; case 's': - if (name == "status") + if (name == "status" || name == "state") return WRAP_FUNCTOR(get_wrapper<&get_status>); else if (name == "seq") return WRAP_FUNCTOR(get_wrapper<&get_seq>); @@ -481,6 +506,8 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind, case 'u': if (name == "uncleared") return WRAP_FUNCTOR(get_wrapper<&get_uncleared>); + else if (name == "uuid") + return WRAP_FUNCTOR(get_wrapper<&get_id>); break; case 'v': @@ -532,7 +559,7 @@ string item_context(const item_t& item, const string& desc) if (! (len > 0)) return empty_string; - assert(len < 8192); + assert(len < 1024 * 1024); std::ostringstream out; @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -60,8 +60,8 @@ struct position_t TRACE_CTOR(position_t, ""); } position_t(const position_t& pos) { - TRACE_CTOR(position_t, "copy"); *this = pos; + TRACE_CTOR(position_t, "copy"); } ~position_t() throw() { TRACE_DTOR(position_t); @@ -100,18 +100,20 @@ private: class item_t : public supports_flags<uint_least16_t>, public scope_t { public: -#define ITEM_NORMAL 0x00 // no flags at all, a basic posting -#define ITEM_GENERATED 0x01 // posting was not found in a journal -#define ITEM_TEMP 0x02 // posting is a managed temporary +#define ITEM_NORMAL 0x00 // no flags at all, a basic posting +#define ITEM_GENERATED 0x01 // posting was not found in a journal +#define ITEM_TEMP 0x02 // posting is a managed temporary +#define ITEM_NOTE_ON_NEXT_LINE 0x04 // did we see a note on the next line? enum state_t { UNCLEARED = 0, CLEARED, PENDING }; typedef std::pair<optional<value_t>, bool> tag_data_t; - typedef std::map<string, tag_data_t> string_map; + typedef std::map<string, tag_data_t, + function<bool(string, string)> > string_map; state_t _state; optional<date_t> _date; - optional<date_t> _date_eff; + optional<date_t> _date_aux; optional<string> note; optional<position_t> pos; optional<string_map> metadata; @@ -123,20 +125,20 @@ public: } item_t(const item_t& item) : supports_flags<uint_least16_t>(), scope_t() { - TRACE_CTOR(item_t, "copy"); copy_details(item); + TRACE_CTOR(item_t, "copy"); } virtual ~item_t() { TRACE_DTOR(item_t); } - void copy_details(const item_t& item) + virtual void copy_details(const item_t& item) { set_flags(item.flags()); set_state(item.state()); _date = item._date; - _date_eff = item._date_eff; + _date_aux = item._date_aux; note = item.note; pos = item.pos; metadata = item.metadata; @@ -149,6 +151,19 @@ public: return ! (*this == xact); } + string id() const { + if (optional<value_t> ref = get_tag(_("UUID"))) { + return ref->to_string(); + } else { + std::ostringstream buf; + buf << seq(); + return buf.str(); + } + } + std::size_t seq() const { + return pos ? pos->sequence : 0L; + } + virtual bool has_tag(const string& tag, bool inherit = true) const; virtual bool has_tag(const mask_t& tag_mask, @@ -173,7 +188,7 @@ public: scope_t& scope, bool overwrite_existing = true); - static bool use_effective_date; + static bool use_aux_date; virtual bool has_date() const { return _date; @@ -181,17 +196,17 @@ public: virtual date_t date() const { assert(_date); - if (use_effective_date) - if (optional<date_t> effective = effective_date()) - return *effective; + if (use_aux_date) + if (optional<date_t> aux = aux_date()) + return *aux; return *_date; } - virtual date_t actual_date() const { + virtual date_t primary_date() const { assert(_date); return *_date; } - virtual optional<date_t> effective_date() const { - return _date_eff; + virtual optional<date_t> aux_date() const { + return _date_aux; } void set_state(state_t new_state) { @@ -201,6 +216,8 @@ public: return _state; } + virtual void define(const symbol_t::kind_t, const string&, + expr_t::ptr_op_t); virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, const string& name); @@ -218,7 +235,7 @@ private: ar & boost::serialization::base_object<scope_t>(*this); ar & _state; ar & _date; - ar & _date_eff; + ar & _date_aux; ar & note; ar & pos; ar & metadata; diff --git a/src/iterators.cc b/src/iterators.cc index b398646e..7cc1291a 100644 --- a/src/iterators.cc +++ b/src/iterators.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -75,6 +75,65 @@ void journal_posts_iterator::increment() } } +namespace { + struct create_price_xact + { + journal_t& journal; + account_t * account; + temporaries_t& temps; + xacts_list& xact_temps; + + std::map<string, xact_t *> xacts_by_commodity; + + create_price_xact(journal_t& _journal, account_t * _account, + temporaries_t& _temps, xacts_list& _xact_temps) + : journal(_journal), account(_account), temps(_temps), + xact_temps(_xact_temps) { + TRACE_CTOR(create_price_xact, + "journal_t&, account_t *, temporaries_t&, xacts_list&"); + } + ~create_price_xact() throw() { + TRACE_DTOR(create_price_xact); + } + + void operator()(datetime_t& date, const amount_t& price) { + xact_t * xact; + string symbol = price.commodity().symbol(); + + std::map<string, xact_t *>::iterator i = + xacts_by_commodity.find(symbol); + if (i != xacts_by_commodity.end()) { + xact = (*i).second; + } else { + xact = &temps.create_xact(); + xact_temps.push_back(xact); + xact->payee = symbol; + xact->_date = date.date(); + xacts_by_commodity.insert + (std::pair<string, xact_t *>(symbol, xact)); + xact->journal = &journal; + } + + bool post_already_exists = false; + + foreach (post_t * post, xact->posts) { + if (post->date() == date.date() && post->amount == price) { + post_already_exists = true; + break; + } + } + + if (! post_already_exists) { + post_t& temp = temps.create_post(*xact, account); + temp._date = date.date(); + temp.amount = price; + + temp.xdata().datetime = date; + } + } + }; +} + void posts_commodities_iterator::reset(journal_t& journal) { journal_posts.reset(journal); @@ -85,57 +144,13 @@ void posts_commodities_iterator::reset(journal_t& journal) commodity_t& comm(post->amount.commodity()); if (comm.flags() & COMMODITY_NOMARKET) continue; - commodities.insert(&comm); + commodities.insert(&comm.referent()); } - std::map<string, xact_t *> xacts_by_commodity; - - foreach (commodity_t * comm, commodities) { - if (optional<commodity_t::varied_history_t&> history = - comm->varied_history()) { - account_t * account = journal.master->find_account(comm->symbol()); - - foreach (commodity_t::history_by_commodity_map::value_type& pair, - history->histories) { - foreach (commodity_t::history_map::value_type& hpair, - pair.second.prices) { - xact_t * xact; - string symbol = hpair.second.commodity().symbol(); - - std::map<string, xact_t *>::iterator i = - xacts_by_commodity.find(symbol); - if (i != xacts_by_commodity.end()) { - xact = (*i).second; - } else { - xact = &temps.create_xact(); - xact_temps.push_back(xact); - xact->payee = symbol; - xact->_date = hpair.first.date(); - xacts_by_commodity.insert - (std::pair<string, xact_t *>(symbol, xact)); - } - - bool post_already_exists = false; - - foreach (post_t * post, xact->posts) { - if (post->_date == hpair.first.date() && - post->amount == hpair.second) { - post_already_exists = true; - break; - } - } - - if (! post_already_exists) { - post_t& temp = temps.create_post(*xact, account); - temp._date = hpair.first.date(); - temp.amount = hpair.second; - - temp.xdata().datetime = hpair.first; - } - } - } - } - } + foreach (commodity_t * comm, commodities) + comm->map_prices + (create_price_xact(journal, journal.master->find_account(comm->symbol()), + temps, xact_temps)); xacts.reset(xact_temps.begin(), xact_temps.end()); diff --git a/src/iterators.h b/src/iterators.h index 93782400..53814666 100644 --- a/src/iterators.h +++ b/src/iterators.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -58,7 +58,15 @@ class iterator_facade_base typedef Value node_base; public: - iterator_facade_base() : m_node(NULL) {} + iterator_facade_base() : m_node(NULL) { + TRACE_CTOR(iterator_facade_base, ""); + } + iterator_facade_base(const iterator_facade_base& i) : m_node(i.m_node) { + TRACE_CTOR(iterator_facade_base, "copy"); + } + ~iterator_facade_base() throw() { + TRACE_DTOR(iterator_facade_base); + } explicit iterator_facade_base(node_base p) : m_node(p) {} @@ -87,12 +95,24 @@ class xact_posts_iterator bool posts_uninitialized; public: - xact_posts_iterator() : posts_uninitialized(true) {} + xact_posts_iterator() : posts_uninitialized(true) { + TRACE_CTOR(xact_posts_iterator, ""); + } xact_posts_iterator(xact_t& xact) : posts_uninitialized(true) { reset(xact); + TRACE_CTOR(xact_posts_iterator, "xact_t&"); + } + xact_posts_iterator(const xact_posts_iterator& i) + : iterator_facade_base<xact_posts_iterator, post_t *, + boost::forward_traversal_tag>(i), + posts_i(i.posts_i), posts_end(i.posts_end), + posts_uninitialized(i.posts_uninitialized) { + TRACE_CTOR(xact_posts_iterator, "copy"); + } + ~xact_posts_iterator() throw() { + TRACE_DTOR(xact_posts_iterator); } - ~xact_posts_iterator() throw() {} void reset(xact_t& xact) { posts_i = xact.posts.begin(); @@ -121,15 +141,28 @@ public: bool xacts_uninitialized; - xacts_iterator() : xacts_uninitialized(true) {} + xacts_iterator() : xacts_uninitialized(true) { + TRACE_CTOR(xacts_iterator, ""); + } xacts_iterator(journal_t& journal) : xacts_uninitialized(false) { reset(journal); + TRACE_CTOR(xacts_iterator, "journal_t&"); } xacts_iterator(xacts_list::iterator beg, xacts_list::iterator end) : xacts_uninitialized(false) { reset(beg, end); + TRACE_CTOR(xacts_iterator, "xacts_list::iterator, xacts_list::iterator"); + } + xacts_iterator(const xacts_iterator& i) + : iterator_facade_base<xacts_iterator, xact_t *, + boost::forward_traversal_tag>(i), + xacts_i(i.xacts_i), xacts_end(i.xacts_end), + xacts_uninitialized(i.xacts_uninitialized) { + TRACE_CTOR(xacts_iterator, "copy"); + } + ~xacts_iterator() throw() { + TRACE_DTOR(xacts_iterator); } - ~xacts_iterator() throw() {} void reset(journal_t& journal); @@ -150,11 +183,22 @@ class journal_posts_iterator xact_posts_iterator posts; public: - journal_posts_iterator() {} + journal_posts_iterator() { + TRACE_CTOR(journal_posts_iterator, ""); + } journal_posts_iterator(journal_t& journal) { reset(journal); + TRACE_CTOR(journal_posts_iterator, "journal_t&"); + } + journal_posts_iterator(const journal_posts_iterator& i) + : iterator_facade_base<journal_posts_iterator, post_t *, + boost::forward_traversal_tag>(i), + xacts(i.xacts), posts(i.posts) { + TRACE_CTOR(journal_posts_iterator, "copy"); + } + ~journal_posts_iterator() throw() { + TRACE_DTOR(journal_posts_iterator); } - ~journal_posts_iterator() throw() {} void reset(journal_t& journal); @@ -169,15 +213,27 @@ protected: journal_posts_iterator journal_posts; xacts_iterator xacts; xact_posts_iterator posts; - temporaries_t temps; xacts_list xact_temps; + temporaries_t temps; public: - posts_commodities_iterator() {} + posts_commodities_iterator() { + TRACE_CTOR(posts_commodities_iterator, ""); + } posts_commodities_iterator(journal_t& journal) { reset(journal); + TRACE_CTOR(posts_commodities_iterator, "journal_t&"); + } + posts_commodities_iterator(const posts_commodities_iterator& i) + : iterator_facade_base<posts_commodities_iterator, post_t *, + boost::forward_traversal_tag>(i), + journal_posts(i.journal_posts), xacts(i.xacts), posts(i.posts), + xact_temps(i.xact_temps), temps(i.temps) { + TRACE_CTOR(posts_commodities_iterator, "copy"); + } + ~posts_commodities_iterator() throw() { + TRACE_DTOR(posts_commodities_iterator); } - ~posts_commodities_iterator() throw() {} void reset(journal_t& journal); @@ -192,12 +248,23 @@ class basic_accounts_iterator std::list<accounts_map::const_iterator> accounts_end; public: - basic_accounts_iterator() {} + basic_accounts_iterator() { + TRACE_CTOR(basic_accounts_iterator, ""); + } basic_accounts_iterator(account_t& account) { push_back(account); increment(); + TRACE_CTOR(basic_accounts_iterator, "account_t&"); + } + basic_accounts_iterator(const basic_accounts_iterator& i) + : iterator_facade_base<basic_accounts_iterator, account_t *, + boost::forward_traversal_tag>(i), + accounts_i(i.accounts_i), accounts_end(i.accounts_end) { + TRACE_CTOR(basic_accounts_iterator, "copy"); + } + ~basic_accounts_iterator() throw() { + TRACE_DTOR(basic_accounts_iterator); } - ~basic_accounts_iterator() throw() {} void increment(); @@ -227,8 +294,20 @@ public: : sort_cmp(_sort_cmp), flatten_all(_flatten_all) { push_back(account); increment(); + TRACE_CTOR(sorted_accounts_iterator, "account_t&, expr_t, bool"); + } + sorted_accounts_iterator(const sorted_accounts_iterator& i) + : iterator_facade_base<sorted_accounts_iterator, account_t *, + boost::forward_traversal_tag>(i), + sort_cmp(i.sort_cmp), flatten_all(i.flatten_all), + accounts_list(i.accounts_list), + sorted_accounts_i(i.sorted_accounts_i), + sorted_accounts_end(i.sorted_accounts_end) { + TRACE_CTOR(sorted_accounts_iterator, "copy"); + } + ~sorted_accounts_iterator() throw() { + TRACE_DTOR(sorted_accounts_iterator); } - ~sorted_accounts_iterator() throw() {} void increment(); diff --git a/src/journal.cc b/src/journal.cc index 0691954f..49d86ff0 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -32,33 +32,37 @@ #include <system.hh> #include "journal.h" +#include "context.h" #include "amount.h" #include "commodity.h" #include "pool.h" #include "xact.h" +#include "post.h" #include "account.h" namespace ledger { journal_t::journal_t() { - TRACE_CTOR(journal_t, ""); initialize(); + TRACE_CTOR(journal_t, ""); } +#if 0 journal_t::journal_t(const path& pathname) { - TRACE_CTOR(journal_t, "path"); initialize(); read(pathname); + TRACE_CTOR(journal_t, "path"); } journal_t::journal_t(const string& str) { - TRACE_CTOR(journal_t, "string"); initialize(); read(str); + TRACE_CTOR(journal_t, "string"); } +#endif journal_t::~journal_t() { @@ -80,9 +84,18 @@ journal_t::~journal_t() void journal_t::initialize() { - master = new account_t; - bucket = NULL; - was_loaded = false; + master = new account_t; + bucket = NULL; + fixed_accounts = false; + fixed_payees = false; + fixed_commodities = false; + fixed_metadata = false; + current_context = NULL; + was_loaded = false; + force_checking = false; + check_payees = false; + day_break = false; + checking_style = CHECK_PERMISSIVE; } void journal_t::add_account(account_t * acct) @@ -105,6 +118,201 @@ account_t * journal_t::find_account_re(const string& regexp) return master->find_account_re(regexp); } +account_t * journal_t::register_account(const string& name, post_t * post, + account_t * master_account) +{ + account_t * result = NULL; + + // If there any account aliases, substitute before creating an account + // object. + if (account_aliases.size() > 0) { + accounts_map::const_iterator i = account_aliases.find(name); + if (i != account_aliases.end()) + result = (*i).second; + } + + // Create the account object and associate it with the journal; this + // is registering the account. + if (! result) + result = master_account->find_account(name); + + // If the account name being registered is "Unknown", check whether + // the payee indicates an account that should be used. + if (result->name == _("Unknown")) { + foreach (account_mapping_t& value, payees_for_unknown_accounts) { + if (value.first.match(post->xact->payee)) { + result = value.second; + break; + } + } + } + + // Now that we have an account, make certain that the account is + // "known", if the user has requested validation of that fact. + if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) { + if (! result->has_flags(ACCOUNT_KNOWN)) { + if (! post) { + if (force_checking) + fixed_accounts = true; + result->add_flags(ACCOUNT_KNOWN); + } + else if (! fixed_accounts && post->_state != item_t::UNCLEARED) { + result->add_flags(ACCOUNT_KNOWN); + } + else if (checking_style == CHECK_WARNING) { + current_context->warning(STR(_("Unknown account '%1'") + << result->fullname())); + } + else if (checking_style == CHECK_ERROR) { + throw_(parse_error, _("Unknown account '%1'") << result->fullname()); + } + } + } + + return result; +} + +string journal_t::register_payee(const string& name, xact_t * xact) +{ + string payee; + + if (check_payees && + (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR)) { + std::set<string>::iterator i = known_payees.find(name); + + if (i == known_payees.end()) { + if (! xact) { + if (force_checking) + fixed_payees = true; + known_payees.insert(name); + } + else if (! fixed_payees && xact->_state != item_t::UNCLEARED) { + known_payees.insert(name); + } + else if (checking_style == CHECK_WARNING) { + current_context->warning(STR(_("Unknown payee '%1'") << name)); + } + else if (checking_style == CHECK_ERROR) { + throw_(parse_error, _("Unknown payee '%1'") << name); + } + } + } + + foreach (payee_mapping_t& value, payee_mappings) { + if (value.first.match(name)) { + payee = value.second; + break; + } + } + + return payee.empty() ? name : payee; +} + +void journal_t::register_commodity(commodity_t& comm, + variant<int, xact_t *, post_t *> context) +{ + if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) { + if (! comm.has_flags(COMMODITY_KNOWN)) { + if (context.which() == 0) { + if (force_checking) + fixed_commodities = true; + comm.add_flags(COMMODITY_KNOWN); + } + else if (! fixed_commodities && + ((context.which() == 1 && + boost::get<xact_t *>(context)->_state != item_t::UNCLEARED) || + (context.which() == 2 && + boost::get<post_t *>(context)->_state != item_t::UNCLEARED))) { + comm.add_flags(COMMODITY_KNOWN); + } + else if (checking_style == CHECK_WARNING) { + current_context->warning(STR(_("Unknown commodity '%1'") << comm)); + } + else if (checking_style == CHECK_ERROR) { + throw_(parse_error, _("Unknown commodity '%1'") << comm); + } + } + } +} + +void journal_t::register_metadata(const string& key, const value_t& value, + variant<int, xact_t *, post_t *> context) +{ + if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) { + std::set<string>::iterator i = known_tags.find(key); + + if (i == known_tags.end()) { + if (context.which() == 0) { + if (force_checking) + fixed_metadata = true; + known_tags.insert(key); + } + else if (! fixed_metadata && + ((context.which() == 1 && + boost::get<xact_t *>(context)->_state != item_t::UNCLEARED) || + (context.which() == 2 && + boost::get<post_t *>(context)->_state != item_t::UNCLEARED))) { + known_tags.insert(key); + } + else if (checking_style == CHECK_WARNING) { + current_context->warning(STR(_("Unknown metadata tag '%1'") << key)); + } + else if (checking_style == CHECK_ERROR) { + throw_(parse_error, _("Unknown metadata tag '%1'") << key); + } + } + } + + if (! value.is_null()) { + std::pair<tag_check_exprs_map::iterator, + tag_check_exprs_map::iterator> range = + tag_check_exprs.equal_range(key); + + for (tag_check_exprs_map::iterator i = range.first; + i != range.second; + ++i) { + bind_scope_t bound_scope + (*current_context->scope, + context.which() == 1 ? + static_cast<scope_t&>(*boost::get<xact_t *>(context)) : + static_cast<scope_t&>(*boost::get<post_t *>(context))); + value_scope_t val_scope(bound_scope, value); + + if (! (*i).second.first.calc(val_scope).to_boolean()) { + if ((*i).second.second == expr_t::EXPR_ASSERTION) + throw_(parse_error, + _("Metadata assertion failed for (%1: %2): %3") + << key << value << (*i).second.first); + else + current_context->warning + (STR(_("Metadata check failed for (%1: %2): %3") + << key << value << (*i).second.first)); + } + } + } +} + +namespace { + void check_all_metadata(journal_t& journal, + variant<int, xact_t *, post_t *> context) + { + xact_t * xact = context.which() == 1 ? boost::get<xact_t *>(context) : NULL; + post_t * post = context.which() == 2 ? boost::get<post_t *>(context) : NULL; + + if ((xact || post) && xact ? xact->metadata : post->metadata) { + foreach (const item_t::string_map::value_type& pair, + xact ? *xact->metadata : *post->metadata) { + const string& key(pair.first); + + if (optional<value_t> value = pair.second.first) + journal.register_metadata(key, *value, context); + else + journal.register_metadata(key, NULL_VALUE, context); + } + } + } +} + bool journal_t::add_xact(xact_t * xact) { xact->journal = this; @@ -115,6 +323,29 @@ bool journal_t::add_xact(xact_t * xact) } extend_xact(xact); + check_all_metadata(*this, xact); + + foreach (post_t * post, xact->posts) { + extend_post(*post, *this); + check_all_metadata(*this, post); + } + + // If a transaction with this UUID has already been seen, simply do + // not add this one to the journal. However, all automated checks + // will have been performed by extend_xact, so asserts can still be + // applied to it. + if (optional<value_t> ref = xact->get_tag(_("UUID"))) { + std::pair<checksum_map_t::iterator, bool> result + = checksum_map.insert(checksum_map_t::value_type(ref->to_string(), xact)); + if (! result.second) { + // jww (2012-02-27): Confirm that the xact in + // (*result.first).second is exact match in its significant + // details to xact. + xact->journal = NULL; + return false; + } + } + xacts.push_back(xact); return true; @@ -123,7 +354,7 @@ bool journal_t::add_xact(xact_t * xact) void journal_t::extend_xact(xact_base_t * xact) { foreach (auto_xact_t * auto_xact, auto_xacts) - auto_xact->extend_xact(*xact); + auto_xact->extend_xact(*xact, *current_context); } bool journal_t::remove_xact(xact_t * xact) @@ -144,28 +375,36 @@ bool journal_t::remove_xact(xact_t * xact) return true; } -std::size_t journal_t::read(std::istream& in, - const path& pathname, - account_t * master_alt, - scope_t * scope) +std::size_t journal_t::read(parse_context_stack_t& context) { std::size_t count = 0; try { - if (! scope) - scope = scope_t::default_scope; + parse_context_t& current(context.get_current()); + current_context = ¤t; + + current.count = 0; + if (! current.scope) + current.scope = scope_t::default_scope; - if (! scope) + if (! current.scope) throw_(std::runtime_error, _("No default scope in which to read journal file '%1'") - << pathname); + << current.pathname); - value_t strict = expr_t("strict").calc(*scope); + if (! current.master) + current.master = master; - count = parse(in, *scope, master_alt ? master_alt : master, - &pathname, strict.to_boolean()); + count = read_textual(context); + if (count > 0) { + if (! current.pathname.empty()) + sources.push_back(fileinfo_t(current.pathname)); + else + sources.push_back(fileinfo_t()); + } } catch (...) { clear_xdata(); + current_context = NULL; throw; } @@ -177,23 +416,6 @@ std::size_t journal_t::read(std::istream& in, return count; } -std::size_t journal_t::read(const path& pathname, - account_t * master_account, - scope_t * scope) -{ - path filename = resolve_path(pathname); - - if (! exists(filename)) - throw_(std::runtime_error, - _("Cannot read journal file '%1'") << filename); - - ifstream stream(filename); - std::size_t count = read(stream, filename, master_account, scope); - if (count > 0) - sources.push_back(fileinfo_t(filename)); - return count; -} - bool journal_t::has_xdata() { foreach (xact_t * xact, xacts) diff --git a/src/journal.h b/src/journal.h index ca6b6e4f..a7a84447 100644 --- a/src/journal.h +++ b/src/journal.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -45,6 +45,7 @@ #include "utils.h" #include "times.h" #include "mask.h" +#include "expr.h" namespace ledger { @@ -52,17 +53,22 @@ class xact_base_t; class xact_t; class auto_xact_t; class period_xact_t; +class post_t; class account_t; -class scope_t; - -typedef std::list<xact_t *> xacts_list; -typedef std::list<auto_xact_t *> auto_xacts_list; -typedef std::list<period_xact_t *> period_xacts_list; - -typedef std::pair<mask_t, string> payee_mapping_t; -typedef std::list<payee_mapping_t> payee_mappings_t; -typedef std::pair<mask_t, account_t *> account_mapping_t; -typedef std::list<account_mapping_t> account_mappings_t; +class parse_context_t; +class parse_context_stack_t; + +typedef std::list<xact_t *> xacts_list; +typedef std::list<auto_xact_t *> auto_xacts_list; +typedef std::list<period_xact_t *> period_xacts_list; +typedef std::pair<mask_t, string> payee_mapping_t; +typedef std::list<payee_mapping_t> payee_mappings_t; +typedef std::pair<mask_t, account_t *> account_mapping_t; +typedef std::list<account_mapping_t> account_mappings_t; +typedef std::map<const string, account_t *> accounts_map; +typedef std::map<string, xact_t *> checksum_map_t; +typedef std::multimap<string, + expr_t::check_expr_pair> tag_check_exprs_map; class journal_t : public noncopyable { @@ -79,9 +85,9 @@ public: } fileinfo_t(const path& _filename) : filename(_filename), from_stream(false) { - TRACE_CTOR(journal_t::fileinfo_t, "const path&"); size = file_size(*filename); modtime = posix_time::from_time_t(last_write_time(*filename)); + TRACE_CTOR(journal_t::fileinfo_t, "const path&"); } fileinfo_t(const fileinfo_t& info) : filename(info.filename), size(info.size), @@ -115,13 +121,36 @@ public: auto_xacts_list auto_xacts; period_xacts_list period_xacts; std::list<fileinfo_t> sources; + std::set<string> known_payees; + std::set<string> known_tags; + bool fixed_accounts; + bool fixed_payees; + bool fixed_commodities; + bool fixed_metadata; + bool was_loaded; + bool force_checking; + bool check_payees; + bool day_break; payee_mappings_t payee_mappings; account_mappings_t account_mappings; - bool was_loaded; + accounts_map account_aliases; + account_mappings_t payees_for_unknown_accounts; + checksum_map_t checksum_map; + tag_check_exprs_map tag_check_exprs; + optional<expr_t> value_expr; + parse_context_t * current_context; + + enum checking_style_t { + CHECK_PERMISSIVE, + CHECK_WARNING, + CHECK_ERROR + } checking_style; journal_t(); +#if 0 journal_t(const path& pathname); journal_t(const string& str); +#endif ~journal_t(); void initialize(); @@ -133,13 +162,19 @@ public: return sources.end(); } - // These four methods are delegated to the current session, since all - // accounts processed are gathered together at the session level. void add_account(account_t * acct); bool remove_account(account_t * acct); account_t * find_account(const string& name, bool auto_create = true); account_t * find_account_re(const string& regexp); + account_t * register_account(const string& name, post_t * post, + account_t * master = NULL); + string register_payee(const string& name, xact_t * xact); + void register_commodity(commodity_t& comm, + variant<int, xact_t *, post_t *> context); + void register_metadata(const string& key, const value_t& value, + variant<int, xact_t *, post_t *> context); + bool add_xact(xact_t * xact); void extend_xact(xact_base_t * xact); bool remove_xact(xact_t * xact); @@ -163,25 +198,16 @@ public: return period_xacts.end(); } - std::size_t read(std::istream& in, - const path& pathname, - account_t * master = NULL, - scope_t * scope = NULL); - std::size_t read(const path& pathname, - account_t * master = NULL, - scope_t * scope = NULL); - - std::size_t parse(std::istream& in, - scope_t& session_scope, - account_t * master = NULL, - const path * original_file = NULL, - bool strict = false); + std::size_t read(parse_context_stack_t& context); bool has_xdata(); void clear_xdata(); bool valid() const; +private: + std::size_t read_textual(parse_context_stack_t& context); + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ @@ -198,6 +224,7 @@ private: ar & sources; ar & payee_mappings; ar & account_mappings; + ar & checksum_map; } #endif // HAVE_BOOST_SERIALIZATION }; diff --git a/src/lookup.cc b/src/lookup.cc index 452727d6..6f0c33ea 100644 --- a/src/lookup.cc +++ b/src/lookup.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -186,12 +186,14 @@ lookup_probable_account(const string& ident, if (in_order_match && pos - index < 3) addend++; +#if 0 #if !defined(HAVE_BOOST_REGEX_UNICODE) if (pos == 0 || (pos > 0 && !std::isalnum(value_key[pos - 1]))) addend++; #else // jww (2010-03-07): Not yet implemented #endif +#endif last_match_pos = pos; } diff --git a/src/lookup.h b/src/lookup.h index 8e83b84e..ba64b0b5 100644 --- a/src/lookup.h +++ b/src/lookup.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/main.cc b/src/main.cc index 820f920d..0130d5c6 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -58,6 +58,7 @@ int main(int argc, char * argv[], char * envp[]) // --verbose ; turns on logging // --debug CATEGORY ; turns on debug logging // --trace LEVEL ; turns on trace logging + // --memory ; turns on memory usage tracing handle_debug_options(argc, argv); #if defined(VERIFY_ON) IF_VERIFY() initialize_memory_tracing(); @@ -80,13 +81,12 @@ int main(int argc, char * argv[], char * envp[]) ::textdomain("ledger"); #endif - std::auto_ptr<global_scope_t> global_scope; + global_scope_t * global_scope = NULL; try { // Create the session object, which maintains nearly all state relating to // this invocation of Ledger; and register all known journal parsers. - global_scope.reset(new global_scope_t(envp)); - + global_scope = new global_scope_t(envp); global_scope->session().set_flush_on_next_data_file(true); // Construct an STL-style argument list from the process command arguments @@ -95,8 +95,7 @@ int main(int argc, char * argv[], char * envp[]) args.push_back(argv[i]); // Look for options and a command verb in the command-line arguments - bind_scope_t bound_scope(*global_scope.get(), global_scope->report()); - + bind_scope_t bound_scope(*global_scope, global_scope->report()); args = global_scope->read_command_arguments(bound_scope, args); if (global_scope->HANDLED(script_)) { @@ -112,8 +111,8 @@ int main(int argc, char * argv[], char * envp[]) char * p = skip_ws(line); if (*p && *p != '#') - status = global_scope->execute_command_wrapper(split_arguments(p), - true); + status = + global_scope->execute_command_wrapper(split_arguments(p), true); } } else if (! args.empty()) { @@ -187,30 +186,35 @@ int main(int argc, char * argv[], char * envp[]) } } catch (const std::exception& err) { - if (global_scope.get()) + if (global_scope) global_scope->report_error(err); else std::cerr << "Exception during initialization: " << err.what() << std::endl; } - catch (int _status) { - status = _status; // used for a "quick" exit, and is used only - // if help text (such as --help) was displayed + catch (const error_count& errors) { + // used for a "quick" exit, and is used only if help text (such as + // --help) was displayed + status = static_cast<int>(errors.count); } // If memory verification is being performed (which can be very slow), clean // up everything by closing the session and deleting the session object, and // then shutting down the memory tracing subsystem. Otherwise, let it all // leak because we're about to exit anyway. +#if defined(VERIFY_ON) IF_VERIFY() { - global_scope.reset(); + checked_delete(global_scope); INFO("Ledger ended (Boost/libstdc++ may still hold memory)"); #if defined(VERIFY_ON) shutdown_memory_tracing(); #endif - } else { - INFO("Ledger ended"); + } else +#endif + { + global_scope->quick_close(); + INFO("Ledger ended"); // let global_scope leak! } // Return the final status to the operating system, either 1 for error or 0 diff --git a/src/mask.cc b/src/mask.cc index 52907cfe..afb68ca0 100644 --- a/src/mask.cc +++ b/src/mask.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -37,8 +37,8 @@ namespace ledger { mask_t::mask_t(const string& pat) : expr() { - TRACE_CTOR(mask_t, "const string&"); *this = pat; + TRACE_CTOR(mask_t, "const string&"); } mask_t& mask_t::operator=(const string& pat) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -38,31 +38,41 @@ namespace ledger { -namespace { - value_t split_cons_expr(expr_t::ptr_op_t op) - { - if (op->kind == expr_t::op_t::O_CONS) { - value_t seq; - seq.push_back(expr_value(op->left())); - - 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; - } - seq.push_back(expr_value(value_op)); +void intrusive_ptr_add_ref(const expr_t::op_t * op) +{ + op->acquire(); +} + +void intrusive_ptr_release(const expr_t::op_t * op) +{ + op->release(); +} + +value_t split_cons_expr(expr_t::ptr_op_t op) +{ + if (op->kind == expr_t::op_t::O_CONS) { + value_t seq; + seq.push_back(expr_value(op->left())); + + 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->has_right() ? next->right() : NULL; + } else { + value_op = next; + next = NULL; } - return seq; - } else { - return expr_value(op); + seq.push_back(expr_value(value_op)); } + return seq; + } else { + return expr_value(op); } +} +namespace { inline void check_type_context(scope_t& scope, value_t& result) { if (scope.type_required() && @@ -76,12 +86,31 @@ namespace { } } -expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth) +expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth, + scope_t * param_scope) { - if (is_ident()) { - DEBUG("expr.compile", "lookup: " << as_ident()); + scope_t * scope_ptr = &scope; + unique_ptr<scope_t> bound_scope; + expr_t::ptr_op_t result; + +#if defined(DEBUG_ON) + if (SHOW_DEBUG("expr.compile")) { + for (int i = 0; i < depth; i++) + ledger::_log_buffer << '.'; + DEBUG("expr.compile", ""); + } +#endif + + assert(kind < LAST); - if (ptr_op_t def = scope.lookup(symbol_t::FUNCTION, as_ident())) { + if (is_ident()) { + DEBUG("expr.compile", "Lookup: " << as_ident() << " in " << scope_ptr); + ptr_op_t def; + if (param_scope) + def = param_scope->lookup(symbol_t::FUNCTION, as_ident()); + if (! def) + def = scope_ptr->lookup(symbol_t::FUNCTION, as_ident()); + if (def) { // Identifier references are first looked up at the point of // definition, and then at the point of every use if they could // not be found there. @@ -91,70 +120,144 @@ expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth) def->dump(*_log_stream, 0); } #endif // defined(DEBUG_ON) - return copy(def); + result = copy(def); } else if (left()) { - return copy(); + result = copy(); + } + else { + result = this; } - return this; } - - if (kind < TERMINALS) - return this; - - if (kind == O_DEFINE) { + else if (is_scope()) { + shared_ptr<scope_t> subscope(new symbol_scope_t(*scope_t::empty_scope)); + set_scope(subscope); + bound_scope.reset(new bind_scope_t(*scope_ptr, *subscope.get())); + scope_ptr = bound_scope.get(); + } + else if (kind < TERMINALS) { + result = this; + } + else if (kind == O_DEFINE) { switch (left()->kind) { - case IDENT: - scope.define(symbol_t::FUNCTION, left()->as_ident(), right()); + case IDENT: { + ptr_op_t node(right()->compile(*scope_ptr, depth + 1, param_scope)); + + DEBUG("expr.compile", + "Defining " << left()->as_ident() << " in " << scope_ptr); + scope_ptr->define(symbol_t::FUNCTION, left()->as_ident(), node); break; + } + case O_CALL: if (left()->left()->is_ident()) { ptr_op_t node(new op_t(op_t::O_LAMBDA)); node->set_left(left()->right()); node->set_right(right()); - scope.define(symbol_t::FUNCTION, left()->left()->as_ident(), node); - } else { - throw_(compile_error, _("Invalid function definition")); + node = node->compile(*scope_ptr, depth + 1, param_scope); + + DEBUG("expr.compile", + "Defining " << left()->left()->as_ident() << " in " << scope_ptr); + scope_ptr->define(symbol_t::FUNCTION, left()->left()->as_ident(), node); + break; } - break; + // fall through... + default: throw_(compile_error, _("Invalid function definition")); } - return wrap_value(value_t()); + result = wrap_value(NULL_VALUE); + } + else if (kind == O_LAMBDA) { + symbol_scope_t params(param_scope ? *param_scope : *scope_t::empty_scope); + + for (ptr_op_t sym = left(); + sym; + sym = sym->has_right() ? sym->right() : NULL) { + ptr_op_t varname = sym->kind == O_CONS ? sym->left() : sym; + + if (! varname->is_ident()) { + std::ostringstream buf; + varname->dump(buf, 0); + throw_(calc_error, + _("Invalid function or lambda parameter: %1") << buf.str()); + } else { + DEBUG("expr.compile", + "Defining function parameter " << varname->as_ident()); + params.define(symbol_t::FUNCTION, varname->as_ident(), + new op_t(PLUG)); + } + } + + ptr_op_t rhs(right()->compile(*scope_ptr, depth + 1, ¶ms)); + if (rhs == right()) + result = this; + else + result = copy(left(), rhs); } - ptr_op_t lhs(left()->compile(scope, depth)); - ptr_op_t rhs(kind > UNARY_OPERATORS && has_right() ? - (kind == O_LOOKUP ? right() : - right()->compile(scope, depth)) : NULL); + if (! result) { + if (! left()) + throw_(calc_error, _("Syntax error")); + + ptr_op_t lhs(left()->compile(*scope_ptr, depth + 1, param_scope)); + ptr_op_t rhs(kind > UNARY_OPERATORS && has_right() ? + (kind == O_LOOKUP ? right() : + right()->compile(*scope_ptr, depth + 1, param_scope)) : NULL); + + if (lhs == left() && (! rhs || rhs == right())) { + result = this; + } else { + ptr_op_t intermediate(copy(lhs, rhs)); + + // Reduce constants immediately if possible + if ((! lhs || lhs->is_value()) && (! rhs || rhs->is_value())) + result = wrap_value(intermediate->calc(*scope_ptr, NULL, depth + 1)); + else + result = intermediate; + } + } - if (lhs == left() && (! rhs || rhs == right())) - return this; +#if defined(DEBUG_ON) + if (SHOW_DEBUG("expr.compile")) { + for (int i = 0; i < depth; i++) + ledger::_log_buffer << '.'; + DEBUG("expr.compile", ""); + } +#endif - ptr_op_t intermediate(copy(lhs, rhs)); + return result; +} - // Reduce constants immediately if possible - if ((! lhs || lhs->is_value()) && (! rhs || rhs->is_value())) - return wrap_value(intermediate->calc(scope, NULL, depth)); +namespace { + expr_t::ptr_op_t lookup_ident(expr_t::ptr_op_t op, scope_t& scope) + { + expr_t::ptr_op_t def = op->left(); - return intermediate; + // If no definition was pre-compiled for this identifier, look it up + // in the current scope. + if (! def || def->kind == expr_t::op_t::PLUG) { + DEBUG("scope.symbols", "Looking for IDENT '" << op->as_ident() << "'"); + def = scope.lookup(symbol_t::FUNCTION, op->as_ident()); + } + if (! def) + throw_(calc_error, _("Unknown identifier '%1'") << op->as_ident()); + return def; + } } value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) { -#if defined(DEBUG_ON) - bool skip_debug = false; -#endif try { value_t result; #if defined(DEBUG_ON) - if (! skip_debug && SHOW_DEBUG("expr.calc")) { + if (SHOW_DEBUG("expr.calc")) { for (int i = 0; i < depth; i++) ledger::_log_buffer << '.'; - ledger::_log_buffer << op_context(this) << " => ..."; + ledger::_log_buffer << op_context(this) << " => ..."; DEBUG("expr.calc", ""); } #endif @@ -165,77 +268,38 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) break; case O_DEFINE: - //result = left()->calc(scope, locus, depth + 1); result = NULL_VALUE; break; - case IDENT: { - ptr_op_t definition = left(); - if (! definition) { - // If no definition was pre-compiled for this identifier, look it - // up in the current scope. - definition = scope.lookup(symbol_t::FUNCTION, as_ident()); + case IDENT: + if (ptr_op_t definition = lookup_ident(this, scope)) { + // Evaluating an identifier is the same as calling its definition + // directly + result = definition->calc(scope, locus, depth + 1); + check_type_context(scope, result); } - if (! definition) - throw_(calc_error, _("Unknown identifier '%1'") << as_ident()); - - // Evaluating an identifier is the same as calling its definition - // directly, so we create an empty call_scope_t to reflect the scope for - // this implicit call. - call_scope_t call_args(scope, locus, depth); - result = definition->compile(call_args, depth + 1) - ->calc(call_args, locus, depth + 1); - check_type_context(scope, result); break; - } 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 - // resolved. - call_scope_t call_args(scope, locus, depth); + // Evaluating a FUNCTION is the same as calling it directly; this + // happens when certain functions-that-look-like-variables (such as + // "amount") are resolved. + call_scope_t call_args(scope, locus, depth + 1); result = as_function()(call_args); check_type_context(scope, result); -#if defined(DEBUG_ON) - skip_debug = true; -#endif break; } - case O_LAMBDA: { - call_scope_t& call_args(downcast<call_scope_t>(scope)); - std::size_t args_count(call_args.size()); - std::size_t args_index(0); - symbol_scope_t call_scope(call_args); - ptr_op_t sym(left()); - - for (; sym; sym = sym->has_right() ? sym->right() : NULL) { - ptr_op_t varname = sym; - if (sym->kind == O_CONS) - varname = sym->left(); - - if (! varname->is_ident()) { - throw_(calc_error, _("Invalid function definition")); - } - else if (args_index == args_count) { - call_scope.define(symbol_t::FUNCTION, varname->as_ident(), - wrap_value(NULL_VALUE)); - } - else { - DEBUG("expr.compile", - "Defining function parameter " << varname->as_ident()); - call_scope.define(symbol_t::FUNCTION, varname->as_ident(), - wrap_value(call_args[args_index++])); - } + case SCOPE: + assert(! is_scope_unset()); + if (is_scope_unset()) { + symbol_scope_t subscope(scope); + result = left()->calc(subscope, locus, depth + 1); + } else { + bind_scope_t bound_scope(scope, *as_scope()); + result = left()->calc(bound_scope, locus, depth + 1); } - - if (args_index < args_count) - throw_(calc_error, - _("Too few arguments in function call (saw %1)") << args_count); - - result = right()->calc(call_scope, locus, depth + 1); break; - } case O_LOOKUP: { context_scope_t context_scope(scope, value_t::SCOPE); @@ -252,43 +316,14 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) break; } - case O_CALL: { - call_scope_t call_args(scope, locus, depth); - if (has_right()) - call_args.set_args(split_cons_expr(right())); - - ptr_op_t func = left(); - const string& name(func->as_ident()); - - func = func->left(); - if (! func) - func = scope.lookup(symbol_t::FUNCTION, name); - if (! func) - throw_(calc_error, _("Calling unknown function '%1'") << name); - -#if defined(DEBUG_ON) - if (! skip_debug && SHOW_DEBUG("expr.calc")) { - for (int i = 0; i < depth; i++) - ledger::_log_buffer << '.'; - ledger::_log_buffer << " args: "; - if (call_args.args.is_sequence()) { - foreach (value_t& arg, call_args) - ledger::_log_buffer << arg << " "; - } else { - ledger::_log_buffer << call_args.args[0] << " "; - } - DEBUG("expr.calc", ""); - } -#endif - - if (func->is_function()) - result = func->as_function()(call_args); - else - result = func->calc(call_args, locus, depth + 1); - + case O_CALL: + result = calc_call(scope, locus, depth); check_type_context(scope, result); break; - } + + case O_LAMBDA: + result = expr_value(this); + break; case O_MATCH: result = (right()->calc(scope, locus, depth + 1).as_mask() @@ -358,7 +393,6 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) case O_QUERY: assert(right()); assert(right()->kind == O_COLON); - if (value_t temp = left()->calc(scope, locus, depth + 1)) result = right()->left()->calc(scope, locus, depth + 1); else @@ -370,63 +404,19 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) break; case O_CONS: - result = left()->calc(scope, locus, depth + 1); - DEBUG("op.cons", "car = " << result); - - if (has_right()) { - value_t temp; - temp.push_back(result); - - ptr_op_t next = right(); - while (next) { - ptr_op_t value_op; - if (next->kind == O_CONS) { - value_op = next->left(); - next = next->right(); - } else { - value_op = next; - next = NULL; - } - temp.push_back(value_op->calc(scope, locus, depth + 1)); - DEBUG("op.cons", "temp now = " << temp); - } - result = temp; - } + result = calc_cons(scope, locus, depth); break; - case O_SEQ: { - symbol_scope_t seq_scope(scope); - - // An O_SEQ is very similar to an O_CONS except that only the last result - // value in the series is kept. O_CONS builds up a list. - // - // Another feature of O_SEQ is that it pushes a new symbol scope onto the - // stack. - result = left()->calc(seq_scope, locus, depth + 1); - - if (has_right()) { - ptr_op_t next = right(); - while (next) { - ptr_op_t value_op; - if (next->kind == O_SEQ) { - value_op = next->left(); - next = next->right(); - } else { - value_op = next; - next = NULL; - } - result = value_op->calc(seq_scope, locus, depth + 1); - } - } + case O_SEQ: + result = calc_seq(scope, locus, depth); break; - } default: throw_(calc_error, _("Unexpected expr node '%1'") << op_context(this)); } #if defined(DEBUG_ON) - if (! skip_debug && SHOW_DEBUG("expr.calc")) { + if (SHOW_DEBUG("expr.calc")) { for (int i = 0; i < depth; i++) ledger::_log_buffer << '.'; ledger::_log_buffer << op_context(this) << " => "; @@ -446,6 +436,184 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) } namespace { + expr_t::ptr_op_t find_definition(expr_t::ptr_op_t op, scope_t& scope, + expr_t::ptr_op_t * locus, const int depth, + int recursion_depth = 0) + { + // If the object we are apply call notation to is a FUNCTION value + // or a O_LAMBDA expression, then this is the object we want to + // call. + if (op->is_function() || op->kind == expr_t::op_t::O_LAMBDA) + return op; + + if (recursion_depth > 256) + throw_(value_error, _("Function recursion_depth too deep (> 256)")); + + // If it's an identifier, look up its definition and see if it's a + // function. + if (op->is_ident()) + return find_definition(lookup_ident(op, scope), scope, + locus, depth, recursion_depth + 1); + + // Value objects might be callable if they contain an expression. + if (op->is_value()) { + value_t def(op->as_value()); + if (is_expr(def)) + return find_definition(as_expr(def), scope, locus, depth, + recursion_depth + 1); + else + throw_(value_error, _("Cannot call %1 as a function") << def.label()); + } + + // Resolve ordinary expressions. + return find_definition(expr_t::op_t::wrap_value(op->calc(scope, locus, + depth + 1)), + scope, locus, depth + 1, recursion_depth + 1); + } + + value_t call_lambda(expr_t::ptr_op_t func, scope_t& scope, + call_scope_t& call_args, expr_t::ptr_op_t * locus, + const int depth) + { + std::size_t args_index(0); + std::size_t args_count(call_args.size()); + + symbol_scope_t args_scope(*scope_t::empty_scope); + + for (expr_t::ptr_op_t sym = func->left(); + sym; + sym = sym->has_right() ? sym->right() : NULL) { + expr_t::ptr_op_t varname = + sym->kind == expr_t::op_t::O_CONS ? sym->left() : sym; + if (! varname->is_ident()) { + throw_(calc_error, _("Invalid function definition")); + } + else if (args_index == args_count) { + DEBUG("expr.calc", "Defining function argument as null: " + << varname->as_ident()); + args_scope.define(symbol_t::FUNCTION, varname->as_ident(), + expr_t::op_t::wrap_value(NULL_VALUE)); + } + else { + DEBUG("expr.calc", "Defining function argument from call_args: " + << varname->as_ident()); + args_scope.define(symbol_t::FUNCTION, varname->as_ident(), + expr_t::op_t::wrap_value(call_args[args_index++])); + } + } + + if (args_index < args_count) + throw_(calc_error, + _("Too few arguments in function call (saw %1, wanted %2)") + << args_count << args_index); + + if (func->right()->is_scope()) { + bind_scope_t outer_scope(scope, *func->right()->as_scope()); + bind_scope_t bound_scope(outer_scope, args_scope); + + return func->right()->left()->calc(bound_scope, locus, depth + 1); + } else { + return func->right()->calc(args_scope, locus, depth + 1); + } + } +} + + +value_t expr_t::op_t::call(const value_t& args, scope_t& scope, + ptr_op_t * locus, const int depth) +{ + call_scope_t call_args(scope, locus, depth + 1); + call_args.set_args(args); + + if (is_function()) + return as_function()(call_args); + else if (kind == O_LAMBDA) + return call_lambda(this, scope, call_args, locus, depth); + else + return find_definition(this, scope, locus, depth) + ->calc(call_args, locus, depth); +} + +value_t expr_t::op_t::calc_call(scope_t& scope, ptr_op_t * locus, + const int depth) +{ + ptr_op_t func = left(); + string name = func->is_ident() ? func->as_ident() : "<value expr>"; + + func = find_definition(func, scope, locus, depth); + + call_scope_t call_args(scope, locus, depth + 1); + if (has_right()) + call_args.set_args(split_cons_expr(right())); + + try { + if (func->is_function()) { + return func->as_function()(call_args); + } else { + assert(func->kind == O_LAMBDA); + return call_lambda(func, scope, call_args, locus, depth); + } + } + catch (const std::exception&) { + add_error_context(_("While calling function '%1 %2':" << name + << call_args.args)); + throw; + } +} + +value_t expr_t::op_t::calc_cons(scope_t& scope, ptr_op_t * locus, + const int depth) +{ + value_t result = left()->calc(scope, locus, depth + 1); + if (has_right()) { + value_t temp; + temp.push_back(result); + + ptr_op_t next = right(); + while (next) { + ptr_op_t value_op; + if (next->kind == O_CONS) { + value_op = next->left(); + next = next->has_right() ? next->right() : NULL; + } else { + value_op = next; + next = NULL; + } + temp.push_back(value_op->calc(scope, locus, depth + 1)); + } + result = temp; + } + return result; +} + +value_t expr_t::op_t::calc_seq(scope_t& scope, ptr_op_t * locus, + const int depth) +{ + // An O_SEQ is very similar to an O_CONS except that only the last + // result value in the series is kept. O_CONS builds up a list. + // + // Another feature of O_SEQ is that it pushes a new symbol scope onto + // the stack. We evaluate the left side here to catch any + // side-effects, such as definitions in the case of 'x = 1; x'. + value_t result = left()->calc(scope, locus, depth + 1); + if (has_right()) { + ptr_op_t next = right(); + while (next) { + ptr_op_t value_op; + if (next->kind == O_SEQ) { + value_op = next->left(); + next = next->right(); + } else { + value_op = next; + next = NULL; + } + result = value_op->calc(scope, locus, depth + 1); + } + } + return result; +} + +namespace { bool print_cons(std::ostream& out, const expr_t::const_ptr_op_t op, const expr_t::op_t::context_t& context) { @@ -476,9 +644,8 @@ namespace { if (op->has_right()) { out << "; "; - - if (op->right()->kind == expr_t::op_t::O_CONS) - found = print_cons(out, op->right(), context); + if (op->right()->kind == expr_t::op_t::O_SEQ) + found = print_seq(out, op->right(), context); else if (op->right()->print(out, context)) found = true; } @@ -515,6 +682,11 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const out << "<FUNCTION>"; break; + case SCOPE: + if (left() && left()->print(out, context)) + found = true; + break; + case O_NOT: out << "! "; if (left() && left()->print(out, context)) @@ -625,7 +797,6 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const case O_CONS: found = print_cons(out, this, context); break; - case O_SEQ: found = print_seq(out, this, context); break; @@ -713,6 +884,10 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const out << " "; switch (kind) { + case PLUG: + out << "PLUG"; + break; + case VALUE: out << "VALUE: "; as_value().dump(out); @@ -726,6 +901,14 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const out << "FUNCTION"; break; + case SCOPE: + out << "SCOPE: "; + if (is_scope_unset()) + out << "null"; + else + out << as_scope().get(); + break; + case O_DEFINE: out << "O_DEFINE"; break; case O_LOOKUP: out << "O_LOOKUP"; break; case O_LAMBDA: out << "O_LAMBDA"; break; @@ -765,7 +948,7 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const // An identifier is a special non-terminal, in that its left() can // hold the compiled definition of the identifier. - if (kind > TERMINALS || is_ident()) { + if (kind > TERMINALS || is_scope() || is_ident()) { if (left()) { left()->dump(out, depth + 1); if (kind > UNARY_OPERATORS && has_right()) @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -58,21 +58,25 @@ private: mutable short refc; ptr_op_t left_; - variant<ptr_op_t, // used by all binary operators + variant<boost::blank, + ptr_op_t, // used by all binary operators value_t, // used by constant VALUE string, // used by constant IDENT - expr_t::func_t // used by terminal FUNCTION + expr_t::func_t, // used by terminal FUNCTION + shared_ptr<scope_t> // used by terminal SCOPE > data; public: enum kind_t { // Constants + PLUG, VALUE, IDENT, CONSTANTS, FUNCTION, + SCOPE, TERMINALS, @@ -173,7 +177,7 @@ public: return kind == FUNCTION; } expr_t::func_t& as_function_lval() { - assert(kind == FUNCTION); + assert(is_function()); return boost::get<expr_t::func_t>(data); } const expr_t::func_t& as_function() const { @@ -183,21 +187,41 @@ public: data = val; } + bool is_scope() const { + return kind == SCOPE; + } + bool is_scope_unset() const { + return data.which() == 0; + } + shared_ptr<scope_t> as_scope_lval() { + assert(is_scope()); + return boost::get<shared_ptr<scope_t> >(data); + } + const shared_ptr<scope_t> as_scope() const { + return const_cast<op_t *>(this)->as_scope_lval(); + } + void set_scope(shared_ptr<scope_t> val) { + data = val; + } + + // These three functions must use 'kind == IDENT' rather than + // 'is_ident()', because they are called before the `data' member gets + // set, which is_ident() tests. ptr_op_t& left() { - assert(kind > TERMINALS || kind == IDENT); + assert(kind > TERMINALS || kind == IDENT || is_scope()); return left_; } const ptr_op_t& left() const { - assert(kind > TERMINALS || kind == IDENT); + assert(kind > TERMINALS || kind == IDENT || is_scope()); return left_; } void set_left(const ptr_op_t& expr) { - assert(kind > TERMINALS || kind == IDENT); + assert(kind > TERMINALS || kind == IDENT || is_scope()); left_ = expr; } ptr_op_t& as_op_lval() { - assert(kind > TERMINALS || kind == IDENT); + assert(kind > TERMINALS || is_ident()); return boost::get<ptr_op_t>(data); } const ptr_op_t& as_op() const { @@ -219,7 +243,7 @@ public: bool has_right() const { if (kind < TERMINALS) return false; - return as_op(); + return data.which() != 0 && as_op(); } private: @@ -237,12 +261,8 @@ private: checked_delete(this); } - friend inline void intrusive_ptr_add_ref(const op_t * op) { - op->acquire(); - } - friend inline void intrusive_ptr_release(const op_t * op) { - op->release(); - } + friend void intrusive_ptr_add_ref(const op_t * op); + friend void intrusive_ptr_release(const op_t * op); ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const { ptr_op_t node(new_node(kind, _left, _right)); @@ -255,10 +275,14 @@ public: static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL, ptr_op_t _right = NULL); - ptr_op_t compile(scope_t& scope, const int depth = 0); + ptr_op_t compile(scope_t& scope, const int depth = 0, + scope_t * param_scope = NULL); value_t calc(scope_t& scope, ptr_op_t * locus = NULL, const int depth = 0); + value_t call(const value_t& args, scope_t& scope, + ptr_op_t * locus = NULL, const int depth = 0); + struct context_t { ptr_op_t expr_op; @@ -284,6 +308,12 @@ public: static ptr_op_t wrap_value(const value_t& val); static ptr_op_t wrap_functor(expr_t::func_t fobj); + static ptr_op_t wrap_scope(shared_ptr<scope_t> sobj); + +private: + value_t calc_call(scope_t& scope, ptr_op_t * locus, const int depth); + value_t calc_cons(scope_t& scope, ptr_op_t * locus, const int depth); + value_t calc_seq(scope_t& scope, ptr_op_t * locus, const int depth); #if defined(HAVE_BOOST_SERIALIZATION) private: @@ -295,13 +325,13 @@ private: void serialize(Archive& ar, const unsigned int /* version */) { ar & refc; ar & kind; - if (Archive::is_loading::value || ! left_ || left_->kind != FUNCTION) { + if (Archive::is_loading::value || ! left_ || ! left_->is_function()) { ar & left_; } else { ptr_op_t temp_op; ar & temp_op; } - if (Archive::is_loading::value || kind == VALUE || kind == IDENT || + if (Archive::is_loading::value || is_value() || is_ident() || (kind > UNARY_OPERATORS && (! has_right() || ! right()->is_function()))) { ar & data; @@ -341,6 +371,8 @@ expr_t::op_t::wrap_functor(expr_t::func_t fobj) { string op_context(const expr_t::ptr_op_t op, const expr_t::ptr_op_t locus = NULL); +value_t split_cons_expr(expr_t::ptr_op_t op); + } // namespace ledger #endif // _OP_H diff --git a/src/option.cc b/src/option.cc index 2843c775..418980bd 100644 --- a/src/option.cc +++ b/src/option.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -89,7 +89,6 @@ namespace { catch (const std::exception&) { if (name[0] == '-') add_error_context(_("While parsing option '%1'") << name); - else add_error_context(_("While parsing environent variable '%1'") << name); throw; diff --git a/src/option.h b/src/option.h index 8f89d081..772f2b01 100644 --- a/src/option.h +++ b/src/option.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -61,16 +61,16 @@ protected: option_t& operator=(const option_t&); public: - T * parent; - value_t value; - bool wants_arg; + T * parent; + string value; + bool wants_arg; option_t(const char * _name, const char _ch = '\0') : name(_name), name_len(std::strlen(name)), ch(_ch), handled(false), parent(NULL), value(), wants_arg(name[name_len - 1] == '_') { - TRACE_CTOR(option_t, "const char *, const char"); DEBUG("option.names", "Option: " << name); + TRACE_CTOR(option_t, "const char *, const char"); } option_t(const option_t& other) : name(other.name), @@ -94,7 +94,8 @@ public: out << std::right << desc(); if (wants_arg) { out << " = "; - value.print(out, 42); + out.width(42); + out << value; } else { out.width(45); out << ' '; @@ -123,43 +124,48 @@ public: return handled; } - string& str() { + string str() const { assert(handled); - if (! value) + if (value.empty()) throw_(std::runtime_error, _("No argument provided for %1") << desc()); - return value.as_string_lval(); + return value; } - string str() const { - assert(handled); - if (! value) - throw_(std::runtime_error, _("No argument provided for %1") << desc()); - return value.as_string(); + void on(const char * whence) { + on(string(whence)); } + void on(const optional<string>& whence) { + handler_thunk(whence); - void on_only(const optional<string>& whence) { handled = true; source = whence; } - void on(const optional<string>& whence, const string& str) { - on_with(whence, string_value(str)); + + void on(const char * whence, const string& str) { + on(string(whence), str); } - virtual void on_with(const optional<string>& whence, - const value_t& val) { + void on(const optional<string>& whence, const string& str) { + string before = value; + + handler_thunk(whence, str); + + if (value == before) + value = str; + handled = true; - value = val; source = whence; } void off() { handled = false; - value = value_t(); + value = ""; source = none; } - virtual void handler_thunk(call_scope_t&) {} + virtual void handler_thunk(const optional<string>&) {} + virtual void handler_thunk(const optional<string>&, const string&) {} - virtual void handler(call_scope_t& args) { + value_t handler(call_scope_t& args) { if (wants_arg) { if (args.size() < 2) throw_(std::runtime_error, _("No argument provided for %1") << desc()); @@ -167,7 +173,7 @@ public: throw_(std::runtime_error, _("To many arguments provided for %1") << desc()); else if (! args[0].is_string()) throw_(std::runtime_error, _("Context argument for %1 not a string") << desc()); - on_with(args.get<string>(0), args[1]); + on(args.get<string>(0), args.get<string>(1)); } else if (args.size() < 1) { throw_(std::runtime_error, _("No argument provided for %1") << desc()); @@ -176,27 +182,18 @@ public: throw_(std::runtime_error, _("Context argument for %1 not a string") << desc()); } else { - on_only(args.get<string>(0)); + on(args.get<string>(0)); } - - handler_thunk(args); - } - - virtual value_t handler_wrapper(call_scope_t& args) { - handler(args); return true; } virtual value_t operator()(call_scope_t& args) { if (! args.empty()) { args.push_front(string_value("?expr")); - return handler_wrapper(args); + return handler(args); } else if (wants_arg) { - if (handled) - return value; - else - return NULL_VALUE; + return string_value(value); } else { return handled; @@ -213,17 +210,18 @@ public: name ## option_t() : option_t<type>(#name), base #define DECL1(type, name, vartype, var, value) \ vartype var ; \ - name ## option_t() : option_t<type>(#name), var(value) + name ## option_t() : option_t<type>(#name), var value -#define DO() virtual void handler_thunk(call_scope_t&) -#define DO_(var) virtual void handler_thunk(call_scope_t& var) +#define DO() virtual void handler_thunk(const optional<string>& whence) +#define DO_(var) virtual void handler_thunk(const optional<string>& whence, \ + const string& var) #define END(name) name ## handler #define COPY_OPT(name, other) name ## handler(other.name ## handler) #define MAKE_OPT_HANDLER(type, x) \ - expr_t::op_t::wrap_functor(bind(&option_t<type>::handler_wrapper, x, _1)) + expr_t::op_t::wrap_functor(bind(&option_t<type>::handler, x, _1)) #define MAKE_OPT_FUNCTOR(type, x) \ expr_t::op_t::wrap_functor(bind(&option_t<type>::operator(), x, _1)) @@ -284,6 +282,10 @@ inline bool is_eq(const char * p, const char * n) { } \ END(name) +#define OTHER(name) \ + parent->HANDLER(name).parent = parent; \ + parent->HANDLER(name) + bool process_option(const string& whence, const string& name, scope_t& scope, const char * arg, const string& varname); @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -45,8 +45,6 @@ posts_to_org_table::posts_to_org_table(report_t& _report, const optional<string>& _prepend_format) : report(_report), last_xact(NULL), last_post(NULL) { - TRACE_CTOR(posts_to_org_table, "report&, optional<string>"); - first_line_format.parse_format ("|%(format_date(date))" "|%(code)" @@ -79,6 +77,8 @@ posts_to_org_table::posts_to_org_table(report_t& _report, if (_prepend_format) prepend_format.parse_format(*_prepend_format); + + TRACE_CTOR(posts_to_org_table, "report&, optional<string>"); } void posts_to_org_table::flush() @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/output.cc b/src/output.cc index b26881a3..742000bd 100644 --- a/src/output.cc +++ b/src/output.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -47,8 +47,6 @@ format_posts::format_posts(report_t& _report, : report(_report), prepend_width(_prepend_width), last_xact(NULL), last_post(NULL), first_report_title(true) { - TRACE_CTOR(format_posts, "report&, const string&, bool"); - const char * f = format.c_str(); if (const char * p = std::strstr(f, "%/")) { @@ -70,6 +68,8 @@ format_posts::format_posts(report_t& _report, if (_prepend_format) prepend_format.parse_format(*_prepend_format); + + TRACE_CTOR(format_posts, "report&, const string&, bool"); } void format_posts::flush() @@ -131,8 +131,6 @@ format_accounts::format_accounts(report_t& _report, : report(_report), prepend_width(_prepend_width), disp_pred(), first_report_title(true) { - TRACE_CTOR(format_accounts, "report&, const string&"); - const char * f = format.c_str(); if (const char * p = std::strstr(f, "%/")) { @@ -154,6 +152,8 @@ format_accounts::format_accounts(report_t& _report, if (_prepend_format) prepend_format.parse_format(*_prepend_format); + + TRACE_CTOR(format_accounts, "report&, const string&"); } std::size_t format_accounts::post_account(account_t& account, const bool flat) diff --git a/src/output.h b/src/output.h index ac3925c4..281f69b6 100644 --- a/src/output.h +++ b/src/output.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/parser.cc b/src/parser.cc index a18fa552..360ac93d 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -54,20 +54,6 @@ expr_t::parser_t::parse_value_term(std::istream& in, node = new op_t(op_t::IDENT); node->set_ident(ident); - - // An identifier followed by ( represents a function call - tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); - if (tok.kind == token_t::LPAREN) { - op_t::kind_t kind = op_t::O_CALL; - ptr_op_t call_node(new op_t(kind)); - call_node->set_left(node); - node = call_node; - - push_token(tok); // let the parser see it again - node->set_right(parse_value_expr(in, tflags.plus_flags(PARSE_SINGLE))); - } else { - push_token(tok); - } break; } @@ -85,8 +71,9 @@ 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, +expr_t::parser_t::parse_call_expr(std::istream& in, const parse_flags_t& tflags) const { ptr_op_t node(parse_value_term(in, tflags)); @@ -94,11 +81,36 @@ expr_t::parser_t::parse_dot_expr(std::istream& in, if (node && ! tflags.has_flags(PARSE_SINGLE)) { while (true) { token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + if (tok.kind == token_t::LPAREN) { + ptr_op_t prev(node); + node = new op_t(op_t::O_CALL); + node->set_left(prev); + push_token(tok); // let the parser see the '(' again + node->set_right(parse_value_expr(in, tflags.plus_flags(PARSE_SINGLE))); + } else { + push_token(tok); + break; + } + } + } + + 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_call_expr(in, tflags)); + + if (node && ! tflags.has_flags(PARSE_SINGLE)) { + while (true) { + token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); 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_value_term(in, tflags)); + node->set_right(parse_call_expr(in, tflags)); if (! node->right()) throw_(parse_error, _("%1 operator not followed by argument") << tok.symbol); @@ -434,7 +446,6 @@ expr_t::parser_t::parse_comma_expr(std::istream& in, ptr_op_t prev(node); node = new op_t(op_t::O_CONS); node->set_left(prev); - next = node; } @@ -471,7 +482,9 @@ expr_t::parser_t::parse_lambda_expr(std::istream& in, ptr_op_t prev(node); node = new op_t(op_t::O_LAMBDA); node->set_left(prev); - node->set_right(parse_querycolon_expr(in, tflags)); + ptr_op_t scope(new op_t(op_t::SCOPE)); + scope->set_left(parse_querycolon_expr(in, tflags)); + node->set_right(scope); } else { push_token(tok); } @@ -493,7 +506,9 @@ expr_t::parser_t::parse_assign_expr(std::istream& in, ptr_op_t prev(node); node = new op_t(op_t::O_DEFINE); node->set_left(prev); - node->set_right(parse_lambda_expr(in, tflags)); + ptr_op_t scope(new op_t(op_t::SCOPE)); + scope->set_left(parse_lambda_expr(in, tflags)); + node->set_right(scope); } else { push_token(tok); } @@ -509,24 +524,20 @@ expr_t::parser_t::parse_value_expr(std::istream& in, ptr_op_t node(parse_assign_expr(in, tflags)); if (node && ! tflags.has_flags(PARSE_SINGLE)) { - ptr_op_t next; + ptr_op_t chain; while (true) { token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); - if (tok.kind == token_t::SEMI) { - if (! next) { - ptr_op_t prev(node); - node = new op_t(op_t::O_SEQ); - node->set_left(prev); - - next = node; + ptr_op_t seq(new op_t(op_t::O_SEQ)); + if (! chain) { + seq->set_left(node); + node = seq; + } else { + seq->set_left(chain->right()); + chain->set_right(seq); } - - ptr_op_t chain(new op_t(op_t::O_SEQ)); - chain->set_left(parse_assign_expr(in, tflags)); - - next->set_right(chain); - next = chain; + seq->set_right(parse_assign_expr(in, tflags)); + chain = seq; } else { push_token(tok); break; diff --git a/src/parser.h b/src/parser.h index 09e12d95..db16a919 100644 --- a/src/parser.h +++ b/src/parser.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -81,6 +81,8 @@ class expr_t::parser_t : public noncopyable ptr_op_t parse_value_term(std::istream& in, const parse_flags_t& flags) const; + ptr_op_t parse_call_expr(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, diff --git a/src/pool.cc b/src/pool.cc index 65edbd6a..61b5bef2 100644 --- a/src/pool.cc +++ b/src/pool.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -35,6 +35,7 @@ #include "commodity.h" #include "annotate.h" #include "pool.h" +#include "history.h" #include "quotes.h" namespace ledger { @@ -46,16 +47,16 @@ commodity_pool_t::commodity_pool_t() quote_leeway(86400), get_quotes(false), get_commodity_quote(commodity_quote_from_script) { - TRACE_CTOR(commodity_pool_t, ""); null_commodity = create(""); null_commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); + TRACE_CTOR(commodity_pool_t, ""); } commodity_t * commodity_pool_t::create(const string& symbol) { shared_ptr<commodity_t::base_t> base_commodity(new commodity_t::base_t(symbol)); - std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity)); + shared_ptr<commodity_t> commodity(new commodity_t(this, base_commodity)); DEBUG("pool.commodities", "Creating base commodity " << symbol); @@ -66,25 +67,19 @@ commodity_t * commodity_pool_t::create(const string& symbol) *commodity->qualified_symbol += "\""; } - DEBUG("pool.commodities", - "Creating commodity '" << commodity->symbol() << "'"); + DEBUG("pool.commodities", "Creating commodity '" << symbol << "'"); - std::pair<commodities_map::iterator, bool> result - = commodities.insert(commodities_map::value_type(commodity->mapping_key(), - commodity.get())); +#if defined(DEBUG_ON) + std::pair<commodities_map::iterator, bool> result = +#endif + commodities.insert(commodities_map::value_type(symbol, commodity)); +#if defined(DEBUG_ON) assert(result.second); +#endif - return commodity.release(); -} - -commodity_t * commodity_pool_t::find_or_create(const string& symbol) -{ - DEBUG("pool.commodities", "Find-or-create commodity " << symbol); + commodity_price_history.add_commodity(*commodity.get()); - commodity_t * commodity = find(symbol); - if (commodity) - return commodity; - return create(symbol); + return commodity.get(); } commodity_t * commodity_pool_t::find(const string& symbol) @@ -93,91 +88,111 @@ commodity_t * commodity_pool_t::find(const string& symbol) commodities_map::const_iterator i = commodities.find(symbol); if (i != commodities.end()) - return (*i).second; + return (*i).second.get(); return NULL; } -commodity_t * -commodity_pool_t::create(const string& symbol, const annotation_t& details) +commodity_t * commodity_pool_t::find_or_create(const string& symbol) { - commodity_t * new_comm = create(symbol); - if (! new_comm) - return NULL; - - if (details) - return find_or_create(*new_comm, details); - else - return new_comm; + DEBUG("pool.commodities", "Find-or-create commodity " << symbol); + if (commodity_t * commodity = find(symbol)) + return commodity; + return create(symbol); } -string commodity_pool_t::make_qualified_name(const commodity_t& comm, - const annotation_t& details) +commodity_t * commodity_pool_t::alias(const string& name, commodity_t& referent) { - assert(details); + commodities_map::const_iterator i = commodities.find(referent.base_symbol()); + assert(i != commodities.end()); - if (details.price && details.price->sign() < 0) - throw_(amount_error, _("A commodity's price may not be negative")); + std::pair<commodities_map::iterator, bool> result + = commodities.insert(commodities_map::value_type(name, (*i).second)); + assert(result.second); - std::ostringstream name; - comm.print(name); - details.print(name, comm.pool().keep_base); + return (*result.first).second.get(); +} -#if defined(DEBUG_ON) - if (comm.qualified_symbol) - DEBUG("pool.commodities", "make_qualified_name for " - << *comm.qualified_symbol << std::endl << details); -#endif - DEBUG("pool.commodities", "qualified_name is " << name.str()); +commodity_t * +commodity_pool_t::create(const string& symbol, const annotation_t& details) +{ + DEBUG("pool.commodities", "commodity_pool_t::create[ann] " + << "symbol " << symbol << std::endl << details); - return name.str(); + if (details) + return create(*find_or_create(symbol), details); + else + return create(symbol); } commodity_t * commodity_pool_t::find(const string& symbol, const annotation_t& details) { - commodity_t * comm = find(symbol); - if (! comm) + DEBUG("pool.commodities", "commodity_pool_t::find[ann] " + << "symbol " << symbol << std::endl << details); + + annotated_commodities_map::const_iterator i = + annotated_commodities.find + (annotated_commodities_map::key_type(symbol, details)); + if (i != annotated_commodities.end()) { + DEBUG("pool.commodities", "commodity_pool_t::find[ann] found " + << "symbol " << (*i).second->base_symbol() << std::endl + << as_annotated_commodity(*(*i).second.get()).details); + return (*i).second.get(); + } else { return NULL; + } +} - if (details) { - string name = make_qualified_name(*comm, details); +commodity_t * +commodity_pool_t::find_or_create(const string& symbol, + const annotation_t& details) +{ + DEBUG("pool.commodities", "commodity_pool_t::find_or_create[ann] " + << "symbol " << symbol << std::endl << details); - if (commodity_t * ann_comm = find(name)) { + if (details) { + if (commodity_t * ann_comm = find(symbol, details)) { assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); return ann_comm; + } else { + return create(symbol, details); } - return NULL; } else { - return comm; + return find_or_create(symbol); } } commodity_t * -commodity_pool_t::find_or_create(const string& symbol, - const annotation_t& details) +commodity_pool_t::find_or_create(commodity_t& comm, const annotation_t& details) { - commodity_t * comm = find_or_create(symbol); - if (! comm) - return NULL; + DEBUG("pool.commodities", "commodity_pool_t::find_or_create[ann:comm] " + << "symbol " << comm.base_symbol() << std::endl << details); - if (details) - return find_or_create(*comm, details); - else - return comm; + if (details) { + if (commodity_t * ann_comm = find(comm.base_symbol(), details)) { + assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); + return ann_comm; + } else { + return create(comm, details); + } + } else { + return &comm; + } } -commodity_t * +annotated_commodity_t * commodity_pool_t::create(commodity_t& comm, - const annotation_t& details, - const string& mapping_key) + const annotation_t& details) { + DEBUG("pool.commodities", "commodity_pool_t::create[ann:comm] " + << "symbol " << comm.base_symbol() << std::endl << details); + assert(comm); assert(! comm.has_annotation()); assert(details); - assert(! mapping_key.empty()); - unique_ptr<commodity_t> commodity - (new annotated_commodity_t(&comm, details)); + shared_ptr<annotated_commodity_t> + commodity(new annotated_commodity_t(&comm, details)); comm.add_flags(COMMODITY_SAW_ANNOTATED); if (details.price) { @@ -187,39 +202,21 @@ commodity_pool_t::create(commodity_t& comm, comm.add_flags(COMMODITY_SAW_ANN_PRICE_FLOAT); } - commodity->qualified_symbol = comm.symbol(); - assert(! commodity->qualified_symbol->empty()); - DEBUG("pool.commodities", "Creating annotated commodity " - << "symbol " << commodity->symbol() - << " key " << mapping_key << std::endl << details); + << "symbol " << commodity->base_symbol() + << std::endl << details); - // Add the fully annotated name to the map, so that this symbol may - // quickly be found again. - commodity->mapping_key_ = mapping_key; - - std::pair<commodities_map::iterator, bool> result - = commodities.insert(commodities_map::value_type(mapping_key, - commodity.get())); +#if defined(DEBUG_ON) + std::pair<annotated_commodities_map::iterator, bool> result = +#endif + annotated_commodities.insert(annotated_commodities_map::value_type + (annotated_commodities_map::key_type + (comm.base_symbol(), details), commodity)); +#if defined(DEBUG_ON) assert(result.second); +#endif - return commodity.release(); -} - -commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, - const annotation_t& details) -{ - assert(comm); - assert(details); - - string name = make_qualified_name(comm, details); - assert(! name.empty()); - - if (commodity_t * ann_comm = find(name)) { - assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); - return ann_comm; - } - return create(comm, details, name); + return commodity.get(); } void commodity_pool_t::exchange(commodity_t& commodity, @@ -240,6 +237,7 @@ cost_breakdown_t commodity_pool_t::exchange(const amount_t& amount, const amount_t& cost, const bool is_per_unit, + const bool add_price, const optional<datetime_t>& moment, const optional<string>& tag) { @@ -261,6 +259,9 @@ commodity_pool_t::exchange(const amount_t& amount, amount_t per_unit_cost = (is_per_unit || amount.is_realzero()) ? cost.abs() : (cost / amount).abs(); + if (! cost.has_commodity()) + per_unit_cost.clear_commodity(); + DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost); // Do not record commodity exchanges where amount's commodity has a @@ -269,8 +270,10 @@ commodity_pool_t::exchange(const amount_t& amount, if (! per_unit_cost.is_realzero() && (current_annotation == NULL || ! (current_annotation->price && - current_annotation->has_flags(ANNOTATION_PRICE_FIXATED)))) + current_annotation->has_flags(ANNOTATION_PRICE_FIXATED))) && + commodity.referent() != per_unit_cost.commodity().referent()) { exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME()); + } cost_breakdown_t breakdown; breakdown.final_cost = ! is_per_unit ? cost : cost * amount.abs(); @@ -382,76 +385,4 @@ commodity_pool_t::parse_price_expression(const std::string& str, return NULL; } -void commodity_pool_t::print_pricemap(std::ostream& out, - const keep_details_t& keep, - const optional<datetime_t>& moment) -{ - typedef std::map<commodity_t *, commodity_t *> comm_map_t; - - comm_map_t comm_map; - - foreach (const commodities_map::value_type& comm_pair, commodities) { - commodity_t * comm(&comm_pair.second->strip_annotations(keep)); - comm_map.insert(comm_map_t::value_type(comm, NULL)); - } - - out << "digraph commodities {\n"; - - foreach (const comm_map_t::value_type& comm_pair, comm_map) { - commodity_t * comm(comm_pair.first); - if (comm->has_flags(COMMODITY_BUILTIN)) - continue; - - out << " "; - if (commodity_t::symbol_needs_quotes(comm->symbol())) - out << comm->symbol() << ";\n"; - else - out << "\"" << comm->symbol() << "\";\n"; - - if (! comm->has_flags(COMMODITY_NOMARKET) && - (! commodity_pool_t::current_pool->default_commodity || - comm != commodity_pool_t::current_pool->default_commodity)) { - if (optional<commodity_t::varied_history_t&> vhist = - comm->varied_history()) { - foreach (const commodity_t::history_by_commodity_map::value_type& pair, - vhist->histories) { - datetime_t most_recent; - amount_t most_recent_amt; - foreach (const commodity_t::history_map::value_type& inner_pair, - pair.second.prices) { - if ((most_recent.is_not_a_date_time() || - inner_pair.first > most_recent) && - (! moment || inner_pair.first <= moment)) { - most_recent = inner_pair.first; - most_recent_amt = inner_pair.second; - } - } - - if (! most_recent.is_not_a_date_time()) { - out << " "; - if (commodity_t::symbol_needs_quotes(comm->symbol())) - out << comm->symbol(); - else - out << "\"" << comm->symbol() << "\""; - - out << " -> "; - - if (commodity_t::symbol_needs_quotes(pair.first->symbol())) - out << pair.first->symbol(); - else - out << "\"" << pair.first->symbol() << "\""; - - out << " [label=\"" - << most_recent_amt.number() << "\\n" - << format_date(most_recent.date(), FMT_WRITTEN) - << "\" fontcolor=\"#008e28\"];\n"; - } - } - } - } - } - - out << "}\n"; -} - } // namespace ledger @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -46,6 +46,9 @@ #ifndef _POOL_H #define _POOL_H +#include "history.h" +#include "annotate.h" + namespace ledger { struct cost_breakdown_t @@ -64,50 +67,47 @@ public: * explicitly by calling the create methods of commodity_pool_t, or * implicitly by parsing a commoditized amount. */ - typedef std::map<string, commodity_t *> commodities_map; - - commodities_map commodities; - commodity_t * null_commodity; - commodity_t * default_commodity; - - bool keep_base; // --base + typedef std::map<string, shared_ptr<commodity_t> > commodities_map; + typedef std::map<std::pair<string, annotation_t>, + shared_ptr<annotated_commodity_t> > annotated_commodities_map; - optional<path> price_db; // --price-db= - long quote_leeway; // --leeway= - bool get_quotes; // --download + commodities_map commodities; + annotated_commodities_map annotated_commodities; + commodity_history_t commodity_price_history; + commodity_t * null_commodity; + commodity_t * default_commodity; - static shared_ptr<commodity_pool_t> current_pool; + bool keep_base; // --base + optional<path> price_db; // --price-db= + long quote_leeway; // --leeway= + bool get_quotes; // --download function<optional<price_point_t> - (commodity_t& commodity, const optional<commodity_t&>& in_terms_of)> + (commodity_t& commodity, const commodity_t * in_terms_of)> get_commodity_quote; - explicit commodity_pool_t(); + static shared_ptr<commodity_pool_t> current_pool; + explicit commodity_pool_t(); virtual ~commodity_pool_t() { TRACE_DTOR(commodity_pool_t); - foreach (commodities_map::value_type& pair, commodities) - checked_delete(pair.second); } - string make_qualified_name(const commodity_t& comm, - const annotation_t& details); - commodity_t * create(const string& symbol); commodity_t * find(const string& name); commodity_t * find_or_create(const string& symbol); + commodity_t * alias(const string& name, commodity_t& referent); - commodity_t * create(const string& symbol, const annotation_t& details); - commodity_t * find(const string& symbol, const annotation_t& details); + commodity_t * create(const string& symbol, + const annotation_t& details); + commodity_t * find(const string& symbol, + const annotation_t& details); commodity_t * find_or_create(const string& symbol, const annotation_t& details); + commodity_t * find_or_create(commodity_t& comm, const annotation_t& details); - commodity_t * create(commodity_t& comm, - const annotation_t& details, - const string& mapping_key); - - commodity_t * find_or_create(commodity_t& comm, - const annotation_t& details); + annotated_commodity_t * create(commodity_t& comm, + const annotation_t& details); // Exchange one commodity for another, while recording the factored price. @@ -118,6 +118,7 @@ public: cost_breakdown_t exchange(const amount_t& amount, const amount_t& cost, const bool is_per_unit = false, + const bool add_price = true, const optional<datetime_t>& moment = none, const optional<string>& tag = none); @@ -131,12 +132,6 @@ public: const bool add_prices = true, const optional<datetime_t>& moment = none); - // Output the commodity price map for a given date as a DOT file - - void print_pricemap(std::ostream& out, - const keep_details_t& keep, - const optional<datetime_t>& moment = none); - #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ @@ -147,6 +142,7 @@ private: void serialize(Archive& ar, const unsigned int /* version */) { ar & current_pool; ar & commodities; + ar & annotated_commodities; ar & null_commodity; ar & default_commodity; ar & keep_base; diff --git a/src/post.cc b/src/post.cc index b40e31f0..0564eaca 100644 --- a/src/post.cc +++ b/src/post.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -36,6 +36,7 @@ #include "account.h" #include "journal.h" #include "format.h" +#include "pool.h" namespace ledger { @@ -48,9 +49,9 @@ bool post_t::has_tag(const string& tag, bool inherit) const return false; } -bool post_t::has_tag(const mask_t& tag_mask, +bool post_t::has_tag(const mask_t& tag_mask, const optional<mask_t>& value_mask, - bool inherit) const + bool inherit) const { if (item_t::has_tag(tag_mask, value_mask)) return true; @@ -68,9 +69,9 @@ optional<value_t> post_t::get_tag(const string& tag, bool inherit) const return none; } -optional<value_t> post_t::get_tag(const mask_t& tag_mask, +optional<value_t> post_t::get_tag(const mask_t& tag_mask, const optional<mask_t>& value_mask, - bool inherit) const + bool inherit) const { if (optional<value_t> value = item_t::get_tag(tag_mask, value_mask)) return value; @@ -91,21 +92,15 @@ date_t post_t::date() const if (xdata_ && is_valid(xdata_->date)) return xdata_->date; - if (item_t::use_effective_date) { - if (_date_eff) - return *_date_eff; - else if (xact && xact->_date_eff) - return *xact->_date_eff; + if (item_t::use_aux_date) { + if (optional<date_t> aux = aux_date()) + return *aux; } - if (! _date) { - assert(xact); - return xact->date(); - } - return *_date; + return primary_date(); } -date_t post_t::actual_date() const +date_t post_t::primary_date() const { if (xdata_ && is_valid(xdata_->date)) return xdata_->date; @@ -117,11 +112,11 @@ date_t post_t::actual_date() const return *_date; } -optional<date_t> post_t::effective_date() const +optional<date_t> post_t::aux_date() const { - optional<date_t> date = item_t::effective_date(); + optional<date_t> date = item_t::aux_date(); if (! date && xact) - return xact->effective_date(); + return xact->aux_date(); return date; } @@ -173,7 +168,8 @@ namespace { return string_value(post.payee()); } - value_t get_note(post_t& post) { + value_t get_note(post_t& post) + { if (post.note || post.xact->note) { string note = post.note ? *post.note : empty_string; note += post.xact->note ? *post.xact->note : empty_string; @@ -186,14 +182,9 @@ namespace { value_t get_magnitude(post_t& post) { return post.xact->magnitude(); } - value_t get_idstring(post_t& post) { - return string_value(post.xact->idstring()); - } - value_t get_id(post_t& post) { - return string_value(post.xact->id()); - } - value_t get_amount(post_t& post) { + value_t get_amount(post_t& post) + { if (post.has_xdata() && post.xdata().has_flags(POST_EXT_COMPOUND)) return post.xdata().compound_value; else if (post.amount.is_null()) @@ -220,7 +211,8 @@ namespace { } } - value_t get_commodity_is_primary(post_t& post) { + value_t get_commodity_is_primary(post_t& post) + { if (post.has_xdata() && post.xdata().has_flags(POST_EXT_COMPOUND)) return post.xdata().compound_value.to_amount() @@ -245,6 +237,15 @@ namespace { return post.amount; } + value_t get_price(post_t& post) { + if (post.amount.is_null()) + return 0L; + if (post.amount.has_annotation() && post.amount.annotation().price) + return *post.amount.price(); + else + return get_cost(post); + } + value_t get_total(post_t& post) { if (post.xdata_ && ! post.xdata_->total.is_null()) return post.xdata_->total; @@ -347,7 +348,14 @@ namespace { return post.date(); } value_t get_datetime(post_t& post) { - return post.xdata().datetime; + return (! post.xdata().datetime.is_not_a_date_time() ? + post.xdata().datetime : datetime_t(post.date())); + } + value_t get_checkin(post_t& post) { + return post.checkin ? *post.checkin : NULL_VALUE; + } + value_t get_checkout(post_t& post) { + return post.checkout ? *post.checkout : NULL_VALUE; } template <value_t (*Func)(post_t&)> @@ -440,6 +448,10 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(get_wrapper<&get_is_calculated>); else if (name == "commodity") return WRAP_FUNCTOR(&get_commodity); + else if (name == "checkin") + return WRAP_FUNCTOR(get_wrapper<&get_checkin>); + else if (name == "checkout") + return WRAP_FUNCTOR(get_wrapper<&get_checkout>); break; case 'd': @@ -459,10 +471,6 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, case 'i': if (name == "index") return WRAP_FUNCTOR(get_wrapper<&get_count>); - else if (name == "id") - return WRAP_FUNCTOR(get_wrapper<&get_id>); - else if (name == "idstring") - return WRAP_FUNCTOR(get_wrapper<&get_idstring>); break; case 'm': @@ -484,6 +492,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(get_wrapper<&get_payee>); else if (name == "primary") return WRAP_FUNCTOR(get_wrapper<&get_commodity_is_primary>); + else if (name == "price") + return WRAP_FUNCTOR(get_wrapper<&get_price>); else if (name == "parent") return WRAP_FUNCTOR(get_wrapper<&get_xact>); break; @@ -616,7 +626,8 @@ bool post_t::valid() const void post_t::add_to_value(value_t& value, const optional<expr_t&>& expr) const { if (xdata_ && xdata_->has_flags(POST_EXT_COMPOUND)) { - add_or_set_value(value, xdata_->compound_value); + if (! xdata_->compound_value.is_null()) + add_or_set_value(value, xdata_->compound_value); } else if (expr) { bind_scope_t bound_scope(*expr->get_context(), @@ -647,6 +658,44 @@ void post_t::set_reported_account(account_t * acct) acct->xdata().reported_posts.push_back(this); } +void extend_post(post_t& post, journal_t& journal) +{ + commodity_t& comm(post.amount.commodity()); + + annotation_t * details = + (comm.has_annotation() ? + &as_annotated_commodity(comm).details : NULL); + + if (! details || ! details->value_expr) { + optional<expr_t> value_expr; + + if (optional<value_t> data = post.get_tag(_("Value"))) + value_expr = expr_t(data->to_string()); + + if (! value_expr) + value_expr = post.account->value_expr; + + if (! value_expr) + value_expr = post.amount.commodity().value_expr(); + + if (! value_expr) + value_expr = journal.value_expr; + + if (value_expr) { + if (! details) { + annotation_t new_details; + new_details.value_expr = value_expr; + + commodity_t * new_comm = + commodity_pool_t::current_pool->find_or_create(comm, new_details); + post.amount.set_commodity(*new_comm); + } else { + details->value_expr = value_expr; + } + } + } +} + void to_xml(std::ostream& out, const post_t& post) { push_xml x(out, "posting", true); @@ -667,9 +716,9 @@ void to_xml(std::ostream& out, const post_t& post) push_xml y(out, "date"); to_xml(out, *post._date, false); } - if (post._date_eff) { - push_xml y(out, "effective-date"); - to_xml(out, *post._date_eff, false); + if (post._date_aux) { + push_xml y(out, "aux-date"); + to_xml(out, *post._date_aux, false); } if (post.account) { @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -58,20 +58,22 @@ public: #define POST_COST_CALCULATED 0x0080 // posting's cost was calculated #define POST_COST_IN_FULL 0x0100 // cost specified using @@ #define POST_COST_FIXATED 0x0200 // cost is fixed using = indicator -#define POST_ANONYMIZED 0x0400 // a temporary, anonymous posting +#define POST_COST_VIRTUAL 0x0400 // cost is virtualized: (@) +#define POST_ANONYMIZED 0x0800 // a temporary, anonymous posting - xact_t * xact; // only set for posts of regular xacts - account_t * account; + xact_t * xact; // only set for posts of regular xacts + account_t * account; - amount_t amount; // can be null until finalization - optional<expr_t> amount_expr; - optional<amount_t> cost; - optional<amount_t> assigned_amount; + amount_t amount; // can be null until finalization + optional<expr_t> amount_expr; + optional<amount_t> cost; + optional<amount_t> assigned_amount; + optional<datetime_t> checkin; + optional<datetime_t> checkout; post_t(account_t * _account = NULL, flags_t _flags = ITEM_NORMAL) - : item_t(_flags), - xact(NULL), account(_account) + : item_t(_flags), xact(NULL), account(_account) { TRACE_CTOR(post_t, "account_t *, flags_t"); } @@ -79,8 +81,7 @@ public: const amount_t& _amount, flags_t _flags = ITEM_NORMAL, const optional<string>& _note = none) - : item_t(_flags, _note), - xact(NULL), account(_account), amount(_amount) + : item_t(_flags, _note), xact(NULL), account(_account), amount(_amount) { TRACE_CTOR(post_t, "account_t *, const amount_t&, flags_t, const optional<string>&"); } @@ -91,8 +92,11 @@ public: amount(post.amount), cost(post.cost), assigned_amount(post.assigned_amount), + checkin(post.checkin), + checkout(post.checkout), xdata_(post.xdata_) { + copy_details(post); TRACE_CTOR(post_t, "copy"); } virtual ~post_t() { @@ -116,15 +120,15 @@ public: bool inherit = true) const; virtual optional<value_t> get_tag(const string& tag, - bool inherit = true) const; + bool inherit = true) const; virtual optional<value_t> get_tag(const mask_t& tag_mask, const optional<mask_t>& value_mask = none, - bool inherit = true) const; + bool inherit = true) const; virtual date_t value_date() const; virtual date_t date() const; - virtual date_t actual_date() const; - virtual optional<date_t> effective_date() const; + virtual date_t primary_date() const; + virtual optional<date_t> aux_date() const; string payee() const; @@ -140,6 +144,12 @@ public: std::size_t xact_id() const; std::size_t account_id() const; + virtual void copy_details(const item_t& item) { + const post_t& post(dynamic_cast<const post_t&>(item)); + xdata_ = post.xdata_; + item_t::copy_details(item); + } + bool valid() const; struct xdata_t : public supports_flags<uint_least16_t> @@ -230,7 +240,7 @@ public: { bool operator()(const post_t * left, const post_t * right) const { gregorian::date_duration duration = - left->actual_date() - right->actual_date(); + left->primary_date() - right->primary_date(); if (duration.days() == 0) { return ((left->pos ? left->pos->sequence : 0) < (right->pos ? right->pos->sequence : 0)); @@ -259,6 +269,9 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +class journal_t; +void extend_post(post_t& post, journal_t& journal); + void to_xml(std::ostream& out, const post_t& post); } // namespace ledger diff --git a/src/precmd.cc b/src/precmd.cc index 663b638d..fe0836bc 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -43,22 +43,6 @@ namespace ledger { namespace { - string join_args(call_scope_t& args) - { - std::ostringstream buf; - bool first = true; - - for (std::size_t i = 0; i < args.size(); i++) { - if (first) - first = false; - else - buf << ' '; - buf << args[i]; - } - - return buf.str(); - } - post_t * get_sample_xact(report_t& report) { { @@ -83,8 +67,14 @@ namespace { out << _("--- Context is first posting of the following transaction ---") << std::endl << str << std::endl; { - std::istringstream in(str); - report.session.journal->parse(in, report.session); + shared_ptr<std::istringstream> in(new std::istringstream(str)); + + parse_context_stack_t parsing_context; + parsing_context.push(in); + parsing_context.get_current().journal = report.session.journal.get(); + parsing_context.get_current().scope = &report.session; + + report.session.journal->read(parsing_context); report.session.journal->clear_xdata(); } } diff --git a/src/precmd.h b/src/precmd.h index 277933c3..1c52d8a7 100644 --- a/src/precmd.h +++ b/src/precmd.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/predicate.h b/src/predicate.h index 673f1d5d..7d58dc2f 100644 --- a/src/predicate.h +++ b/src/predicate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/print.cc b/src/print.cc index b7f72bf0..79d83161 100644 --- a/src/print.cc +++ b/src/print.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -41,14 +41,47 @@ namespace ledger { namespace { + bool post_has_simple_amount(const post_t& post) + { + // Is the amount the result of a computation, i.e., it wasn't + // explicit specified by the user? + if (post.has_flags(POST_CALCULATED)) + return false; + + // Is the amount still empty? This shouldn't be true by this point, + // but we check anyway for safety. + if (post.amount.is_null()) + return false; + + // Is the amount a complex expression. If so, the first 'if' should + // have triggered. + if (post.amount_expr) + return false; + + // Is there a balance assignment? If so, don't elide the amount as + // that can change the semantics. + if (post.assigned_amount) + return false; + + // Does it have an explicitly specified cost (i.e., one that wasn't + // calculated for the user)? If so, don't elide the amount! + if (post.cost && ! post.has_flags(POST_COST_CALCULATED)) + return false; + + return true; + } + void print_note(std::ostream& out, const string& note, + const bool note_on_next_line, const std::size_t columns, const std::size_t prior_width) { - // The 4 is for four leading spaces at the beginning of the posting, and - // the 3 is for two spaces and a semi-colon before the note. - if (columns > 0 && note.length() > columns - (prior_width + 3)) + // The 3 is for two spaces and a semi-colon before the note. + if (note_on_next_line || + (columns > 0 && + (columns <= prior_width + 3 || + note.length() > columns - (prior_width + 3)))) out << "\n ;"; else out << " ;"; @@ -79,11 +112,11 @@ namespace { std::ostringstream buf; - buf << format_date(item_t::use_effective_date ? - xact.date() : xact.actual_date(), + buf << format_date(item_t::use_aux_date ? + xact.date() : xact.primary_date(), format_type, format); - if (! item_t::use_effective_date && xact.effective_date()) - buf << '=' << format_date(*xact.effective_date(), + if (! item_t::use_aux_date && xact.aux_date()) + buf << '=' << format_date(*xact.aux_date(), format_type, format); buf << ' '; @@ -100,10 +133,11 @@ namespace { std::size_t columns = (report.HANDLED(columns_) ? - static_cast<std::size_t>(report.HANDLER(columns_).value.to_long()) : 80); + lexical_cast<std::size_t>(report.HANDLER(columns_).str()) : 80); if (xact.note) - print_note(out, *xact.note, columns, unistring(leader).length()); + print_note(out, *xact.note, xact.has_flags(ITEM_NOTE_ON_NEXT_LINE), + columns, unistring(leader).length()); out << '\n'; if (xact.metadata) { @@ -119,7 +153,12 @@ namespace { } } + std::size_t count = xact.posts.size(); + std::size_t index = 0; + foreach (post_t * post, xact.posts) { + index++; + if (! report.HANDLED(generated) && (post->has_flags(ITEM_TEMP | ITEM_GENERATED) && ! post->has_flags(POST_ANONYMIZED))) @@ -152,8 +191,8 @@ namespace { unistring name(pbuf.str()); std::size_t account_width = - (report.HANDLER(account_width_).specified ? - static_cast<std::size_t>(report.HANDLER(account_width_).value.to_long()) : 36); + (report.HANDLED(account_width_) ? + lexical_cast<std::size_t>(report.HANDLER(account_width_).str()) : 36); if (account_width < name.length()) account_width = name.length(); @@ -163,25 +202,32 @@ namespace { std::string::size_type slip = (static_cast<std::string::size_type>(account_width) - static_cast<std::string::size_type>(name.length())); - if (slip > 0) { - out.width(static_cast<std::streamsize>(slip)); - out << ' '; - } - - std::ostringstream amtbuf; string amt; if (post->amount_expr) { amt = post->amount_expr->text(); - } else { - int amount_width = - (report.HANDLER(amount_width_).specified ? - report.HANDLER(amount_width_).value.to_int() : 12); + } + else if (count == 2 && index == 2 && + post_has_simple_amount(*post) && + post_has_simple_amount(*(*xact.posts.begin())) && + ((*xact.posts.begin())->amount.commodity() == + post->amount.commodity())) { + // If there are two postings and they both simple amount, and + // they are both of the same commodity, don't bother printing + // the second amount as it's always just an inverse of the + // first. + } + else { + std::size_t amount_width = + (report.HANDLED(amount_width_) ? + lexical_cast<std::size_t>(report.HANDLER(amount_width_).str()) : + 12); std::ostringstream amt_str; - value_t(post->amount).print(amt_str, amount_width, -1, - AMOUNT_PRINT_RIGHT_JUSTIFY | - AMOUNT_PRINT_NO_COMPUTED_ANNOTATIONS); + value_t(post->amount).print(amt_str, static_cast<int>(amount_width), + -1, AMOUNT_PRINT_RIGHT_JUSTIFY | + (report.HANDLED(generated) ? 0 : + AMOUNT_PRINT_NO_COMPUTED_ANNOTATIONS)); amt = amt_str.str(); } @@ -191,6 +237,7 @@ namespace { (static_cast<std::string::size_type>(amt.length()) - static_cast<std::string::size_type>(trimmed_amt.length())); + std::ostringstream amtbuf; if (slip + amt_slip < 2) amtbuf << string(2 - (slip + amt_slip), ' '); amtbuf << amt; @@ -208,15 +255,22 @@ namespace { amtbuf << " = " << *post->assigned_amount; string trailer = amtbuf.str(); - out << trailer; - - account_width += unistring(trailer).length(); + if (! trailer.empty()) { + if (slip > 0) { + out.width(static_cast<std::streamsize>(slip)); + out << ' '; + } + out << trailer; + + account_width += unistring(trailer).length(); + } } else { out << pbuf.str(); } if (post->note) - print_note(out, *post->note, columns, 4 + account_width); + print_note(out, *post->note, post->has_flags(ITEM_NOTE_ON_NEXT_LINE), + columns, 4 + account_width); out << '\n'; } } diff --git a/src/print.h b/src/print.h index 527f1912..42bfc8b6 100644 --- a/src/print.h +++ b/src/print.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/pstream.h b/src/pstream.h index 8134495d..6e38158a 100644 --- a/src/pstream.h +++ b/src/pstream.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -61,9 +61,14 @@ class ptristream : public std::istream if (*ptr && len == 0) len = std::strlen(ptr); - setg(ptr, // beginning of putback area - ptr, // read position + setg(ptr, // beginning of putback area + ptr, // read position ptr+len); // end position + + TRACE_CTOR(ptrinbuf, "char *, std::size_t"); + } + ~ptrinbuf() throw() { + TRACE_DTOR(ptrinbuf); } protected: diff --git a/src/py_account.cc b/src/py_account.cc index 5ef86871..64a7ae54 100644 --- a/src/py_account.cc +++ b/src/py_account.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/py_amount.cc b/src/py_amount.cc index 9ce4a02d..0aa8fee8 100644 --- a/src/py_amount.cc +++ b/src/py_amount.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -48,14 +48,19 @@ namespace { return amount.value(CURRENT_TIME()); } boost::optional<amount_t> py_value_1(const amount_t& amount, - commodity_t& in_terms_of) { + const commodity_t * in_terms_of) { return amount.value(CURRENT_TIME(), in_terms_of); } boost::optional<amount_t> py_value_2(const amount_t& amount, - commodity_t& in_terms_of, - datetime_t& moment) { + const commodity_t * in_terms_of, + const datetime_t& moment) { return amount.value(moment, in_terms_of); } + boost::optional<amount_t> py_value_2d(const amount_t& amount, + const commodity_t * in_terms_of, + const date_t& moment) { + return amount.value(datetime_t(moment), in_terms_of); + } void py_parse_2(amount_t& amount, object in, unsigned char flags) { if (PyFile_Check(in.ptr())) { @@ -238,6 +243,7 @@ internal precision.")) .def("value", py_value_0) .def("value", py_value_1, args("in_terms_of")) .def("value", py_value_2, args("in_terms_of", "moment")) + .def("value", py_value_2d, args("in_terms_of", "moment")) .def("price", &amount_t::price) @@ -263,10 +269,11 @@ internal precision.")) .add_property("commodity", make_function(&amount_t::commodity, - return_value_policy<reference_existing_object>()), + return_internal_reference<>()), make_function(&amount_t::set_commodity, with_custodian_and_ward<1, 2>())) .def("has_commodity", &amount_t::has_commodity) + .def("with_commodity", &amount_t::with_commodity) .def("clear_commodity", &amount_t::clear_commodity) .def("number", &amount_t::number) diff --git a/src/py_balance.cc b/src/py_balance.cc index 0140a625..2ae546f1 100644 --- a/src/py_balance.cc +++ b/src/py_balance.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -48,12 +48,12 @@ namespace { return balance.value(CURRENT_TIME()); } boost::optional<balance_t> py_value_1(const balance_t& balance, - commodity_t& in_terms_of) { + const commodity_t * in_terms_of) { return balance.value(CURRENT_TIME(), in_terms_of); } boost::optional<balance_t> py_value_2(const balance_t& balance, - commodity_t& in_terms_of, - datetime_t& moment) { + const commodity_t * in_terms_of, + const datetime_t& moment) { return balance.value(moment, in_terms_of); } @@ -201,8 +201,6 @@ void export_balance() .def("value", py_value_1, args("in_terms_of")) .def("value", py_value_2, args("in_terms_of", "moment")) - .def("price", &balance_t::price) - .def("__nonzero__", &balance_t::is_nonzero) .def("is_nonzero", &balance_t::is_nonzero) .def("is_zero", &balance_t::is_zero) diff --git a/src/py_commodity.cc b/src/py_commodity.cc index 6d8a29b3..b283efcc 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -96,14 +96,15 @@ namespace { pool.exchange(commodity, per_unit_cost, moment); } - cost_breakdown_t py_exchange_5(commodity_pool_t& pool, + cost_breakdown_t py_exchange_7(commodity_pool_t& pool, const amount_t& amount, const amount_t& cost, const bool is_per_unit, + const bool add_prices, const boost::optional<datetime_t>& moment, const boost::optional<string>& tag) { - return pool.exchange(amount, cost, is_per_unit, moment, tag); + return pool.exchange(amount, cost, is_per_unit, add_prices, moment, tag); } commodity_t * py_pool_getitem(commodity_pool_t& pool, const string& symbol) @@ -113,9 +114,9 @@ namespace { if (i == pool.commodities.end()) { PyErr_SetString(PyExc_ValueError, (string("Could not find commodity ") + symbol).c_str()); - throw boost::python::error_already_set(); + throw_error_already_set(); } - return (*i).second; + return (*i).second.get(); } python::list py_pool_keys(commodity_pool_t& pool) { @@ -168,13 +169,15 @@ namespace { py_pool_commodities_values_begin(commodity_pool_t& pool) { return make_transform_iterator (pool.commodities.begin(), - bind(&commodity_pool_t::commodities_map::value_type::second, _1)); + bind(&shared_ptr<commodity_t>::get, + bind(&commodity_pool_t::commodities_map::value_type::second, _1))); } commodities_map_seconds_iterator py_pool_commodities_values_end(commodity_pool_t& pool) { return make_transform_iterator (pool.commodities.end(), - bind(&commodity_pool_t::commodities_map::value_type::second, _1)); + bind(&shared_ptr<commodity_t>::get, + bind(&commodity_pool_t::commodities_map::value_type::second, _1))); } void py_add_price_2(commodity_t& commodity, @@ -255,8 +258,10 @@ void export_commodity() make_getter(&commodity_pool_t::keep_base), make_setter(&commodity_pool_t::keep_base)) .add_property("price_db", - make_getter(&commodity_pool_t::price_db), - make_setter(&commodity_pool_t::price_db)) + make_getter(&commodity_pool_t::price_db, + return_value_policy<return_by_value>()), + make_setter(&commodity_pool_t::price_db, + return_value_policy<return_by_value>())) .add_property("quote_leeway", make_getter(&commodity_pool_t::quote_leeway), make_setter(&commodity_pool_t::quote_leeway)) @@ -267,44 +272,38 @@ void export_commodity() make_getter(&commodity_pool_t::get_commodity_quote), make_setter(&commodity_pool_t::get_commodity_quote)) - .def("make_qualified_name", &commodity_pool_t::make_qualified_name) + .def("create", py_create_1, return_internal_reference<>()) + .def("create", py_create_2, return_internal_reference<>()) - .def("create", py_create_1, - return_value_policy<reference_existing_object>()) - .def("create", py_create_2, - return_value_policy<reference_existing_object>()) + .def("find_or_create", py_find_or_create_1, return_internal_reference<>()) + .def("find_or_create", py_find_or_create_2, return_internal_reference<>()) - .def("find_or_create", py_find_or_create_1, - return_value_policy<reference_existing_object>()) - .def("find_or_create", py_find_or_create_2, - return_value_policy<reference_existing_object>()) - - .def("find", py_find_1, return_value_policy<reference_existing_object>()) - .def("find", py_find_2, return_value_policy<reference_existing_object>()) + .def("find", py_find_1, return_internal_reference<>()) + .def("find", py_find_2, return_internal_reference<>()) .def("exchange", py_exchange_2, with_custodian_and_ward<1, 2>()) .def("exchange", py_exchange_3, with_custodian_and_ward<1, 2>()) - .def("exchange", py_exchange_5) + .def("exchange", py_exchange_7) .def("parse_price_directive", &commodity_pool_t::parse_price_directive) .def("parse_price_expression", &commodity_pool_t::parse_price_expression, - return_value_policy<reference_existing_object>()) + return_internal_reference<>()) .def("__getitem__", py_pool_getitem, - return_value_policy<reference_existing_object>()) + return_internal_reference<>()) .def("keys", py_pool_keys) .def("has_key", py_pool_contains) .def("__contains__", py_pool_contains) .def("__iter__", - python::range<return_value_policy<reference_existing_object> > + python::range<return_internal_reference<> > (py_pool_commodities_begin, py_pool_commodities_end)) .def("iteritems", - python::range<return_value_policy<reference_existing_object> > + python::range<return_internal_reference<> > (py_pool_commodities_begin, py_pool_commodities_end)) .def("iterkeys", python::range<>(py_pool_commodities_keys_begin, py_pool_commodities_keys_end)) .def("itervalues", - python::range<return_value_policy<reference_existing_object> > + python::range<return_internal_reference<> > (py_pool_commodities_values_begin, py_pool_commodities_values_end)) ; @@ -349,21 +348,20 @@ void export_commodity() .add_property("referent", make_function(py_commodity_referent, - return_value_policy<reference_existing_object>())) + return_internal_reference<>())) .def("has_annotation", &commodity_t::has_annotation) .def("strip_annotations", py_strip_annotations_0, - return_value_policy<reference_existing_object>()) + return_internal_reference<>()) .def("strip_annotations", py_strip_annotations_1, - return_value_policy<reference_existing_object>()) + return_internal_reference<>()) .def("write_annotations", &commodity_t::write_annotations) .def("pool", &commodity_t::pool, - return_value_policy<reference_existing_object>()) + return_internal_reference<>()) .add_property("base_symbol", &commodity_t::base_symbol) .add_property("symbol", &commodity_t::symbol) - .add_property("mapping_key", &commodity_t::mapping_key) .add_property("name", &commodity_t::name, &commodity_t::set_name) .add_property("note", &commodity_t::note, &commodity_t::set_note) @@ -394,11 +392,15 @@ void export_commodity() .add_property("price", py_price, py_set_price) .add_property("date", - make_getter(&annotation_t::date), - make_setter(&annotation_t::date)) + make_getter(&annotation_t::date, + return_value_policy<return_by_value>()), + make_setter(&annotation_t::date, + return_value_policy<return_by_value>())) .add_property("tag", - make_getter(&annotation_t::tag), - make_setter(&annotation_t::tag)) + make_getter(&annotation_t::tag, + return_value_policy<return_by_value>()), + make_setter(&annotation_t::tag, + return_value_policy<return_by_value>())) .def("__nonzero__", &annotation_t::operator bool) @@ -441,12 +443,12 @@ void export_commodity() .add_property("referent", make_function(py_annotated_commodity_referent, - return_value_policy<reference_existing_object>())) + return_internal_reference<>())) .def("strip_annotations", py_strip_ann_annotations_0, - return_value_policy<reference_existing_object>()) + return_internal_reference<>()) .def("strip_annotations", py_strip_ann_annotations_1, - return_value_policy<reference_existing_object>()) + return_internal_reference<>()) .def("write_annotations", &annotated_commodity_t::write_annotations) ; } diff --git a/src/py_expr.cc b/src/py_expr.cc index 027125e2..dd9df1f5 100644 --- a/src/py_expr.cc +++ b/src/py_expr.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/py_format.cc b/src/py_format.cc index fc2103c7..482eaf5b 100644 --- a/src/py_format.cc +++ b/src/py_format.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/py_item.cc b/src/py_item.cc index 51d9e50c..893ddcfa 100644 --- a/src/py_item.cc +++ b/src/py_item.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -120,14 +120,20 @@ void export_item() #endif .add_property("note", - make_getter(&item_t::note), - make_setter(&item_t::note)) + make_getter(&item_t::note, + return_value_policy<return_by_value>()), + make_setter(&item_t::note, + return_value_policy<return_by_value>())) .add_property("pos", - make_getter(&item_t::pos), - make_setter(&item_t::pos)) + make_getter(&item_t::pos, + return_value_policy<return_by_value>()), + make_setter(&item_t::pos, + return_value_policy<return_by_value>())) .add_property("metadata", - make_getter(&item_t::metadata), - make_setter(&item_t::metadata)) + make_getter(&item_t::metadata, + return_value_policy<return_by_value>()), + make_setter(&item_t::metadata, + return_value_policy<return_by_value>())) .def("copy_details", &item_t::copy_details) @@ -149,13 +155,13 @@ void export_item() .def("parse_tags", &item_t::parse_tags) .def("append_note", &item_t::append_note) - .add_static_property("use_effective_date", - make_getter(&item_t::use_effective_date), - make_setter(&item_t::use_effective_date)) + .add_static_property("use_aux_date", + make_getter(&item_t::use_aux_date), + make_setter(&item_t::use_aux_date)) .add_property("date", &item_t::date, make_setter(&item_t::_date)) - .add_property("effective_date", &item_t::effective_date, - make_setter(&item_t::_date_eff)) + .add_property("aux_date", &item_t::aux_date, + make_setter(&item_t::_date_aux)) .add_property("state", &item_t::state, &item_t::set_state) diff --git a/src/py_journal.cc b/src/py_journal.cc index bd781225..50a52be9 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -135,51 +135,57 @@ namespace { return journal.find_account(name, auto_create); } +#if 0 std::size_t py_read(journal_t& journal, const string& pathname) { - return journal.read(pathname); + return journal.read(context_stack); } +#endif struct collector_wrapper { - journal_t& journal; - report_t report; - collect_posts * posts_collector; - post_handler_ptr chain; + journal_t& journal; + report_t report; + + post_handler_ptr posts_collector; collector_wrapper(journal_t& _journal, report_t& base) : journal(_journal), report(base), - posts_collector(new collect_posts) {} + posts_collector(new collect_posts) { + TRACE_CTOR(collector_wrapper, "journal_t&, report_t&"); + } ~collector_wrapper() { + TRACE_DTOR(collector_wrapper); journal.clear_xdata(); } std::size_t length() const { - return posts_collector->length(); + return dynamic_cast<collect_posts *>(posts_collector.get())->length(); } std::vector<post_t *>::iterator begin() { - return posts_collector->begin(); + return dynamic_cast<collect_posts *>(posts_collector.get())->begin(); } std::vector<post_t *>::iterator end() { - return posts_collector->end(); + return dynamic_cast<collect_posts *>(posts_collector.get())->end(); } }; - shared_ptr<collector_wrapper> - py_collect(journal_t& journal, const string& query) + shared_ptr<collector_wrapper> py_query(journal_t& journal, + const string& query) { if (journal.has_xdata()) { PyErr_SetString(PyExc_RuntimeError, - _("Cannot have multiple journal collections open at once")); + _("Cannot have more than one active journal query")); throw_error_already_set(); } report_t& current_report(downcast<report_t>(*scope_t::default_scope)); - shared_ptr<collector_wrapper> coll(new collector_wrapper(journal, - current_report)); - unique_ptr<journal_t> save_journal(current_report.session.journal.release()); - current_report.session.journal.reset(&journal); + shared_ptr<collector_wrapper> + coll(new collector_wrapper(journal, current_report)); + + unique_ptr<journal_t> save_journal(coll->report.session.journal.release()); + coll->report.session.journal.reset(&coll->journal); try { strings_list remaining = @@ -189,60 +195,48 @@ namespace { value_t args; foreach (const string& arg, remaining) args.push_back(string_value(arg)); - coll->report.parse_query_args(args, "@Journal.collect"); + coll->report.parse_query_args(args, "@Journal.query"); - journal_posts_iterator walker(coll->journal); - coll->chain = - chain_post_handlers(post_handler_ptr(coll->posts_collector), - coll->report); - pass_down_posts<journal_posts_iterator>(coll->chain, walker); + coll->report.posts_report(coll->posts_collector); } catch (...) { - current_report.session.journal.release(); - current_report.session.journal.reset(save_journal.release()); + coll->report.session.journal.release(); + coll->report.session.journal.reset(save_journal.release()); throw; } - current_report.session.journal.release(); - current_report.session.journal.reset(save_journal.release()); + coll->report.session.journal.release(); + coll->report.session.journal.reset(save_journal.release()); return coll; } post_t * posts_getitem(collector_wrapper& collector, long i) { - post_t * post = - collector.posts_collector->posts[static_cast<std::string::size_type>(i)]; - std::cerr << typeid(post).name() << std::endl; - std::cerr << typeid(*post).name() << std::endl; - std::cerr << typeid(post->account).name() << std::endl; - std::cerr << typeid(*post->account).name() << std::endl; - return post; + return dynamic_cast<collect_posts *>(collector.posts_collector.get()) + ->posts[static_cast<std::size_t>(i)]; } } // unnamed namespace +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_RuntimeError, err.what()); \ + } + +EXC_TRANSLATOR(parse_error) +EXC_TRANSLATOR(error_count) + void export_journal() { class_< item_handler<post_t>, shared_ptr<item_handler<post_t> >, boost::noncopyable >("PostHandler") ; - class_< collect_posts, bases<item_handler<post_t> >, - shared_ptr<collect_posts>, boost::noncopyable >("PostCollector") - .def("__len__", &collect_posts::length) - .def("__iter__", python::range<return_internal_reference<1, - with_custodian_and_ward_postcall<1, 0> > > - (&collect_posts::begin, &collect_posts::end)) - ; - class_< collector_wrapper, shared_ptr<collector_wrapper>, boost::noncopyable >("PostCollectorWrapper", no_init) .def("__len__", &collector_wrapper::length) - .def("__getitem__", posts_getitem, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) - .def("__iter__", - python::range<return_value_policy<reference_existing_object, - with_custodian_and_ward_postcall<0, 1> > > + .def("__getitem__", posts_getitem, return_internal_reference<>()) + .def("__iter__", python::range<return_internal_reference<> > (&collector_wrapper::begin, &collector_wrapper::end)) ; @@ -264,9 +258,10 @@ void export_journal() ; class_< journal_t, boost::noncopyable > ("Journal") +#if 0 .def(init<path>()) .def(init<string>()) - +#endif .add_property("master", make_getter(&journal_t::master, return_internal_reference<1, @@ -283,13 +278,13 @@ void export_journal() .def("find_account", py_find_account_1, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) .def("find_account", py_find_account_2, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) .def("find_account_re", &journal_t::find_account_re, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) .def("add_xact", &journal_t::add_xact) .def("remove_xact", &journal_t::remove_xact) @@ -298,7 +293,7 @@ void export_journal() #if 0 .def("__getitem__", xacts_getitem, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) #endif .def("__iter__", python::range<return_internal_reference<> > @@ -311,16 +306,22 @@ void export_journal() (&journal_t::period_xacts_begin, &journal_t::period_xacts_end)) .def("sources", python::range<return_internal_reference<> > (&journal_t::sources_begin, &journal_t::sources_end)) - +#if 0 .def("read", py_read) - +#endif .def("has_xdata", &journal_t::has_xdata) .def("clear_xdata", &journal_t::clear_xdata) - .def("collect", py_collect, with_custodian_and_ward_postcall<0, 1>()) + .def("query", py_query) .def("valid", &journal_t::valid) ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator<type>(&exc_translate_ ## type); + + EXC_TRANSLATE(parse_error); + EXC_TRANSLATE(error_count); } } // namespace ledger diff --git a/src/py_post.cc b/src/py_post.cc index 62323eb1..692542a0 100644 --- a/src/py_post.cc +++ b/src/py_post.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -116,7 +116,7 @@ void export_post() make_setter(&post_t::xdata_t::datetime)) .add_property("account", make_getter(&post_t::xdata_t::account, - return_value_policy<reference_existing_object>()), + return_internal_reference<>()), make_setter(&post_t::xdata_t::account, with_custodian_and_ward<1, 2>())) .add_property("sort_values", @@ -132,6 +132,9 @@ void export_post() class_< post_t, bases<item_t> > ("Posting") //.def(init<account_t *>()) + .def("id", &post_t::id) + .def("seq", &post_t::seq) + .add_property("xact", make_getter(&post_t::xact, return_internal_reference<>()), @@ -146,11 +149,15 @@ void export_post() make_getter(&post_t::amount), make_setter(&post_t::amount)) .add_property("cost", - make_getter(&post_t::cost), - make_setter(&post_t::cost)) + make_getter(&post_t::cost, + return_value_policy<return_by_value>()), + make_setter(&post_t::cost, + return_value_policy<return_by_value>())) .add_property("assigned_amount", - make_getter(&post_t::assigned_amount), - make_setter(&post_t::assigned_amount)) + make_getter(&post_t::assigned_amount, + return_value_policy<return_by_value>()), + make_setter(&post_t::assigned_amount, + return_value_policy<return_by_value>())) .def("has_tag", py_has_tag_1s) .def("has_tag", py_has_tag_1m) @@ -159,8 +166,8 @@ void export_post() .def("get_tag", py_get_tag_1m) .def("get_tag", py_get_tag_2m) - .def("date", &post_t::date) - .def("effective_date", &post_t::effective_date) + .add_property("date", &post_t::date) + .add_property("aux_date", &post_t::aux_date) .def("must_balance", &post_t::must_balance) @@ -170,8 +177,7 @@ void export_post() .def("has_xdata", &post_t::has_xdata) .def("clear_xdata", &post_t::clear_xdata) - .def("xdata", py_xdata, - return_internal_reference<>()) + .def("xdata", py_xdata, return_internal_reference<>()) //.def("add_to_value", &post_t::add_to_value) .def("set_reported_account", &post_t::set_reported_account) diff --git a/src/py_session.cc b/src/py_session.cc new file mode 100644 index 00000000..f411d5e1 --- /dev/null +++ b/src/py_session.cc @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <system.hh> + +#include "pyinterp.h" +#include "pyutils.h" +#include "session.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + journal_t * py_read_journal(const string& pathname) + { + return python_session->read_journal(path(pathname)); + } + + journal_t * py_read_journal_from_string(const string& data) + { + return python_session->read_journal_from_string(data); + } +} + +void export_session() +{ + class_< session_t, boost::noncopyable > ("Session") + .def("read_journal", &session_t::read_journal, + return_internal_reference<>()) + .def("read_journal_from_string", &session_t::read_journal_from_string, + return_internal_reference<>()) + .def("read_journal_files", &session_t::read_journal_files, + return_internal_reference<>()) + .def("close_journal_files", &session_t::close_journal_files) + ; + + scope().attr("session") = + object(ptr(static_cast<session_t *>(python_session.get()))); + scope().attr("read_journal") = + python::make_function(&py_read_journal, + return_internal_reference<>()); + scope().attr("read_journal_from_string") = + python::make_function(&py_read_journal_from_string, + return_internal_reference<>()); +} + +} // namespace ledger diff --git a/src/py_times.cc b/src/py_times.cc index c2e0b8f8..17f9ec7e 100644 --- a/src/py_times.cc +++ b/src/py_times.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/py_utils.cc b/src/py_utils.cc index 710dca4b..45ffe545 100644 --- a/src/py_utils.cc +++ b/src/py_utils.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/py_value.cc b/src/py_value.cc index f8f36453..b931f008 100644 --- a/src/py_value.cc +++ b/src/py_value.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -51,14 +51,19 @@ namespace { return value.value(CURRENT_TIME()); } boost::optional<value_t> py_value_1(const value_t& value, - commodity_t& in_terms_of) { + const commodity_t * in_terms_of) { return value.value(CURRENT_TIME(), in_terms_of); } boost::optional<value_t> py_value_2(const value_t& value, - commodity_t& in_terms_of, - datetime_t& moment) { + const commodity_t * in_terms_of, + const datetime_t& moment) { return value.value(moment, in_terms_of); } + boost::optional<value_t> py_value_2d(const value_t& value, + const commodity_t * in_terms_of, + const date_t& moment) { + return value.value(datetime_t(moment), in_terms_of); + } PyObject * py_base_type(value_t& value) { @@ -147,7 +152,7 @@ void export_value() .def(init<balance_t>()) .def(init<mask_t>()) .def(init<std::string>()) - // jww (2009-11-02): Need to support conversion eof value_t::sequence_t + // jww (2009-11-02): Need to support conversion of value_t::sequence_t //.def(init<value_t::sequence_t>()) .def(init<value_t>()) @@ -265,9 +270,9 @@ void export_value() .def("value", py_value_0) .def("value", py_value_1, args("in_terms_of")) .def("value", py_value_2, args("in_terms_of", "moment")) + .def("value", py_value_2d, args("in_terms_of", "moment")) - .def("value", &value_t::value, value_overloads()) - .def("price", &value_t::price) + //.def("value", &value_t::value, value_overloads()) .def("exchange_commodities", &value_t::exchange_commodities, exchange_commodities_overloads()) diff --git a/src/py_xact.cc b/src/py_xact.cc index 604d8d59..3d792c7b 100644 --- a/src/py_xact.cc +++ b/src/py_xact.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -76,6 +76,12 @@ namespace { return **elem; } + string py_xact_to_string(xact_t&) + { + // jww (2012-03-01): TODO + return empty_string; + } + } // unnamed namespace using namespace boost::python; @@ -107,6 +113,11 @@ void export_xact() ; class_< xact_t, bases<xact_base_t> > ("Transaction") + .def("id", &xact_t::id) + .def("seq", &xact_t::seq) + + .def("__str__", py_xact_to_string) + .add_property("code", make_getter(&xact_t::code), make_setter(&xact_t::code)) @@ -117,8 +128,6 @@ void export_xact() .def("add_post", &xact_t::add_post, with_custodian_and_ward<1, 2>()) .def("magnitude", &xact_t::magnitude) - .def("idstring", &xact_t::idstring) - .def("id", &xact_t::id) .def("lookup", &xact_t::lookup) diff --git a/src/pyfstream.h b/src/pyfstream.h index 49b072f2..18f28bc4 100644 --- a/src/pyfstream.h +++ b/src/pyfstream.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -86,8 +86,8 @@ protected: public: pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) { - TRACE_CTOR(pyofstream, "PyFileObject *"); rdbuf(&buf); + TRACE_CTOR(pyofstream, "PyFileObject *"); } ~pyofstream() throw() { TRACE_DTOR(pyofstream); @@ -121,11 +121,11 @@ public: * => force underflow() */ pyinbuf (PyFileObject * _fo) : fo(_fo) { - TRACE_CTOR(pyinbuf, "PyFileObject *"); - setg (buffer+pbSize, // beginning of putback area buffer+pbSize, // read position buffer+pbSize); // end position + + TRACE_CTOR(pyinbuf, "PyFileObject *"); } ~pyinbuf() throw() { TRACE_DTOR(pyinbuf); @@ -191,8 +191,8 @@ protected: public: pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) { - TRACE_CTOR(pyifstream, "PyFileObject *"); rdbuf(&buf); + TRACE_CTOR(pyifstream, "PyFileObject *"); } ~pyifstream() throw() { TRACE_DTOR(pyifstream); diff --git a/src/pyinterp.cc b/src/pyinterp.cc index e0fd2d59..8d9c8c84 100644 --- a/src/pyinterp.cc +++ b/src/pyinterp.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -32,6 +32,7 @@ #include <system.hh> #include "pyinterp.h" +#include "pyutils.h" #include "account.h" #include "xact.h" #include "post.h" @@ -51,6 +52,7 @@ void export_commodity(); void export_expr(); void export_format(); void export_item(); +void export_session(); void export_journal(); void export_post(); void export_times(); @@ -72,6 +74,7 @@ void initialize_for_python() export_item(); export_post(); export_xact(); + export_session(); export_journal(); } @@ -81,16 +84,56 @@ struct python_run python_run(python_interpreter_t * intepreter, const string& str, int input_mode) - : result(handle<>(borrowed(PyRun_String(str.c_str(), input_mode, - intepreter->main_nspace.ptr(), - intepreter->main_nspace.ptr())))) {} + : result + (handle<> + (borrowed + (PyRun_String(str.c_str(), input_mode, + intepreter->main_module->module_globals.ptr(), + intepreter->main_module->module_globals.ptr())))) {} operator object() { return result; } }; +python_module_t::python_module_t(const string& name) + : scope_t(), module_name(name), module_globals() +{ + import_module(name); +} + +python_module_t::python_module_t(const string& name, python::object obj) + : scope_t(), module_name(name), module_globals() +{ + module_object = obj; + module_globals = extract<dict>(module_object.attr("__dict__")); +} + +void python_module_t::import_module(const string& name, bool import_direct) +{ + object mod = python::import(name.c_str()); + if (! mod) + throw_(std::runtime_error, + _("Module import failed (couldn't find %1)") << name); + + dict globals = extract<dict>(mod.attr("__dict__")); + if (! globals) + throw_(std::runtime_error, + _("Module import failed (couldn't find %1)") << name); + + if (! import_direct) { + module_object = mod; + module_globals = globals; + } else { + // Import all top-level entries directly into the namespace + module_globals.update(mod.attr("__dict__")); + } +} + void python_interpreter_t::initialize() { + if (is_initialized) + return; + TRACE_START(python_init, 1, "Initialized Python"); try { @@ -101,15 +144,7 @@ void python_interpreter_t::initialize() hack_system_paths(); - object main_module = python::import("__main__"); - if (! main_module) - throw_(std::runtime_error, - _("Python failed to initialize (couldn't find __main__)")); - - main_nspace = extract<dict>(main_module.attr("__dict__")); - if (! main_nspace) - throw_(std::runtime_error, - _("Python failed to initialize (couldn't find __dict__)")); + main_module = import_module("__main__"); python::detail::init_module("ledger", &initialize_for_python); @@ -167,58 +202,57 @@ void python_interpreter_t::hack_system_paths() #endif } -object python_interpreter_t::import_into_main(const string& str) +object python_interpreter_t::import_option(const string& str) { if (! is_initialized) initialize(); - try { - object mod = python::import(str.c_str()); - if (! mod) - throw_(std::runtime_error, - _("Failed to import Python module %1") << str); - - // Import all top-level entries directly into the main namespace - main_nspace.update(mod.attr("__dict__")); - - return mod; - } - catch (const error_already_set&) { - PyErr_Print(); - } - return object(); -} - -object python_interpreter_t::import_option(const string& str) -{ - path file(str); - python::object sys_module = python::import("sys"); python::object sys_dict = sys_module.attr("__dict__"); + path file(str); + string name(str); python::list paths(sys_dict["path"]); + if (contains(str, ".py")) { #if BOOST_VERSION >= 103700 - paths.insert(0, file.parent_path().string()); - sys_dict["path"] = paths; + path& cwd(parsing_context.get_current().current_directory); +#if BOOST_VERSION >= 104600 && BOOST_FILESYSTEM_VERSION >= 3 + path parent(filesystem::absolute(file, cwd).parent_path()); +#else + path parent(filesystem::complete(file, cwd).parent_path()); +#endif + DEBUG("python.interp", "Adding " << parent << " to PYTHONPATH"); + paths.insert(0, parent.string()); + sys_dict["path"] = paths; #if BOOST_VERSION >= 104600 - string name = file.filename().string(); - if (contains(name, ".py")) name = file.stem().string(); #else - string name = file.filename(); - if (contains(name, ".py")) name = file.stem(); #endif #else // BOOST_VERSION >= 103700 - paths.insert(0, file.branch_path().string()); - sys_dict["path"] = paths; - - string name = file.leaf(); + paths.insert(0, file.branch_path().string()); + sys_dict["path"] = paths; + name = file.leaf(); #endif // BOOST_VERSION >= 103700 + } - return python::import(python::str(name.c_str())); + try { + if (contains(str, ".py")) + main_module->import_module(name, true); + else + import_module(str); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::runtime_error, _("Python failed to import: %1") << str); + } + catch (...) { + throw; + } + + return object(); } object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode) @@ -346,13 +380,13 @@ value_t python_interpreter_t::server_command(call_scope_t& args) functor_t func(main_function, "main"); try { func(args); + return true; } catch (const error_already_set&) { PyErr_Print(); throw_(std::runtime_error, _("Error while invoking ledger.server's main() function")); } - return true; } else { throw_(std::runtime_error, _("The ledger.server module is missing its main() function!")); @@ -372,6 +406,40 @@ python_interpreter_t::lookup_option(const char * p) return NULL; } +expr_t::ptr_op_t python_module_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + switch (kind) { + case symbol_t::FUNCTION: + DEBUG("python.interp", "Python lookup: " << name); + if (module_globals.has_key(name.c_str())) { + if (python::object obj = module_globals.get(name.c_str())) { + if (PyModule_Check(obj.ptr())) { + shared_ptr<python_module_t> mod; + python_module_map_t::iterator i = + python_session->modules_map.find(obj.ptr()); + if (i == python_session->modules_map.end()) { + mod.reset(new python_module_t(name, obj)); + python_session->modules_map.insert + (python_module_map_t::value_type(obj.ptr(), mod)); + } else { + mod = (*i).second; + } + return expr_t::op_t::wrap_value(scope_value(mod.get())); + } else { + return WRAP_FUNCTOR(python_interpreter_t::functor_t(obj, name)); + } + } + } + break; + + default: + break; + } + + return NULL; +} + expr_t::ptr_op_t python_interpreter_t::lookup(const symbol_t::kind_t kind, const string& name) { @@ -381,21 +449,18 @@ expr_t::ptr_op_t python_interpreter_t::lookup(const symbol_t::kind_t kind, switch (kind) { case symbol_t::FUNCTION: - if (option_t<python_interpreter_t> * handler = lookup_option(name.c_str())) - return MAKE_OPT_FUNCTOR(python_interpreter_t, handler); - - if (is_initialized && main_nspace.has_key(name.c_str())) { - DEBUG("python.interp", "Python lookup: " << name); - - if (python::object obj = main_nspace.get(name.c_str())) - return WRAP_FUNCTOR(functor_t(obj, name)); - } + if (is_initialized) + return main_module->lookup(kind, name); break; - case symbol_t::OPTION: + case symbol_t::OPTION: { if (option_t<python_interpreter_t> * handler = lookup_option(name.c_str())) return MAKE_OPT_HANDLER(python_interpreter_t, handler); + + if (is_initialized) + return main_module->lookup(symbol_t::FUNCTION, string("option_") + name); break; + } case symbol_t::PRECOMMAND: { const char * p = name.c_str(); @@ -420,29 +485,59 @@ expr_t::ptr_op_t python_interpreter_t::lookup(const symbol_t::kind_t kind, } namespace { - void append_value(list& lst, const value_t& value) + object convert_value_to_python(const value_t& val) { - if (value.is_scope()) { - const scope_t * scope = value.as_scope(); - if (const post_t * post = dynamic_cast<const post_t *>(scope)) - lst.append(ptr(post)); - else if (const xact_t * xact = dynamic_cast<const xact_t *>(scope)) - lst.append(ptr(xact)); - else if (const account_t * account = - dynamic_cast<const account_t *>(scope)) - lst.append(ptr(account)); - else if (const period_xact_t * period_xact = - dynamic_cast<const period_xact_t *>(scope)) - lst.append(ptr(period_xact)); - else if (const auto_xact_t * auto_xact = - dynamic_cast<const auto_xact_t *>(scope)) - lst.append(ptr(auto_xact)); - else - throw_(std::logic_error, - _("Cannot downcast scoped object to specific type")); - } else { - lst.append(value); + switch (val.type()) { + case value_t::VOID: // a null value (i.e., uninitialized) + return object(); + case value_t::BOOLEAN: // a boolean + return object(val.to_boolean()); + case value_t::DATETIME: // a date and time (Boost posix_time) + return object(val.to_datetime()); + case value_t::DATE: // a date (Boost gregorian::date) + return object(val.to_date()); + case value_t::INTEGER: // a signed integer value + return object(val.to_long()); + case value_t::AMOUNT: // a ledger::amount_t + return object(val.as_amount()); + case value_t::BALANCE: // a ledger::balance_t + return object(val.as_balance()); + case value_t::STRING: // a string object + return object(handle<>(borrowed(str_to_py_unicode(val.as_string())))); + case value_t::MASK: // a regular expression mask + return object(val); + case value_t::SEQUENCE: { // a vector of value_t objects + list arglist; + foreach (const value_t& elem, val.as_sequence()) + arglist.append(elem); + return arglist; + } + case value_t::SCOPE: // a pointer to a scope + if (const scope_t * scope = val.as_scope()) { + if (const post_t * post = dynamic_cast<const post_t *>(scope)) + return object(ptr(post)); + else if (const xact_t * xact = dynamic_cast<const xact_t *>(scope)) + return object(ptr(xact)); + else if (const account_t * account = + dynamic_cast<const account_t *>(scope)) + return object(ptr(account)); + else if (const period_xact_t * period_xact = + dynamic_cast<const period_xact_t *>(scope)) + return object(ptr(period_xact)); + else if (const auto_xact_t * auto_xact = + dynamic_cast<const auto_xact_t *>(scope)) + return object(ptr(auto_xact)); + else + throw_(std::logic_error, + _("Cannot downcast scoped object to specific type")); + } + return object(); + case value_t::ANY: // a pointer to an arbitrary object + return object(val); } +#if !defined(__clang__) + return object(); +#endif } } @@ -453,6 +548,7 @@ value_t python_interpreter_t::functor_t::operator()(call_scope_t& args) if (! PyCallable_Check(func.ptr())) { extract<value_t> val(func); + DEBUG("python.interp", "Value of Python '" << name << "': " << val); std::signal(SIGINT, sigint_handler); if (val.check()) return val(); @@ -464,9 +560,9 @@ value_t python_interpreter_t::functor_t::operator()(call_scope_t& args) // rather than a sequence of arguments? if (args.value().is_sequence()) foreach (const value_t& value, args.value().as_sequence()) - append_value(arglist, value); + arglist.append(convert_value_to_python(value)); else - append_value(arglist, args.value()); + arglist.append(convert_value_to_python(args.value())); if (PyObject * val = PyObject_CallObject(func.ptr(), python::tuple(arglist).ptr())) { @@ -474,11 +570,12 @@ value_t python_interpreter_t::functor_t::operator()(call_scope_t& args) value_t result; if (xval.check()) { result = xval(); + DEBUG("python.interp", + "Return from Python '" << name << "': " << result); Py_DECREF(val); } else { Py_DECREF(val); - throw_(calc_error, - _("Could not evaluate Python variable '%1'") << name); + return NULL_VALUE; } std::signal(SIGINT, sigint_handler); return result; diff --git a/src/pyinterp.h b/src/pyinterp.h index ea947c5a..556b1563 100644 --- a/src/pyinterp.h +++ b/src/pyinterp.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -38,20 +38,52 @@ namespace ledger { +class python_module_t : public scope_t, public noncopyable +{ +public: + string module_name; + python::object module_object; + python::dict module_globals; + + explicit python_module_t(const string& name); + explicit python_module_t(const string& name, python::object obj); + + void import_module(const string& name, bool import_direct = false); + + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + void define_global(const string& name, python::object obj) { + module_globals[name] = obj; + } + + virtual string description() { + return module_name; + } +}; + +typedef std::map<PyObject *, shared_ptr<python_module_t> > python_module_map_t; + class python_interpreter_t : public session_t { public: - python::dict main_nspace; bool is_initialized; - python_interpreter_t() - : session_t(), main_nspace(), is_initialized(false) { + shared_ptr<python_module_t> main_module; + python_module_map_t modules_map; + + shared_ptr<python_module_t> import_module(const string& name) { + shared_ptr<python_module_t> mod(new python_module_t(name)); + if (name != "__main__") + main_module->define_global(name, mod->module_object); + return mod; + } + + python_interpreter_t() : session_t(), is_initialized(false) { TRACE_CTOR(python_interpreter_t, ""); } - virtual ~python_interpreter_t() { TRACE_DTOR(python_interpreter_t); - if (is_initialized) Py_Finalize(); } @@ -59,7 +91,6 @@ public: void initialize(); void hack_system_paths(); - python::object import_into_main(const string& name); python::object import_option(const string& name); enum py_eval_mode_t { @@ -68,14 +99,10 @@ public: PY_EVAL_MULTI }; - python::object eval(std::istream& in, - py_eval_mode_t mode = PY_EVAL_EXPR); - python::object eval(const string& str, - py_eval_mode_t mode = PY_EVAL_EXPR); - python::object eval(const char * c_str, - py_eval_mode_t mode = PY_EVAL_EXPR) { - string str(c_str); - return eval(str, mode); + python::object eval(std::istream& in, py_eval_mode_t mode = PY_EVAL_EXPR); + python::object eval(const string& str, py_eval_mode_t mode = PY_EVAL_EXPR); + python::object eval(const char * c_str, py_eval_mode_t mode = PY_EVAL_EXPR) { + return eval(string(c_str), mode); } value_t python_command(call_scope_t& scope); @@ -109,8 +136,8 @@ public: virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, const string& name); - OPTION_(python_interpreter_t, import_, DO_(args) { - parent->import_option(args.get<string>(1)); + OPTION_(python_interpreter_t, import_, DO_(str) { + parent->import_option(str); }); }; diff --git a/src/pyledger.cc b/src/pyledger.cc index 4a53532a..cf5e362e 100644 --- a/src/pyledger.cc +++ b/src/pyledger.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/pyutils.h b/src/pyutils.h index 7e016502..44bb6d90 100644 --- a/src/pyutils.h +++ b/src/pyutils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/query.cc b/src/query.cc index 812123cb..3fec708a 100644 --- a/src/query.cc +++ b/src/query.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -146,7 +146,7 @@ query_t::lexer_t::next_token(query_t::lexer_t::token_t::kind_t tok_context) case '\t': case '\n': case '\r': - if (! multiple_args && ! consume_whitespace) + if (! multiple_args && ! consume_whitespace && ! consume_next_arg) goto test_ident; else ident.push_back(*arg_i); diff --git a/src/query.h b/src/query.h index 8f7917b2..fe52eb35 100644 --- a/src/query.h +++ b/src/query.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -153,8 +153,6 @@ public: case TERM: return string("TERM(") + *value + ")"; case END_REACHED: return "END_REACHED"; } - assert(false); - return empty_string; } string symbol() const { @@ -188,6 +186,9 @@ public: assert(false); return "<UNKNOWN>"; } +#if !defined(__clang__) + return "<ERROR>"; +#endif } void unexpected(); @@ -203,10 +204,11 @@ public: consume_whitespace(false), consume_next_arg(false), multiple_args(_multiple_args) { - TRACE_CTOR(query_t::lexer_t, ""); assert(begin != end); arg_i = (*begin).as_string().begin(); arg_end = (*begin).as_string().end(); + + TRACE_CTOR(query_t::lexer_t, ""); } lexer_t(const lexer_t& lexer) : begin(lexer.begin), end(lexer.end), @@ -301,18 +303,19 @@ public: query_t(const string& arg, const keep_details_t& what_to_keep = keep_details_t(), bool multiple_args = true) { - TRACE_CTOR(query_t, "string, keep_details_t, bool"); if (! arg.empty()) { value_t temp(string_value(arg)); parse_args(temp.to_sequence(), what_to_keep, multiple_args); } + TRACE_CTOR(query_t, "string, keep_details_t, bool"); } query_t(const value_t& args, const keep_details_t& what_to_keep = keep_details_t(), bool multiple_args = true) { - TRACE_CTOR(query_t, "value_t, keep_details_t, bool"); if (! args.empty()) parse_args(args, what_to_keep, multiple_args); + + TRACE_CTOR(query_t, "value_t, keep_details_t, bool"); } virtual ~query_t() { TRACE_DTOR(query_t); diff --git a/src/quotes.cc b/src/quotes.cc index 0cc8d06b..c33e0826 100644 --- a/src/quotes.cc +++ b/src/quotes.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -40,7 +40,7 @@ namespace ledger { optional<price_point_t> commodity_quote_from_script(commodity_t& commodity, - const optional<commodity_t&>& exchange_commodity) + const commodity_t * exchange_commodity) { DEBUG("commodity.download", "downloading quote for symbol " << commodity.symbol()); #if defined(DEBUG_ON) diff --git a/src/quotes.h b/src/quotes.h index 376d8918..56740e47 100644 --- a/src/quotes.h +++ b/src/quotes.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -46,7 +46,7 @@ namespace ledger { optional<price_point_t> commodity_quote_from_script(commodity_t& commodity, - const optional<commodity_t&>& exchange_commodity); + const commodity_t * exchange_commodity); } // namespace ledger diff --git a/src/report.cc b/src/report.cc index f4dc450e..c7559cf7 100644 --- a/src/report.cc +++ b/src/report.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -41,6 +41,7 @@ #include "iterators.h" #include "filters.h" #include "precmd.h" +#include "select.h" #include "stats.h" #include "generate.h" #include "draft.h" @@ -59,7 +60,7 @@ void report_t::normalize_options(const string& verb) #ifdef HAVE_ISATTY if (! HANDLED(force_color)) { if (! HANDLED(no_color) && isatty(STDOUT_FILENO)) - HANDLER(color).on_only(string("?normalize")); + HANDLER(color).on("?normalize"); if (HANDLED(color) && ! isatty(STDOUT_FILENO)) HANDLER(color).off(); } @@ -76,15 +77,14 @@ void report_t::normalize_options(const string& verb) HANDLER(pager_).off(); } - item_t::use_effective_date = (HANDLED(effective) && - ! HANDLED(actual_dates)); + item_t::use_aux_date = (HANDLED(aux_date) && ! HANDLED(primary_date)); commodity_pool_t::current_pool->keep_base = HANDLED(base); commodity_pool_t::current_pool->get_quotes = session.HANDLED(download); if (session.HANDLED(price_exp_)) commodity_pool_t::current_pool->quote_leeway = - session.HANDLER(price_exp_).value.as_long(); + lexical_cast<long>(session.HANDLER(price_exp_).value) * 3600L; if (session.HANDLED(price_db_)) commodity_pool_t::current_pool->price_db = session.HANDLER(price_db_).str(); @@ -107,39 +107,35 @@ void report_t::normalize_options(const string& verb) if (! HANDLED(meta_width_)) { string::size_type i = HANDLER(meta_).str().find(':'); if (i != string::npos) { - HANDLED(meta_width_).on_with - (string("?normalize"), - lexical_cast<long>(string(HANDLER(meta_).str(), i + 1))); - HANDLED(meta_).on(string("?normalize"), + HANDLED(meta_width_).on("?normalize", + string(HANDLER(meta_).str(), i + 1)); + HANDLED(meta_).on("?normalize", string(HANDLER(meta_).str(), 0, i)); } } if (HANDLED(meta_width_)) { - HANDLER(prepend_format_).on - (string("?normalize"), - string("%(justify(truncated(tag(\"") + - HANDLER(meta_).str() + "\"), " + - HANDLED(meta_width_).value.to_string() + " - 1), " + - HANDLED(meta_width_).value.to_string() + "))"); - meta_width = HANDLED(meta_width_).value.to_long(); + HANDLER(prepend_format_) + .on("?normalize", string("%(justify(truncated(tag(\"") + + HANDLER(meta_).str() + "\"), " + + HANDLED(meta_width_).value + " - 1), " + + HANDLED(meta_width_).value + "))"); + meta_width = lexical_cast<long>(HANDLED(meta_width_).value); } else { - HANDLER(prepend_format_).on(string("?normalize"), string("%(tag(\"") + - HANDLER(meta_).str() + "\"))"); + HANDLER(prepend_format_) + .on("?normalize", string("%(tag(\"") + HANDLER(meta_).str() + "\"))"); } } - if (! HANDLED(prepend_width_)) - HANDLER(prepend_width_).on_with(string("?normalize"), static_cast<long>(0)); if (verb == "print" || verb == "xact" || verb == "dump") { - HANDLER(related).on_only(string("?normalize")); - HANDLER(related_all).on_only(string("?normalize")); + HANDLER(related_all).parent = this; + HANDLER(related_all).on("?normalize"); } else if (verb == "equity") { - HANDLER(equity).on_only(string("?normalize")); + HANDLER(equity).on("?normalize"); } if (verb[0] != 'b' && verb[0] != 'r') - HANDLER(base).on_only(string("?normalize")); + HANDLER(base).on("?normalize"); // If a time period was specified with -p, check whether it also gave a // begin and/or end to the report period (though these can be overridden @@ -153,12 +149,10 @@ void report_t::normalize_options(const string& verb) // to avoid option ordering issues were we to have done it during the // initial parsing of the options. if (HANDLED(amount_data)) { - HANDLER(format_) - .on_with(string("?normalize"), HANDLER(plot_amount_format_).value); + HANDLER(format_).on("?normalize", HANDLER(plot_amount_format_).value); } else if (HANDLED(total_data)) { - HANDLER(format_) - .on_with(string("?normalize"), HANDLER(plot_total_format_).value); + HANDLER(format_).on("?normalize", HANDLER(plot_total_format_).value); } // If the --exchange (-X) option was used, parse out any final price @@ -169,9 +163,26 @@ void report_t::normalize_options(const string& verb) terminus); } + if (HANDLED(percent)) { + commodity_t::decimal_comma_by_default = false; + if (HANDLED(market)) { + HANDLER(total_) + .on("?normalize", + "(__tmp = market(parent.total, value_date, exchange);" + " ((is_account & parent & __tmp) ?" + " percent(scrub(market(total, value_date, exchange)), " + " scrub(__tmp)) : 0))"); + } + } + + if (HANDLED(immediate) && HANDLED(market)) { + HANDLER(amount_) + .on("?normalize", "market(amount_expr, value_date, exchange)"); + } + long cols = 0; if (HANDLED(columns_)) - cols = HANDLER(columns_).value.to_long(); + cols = lexical_cast<long>(HANDLER(columns_).value); else if (const char * columns = std::getenv("COLUMNS")) cols = lexical_cast<long>(columns); else @@ -183,23 +194,21 @@ void report_t::normalize_options(const string& verb) if (cols > 0) { DEBUG("auto.columns", "cols = " << cols); - if (! HANDLER(date_width_).specified) - HANDLER(date_width_) - .on_with(none, static_cast<long>(format_date(CURRENT_DATE(), - FMT_PRINTED).length())); - - long date_width = HANDLER(date_width_).value.to_long(); - long payee_width = (HANDLER(payee_width_).specified ? - HANDLER(payee_width_).value.to_long() : - int(double(cols) * 0.263157)); - long account_width = (HANDLER(account_width_).specified ? - HANDLER(account_width_).value.to_long() : - int(double(cols) * 0.302631)); - long amount_width = (HANDLER(amount_width_).specified ? - HANDLER(amount_width_).value.to_long() : - int(double(cols) * 0.157894)); - long total_width = (HANDLER(total_width_).specified ? - HANDLER(total_width_).value.to_long() : + long date_width = (HANDLED(date_width_) ? + lexical_cast<long>(HANDLER(date_width_).str()) : + static_cast<long> + (format_date(CURRENT_DATE(),FMT_PRINTED).length())); + long payee_width = (HANDLED(payee_width_) ? + lexical_cast<long>(HANDLER(payee_width_).str()) : + long(double(cols) * 0.263157)); + long account_width = (HANDLED(account_width_) ? + lexical_cast<long>(HANDLER(account_width_).str()) : + long(double(cols) * 0.302631)); + long amount_width = (HANDLED(amount_width_) ? + lexical_cast<long>(HANDLER(amount_width_).str()) : + long(double(cols) * 0.157894)); + long total_width = (HANDLED(total_width_) ? + lexical_cast<long>(HANDLER(total_width_).str()) : amount_width); DEBUG("auto.columns", "date_width = " << date_width); @@ -208,32 +217,46 @@ void report_t::normalize_options(const string& verb) DEBUG("auto.columns", "amount_width = " << amount_width); DEBUG("auto.columns", "total_width = " << total_width); - if (! HANDLER(date_width_).specified && - ! HANDLER(payee_width_).specified && - ! HANDLER(account_width_).specified && - ! HANDLER(amount_width_).specified && - ! HANDLER(total_width_).specified) { + if (! HANDLED(date_width_) && + ! HANDLED(payee_width_) && + ! HANDLED(account_width_) && + ! HANDLED(amount_width_) && + ! HANDLED(total_width_)) { long total = (4 /* the spaces between */ + date_width + payee_width + - account_width + amount_width + total_width); - if (total > cols) { + account_width + amount_width + total_width + + (HANDLED(dc) ? 1 + amount_width : 0)); + while (total > cols && account_width > 5 && payee_width > 5) { DEBUG("auto.columns", "adjusting account down"); - account_width -= total - cols; + if (total > cols) { + --account_width; + --total; + if (total > cols) { + --account_width; + --total; + } + } + if (total > cols) { + --payee_width; + --total; + } DEBUG("auto.columns", "account_width now = " << account_width); } } if (! HANDLED(meta_width_)) - HANDLER(meta_width_).on_with(string("?normalize"), 0L); - if (! HANDLER(date_width_).specified) - HANDLER(date_width_).on_with(string("?normalize"), date_width); - if (! HANDLER(payee_width_).specified) - HANDLER(payee_width_).on_with(string("?normalize"), payee_width); - if (! HANDLER(account_width_).specified) - HANDLER(account_width_).on_with(string("?normalize"), account_width); - if (! HANDLER(amount_width_).specified) - HANDLER(amount_width_).on_with(string("?normalize"), amount_width); - if (! HANDLER(total_width_).specified) - HANDLER(total_width_).on_with(string("?normalize"), total_width); + HANDLER(meta_width_).value = "0"; + if (! HANDLED(prepend_width_)) + HANDLER(prepend_width_).value = "0"; + if (! HANDLED(date_width_)) + HANDLER(date_width_).value = to_string(date_width); + if (! HANDLED(payee_width_)) + HANDLER(payee_width_).value = to_string(payee_width); + if (! HANDLED(account_width_)) + HANDLER(account_width_).value = to_string(account_width); + if (! HANDLED(amount_width_)) + HANDLER(amount_width_).value = to_string(amount_width); + if (! HANDLED(total_width_)) + HANDLER(total_width_).value = to_string(total_width); } } @@ -256,7 +279,7 @@ void report_t::normalize_period() if (! interval.duration) HANDLER(period_).off(); else if (! HANDLED(sort_all_)) - HANDLER(sort_xacts_).on_only(string("?normalize")); + HANDLER(sort_xacts_).on("?normalize"); } void report_t::parse_query_args(const value_t& args, const string& whence) @@ -279,7 +302,7 @@ void report_t::parse_query_args(const value_t& args, const string& whence) } if (query.has_query(query_t::QUERY_BOLD)) { - HANDLER(bold_if_).set_expr(whence, query.get_query(query_t::QUERY_BOLD)); + HANDLER(bold_if_).on(whence, query.get_query(query_t::QUERY_BOLD)); DEBUG("report.predicate", "Bolding predicate = " << HANDLER(bold_if_).str()); } @@ -298,7 +321,12 @@ namespace { report_t& report; posts_flusher(post_handler_ptr _handler, report_t& _report) - : handler(_handler), report(_report) {} + : handler(_handler), report(_report) { + TRACE_CTOR(posts_flusher, "post_handler_ptr, report_t&"); + } + ~posts_flusher() throw() { + TRACE_DTOR(posts_flusher); + } void operator()(const value_t&) { report.session.journal->clear_xdata(); @@ -310,7 +338,7 @@ void report_t::posts_report(post_handler_ptr handler) { handler = chain_post_handlers(handler, *this); if (HANDLED(group_by_)) { - std::auto_ptr<post_splitter> + unique_ptr<post_splitter> splitter(new post_splitter(handler, *this, HANDLER(group_by_).expr)); splitter->set_postflush_func(posts_flusher(handler, *this)); handler = post_handler_ptr(splitter.release()); @@ -330,9 +358,9 @@ void report_t::generate_report(post_handler_ptr handler) generate_posts_iterator walker (session, HANDLED(seed_) ? - static_cast<unsigned int>(HANDLER(seed_).value.to_long()) : 0, + lexical_cast<unsigned int>(HANDLER(seed_).str()) : 0, HANDLED(head_) ? - static_cast<unsigned int>(HANDLER(head_).value.to_long()) : 50); + lexical_cast<unsigned int>(HANDLER(head_).str()) : 50); pass_down_posts<generate_posts_iterator>(handler, walker); } @@ -447,8 +475,20 @@ void report_t::commodities_report(post_handler_ptr handler) { handler = chain_handlers(handler, *this); - posts_commodities_iterator walker(*session.journal.get()); - pass_down_posts<posts_commodities_iterator>(handler, walker); + posts_commodities_iterator * walker(new posts_commodities_iterator(*session.journal.get())); + try { + pass_down_posts<posts_commodities_iterator>(handler, *walker); + } + catch (...) { +#if defined(VERIFY_ON) + IF_VERIFY() { + // If --verify was used, clean up the posts_commodities_iterator. + // Otherwise, just leak like a sieve. + checked_delete(walker); + } +#endif + throw; + } session.journal->clear_xdata(); } @@ -514,20 +554,32 @@ value_t report_t::fn_should_bold(call_scope_t& scope) value_t report_t::fn_market(call_scope_t& args) { - optional<datetime_t> moment = (args.has<datetime_t>(1) ? - args.get<datetime_t>(1) : - optional<datetime_t>()); value_t result; + value_t arg0 = args[0]; + + datetime_t moment; + if (args.has<datetime_t>(1)) + moment = args.get<datetime_t>(1); + + if (arg0.is_string()) { + amount_t tmp(1L); + commodity_t * commodity = + commodity_pool_t::current_pool->find_or_create(arg0.as_string()); + tmp.set_commodity(*commodity); + arg0 = tmp; + } + + string target_commodity; if (args.has<string>(2)) - result = args[0].exchange_commodities(args.get<string>(2), - /* add_prices= */ false, moment); - else - result = args[0].value(moment); + target_commodity = args.get<string>(2); - if (! result.is_null()) - return result; + if (! target_commodity.empty()) + result = arg0.exchange_commodities(target_commodity, + /* add_prices= */ false, moment); + else + result = arg0.value(moment); - return args[0]; + return ! result.is_null() ? result : arg0; } value_t report_t::fn_get_at(call_scope_t& args) @@ -536,13 +588,20 @@ value_t report_t::fn_get_at(call_scope_t& args) if (index == 0) { if (! args[0].is_sequence()) return args[0]; - } else { - if (! args[0].is_sequence()) - throw_(std::runtime_error, - _("Attempting to get argument at index %1 from %2") - << index << args[0].label()); } - return args[0].as_sequence()[index]; + else if (! args[0].is_sequence()) { + throw_(std::runtime_error, + _("Attempting to get argument at index %1 from %2") + << index << args[0].label()); + } + + value_t::sequence_t& seq(args[0].as_sequence_lval()); + if (index >= seq.size()) + throw_(std::runtime_error, + _("Attempting to get index %1 from %2 with %3 elements") + << index << args[0].label() << seq.size()); + + return seq[index]; } value_t report_t::fn_is_seq(call_scope_t& scope) @@ -703,6 +762,15 @@ value_t report_t::fn_format_date(call_scope_t& args) return string_value(format_date(args.get<date_t>(0), FMT_PRINTED)); } +value_t report_t::fn_format_datetime(call_scope_t& args) +{ + if (args.has<string>(1)) + return string_value(format_datetime(args.get<datetime_t>(0), FMT_CUSTOM, + args.get<string>(1).c_str())); + else + return string_value(format_datetime(args.get<datetime_t>(0), FMT_PRINTED)); +} + value_t report_t::fn_ansify_if(call_scope_t& args) { if (args.has<string>(1)) { @@ -732,14 +800,62 @@ value_t report_t::fn_percent(call_scope_t& args) (args.get<amount_t>(0) / args.get<amount_t>(1)).number()); } -value_t report_t::fn_price(call_scope_t& args) +value_t report_t::fn_commodity(call_scope_t& args) { - return args[0].price(); + return string_value(args.get<amount_t>(0).commodity().symbol()); } -value_t report_t::fn_commodity(call_scope_t& args) +value_t report_t::fn_nail_down(call_scope_t& args) { - return string_value(args.get<amount_t>(0).commodity().symbol()); + value_t arg0(args[0]); + value_t arg1(args[1]); + + switch (arg0.type()) { + case value_t::AMOUNT: { + amount_t tmp(arg0.as_amount()); + if (tmp.has_commodity() && ! tmp.is_null() && ! tmp.is_realzero()) { + arg1 = arg1.strip_annotations(keep_details_t()).to_amount(); + expr_t value_expr(is_expr(arg1) ? + as_expr(arg1) : + expr_t::op_t::wrap_value(arg1.unrounded() / + arg0.number())); + std::ostringstream buf; + value_expr.print(buf); + value_expr.set_text(buf.str()); + + tmp.set_commodity(tmp.commodity().nail_down(value_expr)); + } + return tmp; + } + + case value_t::BALANCE: { + balance_t tmp; + foreach (const balance_t::amounts_map::value_type& pair, + arg0.as_balance_lval().amounts) { + call_scope_t inner_args(*args.parent); + inner_args.push_back(pair.second); + inner_args.push_back(arg1); + tmp += fn_nail_down(inner_args).as_amount(); + } + return tmp; + } + + case value_t::SEQUENCE: { + value_t tmp; + foreach (value_t& value, arg0.as_sequence_lval()) { + call_scope_t inner_args(*args.parent); + inner_args.push_back(value); + inner_args.push_back(arg1); + tmp.push_back(fn_nail_down(inner_args)); + } + return tmp; + } + + default: + throw_(std::runtime_error, _("Attempting to nail down %1") + << args[0].label()); + } + return arg0; } value_t report_t::fn_lot_date(call_scope_t& args) @@ -879,11 +995,9 @@ value_t report_t::echo_command(call_scope_t& args) value_t report_t::pricemap_command(call_scope_t& args) { std::ostream& out(output_stream); - - commodity_pool_t::current_pool->print_pricemap - (out, what_to_keep(), args.has<string>(0) ? - optional<datetime_t>(datetime_t(parse_date(args.get<string>(0)))) : none); - + commodity_pool_t::current_pool->commodity_price_history.print_map + (out, args.has<string>(0) ? + datetime_t(parse_date(args.get<string>(0))) : datetime_t()); return true; } @@ -914,6 +1028,9 @@ option_t<report_t> * report_t::lookup_option(const char * p) case 'G': OPT_CH(gain); break; + case 'H': + OPT_CH(historical); + break; case 'I': OPT_CH(price); break; @@ -960,12 +1077,13 @@ option_t<report_t> * report_t::lookup_option(const char * p) OPT(abbrev_len_); else OPT_(account_); else OPT(actual); - else OPT(actual_dates); else OPT(add_budget); else OPT(amount_); else OPT(amount_data); + else OPT_ALT(primary_date, actual_dates); else OPT(anon); else OPT_ALT(color, ansi); + else OPT(auto_match); else OPT(average); else OPT(account_width_); else OPT(amount_width_); @@ -977,6 +1095,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT_(begin_); else OPT(bold_if_); else OPT(budget); + else OPT(budget_format_); else OPT(by_payee); break; case 'c': @@ -995,21 +1114,22 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(date_); else OPT(date_format_); else OPT(datetime_format_); + else OPT(dc); else OPT(depth_); else OPT(deviation); else OPT_(display_); else OPT(display_amount_); else OPT(display_total_); - else OPT_ALT(dow, days-of-week); + else OPT_ALT(dow, days_of_week); else OPT(date_width_); break; case 'e': - OPT(effective); - else OPT(empty); + OPT(empty); else OPT_(end_); else OPT(equity); else OPT(exact); else OPT(exchange_); + else OPT_ALT(aux_date, effective); break; case 'f': OPT(flat); @@ -1021,17 +1141,19 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT_ALT(head_, first_); break; case 'g': - OPT(gain); + OPT_ALT(gain, change); else OPT(group_by_); else OPT(group_title_format_); else OPT(generated); break; case 'h': OPT(head_); + else OPT(historical); break; case 'i': OPT(invert); else OPT(inject_); + else OPT(immediate); break; case 'j': OPT_CH(amount_data); @@ -1040,13 +1162,13 @@ option_t<report_t> * report_t::lookup_option(const char * p) OPT_(limit_); else OPT(lot_dates); else OPT(lot_prices); - else OPT(lot_tags); + else OPT_ALT(lot_notes, lot_tags); else OPT(lots); else OPT(lots_actual); else OPT_ALT(tail_, last_); break; case 'm': - OPT(market); + OPT_ALT(market, value); else OPT(monthly); else OPT(meta_); else OPT(meta_width_); @@ -1093,6 +1215,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(revalued); else OPT(revalued_only); else OPT(revalued_total_); + else OPT_ALT(rich_data, detail); break; case 's': OPT(sort_); @@ -1109,6 +1232,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(total_data); else OPT(truncate_); else OPT(total_width_); + else OPT(time_report); break; case 'u': OPT(unbudgeted); @@ -1219,12 +1343,14 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, else if (is_eq(p, "display_total")) return MAKE_FUNCTOR(report_t::fn_display_total); else if (is_eq(p, "date")) - return MAKE_FUNCTOR(report_t::fn_now); + return MAKE_FUNCTOR(report_t::fn_today); break; case 'f': if (is_eq(p, "format_date")) return MAKE_FUNCTOR(report_t::fn_format_date); + else if (is_eq(p, "format_datetime")) + return MAKE_FUNCTOR(report_t::fn_format_datetime); else if (is_eq(p, "format")) return MAKE_FUNCTOR(report_t::fn_format); else if (is_eq(p, "floor")) @@ -1262,6 +1388,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(fn_null); else if (is_eq(p, "now")) return MAKE_FUNCTOR(report_t::fn_now); + else if (is_eq(p, "nail_down")) + return MAKE_FUNCTOR(report_t::fn_nail_down); break; case 'o': @@ -1274,8 +1402,6 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(fn_false); else if (is_eq(p, "percent")) 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; @@ -1378,79 +1504,98 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return MAKE_OPT_HANDLER(report_t, handler); break; +#define POSTS_REPORTER(formatter) \ + WRAP_FUNCTOR(reporter<>(post_handler_ptr(formatter), *this, \ + string("#") + p)) + + // Can't use WRAP_FUNCTOR here because the template arguments + // confuse the parser +#define POSTS_REPORTER_(method, formatter) \ + expr_t::op_t::wrap_functor \ + (reporter<post_t, post_handler_ptr, method> \ + (post_handler_ptr(formatter), *this, string("#") + p)) + +#define FORMATTED_POSTS_REPORTER(format) \ + POSTS_REPORTER \ + (new format_posts \ + (*this, report_format(HANDLER(format)), \ + maybe_format(HANDLER(prepend_format_)), \ + HANDLED(prepend_width_) ? \ + lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0)) + +#define FORMATTED_COMMODITIES_REPORTER(format) \ + POSTS_REPORTER_ \ + (&report_t::commodities_report, \ + new format_posts \ + (*this, report_format(HANDLER(format)), \ + maybe_format(HANDLER(prepend_format_)), \ + HANDLED(prepend_width_) ? \ + lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0)) + +#define ACCOUNTS_REPORTER(formatter) \ + expr_t::op_t::wrap_functor(reporter<account_t, acct_handler_ptr, \ + &report_t::accounts_report> \ + (acct_handler_ptr(formatter), *this, \ + string("#") + p)) + +#define FORMATTED_ACCOUNTS_REPORTER(format) \ + ACCOUNTS_REPORTER \ + (new format_accounts \ + (*this, report_format(HANDLER(format)), \ + maybe_format(HANDLER(prepend_format_)), \ + HANDLED(prepend_width_) ? \ + lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0)) + case symbol_t::COMMAND: switch (*p) { case 'a': if (is_eq(p, "accounts")) { - return WRAP_FUNCTOR(reporter<>(new report_accounts(*this), *this, - "#accounts")); + return POSTS_REPORTER(new report_accounts(*this)); } break; case 'b': if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) { - return expr_t::op_t::wrap_functor - (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> - (new format_accounts(*this, report_format(HANDLER(balance_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t()), - *this, "#balance")); + return FORMATTED_ACCOUNTS_REPORTER(balance_format_); } else if (is_eq(p, "budget")) { - HANDLER(amount_).set_expr(string("#budget"), "(amount, 0)"); + HANDLER(amount_).on(string("#budget"), "(amount, 0)"); budget_flags |= BUDGET_WRAP_VALUES; if (! (budget_flags & ~BUDGET_WRAP_VALUES)) budget_flags |= BUDGET_BUDGETED; - return expr_t::op_t::wrap_functor - (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> - (new format_accounts(*this, report_format(HANDLER(budget_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t()), - *this, "#budget")); + return FORMATTED_ACCOUNTS_REPORTER(budget_format_); } break; case 'c': if (is_eq(p, "csv")) { - return WRAP_FUNCTOR - (reporter<> - (new format_posts(*this, report_format(HANDLER(csv_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t()), - *this, "#csv")); + return FORMATTED_POSTS_REPORTER(csv_format_); } else if (is_eq(p, "cleared")) { - HANDLER(amount_).set_expr(string("#cleared"), - "(amount, cleared ? amount : 0)"); - return expr_t::op_t::wrap_functor - (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> - (new format_accounts(*this, report_format(HANDLER(cleared_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t()), - *this, "#cleared")); + HANDLER(amount_).on(string("#cleared"), + "(amount, cleared ? amount : 0)"); + return FORMATTED_ACCOUNTS_REPORTER(cleared_format_); } else if (is_eq(p, "convert")) { return WRAP_FUNCTOR(convert_command); } else if (is_eq(p, "commodities")) { - return WRAP_FUNCTOR(reporter<>(new report_commodities(*this), *this, - "#commodities")); + return POSTS_REPORTER(new report_commodities(*this)); } break; case 'e': if (is_eq(p, "equity")) { - HANDLER(generated).on_only(string("#equity")); - return WRAP_FUNCTOR(reporter<>(new print_xacts(*this), *this, "#equity")); + HANDLER(generated).on("#equity"); + return POSTS_REPORTER(new print_xacts(*this)); } else if (is_eq(p, "entry")) { return WRAP_FUNCTOR(xact_command); } else if (is_eq(p, "emacs")) { - return WRAP_FUNCTOR - (reporter<>(new format_emacs_posts(output_stream), *this, "#emacs")); + return POSTS_REPORTER(new format_emacs_posts(output_stream)); } else if (is_eq(p, "echo")) { return MAKE_FUNCTOR(report_t::echo_command); @@ -1459,51 +1604,32 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, case 'o': if (is_eq(p, "org")) { - return WRAP_FUNCTOR - (reporter<> - (new posts_to_org_table(*this, maybe_format(HANDLER(prepend_format_))), - *this, "#org")); + return POSTS_REPORTER(new posts_to_org_table + (*this, maybe_format(HANDLER(prepend_format_)))); } break; case 'p': if (*(p + 1) == '\0' || is_eq(p, "print")) { - return WRAP_FUNCTOR - (reporter<>(new print_xacts(*this, HANDLED(raw)), *this, "#print")); + return POSTS_REPORTER(new print_xacts(*this, HANDLED(raw))); } else if (is_eq(p, "prices")) { - return expr_t::op_t::wrap_functor - (reporter<post_t, post_handler_ptr, &report_t::commodities_report> - (new format_posts(*this, report_format(HANDLER(prices_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t()), - *this, "#prices")); + return FORMATTED_COMMODITIES_REPORTER(prices_format_); } - else if (is_eq(p, "pricedb")) { - return expr_t::op_t::wrap_functor - (reporter<post_t, post_handler_ptr, &report_t::commodities_report> - (new format_posts(*this, report_format(HANDLER(pricedb_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t()), - *this, "#pricedb")); + else if (is_eq(p, "pricedb") || is_eq(p, "pricesdb")) { + return FORMATTED_COMMODITIES_REPORTER(pricedb_format_); } else if (is_eq(p, "pricemap")) { return MAKE_FUNCTOR(report_t::pricemap_command); } else if (is_eq(p, "payees")) { - return WRAP_FUNCTOR(reporter<>(new report_payees(*this), *this, - "#payees")); + return POSTS_REPORTER(new report_payees(*this)); } break; case 'r': if (*(p + 1) == '\0' || is_eq(p, "reg") || is_eq(p, "register")) { - return WRAP_FUNCTOR - (reporter<> - (new format_posts(*this, report_format(HANDLER(register_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t()), - *this, "#register")); + return FORMATTED_POSTS_REPORTER(register_format_); } else if (is_eq(p, "reload")) { return MAKE_FUNCTOR(report_t::reload_command); @@ -1515,13 +1641,15 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(report_statistics); else if (is_eq(p, "source")) return WRAP_FUNCTOR(source_command); + else if (is_eq(p, "select")) + return WRAP_FUNCTOR(select_command); break; case 'x': if (is_eq(p, "xact")) return WRAP_FUNCTOR(xact_command); else if (is_eq(p, "xml")) - return WRAP_FUNCTOR(reporter<>(new format_xml(*this), *this, "#xml")); + return POSTS_REPORTER(new format_xml(*this)); break; } break; @@ -1543,11 +1671,9 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(format_command); break; case 'g': - if (is_eq(p, "generate")) { - return expr_t::op_t::wrap_functor - (reporter<post_t, post_handler_ptr, &report_t::generate_report> - (new print_xacts(*this), *this, "#generate")); - } + if (is_eq(p, "generate")) + return POSTS_REPORTER_(&report_t::generate_report, + new print_xacts(*this)); break; case 'p': if (is_eq(p, "parse")) @@ -1559,6 +1685,10 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, if (is_eq(p, "query")) return WRAP_FUNCTOR(query_command); break; + case 's': + if (is_eq(p, "script")) + return WRAP_FUNCTOR(source_command); + break; case 't': if (is_eq(p, "template")) return WRAP_FUNCTOR(template_command); diff --git a/src/report.h b/src/report.h index 5b403205..e7d68dda 100644 --- a/src/report.h +++ b/src/report.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -119,9 +119,23 @@ public: explicit report_t(session_t& _session) : session(_session), terminus(CURRENT_TIME()), - budget_flags(BUDGET_NO_BUDGET) {} + budget_flags(BUDGET_NO_BUDGET) { + TRACE_CTOR(report_t, "session_t&"); + } + report_t(const report_t& report) + : session(report.session), + output_stream(report.output_stream), + terminus(report.terminus), + budget_flags(report.budget_flags) { + TRACE_CTOR(report_t, "copy"); + } virtual ~report_t() { + TRACE_DTOR(report_t); + output_stream.close(); + } + + void quick_close() { output_stream.close(); } @@ -167,10 +181,11 @@ public: value_t fn_quoted(call_scope_t& scope); value_t fn_join(call_scope_t& scope); value_t fn_format_date(call_scope_t& scope); + value_t fn_format_datetime(call_scope_t& scope); value_t fn_ansify_if(call_scope_t& scope); value_t fn_percent(call_scope_t& scope); - value_t fn_price(call_scope_t& scope); value_t fn_commodity(call_scope_t& scope); + value_t fn_nail_down(call_scope_t& scope); value_t fn_lot_date(call_scope_t& scope); value_t fn_lot_price(call_scope_t& scope); value_t fn_lot_tag(call_scope_t& scope); @@ -215,7 +230,7 @@ public: bool lots = HANDLED(lots) || HANDLED(lots_actual); return keep_details_t(lots || HANDLED(lot_prices), lots || HANDLED(lot_dates), - lots || HANDLED(lot_tags), + lots || HANDLED(lot_notes), HANDLED(lots_actual)); } @@ -224,11 +239,12 @@ public: HANDLER(abbrev_len_).report(out); HANDLER(account_).report(out); HANDLER(actual).report(out); - HANDLER(actual_dates).report(out); HANDLER(add_budget).report(out); HANDLER(amount_).report(out); HANDLER(amount_data).report(out); HANDLER(anon).report(out); + HANDLER(auto_match).report(out); + HANDLER(aux_date).report(out); HANDLER(average).report(out); HANDLER(balance_format_).report(out); HANDLER(base).report(out); @@ -249,13 +265,13 @@ public: HANDLER(date_).report(out); HANDLER(date_format_).report(out); HANDLER(datetime_format_).report(out); + HANDLER(dc).report(out); HANDLER(depth_).report(out); HANDLER(deviation).report(out); HANDLER(display_).report(out); HANDLER(display_amount_).report(out); HANDLER(display_total_).report(out); HANDLER(dow).report(out); - HANDLER(effective).report(out); HANDLER(empty).report(out); HANDLER(end_).report(out); HANDLER(equity).report(out); @@ -272,12 +288,13 @@ public: HANDLER(group_by_).report(out); HANDLER(group_title_format_).report(out); HANDLER(head_).report(out); + HANDLER(immediate).report(out); HANDLER(inject_).report(out); HANDLER(invert).report(out); HANDLER(limit_).report(out); HANDLER(lot_dates).report(out); HANDLER(lot_prices).report(out); - HANDLER(lot_tags).report(out); + HANDLER(lot_notes).report(out); HANDLER(lots).report(out); HANDLER(lots_actual).report(out); HANDLER(market).report(out); @@ -302,6 +319,7 @@ public: HANDLER(price).report(out); HANDLER(prices_format_).report(out); HANDLER(pricedb_format_).report(out); + HANDLER(primary_date).report(out); HANDLER(quantity).report(out); HANDLER(quarterly).report(out); HANDLER(raw).report(out); @@ -312,6 +330,7 @@ public: HANDLER(revalued).report(out); HANDLER(revalued_only).report(out); HANDLER(revalued_total_).report(out); + HANDLER(rich_data).report(out); HANDLER(seed_).report(out); HANDLER(sort_).report(out); HANDLER(sort_all_).report(out); @@ -319,6 +338,7 @@ public: HANDLER(start_of_week_).report(out); HANDLER(subtotal).report(out); HANDLER(tail_).report(out); + HANDLER(time_report).report(out); HANDLER(total_).report(out); HANDLER(total_data).report(out); HANDLER(truncate_).report(out); @@ -351,252 +371,299 @@ public: * Option handlers */ - OPTION__(report_t, abbrev_len_, - CTOR(report_t, abbrev_len_) { on_with(none, 2L); }); + OPTION__ + (report_t, abbrev_len_, + CTOR(report_t, abbrev_len_) { + on(none, "2"); + }); + OPTION(report_t, account_); OPTION_(report_t, actual, DO() { // -L - parent->HANDLER(limit_).on(string("--actual"), "actual"); + OTHER(limit_).on(whence, "actual"); }); - OPTION(report_t, actual_dates); - OPTION_(report_t, add_budget, DO() { parent->budget_flags |= BUDGET_BUDGETED | BUDGET_UNBUDGETED; }); OPTION__ (report_t, amount_, // -t - expr_t expr; - CTOR(report_t, amount_) { - set_expr(none, "amount"); - } - void set_expr(const optional<string>& whence, const string& str) { - expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); + DECL1(report_t, amount_, merged_expr_t, expr, ("amount_expr", "amount")) {} + DO_(str) { + expr.append(str); }); OPTION(report_t, amount_data); // -j OPTION(report_t, anon); + OPTION(report_t, auto_match); OPTION_(report_t, average, DO() { // -A - parent->HANDLER(display_total_) - .set_expr(string("--average"), "count>0?(total_expr/count):0"); + OTHER(display_total_) + .on(whence, "count>0?(display_total/count):0"); }); - OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) { - on(none, - "%(ansify_if(" - " justify(scrub(display_total), 20, 20 + prepend_width, true, color)," - " bold if should_bold))" - " %(!options.flat ? depth_spacer : \"\")" - "%-(ansify_if(" - " ansify_if(partial_account(options.flat), blue if color)," - " bold if should_bold))\n%/" - "%$1\n%/" - "%(prepend_width ? \" \" * prepend_width : \"\")" - "--------------------\n"); - }); + OPTION__ + (report_t, balance_format_, + CTOR(report_t, balance_format_) { + on(none, + "%(ansify_if(" + " justify(scrub(display_total), 20," + " 20 + int(prepend_width), true, color)," + " bold if should_bold))" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(" + " ansify_if(partial_account(options.flat), blue if color)," + " bold if should_bold))\n%/" + "%$1\n%/" + "%(prepend_width ? \" \" * int(prepend_width) : \"\")" + "--------------------\n"); + }); OPTION(report_t, base); OPTION_(report_t, basis, DO() { // -B - parent->HANDLER(revalued).on_only(string("--basis")); - parent->HANDLER(amount_).set_expr(string("--basis"), "rounded(cost)"); + OTHER(revalued).on(whence); + OTHER(amount_).expr.set_base_expr("rounded(cost)"); }); - OPTION_(report_t, begin_, DO_(args) { // -b - date_interval_t interval(args.get<string>(1)); - optional<date_t> begin = interval.begin(); - if (! begin) + OPTION_(report_t, begin_, DO_(str) { // -b + date_interval_t interval(str); + if (optional<date_t> begin = interval.begin()) { + string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; + OTHER(limit_).on(whence, predicate); + } else { throw_(std::invalid_argument, - _("Could not determine beginning of period '%1'") - << args.get<string>(1)); - - string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; - parent->HANDLER(limit_).on(string("--begin"), predicate); + _("Could not determine beginning of period '%1'") << str); + } }); - OPTION__ + OPTION_ (report_t, bold_if_, expr_t expr; - CTOR(report_t, bold_if_) {} - void set_expr(const optional<string>& whence, const string& str) { + DO_(str) { expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); }); OPTION_(report_t, budget, DO() { parent->budget_flags |= BUDGET_BUDGETED; }); - OPTION__(report_t, budget_format_, CTOR(report_t, budget_format_) { - on(none, - "%(justify(scrub(get_at(total_expr, 0)), 12, -1, true, color))" - " %(justify(-scrub(get_at(total_expr, 1)), 12, " - " 12 + 1 + 12, true, color))" - " %(justify(scrub(get_at(total_expr, 1) + " - " get_at(total_expr, 0)), 12, " - " 12 + 1 + 12 + 1 + 12, true, color))" - " %(ansify_if(" - " justify((get_at(total_expr, 1) ? " - " (100% * scrub(get_at(total_expr, 0))) / " - " -scrub(get_at(total_expr, 1)) : 0), " - " 5, -1, true, false)," - " magenta if (color and get_at(total_expr, 1) and " - " (abs(quantity(scrub(get_at(total_expr, 0))) / " - " quantity(scrub(get_at(total_expr, 1)))) >= 1))))" - " %(!options.flat ? depth_spacer : \"\")" - "%-(ansify_if(partial_account(options.flat), blue if color))\n" - "%/%$1 %$2 %$3 %$4\n%/" - "%(prepend_width ? \" \" * prepend_width : \"\")" - "------------ ------------ ------------ -----\n"); - }); + OPTION__ + (report_t, budget_format_, + CTOR(report_t, budget_format_) { + on(none, + "%(justify(scrub(get_at(display_total, 0)), 12, -1, true, color))" + " %(justify(-scrub(get_at(display_total, 1)), 12, " + " 12 + 1 + 12, true, color))" + " %(justify(scrub(get_at(display_total, 1) + " + " get_at(display_total, 0)), 12, " + " 12 + 1 + 12 + 1 + 12, true, color))" + " %(ansify_if(" + " justify((get_at(display_total, 1) ? " + " (100% * scrub(get_at(display_total, 0))) / " + " -scrub(get_at(display_total, 1)) : 0), " + " 5, -1, true, false)," + " magenta if (color and get_at(display_total, 1) and " + " (abs(quantity(scrub(get_at(display_total, 0))) / " + " quantity(scrub(get_at(display_total, 1)))) >= 1))))" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(partial_account(options.flat), blue if color))\n" + "%/%$1 %$2 %$3 %$4\n%/" + "%(prepend_width ? \" \" * int(prepend_width) : \"\")" + "------------ ------------ ------------ -----\n"); + }); OPTION(report_t, by_payee); // -P OPTION_(report_t, cleared, DO() { // -C - parent->HANDLER(limit_).on(string("--cleared"), "cleared"); + OTHER(limit_).on(whence, "cleared"); }); - OPTION__(report_t, cleared_format_, CTOR(report_t, cleared_format_) { - on(none, - "%(justify(scrub(get_at(total_expr, 0)), 16, 16 + prepend_width, " - " true, color)) %(justify(scrub(get_at(total_expr, 1)), 18, " - " 36 + prepend_width, true, color))" - " %(latest_cleared ? format_date(latest_cleared) : \" \")" - " %(!options.flat ? depth_spacer : \"\")" - "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" - "%$1 %$2 %$3\n%/" - "%(prepend_width ? \" \" * prepend_width : \"\")" - "---------------- ---------------- ---------\n"); - }); + OPTION__ + (report_t, cleared_format_, + CTOR(report_t, cleared_format_) { + on(none, + "%(justify(scrub(get_at(display_total, 0)), 16, 16 + int(prepend_width), " + " true, color)) %(justify(scrub(get_at(display_total, 1)), 18, " + " 36 + int(prepend_width), true, color))" + " %(latest_cleared ? format_date(latest_cleared) : \" \")" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" + "%$1 %$2 %$3\n%/" + "%(prepend_width ? \" \" * int(prepend_width) : \"\")" + "---------------- ---------------- ---------\n"); + }); OPTION(report_t, color); OPTION_(report_t, collapse, DO() { // -n // Make sure that balance reports are collapsed too, but only apply it // to account xacts - parent->HANDLER(display_).on(string("--collapse"), "post|depth<=1"); + OTHER(display_).on(whence, "post|depth<=1"); }); OPTION_(report_t, collapse_if_zero, DO() { - parent->HANDLER(collapse).on_only(string("--collapse-if-zero")); + OTHER(collapse).on(whence); }); OPTION(report_t, columns_); OPTION(report_t, count); - OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) { - on(none, - "%(quoted(date))," - "%(quoted(code))," - "%(quoted(payee))," - "%(quoted(display_account))," - "%(quoted(commodity))," - "%(quoted(quantity(scrub(display_amount))))," - "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," - "%(quoted(join(note | xact.note)))\n"); - }); + OPTION__ + (report_t, csv_format_, + CTOR(report_t, csv_format_) { + on(none, + "%(quoted(date))," + "%(quoted(code))," + "%(quoted(payee))," + "%(quoted(display_account))," + "%(quoted(commodity))," + "%(quoted(quantity(scrub(display_amount))))," + "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," + "%(quoted(join(note | xact.note)))\n"); + }); OPTION_(report_t, current, DO() { // -c - parent->HANDLER(limit_).on(string("--current"), "date<=today"); + OTHER(limit_).on(whence, "date<=today"); }); OPTION_(report_t, daily, DO() { // -D - parent->HANDLER(period_).on(string("--daily"), "daily"); + OTHER(period_).on(whence, "daily"); }); OPTION(report_t, date_); OPTION(report_t, date_format_); OPTION(report_t, datetime_format_); - OPTION_(report_t, depth_, DO_(args) { - parent->HANDLER(display_) - .on(string("--depth"), string("depth<=") + args.get<string>(1)); + OPTION_(report_t, dc, DO() { + OTHER(amount_).expr.set_base_expr + ("(amount > 0 ? amount : 0, amount < 0 ? amount : 0)"); + + OTHER(register_format_) + .on(none, + "%(ansify_if(" + " ansify_if(justify(format_date(date), int(date_width))," + " green if color and date > today)," + " bold if should_bold))" + " %(ansify_if(" + " ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), " + " bold if color and !cleared and actual)," + " bold if should_bold))" + " %(ansify_if(" + " ansify_if(justify(truncated(display_account, int(account_width), " + " int(abbrev_len)), int(account_width))," + " blue if color)," + " bold if should_bold))" + " %(ansify_if(" + " justify(scrub(abs(get_at(display_amount, 0))), int(amount_width), " + " 3 + int(meta_width) + int(date_width) + int(payee_width)" + " + int(account_width) + int(amount_width) + int(prepend_width)," + " true, color)," + " bold if should_bold))" + " %(ansify_if(" + " justify(scrub(abs(get_at(display_amount, 1))), int(amount_width), " + " 4 + int(meta_width) + int(date_width) + int(payee_width)" + " + int(account_width) + int(amount_width) + int(amount_width) + int(prepend_width)," + " true, color)," + " bold if should_bold))" + " %(ansify_if(" + " justify(scrub(get_at(display_total, 0) + get_at(display_total, 1)), int(total_width), " + " 5 + int(meta_width) + int(date_width) + int(payee_width)" + " + int(account_width) + int(amount_width) + int(amount_width) + int(total_width)" + " + int(prepend_width), true, color)," + " bold if should_bold))\n%/" + "%(justify(\" \", int(date_width)))" + " %(ansify_if(" + " justify(truncated(has_tag(\"Payee\") ? payee : \" \", " + " int(payee_width)), int(payee_width))," + " bold if should_bold))" + " %$3 %$4 %$5 %$6\n"); + + OTHER(balance_format_) + .on(none, + "%(ansify_if(" + " justify(scrub(abs(get_at(display_total, 0))), 14," + " 14 + int(prepend_width), true, color)," + " bold if should_bold)) " + "%(ansify_if(" + " justify(scrub(abs(get_at(display_total, 1))), 14," + " 14 + 1 + int(prepend_width) + int(total_width), true, color)," + " bold if should_bold)) " + "%(ansify_if(" + " justify(scrub(get_at(display_total, 0) + get_at(display_total, 1)), 14," + " 14 + 2 + int(prepend_width) + int(total_width) + int(total_width), true, color)," + " bold if should_bold))" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(" + " ansify_if(partial_account(options.flat), blue if color)," + " bold if should_bold))\n%/" + "%$1 %$2 %$3\n%/" + "%(prepend_width ? \" \" * int(prepend_width) : \"\")" + "--------------------------------------------\n"); + }); + + OPTION_(report_t, depth_, DO_(str) { + OTHER(display_).on(whence, string("depth<=") + str); }); OPTION_(report_t, deviation, DO() { - parent->HANDLER(display_total_) - .set_expr(string("--deviation"), "amount_expr-total_expr/count"); + OTHER(display_total_) + .on(whence, "display_amount-display_total"); }); - OPTION__ - (report_t, display_, // -d - CTOR(report_t, display_) {} - virtual void on_with(const optional<string>& whence, const value_t& text) { - if (! handled) - option_t<report_t>::on_with(whence, text); - else - option_t<report_t>::on_with(whence, - string_value(string("(") + str() + ")&(" + - text.as_string() + ")")); + OPTION_ + (report_t, display_, + DO_(str) { // -d + if (handled) + value = string("(") + value + ")&(" + str + ")"; }); OPTION__ (report_t, display_amount_, - expr_t expr; - CTOR(report_t, display_amount_) { - set_expr(none, "amount_expr"); - } - void set_expr(const optional<string>& whence, const string& str) { - expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); + DECL1(report_t, display_amount_, merged_expr_t, expr, + ("display_amount", "amount_expr")) {} + DO_(str) { + expr.append(str); }); OPTION__ (report_t, display_total_, - expr_t expr; - CTOR(report_t, display_total_) { - set_expr(none, "total_expr"); - } - void set_expr(const optional<string>& whence, const string& str) { - expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); + DECL1(report_t, display_total_, merged_expr_t, expr, + ("display_total", "total_expr")) {} + DO_(str) { + expr.append(str); }); OPTION(report_t, dow); - OPTION(report_t, effective); + OPTION(report_t, aux_date); OPTION(report_t, empty); // -E - OPTION_(report_t, end_, DO_(args) { // -e - date_interval_t interval(args.get<string>(1)); + OPTION_(report_t, end_, DO_(str) { // -e // Use begin() here so that if the user says --end=2008, we end on // 2008/01/01 instead of 2009/01/01 (which is what end() would // return). - optional<date_t> end = interval.begin(); - if (! end) + date_interval_t interval(str); + if (optional<date_t> end = interval.begin()) { + string predicate = "date<[" + to_iso_extended_string(*end) + "]"; + OTHER(limit_).on(whence, predicate); + + parent->terminus = datetime_t(*end); + } else { throw_(std::invalid_argument, _("Could not determine end of period '%1'") - << args.get<string>(1)); - - string predicate = "date<[" + to_iso_extended_string(*end) + "]"; - parent->HANDLER(limit_).on(string("--end"), predicate); - - parent->terminus = datetime_t(*end); + << str); + } }); OPTION(report_t, equity); OPTION(report_t, exact); - OPTION_(report_t, exchange_, DO_(args) { // -X - on_with(args.get<string>(0), args[1]); - call_scope_t no_args(*parent); - no_args.push_back(args[0]); - parent->HANDLER(market).parent = parent; - parent->HANDLER(market).handler(no_args); + OPTION_(report_t, exchange_, DO_() { // -X + // Using -X implies -V. The main difference is that now + // HANDLER(exchange_) contains the name of a commodity, which + // is accessed via the "exchange" value expression function. + OTHER(market).on(whence); }); OPTION(report_t, flat); @@ -607,115 +674,112 @@ public: OPTION(report_t, format_); // -F OPTION_(report_t, gain, DO() { // -G - parent->HANDLER(revalued).on_only(string("--gain")); - parent->HANDLER(amount_).set_expr(string("--gain"), "(amount, cost)"); + OTHER(revalued).on(whence); + OTHER(amount_).expr.set_base_expr("(amount, cost)"); + // Since we are displaying the amounts of revalued postings, they // will end up being composite totals, and hence a pair of pairs. - parent->HANDLER(display_amount_) - .set_expr(string("--gain"), - "use_direct_amount ? amount :" - " (is_seq(get_at(amount_expr, 0)) ?" - " get_at(get_at(amount_expr, 0), 0) :" - " market(get_at(amount_expr, 0), value_date, exchange)" - " - get_at(amount_expr, 1))"); - parent->HANDLER(revalued_total_) - .set_expr(string("--gain"), - "(market(get_at(total_expr, 0), value_date, exchange), " - "get_at(total_expr, 1))"); - parent->HANDLER(display_total_) - .set_expr(string("--gain"), - "use_direct_amount ? total_expr :" - " market(get_at(total_expr, 0), value_date, exchange)" - " - get_at(total_expr, 1)"); + OTHER(display_amount_) + .on(whence, + "use_direct_amount ? amount :" + " (is_seq(get_at(amount_expr, 0)) ?" + " get_at(get_at(amount_expr, 0), 0) :" + " market(get_at(amount_expr, 0), value_date, exchange)" + " - get_at(amount_expr, 1))"); + OTHER(revalued_total_) + .on(whence, + "(market(get_at(total_expr, 0), value_date, exchange), " + "get_at(total_expr, 1))"); + OTHER(display_total_) + .on(whence, + "use_direct_amount ? total_expr :" + " market(get_at(total_expr, 0), value_date, exchange)" + " - get_at(total_expr, 1)"); }); OPTION(report_t, generated); - OPTION__ + OPTION_ (report_t, group_by_, expr_t expr; - CTOR(report_t, group_by_) {} - void set_expr(const optional<string>& whence, const string& str) { + DO_(str) { expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); }); - OPTION__(report_t, group_title_format_, CTOR(report_t, group_title_format_) { - on(none, "%(value)\n"); - }); + OPTION__ + (report_t, group_title_format_, + CTOR(report_t, group_title_format_) { + on(none, "%(value)\n"); + }); OPTION(report_t, head_); + + OPTION_(report_t, historical, DO() { // -H + OTHER(market).on(whence); + OTHER(amount_) + .on(whence, "nail_down(amount_expr, " + "market(amount_expr, value_date, exchange))"); + }); + + OPTION(report_t, immediate); OPTION(report_t, inject_); OPTION_(report_t, invert, DO() { - parent->HANDLER(amount_).set_expr(string("--invert"), "-amount"); + OTHER(amount_).on(whence, "-amount_expr"); }); - OPTION__ - (report_t, limit_, // -l - CTOR(report_t, limit_) {} - virtual void on_with(const optional<string>& whence, const value_t& text) { - if (! handled) - option_t<report_t>::on_with(whence, text); - else - option_t<report_t>::on_with(whence, - string_value(string("(") + str() + ")&(" + - text.as_string() + ")")); + OPTION_ + (report_t, limit_, + DO_(str) { // -l + if (handled) + value = string("(") + value + ")&(" + str + ")"; }); OPTION(report_t, lot_dates); OPTION(report_t, lot_prices); - OPTION(report_t, lot_tags); + OPTION(report_t, lot_notes); OPTION(report_t, lots); OPTION(report_t, lots_actual); OPTION_(report_t, market, DO() { // -V - parent->HANDLER(revalued).on_only(string("--market")); - parent->HANDLER(display_amount_) - .set_expr(string("--market"), - "market(amount_expr, value_date, exchange)"); - parent->HANDLER(display_total_) - .set_expr(string("--market"), - "market(total_expr, value_date, exchange)"); + OTHER(revalued).on(whence); + + OTHER(display_amount_) + .on(whence, "market(display_amount, value_date, exchange)"); + OTHER(display_total_) + .on(whence, "market(display_total, value_date, exchange)"); }); OPTION(report_t, meta_); OPTION_(report_t, monthly, DO() { // -M - parent->HANDLER(period_).on(string("--monthly"), "monthly"); + OTHER(period_).on(whence, "monthly"); }); OPTION_(report_t, no_color, DO() { - parent->HANDLER(color).off(); + OTHER(color).off(); }); OPTION(report_t, no_rounding); OPTION(report_t, no_titles); OPTION(report_t, no_total); - OPTION_(report_t, now_, DO_(args) { - date_interval_t interval(args.get<string>(1)); - optional<date_t> begin = interval.begin(); - if (! begin) + OPTION_(report_t, now_, DO_(str) { + date_interval_t interval(str); + if (optional<date_t> begin = interval.begin()) { + ledger::epoch = parent->terminus = datetime_t(*begin); + } else { throw_(std::invalid_argument, _("Could not determine beginning of period '%1'") - << args.get<string>(1)); - ledger::epoch = parent->terminus = datetime_t(*begin); + << str); + } }); - OPTION__ + OPTION_ (report_t, only_, - CTOR(report_t, only_) {} - virtual void on_with(const optional<string>& whence, const value_t& text) { - if (! handled) - option_t<report_t>::on_with(whence, text); - else - option_t<report_t>::on_with(whence, - string_value(string("(") + str() + ")&(" + - text.as_string() + ")")); + DO_(str) { + if (handled) + value = string("(") + value + ")&(" + str + ")"; }); OPTION(report_t, output_); // -o @@ -736,201 +800,197 @@ public: setenv("LESS", "-FRSX", 0); // don't overwrite } } - } - virtual void on_with(const optional<string>& whence, const value_t& text) { - string cmd(text.to_string()); - if (cmd == "" || cmd == "false" || cmd == "off" || - cmd == "none" || cmd == "no" || cmd == "disable") - option_t<report_t>::off(); - else - option_t<report_t>::on_with(whence, text); }); #else // HAVE_ISATTY - OPTION__ - (report_t, pager_, - CTOR(report_t, pager_) { - } - virtual void on_with(const optional<string>& whence, const value_t& text) { - string cmd(text.to_string()); - if (cmd == "" || cmd == "false" || cmd == "off" || - cmd == "none" || cmd == "no" || cmd == "disable") - option_t<report_t>::off(); - else - option_t<report_t>::on_with(whence, text); - }); + OPTION(report_t, pager_); #endif // HAVE_ISATTY + OPTION_(report_t, no_pager, DO() { + OTHER(pager_).off(); + }); + OPTION(report_t, payee_); OPTION_(report_t, pending, DO() { // -C - parent->HANDLER(limit_).on(string("--pending"), "pending"); + OTHER(limit_).on(whence, "pending"); }); OPTION_(report_t, percent, DO() { // -% - parent->HANDLER(total_) - .set_expr(string("--percent"), - "((is_account&parent&parent.total)?" - " percent(scrub(total), scrub(parent.total)):0)"); + OTHER(total_) + .on(whence, + "((is_account&parent&parent.total)?" + " percent(scrub(total), scrub(parent.total)):0)"); }); - OPTION__ - (report_t, period_, // -p - CTOR(report_t, period_) {} - virtual void on_with(const optional<string>& whence, const value_t& text) { - if (! handled) - option_t<report_t>::on_with(whence, text); - else - option_t<report_t>::on_with(whence, - string_value(text.as_string() + " " + str())); + OPTION_ + (report_t, period_, + DO_(str) { // -p + if (handled) + value += string(" ") + str; }); OPTION(report_t, pivot_); - OPTION__(report_t, plot_amount_format_, CTOR(report_t, plot_amount_format_) { - on(none, - "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n"); - }); + OPTION__ + (report_t, plot_amount_format_, + CTOR(report_t, plot_amount_format_) { + on(none, + "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n"); + }); - OPTION__(report_t, plot_total_format_, CTOR(report_t, plot_total_format_) { - on(none, - "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n"); - }); + OPTION__ + (report_t, plot_total_format_, + CTOR(report_t, plot_total_format_) { + on(none, + "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n"); + }); OPTION(report_t, prepend_format_); - OPTION_(report_t, prepend_width_, DO_(args) { - value = args.get<long>(1); - }); + OPTION(report_t, prepend_width_); OPTION_(report_t, price, DO() { // -I - parent->HANDLER(display_amount_) - .set_expr(string("--price"), "price(amount_expr)"); - parent->HANDLER(display_total_) - .set_expr(string("--price"), "price(total_expr)"); + OTHER(amount_).expr.set_base_expr("price"); }); - OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) { - on(none, - "%(date) %-8(display_account) %(justify(scrub(display_amount), 12, " - " 2 + 9 + 8 + 12, true, color))\n"); - }); + OPTION__ + (report_t, prices_format_, + CTOR(report_t, prices_format_) { + on(none, + "%(date) %-8(display_account) %(justify(scrub(display_amount), 12, " + " 2 + 9 + 8 + 12, true, color))\n"); + }); - OPTION__(report_t, pricedb_format_, CTOR(report_t, pricedb_format_) { - on(none, - "P %(datetime) %(display_account) %(scrub(display_amount))\n"); - }); + OPTION__ + (report_t, pricedb_format_, + CTOR(report_t, pricedb_format_) { + on(none, + "P %(datetime) %(display_account) %(scrub(display_amount))\n"); + }); + + OPTION(report_t, primary_date); OPTION_(report_t, quantity, DO() { // -O - parent->HANDLER(revalued).off(); - parent->HANDLER(amount_).set_expr(string("--quantity"), "amount"); - parent->HANDLER(total_).set_expr(string("--quantity"), "total"); + OTHER(revalued).off(); + + OTHER(amount_).expr.set_base_expr("amount"); + OTHER(total_).expr.set_base_expr("total"); }); OPTION_(report_t, quarterly, DO() { - parent->HANDLER(period_).on(string("--quarterly"), "quarterly"); + OTHER(period_).on(whence, "quarterly"); }); OPTION(report_t, raw); OPTION_(report_t, real, DO() { // -R - parent->HANDLER(limit_).on(string("--real"), "real"); + OTHER(limit_).on(whence, "real"); }); - OPTION__(report_t, register_format_, CTOR(report_t, register_format_) { - on(none, - "%(ansify_if(" - " ansify_if(justify(format_date(date), date_width)," - " green if color and date > today)," - " bold if should_bold))" - " %(ansify_if(" - " ansify_if(justify(truncated(payee, payee_width), payee_width), " - " bold if color and !cleared and actual)," - " bold if should_bold))" - " %(ansify_if(" - " ansify_if(justify(truncated(display_account, account_width, " - " abbrev_len), account_width)," - " blue if color)," - " bold if should_bold))" - " %(ansify_if(" - " justify(scrub(display_amount), amount_width, " - " 3 + meta_width + date_width + payee_width" - " + account_width + amount_width + prepend_width," - " true, color)," - " bold if should_bold))" - " %(ansify_if(" - " justify(scrub(display_total), total_width, " - " 4 + meta_width + date_width + payee_width" - " + account_width + amount_width + total_width" - " + prepend_width, true, color)," - " bold if should_bold))\n%/" - "%(justify(\" \", date_width))" - " %(ansify_if(" - " justify(truncated(has_tag(\"Payee\") ? payee : \" \", " - " payee_width), payee_width)," - " bold if should_bold))" - " %$3 %$4 %$5\n"); - }); + OPTION__ + (report_t, register_format_, + CTOR(report_t, register_format_) { + on(none, + "%(ansify_if(" + " ansify_if(justify(format_date(date), int(date_width))," + " green if color and date > today)," + " bold if should_bold))" + " %(ansify_if(" + " ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), " + " bold if color and !cleared and actual)," + " bold if should_bold))" + " %(ansify_if(" + " ansify_if(justify(truncated(display_account, int(account_width), " + " int(abbrev_len)), int(account_width))," + " blue if color)," + " bold if should_bold))" + " %(ansify_if(" + " justify(scrub(display_amount), int(amount_width), " + " 3 + int(meta_width) + int(date_width) + int(payee_width)" + " + int(account_width) + int(amount_width) + int(prepend_width)," + " true, color)," + " bold if should_bold))" + " %(ansify_if(" + " justify(scrub(display_total), int(total_width), " + " 4 + int(meta_width) + int(date_width) + int(payee_width)" + " + int(account_width) + int(amount_width) + int(total_width)" + " + int(prepend_width), true, color)," + " bold if should_bold))\n%/" + "%(justify(\" \", int(date_width)))" + " %(ansify_if(" + " justify(truncated(has_tag(\"Payee\") ? payee : \" \", " + " int(payee_width)), int(payee_width))," + " bold if should_bold))" + " %$3 %$4 %$5\n"); + }); OPTION(report_t, related); // -r OPTION_(report_t, related_all, DO() { - parent->HANDLER(related).on_only(string("--related-all")); + OTHER(related).on(whence); }); OPTION(report_t, revalued); OPTION(report_t, revalued_only); - OPTION__ + OPTION_ (report_t, revalued_total_, expr_t expr; - CTOR(report_t, revalued_total_) {} - void set_expr(const optional<string>& whence, const string& str) { + DO_(str) { expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); }); + OPTION(report_t, rich_data); + OPTION(report_t, seed_); - OPTION_(report_t, sort_, DO_(args) { // -S - on_with(args.get<string>(0), args[1]); - parent->HANDLER(sort_xacts_).off(); - parent->HANDLER(sort_all_).off(); + OPTION_(report_t, sort_, DO_(str) { // -S + OTHER(sort_xacts_).off(); + OTHER(sort_all_).off(); }); - OPTION_(report_t, sort_all_, DO_(args) { - parent->HANDLER(sort_).on_with(string("--sort-all"), args[1]); - parent->HANDLER(sort_xacts_).off(); + OPTION_(report_t, sort_all_, DO_(str) { + OTHER(sort_).on(whence, str); + OTHER(sort_xacts_).off(); }); - OPTION_(report_t, sort_xacts_, DO_(args) { - parent->HANDLER(sort_).on_with(string("--sort-xacts"), args[1]); - parent->HANDLER(sort_all_).off(); + OPTION_(report_t, sort_xacts_, DO_(str) { + OTHER(sort_).on(whence, str); + OTHER(sort_all_).off(); }); OPTION(report_t, start_of_week_); OPTION(report_t, subtotal); // -s OPTION(report_t, tail_); + OPTION_(report_t, time_report, DO() { + OTHER(balance_format_) + .on(none, + "%(justify(earliest_checkin ? " + " format_datetime(earliest_checkin) : \"\", 19, -1, true)) " + "%(justify(latest_checkout ? " + " format_datetime(latest_checkout) : \"\", 19, -1, true)) " + "%(ansify_if(" + " justify(scrub(display_total), 8," + " 8 + 4 + 19 * 2, true, color), bold if should_bold))" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(" + " ansify_if(partial_account(options.flat), blue if color)," + " bold if should_bold))\n%/" + "%$1 %$2 %$3\n%/" + "%(prepend_width ? \" \" * int(prepend_width) : \"\")" + "--------------------------------------------------\n"); + }); + OPTION__ (report_t, total_, // -T - expr_t expr; - CTOR(report_t, total_) { - set_expr(none, "total"); - } - void set_expr(const optional<string>& whence, const string& str) { - expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); + DECL1(report_t, total_, merged_expr_t, expr, ("total_expr", "total")) {} + DO_(str) { + expr.append(str); }); OPTION(report_t, total_data); // -J - OPTION_(report_t, truncate_, DO_(args) { - string style(args.get<string>(1)); + OPTION_(report_t, truncate_, DO_(style) { if (style == "leading") format_t::default_style = format_t::TRUNCATE_LEADING; else if (style == "middle") @@ -948,7 +1008,7 @@ public: }); OPTION_(report_t, uncleared, DO() { // -U - parent->HANDLER(limit_).on(string("--uncleared"), "uncleared|pending"); + OTHER(limit_).on(whence, "uncleared|pending"); }); OPTION(report_t, unrealized); @@ -957,51 +1017,30 @@ public: OPTION(report_t, unrealized_losses_); OPTION_(report_t, unround, DO() { - parent->HANDLER(display_amount_) - .set_expr(string("--unround"), "unrounded(amount_expr)"); - parent->HANDLER(display_total_) - .set_expr(string("--unround"), "unrounded(total_expr)"); + OTHER(amount_).on(whence, "unrounded(amount_expr)"); + OTHER(total_).on(whence, "unrounded(total_expr)"); }); OPTION_(report_t, weekly, DO() { // -W - parent->HANDLER(period_).on(string("--weekly"), "weekly"); + OTHER(period_).on(whence, "weekly"); }); OPTION_(report_t, wide, DO() { // -w - parent->HANDLER(columns_).on_with(string("--wide"), 132L); + OTHER(columns_).on(whence, "132"); }); OPTION_(report_t, yearly, DO() { // -Y - parent->HANDLER(period_).on(string("--yearly"), "yearly"); + OTHER(period_).on(whence, "yearly"); }); - OPTION__(report_t, meta_width_, - bool specified; - CTOR(report_t, meta_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); - OPTION__(report_t, date_width_, - bool specified; - CTOR(report_t, date_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); - OPTION__(report_t, payee_width_, - bool specified; - CTOR(report_t, payee_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); - OPTION__(report_t, account_width_, - bool specified; - CTOR(report_t, account_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); - OPTION__(report_t, amount_width_, - bool specified; - CTOR(report_t, amount_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); - OPTION__(report_t, total_width_, - bool specified; - CTOR(report_t, total_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); + OPTION(report_t, meta_width_); + OPTION(report_t, date_width_); + OPTION(report_t, payee_width_); + OPTION(report_t, account_width_); + OPTION(report_t, amount_width_); + OPTION(report_t, total_width_); }; - template <class Type = post_t, class handler_ptr = post_handler_ptr, void (report_t::*report_method)(handler_ptr) = @@ -1014,9 +1053,18 @@ class reporter string whence; public: - reporter(item_handler<Type> * _handler, + reporter(shared_ptr<item_handler<Type> > _handler, report_t& _report, const string& _whence) - : handler(_handler), report(_report), whence(_whence) {} + : handler(_handler), report(_report), whence(_whence) { + TRACE_CTOR(reporter, "item_handler<Type>, report_t&, string"); + } + reporter(const reporter& other) + : handler(other.handler), report(other.report), whence(other.whence) { + TRACE_CTOR(reporter, "copy"); + } + ~reporter() throw() { + TRACE_DTOR(reporter); + } value_t operator()(call_scope_t& args) { diff --git a/src/scope.cc b/src/scope.cc index e18b5a0a..00327159 100644 --- a/src/scope.cc +++ b/src/scope.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -35,12 +35,14 @@ namespace ledger { -scope_t * scope_t::default_scope = NULL; +scope_t * scope_t::default_scope = NULL; +empty_scope_t * scope_t::empty_scope = NULL; void symbol_scope_t::define(const symbol_t::kind_t kind, const string& name, expr_t::ptr_op_t def) { - DEBUG("scope.symbols", "Defining '" << name << "' = " << def); + DEBUG("scope.symbols", + "Defining '" << name << "' = " << def << " in " << this); if (! symbols) symbols = symbol_map(); @@ -52,8 +54,8 @@ void symbol_scope_t::define(const symbol_t::kind_t kind, assert(i != symbols->end()); symbols->erase(i); - result = symbols->insert(symbol_map::value_type(symbol_t(kind, name, def), - def)); + result = symbols->insert(symbol_map::value_type + (symbol_t(kind, name, def), def)); if (! result.second) throw_(compile_error, _("Redefinition of '%1' in the same scope") << name); @@ -64,9 +66,12 @@ expr_t::ptr_op_t symbol_scope_t::lookup(const symbol_t::kind_t kind, const string& name) { if (symbols) { + DEBUG("scope.symbols", "Looking for '" << name << "' in " << this); symbol_map::const_iterator i = symbols->find(symbol_t(kind, name)); - if (i != symbols->end()) + if (i != symbols->end()) { + DEBUG("scope.symbols", "Found '" << name << "' in " << this); return (*i).second; + } } return child_scope_t::lookup(kind, name); } @@ -84,8 +89,7 @@ value_t& call_scope_t::resolve(const std::size_t index, value = as_expr(value)->calc(scope, locus, depth); if (required && ! value.is_type(context)) throw_(calc_error, _("Expected %1 for argument %2, but received %3") - << value.label(context) << index - << value.label()); + << value.label(context) << index << value.label()); } return value; } diff --git a/src/scope.h b/src/scope.h index a7b3c5cb..c43d73d6 100644 --- a/src/scope.h +++ b/src/scope.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -70,8 +70,7 @@ struct symbol_t TRACE_CTOR(symbol_t, "symbol_t::kind_t, string"); } symbol_t(const symbol_t& sym) - : kind(sym.kind), name(sym.name), - definition(sym.definition) { + : kind(sym.kind), name(sym.name), definition(sym.definition) { TRACE_CTOR(symbol_t, "copy"); } ~symbol_t() throw() { @@ -81,6 +80,9 @@ struct symbol_t bool operator<(const symbol_t& sym) const { return kind < sym.kind || name < sym.name; } + bool operator==(const symbol_t& sym) const { + return kind == sym.kind || name == sym.name; + } #if defined(HAVE_BOOST_SERIALIZATION) private: @@ -97,10 +99,13 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +class empty_scope_t; + class scope_t { public: - static scope_t * default_scope; + static scope_t * default_scope; + static empty_scope_t * empty_scope; explicit scope_t() { TRACE_CTOR(scope_t, ""); @@ -134,6 +139,24 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +class empty_scope_t : public scope_t +{ +public: + empty_scope_t() { + TRACE_CTOR(empty_scope_t, ""); + } + ~empty_scope_t() throw() { + TRACE_DTOR(empty_scope_t); + } + + virtual string description() { + return _("<empty>"); + } + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t, const string&) { + return NULL; + } +}; + class child_scope_t : public noncopyable, public scope_t { public: @@ -142,8 +165,7 @@ public: explicit child_scope_t() : parent(NULL) { TRACE_CTOR(child_scope_t, ""); } - explicit child_scope_t(scope_t& _parent) - : parent(&_parent) { + explicit child_scope_t(scope_t& _parent) : parent(&_parent) { TRACE_CTOR(child_scope_t, "scope_t&"); } virtual ~child_scope_t() { @@ -187,6 +209,8 @@ public: explicit bind_scope_t(scope_t& _parent, scope_t& _grandchild) : child_scope_t(_parent), grandchild(_grandchild) { + DEBUG("scope.symbols", + "Binding scope " << &_parent << " with " << &_grandchild); TRACE_CTOR(bind_scope_t, "scope_t&, scope_t&"); } virtual ~bind_scope_t() { @@ -225,15 +249,19 @@ private: }; template <typename T> -T * search_scope(scope_t * ptr) +T * search_scope(scope_t * ptr, bool prefer_direct_parents = false) { + DEBUG("scope.search", "Searching scope " << ptr->description()); + if (T * sought = dynamic_cast<T *>(ptr)) return sought; if (bind_scope_t * scope = dynamic_cast<bind_scope_t *>(ptr)) { - if (T * sought = search_scope<T>(&scope->grandchild)) + if (T * sought = search_scope<T>(prefer_direct_parents ? + scope->parent : &scope->grandchild)) return sought; - return search_scope<T>(scope->parent); + return search_scope<T>(prefer_direct_parents ? + &scope->grandchild : scope->parent); } else if (child_scope_t * child_scope = dynamic_cast<child_scope_t *>(ptr)) { return search_scope<T>(child_scope->parent); @@ -242,9 +270,21 @@ T * search_scope(scope_t * ptr) } template <typename T> -inline T& find_scope(child_scope_t& scope, bool skip_this = true) +inline T& find_scope(child_scope_t& scope, bool skip_this = true, + bool prefer_direct_parents = false) +{ + if (T * sought = search_scope<T>(skip_this ? scope.parent : &scope, + prefer_direct_parents)) + return *sought; + + throw_(std::runtime_error, _("Could not find scope")); + return reinterpret_cast<T&>(scope); // never executed +} + +template <typename T> +inline T& find_scope(scope_t& scope, bool prefer_direct_parents = false) { - if (T * sought = search_scope<T>(skip_this ? scope.parent : &scope)) + if (T * sought = search_scope<T>(&scope, prefer_direct_parents)) return *sought; throw_(std::runtime_error, _("Could not find scope")); @@ -258,7 +298,7 @@ class symbol_scope_t : public child_scope_t optional<symbol_map> symbols; public: - explicit symbol_scope_t() { + explicit symbol_scope_t() : child_scope_t() { TRACE_CTOR(symbol_scope_t, ""); } explicit symbol_scope_t(scope_t& _parent) : child_scope_t(_parent) { @@ -347,13 +387,8 @@ protected: class call_scope_t : public context_scope_t { -#if defined(DEBUG_ON) public: -#endif value_t args; -#if defined(DEBUG_ON) -private: -#endif mutable void * ptr; value_t& resolve(const std::size_t index, @@ -370,17 +405,12 @@ public: : context_scope_t(_parent, _parent.type_context(), _parent.type_required()), ptr(NULL), locus(_locus), depth(_depth) { - TRACE_CTOR(call_scope_t, - "scope_t&, value_t::type_t, bool, expr_t::ptr_op_t *, int"); + TRACE_CTOR(call_scope_t, "scope_t&, expr_t::ptr_op_t *, const int"); } virtual ~call_scope_t() { TRACE_DTOR(call_scope_t); } - virtual string description() { - return context_scope_t::description(); - } - void set_args(const value_t& _args) { args = _args; } @@ -453,7 +483,7 @@ public: #if defined(HAVE_BOOST_SERIALIZATION) protected: - explicit call_scope_t() { + explicit call_scope_t() : depth(0) { TRACE_CTOR(call_scope_t, ""); } @@ -635,6 +665,21 @@ call_scope_t::get<expr_t::ptr_op_t>(std::size_t index, bool) { return args[index].as_any<expr_t::ptr_op_t>(); } +inline string join_args(call_scope_t& args) { + std::ostringstream buf; + bool first = true; + + for (std::size_t i = 0; i < args.size(); i++) { + if (first) + first = false; + else + buf << ' '; + buf << args[i]; + } + + return buf.str(); +} + class value_scope_t : public child_scope_t { value_t value; @@ -645,7 +690,12 @@ class value_scope_t : public child_scope_t public: value_scope_t(scope_t& _parent, const value_t& _value) - : child_scope_t(_parent), value(_value) {} + : child_scope_t(_parent), value(_value) { + TRACE_CTOR(value_scope_t, "scope_t&, value_t"); + } + ~value_scope_t() throw() { + TRACE_DTOR(value_scope_t); + } virtual string description() { return parent->description(); @@ -660,7 +710,7 @@ public: if (name == "value") return MAKE_FUNCTOR(value_scope_t::get_value); - return NULL; + return child_scope_t::lookup(kind, name); } }; diff --git a/src/select.cc b/src/select.cc new file mode 100644 index 00000000..56bd3f2d --- /dev/null +++ b/src/select.cc @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <system.hh> + +#include "select.h" +#include "journal.h" +#include "account.h" +#include "report.h" +#include "output.h" +#include "print.h" +#include "chain.h" +#include "filters.h" +#include "scope.h" +#include "op.h" + +namespace ledger { + +namespace { + bool get_principal_identifiers(expr_t::ptr_op_t expr, string& ident, + bool do_transforms = false) + { + bool result = true; + + if (expr->is_ident()) { + string name(expr->as_ident()); + if (name == "date" || name == "aux_date" || name == "payee") { + if (! ident.empty() && + ! (name == "date" || name == "aux_date" || name == "payee")) + result = false; + ident = name; + } + else if (name == "account") { + if (! ident.empty() && ! (name == "account")) + result = false; + ident = name; + if (do_transforms) + expr->set_ident("display_account"); + } + else if (name == "amount") { + if (! ident.empty() && ! (name == "amount")) + result = false; + ident = name; + if (do_transforms) + expr->set_ident("display_amount"); + } + else if (name == "total") { + if (! ident.empty() && ! (name == "total")) + result = false; + ident = name; + if (do_transforms) + expr->set_ident("display_total"); + } + } + + if (expr->kind > expr_t::op_t::TERMINALS || expr->is_scope()) { + if (expr->left()) { + if (! get_principal_identifiers(expr->left(), ident, do_transforms)) + result = false; + if (expr->kind > expr_t::op_t::UNARY_OPERATORS && expr->has_right()) + if (! get_principal_identifiers(expr->right(), ident, do_transforms)) + result = false; + } + } + + return result; + } +} + +value_t select_command(call_scope_t& args) +{ + string text = "select " + join_args(args); + if (text.empty()) + throw std::logic_error(_("Usage: select TEXT")); + + report_t& report(find_scope<report_t>(args)); + + // Our first step is to divide the select statement into its principal + // parts: + // + // SELECT <VALEXPR-LIST> + // FROM <NAME> + // WHERE <VALEXPR> + // DISPLAY <VALEXPR> + // COLLECT <VALEXPR> + // GROUP BY <VALEXPR> + // STYLE <NAME> + + boost::regex select_re + ("(select|from|where|display|collect|group\\s+by|style)\\s+" + "(.+?)" + "(?=(\\s+(from|where|display|collect|group\\s+by|style)\\s+|$))", + boost::regex::perl | boost::regex::icase); + + boost::regex from_accounts_re("from\\s+accounts\\>"); + bool accounts_report = boost::regex_search(text, from_accounts_re); + + boost::sregex_iterator m1(text.begin(), text.end(), select_re); + boost::sregex_iterator m2; + + expr_t::ptr_op_t report_functor; + std::ostringstream formatter; + + while (m1 != m2) { + const boost::match_results<string::const_iterator>& match(*m1); + + string keyword(match[1]); + string arg(match[2]); + + DEBUG("select.parse", "keyword: " << keyword); + DEBUG("select.parse", "arg: " << arg); + + if (keyword == "select") { + expr_t args_expr(arg); + value_t columns(split_cons_expr(args_expr.get_op())); + bool first = true; + string thus_far = ""; + + std::size_t cols = 0; + if (report.HANDLED(columns_)) + cols = lexical_cast<std::size_t>(report.HANDLER(columns_).value); + else if (const char * columns_env = std::getenv("COLUMNS")) + cols = lexical_cast<std::size_t>(columns_env); + else + cols = 80; + + std::size_t date_width = + (report.HANDLED(date_width_) ? + lexical_cast<std::size_t>(report.HANDLER(date_width_).str()) : + static_cast<std::size_t> + (format_date(CURRENT_DATE(),FMT_PRINTED).length())); + std::size_t payee_width = + (report.HANDLED(payee_width_) ? + lexical_cast<std::size_t>(report.HANDLER(payee_width_).str()) : + std::size_t(double(cols) * 0.263157)); + std::size_t account_width = + (report.HANDLED(account_width_) ? + lexical_cast<std::size_t>(report.HANDLER(account_width_).str()) : + std::size_t(double(cols) * 0.302631)); + std::size_t amount_width = + (report.HANDLED(amount_width_) ? + lexical_cast<std::size_t>(report.HANDLER(amount_width_).str()) : + std::size_t(double(cols) * 0.157894)); + std::size_t total_width = + (report.HANDLED(total_width_) ? + lexical_cast<std::size_t>(report.HANDLER(total_width_).str()) : + amount_width); + std::size_t meta_width = + (report.HANDLED(meta_width_) ? + lexical_cast<std::size_t>(report.HANDLER(meta_width_).str()) : + 10); + + bool saw_date = false; + bool saw_payee = false; + bool saw_account = false; + bool saw_amount = false; + bool saw_total = false; + bool saw_meta = false; + + std::size_t cols_needed = 0; + foreach (const value_t& column, columns.to_sequence()) { + string ident; + if (get_principal_identifiers(as_expr(column), ident)) { + if (ident == "date" || ident == "aux_date") { + cols_needed += date_width + 1; + saw_date = true; + } + else if (ident == "payee") { + cols_needed += payee_width + 1; + saw_payee = true; + } + else if (ident == "account") { + cols_needed += account_width + 1; + saw_account = true; + } + else if (ident == "amount") { + cols_needed += amount_width + 1; + saw_amount = true; + } + else if (ident == "total") { + cols_needed += total_width + 1; + saw_total = true; + } + else { + cols_needed += meta_width + 1; + saw_meta = true; + } + } + } + + while ((saw_account || saw_payee) && cols_needed < cols) { + if (saw_account && cols_needed < cols) { + ++account_width; + ++cols_needed; + if (cols_needed < cols) { + ++account_width; + ++cols_needed; + } + } + if (saw_payee && cols_needed < cols) { + ++payee_width; + ++cols_needed; + } + } + + while ((saw_account || saw_payee) && cols_needed > cols && + account_width > 5 && payee_width > 5) { + DEBUG("auto.columns", "adjusting account down"); + if (saw_account && cols_needed > cols) { + --account_width; + --cols_needed; + if (cols_needed > cols) { + --account_width; + --cols_needed; + } + } + if (saw_payee && cols_needed > cols) { + --payee_width; + --cols_needed; + } + DEBUG("auto.columns", "account_width now = " << account_width); + } + + if (! report.HANDLED(date_width_)) + report.HANDLER(date_width_).value = to_string(date_width); + if (! report.HANDLED(payee_width_)) + report.HANDLER(payee_width_).value = to_string(payee_width); + if (! report.HANDLED(account_width_)) + report.HANDLER(account_width_).value = to_string(account_width); + if (! report.HANDLED(amount_width_)) + report.HANDLER(amount_width_).value = to_string(amount_width); + if (! report.HANDLED(total_width_)) + report.HANDLER(total_width_).value = to_string(total_width); + + foreach (const value_t& column, columns.to_sequence()) { + if (first) + first = false; + else + formatter << ' '; + + formatter << "%("; + + string ident; + if (get_principal_identifiers(as_expr(column), ident, true)) { + if (ident == "date" || ident == "aux_date") { + formatter << "ansify_if(" + << "ansify_if(justify(format_date("; + + as_expr(column)->print(formatter); + + formatter << "), int(date_width)),"; + formatter << "green if color and date > today)," + << "bold if should_bold)"; + + if (! thus_far.empty()) + thus_far += " + "; + thus_far += "int(date_width) + 1"; + } + else if (ident == "payee") { + formatter << "ansify_if(" + << "ansify_if(justify(truncated("; + + as_expr(column)->print(formatter); + + formatter << ", int(payee_width)), int(payee_width)),"; + formatter << "bold if color and !cleared and actual)," + << "bold if should_bold)"; + + if (! thus_far.empty()) + thus_far += " + "; + thus_far += "int(payee_width) + 1"; + } + else if (ident == "account") { + formatter << "ansify_if(" + << "ansify_if("; + + if (accounts_report) { + formatter << "partial_account(options.flat), blue if color),"; + } else { + formatter << "justify(truncated("; + as_expr(column)->print(formatter); + formatter << ", int(account_width), int(abbrev_len))," + << "int(account_width)),"; + formatter << "true, color),"; + + if (! thus_far.empty()) + thus_far += " + "; + thus_far += "int(account_width) + 1"; + } + + formatter << " bold if should_bold)"; + } + else if (ident == "amount" || ident == "total") { + formatter << "ansify_if(" + << "justify(scrub("; + + as_expr(column)->print(formatter); + + formatter << "), "; + + if (ident == "amount") + formatter << "int(amount_width),"; + else + formatter << "int(total_width),"; + + if (! thus_far.empty()) + thus_far += " + "; + + if (ident == "amount") + thus_far += "int(amount_width)"; + else + thus_far += "int(total_width)"; + + if (thus_far.empty()) + formatter << "-1"; + else + formatter << thus_far; + + formatter << ", true, color)," + << " bold if should_bold)"; + + thus_far += " + 1"; + } + else { + formatter << "ansify_if(" + << "justify(truncated("; + + as_expr(column)->print(formatter); + + formatter << ", int(meta_width or 10)), int(meta_width) or 10),"; + formatter << "bold if should_bold)"; + + if (! thus_far.empty()) + thus_far += " + "; + thus_far += "(int(meta_width) or 10) + 1"; + } + } + formatter << ")"; + } + formatter << "\\n"; + } + else if (keyword == "from") { + DEBUG("select.parse", "formatter: " << formatter.str()); + + if (arg == "xacts" || arg == "txns" || arg == "transactions") { + report_functor = expr_t::op_t::wrap_functor + (reporter<>(post_handler_ptr(new print_xacts(report, + report.HANDLED(raw))), + report, string("#select"))); + } + else if (arg == "posts" || arg == "postings") { + report_functor = expr_t::op_t::wrap_functor + (reporter<>(post_handler_ptr(new format_posts(report, formatter.str())), + report, string("#select"))); + } + else if (arg == "accounts") { + report_functor = expr_t::op_t::wrap_functor + (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> + (acct_handler_ptr(new format_accounts(report, formatter.str())), + report, string("#select"))); + } + else if (arg == "commodities") { + report_functor = expr_t::op_t::wrap_functor + (reporter<post_t, post_handler_ptr, &report_t::commodities_report> + (post_handler_ptr(new format_posts(report, formatter.str())), + report, string("#select"))); + } + } + else if (keyword == "where") { +#if 0 + query_t query; + keep_details_t keeper(true, true, true); + expr_t::ptr_op_t expr = + query.parse_args(string_value(arg).to_sequence(), keeper, false, true); + report.HANDLER(limit_).on("#select", query.get_query(query_t::QUERY_LIMIT)); +#else + report.HANDLER(limit_).on("#select", arg); +#endif + } + else if (keyword == "display") { + report.HANDLER(display_).on("#select", arg); + } + else if (keyword == "collect") { + report.HANDLER(amount_).on("#select", arg); + } + else if (keyword == "group by") { + report.HANDLER(group_by_).on("#select", arg); + } + else if (keyword == "style") { + if (arg == "csv") { + } + else if (arg == "xml") { + } + else if (arg == "emacs") { + } + else if (arg == "org") { + } + } + + ++m1; + } + + if (! report_functor) { + report_functor = expr_t::op_t::wrap_functor + (reporter<>(post_handler_ptr(new format_posts(report, formatter.str())), + report, string("#select"))); + } + + call_scope_t call_args(report); + return report_functor->as_function()(call_args); +} + +} // namespace ledger diff --git a/src/predicate.cc b/src/select.h index fd301a7d..54883d22 100644 --- a/src/predicate.cc +++ b/src/select.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -29,12 +29,27 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include <system.hh> +/** + * @addtogroup select + */ + +/** + * @file select.h + * @author John Wiegley + * + * @ingroup select + */ +#ifndef _SELECT_H +#define _SELECT_H -#include "predicate.h" -#include "query.h" -#include "op.h" +#include "utils.h" +#include "value.h" namespace ledger { +class call_scope_t; +value_t select_command(call_scope_t& args); + } // namespace ledger + +#endif // _SELECT_H diff --git a/src/series.h b/src/series.h deleted file mode 100644 index 40f34051..00000000 --- a/src/series.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * - Neither the name of New Artisans LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @addtogroup expr - */ - -/** - * @file series.h - * @author John Wiegley - * - * @ingroup expr - */ -#ifndef _SERIES_H -#define _SERIES_H - -#include "scope.h" - -namespace ledger { - -class expr_series_t -{ -protected: - scope_t * context; - -public: - optional<std::list<expr_t> > exprs; - expr_t default_expr; - std::string variable; - - expr_series_t(const std::string& _variable) - : context(NULL), default_expr(_variable), variable(_variable) { - TRACE_CTOR(expr_series_t, "std::string"); - } - expr_series_t(const expr_t& expr, const std::string& _variable) - : context(const_cast<expr_t&>(expr).get_context()), - default_expr(expr), variable(_variable) { - TRACE_CTOR(expr_series_t, "expr_t, std::string"); - } - expr_series_t(const expr_series_t& other) - : context(other.context), exprs(other.exprs), - default_expr(other.default_expr), variable(other.variable) { - TRACE_CTOR(expr_series_t, "copy"); - } - virtual ~expr_series_t() { - TRACE_DTOR(expr_series_t); - } - - scope_t * get_context() { - return context; - } - void set_context(scope_t * scope) { - context = scope; - } - - bool empty() const { - return ! exprs || exprs->empty(); - } - - void push_back(const expr_t& expr) { - if (! exprs) - exprs = std::list<expr_t>(); - exprs->push_back(expr); - } - void pop_back() { - assert(exprs); - exprs->pop_back(); - } - - void mark_uncompiled() { - if (exprs) - foreach (expr_t& expr, *exprs) - expr.mark_uncompiled(); - else - default_expr.mark_uncompiled(); - } - - void compile(scope_t& scope) { - if (exprs) - foreach (expr_t& expr, *exprs) - expr.compile(scope); - else - default_expr.compile(scope); - } - - value_t calc(scope_t& scope) { - if (exprs) { - value_t result; - symbol_scope_t sym_scope(scope); - std::size_t len(exprs->size()); - - foreach (expr_t& expr, *exprs) { - result = expr.calc(sym_scope); - if (--len > 0) - sym_scope.define(symbol_t::FUNCTION, variable, - expr_t::op_t::wrap_value(result)); - } - return result; - } else { - return default_expr.calc(scope); - } - } -}; - -} // namespace ledger - -#endif // _SERIES_H diff --git a/src/session.cc b/src/session.cc index 72e29895..9a77d341 100644 --- a/src/session.cc +++ b/src/session.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -62,12 +62,14 @@ void set_session_context(session_t * session) session_t::session_t() : flush_on_next_data_file(false), journal(new journal_t) { - TRACE_CTOR(session_t, ""); - if (const char * home_var = std::getenv("HOME")) HANDLER(price_db_).on(none, (path(home_var) / ".pricedb").string()); else HANDLER(price_db_).on(none, path("./.pricedb").string()); + + parsing_context.push(); + + TRACE_CTOR(session_t, ""); } std::size_t session_t::read_data(const string& master_account) @@ -89,14 +91,32 @@ std::size_t session_t::read_data(const string& master_account) std::size_t xact_count = 0; - account_t * acct = journal->master; - if (! master_account.empty()) + account_t * acct; + if (master_account.empty()) + acct = journal->master; + else acct = journal->find_account(master_account); optional<path> price_db_path; if (HANDLED(price_db_)) price_db_path = resolve_path(HANDLER(price_db_).str()); + if (HANDLED(explicit)) + journal->force_checking = true; + if (HANDLED(check_payees)) + journal->check_payees = true; + if (HANDLED(day_break)) + journal->day_break = true; + + if (HANDLED(permissive)) + journal->checking_style = journal_t::CHECK_PERMISSIVE; + else if (HANDLED(pedantic)) + journal->checking_style = journal_t::CHECK_ERROR; + else if (HANDLED(strict)) + journal->checking_style = journal_t::CHECK_WARNING; + else if (HANDLED(value_expr_)) + journal->value_expr = HANDLER(value_expr_).str(); + #if defined(HAVE_BOOST_SERIALIZATION) optional<archive_t> cache; if (HANDLED(cache_) && master_account.empty()) @@ -108,8 +128,17 @@ std::size_t session_t::read_data(const string& master_account) #endif // HAVE_BOOST_SERIALIZATION if (price_db_path) { if (exists(*price_db_path)) { - if (journal->read(*price_db_path) > 0) - throw_(parse_error, _("Transactions not allowed in price history file")); + parsing_context.push(*price_db_path); + parsing_context.get_current().journal = journal.get(); + try { + if (journal->read(parsing_context) > 0) + throw_(parse_error, _("Transactions not allowed in price history file")); + } + catch (...) { + parsing_context.pop(); + throw; + } + parsing_context.pop(); } } @@ -128,12 +157,22 @@ std::size_t session_t::read_data(const string& master_account) } buffer.flush(); - std::istringstream buf_in(buffer.str()); - xact_count += journal->read(buf_in, "/dev/stdin", acct); - journal->sources.push_back(journal_t::fileinfo_t()); + shared_ptr<std::istream> stream(new std::istringstream(buffer.str())); + parsing_context.push(stream); } else { - xact_count += journal->read(pathname, acct); + parsing_context.push(pathname); } + + parsing_context.get_current().journal = journal.get(); + parsing_context.get_current().master = acct; + try { + xact_count += journal->read(parsing_context); + } + catch (...) { + parsing_context.pop(); + throw; + } + parsing_context.pop(); } DEBUG("ledger.read", "xact_count [" << xact_count @@ -154,7 +193,7 @@ std::size_t session_t::read_data(const string& master_account) return journal->xacts.size(); } -void session_t::read_journal_files() +journal_t * session_t::read_journal_files() { INFO_START(journal, "Read journal file"); @@ -172,6 +211,37 @@ void session_t::read_journal_files() #if defined(DEBUG_ON) INFO("Found " << count << " transactions"); #endif + + return journal.get(); +} + +journal_t * session_t::read_journal(const path& pathname) +{ + HANDLER(file_).data_files.clear(); + HANDLER(file_).data_files.push_back(pathname); + + return read_journal_files(); +} + +journal_t * session_t::read_journal_from_string(const string& data) +{ + HANDLER(file_).data_files.clear(); + + shared_ptr<std::istream> stream(new std::istringstream(data)); + parsing_context.push(stream); + + parsing_context.get_current().journal = journal.get(); + parsing_context.get_current().master = journal->master; + try { + journal->read(parsing_context); + } + catch (...) { + parsing_context.pop(); + throw; + } + parsing_context.pop(); + + return journal.get(); } void session_t::close_journal_files() @@ -202,6 +272,15 @@ value_t session_t::fn_max(call_scope_t& args) return args[1] > args[0] ? args[1] : args[0]; } +value_t session_t::fn_int(call_scope_t& args) +{ + return args[0].to_long(); +} +value_t session_t::fn_str(call_scope_t& args) +{ + return string_value(args[0].to_string()); +} + value_t session_t::fn_lot_price(call_scope_t& args) { amount_t amt(args.get<amount_t>(1, false)); @@ -238,10 +317,15 @@ option_t<session_t> * session_t::lookup_option(const char * p) break; case 'c': OPT(cache_); + else OPT(check_payees); break; case 'd': OPT(download); // -Q else OPT(decimal_comma); + else OPT(day_break); + break; + case 'e': + OPT(explicit); break; case 'f': OPT_(file_); // -f @@ -258,10 +342,15 @@ option_t<session_t> * session_t::lookup_option(const char * p) case 'p': OPT(price_db_); else OPT(price_exp_); + else OPT(pedantic); + else OPT(permissive); break; case 's': OPT(strict); break; + case 'v': + OPT(value_expr_); + break; } return NULL; } @@ -288,6 +377,11 @@ expr_t::ptr_op_t session_t::lookup(const symbol_t::kind_t kind, return MAKE_FUNCTOR(session_t::fn_lot_tag); break; + case 'i': + if (is_eq(p, "int")) + return MAKE_FUNCTOR(session_t::fn_int); + break; + case 'm': if (is_eq(p, "min")) return MAKE_FUNCTOR(session_t::fn_min); @@ -295,6 +389,11 @@ expr_t::ptr_op_t session_t::lookup(const symbol_t::kind_t kind, return MAKE_FUNCTOR(session_t::fn_max); break; + case 's': + if (is_eq(p, "str")) + return MAKE_FUNCTOR(session_t::fn_str); + break; + default: break; } diff --git a/src/session.h b/src/session.h index b8fd52f2..a0aba91b 100644 --- a/src/session.h +++ b/src/session.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -44,6 +44,7 @@ #include "account.h" #include "journal.h" +#include "context.h" #include "option.h" #include "commodity.h" @@ -57,11 +58,15 @@ class session_t : public symbol_scope_t public: bool flush_on_next_data_file; - std::auto_ptr<journal_t> journal; + + unique_ptr<journal_t> journal; + parse_context_stack_t parsing_context; + optional<expr_t> value_expr; explicit session_t(); virtual ~session_t() { TRACE_DTOR(session_t); + parsing_context.pop(); } virtual string description() { @@ -72,14 +77,18 @@ public: flush_on_next_data_file = truth; } + journal_t * read_journal(const path& pathname); + journal_t * read_journal_from_string(const string& data); std::size_t read_data(const string& master_account = ""); - void read_journal_files(); + journal_t * read_journal_files(); void close_journal_files(); value_t fn_account(call_scope_t& scope); value_t fn_min(call_scope_t& scope); value_t fn_max(call_scope_t& scope); + value_t fn_int(call_scope_t& scope); + value_t fn_str(call_scope_t& scope); value_t fn_lot_price(call_scope_t& scope); value_t fn_lot_date(call_scope_t& scope); value_t fn_lot_tag(call_scope_t& scope); @@ -87,14 +96,20 @@ public: void report_options(std::ostream& out) { HANDLER(cache_).report(out); + HANDLER(check_payees).report(out); + HANDLER(day_break).report(out); HANDLER(download).report(out); HANDLER(decimal_comma).report(out); HANDLER(file_).report(out); HANDLER(input_date_format_).report(out); + HANDLER(explicit).report(out); HANDLER(master_account_).report(out); + HANDLER(pedantic).report(out); + HANDLER(permissive).report(out); HANDLER(price_db_).report(out); HANDLER(price_exp_).report(out); HANDLER(strict).report(out); + HANDLER(value_expr_).report(out); } option_t<session_t> * lookup_option(const char * p); @@ -107,6 +122,8 @@ public: */ OPTION(session_t, cache_); + OPTION(session_t, check_payees); + OPTION(session_t, day_break); OPTION(session_t, download); // -Q OPTION_(session_t, decimal_comma, DO() { @@ -115,33 +132,33 @@ public: OPTION__ (session_t, price_exp_, // -Z - CTOR(session_t, price_exp_) { value = 24L * 3600L; } - DO_(args) { - value = args.get<long>(1) * 60L; - }); + CTOR(session_t, price_exp_) { value = "24"; }); OPTION__ (session_t, file_, // -f std::list<path> data_files; CTOR(session_t, file_) {} - DO_(args) { - assert(args.size() == 2); + DO_(str) { if (parent->flush_on_next_data_file) { data_files.clear(); parent->flush_on_next_data_file = false; } - data_files.push_back(args.get<string>(1)); + data_files.push_back(str); }); - OPTION_(session_t, input_date_format_, DO_(args) { - // This changes static variables inside times.h, which affects the basic - // date parser. - set_input_date_format(args.get<string>(1).c_str()); + OPTION_(session_t, input_date_format_, DO_(str) { + // This changes static variables inside times.h, which affects the + // basic date parser. + set_input_date_format(str.c_str()); }); + OPTION(session_t, explicit); OPTION(session_t, master_account_); + OPTION(session_t, pedantic); + OPTION(session_t, permissive); OPTION(session_t, price_db_); OPTION(session_t, strict); + OPTION(session_t, value_expr_); }; /** diff --git a/src/stats.cc b/src/stats.cc index 524f5a87..0966fee2 100644 --- a/src/stats.cc +++ b/src/stats.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/stats.h b/src/stats.h index b7bf94c5..7b00fec8 100644 --- a/src/stats.h +++ b/src/stats.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/stream.cc b/src/stream.cc index 5d4cf5e0..ce40bfcc 100644 --- a/src/stream.cc +++ b/src/stream.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/stream.h b/src/stream.h index 42c85534..c317ebdf 100644 --- a/src/stream.h +++ b/src/stream.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/system.hh.in b/src/system.hh.in index 42a82e41..552a591a 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -138,38 +138,58 @@ typedef std::ostream::pos_type ostream_pos_type; #include <boost/bind.hpp> #include <boost/cast.hpp> #include <boost/current_function.hpp> + #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/date_time/posix_time/posix_time_io.hpp> #include <boost/date_time/gregorian/gregorian_io.hpp> + #include <boost/filesystem/convenience.hpp> #include <boost/filesystem/exception.hpp> #include <boost/filesystem/fstream.hpp> #include <boost/filesystem/operations.hpp> #include <boost/filesystem/path.hpp> + #if !(defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__) #include <boost/foreach.hpp> #endif #include <boost/function.hpp> + +#include <boost/graph/adjacency_list.hpp> +#include <boost/graph/filtered_graph.hpp> +#include <boost/graph/dijkstra_shortest_paths.hpp> +#include <boost/graph/graphviz.hpp> + #include <boost/intrusive_ptr.hpp> + #include <boost/iostreams/stream.hpp> #include <boost/iostreams/write.hpp> #define BOOST_IOSTREAMS_USE_DEPRECATED 1 #include <boost/iostreams/device/file_descriptor.hpp> + #include <boost/iterator/iterator_facade.hpp> #include <boost/iterator/transform_iterator.hpp> + #include <boost/lexical_cast.hpp> #include <boost/operators.hpp> #include <boost/optional.hpp> #include <boost/ptr_container/ptr_list.hpp> + #include <boost/random/mersenne_twister.hpp> #include <boost/random/uniform_int.hpp> #include <boost/random/uniform_real.hpp> #include <boost/random/variate_generator.hpp> + #if defined(HAVE_BOOST_REGEX_UNICODE) #include <boost/regex/icu.hpp> #else #include <boost/regex.hpp> #endif // HAVE_BOOST_REGEX_UNICODE + +#include <boost/tokenizer.hpp> + +#include <boost/tuple/tuple.hpp> +#include <boost/tuple/tuple_comparison.hpp> + #include <boost/variant.hpp> #include <boost/version.hpp> @@ -226,12 +246,19 @@ void serialize(Archive& ar, boost::intrusive_ptr<T>& ptr, const unsigned int) } } -template <class Archive, class T> -void serialize(Archive&, boost::function<T>&, const unsigned int) -{ +template <class Archive> +void serialize(Archive&, boost::any&, const unsigned int) { + // jww (2012-03-29): Should we really ignore any fields entirely? + // These occur inside value_t::storage_t::data's variant. } template <class Archive> +void serialize(Archive&, boost::blank&, const unsigned int) {} + +template <class Archive, class T> +void serialize(Archive&, boost::function<T>&, const unsigned int) {} + +template <class Archive> void serialize(Archive& ar, istream_pos_type& pos, const unsigned int) { ar & make_binary_object(&pos, sizeof(istream_pos_type)); @@ -257,4 +284,6 @@ void serialize(Archive& ar, istream_pos_type& pos, const unsigned int) #include <boost/python/module_init.hpp> #include <boost/python/suite/indexing/vector_indexing_suite.hpp> +#include <boost/iterator/indirect_iterator.hpp> + #endif // HAVE_BOOST_PYTHON diff --git a/src/temps.cc b/src/temps.cc index 365c33c5..881077f6 100644 --- a/src/temps.cc +++ b/src/temps.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -81,7 +81,8 @@ post_t& temporaries_t::copy_post(post_t& origin, xact_t& xact, return temp; } -post_t& temporaries_t::create_post(xact_t& xact, account_t * account) +post_t& temporaries_t::create_post(xact_t& xact, account_t * account, + bool bidir_link) { if (! post_temps) post_temps = std::list<post_t>(); @@ -93,7 +94,10 @@ post_t& temporaries_t::create_post(xact_t& xact, account_t * account) temp.account = account; temp.account->add_post(&temp); - xact.add_post(&temp); + if (bidir_link) + xact.add_post(&temp); + else + temp.xact = &xact; return temp; } diff --git a/src/temps.h b/src/temps.h index 1e7eb69f..daa1493b 100644 --- a/src/temps.h +++ b/src/temps.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -51,7 +51,11 @@ class temporaries_t optional<std::list<account_t> > acct_temps; public: + temporaries_t() { + TRACE_CTOR(temporaries_t, ""); + } ~temporaries_t() { + TRACE_DTOR(temporaries_t); clear(); } @@ -62,7 +66,8 @@ public: } post_t& copy_post(post_t& origin, xact_t& xact, account_t * account = NULL); - post_t& create_post(xact_t& xact, account_t * account); + post_t& create_post(xact_t& xact, account_t * account, + bool bidir_link = true); post_t& last_post() { return post_temps->back(); } diff --git a/src/textual.cc b/src/textual.cc index c7c49e2a..d0e4dad2 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -32,6 +32,7 @@ #include <system.hh> #include "journal.h" +#include "context.h" #include "xact.h" #include "post.h" #include "account.h" @@ -39,7 +40,9 @@ #include "query.h" #include "pstream.h" #include "pool.h" -#include "session.h" +#if defined(HAVE_BOOST_PYTHON) +#include "pyinterp.h" +#endif #define TIMELOG_SUPPORT 1 #if defined(TIMELOG_SUPPORT) @@ -49,123 +52,150 @@ namespace ledger { namespace { - typedef std::pair<commodity_t *, amount_t> fixed_rate_t; - typedef variant<account_t *, string, fixed_rate_t> state_t; + typedef std::pair<commodity_t *, amount_t> fixed_rate_t; - class parse_context_t : public noncopyable + struct application_t + { + string label; + variant<optional<datetime_t>, account_t *, string, fixed_rate_t> value; + + application_t(string _label, optional<datetime_t> epoch) + : label(_label), value(epoch) {} + application_t(string _label, account_t * acct) + : label(_label), value(acct) {} + application_t(string _label, string tag) + : label(_label), value(tag) {} + application_t(string _label, fixed_rate_t rate) + : label(_label), value(rate) {} + }; + + class instance_t : public noncopyable, public scope_t { public: - journal_t& journal; - scope_t& scope; - std::list<state_t> state_stack; + parse_context_stack_t& context_stack; + parse_context_t& context; + std::istream& in; + instance_t * parent; + std::list<application_t> apply_stack; #if defined(TIMELOG_SUPPORT) - time_log_t timelog; + time_log_t timelog; #endif - bool strict; - std::size_t count; - std::size_t errors; - std::size_t sequence; - - parse_context_t(journal_t& _journal, scope_t& _scope) - : journal(_journal), scope(_scope), timelog(journal, scope), - strict(false), count(0), errors(0), sequence(1) { - timelog.context_count = &count; - } - bool front_is_account() { - return state_stack.front().type() == typeid(account_t *); - } - bool front_is_string() { - return state_stack.front().type() == typeid(string); - } - bool front_is_fixed_rate() { - return state_stack.front().type() == typeid(fixed_rate_t); - } + instance_t(parse_context_stack_t& _context_stack, + parse_context_t& _context, + instance_t * _parent = NULL) + : context_stack(_context_stack), context(_context), + in(*context.stream.get()), parent(_parent), + timelog(context) {} - account_t * top_account() { - foreach (state_t& state, state_stack) - if (state.type() == typeid(account_t *)) - return boost::get<account_t *>(state); - return NULL; + virtual string description() { + return _("textual parser"); } - void close() { - timelog.close(); + template <typename T> + void get_applications(std::vector<T>& result) { + foreach (application_t& state, apply_stack) { + if (state.value.type() == typeid(T)) + result.push_back(boost::get<T>(state.value)); + } + if (parent) + parent->get_applications<T>(result); } - }; - class instance_t : public noncopyable, public scope_t - { - static const std::size_t MAX_LINE = 1024; - - public: - parse_context_t& context; - instance_t * parent; - accounts_map account_aliases; - const path * original_file; - path pathname; - std::istream& in; - char linebuf[MAX_LINE + 1]; - std::size_t linenum; - istream_pos_type line_beg_pos; - istream_pos_type curr_pos; - optional<datetime_t> prev_epoch; - - instance_t(parse_context_t& _context, - std::istream& _in, - const path * _original_file = NULL, - instance_t * _parent = NULL); - - ~instance_t(); + template <typename T> + optional<T> get_application() { + foreach (application_t& state, apply_stack) { + if (state.value.type() == typeid(T)) + return boost::get<T>(state.value); + } + return parent ? parent->get_application<T>() : none; + } - virtual string description() { - return _("textual parser"); + account_t * top_account() { + if (optional<account_t *> acct = get_application<account_t *>()) + return *acct; + else + return NULL; } void parse(); + std::streamsize read_line(char *& line); + bool peek_whitespace_line() { return (in.good() && ! in.eof() && (in.peek() == ' ' || in.peek() == '\t')); } +#if defined(HAVE_BOOST_PYTHON) + bool peek_blank_line() { + return (in.good() && ! in.eof() && + (in.peek() == '\n' || in.peek() == '\r')); + } +#endif - void read_next_directive(); + void read_next_directive(bool& error_flag); #if defined(TIMELOG_SUPPORT) void clock_in_directive(char * line, bool capitalized); void clock_out_directive(char * line, bool capitalized); #endif - void default_commodity_directive(char * line); + bool general_directive(char * line); + + void account_directive(char * line); + void account_alias_directive(account_t * account, string alias); + void account_payee_directive(account_t * account, string payee); + void account_value_directive(account_t * account, string expr_str); + void account_default_directive(account_t * account); + void default_account_directive(char * line); - void price_conversion_directive(char * line); + void alias_directive(char * line); + + void payee_directive(char * line); + void payee_alias_directive(const string& payee, string alias); + + void commodity_directive(char * line); + void commodity_alias_directive(commodity_t& comm, string alias); + void commodity_value_directive(commodity_t& comm, string expr_str); + void commodity_format_directive(commodity_t& comm, string format); + void commodity_nomarket_directive(commodity_t& comm); + void commodity_default_directive(commodity_t& comm); + + void default_commodity_directive(char * line); + + void tag_directive(char * line); + + void apply_directive(char * line); + void apply_account_directive(char * line); + void apply_tag_directive(char * line); + void apply_rate_directive(char * line); + void apply_year_directive(char * line); + void end_apply_directive(char * line); + + void xact_directive(char * line, std::streamsize len); + void period_xact_directive(char * line); + void automated_xact_directive(char * line); void price_xact_directive(char * line); + void price_conversion_directive(char * line); void nomarket_directive(char * line); - void year_directive(char * line); - void option_directive(char * line); - void automated_xact_directive(char * line); - void period_xact_directive(char * line); - void xact_directive(char * line, std::streamsize len); + void include_directive(char * line); - void master_account_directive(char * line); - void end_directive(char * line); - void alias_directive(char * line); - void fixed_directive(char * line); - void payee_mapping_directive(char * line); - void account_mapping_directive(char * line); - void tag_directive(char * line); - void define_directive(char * line); + void option_directive(char * line); + void comment_directive(char * line); + + void eval_directive(char * line); void assert_directive(char * line); void check_directive(char * line); - void comment_directive(char * line); - void expr_directive(char * line); - bool general_directive(char * line); + void value_directive(char * line); + + void import_directive(char * line); + void python_directive(char * line); post_t * parse_post(char * line, std::streamsize len, account_t * account, xact_t * xact, - bool defer_expr = false); + bool defer_expr = false); bool parse_posts(account_t * account, xact_base_t& xact, @@ -209,43 +239,27 @@ namespace { } } -instance_t::instance_t(parse_context_t& _context, - std::istream& _in, - const path * _original_file, - instance_t * _parent) - : context(_context), parent(_parent), original_file(_original_file), - pathname(original_file ? *original_file : "/dev/stdin"), in(_in) -{ - TRACE_CTOR(instance_t, "..."); - DEBUG("times.epoch", "Saving epoch " << epoch); - prev_epoch = epoch; // declared in times.h -} - -instance_t::~instance_t() -{ - TRACE_DTOR(instance_t); - epoch = prev_epoch; - DEBUG("times.epoch", "Restored epoch to " << epoch); -} - void instance_t::parse() { - INFO("Parsing file '" << pathname.string() << "'"); + INFO("Parsing file " << context.pathname); - TRACE_START(instance_parse, 1, - "Done parsing file '" << pathname.string() << "'"); + TRACE_START(instance_parse, 1, "Done parsing file " << context.pathname); if (! in.good() || in.eof()) return; - linenum = 0; - curr_pos = in.tellg(); + context.linenum = 0; + context.curr_pos = in.tellg(); + + bool error_flag = false; while (in.good() && ! in.eof()) { try { - read_next_directive(); + read_next_directive(error_flag); } catch (const std::exception& err) { + error_flag = true; + string current_context = error_context(); if (parent) { @@ -258,11 +272,9 @@ void instance_t::parse() foreach (instance_t * instance, instances) add_error_context(_("In file included from %1") - << file_context(instance->pathname, - instance->linenum)); + << instance->context.location()); } - add_error_context(_("While parsing file %1") - << file_context(pathname, linenum)); + add_error_context(_("While parsing file %1") << context.location()); if (caught_signal != NONE_CAUGHT) throw; @@ -287,48 +299,55 @@ std::streamsize instance_t::read_line(char *& line) assert(in.good()); assert(! in.eof()); // no one should call us in that case - line_beg_pos = curr_pos; + context.line_beg_pos = context.curr_pos; check_for_signal(); - in.getline(linebuf, MAX_LINE); + in.getline(context.linebuf, parse_context_t::MAX_LINE); std::streamsize len = in.gcount(); if (len > 0) { - if (linenum == 0 && utf8::is_bom(linebuf)) - line = &linebuf[3]; - else - line = linebuf; + context.linenum++; - if (line[len - 1] == '\r') // strip Windows CRLF down to LF - line[--len] = '\0'; + context.curr_pos = context.line_beg_pos; + context.curr_pos += len; - linenum++; + if (context.linenum == 0 && utf8::is_bom(context.linebuf)) { + line = &context.linebuf[3]; + len -= 3; + } else { + line = context.linebuf; + } - curr_pos = line_beg_pos; - curr_pos += len; + --len; + while (len > 0 && std::isspace(line[len - 1])) // strip trailing whitespace + line[--len] = '\0'; - return len - 1; // LF is being silently dropped + return len; } return 0; } -void instance_t::read_next_directive() +void instance_t::read_next_directive(bool& error_flag) { char * line; std::streamsize len = read_line(line); if (len == 0 || line == NULL) return; + if (! std::isspace(line[0])) + error_flag = false; + switch (line[0]) { case '\0': assert(false); // shouldn't ever reach here break; case ' ': - case '\t': { + case '\t': + if (! error_flag) + throw parse_error(_("Unexpected whitespace at beginning of line")); break; - } case ';': // comments case '#': @@ -402,7 +421,7 @@ void instance_t::read_next_directive() price_xact_directive(line); break; case 'Y': // set the current year - year_directive(line); + apply_year_directive(line); break; } } @@ -426,19 +445,19 @@ void instance_t::clock_in_directive(char * line, bool /*capitalized*/) end = NULL; position_t position; - position.pathname = pathname; - position.beg_pos = line_beg_pos; - position.beg_line = linenum; - position.end_pos = curr_pos; - position.end_line = linenum; + position.pathname = context.pathname; + position.beg_pos = context.line_beg_pos; + position.beg_line = context.linenum; + position.end_pos = context.curr_pos; + position.end_line = context.linenum; position.sequence = context.sequence++; time_xact_t event(position, parse_datetime(datetime), - p ? context.top_account()->find_account(p) : NULL, + p ? top_account()->find_account(p) : NULL, n ? n : "", end ? end : ""); - context.timelog.clock_in(event); + timelog.clock_in(event); } void instance_t::clock_out_directive(char * line, bool /*capitalized*/) @@ -455,20 +474,19 @@ void instance_t::clock_out_directive(char * line, bool /*capitalized*/) end = NULL; position_t position; - position.pathname = pathname; - position.beg_pos = line_beg_pos; - position.beg_line = linenum; - position.end_pos = curr_pos; - position.end_line = linenum; + position.pathname = context.pathname; + position.beg_pos = context.line_beg_pos; + position.beg_line = context.linenum; + position.end_pos = context.curr_pos; + position.end_line = context.linenum; position.sequence = context.sequence++; time_xact_t event(position, parse_datetime(datetime), - p ? context.top_account()->find_account(p) : NULL, + p ? top_account()->find_account(p) : NULL, n ? n : "", end ? end : ""); - context.timelog.clock_out(event); - context.count++; + context.count += timelog.clock_out(event); } #endif // TIMELOG_SUPPORT @@ -483,8 +501,8 @@ void instance_t::default_commodity_directive(char * line) void instance_t::default_account_directive(char * line) { - context.journal.bucket = context.top_account()->find_account(skip_ws(line + 1)); - context.journal.bucket->add_flags(ACCOUNT_KNOWN); + context.journal->bucket = top_account()->find_account(skip_ws(line + 1)); + context.journal->bucket->add_flags(ACCOUNT_KNOWN); } void instance_t::price_conversion_directive(char * line) @@ -514,16 +532,6 @@ void instance_t::nomarket_directive(char * line) commodity->add_flags(COMMODITY_NOMARKET | COMMODITY_KNOWN); } -void instance_t::year_directive(char * line) -{ - unsigned short year(lexical_cast<unsigned short>(skip_ws(line + 1))); - DEBUG("times.epoch", "Setting current year to " << year); - // This must be set to the last day of the year, otherwise partial - // dates like "11/01" will refer to last year's november, not the - // current year. - epoch = datetime_t(date_t(year, 12, 31)); -} - void instance_t::option_directive(char * line) { char * p = next_element(line); @@ -533,13 +541,15 @@ void instance_t::option_directive(char * line) *p++ = '\0'; } - if (! process_option(pathname.string(), line + 2, context.scope, p, line)) + path abs_path(filesystem::absolute(context.pathname, + context.current_directory)); + if (! process_option(abs_path.string(), line + 2, *context.scope, p, line)) throw_(option_error, _("Illegal option --%1") << line + 2); } void instance_t::automated_xact_directive(char * line) { - istream_pos_type pos= line_beg_pos; + istream_pos_type pos = context.line_beg_pos; bool reveal_context = true; @@ -550,18 +560,17 @@ void instance_t::automated_xact_directive(char * line) query.parse_args(string_value(skip_ws(line + 1)).to_sequence(), keeper, false, true); - std::auto_ptr<auto_xact_t> ae(new auto_xact_t(predicate_t(expr, keeper))); + unique_ptr<auto_xact_t> ae(new auto_xact_t(predicate_t(expr, keeper))); ae->pos = position_t(); - ae->pos->pathname = pathname; - ae->pos->beg_pos = line_beg_pos; - ae->pos->beg_line = linenum; + ae->pos->pathname = context.pathname; + ae->pos->beg_pos = context.line_beg_pos; + ae->pos->beg_line = context.linenum; ae->pos->sequence = context.sequence++; post_t * last_post = NULL; while (peek_whitespace_line()) { std::streamsize len = read_line(line); - char * p = skip_ws(line); if (! *p) break; @@ -576,59 +585,56 @@ void instance_t::automated_xact_directive(char * line) item = ae.get(); // This is a trailing note, and possibly a metadata info tag - item->append_note(p + 1, context.scope, true); - item->pos->end_pos = curr_pos; + item->append_note(p + 1, *context.scope, true); + item->add_flags(ITEM_NOTE_ON_NEXT_LINE); + item->pos->end_pos = context.curr_pos; item->pos->end_line++; - - // If there was no last_post yet, then deferred notes get applied to - // the matched posting. Other notes get applied to the auto-generated - // posting. - ae->deferred_notes->back().apply_to_post = last_post; } else if ((remlen > 7 && *p == 'a' && std::strncmp(p, "assert", 6) == 0 && std::isspace(p[6])) || (remlen > 6 && *p == 'c' && std::strncmp(p, "check", 5) == 0 && std::isspace(p[5])) || (remlen > 5 && *p == 'e' && - std::strncmp(p, "expr", 4) == 0 && std::isspace(p[4]))) { + ((std::strncmp(p, "expr", 4) == 0 && std::isspace(p[4])) || + (std::strncmp(p, "eval", 4) == 0 && std::isspace(p[4]))))) { const char c = *p; p = skip_ws(&p[*p == 'a' ? 6 : (*p == 'c' ? 5 : 4)]); if (! ae->check_exprs) - ae->check_exprs = auto_xact_t::check_expr_list(); + ae->check_exprs = expr_t::check_expr_list(); ae->check_exprs->push_back - (auto_xact_t::check_expr_pair(expr_t(p), - c == 'a' ? - auto_xact_t::EXPR_ASSERTION : - (c == 'c' ? - auto_xact_t::EXPR_CHECK : - auto_xact_t::EXPR_GENERAL))); + (expr_t::check_expr_pair(expr_t(p), + c == 'a' ? + expr_t::EXPR_ASSERTION : + (c == 'c' ? + expr_t::EXPR_CHECK : + expr_t::EXPR_GENERAL))); } else { reveal_context = false; if (post_t * post = - parse_post(p, len - (p - line), context.top_account(), - NULL, true)) { + parse_post(p, len - (p - line), top_account(), NULL, true)) { reveal_context = true; ae->add_post(post); - last_post = post; + ae->active_post = last_post = post; } reveal_context = true; } } - context.journal.auto_xacts.push_back(ae.get()); + context.journal->auto_xacts.push_back(ae.get()); - ae->journal = &context.journal; - ae->pos->end_pos = curr_pos; - ae->pos->end_line = linenum; + ae->journal = context.journal; + ae->pos->end_pos = context.curr_pos; + ae->pos->end_line = context.linenum; ae.release(); } catch (const std::exception&) { if (reveal_context) { add_error_context(_("While parsing automated transaction:")); - add_error_context(source_context(pathname, pos, curr_pos, "> ")); + add_error_context(source_context(context.pathname, pos, + context.curr_pos, "> ")); } throw; } @@ -636,31 +642,31 @@ void instance_t::automated_xact_directive(char * line) void instance_t::period_xact_directive(char * line) { - istream_pos_type pos = line_beg_pos; + istream_pos_type pos = context.line_beg_pos; bool reveal_context = true; try { - std::auto_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1))); + unique_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1))); pe->pos = position_t(); - pe->pos->pathname = pathname; - pe->pos->beg_pos = line_beg_pos; - pe->pos->beg_line = linenum; + pe->pos->pathname = context.pathname; + pe->pos->beg_pos = context.line_beg_pos; + pe->pos->beg_line = context.linenum; pe->pos->sequence = context.sequence++; reveal_context = false; - if (parse_posts(context.top_account(), *pe.get())) { + if (parse_posts(top_account(), *pe.get())) { reveal_context = true; - pe->journal = &context.journal; + pe->journal = context.journal; if (pe->finalize()) { - context.journal.extend_xact(pe.get()); - context.journal.period_xacts.push_back(pe.get()); + context.journal->extend_xact(pe.get()); + context.journal->period_xacts.push_back(pe.get()); - pe->pos->end_pos = curr_pos; - pe->pos->end_line = linenum; + pe->pos->end_pos = context.curr_pos; + pe->pos->end_line = context.linenum; pe.release(); } else { @@ -674,7 +680,8 @@ void instance_t::period_xact_directive(char * line) catch (const std::exception&) { if (reveal_context) { add_error_context(_("While parsing periodic transaction:")); - add_error_context(source_context(pathname, pos, curr_pos, "> ")); + add_error_context(source_context(context.pathname, pos, + context.curr_pos, "> ")); } throw; } @@ -684,10 +691,10 @@ void instance_t::xact_directive(char * line, std::streamsize len) { TRACE_START(xacts, 1, "Time spent handling transactions:"); - if (xact_t * xact = parse_xact(line, len, context.top_account())) { - std::auto_ptr<xact_t> manager(xact); + if (xact_t * xact = parse_xact(line, len, top_account())) { + unique_ptr<xact_t> manager(xact); - if (context.journal.add_xact(xact)) { + if (context.journal->add_xact(xact)) { manager.release(); // it's owned by the journal now context.count++; } @@ -709,12 +716,13 @@ void instance_t::include_directive(char * line) if (line[0] != '/' && line[0] != '\\' && line[0] != '~') { DEBUG("textual.include", "received a relative path"); - DEBUG("textual.include", "parent file path: " << pathname.string()); - string::size_type pos = pathname.string().rfind('/'); + DEBUG("textual.include", "parent file path: " << context.pathname); + string pathstr(context.pathname.string()); + string::size_type pos = pathstr.rfind('/'); if (pos == string::npos) - pos = pathname.string().rfind('\\'); + pos = pathstr.rfind('\\'); if (pos != string::npos) { - filename = path(string(pathname.string(), 0, pos + 1)) / line; + filename = path(string(pathstr, 0, pos + 1)) / line; DEBUG("textual.include", "normalized path: " << filename.string()); } else { filename = path(string(".")) / line; @@ -761,10 +769,42 @@ void instance_t::include_directive(char * line) string base = (*iter).leaf(); #endif // BOOST_VERSION >= 103700 if (glob.match(base)) { - path inner_file(*iter); - ifstream stream(inner_file); - instance_t instance(context, stream, &inner_file, this); - instance.parse(); + journal_t * journal = context.journal; + account_t * master = top_account(); + scope_t * scope = context.scope; + std::size_t& errors = context.errors; + std::size_t& count = context.count; + std::size_t& sequence = context.sequence; + + DEBUG("textual.include", "Including: " << *iter); + DEBUG("textual.include", "Master account: " << master->fullname()); + + context_stack.push(*iter); + + context_stack.get_current().journal = journal; + context_stack.get_current().master = master; + context_stack.get_current().scope = scope; + try { + instance_t instance(context_stack, + context_stack.get_current(), this); + instance.apply_stack.push_front(application_t("account", master)); + instance.parse(); + } + catch (...) { + errors += context_stack.get_current().errors; + count += context_stack.get_current().count; + sequence += context_stack.get_current().sequence; + + context_stack.pop(); + throw; + } + + errors += context_stack.get_current().errors; + count += context_stack.get_current().count; + sequence += context_stack.get_current().sequence; + + context_stack.pop(); + files_found = true; } } @@ -773,156 +813,348 @@ void instance_t::include_directive(char * line) if (! files_found) throw_(std::runtime_error, - _("File to include was not found: '%1'") << filename); + _("File to include was not found: %1") << filename); } -void instance_t::master_account_directive(char * line) +void instance_t::apply_directive(char * line) +{ + char * b = next_element(line); + string keyword(line); + if (keyword == "account") + apply_account_directive(b); + else if (keyword == "tag") + apply_tag_directive(b); + else if (keyword == "fixed" || keyword == "rate") + apply_rate_directive(b); + else if (keyword == "year") + apply_year_directive(b); +} + +void instance_t::apply_account_directive(char * line) { - if (account_t * acct = context.top_account()->find_account(line)) - context.state_stack.push_front(acct); + if (account_t * acct = top_account()->find_account(line)) + apply_stack.push_front(application_t("account", acct)); #if !defined(NO_ASSERTS) else assert("Failed to create account" == NULL); #endif } -void instance_t::end_directive(char * kind) +void instance_t::apply_tag_directive(char * line) { - string name(kind ? kind : ""); + string tag(trim_ws(line)); - if ((name.empty() || name == "account") && ! context.front_is_account()) - throw_(std::runtime_error, - _("'end account' directive does not match open directive")); - else if (name == "tag" && ! context.front_is_string()) - throw_(std::runtime_error, - _("'end tag' directive does not match open directive")); - else if (name == "fixed" && ! context.front_is_fixed_rate()) - throw_(std::runtime_error, - _("'end fixed' directive does not match open directive")); + if (tag.find(':') == string::npos) + tag = string(":") + tag + ":"; - if (context.state_stack.size() <= 1) + apply_stack.push_front(application_t("tag", tag)); +} + +void instance_t::apply_rate_directive(char * line) +{ + if (optional<std::pair<commodity_t *, price_point_t> > price_point = + commodity_pool_t::current_pool->parse_price_directive(trim_ws(line), true)) { + apply_stack.push_front + (application_t("fixed", fixed_rate_t(price_point->first, + price_point->second.price))); + } else { + throw_(std::runtime_error, _("Error in fixed directive")); + } +} + +void instance_t::apply_year_directive(char * line) +{ + apply_stack.push_front(application_t("year", epoch)); + + // This must be set to the last day of the year, otherwise partial + // dates like "11/01" will refer to last year's november, not the + // current year. + unsigned short year(lexical_cast<unsigned short>(skip_ws(line + 1))); + DEBUG("times.epoch", "Setting current year to " << year); + epoch = datetime_t(date_t(year, 12, 31)); +} + +void instance_t::end_apply_directive(char * kind) +{ + char * b = kind ? next_element(kind) : NULL; + string name(b ? b : ""); + + if (apply_stack.size() <= 1) { + if (name.empty()) { + throw_(std::runtime_error, + _("'end' or 'end apply' found, but no enclosing 'apply' directive")); + } else { + throw_(std::runtime_error, + _("'end apply %1' found, but no enclosing 'apply' directive") + << name); + } + } + + if (! name.empty() && name != apply_stack.front().label) throw_(std::runtime_error, - _("'end' found, but no enclosing tag or account directive")); - else - context.state_stack.pop_front(); + _("'end apply %1' directive does not match 'apply %2' directive") + << name << apply_stack.front().label); + + if (apply_stack.front().value.type() == typeid(optional<datetime_t>)) + epoch = boost::get<optional<datetime_t> >(apply_stack.front().value); + + apply_stack.pop_front(); +} + +void instance_t::account_directive(char * line) +{ + istream_pos_type beg_pos = context.line_beg_pos; + std::size_t beg_linenum = context.linenum; + + char * p = skip_ws(line); + account_t * account = + context.journal->register_account(p, NULL, top_account()); + unique_ptr<auto_xact_t> ae; + + while (peek_whitespace_line()) { + read_line(line); + char * q = skip_ws(line); + if (! *q) + break; + + char * b = next_element(q); + string keyword(q); + if (keyword == "alias") { + account_alias_directive(account, b); + } + else if (keyword == "payee") { + account_payee_directive(account, b); + } + else if (keyword == "value") { + account_value_directive(account, b); + } + else if (keyword == "default") { + account_default_directive(account); + } + else if (keyword == "assert" || keyword == "check") { + keep_details_t keeper(true, true, true); + expr_t expr(string("account == \"") + account->fullname() + "\""); + predicate_t pred(expr.get_op(), keeper); + + if (! ae.get()) { + ae.reset(new auto_xact_t(pred)); + + ae->pos = position_t(); + ae->pos->pathname = context.pathname; + ae->pos->beg_pos = beg_pos; + ae->pos->beg_line = beg_linenum; + ae->pos->sequence = context.sequence++; + ae->check_exprs = expr_t::check_expr_list(); + } + + ae->check_exprs->push_back + (expr_t::check_expr_pair(expr_t(b), + keyword == "assert" ? + expr_t::EXPR_ASSERTION : + expr_t::EXPR_CHECK)); + } + else if (keyword == "eval" || keyword == "expr") { + // jww (2012-02-27): Make account into symbol scopes so that this + // can be used to override definitions within the account. + bind_scope_t bound_scope(*context.scope, *account); + expr_t(b).calc(bound_scope); + } + else if (keyword == "note") { + account->note = b; + } + } + + if (ae.get()) { + context.journal->auto_xacts.push_back(ae.get()); + + ae->journal = context.journal; + ae->pos->end_pos = in.tellg(); + ae->pos->end_line = context.linenum; + + ae.release(); + } +} + +void instance_t::account_alias_directive(account_t * account, string alias) +{ + // Once we have an alias name (alias) and the target account + // (account), add a reference to the account in the `account_aliases' + // map, which is used by the post parser to resolve alias references. + trim(alias); + std::pair<accounts_map::iterator, bool> result = + context.journal->account_aliases.insert + (accounts_map::value_type(alias, account)); + if (! result.second) + (*result.first).second = account; } void instance_t::alias_directive(char * line) { - char * b = skip_ws(line); - if (char * e = std::strchr(b, '=')) { + if (char * e = std::strchr(line, '=')) { char * z = e - 1; while (std::isspace(*z)) *z-- = '\0'; *e++ = '\0'; e = skip_ws(e); - // Once we have an alias name (b) and the target account - // name (e), add a reference to the account in the - // `account_aliases' map, which is used by the post - // parser to resolve alias references. - account_t * acct = context.top_account()->find_account(e); - std::pair<accounts_map::iterator, bool> result - = account_aliases.insert(accounts_map::value_type(b, acct)); - assert(result.second); + account_alias_directive(top_account()->find_account(e), line); } } -void instance_t::fixed_directive(char * line) +void instance_t::account_payee_directive(account_t * account, string payee) { - if (optional<std::pair<commodity_t *, price_point_t> > price_point = - commodity_pool_t::current_pool->parse_price_directive(trim_ws(line), - true)) { - context.state_stack.push_front(fixed_rate_t(price_point->first, - price_point->second.price)); - } else { - throw_(std::runtime_error, _("Error in fixed directive")); - } + trim(payee); + context.journal->payees_for_unknown_accounts + .push_back(account_mapping_t(mask_t(payee), account)); } -void instance_t::payee_mapping_directive(char * line) +void instance_t::account_default_directive(account_t * account) { - char * payee = skip_ws(line); - char * regex = next_element(payee, true); + context.journal->bucket = account; +} - if (regex) - context.journal.payee_mappings.push_back - (payee_mapping_t(mask_t(regex), payee)); +void instance_t::account_value_directive(account_t * account, string expr_str) +{ + account->value_expr = expr_t(expr_str); +} + +void instance_t::payee_directive(char * line) +{ + string payee = context.journal->register_payee(line, NULL); while (peek_whitespace_line()) { -#if defined(NO_ASSERTS) read_line(line); -#else - std::streamsize len = read_line(line); - assert(len > 0); -#endif - - regex = skip_ws(line); - if (! *regex) + char * p = skip_ws(line); + if (! *p) break; - context.journal.payee_mappings.push_back - (payee_mapping_t(mask_t(regex), payee)); + char * b = next_element(p); + string keyword(p); + if (keyword == "alias") + payee_alias_directive(payee, b); } } -void instance_t::account_mapping_directive(char * line) +void instance_t::payee_alias_directive(const string& payee, string alias) { - char * account_name = skip_ws(line); - char * payee_regex = next_element(account_name, true); + trim(alias); + context.journal->payee_mappings + .push_back(payee_mapping_t(mask_t(alias), payee)); +} - if (payee_regex) - context.journal.account_mappings.push_back - (account_mapping_t(mask_t(payee_regex), - context.top_account()->find_account(account_name))); +void instance_t::commodity_directive(char * line) +{ + char * p = skip_ws(line); + string symbol; + commodity_t::parse_symbol(p, symbol); - while (peek_whitespace_line()) { -#if defined(NO_ASSERTS) - read_line(line); -#else - std::streamsize len = read_line(line); - assert(len > 0); -#endif + if (commodity_t * commodity = + commodity_pool_t::current_pool->find_or_create(symbol)) { + context.journal->register_commodity(*commodity, 0); - payee_regex = skip_ws(line); - if (! *payee_regex) - break; + while (peek_whitespace_line()) { + read_line(line); + char * q = skip_ws(line); + if (! *q) + break; - context.journal.account_mappings.push_back - (account_mapping_t(mask_t(payee_regex), - context.top_account()->find_account(account_name))); + char * b = next_element(q); + string keyword(q); + if (keyword == "alias") + commodity_alias_directive(*commodity, b); + else if (keyword == "value") + commodity_value_directive(*commodity, b); + else if (keyword == "format") + commodity_format_directive(*commodity, b); + else if (keyword == "nomarket") + commodity_nomarket_directive(*commodity); + else if (keyword == "default") + commodity_default_directive(*commodity); + else if (keyword == "note") + commodity->set_note(string(b)); + } } } +void instance_t::commodity_alias_directive(commodity_t& comm, string alias) +{ + trim(alias); + commodity_pool_t::current_pool->alias(alias, comm); +} + +void instance_t::commodity_value_directive(commodity_t& comm, string expr_str) +{ + comm.set_value_expr(expr_t(expr_str)); +} + +void instance_t::commodity_format_directive(commodity_t&, string format) +{ + // jww (2012-02-27): A format specified this way should turn off + // observational formatting. + trim(format); + amount_t amt; + amt.parse(format); + VERIFY(amt.valid()); +} + +void instance_t::commodity_nomarket_directive(commodity_t& comm) +{ + comm.add_flags(COMMODITY_NOMARKET); +} + +void instance_t::commodity_default_directive(commodity_t& comm) +{ + commodity_pool_t::current_pool->default_commodity = &comm; +} + void instance_t::tag_directive(char * line) { - string tag(trim_ws(line)); + char * p = skip_ws(line); + context.journal->register_metadata(p, NULL_VALUE, 0); - if (tag.find(':') == string::npos) - tag = string(":") + tag + ":"; + while (peek_whitespace_line()) { + read_line(line); + char * q = skip_ws(line); + if (! *q) + break; - context.state_stack.push_front(tag); + char * b = next_element(q); + string keyword(q); + if (keyword == "assert" || keyword == "check") { + context.journal->tag_check_exprs.insert + (tag_check_exprs_map::value_type + (string(p), expr_t::check_expr_pair(expr_t(b), + keyword == "assert" ? + expr_t::EXPR_ASSERTION : + expr_t::EXPR_CHECK))); + } + } } -void instance_t::define_directive(char * line) +void instance_t::eval_directive(char * line) { - expr_t def(skip_ws(line)); - def.compile(context.scope); // causes definitions to be established + expr_t expr(line); + expr.calc(*context.scope); } void instance_t::assert_directive(char * line) { expr_t expr(line); - if (! expr.calc(context.scope).to_boolean()) + if (! expr.calc(*context.scope).to_boolean()) throw_(parse_error, _("Assertion failed: %1") << line); } void instance_t::check_directive(char * line) { expr_t expr(line); - if (! expr.calc(context.scope).to_boolean()) - warning_(_("Check failed: %1") << line); + if (! expr.calc(*context.scope).to_boolean()) + context.warning(STR(_("Check failed: %1") << line)); +} + +void instance_t::value_directive(char * line) +{ + context.journal->value_expr = expr_t(line); } void instance_t::comment_directive(char * line) @@ -936,12 +1168,71 @@ void instance_t::comment_directive(char * line) } } -void instance_t::expr_directive(char * line) +#if defined(HAVE_BOOST_PYTHON) + +void instance_t::import_directive(char * line) { - expr_t expr(line); - expr.calc(context.scope); + string module_name(line); + trim(module_name); + python_session->import_option(module_name); +} + +void instance_t::python_directive(char * line) +{ + std::ostringstream script; + + if (line) + script << skip_ws(line) << '\n'; + + std::size_t indent = 0; + + while (peek_whitespace_line() || peek_blank_line()) { + if (read_line(line) > 0) { + if (! indent) { + const char * p = line; + while (*p && std::isspace(*p)) { + ++indent; + ++p; + } + } + + const char * p = line; + for (std::size_t i = 0; i < indent; i++) { + if (std::isspace(*p)) + ++p; + else + break; + } + + if (*p) + script << p << '\n'; + } + } + + if (! python_session->is_initialized) + python_session->initialize(); + + python_session->main_module->define_global + ("journal", python::object(python::ptr(context.journal))); + python_session->eval(script.str(), python_interpreter_t::PY_EVAL_MULTI); +} + +#else + +void instance_t::import_directive(char *) +{ + throw_(parse_error, + _("'python' directive seen, but Python support is missing")); +} + +void instance_t::python_directive(char *) +{ + throw_(parse_error, + _("'import' directive seen, but Python support is missing")); } +#endif // HAVE_BOOST_PYTHON + bool instance_t::general_directive(char * line) { char buf[8192]; @@ -957,13 +1248,17 @@ bool instance_t::general_directive(char * line) switch (*p) { case 'a': if (std::strcmp(p, "account") == 0) { - master_account_directive(arg); + account_directive(arg); return true; } else if (std::strcmp(p, "alias") == 0) { alias_directive(arg); return true; } + else if (std::strcmp(p, "apply") == 0) { + apply_directive(arg); + return true; + } else if (std::strcmp(p, "assert") == 0) { assert_directive(arg); return true; @@ -978,11 +1273,7 @@ bool instance_t::general_directive(char * line) break; case 'c': - if (std::strcmp(p, "capture") == 0) { - account_mapping_directive(arg); - return true; - } - else if (std::strcmp(p, "check") == 0) { + if (std::strcmp(p, "check") == 0) { check_directive(arg); return true; } @@ -990,29 +1281,26 @@ bool instance_t::general_directive(char * line) comment_directive(arg); return true; } + else if (std::strcmp(p, "commodity") == 0) { + commodity_directive(arg); + return true; + } break; case 'd': if (std::strcmp(p, "def") == 0 || std::strcmp(p, "define") == 0) { - define_directive(arg); + eval_directive(arg); return true; } break; case 'e': if (std::strcmp(p, "end") == 0) { - end_directive(arg); + end_apply_directive(arg); return true; } - else if (std::strcmp(p, "expr") == 0) { - expr_directive(arg); - return true; - } - break; - - case 'f': - if (std::strcmp(p, "fixed") == 0) { - fixed_directive(arg); + else if (std::strcmp(p, "expr") == 0 || std::strcmp(p, "eval") == 0) { + eval_directive(arg); return true; } break; @@ -1022,11 +1310,19 @@ bool instance_t::general_directive(char * line) include_directive(arg); return true; } + else if (std::strcmp(p, "import") == 0) { + import_directive(arg); + return true; + } break; case 'p': if (std::strcmp(p, "payee") == 0) { - payee_mapping_directive(arg); + payee_directive(arg); + return true; + } + else if (std::strcmp(p, "python") == 0) { + python_directive(arg); return true; } break; @@ -1042,9 +1338,9 @@ bool instance_t::general_directive(char * line) } break; - case 'y': - if (std::strcmp(p, "year") == 0) { - year_directive(arg); + case 'v': + if (std::strcmp(p, "value") == 0) { + value_directive(arg); return true; } break; @@ -1068,16 +1364,16 @@ post_t * instance_t::parse_post(char * line, { TRACE_START(post_details, 1, "Time spent parsing postings:"); - std::auto_ptr<post_t> post(new post_t); + unique_ptr<post_t> post(new post_t); post->xact = xact; // this could be NULL post->pos = position_t(); - post->pos->pathname = pathname; - post->pos->beg_pos = line_beg_pos; - post->pos->beg_line = linenum; + post->pos->pathname = context.pathname; + post->pos->beg_pos = context.line_beg_pos; + post->pos->beg_line = context.linenum; post->pos->sequence = context.sequence++; - char buf[MAX_LINE + 1]; + char buf[parse_context_t::MAX_LINE + 1]; std::strcpy(buf, line); std::streamsize beg = 0; @@ -1094,14 +1390,14 @@ post_t * instance_t::parse_post(char * line, case '*': post->set_state(item_t::CLEARED); p = skip_ws(p + 1); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed the CLEARED flag"); break; case '!': post->set_state(item_t::PENDING); p = skip_ws(p + 1); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed the PENDING flag"); break; } @@ -1124,44 +1420,23 @@ post_t * instance_t::parse_post(char * line, if ((*p == '[' && *(e - 1) == ']') || (*p == '(' && *(e - 1) == ')')) { post->add_flags(POST_VIRTUAL); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed a virtual account name"); if (*p == '[') { post->add_flags(POST_MUST_BALANCE); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Posting must balance"); } p++; e--; } - string name(p, static_cast<std::string::size_type>(e - p)); - DEBUG("textual.parse", "line " << linenum << ": " + string name(p, static_cast<string::size_type>(e - p)); + DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed account name " << name); - if (account_aliases.size() > 0) { - accounts_map::const_iterator i = account_aliases.find(name); - if (i != account_aliases.end()) - post->account = (*i).second; - } - if (! post->account) - post->account = account->find_account(name); - - if (context.strict && ! post->account->has_flags(ACCOUNT_KNOWN)) { - if (post->_state == item_t::UNCLEARED) - warning_(_("\"%1\", line %2: Unknown account '%3'") - << pathname.string() << linenum << post->account->fullname()); - post->account->add_flags(ACCOUNT_KNOWN); - } - - if (post->account->name == _("Unknown")) { - foreach (account_mapping_t& value, context.journal.account_mappings) { - if (value.first.match(xact->payee)) { - post->account = value.second; - break; - } - } - } + post->account = + context.journal->register_account(name, post.get(), account); // Parse the optional amount @@ -1172,35 +1447,28 @@ post_t * instance_t::parse_post(char * line, if (*next != '(') // indicates a value expression post->amount.parse(stream, PARSE_NO_REDUCE); else - parse_amount_expr(stream, context.scope, *post.get(), post->amount, + parse_amount_expr(stream, *context.scope, *post.get(), post->amount, PARSE_NO_REDUCE | PARSE_SINGLE | PARSE_NO_ASSIGN, defer_expr, &post->amount_expr); if (! post->amount.is_null() && post->amount.has_commodity()) { - if (context.strict && - ! post->amount.commodity().has_flags(COMMODITY_KNOWN)) { - if (post->_state == item_t::UNCLEARED) - warning_(_("\"%1\", line %2: Unknown commodity '%3'") - << pathname.string() << linenum << post->amount.commodity()); - post->amount.commodity().add_flags(COMMODITY_KNOWN); - } + context.journal->register_commodity(post->amount.commodity(), post.get()); if (! post->amount.has_annotation()) { - foreach (state_t& state, context.state_stack) { - if (state.type() == typeid(fixed_rate_t)) { - fixed_rate_t& rate(boost::get<fixed_rate_t>(state)); - if (*rate.first == post->amount.commodity()) { - annotation_t details(rate.second); - details.add_flags(ANNOTATION_PRICE_FIXATED); - post->amount.annotate(details); - break; - } + std::vector<fixed_rate_t> rates; + get_applications<fixed_rate_t>(rates); + foreach (fixed_rate_t& rate, rates) { + if (*rate.first == post->amount.commodity()) { + annotation_t details(rate.second); + details.add_flags(ANNOTATION_PRICE_FIXATED); + post->amount.annotate(details); + break; } } } } - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "post amount = " << post->amount); if (stream.eof()) { @@ -1210,19 +1478,26 @@ post_t * instance_t::parse_post(char * line, // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST) - if (*next == '@') { - DEBUG("textual.parse", "line " << linenum << ": " + if (*next == '@' || (*next == '(' && *(next + 1) == '@')) { + DEBUG("textual.parse", "line " << context.linenum << ": " << "Found a price indicator"); - bool per_unit = true; + if (*next == '(') { + post->add_flags(POST_COST_VIRTUAL); + ++next; + } + bool per_unit = true; if (*++next == '@') { per_unit = false; post->add_flags(POST_COST_IN_FULL); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "And it's for a total price"); } + if (post->has_flags(POST_COST_VIRTUAL) && *(next + 1) == ')') + ++next; + beg = static_cast<std::streamsize>(++next - line); p = skip_ws(next); @@ -1243,7 +1518,7 @@ post_t * instance_t::parse_post(char * line, if (*p != '(') // indicates a value expression post->cost->parse(cstream, PARSE_NO_MIGRATE); else - parse_amount_expr(cstream, context.scope, *post.get(), *post->cost, + parse_amount_expr(cstream, *context.scope, *post.get(), *post->cost, PARSE_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_ASSIGN); if (post->cost->sign() < 0) @@ -1266,9 +1541,9 @@ post_t * instance_t::parse_post(char * line, if (fixed_cost) post->add_flags(POST_COST_FIXATED); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Total cost is " << *post->cost); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Annotated amount is " << post->amount); if (cstream.eof()) @@ -1285,7 +1560,7 @@ post_t * instance_t::parse_post(char * line, // Parse the optional balance assignment if (xact && next && *next == '=') { - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Found a balance assignment indicator"); beg = static_cast<std::streamsize>(++next - line); @@ -1300,7 +1575,7 @@ post_t * instance_t::parse_post(char * line, if (*p != '(') // indicates a value expression post->assigned_amount->parse(stream, PARSE_NO_MIGRATE); else - parse_amount_expr(stream, context.scope, *post.get(), + parse_amount_expr(stream, *context.scope, *post.get(), *post->assigned_amount, PARSE_SINGLE | PARSE_NO_MIGRATE); @@ -1311,17 +1586,17 @@ post_t * instance_t::parse_post(char * line, throw parse_error(_("Balance assertion must evaluate to a constant")); } - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "POST assign: parsed amt = " << *post->assigned_amount); amount_t& amt(*post->assigned_amount); value_t account_total (post->account->amount().strip_annotations(keep_details_t())); + DEBUG("post.assign", "line " << context.linenum << ": " + << "account balance = " << account_total); DEBUG("post.assign", - "line " << linenum << ": " "account balance = " << account_total); - DEBUG("post.assign", - "line " << linenum << ": " "post amount = " << amt); + "line " << context.linenum << ": " << "post amount = " << amt); amount_t diff = amt; @@ -1341,9 +1616,9 @@ post_t * instance_t::parse_post(char * line, } DEBUG("post.assign", - "line " << linenum << ": " << "diff = " << diff); - DEBUG("textual.parse", - "line " << linenum << ": " << "POST assign: diff = " << diff); + "line " << context.linenum << ": " << "diff = " << diff); + DEBUG("textual.parse", "line " << context.linenum << ": " + << "POST assign: diff = " << diff); if (! diff.is_zero()) { if (! post->amount.is_null()) { @@ -1352,7 +1627,7 @@ post_t * instance_t::parse_post(char * line, throw_(parse_error, _("Balance assertion off by %1") << diff); } else { post->amount = diff; - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Overwrite null posting"); } } @@ -1369,9 +1644,9 @@ post_t * instance_t::parse_post(char * line, // Parse the optional note if (next && *next == ';') { - post->append_note(++next, context.scope, true); + post->append_note(++next, *context.scope, true); next = line + len; - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed a posting note"); } @@ -1382,14 +1657,13 @@ post_t * instance_t::parse_post(char * line, _("Unexpected char '%1' (Note: inline math requires parentheses)") << *next); - post->pos->end_pos = curr_pos; - post->pos->end_line = linenum; + post->pos->end_pos = context.curr_pos; + post->pos->end_line = context.linenum; - if (! context.state_stack.empty()) { - foreach (const state_t& state, context.state_stack) - if (state.type() == typeid(string)) - post->parse_tags(boost::get<string>(state).c_str(), context.scope, true); - } + std::vector<string> tags; + get_applications<string>(tags); + foreach (string& tag, tags) + post->parse_tags(tag.c_str(), *context.scope, true); TRACE_STOP(post_details, 1); @@ -1398,8 +1672,8 @@ post_t * instance_t::parse_post(char * line, } catch (const std::exception&) { add_error_context(_("While parsing posting:")); - add_error_context(line_context(buf, static_cast<std::string::size_type>(beg), - static_cast<std::string::size_type>(len))); + add_error_context(line_context(buf, static_cast<string::size_type>(beg), + static_cast<string::size_type>(len))); throw; } } @@ -1415,11 +1689,12 @@ bool instance_t::parse_posts(account_t * account, while (peek_whitespace_line()) { char * line; std::streamsize len = read_line(line); - assert(len > 0); - - if (post_t * post = parse_post(line, len, account, NULL, defer_expr)) { - xact.add_post(post); - added = true; + char * p = skip_ws(line); + if (*p != ';') { + if (post_t * post = parse_post(line, len, account, NULL, defer_expr)) { + xact.add_post(post); + added = true; + } } } @@ -1437,9 +1712,9 @@ xact_t * instance_t::parse_xact(char * line, unique_ptr<xact_t> xact(new xact_t); xact->pos = position_t(); - xact->pos->pathname = pathname; - xact->pos->beg_pos = line_beg_pos; - xact->pos->beg_line = linenum; + xact->pos->pathname = context.pathname; + xact->pos->beg_pos = context.line_beg_pos; + xact->pos->beg_line = context.linenum; xact->pos->sequence = context.sequence++; bool reveal_context = true; @@ -1452,7 +1727,7 @@ xact_t * instance_t::parse_xact(char * line, if (char * p = std::strchr(line, '=')) { *p++ = '\0'; - xact->_date_eff = parse_date(p); + xact->_date_aux = parse_date(p); } xact->_date = parse_date(line); @@ -1484,15 +1759,31 @@ xact_t * instance_t::parse_xact(char * line, // Parse the description text if (next && *next) { - char * p = next_element(next, true); - foreach (payee_mapping_t& value, context.journal.payee_mappings) { - if (value.first.match(next)) { - xact->payee = value.second; + char * p = next; + std::size_t spaces = 0; + std::size_t tabs = 0; + while (*p) { + if (*p == ' ') { + ++spaces; + } + else if (*p == '\t') { + ++tabs; + } + else if (*p == ';' && (tabs > 0 || spaces > 1)) { + char *q = p - 1; + while (q > next && std::isspace(*q)) + --q; + if (q > next) + *(q + 1) = '\0'; break; } + else { + spaces = 0; + tabs = 0; + } + ++p; } - if (xact->payee.empty()) - xact->payee = next; + xact->payee = context.journal->register_payee(next, xact.get()); next = p; } else { xact->payee = _("<Unspecified payee>"); @@ -1501,7 +1792,7 @@ xact_t * instance_t::parse_xact(char * line, // Parse the xact note if (next && *next == ';') - xact->append_note(++next, context.scope, false); + xact->append_note(++next, *context.scope, false); TRACE_STOP(xact_text, 1); @@ -1513,7 +1804,6 @@ xact_t * instance_t::parse_xact(char * line, while (peek_whitespace_line()) { len = read_line(line); - char * p = skip_ws(line); if (! *p) break; @@ -1528,8 +1818,9 @@ xact_t * instance_t::parse_xact(char * line, if (*p == ';') { // This is a trailing note, and possibly a metadata info tag - item->append_note(p + 1, context.scope, true); - item->pos->end_pos = curr_pos; + item->append_note(p + 1, *context.scope, true); + item->add_flags(ITEM_NOTE_ON_NEXT_LINE); + item->pos->end_pos = context.curr_pos; item->pos->end_line++; } else if ((remlen > 7 && *p == 'a' && @@ -1541,7 +1832,7 @@ xact_t * instance_t::parse_xact(char * line, const char c = *p; p = skip_ws(&p[*p == 'a' ? 6 : (*p == 'c' ? 5 : 4)]); expr_t expr(p); - bind_scope_t bound_scope(context.scope, *item); + bind_scope_t bound_scope(*context.scope, *item); if (c == 'e') { expr.calc(bound_scope); } @@ -1549,7 +1840,7 @@ xact_t * instance_t::parse_xact(char * line, if (c == 'a') { throw_(parse_error, _("Transaction assertion failed: %1") << p); } else { - warning_(_("Transaction check failed: %1") << p); + context.warning(STR(_("Transaction check failed: %1") << p)); } } } @@ -1568,7 +1859,7 @@ xact_t * instance_t::parse_xact(char * line, #if 0 if (xact->_state == item_t::UNCLEARED) { - item_t::state_t result = item_t::CLEARED; + item_t::application_t result = item_t::CLEARED; foreach (post_t * post, xact->posts) { if (post->_state == item_t::UNCLEARED) { @@ -1582,15 +1873,13 @@ xact_t * instance_t::parse_xact(char * line, } #endif - xact->pos->end_pos = curr_pos; - xact->pos->end_line = linenum; + xact->pos->end_pos = context.curr_pos; + xact->pos->end_line = context.linenum; - if (! context.state_stack.empty()) { - foreach (const state_t& state, context.state_stack) - if (state.type() == typeid(string)) - xact->parse_tags(boost::get<string>(state).c_str(), context.scope, - false); - } + std::vector<string> tags; + get_applications<string>(tags); + foreach (string& tag, tags) + xact->parse_tags(tag.c_str(), *context.scope, false); TRACE_STOP(xact_details, 1); @@ -1601,7 +1890,8 @@ xact_t * instance_t::parse_xact(char * line, if (reveal_context) { add_error_context(_("While parsing transaction:")); add_error_context(source_context(xact->pos->pathname, - xact->pos->beg_pos, curr_pos, "> ")); + xact->pos->beg_pos, + context.curr_pos, "> ")); } throw; } @@ -1610,27 +1900,18 @@ xact_t * instance_t::parse_xact(char * line, expr_t::ptr_op_t instance_t::lookup(const symbol_t::kind_t kind, const string& name) { - return context.scope.lookup(kind, name); + return context.scope->lookup(kind, name); } -std::size_t journal_t::parse(std::istream& in, - scope_t& scope, - account_t * master_account, - const path * original_file, - bool strict) +std::size_t journal_t::read_textual(parse_context_stack_t& context_stack) { TRACE_START(parsing_total, 1, "Total time spent parsing text:"); - - parse_context_t context(*this, scope); - context.strict = strict; - if (master_account || this->master) - context.state_stack.push_front(master_account ? - master_account : this->master); - - instance_t instance(context, in, original_file); - instance.parse(); - context.close(); - + { + instance_t instance(context_stack, context_stack.get_current()); + instance.apply_stack.push_front + (application_t("account", context_stack.get_current().master)); + instance.parse(); + } TRACE_STOP(parsing_total, 1); // These tracers were started in textual.cc @@ -1641,10 +1922,10 @@ std::size_t journal_t::parse(std::istream& in, TRACE_FINISH(instance_parse, 1); // report per-instance timers TRACE_FINISH(parsing_total, 1); - if (context.errors > 0) - throw static_cast<int>(context.errors); + if (context_stack.get_current().errors > 0) + throw error_count(context_stack.get_current().errors); - return context.count; + return context_stack.get_current().count; } } // namespace ledger diff --git a/src/timelog.cc b/src/timelog.cc index ee9a0b6c..e84e4188 100644 --- a/src/timelog.cc +++ b/src/timelog.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -36,14 +36,48 @@ #include "post.h" #include "account.h" #include "journal.h" +#include "context.h" namespace ledger { namespace { - void clock_out_from_timelog(std::list<time_xact_t>& time_xacts, - time_xact_t out_event, - journal_t& journal, - scope_t& scope) + void create_timelog_xact(const time_xact_t& in_event, + const time_xact_t& out_event, + parse_context_t& context) + { + unique_ptr<xact_t> curr(new xact_t); + curr->_date = in_event.checkin.date(); + curr->code = out_event.desc; // if it wasn't used above + curr->payee = in_event.desc; + curr->pos = in_event.position; + + if (! in_event.note.empty()) + curr->append_note(in_event.note.c_str(), *context.scope); + + char buf[32]; + std::sprintf(buf, "%lds", long((out_event.checkin - in_event.checkin) + .total_seconds())); + amount_t amt; + amt.parse(buf); + VERIFY(amt.valid()); + + post_t * post = new post_t(in_event.account, amt, POST_VIRTUAL); + post->set_state(item_t::CLEARED); + post->pos = in_event.position; + post->checkin = in_event.checkin; + post->checkout = out_event.checkin; + curr->add_post(post); + in_event.account->add_post(post); + + if (! context.journal->add_xact(curr.get())) + throw parse_error(_("Failed to record 'out' timelog transaction")); + else + curr.release(); + } + + std::size_t clock_out_from_timelog(std::list<time_xact_t>& time_xacts, + time_xact_t out_event, + parse_context_t& context) { time_xact_t event; @@ -76,6 +110,11 @@ namespace { (_("Timelog check-out event does not match any current check-ins")); } + if (event.checkin.is_not_a_date_time()) + throw parse_error(_("Timelog check-in has no corresponding check-out")); + if (out_event.checkin.is_not_a_date_time()) + throw parse_error(_("Timelog check-out has no corresponding check-in")); + if (out_event.checkin < event.checkin) throw parse_error (_("Timelog check-out date less than corresponding check-in")); @@ -88,32 +127,35 @@ namespace { if (! out_event.note.empty() && event.note.empty()) event.note = out_event.note; - std::auto_ptr<xact_t> curr(new xact_t); - curr->_date = out_event.checkin.date(); - curr->code = out_event.desc; // if it wasn't used above - curr->payee = event.desc; - curr->pos = event.position; - - if (! event.note.empty()) - curr->append_note(event.note.c_str(), scope); - - char buf[32]; - std::sprintf(buf, "%lds", long((out_event.checkin - event.checkin) - .total_seconds())); - amount_t amt; - amt.parse(buf); - VERIFY(amt.valid()); - - post_t * post = new post_t(event.account, amt, POST_VIRTUAL); - post->set_state(item_t::CLEARED); - post->pos = event.position; - curr->add_post(post); - event.account->add_post(post); - - if (! journal.add_xact(curr.get())) - throw parse_error(_("Failed to record 'out' timelog transaction")); - else - curr.release(); + if (! context.journal->day_break) { + create_timelog_xact(event, out_event, context); + return 1; + } else { + time_xact_t begin(event); + std::size_t xact_count = 0; + + while (begin.checkin < out_event.checkin) { + DEBUG("timelog", "begin.checkin: " << begin.checkin); + datetime_t days_end(begin.checkin.date(), time_duration_t(23, 59, 59)); + days_end += seconds(1); + DEBUG("timelog", "days_end: " << days_end); + + if (out_event.checkin <= days_end) { + create_timelog_xact(begin, out_event, context); + ++xact_count; + break; + } else { + time_xact_t end(out_event); + end.checkin = days_end; + DEBUG("timelog", "end.checkin: " << end.checkin); + create_timelog_xact(begin, end, context); + ++xact_count; + + begin.checkin = end.checkin; + } + } + return xact_count; + } } } // unnamed namespace @@ -129,9 +171,8 @@ void time_log_t::close() DEBUG("timelog", "Clocking out from account " << account->fullname()); clock_out_from_timelog(time_xacts, time_xact_t(none, CURRENT_TIME(), account), - journal, scope); - if (context_count) - (*context_count)++; + context); + context.count++; } assert(time_xacts.empty()); } @@ -149,12 +190,12 @@ void time_log_t::clock_in(time_xact_t event) time_xacts.push_back(event); } -void time_log_t::clock_out(time_xact_t event) +std::size_t time_log_t::clock_out(time_xact_t event) { if (time_xacts.empty()) throw std::logic_error(_("Timelog check-out event without a check-in")); - clock_out_from_timelog(time_xacts, event, journal, scope); + return clock_out_from_timelog(time_xacts, event, context); } } // namespace ledger diff --git a/src/timelog.h b/src/timelog.h index 020ae4f2..857952ff 100644 --- a/src/timelog.h +++ b/src/timelog.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -50,6 +50,7 @@ namespace ledger { class account_t; class journal_t; +class parse_context_t; class time_xact_t { @@ -86,22 +87,18 @@ public: class time_log_t : public boost::noncopyable { std::list<time_xact_t> time_xacts; - journal_t& journal; - scope_t& scope; + parse_context_t& context; public: - std::size_t * context_count; - - time_log_t(journal_t& _journal, scope_t& _scope) - : journal(_journal), scope(_scope), context_count(NULL) { - TRACE_CTOR(time_log_t, "journal_t&, scope_t&, std::size&"); + time_log_t(parse_context_t& _context) : context(_context) { + TRACE_CTOR(time_log_t, "parse_context_t&"); } ~time_log_t() { TRACE_DTOR(time_log_t); } void clock_in(time_xact_t event); - void clock_out(time_xact_t event); + std::size_t clock_out(time_xact_t event); void close(); }; diff --git a/src/times.cc b/src/times.cc index 0384edf6..3c556a47 100644 --- a/src/times.cc +++ b/src/times.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -173,6 +173,7 @@ namespace { #else // USE_BOOST_FACETS std::tm data; std::memset(&data, 0, sizeof(std::tm)); + data.tm_year = CURRENT_DATE().year() - 1900; data.tm_mday = 1; // some formats have no day if (strptime(str, fmt_str, &data)) return gregorian::date_from_tm(data); @@ -196,6 +197,8 @@ namespace { std::deque<shared_ptr<date_io_t> > readers; + bool convert_separators_to_slashes = true; + date_t parse_date_mask_routine(const char * date_str, date_io_t& io, date_traits_t * traits = NULL) { @@ -204,9 +207,11 @@ namespace { char buf[128]; std::strcpy(buf, date_str); - for (char * p = buf; *p; p++) - if (*p == '.' || *p == '-') - *p = '/'; + if (convert_separators_to_slashes) { + for (char * p = buf; *p; p++) + if (*p == '.' || *p == '-') + *p = '/'; + } date_t when = io.parse(buf); @@ -1305,7 +1310,7 @@ void date_interval_t::stabilize(const optional<date_t>& date) date_interval_t next_interval(*this); ++next_interval; - if (next_interval.start && *next_interval.start < *date) { + if (next_interval.start && *next_interval.start <= *date) { *this = next_interval; } else { end_of_duration = none; @@ -1355,7 +1360,8 @@ void date_interval_t::stabilize(const optional<date_t>& date) } } -bool date_interval_t::find_period(const date_t& date) +bool date_interval_t::find_period(const date_t& date, + const bool allow_shift) { stabilize(date); @@ -1397,6 +1403,12 @@ bool date_interval_t::find_period(const date_t& date) DEBUG("times.interval", "date = " << date); DEBUG("times.interval", "scan = " << scan); DEBUG("times.interval", "end_of_scan = " << end_of_scan); +#if defined(DEBUG_ON) + if (finish) + DEBUG("times.interval", "finish = " << *finish); + else + DEBUG("times.interval", "finish is not set"); +#endif while (date >= scan && (! finish || scan < *finish)) { if (date < end_of_scan) { @@ -1411,11 +1423,19 @@ bool date_interval_t::find_period(const date_t& date) return true; } + else if (! allow_shift) { + break; + } scan = duration->add(scan); end_of_scan = duration->add(scan); + + DEBUG("times.interval", "scan = " << scan); + DEBUG("times.interval", "end_of_scan = " << end_of_scan); } + DEBUG("times.interval", "false: failed scan"); + return false; } @@ -1759,6 +1779,7 @@ void set_date_format(const char * format) void set_input_date_format(const char * format) { readers.push_front(shared_ptr<date_io_t>(new date_io_t(format, true))); + convert_separators_to_slashes = false; } void times_initialize() diff --git a/src/times.h b/src/times.h index 39945824..3bb95903 100644 --- a/src/times.h +++ b/src/times.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -218,6 +218,9 @@ struct date_duration_t case YEARS: return date + gregorian::years(length); } +#if !defined(__clang__) + return date_t(); +#endif } date_t subtract(const date_t& date) const { @@ -233,6 +236,9 @@ struct date_duration_t case YEARS: return date - gregorian::years(length); } +#if !defined(__clang__) + return date_t(); +#endif } string to_string() const { @@ -301,13 +307,14 @@ public: } date_specifier_t(const date_t& date, const optional<date_traits_t>& traits = none) { - TRACE_CTOR(date_specifier_t, "date_t, date_traits_t"); if (! traits || traits->has_year) year = date.year(); if (! traits || traits->has_month) month = date.month(); if (! traits || traits->has_day) day = date.day(); + + TRACE_CTOR(date_specifier_t, "date_t, date_traits_t"); } date_specifier_t(const date_specifier_t& other) : year(other.year), month(other.month), @@ -532,8 +539,8 @@ public: TRACE_CTOR(date_interval_t, ""); } date_interval_t(const string& str) : aligned(false) { - TRACE_CTOR(date_interval_t, "const string&"); parse(str); + TRACE_CTOR(date_interval_t, "const string&"); } date_interval_t(const date_interval_t& other) : range(other.range), @@ -553,6 +560,10 @@ public: return (start == other.start && (! start || *start == *other.start)); } + bool operator<(const date_interval_t& other) const { + return (start == other.start && + (! start || *start < *other.start)); + } operator bool() const { return is_valid(); @@ -574,10 +585,14 @@ public: return start; } - /** Find the current or next period containing date. Returns true if the - date_interval_t object has been altered to reflect the interval - containing date, or false if no such period can be found. */ - bool find_period(const date_t& date = CURRENT_DATE()); + /** Find the current or next period containing date. Returns false if + no such period can be found. If allow_shift is true, the default, + then the interval may be shifted in time to find the period. */ + bool find_period(const date_t& date = CURRENT_DATE(), + const bool allow_shift = true); + bool within_period(const date_t& date = CURRENT_DATE()) { + return find_period(date, false); + } optional<date_t> inclusive_end() const { if (end_of_duration) diff --git a/src/token.cc b/src/token.cc index 77092d49..e5d6b218 100644 --- a/src/token.cc +++ b/src/token.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -415,16 +415,13 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) try { amount_t temp; if (! temp.parse(in, parse_flags.plus_flags(PARSE_SOFT_FAIL))) { - // If the amount had no commodity, it must be an unambiguous - // variable reference - in.clear(); in.seekg(pos, std::ios::beg); if (in.fail()) throw_(parse_error, _("Failed to reset input stream")); c = static_cast<char>(in.peek()); - if (! std::isalpha(c)) + if (! std::isalpha(c) && c != '_') expected('\0', c); parse_ident(in); diff --git a/src/token.h b/src/token.h index cbdf1258..01ff7ee9 100644 --- a/src/token.h +++ b/src/token.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/src/unistring.h b/src/unistring.h index 4be36b0d..b2278796 100644 --- a/src/unistring.h +++ b/src/unistring.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -64,14 +64,14 @@ public: } unistring(const std::string& input) { - TRACE_CTOR(unistring, "std::string"); - const char * p = input.c_str(); std::size_t len = input.length(); assert(len < 1024); VERIFY(utf8::is_valid(p, p + len)); utf8::unchecked::utf8to32(p, p + len, std::back_inserter(utf32chars)); + + TRACE_CTOR(unistring, "std::string"); } ~unistring() { TRACE_DTOR(unistring); diff --git a/src/utils.cc b/src/utils.cc index 42600db3..17118904 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -50,8 +50,8 @@ void debug_assert(const string& reason, std::size_t line) { std::ostringstream buf; - buf << "Assertion failed in \"" << file << "\", line " << line - << ": " << func << ": " << reason; + buf << "Assertion failed in " << file_context(file, line) + << func << ": " << reason; throw assertion_failed(buf.str()); } @@ -270,13 +270,81 @@ void operator delete[](void * ptr, const std::nothrow_t&) throw() { namespace ledger { -inline void report_count_map(std::ostream& out, object_count_map& the_map) -{ - foreach (object_count_map::value_type& pair, the_map) - out << " " << std::right << std::setw(12) << pair.second.first - << " " << std::right << std::setw(7) << pair.second.second - << " " << std::left << pair.first - << std::endl; +namespace { + void stream_commified_number(std::ostream& out, std::size_t num) + { + std::ostringstream buf; + std::ostringstream obuf; + + buf << num; + + string number(buf.str()); + + int integer_digits = 0; + // Count the number of integer digits + for (const char * p = number.c_str(); *p; p++) { + if (*p == '.') + break; + else if (*p != '-') + integer_digits++; + } + + for (const char * p = number.c_str(); *p; p++) { + if (*p == '.') { + obuf << *p; + assert(integer_digits <= 3); + } + else if (*p == '-') { + obuf << *p; + } + else { + obuf << *p; + + if (integer_digits > 3 && --integer_digits % 3 == 0) + obuf << ','; + } + } + + out << obuf.str(); + } + + void stream_memory_size(std::ostream& out, std::size_t size) + { + std::ostringstream obuf; + + if (size > 10 * 1024 * 1024) + obuf << "\033[1m"; + if (size > 100 * 1024 * 1024) + obuf << "\033[31m"; + + obuf << std::setw(7); + + if (size < 1024) + obuf << size << 'b'; + else if (size < (1024 * 1024)) + obuf << int(double(size) / 1024.0) << 'K'; + else if (size < (1024 * 1024 * 1024)) + obuf << int(double(size) / (1024.0 * 1024.0)) << 'M'; + else + obuf << int(double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G'; + + if (size > 10 * 1024 * 1024) + obuf << "\033[0m"; + + out << obuf.str(); + } + + void report_count_map(std::ostream& out, object_count_map& the_map) + { + foreach (object_count_map::value_type& pair, the_map) { + out << " " << std::right << std::setw(18); + stream_commified_number(out, pair.second.first); + out << " " << std::right << std::setw(7); + stream_memory_size(out, pair.second.second); + out << " " << std::left << pair.first + << std::endl; + } + } } std::size_t current_objects_size() @@ -354,7 +422,7 @@ void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size) void report_memory(std::ostream& out, bool report_all) { - if (! live_memory || ! memory_tracing_active) return; + if (! live_memory) return; if (live_memory_count->size() > 0) { out << "NOTE: There may be memory held by Boost " @@ -366,11 +434,13 @@ void report_memory(std::ostream& out, bool report_all) if (live_memory->size() > 0) { out << "Live memory:" << std::endl; - foreach (const memory_map::value_type& pair, *live_memory) - out << " " << std::right << std::setw(12) << pair.first - << " " << std::right << std::setw(7) << pair.second.second - << " " << std::left << pair.second.first + foreach (const memory_map::value_type& pair, *live_memory) { + out << " " << std::right << std::setw(18) << pair.first + << " " << std::right << std::setw(7); + stream_memory_size(out, pair.second.second); + out << " " << std::left << pair.second.first << std::endl; + } } if (report_all && total_memory_count->size() > 0) { @@ -386,11 +456,13 @@ void report_memory(std::ostream& out, bool report_all) if (live_objects->size() > 0) { out << "Live objects:" << std::endl; - foreach (const objects_map::value_type& pair, *live_objects) - out << " " << std::right << std::setw(12) << pair.first - << " " << std::right << std::setw(7) << pair.second.second - << " " << std::left << pair.second.first + foreach (const objects_map::value_type& pair, *live_objects) { + out << " " << std::right << std::setw(18) << pair.first + << " " << std::right << std::setw(7); + stream_memory_size(out, pair.second.second); + out << " " << std::left << pair.second.first << std::endl; + } } if (report_all) { @@ -529,18 +601,6 @@ std::ostringstream _log_buffer; uint8_t _trace_level; #endif -static inline void stream_memory_size(std::ostream& out, std::size_t size) -{ - if (size < 1024) - out << size << 'b'; - else if (size < (1024 * 1024)) - out << (double(size) / 1024.0) << 'K'; - else if (size < (1024 * 1024 * 1024)) - out << (double(size) / (1024.0 * 1024.0)) << 'M'; - else - out << (double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G'; -} - static bool logger_has_run = false; static ptime logger_start; @@ -553,9 +613,6 @@ void logger_func(log_level_t level) #if defined(VERIFY_ON) IF_VERIFY() *_log_stream << " TIME OBJSZ MEMSZ" << std::endl; -#else - IF_VERIFY() - *_log_stream << " TIME" << std::endl; #endif } @@ -603,12 +660,16 @@ void logger_func(log_level_t level) namespace ledger { -optional<std::string> _log_category; +optional<std::string> _log_category; +#if defined(HAVE_BOOST_REGEX_UNICODE) +optional<boost::u32regex> _log_category_re; +#else +optional<boost::regex> _log_category_re; +#endif struct __maybe_enable_debugging { __maybe_enable_debugging() { - const char * p = std::getenv("LEDGER_DEBUG"); - if (p != NULL) { + if (const char * p = std::getenv("LEDGER_DEBUG")) { _log_level = LOG_DEBUG; _log_category = p; } diff --git a/src/utils.h b/src/utils.h index e1a03d79..8f11f75a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -171,7 +171,7 @@ void report_memory(std::ostream& out, bool report_all = false); #else // ! VERIFY_ON #define VERIFY(x) -#define DO_VERIFY() true +#define DO_VERIFY() false #define TRACE_CTOR(cls, args) #define TRACE_DTOR(cls) @@ -278,6 +278,28 @@ extern string empty_string; strings_list split_arguments(const char * line); +inline string to_string(long num) { + std::ostringstream buf; + buf << num; + return buf.str(); +} + +inline string to_string(std::size_t num) { + std::ostringstream buf; + buf << num; + return buf.str(); +} + +inline string lowered(const string& str) { + string tmp(str); + to_lower(tmp); + return tmp; +} + +inline string operator+(const char * left, const string& right) { + return string(left) + right; +} + } // namespace ledger /*@}*/ @@ -338,10 +360,32 @@ extern uint8_t _trace_level; #if defined(DEBUG_ON) -extern optional<std::string> _log_category; +extern optional<std::string> _log_category; +#if defined(HAVE_BOOST_REGEX_UNICODE) + extern optional<boost::u32regex> _log_category_re; +#else + extern optional<boost::regex> _log_category_re; +#endif inline bool category_matches(const char * cat) { - return _log_category && starts_with(cat, *_log_category); + if (_log_category) { + if (! _log_category_re) { + _log_category_re = +#if defined(HAVE_BOOST_REGEX_UNICODE) + boost::make_u32regex(_log_category->c_str(), + boost::regex::perl | boost::regex::icase); +#else + boost::regex(_log_category->c_str(), + boost::regex::perl | boost::regex::icase); +#endif + } +#if defined(HAVE_BOOST_REGEX_UNICODE) + return boost::u32regex_search(cat, *_log_category_re); +#else + return boost::regex_search(cat, *_log_category_re); +#endif + } + return false; } #define SHOW_DEBUG(cat) \ diff --git a/src/value.cc b/src/value.cc index c62e6f32..2b1b561f 100644 --- a/src/value.cc +++ b/src/value.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -724,7 +724,7 @@ value_t& value_t::operator/=(const value_t& val) return *this; case AMOUNT: if (as_balance().single_amount()) { - in_place_simplify(); + in_place_cast(AMOUNT); as_amount_lval() /= val.as_amount(); return *this; } @@ -742,7 +742,7 @@ value_t& value_t::operator/=(const value_t& val) break; } - add_error_context(_("While dividing %1 by %2:") << val << *this); + add_error_context(_("While dividing %1 by %2:") << *this << val); throw_(value_error, _("Cannot divide %1 by %2") << label() << val.label()); return *this; @@ -869,7 +869,7 @@ bool value_t::is_less_than(const value_t& val) const case INTEGER: return as_long() < val.as_long(); case AMOUNT: - return val.as_amount() >= as_long(); + return val.as_amount() > as_long(); default: break; } @@ -948,8 +948,7 @@ bool value_t::is_less_than(const value_t& val) const break; } - add_error_context(_("While comparing if %1 is less than %2:") - << *this << val); + add_error_context(_("While comparing if %1 is less than %2:") << *this << val); throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label()); return *this; @@ -990,7 +989,7 @@ bool value_t::is_greater_than(const value_t& val) const case INTEGER: return as_long() > val.as_long(); case AMOUNT: - return val.as_amount() > as_long(); + return val.as_amount() < as_long(); default: break; } @@ -1064,8 +1063,7 @@ bool value_t::is_greater_than(const value_t& val) const break; } - add_error_context(_("While comparing if %1 is greater than %2:") - << *this << val); + add_error_context(_("While comparing if %1 is greater than %2:") << *this << val); throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label()); return *this; @@ -1228,7 +1226,7 @@ void value_t::in_place_cast(type_t cast_type) case STRING: switch (cast_type) { case INTEGER: { - if (all(as_string(), is_digit())) { + if (all(as_string(), is_any_of("-0123456789"))) { set_long(lexical_cast<long>(as_string())); return; } @@ -1251,13 +1249,23 @@ void value_t::in_place_cast(type_t cast_type) } break; + case MASK: + switch (cast_type) { + case STRING: + set_string(as_mask().str()); + return; + default: + break; + } + break; + default: break; } add_error_context(_("While converting %1:") << *this); - throw_(value_error, _("Cannot convert %1 to %2") - << label() << label(cast_type)); + throw_(value_error, + _("Cannot convert %1 to %2") << label() << label(cast_type)); } void value_t::in_place_negate() @@ -1389,25 +1397,30 @@ bool value_t::is_zero() const return false; } -value_t value_t::value(const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of) const +value_t value_t::value(const datetime_t& moment, + const commodity_t * in_terms_of) const { switch (type()) { case INTEGER: return NULL_VALUE; case AMOUNT: - if (optional<amount_t> val = - as_amount().value(moment, in_terms_of)) + if (optional<amount_t> val = as_amount().value(moment, in_terms_of)) return *val; return NULL_VALUE; case BALANCE: - if (optional<balance_t> bal = - as_balance().value(moment, in_terms_of)) + if (optional<balance_t> bal = as_balance().value(moment, in_terms_of)) return *bal; return NULL_VALUE; + case SEQUENCE: { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(value.value(moment, in_terms_of)); + return temp; + } + default: break; } @@ -1417,37 +1430,100 @@ value_t value_t::value(const optional<datetime_t>& moment, return NULL_VALUE; } -value_t value_t::price() const +value_t value_t::exchange_commodities(const std::string& commodities, + const bool add_prices, + const datetime_t& moment) { - switch (type()) { - case AMOUNT: - return as_amount().price(); - case BALANCE: - return as_balance().price(); - default: - return *this; + if (type() == SEQUENCE) { + value_t temp; + foreach (value_t& value, as_sequence_lval()) + temp.push_back(value.exchange_commodities(commodities, add_prices, moment)); + return temp; } -} -value_t value_t::exchange_commodities(const std::string& commodities, - const bool add_prices, - const optional<datetime_t>& moment) -{ - scoped_array<char> buf(new char[commodities.length() + 1]); - - std::strcpy(buf.get(), commodities.c_str()); - - for (char * p = std::strtok(buf.get(), ","); - p; - p = std::strtok(NULL, ",")) { - if (commodity_t * commodity = - commodity_pool_t::current_pool->parse_price_expression(p, add_prices, - moment)) { - value_t result = value(moment, *commodity); - if (! result.is_null()) - return result; + // If we are repricing to just a single commodity, with no price + // expression, skip the expensive logic below. + if (commodities.find(',') == string::npos && + commodities.find('=') == string::npos) + return value(moment, commodity_pool_t::current_pool->find_or_create(commodities)); + + std::vector<commodity_t *> comms; + std::vector<bool> force; + + typedef tokenizer<char_separator<char> > tokenizer; + tokenizer tokens(commodities, char_separator<char>(",")); + + foreach (const string& name, tokens) { + string::size_type name_len = name.length(); + + if (commodity_t * commodity = commodity_pool_t::current_pool + ->parse_price_expression(name[name_len - 1] == '!' ? + string(name, 0, name_len - 1) : + name, add_prices, moment)) { + DEBUG("commodity.exchange", "Pricing for commodity: " << commodity->symbol()); + comms.push_back(&commodity->referent()); + force.push_back(name[name_len - 1] == '!'); + } + } + + std::size_t index = 0; + foreach (commodity_t * comm, comms) { + switch (type()) { + case AMOUNT: + DEBUG("commodity.exchange", "We have an amount: " << as_amount_lval()); + if (! force[index] && + std::find(comms.begin(), comms.end(), + &as_amount_lval().commodity().referent()) != comms.end()) + break; + + DEBUG("commodity.exchange", "Referent doesn't match, pricing..."); + if (optional<amount_t> val = as_amount_lval().value(moment, comm)) { + DEBUG("commodity.exchange", "Re-priced amount is: " << *val); + return *val; + } + DEBUG("commodity.exchange", "Was unable to find a price"); + break; + + case BALANCE: { + balance_t temp; + bool repriced = false; + + DEBUG("commodity.exchange", "We have a balance: " << as_balance_lval()); + foreach (const balance_t::amounts_map::value_type& pair, + as_balance_lval().amounts) { + DEBUG("commodity.exchange", "We have a balance amount of commodity: " + << pair.first->symbol() << " == " + << pair.second.commodity().symbol()); + if (! force[index] && + std::find(comms.begin(), comms.end(), + &pair.first->referent()) != comms.end()) { + temp += pair.second; + } else { + DEBUG("commodity.exchange", "Referent doesn't match, pricing..."); + if (optional<amount_t> val = pair.second.value(moment, comm)) { + DEBUG("commodity.exchange", "Re-priced member amount is: " << *val); + temp += *val; + repriced = true; + } else { + DEBUG("commodity.exchange", "Was unable to find price"); + temp += pair.second; + } + } + } + + if (repriced) { + DEBUG("commodity.exchange", "Re-priced balance is: " << temp); + return temp; + } } + + default: + break; + } + + ++index; } + return *this; } @@ -1727,7 +1803,7 @@ void value_t::print(std::ostream& _out, switch (type()) { case VOID: - out << ""; + out << "(null)"; break; case BOOLEAN: diff --git a/src/value.h b/src/value.h index f8495002..d128bb89 100644 --- a/src/value.h +++ b/src/value.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -179,8 +179,8 @@ public: */ explicit storage_t(const storage_t& rhs) : type(rhs.type), refc(0) { - TRACE_CTOR(value_t::storage_t, "copy"); *this = rhs; + TRACE_CTOR(value_t::storage_t, "copy"); } storage_t& operator=(const storage_t& rhs); @@ -290,73 +290,75 @@ public: } value_t(const bool val) { - TRACE_CTOR(value_t, "const bool"); set_boolean(val); + TRACE_CTOR(value_t, "const bool"); } value_t(const datetime_t& val) { - TRACE_CTOR(value_t, "const datetime_t&"); set_datetime(val); + TRACE_CTOR(value_t, "const datetime_t&"); } value_t(const date_t& val) { - TRACE_CTOR(value_t, "const date_t&"); set_date(val); + TRACE_CTOR(value_t, "const date_t&"); } value_t(const long val) { - TRACE_CTOR(value_t, "const long"); set_long(val); + TRACE_CTOR(value_t, "const long"); } value_t(const unsigned long val) { - TRACE_CTOR(value_t, "const unsigned long"); set_amount(val); + TRACE_CTOR(value_t, "const unsigned long"); } value_t(const double val) { - TRACE_CTOR(value_t, "const double"); set_amount(val); + TRACE_CTOR(value_t, "const double"); } value_t(const amount_t& val) { - TRACE_CTOR(value_t, "const amount_t&"); set_amount(val); + TRACE_CTOR(value_t, "const amount_t&"); } value_t(const balance_t& val) { - TRACE_CTOR(value_t, "const balance_t&"); set_balance(val); + TRACE_CTOR(value_t, "const balance_t&"); } value_t(const mask_t& val) { - TRACE_CTOR(value_t, "const mask_t&"); set_mask(val); + TRACE_CTOR(value_t, "const mask_t&"); } explicit value_t(const string& val, bool literal = false) { - TRACE_CTOR(value_t, "const string&, bool"); if (literal) set_string(val); else set_amount(amount_t(val)); + + TRACE_CTOR(value_t, "const string&, bool"); } explicit value_t(const char * val, bool literal = false) { - TRACE_CTOR(value_t, "const char *"); if (literal) set_string(val); else set_amount(amount_t(val)); + + TRACE_CTOR(value_t, "const char *"); } value_t(const sequence_t& val) { - TRACE_CTOR(value_t, "const sequence_t&"); set_sequence(val); + TRACE_CTOR(value_t, "const sequence_t&"); } explicit value_t(scope_t * item) { - TRACE_CTOR(value_t, "scope_t *"); set_scope(item); + TRACE_CTOR(value_t, "scope_t *"); } #if 0 template <typename T> explicit value_t(T& item) { - TRACE_CTOR(value_t, "T&"); set_any(item); + TRACE_CTOR(value_t, "T&"); } #endif @@ -375,8 +377,8 @@ public: * object. A true copy is only ever made prior to modification. */ value_t(const value_t& val) { - TRACE_CTOR(value_t, "copy"); *this = val; + TRACE_CTOR(value_t, "copy"); } value_t& operator=(const value_t& val) { if (! (this == &val || storage == val.storage)) @@ -477,14 +479,12 @@ public: void in_place_unreduce(); // exists for efficiency's sake // Return the "market value" of a given value at a specific time. - value_t value(const optional<datetime_t>& moment = none, - const optional<commodity_t&>& in_terms_of = none) const; - - value_t price() const; + value_t value(const datetime_t& moment = datetime_t(), + const commodity_t * in_terms_of = NULL) const; - value_t exchange_commodities(const std::string& commodities, - const bool add_prices = false, - const optional<datetime_t>& moment = none); + value_t exchange_commodities(const std::string& commodities, + const bool add_prices = false, + const datetime_t& moment = datetime_t()); /** * Truth tests. diff --git a/src/views.cc b/src/views.cc new file mode 100644 index 00000000..bbd58ce2 --- /dev/null +++ b/src/views.cc @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef DOCUMENT_MODEL + +#include <system.hh> + +#include "views.h" +#include "report.h" +#include "journal.h" +#include "xact.h" +#include "post.h" +#include "account.h" + +namespace ledger { + +r_xact_ptr r_journal_t::create_xact(xact_t * xact) +{ + r_xact_ptr x = new r_xact_t(this, xact); + add_xact(x); + assert(xact->data == NULL); + xact->data = &x; + return x; +} + +void r_journal_t::add_xact(r_xact_ptr xact) +{ + xacts.push_back(xact); +} + +r_post_ptr r_journal_t::add_post(post_t * post) +{ + r_xact_ptr x; + if (post->xact->data) + x = *static_cast<r_xact_ptr *>(post->xact->data); + else + x = create_xact(post->xact); + + r_post_ptr p = create_post(post, x, create_account(post->account)); + return p; +} + +void r_journal_t::add_post(r_post_ptr post) +{ + posts.push_back(post); +} + +r_post_ptr r_journal_t::create_post(post_t * post, r_xact_ptr xact, + r_account_ptr account) +{ + r_post_ptr p = new r_post_t(this, post, xact, account); + + add_post(p); + xact->add_post(p); + account->add_post(p); + + return p; +} + +r_post_ptr r_journal_t::create_post(r_post_ptr post, r_xact_ptr xact, + r_account_ptr account) +{ + r_post_ptr temp(new r_post_t(*post.get())); + + add_post(temp); + + temp->set_xact(xact); + xact->add_post(temp); + + temp->set_account(account); + account->add_post(temp); + + return temp; +} + +r_account_ptr r_journal_t::create_account(account_t * account) +{ + return create_account(account->fullname()); +} + +r_account_ptr r_journal_t::create_account(const std::string& name) +{ + return master_ptr->create_account(name); +} + + +const optional<position_t> r_item_t::position() const +{ + return ptr()->pos; +} + +date_t r_item_t::date() const +{ + return ptr()->date(); +} + +void r_item_t::set_date(const date_t& when) +{ +} + +item_t::state_t r_item_t::state() const +{ + return ptr()->state(); +} + +void r_item_t::set_state(item_t::state_t val) +{ +} + +string r_item_t::payee() const +{ + if (optional<value_t> desc = get_tag(_("Payee"))) + return desc->as_string(); + else + return empty_string; +} + +void r_item_t::set_payee(const string& name) +{ +} + +void r_item_t::define(const symbol_t::kind_t, const string& name, + expr_t::ptr_op_t def) +{ + bind_scope_t bound_scope(*scope_t::default_scope, *this); + set_tag(name, def->calc(bound_scope)); +} + +expr_t::ptr_op_t r_item_t::lookup(const symbol_t::kind_t kind, + const string& name) +{ + if (kind != symbol_t::FUNCTION) + return NULL; + + switch (name[0]) { + } + + return base_item->lookup(kind, name); +} + + +string r_xact_t::description() +{ + return ptr()->description(); +} + +void r_xact_t::add_post(r_post_ptr post) +{ + posts.push_back(post); +} + +string r_xact_t::payee() const +{ + string desc(r_item_t::payee()); + if (desc.empty()) + return ptr()->payee; + else + return desc; +} + + +string r_post_t::description() +{ + return ptr()->description(); +} + +string r_post_t::payee() const +{ + string desc(r_item_t::payee()); + if (desc.empty()) + return const_cast<r_post_t *>(this)->xact()->payee(); + else + return desc; +} + + +string r_account_t::description() +{ + return string(_("account ")) + fullname(); +} + +void r_account_t::add_post(r_post_ptr post) +{ + posts.push_back(post); +} + +r_account_ptr r_account_t::create_account(const std::string& fname) +{ + string::size_type sep = fname.find(':'); + string head, tail; + if (sep == string::npos) { + head = fname; + } else { + head = string(fname, 0, sep); + tail = string(fname, sep + 1); + } + + std::pair<r_accounts_map::iterator, bool> result = + accounts.insert(r_accounts_map::value_type + (head, new r_account_t(journal_ptr, this, name))); + + r_account_ptr acct((*result.first).second); + if (tail.empty()) + return acct; + else + return acct->create_account(tail); +} + +string r_account_t::fullname() const +{ + if (! _fullname.empty()) { + return _fullname; + } else { + r_account_ptr first = NULL; + string fname = name; + + while (! first || first->parent_ptr) { + first = first ? first->parent_ptr : parent_ptr; + if (! first->name.empty()) + fname = first->name + ":" + fname; + } + + _fullname = fname; + + return fname; + } +} + +} // namespace ledger + +#endif /* DOCUMENT_MODEL */ diff --git a/src/views.h b/src/views.h new file mode 100644 index 00000000..f9a007b7 --- /dev/null +++ b/src/views.h @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @addtogroup views + */ + +/** + * @file views.h + * @author John Wiegley + * + * @ingroup views + */ +#ifndef _VIEWS_H +#define _VIEWS_H + +#include "utils.h" + +#ifdef DOCUMENT_MODEL + +#include "scope.h" +#include "item.h" +#include "report.h" +#include "post.h" +#include "predicate.h" + +namespace ledger { + +class journal_t; +class xact_t; +class post_t; +class account_t; +class report_t; + +class r_base_t : public supports_flags<uint_least16_t>, + public scope_t +{ +public: + r_base_t() : refc(0) { + TRACE_CTOR(r_base_t, ""); + } + r_base_t(const r_base_t& other) : refc(0) { + TRACE_CTOR(r_base_t, "copy"); + } + virtual ~r_base_t() { + TRACE_DTOR(r_base_t); + } + +protected: + /** + * `refc' holds the current reference count for each object. + */ + mutable int refc; + + /** + * Reference counting methods. The intrusive_ptr_* methods are used + * by boost::intrusive_ptr to manage the calls to acquire and release. + */ + void acquire() const { + VERIFY(refc >= 0); + refc++; + } + void release() const { + VERIFY(refc > 0); + if (--refc == 0) + checked_delete(this); + } + + friend inline void intrusive_ptr_add_ref(r_base_t * r_ptr) { + r_ptr->acquire(); + } + friend inline void intrusive_ptr_release(r_base_t * r_ptr) { + r_ptr->release(); + } +}; + +class r_journal_t; +class r_item_t; +class r_xact_t; +class r_post_t; +class r_account_t; + +typedef intrusive_ptr<r_journal_t> r_journal_ptr; +typedef intrusive_ptr<r_item_t> r_item_ptr; +typedef intrusive_ptr<r_xact_t> r_xact_ptr; +typedef intrusive_ptr<r_post_t> r_post_ptr; +typedef intrusive_ptr<r_account_t> r_account_ptr; + +typedef std::list<r_xact_ptr> r_xacts_list; +typedef std::list<r_post_ptr> r_posts_list; + +class r_journal_t : public r_base_t +{ + journal_t * base_journal; + + journal_t * ptr() { + return base_journal; + } + const journal_t * ptr() const { + return base_journal; + } + + r_account_ptr master_ptr; + r_xacts_list xacts; + r_posts_list posts; + + void set_master(r_account_ptr ptr) { + master_ptr = ptr; + } + +public: + r_journal_t(journal_t * _journal, r_account_ptr _master) + : r_base_t(), base_journal(_journal), master_ptr(_master) { + TRACE_CTOR(r_journal_t, "journal_t *, account_t *"); + } + r_journal_t(const r_journal_t& other) + : r_base_t(other), + base_journal(other.base_journal), + master_ptr(other.master_ptr), + xacts(other.xacts), + posts(other.posts) { + TRACE_CTOR(r_journal_t, "copy"); + } + virtual ~r_journal_t() { + TRACE_DTOR(r_journal_t); + } + + r_xact_ptr create_xact(xact_t * xact = NULL); + + void add_xact(r_xact_ptr xact); + + r_xacts_list::iterator xacts_begin() { + return xacts.begin(); + } + r_xacts_list::iterator xacts_end() { + return xacts.end(); + } + + r_post_ptr add_post(post_t * post); + void add_post(r_post_ptr post); + + r_post_ptr create_post(post_t * post = NULL, r_xact_ptr xact = NULL, + r_account_ptr account = NULL); + r_post_ptr create_post(r_post_ptr post, r_xact_ptr xact = NULL, + r_account_ptr account = NULL); + + r_posts_list::iterator posts_begin() { + return posts.begin(); + } + r_posts_list::iterator posts_end() { + return posts.end(); + } + + r_account_ptr create_account(account_t * account = NULL); + r_account_ptr create_account(const std::string& name); + + friend void to_xml(std::ostream& out, r_journal_ptr journal); +}; + +class r_item_t : public r_base_t +{ +protected: + item_t * base_item; + + item_t * ptr() { + return base_item; + } + const item_t * ptr() const { + return base_item; + } + + r_journal_ptr journal_ptr; + +public: + r_item_t(r_journal_ptr _journal_ptr, item_t * _item) + : r_base_t(), base_item(_item), journal_ptr(_journal_ptr) { + TRACE_CTOR(r_item_t, "r_journal_ptr, item_t *"); + } + r_item_t(const r_item_t& other) + : r_base_t(other), + base_item(other.base_item), + journal_ptr(other.journal_ptr) { + TRACE_CTOR(r_item_t, "copy"); + } + virtual ~r_item_t() { + TRACE_DTOR(r_item_t); + } + + const optional<position_t> position() const; + + string id() const { + return ptr()->id(); + } + std::size_t seq() const { + return ptr()->seq(); + } + + date_t date() const; + void set_date(const date_t& when); + + item_t::state_t state() const; + void set_state(item_t::state_t val); + + string payee() const; + void set_payee(const string& name); + + optional<string> note() const { + return ptr()->note; + } + + bool has_tag(const string& tag) const { + return ptr()->has_tag(tag); + } + bool has_tag(const mask_t& tag_mask, + const optional<mask_t>& value_mask = none) const { + return ptr()->has_tag(tag_mask, value_mask); + } + + optional<value_t> get_tag(const string& tag) const { + return ptr()->get_tag(tag); + } + optional<value_t> get_tag(const mask_t& tag_mask, + const optional<mask_t>& value_mask = none) const { + return ptr()->get_tag(tag_mask, value_mask); + } + + void set_tag(const string& tag, + const optional<value_t>& value = none, + const bool overwrite_existing = true) { + ptr()->set_tag(tag, value, overwrite_existing); + } + + /** + * Symbol scope methods. + */ + virtual void define(const symbol_t::kind_t, const string&, + expr_t::ptr_op_t); + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& name); + + friend class r_journal_t; + friend void to_xml(std::ostream& out, r_item_ptr item); +}; + +class r_xact_t : public r_item_t +{ + xact_t * ptr() { + return reinterpret_cast<xact_t *>(base_item); + } + const xact_t * ptr() const { + return reinterpret_cast<const xact_t *>(base_item); + } + + r_posts_list posts; + +public: + r_xact_t(r_journal_ptr journal_ptr, xact_t * _xact) + : r_item_t(journal_ptr, reinterpret_cast<item_t *>(_xact)) { + TRACE_CTOR(r_xact_t, "r_journal_ptr, xact_t *"); + } + r_xact_t(const r_xact_t& other) + : r_item_t(other), + posts(other.posts) { + TRACE_CTOR(r_xact_t, "copy"); + } + virtual ~r_xact_t() { + TRACE_DTOR(r_xact_t); + } + + virtual string description(); + + void add_post(r_post_ptr post); + +#if 0 + r_post_ptr create_post(post_t * post = NULL, r_account_ptr account = NULL); + r_post_ptr create_post(r_post_ptr post, r_account_ptr account = NULL); +#endif + + r_posts_list::iterator posts_begin() { + return posts.begin(); + } + r_posts_list::iterator posts_end() { + return posts.end(); + } + + string code() const; + string payee() const; + + friend class r_journal_t; + friend void to_xml(std::ostream& out, r_xact_ptr xact); +}; + +class r_post_t : public r_item_t +{ + post_t * ptr() { + return reinterpret_cast<post_t *>(base_item); + } + const post_t * ptr() const { + return reinterpret_cast<const post_t *>(base_item); + } + + r_xact_ptr xact_ptr; + r_account_ptr account_ptr; + + void set_xact(r_xact_ptr ptr) { + xact_ptr = ptr; + } + void set_account(r_account_ptr ptr) { + account_ptr = ptr; + } + +public: + r_post_t(r_journal_ptr journal_ptr, post_t * _post, + r_xact_ptr _xact_ptr, r_account_ptr _account_ptr) + : r_item_t(journal_ptr, reinterpret_cast<item_t *>(_post)), + xact_ptr(_xact_ptr), account_ptr(_account_ptr) { + TRACE_CTOR(r_post_t, "r_journal_ptr, post_t *, r_xact_ptr, r_account_ptr"); + } + r_post_t(const r_post_t& other) + : r_item_t(other), + xact_ptr(other.xact_ptr), + account_ptr(other.account_ptr) { + TRACE_CTOR(r_post_t, "copy"); + } + virtual ~r_post_t() { + TRACE_DTOR(r_post_t); + } + + virtual string description(); + + string payee() const; + + r_xact_ptr xact(); + r_account_ptr account(); + + value_t amount() const; + value_t cost() const; + + std::size_t count() const; + value_t running_total() const; + + optional<datetime_t> checkin() const; + optional<datetime_t> checkout() const; + + friend class r_journal_t; + friend void to_xml(std::ostream& out, r_post_ptr post); +}; + +typedef std::map<string, r_account_ptr> r_accounts_map; + +class r_account_t : public r_base_t +{ + r_journal_ptr journal_ptr; + r_account_ptr parent_ptr; + r_accounts_map accounts; + r_posts_list posts; + + string name; + + mutable string _fullname; + +public: + r_account_t(r_journal_ptr _journal_ptr, r_account_ptr _parent_ptr, + string _name) + : r_base_t(), journal_ptr(_journal_ptr), parent_ptr(_parent_ptr), + name(_name) { + TRACE_CTOR(r_account_t, "r_journal_ptr, r_account_ptr, string"); + } + r_account_t(const r_account_t& other) + : r_base_t(other), + journal_ptr(other.journal_ptr), + parent_ptr(other.parent_ptr), + accounts(other.accounts), + posts(other.posts), + name(other.name), + _fullname(other._fullname) { + TRACE_CTOR(r_account_t, "copy"); + } + virtual ~r_account_t() { + TRACE_DTOR(r_account_t); + } + + virtual string description(); + + void add_post(r_post_ptr post); + + r_posts_list::iterator posts_begin() { + return posts.begin(); + } + r_posts_list::iterator posts_end() { + return posts.end(); + } + + r_account_ptr create_account(const std::string& name); + + string fullname() const; + + /** + * Symbol scope methods. + */ + virtual void define(const symbol_t::kind_t, const string&, + expr_t::ptr_op_t) {} + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, + const string& fname) { + return NULL; + } + + friend class r_journal_t; + friend void to_xml(std::ostream& out, r_account_ptr account); +}; + +template <typename PostsIterator> +void populate_journal(r_journal_ptr journal, report_t& report, + PostsIterator iter, predicate_t& pred) +{ + while (post_t * post = *iter) { + bind_scope_t bound_scope(report, *post); + if (pred.calc(bound_scope)) + journal->add_post(post); + + iter.increment(); + } +} + +} // namespace ledger + +#endif /* DOCUMENT_MODEL */ + +#endif // _VIEWS_H diff --git a/src/xact.cc b/src/xact.cc index 596b39fa..226fd5ab 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -35,6 +35,7 @@ #include "post.h" #include "account.h" #include "journal.h" +#include "context.h" #include "pool.h" namespace ledger { @@ -54,6 +55,9 @@ xact_base_t::~xact_base_t() // If the posting is a temporary, it will be destructed when the // temporary is. assert(! post->has_flags(ITEM_TEMP)); + + if (post->account) + post->account->remove_post(post); checked_delete(post); } } @@ -108,6 +112,46 @@ value_t xact_base_t::magnitude() const return halfbal; } +namespace { + inline bool account_ends_with_special_char(const string& name) { + string::size_type len(name.length()); + return (std::isdigit(name[len - 1]) || name[len - 1] == ')' || + name[len - 1] == '}' || name[len - 1] == ']'); + } + + struct add_balancing_post + { + bool first; + xact_base_t& xact; + post_t * null_post; + + explicit add_balancing_post(xact_base_t& _xact, post_t * _null_post) + : first(true), xact(_xact), null_post(_null_post) { + TRACE_CTOR(add_balancing_post, "xact_base_t&, post_t *"); + } + add_balancing_post(const add_balancing_post& other) + : first(other.first), xact(other.xact), null_post(other.null_post) { + TRACE_CTOR(add_balancing_post, "copy"); + } + ~add_balancing_post() throw() { + TRACE_DTOR(add_balancing_post); + } + + void operator()(const amount_t& amount) { + if (first) { + null_post->amount = amount.negated(); + null_post->add_flags(POST_CALCULATED); + first = false; + } else { + unique_ptr<post_t> p(new post_t(null_post->account, amount.negated(), + ITEM_GENERATED | POST_CALCULATED)); + p->set_state(null_post->state()); + xact.add_post(p.release()); + } + } + }; +} + bool xact_base_t::finalize() { // Scan through and compute the total balance for the xact. This is used @@ -133,8 +177,19 @@ bool xact_base_t::finalize() p.rounded().reduced() : p.reduced()); } else if (null_post) { - throw_(std::logic_error, - _("Only one posting with null amount allowed per transaction")); + bool post_account_bad = + account_ends_with_special_char(post->account->fullname()); + bool null_post_account_bad = + account_ends_with_special_char(null_post->account->fullname()); + + if (post_account_bad || null_post_account_bad) + throw_(std::logic_error, + _("Posting with null amount's account may be mispelled:\n \"%1\"") + << (post_account_bad ? post->account->fullname() : + null_post->account->fullname())); + else + throw_(std::logic_error, + _("Only one posting with null amount allowed per transaction")); } else { null_post = post; @@ -266,11 +321,13 @@ bool xact_base_t::finalize() cost_breakdown_t breakdown = commodity_pool_t::current_pool->exchange - (post->amount, *post->cost, false, + (post->amount, *post->cost, false, ! post->has_flags(POST_COST_VIRTUAL), datetime_t(date(), time_duration(0, 0, 0, 0))); if (post->amount.has_annotation() && post->amount.annotation().price) { if (breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) { + DEBUG("xact.finalize", "breakdown.basis_cost = " << breakdown.basis_cost); + DEBUG("xact.finalize", "breakdown.final_cost = " << breakdown.final_cost); if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) { DEBUG("xact.finalize", "gain_loss = " << gain_loss); gain_loss.in_place_round(); @@ -323,43 +380,17 @@ bool xact_base_t::finalize() // generated to balance them all. DEBUG("xact.finalize", "there was a null posting"); - - if (balance.is_balance()) { - const balance_t& bal(balance.as_balance()); - typedef std::map<string, amount_t> sorted_amounts_map; - sorted_amounts_map samp; - foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { - std::pair<sorted_amounts_map::iterator, bool> result = - samp.insert(sorted_amounts_map::value_type(pair.first->mapping_key(), - pair.second)); - assert(result.second); - } - - bool first = true; - foreach (sorted_amounts_map::value_type& pair, samp) { - if (first) { - null_post->amount = pair.second.negated(); - null_post->add_flags(POST_CALCULATED); - first = false; - } else { - post_t * p = new post_t(null_post->account, pair.second.negated(), - ITEM_GENERATED | POST_CALCULATED); - p->set_state(null_post->state()); - add_post(p); - } - } - } - else if (balance.is_amount()) { - null_post->amount = balance.as_amount().negated(); - null_post->add_flags(POST_CALCULATED); - } - else if (balance.is_long()) { - null_post->amount = amount_t(- balance.as_long()); - null_post->add_flags(POST_CALCULATED); - } - else if (! balance.is_null() && ! balance.is_realzero()) { + add_balancing_post post_adder(*this, null_post); + + if (balance.is_balance()) + balance.as_balance_lval().map_sorted_amounts(post_adder); + else if (balance.is_amount()) + post_adder(balance.as_amount_lval()); + else if (balance.is_long()) + post_adder(balance.to_amount()); + else if (! balance.is_null() && ! balance.is_realzero()) throw_(balance_error, _("Transaction does not balance")); - } + balance = NULL_VALUE; } @@ -457,6 +488,9 @@ bool xact_base_t::verify() xact_t::xact_t(const xact_t& e) : xact_base_t(e), code(e.code), payee(e.payee) +#ifdef DOCUMENT_MODEL + , data(NULL) +#endif { TRACE_CTOR(xact_t, "copy"); } @@ -467,30 +501,10 @@ void xact_t::add_post(post_t * post) xact_base_t::add_post(post); } -string xact_t::idstring() const -{ - std::ostringstream buf; - buf << format_date(*_date, FMT_WRITTEN); - buf << payee; - magnitude().number().print(buf); - return buf.str(); -} - -string xact_t::id() const -{ - return sha1sum(idstring()); -} - namespace { value_t get_magnitude(xact_t& xact) { return xact.magnitude(); } - value_t get_idstring(xact_t& xact) { - return string_value(xact.idstring()); - } - value_t get_id(xact_t& xact) { - return string_value(xact.id()); - } value_t get_code(xact_t& xact) { if (xact.code) @@ -554,13 +568,6 @@ expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(get_wrapper<&get_code>); break; - case 'i': - if (name == "id") - return WRAP_FUNCTOR(get_wrapper<&get_id>); - else if (name == "idstring") - return WRAP_FUNCTOR(get_wrapper<&get_idstring>); - break; - case 'm': if (name == "magnitude") return WRAP_FUNCTOR(get_wrapper<&get_magnitude>); @@ -592,7 +599,6 @@ bool xact_t::valid() const } namespace { - bool post_pred(expr_t::ptr_op_t op, post_t& post) { switch (op->kind) { @@ -609,6 +615,9 @@ namespace { else break; + case expr_t::op_t::O_EQ: + return post_pred(op->left(), post) == post_pred(op->right(), post); + case expr_t::op_t::O_NOT: return ! post_pred(op->left(), post); @@ -631,10 +640,9 @@ namespace { throw_(calc_error, _("Unhandled operator")); return false; } +} -} // unnamed namespace - -void auto_xact_t::extend_xact(xact_base_t& xact) +void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) { posts_list initial_posts(xact.posts.begin(), xact.posts.end()); @@ -646,6 +654,8 @@ void auto_xact_t::extend_xact(xact_base_t& xact) if (initial_post->has_flags(ITEM_GENERATED)) continue; + bind_scope_t bound_scope(*scope_t::default_scope, *initial_post); + bool matches_predicate = false; if (try_quick_match) { try { @@ -673,14 +683,13 @@ void auto_xact_t::extend_xact(xact_base_t& xact) DEBUG("xact.extend.fail", "The quick matcher failed, going back to regular eval"); try_quick_match = false; - matches_predicate = predicate(*initial_post); + matches_predicate = predicate(bound_scope); } } else { - matches_predicate = predicate(*initial_post); + matches_predicate = predicate(bound_scope); } - if (matches_predicate) { - bind_scope_t bound_scope(*scope_t::default_scope, *initial_post); + if (matches_predicate) { if (deferred_notes) { foreach (deferred_tag_data_t& data, *deferred_notes) { if (data.apply_to_post == NULL) @@ -688,18 +697,19 @@ void auto_xact_t::extend_xact(xact_base_t& xact) data.overwrite_existing); } } + if (check_exprs) { - foreach (check_expr_pair& pair, *check_exprs) { - if (pair.second == auto_xact_t::EXPR_GENERAL) { + foreach (expr_t::check_expr_pair& pair, *check_exprs) { + if (pair.second == expr_t::EXPR_GENERAL) { pair.first.calc(bound_scope); } else if (! pair.first.calc(bound_scope).to_boolean()) { - if (pair.second == auto_xact_t::EXPR_ASSERTION) { + if (pair.second == expr_t::EXPR_ASSERTION) throw_(parse_error, _("Transaction assertion failed: %1") << pair.first); - } else { - warning_(_("Transaction check failed: %1") << pair.first); - } + else + context.warning(STR(_("Transaction check failed: %1") + << pair.first)); } } } @@ -770,20 +780,25 @@ void auto_xact_t::extend_xact(xact_base_t& xact) post_t * new_post = new post_t(account, amt); new_post->copy_details(*post); new_post->add_flags(ITEM_GENERATED); - - xact.add_post(new_post); - new_post->account->add_post(new_post); - - if (new_post->must_balance()) - needs_further_verification = true; + new_post->account = + journal->register_account(account->fullname(), new_post, + journal->master); if (deferred_notes) { foreach (deferred_tag_data_t& data, *deferred_notes) { - if (data.apply_to_post == post) + if (! data.apply_to_post || data.apply_to_post == post) new_post->parse_tags(data.tag_data.c_str(), bound_scope, data.overwrite_existing); } } + + extend_post(*new_post, *journal); + + xact.add_post(new_post); + new_post->account->add_post(new_post); + + if (new_post->must_balance()) + needs_further_verification = true; } } } @@ -817,9 +832,9 @@ void to_xml(std::ostream& out, const xact_t& xact) push_xml y(out, "date"); to_xml(out, *xact._date, false); } - if (xact._date_eff) { - push_xml y(out, "effective-date"); - to_xml(out, *xact._date_eff, false); + if (xact._date_aux) { + push_xml y(out, "aux-date"); + to_xml(out, *xact._date_aux, false); } if (xact.code) { @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -49,6 +49,7 @@ namespace ledger { class post_t; class journal_t; +class parse_context_t; typedef std::list<post_t *> posts_list; @@ -108,7 +109,15 @@ public: optional<string> code; string payee; - xact_t() { +#ifdef DOCUMENT_MODEL + mutable void * data; +#endif + + xact_t() +#ifdef DOCUMENT_MODEL + : data(NULL) +#endif + { TRACE_CTOR(xact_t, ""); } xact_t(const xact_t& e); @@ -129,9 +138,6 @@ public: virtual void add_post(post_t * post); - string idstring() const; - string id() const; - virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, const string& name); @@ -155,21 +161,11 @@ private: class auto_xact_t : public xact_base_t { public: - predicate_t predicate; - bool try_quick_match; - + predicate_t predicate; + bool try_quick_match; std::map<string, bool> memoized_results; - enum xact_expr_kind_t { - EXPR_GENERAL, - EXPR_ASSERTION, - EXPR_CHECK - }; - - typedef std::pair<expr_t, xact_expr_kind_t> check_expr_pair; - typedef std::list<check_expr_pair> check_expr_list; - - optional<check_expr_list> check_exprs; + optional<expr_t::check_expr_list> check_exprs; struct deferred_tag_data_t { string tag_data; @@ -180,22 +176,39 @@ public: bool _overwrite_existing) : tag_data(_tag_data), overwrite_existing(_overwrite_existing), apply_to_post(NULL) {} + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + deferred_tag_data_t() : apply_to_post(NULL) {} + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */) { + ar & tag_data; + ar & overwrite_existing; + ar & apply_to_post; + } +#endif // HAVE_BOOST_SERIALIZATION }; typedef std::list<deferred_tag_data_t> deferred_notes_list; optional<deferred_notes_list> deferred_notes; + post_t * active_post; - auto_xact_t() : try_quick_match(true) { + auto_xact_t() : try_quick_match(true), active_post(NULL) { TRACE_CTOR(auto_xact_t, ""); } auto_xact_t(const auto_xact_t& other) : xact_base_t(), predicate(other.predicate), - try_quick_match(other.try_quick_match) { + try_quick_match(other.try_quick_match), + active_post(other.active_post) { TRACE_CTOR(auto_xact_t, "copy"); } auto_xact_t(const predicate_t& _predicate) - : predicate(_predicate), try_quick_match(true) + : predicate(_predicate), try_quick_match(true), active_post(NULL) { TRACE_CTOR(auto_xact_t, "const predicate_t&"); } @@ -214,15 +227,15 @@ public: } } - virtual void parse_tags(const char * p, - scope_t&, - bool overwrite_existing = true) { + virtual void parse_tags(const char * p, scope_t&, + bool overwrite_existing = true) { if (! deferred_notes) deferred_notes = deferred_notes_list(); deferred_notes->push_back(deferred_tag_data_t(p, overwrite_existing)); + deferred_notes->back().apply_to_post = active_post; } - virtual void extend_xact(xact_base_t& xact); + virtual void extend_xact(xact_base_t& xact, parse_context_t& context); #if defined(HAVE_BOOST_SERIALIZATION) private: @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -52,6 +52,7 @@ namespace { out << "\">\n"; out << "<name>" << acct->name << "</name>\n"; + out << "<fullname>" << acct->fullname() << "</fullname>\n"; value_t total = acct->amount(); if (! total.is_null()) { out << "<amount>\n"; @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are |