diff options
Diffstat (limited to 'src')
49 files changed, 923 insertions, 528 deletions
diff --git a/src/account.cc b/src/account.cc index e6c7af56..f8729409 100644 --- a/src/account.cc +++ b/src/account.cc @@ -512,7 +512,7 @@ void account_t::xdata_t::details_t::update(post_t& post, if (post.has_flags(POST_VIRTUAL)) posts_virtuals_count++; - if (gather_all) + if (gather_all && post.pos) filenames.insert(post.pos->pathname); date_t date = post.date(); diff --git a/src/amount.cc b/src/amount.cc index 82b93931..eddbca18 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -106,8 +106,6 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; -shared_ptr<commodity_pool_t> amount_t::current_pool; - bool amount_t::is_initialized = false; namespace { @@ -203,7 +201,7 @@ namespace { } } -void amount_t::initialize(shared_ptr<commodity_pool_t> pool) +void amount_t::initialize() { if (! is_initialized) { mpz_init(temp); @@ -211,26 +209,35 @@ void amount_t::initialize(shared_ptr<commodity_pool_t> pool) mpfr_init(tempf); mpfr_init(tempfb); + commodity_pool_t::current_pool.reset(new commodity_pool_t); + + // Add time commodity conversions, so that timelog's may be parsed + // in terms of seconds, but reported as minutes or hours. + if (commodity_t * commodity = commodity_pool_t::current_pool->create("s")) + commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); + else + assert(false); + + // Add a "percentile" commodity + if (commodity_t * commodity = commodity_pool_t::current_pool->create("%")) + commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); + else + assert(false); + is_initialized = true; } - current_pool = pool; -} - -void amount_t::initialize() -{ - initialize(shared_ptr<commodity_pool_t>(new commodity_pool_t)); } void amount_t::shutdown() { - current_pool.reset(); - if (is_initialized) { mpz_clear(temp); mpq_clear(tempq); mpfr_clear(tempf); mpfr_clear(tempfb); + commodity_pool_t::current_pool.reset(); + is_initialized = false; } } @@ -670,7 +677,7 @@ amount_t::value(const bool primary_only, if (in_terms_of && commodity() == *in_terms_of) { return *this; } - else if (is_annotated() && annotation().price && + else if (has_annotation() && annotation().price && annotation().has_flags(ANNOTATION_PRICE_FIXATED)) { return (*annotation().price * number()).rounded(); } @@ -696,7 +703,7 @@ amount_t::value(const bool primary_only, amount_t amount_t::price() const { - if (is_annotated() && annotation().price) { + if (has_annotation() && annotation().price) { amount_t temp(*annotation().price); temp *= *this; DEBUG("amount.price", "Returning price of " << *this << " = " << temp); @@ -776,7 +783,8 @@ bool amount_t::fits_in_long() const commodity_t& amount_t::commodity() const { - return has_commodity() ? *commodity_ : *current_pool->null_commodity; + return (has_commodity() ? + *commodity_ : *commodity_pool_t::current_pool->null_commodity); } bool amount_t::has_commodity() const @@ -794,7 +802,7 @@ void amount_t::annotate(const annotation_t& details) else if (! has_commodity()) return; // ignore attempt to annotate a "bare commodity - if (commodity().is_annotated()) { + if (commodity().has_annotation()) { this_ann = &as_annotated_commodity(commodity()); this_base = &this_ann->referent(); } else { @@ -816,15 +824,15 @@ void amount_t::annotate(const annotation_t& details) DEBUG("amounts.commodities", "Annotated amount is " << *this); } -bool amount_t::is_annotated() const +bool amount_t::has_annotation() const { if (! quantity) throw_(amount_error, _("Cannot determine if an uninitialized amount's commodity is annotated")); - assert(! has_commodity() || ! commodity().is_annotated() || + assert(! has_commodity() || ! commodity().has_annotation() || as_annotated_commodity(commodity()).details); - return has_commodity() && commodity().is_annotated(); + return has_commodity() && commodity().has_annotation(); } annotation_t& amount_t::annotation() @@ -833,7 +841,7 @@ annotation_t& amount_t::annotation() throw_(amount_error, _("Cannot return commodity annotation details of an uninitialized amount")); - if (! commodity().is_annotated()) + if (! commodity().has_annotation()) throw_(amount_error, _("Request for annotation details from an unannotated amount")); @@ -963,15 +971,16 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (symbol.empty()) { commodity_ = NULL; } else { - commodity_ = current_pool->find(symbol); + commodity_ = commodity_pool_t::current_pool->find(symbol); if (! commodity_) { - commodity_ = current_pool->create(symbol); + commodity_ = commodity_pool_t::current_pool->create(symbol); newly_created = true; } assert(commodity_); if (details) - commodity_ = current_pool->find_or_create(*commodity_, 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 @@ -1206,7 +1215,6 @@ void to_xml(std::ostream& out, const amount_t& amt, bool commodity_details) template<class Archive> void amount_t::serialize(Archive& ar, const unsigned int /* version */) { - ar & current_pool; ar & is_initialized; ar & quantity; ar & commodity_; diff --git a/src/amount.h b/src/amount.h index c75370e3..a37efdb8 100644 --- a/src/amount.h +++ b/src/amount.h @@ -97,12 +97,8 @@ class amount_t ordered_field_operators<amount_t, long> > > > { public: - /** Indicates which commodity pool should be used. */ - static shared_ptr<commodity_pool_t> current_pool; - /** Ready the amount subsystem for use. @note Normally called by session_t::initialize(). */ - static void initialize(shared_ptr<commodity_pool_t> pool); static void initialize(); /** Shutdown the amount subsystem and free all resources. @note Normally called by session_t::shutdown(). */ @@ -500,6 +496,9 @@ public: long to_long() const; bool fits_in_long() const; + operator string() const { + return to_string(); + } string to_string() const; string to_fullstring() const; string quantity_string() const; @@ -577,7 +576,7 @@ public: been stripped. */ void annotate(const annotation_t& details); - bool is_annotated() const; + bool has_annotation() const; annotation_t& annotation(); const annotation_t& annotation() const { diff --git a/src/annotate.cc b/src/annotate.cc index bd5a8ef8..146a7afd 100644 --- a/src/annotate.cc +++ b/src/annotate.cc @@ -135,13 +135,13 @@ void annotation_t::print(std::ostream& out, bool keep_base) const bool keep_details_t::keep_all(const commodity_t& comm) const { - return (! comm.is_annotated() || + return (! comm.has_annotation() || (keep_price && keep_date && keep_tag && ! only_actuals)); } bool keep_details_t::keep_any(const commodity_t& comm) const { - return comm.is_annotated() && (keep_price || keep_date || keep_tag); + return comm.has_annotation() && (keep_price || keep_date || keep_tag); } bool annotated_commodity_t::operator==(const commodity_t& comm) const diff --git a/src/balance.cc b/src/balance.cc index 59eb4d92..4fcc83fa 100644 --- a/src/balance.cc +++ b/src/balance.cc @@ -43,21 +43,21 @@ balance_t::balance_t(const double val) { TRACE_CTOR(balance_t, "const double"); amounts.insert - (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); } balance_t::balance_t(const unsigned long val) { TRACE_CTOR(balance_t, "const unsigned long"); amounts.insert - (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); } balance_t::balance_t(const long val) { TRACE_CTOR(balance_t, "const long"); amounts.insert - (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); } balance_t& balance_t::operator+=(const balance_t& bal) @@ -271,7 +271,8 @@ void balance_t::print(std::ostream& out, if (pair.second) sorted.push_back(&pair.second); - std::stable_sort(sorted.begin(), sorted.end(), compare_amount_commodities()); + std::stable_sort(sorted.begin(), sorted.end(), + commodity_t::compare_by_commodity()); foreach (const amount_t * amount, sorted) { int width; diff --git a/src/balance.h b/src/balance.h index 8a40dea9..826de134 100644 --- a/src/balance.h +++ b/src/balance.h @@ -461,6 +461,15 @@ public: * Conversion methods. A balance can be converted to an amount, but * only if contains a single component amount. */ + operator string() const { + return to_string(); + } + string to_string() const { + std::ostringstream buf; + print(buf); + return buf.str(); + } + amount_t to_amount() const { if (is_empty()) throw_(balance_error, _("Cannot convert an empty balance to an amount")); @@ -532,8 +541,8 @@ public: void print(std::ostream& out, const int first_width = -1, const int latter_width = -1, - const bool right_justify = true, - const bool colorize = true) const; + const bool right_justify = false, + const bool colorize = false) const; /** * Debugging methods. There are two methods defined to help with diff --git a/src/chain.cc b/src/chain.cc index 113a71d8..ecb39e0b 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -158,11 +158,21 @@ post_handler_ptr chain_post_handlers(report_t& report, report.session.journal->master, report.HANDLER(date_).str(), report)); - if (report.HANDLED(account_)) + + if (report.HANDLED(account_)) { handler.reset(new transfer_details(handler, transfer_details::SET_ACCOUNT, report.session.journal->master, report.HANDLER(account_).str(), report)); + } + else if (report.HANDLED(pivot_)) { + string pivot = report.HANDLER(pivot_).str(); + pivot = string("\"") + pivot + ":\" + tag(/" + pivot + "/)"; + handler.reset(new transfer_details(handler, transfer_details::SET_ACCOUNT, + report.session.journal->master, pivot, + report)); + } + if (report.HANDLED(payee_)) handler.reset(new transfer_details(handler, transfer_details::SET_PAYEE, report.session.journal->master, diff --git a/src/commodity.cc b/src/commodity.cc index b76c7896..24016830 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -587,8 +587,8 @@ bool commodity_t::valid() const return true; } -bool compare_amount_commodities::operator()(const amount_t * left, - const amount_t * right) const +bool commodity_t::compare_by_commodity::operator()(const amount_t * left, + const amount_t * right) const { commodity_t& leftcomm(left->commodity()); commodity_t& rightcomm(right->commodity()); @@ -600,11 +600,11 @@ bool compare_amount_commodities::operator()(const amount_t * left, if (cmp != 0) return cmp < 0; - if (! leftcomm.is_annotated()) { - return rightcomm.is_annotated(); + if (! leftcomm.has_annotation()) { + return rightcomm.has_annotation(); } - else if (! rightcomm.is_annotated()) { - return ! leftcomm.is_annotated(); + else if (! rightcomm.has_annotation()) { + return ! leftcomm.has_annotation(); } else { annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm)); @@ -675,7 +675,7 @@ void to_xml(std::ostream& out, const commodity_t& comm, } if (commodity_details) { - if (comm.is_annotated()) + if (comm.has_annotation()) to_xml(out, as_annotated_commodity(comm).details); if (comm.varied_history()) { diff --git a/src/commodity.h b/src/commodity.h index 42cc6d8f..d2d8af21 100644 --- a/src/commodity.h +++ b/src/commodity.h @@ -185,7 +185,7 @@ protected: symbol(_symbol), precision(0), searched(false) { TRACE_CTOR(base_t, "const string&"); } - ~base_t() { + virtual ~base_t() { TRACE_DTOR(base_t); } @@ -251,7 +251,7 @@ public: return *this; } - bool is_annotated() const { + bool has_annotation() const { return annotated; } @@ -386,6 +386,10 @@ public: bool valid() const; + struct compare_by_commodity { + bool operator()(const amount_t * left, const amount_t * right) const; + }; + #if defined(HAVE_BOOST_SERIALIZATION) private: supports_flags<uint_least16_t> temp_flags; @@ -419,10 +423,6 @@ inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { return out; } -struct compare_amount_commodities { - bool operator()(const amount_t * left, const amount_t * right) const; -}; - void to_xml(std::ostream& out, const commodity_t& comm, bool commodity_details = false); diff --git a/src/draft.cc b/src/draft.cc index 8478a31d..fc53e956 100644 --- a/src/draft.cc +++ b/src/draft.cc @@ -458,6 +458,9 @@ xact_t * draft_t::insert(journal_t& journal) *post.cost *= new_post->amount; post.cost->set_commodity(cost_commodity); } + else if (new_post->amount.sign() < 0) { + new_post->cost->in_place_negate(); + } new_post->cost = *post.cost; DEBUG("derive.xact", "Copied over posting cost"); diff --git a/src/draft.h b/src/draft.h index 003dcefa..93e98ff5 100644 --- a/src/draft.h +++ b/src/draft.h @@ -86,7 +86,7 @@ public: if (! args.empty()) parse_args(args); } - ~draft_t() { + virtual ~draft_t() { TRACE_DTOR(draft_t); } @@ -87,7 +87,7 @@ public: parse(in, flags); } - ~expr_t() throw() { + virtual ~expr_t() { TRACE_DTOR(expr_t); } diff --git a/src/exprbase.h b/src/exprbase.h index d2bf5a6d..0b3466b0 100644 --- a/src/exprbase.h +++ b/src/exprbase.h @@ -92,8 +92,7 @@ public: { TRACE_CTOR(expr_base_t, "scope_t *"); } - - ~expr_base_t() throw() { + virtual ~expr_base_t() { TRACE_DTOR(expr_base_t); } diff --git a/src/filters.cc b/src/filters.cc index 39097c58..2926eb08 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -430,12 +430,22 @@ changed_value_posts::changed_value_posts(post_handler_ptr handler, display_total_expr = report.HANDLER(display_total_).expr; changed_values_only = report.HANDLED(revalued_only); + string gains_equity_account_name; + if (report.HANDLED(unrealized_gains_)) + gains_equity_account_name = report.HANDLER(unrealized_gains_).str(); + else + gains_equity_account_name = _("Equity:Unrealized Gains"); gains_equity_account = - report.session.journal->master->find_account(_("Equity:Unrealized Gains")); + report.session.journal->master->find_account(gains_equity_account_name); gains_equity_account->add_flags(ACCOUNT_GENERATED); + string losses_equity_account_name; + if (report.HANDLED(unrealized_losses_)) + losses_equity_account_name = report.HANDLER(unrealized_losses_).str(); + else + losses_equity_account_name = _("Equity:Unrealized Losses"); losses_equity_account = - report.session.journal->master->find_account(_("Equity:Unrealized Losses")); + report.session.journal->master->find_account(losses_equity_account_name); losses_equity_account->add_flags(ACCOUNT_GENERATED); } @@ -783,12 +793,25 @@ void transfer_details::operator()(post_t& post) break; case SET_ACCOUNT: { - std::list<string> account_names; - temp.account->remove_post(&temp); - split_string(substitute.to_string(), ':', account_names); - temp.account = create_temp_account_from_path(account_names, temps, - xact.journal->master); - temp.account->add_post(&temp); + string account_name = substitute.to_string(); + if (! account_name.empty() && + account_name[account_name.length() - 1] != ':') { + account_t * prev_account = temp.account; + temp.account->remove_post(&temp); + + account_name += ':'; + account_name += prev_account->fullname(); + + std::list<string> account_names; + split_string(account_name, ':', account_names); + temp.account = create_temp_account_from_path(account_names, temps, + xact.journal->master); + temp.account->add_post(&temp); + + temp.account->add_flags(prev_account->flags()); + if (prev_account->has_xdata()) + temp.account->xdata().add_flags(prev_account->xdata().flags()); + } break; } diff --git a/src/format.h b/src/format.h index fc1272aa..a2bf1015 100644 --- a/src/format.h +++ b/src/format.h @@ -132,7 +132,7 @@ public: if (! _str.empty()) parse_format(_str); } - ~format_t() { + virtual ~format_t() { TRACE_DTOR(format_t); } diff --git a/src/item.cc b/src/item.cc index da6429ed..8d1ba34f 100644 --- a/src/item.cc +++ b/src/item.cc @@ -115,7 +115,8 @@ void item_t::set_tag(const string& tag, assert(result.second); } -void item_t::parse_tags(const char * p, optional<date_t::year_type> current_year) +void item_t::parse_tags(const char * p, + optional<date_t::year_type> current_year) { if (const char * b = std::strchr(p, '[')) { if (*(b + 1) != '\0' && @@ -164,7 +165,8 @@ void item_t::parse_tags(const char * p, optional<date_t::year_type> current_year } } -void item_t::append_note(const char * p, optional<date_t::year_type> current_year) +void item_t::append_note(const char * p, + optional<date_t::year_type> current_year) { if (note) { *note += '\n'; @@ -197,6 +199,9 @@ 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_effective_date(item_t& item) { if (optional<date_t> effective = item.effective_date()) return *effective; @@ -236,11 +241,39 @@ namespace { return false; } - value_t get_tag(call_scope_t& scope) { - in_context_t<item_t> env(scope, "s"); - if (optional<string> value = env->get_tag(env.get<string>(0))) - return string_value(*value); - return string_value(empty_string); + value_t get_tag(call_scope_t& args) { + item_t& item(find_scope<item_t>(args)); + optional<string> str; + + if (args.size() == 1) { + if (args[0].is_string()) + str = item.get_tag(args[0].as_string()); + else if (args[0].is_mask()) + str = item.get_tag(args[0].as_mask()); + else + throw_(std::runtime_error, + _("Expected string or mask for argument 1, but received %1") + << args[0].label()); + } + else if (args.size() == 2) { + if (args[0].is_mask() && args[1].is_mask()) + str = item.get_tag(args[0].to_mask(), args[1].to_mask()); + else + throw_(std::runtime_error, + _("Expected masks for arguments 1 and 2, but received %1 and %2") + << args[0].label() << args[1].label()); + } + else if (args.size() == 0) { + throw_(std::runtime_error, _("Too few arguments to function")); + } + else { + throw_(std::runtime_error, _("Too many arguments to function")); + } + + if (str) + return string_value(*str); + else + return string_value(empty_string); } value_t get_pathname(item_t& item) { @@ -266,6 +299,10 @@ namespace { return item.pos ? long(item.pos->end_line) : 0L; } + value_t get_seq(item_t& item) { + return item.pos ? long(item.pos->sequence) : 0L; + } + value_t get_depth(item_t&) { return 0L; } @@ -317,6 +354,8 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind, case 'a': if (name == "actual") return WRAP_FUNCTOR(get_wrapper<&get_actual>); + else if (name == "actual_date") + return WRAP_FUNCTOR(get_wrapper<&get_actual_date>); break; case 'b': @@ -386,6 +425,8 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind, case 's': if (name == "status") return WRAP_FUNCTOR(get_wrapper<&get_status>); + else if (name == "seq") + return WRAP_FUNCTOR(get_wrapper<&get_seq>); break; case 't': @@ -435,9 +476,12 @@ void print_item(std::ostream& out, const item_t& item, const string& prefix) string item_context(const item_t& item, const string& desc) { + if (! item.pos) + return empty_string; + std::streamoff len = item.pos->end_pos - item.pos->beg_pos; if (! len) - return _("<no item context>"); + return empty_string; assert(len > 0); assert(len < 2048); @@ -53,8 +53,10 @@ struct position_t std::size_t beg_line; istream_pos_type end_pos; std::size_t end_line; + std::size_t sequence; - position_t() : beg_pos(0), beg_line(0), end_pos(0), end_line(0) { + position_t() + : beg_pos(0), beg_line(0), end_pos(0), end_line(0), sequence(0) { TRACE_CTOR(position_t, ""); } position_t(const position_t& pos) { @@ -72,6 +74,7 @@ struct position_t beg_line = pos.beg_line; end_pos = pos.end_pos; end_line = pos.end_line; + sequence = pos.sequence; } return *this; } @@ -89,6 +92,7 @@ private: ar & beg_line; ar & end_pos; ar & end_line; + ar & sequence; } #endif // HAVE_BOOST_SERIALIZATION }; @@ -169,6 +173,10 @@ public: return *effective; return *_date; } + virtual date_t actual_date() const { + assert(_date); + return *_date; + } virtual optional<date_t> effective_date() const { return _date_eff; } diff --git a/src/journal.cc b/src/journal.cc index 2366ce30..5aa2e7f7 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -64,9 +64,8 @@ journal_t::~journal_t() { TRACE_DTOR(journal_t); - // Don't bother unhooking each xact's posts from the - // accounts they refer to, because all accounts are about to - // be deleted. + // Don't bother unhooking each xact's posts from the accounts they refer to, + // because all accounts are about to be deleted. foreach (xact_t * xact, xacts) checked_delete(xact); @@ -77,7 +76,6 @@ journal_t::~journal_t() checked_delete(xact); checked_delete(master); - commodity_pool.reset(); } void journal_t::initialize() @@ -85,21 +83,6 @@ void journal_t::initialize() master = new account_t; bucket = NULL; was_loaded = false; - - commodity_pool.reset(new commodity_pool_t); - - // Add time commodity conversions, so that timelog's may be parsed - // in terms of seconds, but reported as minutes or hours. - if (commodity_t * commodity = commodity_pool->create("s")) - commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); - else - assert(false); - - // Add a "percentile" commodity - if (commodity_t * commodity = commodity_pool->create("%")) - commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); - else - assert(false); } void journal_t::add_account(account_t * acct) diff --git a/src/journal.h b/src/journal.h index f7124736..8d59e3b4 100644 --- a/src/journal.h +++ b/src/journal.h @@ -47,7 +47,6 @@ namespace ledger { -class commodity_pool_t; class xact_base_t; class xact_t; class auto_xact_t; @@ -112,8 +111,6 @@ public: std::list<fileinfo_t> sources; bool was_loaded; - shared_ptr<commodity_pool_t> commodity_pool; - journal_t(); journal_t(const path& pathname); journal_t(const string& str); @@ -624,7 +624,7 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const } if (! symbol.empty()) { - if (amount_t::current_pool->find(symbol)) + if (commodity_pool_t::current_pool->find(symbol)) out << '@'; out << symbol; } diff --git a/src/output.cc b/src/output.cc index 71ec6d88..bb7eff5c 100644 --- a/src/output.cc +++ b/src/output.cc @@ -152,13 +152,12 @@ format_accounts::format_accounts(report_t& _report, std::size_t format_accounts::post_account(account_t& account, const bool flat) { + if (! flat && account.parent) + post_account(*account.parent, flat); + if (account.xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) && ! account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) { - if (! flat && account.parent && - account.parent->xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) && - ! account.parent->xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) - post_account(*account.parent, flat); - + DEBUG("account.display", "Displaying account: " << account.fullname()); account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED); bind_scope_t bound_scope(report, account); diff --git a/src/pool.cc b/src/pool.cc index b08c8fad..f895a8bc 100644 --- a/src/pool.cc +++ b/src/pool.cc @@ -39,6 +39,8 @@ namespace ledger { +shared_ptr<commodity_pool_t> commodity_pool_t::current_pool; + commodity_pool_t::commodity_pool_t() : default_commodity(NULL), keep_base(false), quote_leeway(86400), get_quotes(false), @@ -245,7 +247,7 @@ commodity_pool_t::exchange(const amount_t& amount, current_annotation = &as_annotated_commodity(commodity).details; amount_t per_unit_cost = - (is_per_unit || amount.is_realzero() ? cost : cost / amount).abs(); + (is_per_unit || amount.is_realzero()) ? cost.abs() : (cost / amount).abs(); DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost); @@ -253,7 +255,7 @@ commodity_pool_t::exchange(const amount_t& amount, exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME()); cost_breakdown_t breakdown; - breakdown.final_cost = ! is_per_unit ? cost : cost * amount; + breakdown.final_cost = ! is_per_unit ? cost : cost * amount.abs(); DEBUG("commodity.prices.add", "exchange: final-cost = " << breakdown.final_cost); @@ -284,7 +286,8 @@ commodity_pool_t::exchange(const amount_t& amount, return breakdown; } -optional<price_point_t> commodity_pool_t::parse_price_directive(char * line) +optional<std::pair<commodity_t *, price_point_t> > +commodity_pool_t::parse_price_directive(char * line, bool do_not_add_price) { char * date_field_ptr = line; char * time_field_ptr = next_element(date_field_ptr); @@ -293,6 +296,7 @@ optional<price_point_t> commodity_pool_t::parse_price_directive(char * line) char * symbol_and_price; datetime_t datetime; + string symbol; if (std::isdigit(time_field_ptr[0])) { symbol_and_price = next_element(time_field_ptr); @@ -305,26 +309,27 @@ optional<price_point_t> commodity_pool_t::parse_price_directive(char * line) datetime = datetime_t(parse_date(date_field)); } else { - symbol_and_price = date_field_ptr; + symbol = date_field_ptr; + symbol_and_price = time_field_ptr; datetime = CURRENT_TIME(); } - string symbol; - commodity_t::parse_symbol(symbol_and_price, symbol); + if (symbol.empty()) + commodity_t::parse_symbol(symbol_and_price, symbol); price_point_t point; point.when = datetime; - point.price.parse(symbol_and_price); + point.price.parse(symbol_and_price, PARSE_NO_MIGRATE); VERIFY(point.price.valid()); DEBUG("commodity.download", "Looking up symbol: " << symbol); - if (commodity_t * commodity = - amount_t::current_pool->find_or_create(symbol)) { + if (commodity_t * commodity = find_or_create(symbol)) { DEBUG("commodity.download", "Adding price for " << symbol << ": " << point.when << " " << point.price); - commodity->add_price(point.when, point.price, true); + if (! do_not_add_price) + commodity->add_price(point.when, point.price, true); commodity->add_flags(COMMODITY_KNOWN); - return point; + return std::pair<commodity_t *, price_point_t>(commodity, point); } return none; @@ -57,6 +57,7 @@ struct cost_breakdown_t class commodity_pool_t : public noncopyable { +public: /** * The commodities collection in commodity_pool_t maintains pointers to all * the commodities which have ever been created by the user, whether @@ -65,7 +66,6 @@ class commodity_pool_t : public noncopyable */ typedef std::map<string, commodity_t *> commodities_map; -public: commodities_map commodities; commodity_t * null_commodity; commodity_t * default_commodity; @@ -76,13 +76,15 @@ public: long quote_leeway; // --leeway= bool get_quotes; // --download + static shared_ptr<commodity_pool_t> current_pool; + function<optional<price_point_t> (commodity_t& commodity, const optional<commodity_t&>& in_terms_of)> get_commodity_quote; explicit commodity_pool_t(); - ~commodity_pool_t() { + virtual ~commodity_pool_t() { TRACE_DTOR(commodity_pool_t); foreach (commodities_map::value_type pair, commodities) checked_delete(pair.second); @@ -121,7 +123,8 @@ public: // Parse commodity prices from a textual representation - optional<price_point_t> parse_price_directive(char * line); + optional<std::pair<commodity_t *, price_point_t> > + parse_price_directive(char * line, bool do_not_add_price = false); commodity_t * parse_price_expression(const std::string& str, @@ -136,6 +139,7 @@ private: template<class Archive> void serialize(Archive& ar, const unsigned int /* version */) { + ar & current_pool; ar & commodities; ar & null_commodity; ar & default_commodity; diff --git a/src/post.cc b/src/post.cc index 34284e1b..43cfe55f 100644 --- a/src/post.cc +++ b/src/post.cc @@ -97,6 +97,18 @@ date_t post_t::date() const return *_date; } +date_t post_t::actual_date() const +{ + if (xdata_ && is_valid(xdata_->date)) + return xdata_->date; + + if (! _date) { + assert(xact); + return xact->date(); + } + return *_date; +} + optional<date_t> post_t::effective_date() const { optional<date_t> date = item_t::effective_date(); @@ -405,6 +417,20 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, return item_t::lookup(kind, name); } +amount_t post_t::resolve_expr(scope_t& scope, expr_t& expr) +{ + bind_scope_t bound_scope(scope, *this); + value_t result(expr.calc(bound_scope)); + if (result.is_long()) { + return result.to_amount(); + } else { + if (! result.is_amount()) + throw_(amount_error, + _("Amount expressions must result in a simple amount")); + return result.as_amount(); + } +} + bool post_t::valid() const { if (! xact) { @@ -105,6 +105,7 @@ public: const optional<mask_t>& value_mask = none) const; virtual date_t date() const; + virtual date_t actual_date() const; virtual optional<date_t> effective_date() const; bool must_balance() const { @@ -114,6 +115,8 @@ public: virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, const string& name); + amount_t resolve_expr(scope_t& scope, expr_t& expr); + bool valid() const; struct xdata_t : public supports_flags<uint_least16_t> @@ -198,6 +201,20 @@ public: friend class xact_t; + struct compare_by_date_and_sequence + { + bool operator()(const post_t * left, const post_t * right) const { + gregorian::date_duration duration = + left->actual_date() - right->actual_date(); + if (duration.days() == 0) { + return ((left->pos ? left->pos->sequence : 0) < + (right->pos ? right->pos->sequence : 0)); + } else { + return duration.days() < 0; + } + } + }; + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ diff --git a/src/precmd.cc b/src/precmd.cc index 31249016..632caeae 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -174,7 +174,7 @@ value_t period_command(call_scope_t& args) return NULL_VALUE; } -value_t args_command(call_scope_t& args) +value_t query_command(call_scope_t& args) { report_t& report(find_scope<report_t>(args)); std::ostream& out(report.output_stream); diff --git a/src/precmd.h b/src/precmd.h index e0f81cf8..88d66ab2 100644 --- a/src/precmd.h +++ b/src/precmd.h @@ -52,7 +52,7 @@ value_t parse_command(call_scope_t& args); value_t eval_command(call_scope_t& args); value_t format_command(call_scope_t& args); value_t period_command(call_scope_t& args); -value_t args_command(call_scope_t& args); +value_t query_command(call_scope_t& args); } // namespace ledger diff --git a/src/predicate.h b/src/predicate.h index 943b5a12..b3b81f9b 100644 --- a/src/predicate.h +++ b/src/predicate.h @@ -75,7 +75,7 @@ public: : expr_t(in, flags), what_to_keep(_what_to_keep) { TRACE_CTOR(predicate_t, "std::istream&, keep_details_t, parse_flags_t"); } - ~predicate_t() throw() { + virtual ~predicate_t() { TRACE_DTOR(predicate_t); } diff --git a/src/py_account.cc b/src/py_account.cc index d1d35cda..056cd722 100644 --- a/src/py_account.cc +++ b/src/py_account.cc @@ -32,6 +32,7 @@ #include <system.hh> #include "pyinterp.h" +#include "pyutils.h" #include "account.h" #include "post.h" @@ -90,6 +91,10 @@ namespace { return account.xdata(); } + PyObject * py_account_unicode(account_t& account) { + return str_to_py_unicode(account.fullname()); + } + } // unnamed namespace void export_account() @@ -180,7 +185,8 @@ void export_account() .def_readwrite("note", &account_t::note) .def_readonly("depth", &account_t::depth) - .def(self_ns::str(self)) + .def("__str__", &account_t::fullname) + .def("__unicode__", py_account_unicode) .def("fullname", &account_t::fullname) .def("partial_name", &account_t::partial_name) diff --git a/src/py_amount.cc b/src/py_amount.cc index b44f3716..8fb507a3 100644 --- a/src/py_amount.cc +++ b/src/py_amount.cc @@ -45,26 +45,16 @@ using namespace boost::python; namespace { boost::optional<amount_t> py_value_0(const amount_t& amount) { - return amount.value(); + return amount.value(false, CURRENT_TIME()); } boost::optional<amount_t> py_value_1(const amount_t& amount, - const bool primary_only) { - return amount.value(primary_only); + commodity_t& in_terms_of) { + return amount.value(false, CURRENT_TIME(), in_terms_of); } - - boost::optional<amount_t> - py_value_2(const amount_t& amount, - const bool primary_only, - const boost::optional<datetime_t>& moment) { - return amount.value(primary_only, moment); - } - - boost::optional<amount_t> - py_value_3(const amount_t& amount, - const bool primary_only, - const boost::optional<datetime_t>& moment, - const boost::optional<commodity_t&>& in_terms_of) { - return amount.value(primary_only, moment, in_terms_of); + boost::optional<amount_t> py_value_2(const amount_t& amount, + commodity_t& in_terms_of, + datetime_t& moment) { + return amount.value(false, moment, in_terms_of); } void py_parse_2(amount_t& amount, object in, unsigned char flags) { @@ -97,8 +87,19 @@ namespace { } } - void py_amount_initialize() { - amount_t::initialize(); + annotation_t& py_amount_annotation(amount_t& amount) { + return amount.annotation(); + } + + amount_t py_strip_annotations_0(amount_t& amount) { + return amount.strip_annotations(keep_details_t()); + } + amount_t py_strip_annotations_1(amount_t& amount, const keep_details_t& keep) { + return amount.strip_annotations(keep); + } + + PyObject * py_amount_unicode(amount_t& amount) { + return str_to_py_unicode(amount.to_string()); } } // unnamed namespace @@ -113,11 +114,7 @@ EXC_TRANSLATOR(amount_error) void export_amount() { class_< amount_t > ("Amount") - .add_static_property("current_pool", - make_getter(&amount_t::current_pool, - return_internal_reference<>())) - - .def("initialize", py_amount_initialize) // only for the PyUnitTests + .def("initialize", &amount_t::initialize) // only for the PyUnitTests .staticmethod("initialize") .def("shutdown", &amount_t::shutdown) .staticmethod("shutdown") @@ -196,11 +193,11 @@ internal precision.")) .def(self / long()) .def(long() / self) - .def("precision", &amount_t::precision) - .def("keep_precision", &amount_t::keep_precision) - .def("set_keep_precision", &amount_t::set_keep_precision, args("keep"), - _("Set whether an amount should always display at full precision.")) - .def("display_precision", &amount_t::display_precision) + .add_property("precision", &amount_t::precision) + .add_property("display_precision", &amount_t::display_precision) + .add_property("keep_precision", + &amount_t::keep_precision, + &amount_t::set_keep_precision) .def("negated", &amount_t::negated) .def("in_place_negate", &amount_t::in_place_negate, @@ -237,9 +234,8 @@ internal precision.")) return_internal_reference<>()) .def("value", py_value_0) - .def("value", py_value_1, args("primary_only")) - .def("value", py_value_2, args("primary_only", "moment")) - .def("value", py_value_3, args("primary_only", "moment", "in_terms_of")) + .def("value", py_value_1, args("in_terms_of")) + .def("value", py_value_2, args("in_terms_of", "moment")) .def("price", &amount_t::price) @@ -256,27 +252,30 @@ internal precision.")) .def("__int__", &amount_t::to_long) .def("fits_in_long", &amount_t::fits_in_long) - .def("to_string", &amount_t::to_string) .def("__str__", &amount_t::to_string) + .def("to_string", &amount_t::to_string) + .def("__unicode__", py_amount_unicode) .def("to_fullstring", &amount_t::to_fullstring) .def("__repr__", &amount_t::to_fullstring) .def("quantity_string", &amount_t::quantity_string) - .def("commodity", &amount_t::commodity, - return_internal_reference<>()) + .add_property("commodity", + make_function(&amount_t::commodity, + return_value_policy<reference_existing_object>()), + make_function(&amount_t::set_commodity, + with_custodian_and_ward<1, 2>())) .def("has_commodity", &amount_t::has_commodity) - .def("set_commodity", &amount_t::set_commodity, - with_custodian_and_ward<1, 2>()) .def("clear_commodity", &amount_t::clear_commodity) .def("number", &amount_t::number) .def("annotate", &amount_t::annotate) - .def("is_annotated", &amount_t::is_annotated) -#if 0 - .def("annotation", &amount_t::annotation) -#endif - .def("strip_annotations", &amount_t::strip_annotations) + .def("has_annotation", &amount_t::has_annotation) + .add_property("annotation", + make_function(py_amount_annotation, + return_internal_reference<>())) + .def("strip_annotations", py_strip_annotations_0) + .def("strip_annotations", py_strip_annotations_1) .def("parse", py_parse_1) .def("parse", py_parse_2) diff --git a/src/py_balance.cc b/src/py_balance.cc index 23a2ff73..8c0c4c58 100644 --- a/src/py_balance.cc +++ b/src/py_balance.cc @@ -45,28 +45,18 @@ using namespace boost::python; namespace { boost::optional<balance_t> py_value_0(const balance_t& balance) { - return balance.value(); + return balance.value(false, CURRENT_TIME()); } boost::optional<balance_t> py_value_1(const balance_t& balance, - const bool primary_only) { - return balance.value(primary_only); + commodity_t& in_terms_of) { + return balance.value(false, CURRENT_TIME(), in_terms_of); } - - boost::optional<balance_t> - py_value_2(const balance_t& balance, - const bool primary_only, - const boost::optional<datetime_t>& moment) { - return balance.value(primary_only, moment); - } - - boost::optional<balance_t> - py_value_3(const balance_t& balance, - const bool primary_only, - const boost::optional<datetime_t>& moment, - const boost::optional<commodity_t&>& in_terms_of) { - return balance.value(primary_only, moment, in_terms_of); + boost::optional<balance_t> py_value_2(const balance_t& balance, + commodity_t& in_terms_of, + datetime_t& moment) { + return balance.value(false, moment, in_terms_of); } - + boost::optional<amount_t> py_commodity_amount_0(const balance_t& balance) { return balance.commodity_amount(); @@ -108,6 +98,17 @@ namespace { return (*elem).second; } + balance_t py_strip_annotations_0(balance_t& balance) { + return balance.strip_annotations(keep_details_t()); + } + balance_t py_strip_annotations_1(balance_t& balance, const keep_details_t& keep) { + return balance.strip_annotations(keep); + } + + PyObject * py_balance_unicode(balance_t& balance) { + return str_to_py_unicode(balance.to_string()); + } + } // unnamed namespace #define EXC_TRANSLATOR(type) \ @@ -155,7 +156,9 @@ void export_balance() .def(self != long()) .def(! self) - .def(self_ns::str(self)) + .def("__str__", &balance_t::to_string) + .def("to_string", &balance_t::to_string) + .def("__unicode__", py_balance_unicode) .def("negated", &balance_t::negated) .def("in_place_negate", &balance_t::in_place_negate, @@ -193,9 +196,8 @@ void export_balance() return_internal_reference<>()) .def("value", py_value_0) - .def("value", py_value_1, args("primary_only")) - .def("value", py_value_2, args("primary_only", "moment")) - .def("value", py_value_3, args("primary_only", "moment", "in_terms_of")) + .def("value", py_value_1, args("in_terms_of")) + .def("value", py_value_2, args("in_terms_of", "moment")) .def("price", &balance_t::price) @@ -215,7 +217,8 @@ void export_balance() .def("number", &balance_t::number) - .def("strip_annotations", &balance_t::strip_annotations) + .def("strip_annotations", py_strip_annotations_0) + .def("strip_annotations", py_strip_annotations_1) .def("valid", &balance_t::valid) ; diff --git a/src/py_commodity.cc b/src/py_commodity.cc index 08af8f62..984be5f0 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -32,6 +32,7 @@ #include <system.hh> #include "pyinterp.h" +#include "pyutils.h" #include "commodity.h" #include "annotate.h" #include "pool.h" @@ -81,6 +82,12 @@ namespace { // Exchange one commodity for another, while recording the factored price. + void py_exchange_2(commodity_pool_t& pool, + commodity_t& commodity, + const amount_t& per_unit_cost) + { + pool.exchange(commodity, per_unit_cost, CURRENT_TIME()); + } void py_exchange_3(commodity_pool_t& pool, commodity_t& commodity, const amount_t& per_unit_cost, @@ -99,6 +106,77 @@ namespace { return pool.exchange(amount, cost, is_per_unit, moment, tag); } + commodity_t * py_pool_getitem(commodity_pool_t& pool, const string& symbol) + { + commodity_pool_t::commodities_map::iterator i = + pool.commodities.find(symbol); + if (i == pool.commodities.end()) { + PyErr_SetString(PyExc_ValueError, + (string("Could not find commodity ") + symbol).c_str()); + throw boost::python::error_already_set(); + } + return (*i).second; + } + + python::list py_pool_keys(commodity_pool_t& pool) { + python::list keys; + BOOST_REVERSE_FOREACH + (const commodity_pool_t::commodities_map::value_type& pair, + pool.commodities) { + keys.insert(0, pair.first); + } + return keys; + } + + bool py_pool_contains(commodity_pool_t& pool, const string& symbol) { + return pool.commodities.find(symbol) != pool.commodities.end(); + } + + commodity_pool_t::commodities_map::iterator + py_pool_commodities_begin(commodity_pool_t& pool) { + return pool.commodities.begin(); + } + commodity_pool_t::commodities_map::iterator + py_pool_commodities_end(commodity_pool_t& pool) { + return pool.commodities.end(); + } + + typedef transform_iterator + <function<string(commodity_pool_t::commodities_map::value_type&)>, + commodity_pool_t::commodities_map::iterator> + commodities_map_firsts_iterator; + commodities_map_firsts_iterator + + py_pool_commodities_keys_begin(commodity_pool_t& pool) { + return make_transform_iterator + (pool.commodities.begin(), + bind(&commodity_pool_t::commodities_map::value_type::first, _1)); + } + commodities_map_firsts_iterator + py_pool_commodities_keys_end(commodity_pool_t& pool) { + return make_transform_iterator + (pool.commodities.end(), + bind(&commodity_pool_t::commodities_map::value_type::first, _1)); + } + + typedef transform_iterator + <function<commodity_t *(commodity_pool_t::commodities_map::value_type&)>, + commodity_pool_t::commodities_map::iterator> + commodities_map_seconds_iterator; + + commodities_map_seconds_iterator + 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)); + } + 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)); + } + void py_add_price_2(commodity_t& commodity, const datetime_t& date, const amount_t& price) { commodity.add_price(date, price); @@ -123,16 +201,50 @@ namespace { return details.keep_any(comm); } + commodity_t& py_commodity_referent(commodity_t& comm) { + return comm.referent(); + } + commodity_t& py_annotated_commodity_referent(annotated_commodity_t& comm) { + return comm.referent(); + } + + commodity_t& py_strip_annotations_0(commodity_t& comm) { + return comm.strip_annotations(keep_details_t()); + } + commodity_t& py_strip_annotations_1(commodity_t& comm, + const keep_details_t& keep) { + return comm.strip_annotations(keep); + } + + commodity_t& py_strip_ann_annotations_0(annotated_commodity_t& comm) { + return comm.strip_annotations(keep_details_t()); + } + commodity_t& py_strip_ann_annotations_1(annotated_commodity_t& comm, + const keep_details_t& keep) { + return comm.strip_annotations(keep); + } + + boost::optional<amount_t> py_price(annotation_t& ann) { + return ann.price; + } + boost::optional<amount_t> py_set_price(annotation_t& ann, + const boost::optional<amount_t>& price) { + return ann.price = price; + } + + PyObject * py_commodity_unicode(commodity_t& commodity) { + return str_to_py_unicode(commodity.symbol()); + } + } // unnamed namespace void export_commodity() { - class_< commodity_pool_t, boost::noncopyable > ("CommodityPool", no_init) + class_< commodity_pool_t, shared_ptr<commodity_pool_t>, + boost::noncopyable > ("CommodityPool", no_init) .add_property("null_commodity", make_getter(&commodity_pool_t::null_commodity, - return_internal_reference<>()), - make_setter(&commodity_pool_t::null_commodity, - with_custodian_and_ward<1, 2>())) + return_internal_reference<>())) .add_property("default_commodity", make_getter(&commodity_pool_t::default_commodity, return_internal_reference<>()), @@ -157,25 +269,46 @@ void export_commodity() .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<>()) + return_value_policy<reference_existing_object>()) .def("find_or_create", py_find_or_create_2, - return_internal_reference<>()) + return_value_policy<reference_existing_object>()) - .def("find", py_find_1, return_internal_reference<>()) - .def("find", py_find_2, return_internal_reference<>()) + .def("find", py_find_1, return_value_policy<reference_existing_object>()) + .def("find", py_find_2, return_value_policy<reference_existing_object>()) + .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("parse_price_directive", &commodity_pool_t::parse_price_directive) .def("parse_price_expression", &commodity_pool_t::parse_price_expression, - return_internal_reference<>()) + return_value_policy<reference_existing_object>()) + + .def("__getitem__", py_pool_getitem, + return_value_policy<reference_existing_object>()) + .def("keys", py_pool_keys) + .def("has_key", py_pool_contains) + .def("__contains__", py_pool_contains) + .def("__iter__", range<return_value_policy<reference_existing_object> > + (py_pool_commodities_begin, py_pool_commodities_end)) + .def("iteritems", range<return_value_policy<reference_existing_object> > + (py_pool_commodities_begin, py_pool_commodities_end)) + .def("iterkeys", range<>(py_pool_commodities_keys_begin, + py_pool_commodities_keys_end)) + .def("itervalues", range<return_value_policy<reference_existing_object> > + (py_pool_commodities_values_begin, py_pool_commodities_values_end)) ; + map_value_type_converter<commodity_pool_t::commodities_map>(); + + scope().attr("commodities") = commodity_pool_t::current_pool; + scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; @@ -202,6 +335,8 @@ void export_commodity() make_getter(&commodity_t::european_by_default), make_setter(&commodity_t::european_by_default)) + .def("__str__", &commodity_t::symbol) + .def("__unicode__", py_commodity_unicode) .def("__nonzero__", &commodity_t::operator bool) .def(self == self) @@ -209,33 +344,30 @@ void export_commodity() .def("symbol_needs_quotes", &commodity_t::symbol_needs_quotes) .staticmethod("symbol_needs_quotes") -#if 0 - .def("referent", &commodity_t::referent, - return_internal_reference<>()) -#endif + .add_property("referent", + make_function(py_commodity_referent, + return_value_policy<reference_existing_object>())) - .def("is_annotated", &commodity_t::is_annotated) - .def("strip_annotations", &commodity_t::strip_annotations, - return_internal_reference<>()) + .def("has_annotation", &commodity_t::has_annotation) + .def("strip_annotations", py_strip_annotations_0, + return_value_policy<reference_existing_object>()) + .def("strip_annotations", py_strip_annotations_1, + return_value_policy<reference_existing_object>()) .def("write_annotations", &commodity_t::write_annotations) .def("pool", &commodity_t::pool, - return_internal_reference<>()) - - .def("base_symbol", &commodity_t::base_symbol) - .def("symbol", &commodity_t::symbol) - .def("mapping_key", &commodity_t::mapping_key) - - .def("name", &commodity_t::name) - .def("set_name", &commodity_t::set_name) - .def("note", &commodity_t::note) - .def("set_note", &commodity_t::set_note) - .def("precision", &commodity_t::precision) - .def("set_precision", &commodity_t::set_precision) - .def("smaller", &commodity_t::smaller) - .def("set_smaller", &commodity_t::set_smaller) - .def("larger", &commodity_t::larger) - .def("set_larger", &commodity_t::set_larger) + return_value_policy<reference_existing_object>()) + + .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) + .add_property("precision", &commodity_t::precision, + &commodity_t::set_precision) + .add_property("smaller", &commodity_t::smaller, &commodity_t::set_smaller) + .add_property("larger", &commodity_t::larger, &commodity_t::set_larger) .def("add_price", py_add_price_2) .def("add_price", py_add_price_3) @@ -257,9 +389,7 @@ void export_commodity() .def("drop_flags", &supports_flags<>::drop_flags) #endif - .add_property("price", - make_getter(&annotation_t::price), - make_setter(&annotation_t::price)) + .add_property("price", py_price, py_set_price) .add_property("date", make_getter(&annotation_t::date), make_setter(&annotation_t::date)) @@ -306,13 +436,14 @@ void export_commodity() .def(self == self) .def(self == other<commodity_t>()) -#if 0 - .def("referent", &annotated_commodity_t::referent, - return_internal_reference<>()) -#endif + .add_property("referent", + make_function(py_annotated_commodity_referent, + return_value_policy<reference_existing_object>())) - .def("strip_annotations", &annotated_commodity_t::strip_annotations, - return_internal_reference<>()) + .def("strip_annotations", py_strip_ann_annotations_0, + return_value_policy<reference_existing_object>()) + .def("strip_annotations", py_strip_ann_annotations_1, + return_value_policy<reference_existing_object>()) .def("write_annotations", &annotated_commodity_t::write_annotations) ; } diff --git a/src/py_journal.cc b/src/py_journal.cc index 7e9f8a1b..5be9cbe1 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -52,10 +52,6 @@ namespace { return *journal.master; } - commodity_pool_t& py_commodity_pool(journal_t& journal) { - return *journal.commodity_pool; - } - long xacts_len(journal_t& journal) { return journal.xacts.size(); @@ -276,10 +272,6 @@ void export_journal() with_custodian_and_ward_postcall<1, 0> >()), make_setter(&journal_t::bucket)) .add_property("was_loaded", make_getter(&journal_t::was_loaded)) - .add_property("commodity_pool", - make_getter(&journal_t::commodity_pool, - return_internal_reference<1, - with_custodian_and_ward_postcall<1, 0> >())) .def("add_account", &journal_t::add_account) .def("remove_account", &journal_t::remove_account) diff --git a/src/py_utils.cc b/src/py_utils.cc index 2736ed3e..5203599f 100644 --- a/src/py_utils.cc +++ b/src/py_utils.cc @@ -79,15 +79,8 @@ struct string_to_python { static PyObject* convert(const string& str) { -#if 1 - // Return a Unicode object - PyObject * pstr = PyString_FromString(str.c_str()); - PyObject * uni = PyUnicode_FromEncodedObject(pstr, "UTF-8", NULL); - return object(handle<>(borrowed(uni))).ptr(); -#else - // Return a 7-bit ASCII string + // Return bytes, not characters; see __unicode__ methods for that return incref(object(static_cast<const std::string&>(str)).ptr()); -#endif } }; diff --git a/src/py_value.cc b/src/py_value.cc index ee039519..713dc3d4 100644 --- a/src/py_value.cc +++ b/src/py_value.cc @@ -47,7 +47,21 @@ BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(set_string_overloads, set_string, 0, 2) namespace { - PyObject * py_base_type(value_t& value) { + boost::optional<value_t> py_value_0(const value_t& value) { + return value.value(false, CURRENT_TIME()); + } + boost::optional<value_t> py_value_1(const value_t& value, + commodity_t& in_terms_of) { + return value.value(false, CURRENT_TIME(), in_terms_of); + } + boost::optional<value_t> py_value_2(const value_t& value, + commodity_t& in_terms_of, + datetime_t& moment) { + return value.value(false, moment, in_terms_of); + } + + PyObject * py_base_type(value_t& value) + { if (value.is_boolean()) { return (PyObject *)&PyBool_Type; } @@ -63,16 +77,6 @@ namespace { } } - expr_t py_value_getattr(const value_t& value, const string& name) - { - if (value.is_scope()) { - if (scope_t * scope = value.as_scope()) - return expr_t(scope->lookup(symbol_t::FUNCTION, name), scope); - } - throw_(value_error, _("Cannot lookup attributes in %1") << value.label()); - return expr_t(); - } - string py_dump(const value_t& value) { std::ostringstream buf; value.dump(buf); @@ -85,8 +89,23 @@ namespace { return buf.str(); } - void py_set_string(value_t& amount, const string& str) { - return amount.set_string(str); + void py_set_string(value_t& value, const string& str) { + return value.set_string(str); + } + + annotation_t& py_value_annotation(value_t& value) { + return value.annotation(); + } + + value_t py_strip_annotations_0(value_t& value) { + return value.strip_annotations(keep_details_t()); + } + value_t py_strip_annotations_1(value_t& value, const keep_details_t& keep) { + return value.strip_annotations(keep); + } + + PyObject * py_value_unicode(value_t& value) { + return str_to_py_unicode(value.to_string()); } } // unnamed namespace @@ -101,16 +120,16 @@ EXC_TRANSLATOR(value_error) void export_value() { enum_< value_t::type_t >("ValueType") - .value("VOID", value_t::VOID) - .value("BOOLEAN", value_t::BOOLEAN) - .value("DATETIME", value_t::DATETIME) - .value("DATE", value_t::DATE) - .value("INTEGER", value_t::INTEGER) - .value("AMOUNT", value_t::AMOUNT) - .value("BALANCE", value_t::BALANCE) - .value("STRING", value_t::STRING) - .value("SEQUENCE", value_t::SEQUENCE) - .value("SCOPE", value_t::SCOPE) + .value("Void", value_t::VOID) + .value("Boolean", value_t::BOOLEAN) + .value("DateTime", value_t::DATETIME) + .value("Date", value_t::DATE) + .value("Integer", value_t::INTEGER) + .value("Amount", value_t::AMOUNT) + .value("Balance", value_t::BALANCE) + .value("String", value_t::STRING) + .value("Sequence", value_t::SEQUENCE) + .value("Scope", value_t::SCOPE) ; class_< value_t > ("Value") @@ -243,6 +262,10 @@ void export_value() .def("unreduced", &value_t::unreduced) .def("in_place_unreduce", &value_t::in_place_unreduce) + .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", &value_t::value, value_overloads()) .def("price", &value_t::price) .def("exchange_commodities", &value_t::exchange_commodities, @@ -291,11 +314,13 @@ void export_value() .def("to_date", &value_t::to_date) .def("to_amount", &value_t::to_amount) .def("to_balance", &value_t::to_balance) + .def("__str__", &value_t::to_string) + .def("__unicode__", py_value_unicode) .def("to_string", &value_t::to_string) .def("to_mask", &value_t::to_mask) .def("to_sequence", &value_t::to_sequence) - .def("__str__", py_dump_relaxed) + .def("__unicode__", py_dump_relaxed) .def("__repr__", py_dump) .def("casted", &value_t::casted) @@ -306,16 +331,16 @@ void export_value() .def("number", &value_t::number) .def("annotate", &value_t::annotate) - .def("is_annotated", &value_t::is_annotated) -#if 0 - .def("annotation", &value_t::annotation) -#endif - .def("strip_annotations", &value_t::strip_annotations) + .def("has_annotation", &value_t::has_annotation) + .add_property("annotation", + make_function(py_value_annotation, + return_internal_reference<>())) + .def("strip_annotations", py_strip_annotations_0) + .def("strip_annotations", py_strip_annotations_1) #if 0 .def("__getitem__", &value_t::operator[]) #endif - .def("__getattr__", py_value_getattr) .def("push_back", &value_t::push_back) .def("pop_back", &value_t::pop_back) .def("size", &value_t::size) diff --git a/src/pyutils.h b/src/pyutils.h index a9e968e0..54d6fa28 100644 --- a/src/pyutils.h +++ b/src/pyutils.h @@ -106,6 +106,35 @@ struct register_optional_to_python : public boost::noncopyable } }; +template <typename T1, typename T2> +struct PairToTupleConverter +{ + static PyObject * convert(const std::pair<T1, T2>& pair) { + return boost::python::incref + (boost::python::make_tuple(pair.first, pair.second).ptr()); + } +}; + +template <typename MapType> +struct map_value_type_converter +{ + map_value_type_converter() { + boost::python::to_python_converter + <typename MapType::value_type, + PairToTupleConverter<const typename MapType::key_type, + typename MapType::mapped_type> >(); + } +}; + +template <typename T> +PyObject * str_to_py_unicode(const T& str) +{ + using namespace boost::python; + PyObject * pstr = PyString_FromString(str.c_str()); + PyObject * uni = PyUnicode_FromEncodedObject(pstr, "UTF-8", NULL); + return object(handle<>(borrowed(uni))).ptr(); +} + namespace boost { namespace python { // Use expr to create the PyObject corresponding to x diff --git a/src/query.cc b/src/query.cc index 2d6085fa..cfa321b0 100644 --- a/src/query.cc +++ b/src/query.cc @@ -337,7 +337,9 @@ query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_contex } expr_t::ptr_op_t mask = new expr_t::op_t(expr_t::op_t::VALUE); + DEBUG("query.mask", "Mask from string: " << *tok.value); mask->set_value(mask_t(*tok.value)); + DEBUG("query.mask", "Mask is: " << mask->as_value().as_mask().str()); node->set_left(ident); node->set_right(mask); diff --git a/src/query.h b/src/query.h index fb73ef2a..ebc14020 100644 --- a/src/query.h +++ b/src/query.h @@ -276,7 +276,7 @@ public: if (! args.empty()) parse_args(args); } - ~query_t() throw() { + virtual ~query_t() { TRACE_DTOR(query_t); } diff --git a/src/quotes.cc b/src/quotes.cc index 7f41e4ff..f892e93f 100644 --- a/src/quotes.cc +++ b/src/quotes.cc @@ -75,23 +75,23 @@ commodity_quote_from_script(commodity_t& commodity, if (char * p = std::strchr(buf, '\n')) *p = '\0'; DEBUG("commodity.download", "downloaded quote: " << buf); - if (optional<price_point_t> point = - amount_t::current_pool->parse_price_directive(buf)) { - if (amount_t::current_pool->price_db) { + if (optional<std::pair<commodity_t *, price_point_t> > point = + commodity_pool_t::current_pool->parse_price_directive(buf)) { + if (commodity_pool_t::current_pool->price_db) { #if defined(__GNUG__) && __GNUG__ < 3 - ofstream database(*amount_t::current_pool->price_db, + ofstream database(*commodity_pool_t::current_pool->price_db, ios::out | ios::app); #else - ofstream database(*amount_t::current_pool->price_db, + ofstream database(*commodity_pool_t::current_pool->price_db, std::ios_base::out | std::ios_base::app); #endif database << "P " - << format_datetime(point->when, FMT_WRITTEN) + << format_datetime(point->second.when, FMT_WRITTEN) << " " << commodity.symbol() - << " " << point->price + << " " << point->second.price << std::endl; } - return point; + return point->second; } } else { DEBUG("commodity.download", diff --git a/src/report.cc b/src/report.cc index 7da44f8c..b882ca92 100644 --- a/src/report.cc +++ b/src/report.cc @@ -69,18 +69,17 @@ void report_t::normalize_options(const string& verb) item_t::use_effective_date = (HANDLED(effective) && ! HANDLED(actual_dates)); - session.journal->commodity_pool->keep_base = HANDLED(base); - session.journal->commodity_pool->get_quotes = session.HANDLED(download); + commodity_pool_t::current_pool->keep_base = HANDLED(base); + commodity_pool_t::current_pool->get_quotes = session.HANDLED(download); if (session.HANDLED(price_exp_)) - session.journal->commodity_pool->quote_leeway = + commodity_pool_t::current_pool->quote_leeway = session.HANDLER(price_exp_).value.as_long(); if (session.HANDLED(price_db_)) - session.journal->commodity_pool->price_db = - session.HANDLER(price_db_).str(); + commodity_pool_t::current_pool->price_db = session.HANDLER(price_db_).str(); else - session.journal->commodity_pool->price_db = none; + commodity_pool_t::current_pool->price_db = none; if (HANDLED(date_format_)) set_date_format(HANDLER(date_format_).str().c_str()); @@ -294,12 +293,15 @@ void report_t::accounts_report(acct_handler_ptr handler) sort_expr, HANDLED(flat))); } - if (HANDLED(display_)) + if (HANDLED(display_)) { + DEBUG("report.predicate", + "Display predicate = " << HANDLER(display_).str()); pass_down_accounts(handler, *iter.get(), predicate_t(HANDLER(display_).str(), what_to_keep()), *this); - else + } else { pass_down_accounts(handler, *iter.get()); + } session.journal->clear_xdata(); } @@ -381,6 +383,32 @@ value_t report_t::fn_strip(call_scope_t& args) return args.value().strip_annotations(what_to_keep()); } +value_t report_t::fn_trim(call_scope_t& args) +{ + string temp(args.value().to_string()); + scoped_array<char> buf(new char[temp.length() + 1]); + std::strcpy(buf.get(), temp.c_str()); + + const char * p = buf.get(); + while (*p && std::isspace(*p)) + p++; + + const char * e = buf.get() + temp.length(); + while (e > p && std::isspace(*e)) + e--; + + if (e == p) { + return string_value(empty_string); + } + else if (e < p) { + assert(false); + return string_value(empty_string); + } + else { + return string_value(string(p, e - p)); + } +} + value_t report_t::fn_scrub(call_scope_t& args) { value_t temp(args.value().strip_annotations(what_to_keep())); @@ -522,7 +550,7 @@ value_t report_t::fn_price(call_scope_t& scope) value_t report_t::fn_lot_date(call_scope_t& scope) { interactive_t args(scope, "v"); - if (args.value_at(0).is_annotated()) { + if (args.value_at(0).has_annotation()) { const annotation_t& details(args.value_at(0).annotation()); if (details.date) return *details.date; @@ -533,7 +561,7 @@ value_t report_t::fn_lot_date(call_scope_t& scope) value_t report_t::fn_lot_price(call_scope_t& scope) { interactive_t args(scope, "v"); - if (args.value_at(0).is_annotated()) { + if (args.value_at(0).has_annotation()) { const annotation_t& details(args.value_at(0).annotation()); if (details.price) return *details.price; @@ -544,7 +572,7 @@ value_t report_t::fn_lot_price(call_scope_t& scope) value_t report_t::fn_lot_tag(call_scope_t& scope) { interactive_t args(scope, "v"); - if (args.value_at(0).is_annotated()) { + if (args.value_at(0).has_annotation()) { const annotation_t& details(args.value_at(0).annotation()); if (details.tag) return string_value(*details.tag); @@ -845,6 +873,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(percent); else OPT_(period_); else OPT_ALT(sort_xacts_, period_sort_); + else OPT(pivot_); else OPT(plot_amount_format_); else OPT(plot_total_format_); else OPT(price); @@ -888,6 +917,8 @@ option_t<report_t> * report_t::lookup_option(const char * p) OPT(unbudgeted); else OPT(uncleared); else OPT(unrealized); + else OPT(unrealized_gains_); + else OPT(unrealized_losses_); else OPT(unround); else OPT(unsorted); break; @@ -1077,6 +1108,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return MAKE_FUNCTOR(report_t::fn_today); else if (is_eq(p, "t")) return MAKE_FUNCTOR(report_t::fn_display_amount); + else if (is_eq(p, "trim")) + return MAKE_FUNCTOR(report_t::fn_trim); else if (is_eq(p, "to_boolean")) return MAKE_FUNCTOR(report_t::fn_to_boolean); else if (is_eq(p, "to_int")) @@ -1239,7 +1272,7 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, switch (*p) { case 'a': if (is_eq(p, "args")) - return WRAP_FUNCTOR(args_command); + return WRAP_FUNCTOR(query_command); break; case 'e': if (is_eq(p, "eval")) @@ -1261,6 +1294,10 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, else if (is_eq(p, "period")) return WRAP_FUNCTOR(period_command); break; + case 'q': + if (is_eq(p, "query")) + return WRAP_FUNCTOR(query_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 354e31b6..08819e23 100644 --- a/src/report.h +++ b/src/report.h @@ -143,6 +143,7 @@ public: value_t fn_get_at(call_scope_t& scope); value_t fn_is_seq(call_scope_t& scope); value_t fn_strip(call_scope_t& scope); + value_t fn_trim(call_scope_t& scope); value_t fn_scrub(call_scope_t& scope); value_t fn_quantity(call_scope_t& scope); value_t fn_rounded(call_scope_t& scope); @@ -272,6 +273,7 @@ public: HANDLER(pending).report(out); HANDLER(percent).report(out); HANDLER(period_).report(out); + HANDLER(pivot_).report(out); HANDLER(plot_amount_format_).report(out); HANDLER(plot_total_format_).report(out); HANDLER(prepend_format_).report(out); @@ -302,6 +304,8 @@ public: HANDLER(unbudgeted).report(out); HANDLER(uncleared).report(out); HANDLER(unrealized).report(out); + HANDLER(unrealized_gains_).report(out); + HANDLER(unrealized_losses_).report(out); HANDLER(unround).report(out); HANDLER(unsorted).report(out); HANDLER(weekly).report(out); @@ -359,7 +363,7 @@ public: OPTION_(report_t, average, DO() { // -A parent->HANDLER(display_total_) - .set_expr(string("--average"), "total_expr/count"); + .set_expr(string("--average"), "count>0?(total_expr/count):0"); }); OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) { @@ -712,6 +716,8 @@ public: string_value(text.as_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"); @@ -876,6 +882,9 @@ public: OPTION(report_t, unrealized); + OPTION(report_t, unrealized_gains_); + OPTION(report_t, unrealized_losses_); + OPTION_(report_t, unround, DO() { parent->HANDLER(display_amount_) .set_expr(string("--unround"), "unrounded(amount_expr)"); diff --git a/src/scope.cc b/src/scope.cc index 99f6b669..64736ca3 100644 --- a/src/scope.cc +++ b/src/scope.cc @@ -42,15 +42,18 @@ void symbol_scope_t::define(const symbol_t::kind_t kind, { DEBUG("scope.symbols", "Defining '" << name << "' = " << def); + if (! symbols) + symbols = symbol_map(); + std::pair<symbol_map::iterator, bool> result - = symbols.insert(symbol_map::value_type(symbol_t(kind, name, def), def)); + = symbols->insert(symbol_map::value_type(symbol_t(kind, name, def), def)); if (! result.second) { - symbol_map::iterator i = symbols.find(symbol_t(kind, name)); - assert(i != symbols.end()); - symbols.erase(i); + symbol_map::iterator i = symbols->find(symbol_t(kind, name)); + 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); @@ -60,10 +63,11 @@ void symbol_scope_t::define(const symbol_t::kind_t kind, expr_t::ptr_op_t symbol_scope_t::lookup(const symbol_t::kind_t kind, const string& name) { - symbol_map::const_iterator i = symbols.find(symbol_t(kind, name)); - if (i != symbols.end()) - return (*i).second; - + if (symbols) { + symbol_map::const_iterator i = symbols->find(symbol_t(kind, name)); + if (i != symbols->end()) + return (*i).second; + } return child_scope_t::lookup(kind, name); } diff --git a/src/scope.h b/src/scope.h index 44ca3229..93f80230 100644 --- a/src/scope.h +++ b/src/scope.h @@ -172,7 +172,7 @@ class symbol_scope_t : public child_scope_t { typedef std::map<symbol_t, expr_t::ptr_op_t> symbol_map; - symbol_map symbols; + optional<symbol_map> symbols; public: explicit symbol_scope_t() { diff --git a/src/session.cc b/src/session.cc index 0d6a6829..1882e554 100644 --- a/src/session.cc +++ b/src/session.cc @@ -45,7 +45,7 @@ void set_session_context(session_t * session) { if (session) { times_initialize(); - amount_t::initialize(session->journal->commodity_pool); + amount_t::initialize(); amount_t::parse_conversion("1.0m", "60s"); amount_t::parse_conversion("1.0h", "60m"); @@ -179,7 +179,7 @@ void session_t::close_journal_files() amount_t::shutdown(); journal.reset(new journal_t); - amount_t::initialize(journal->commodity_pool); + amount_t::initialize(); } option_t<session_t> * session_t::lookup_option(const char * p) diff --git a/src/textual.cc b/src/textual.cc index aec7dbda..8953d2b8 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -49,49 +49,26 @@ namespace ledger { namespace { - class instance_t : public noncopyable, public scope_t - { - static const std::size_t MAX_LINE = 1024; + typedef std::pair<commodity_t *, amount_t> fixed_rate_t; + typedef variant<account_t *, string, fixed_rate_t> state_t; + class parse_context_t : public noncopyable + { public: - typedef variant<account_t *, string> state_t; - - std::list<state_t>& state_stack; - -#if defined(TIMELOG_SUPPORT) - time_log_t& timelog; -#endif - instance_t * parent; - std::istream& in; - scope_t& scope; - journal_t& journal; - account_t * master; - const path * original_file; - accounts_map account_aliases; - bool strict; - path pathname; - char linebuf[MAX_LINE + 1]; - std::size_t linenum; - istream_pos_type line_beg_pos; - istream_pos_type curr_pos; - std::size_t count; - std::size_t errors; - - optional<date_t::year_type> current_year; - - instance_t(std::list<state_t>& _state_stack, + journal_t& journal; + scope_t& scope; + std::list<state_t> state_stack; #if defined(TIMELOG_SUPPORT) - time_log_t& _timelog, + time_log_t timelog; #endif - std::istream& _in, - scope_t& _scope, - journal_t& _journal, - account_t * _master = NULL, - const path * _original_file = NULL, - bool _strict = false, - instance_t * _parent = NULL); + bool strict; + std::size_t count; + std::size_t errors; + std::size_t sequence; - ~instance_t(); + parse_context_t(journal_t& _journal, scope_t& _scope) + : journal(_journal), scope(_scope), timelog(journal), + strict(false), count(0), errors(0), sequence(1) {} bool front_is_account() { return state_stack.front().type() == typeid(account_t *); @@ -99,6 +76,9 @@ namespace { 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); + } account_t * top_account() { foreach (state_t& state, state_stack) @@ -106,6 +86,34 @@ namespace { return boost::get<account_t *>(state); return NULL; } + }; + + class instance_t : public noncopyable, public scope_t + { + static const std::size_t MAX_LINE = 1024; + + public: + parse_context_t& context; + instance_t * parent; + account_t * master; + 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<date_t::year_type> current_year; + + instance_t(parse_context_t& _context, + std::istream& _in, + account_t * _master = NULL, + const path * _original_file = NULL, + instance_t * _parent = NULL); + + ~instance_t(); void parse(); std::streamsize read_line(char *& line); @@ -135,6 +143,7 @@ namespace { void master_account_directive(char * line); void end_directive(char * line); void alias_directive(char * line); + void fixed_directive(char * line); void tag_directive(char * line); void define_directive(char * line); bool general_directive(char * line); @@ -143,7 +152,6 @@ namespace { std::streamsize len, account_t * account, xact_t * xact, - bool honor_strict = true, bool defer_expr = false); bool parse_posts(account_t * account, @@ -158,13 +166,13 @@ namespace { const string& name); }; - void parse_amount_expr(scope_t& scope, - std::istream& in, + void parse_amount_expr(std::istream& in, + scope_t& scope, + post_t& post, amount_t& amount, - optional<expr_t> * amount_expr, - post_t * post, - const parse_flags_t& flags = PARSE_DEFAULT, - const bool defer_expr = false) + const parse_flags_t& flags = PARSE_DEFAULT, + const bool defer_expr = false, + optional<expr_t> * amount_expr = NULL) { expr_t expr(in, flags.plus_flags(PARSE_PARTIAL)); @@ -180,51 +188,27 @@ namespace { #endif if (expr) { - bind_scope_t bound_scope(scope, *post); - if (defer_expr) { - assert(amount_expr); + if (amount_expr) *amount_expr = expr; - (*amount_expr)->compile(bound_scope); - } else { - value_t result(expr.calc(bound_scope)); - if (result.is_long()) { - amount = result.to_amount(); - } else { - if (! result.is_amount()) - throw_(amount_error, - _("Amount expressions must result in a simple amount")); - amount = result.as_amount(); - } - DEBUG("textual.parse", "The posting amount is " << amount); - } + if (! defer_expr) + amount = post.resolve_expr(scope, expr); } } } -instance_t::instance_t(std::list<state_t>& _state_stack, -#if defined(TIMELOG_SUPPORT) - time_log_t& _timelog, -#endif - std::istream& _in, - scope_t& _scope, - journal_t& _journal, - account_t * _master, - const path * _original_file, - bool _strict, - instance_t * _parent) - : state_stack(_state_stack), -#if defined(TIMELOG_SUPPORT) - timelog(_timelog), -#endif - parent(_parent), in(_in), scope(_scope), - journal(_journal), master(_master), - original_file(_original_file), strict(_strict) +instance_t::instance_t(parse_context_t& _context, + std::istream& _in, + account_t * _master, + const path * _original_file, + instance_t * _parent) + : context(_context), parent(_parent), master(_master), + original_file(_original_file), in(_in) { TRACE_CTOR(instance_t, "..."); if (! master) - master = journal.master; - state_stack.push_front(master); + master = context.journal.master; + context.state_stack.push_front(master); if (_original_file) pathname = *_original_file; @@ -236,8 +220,8 @@ instance_t::~instance_t() { TRACE_DTOR(instance_t); - assert(! state_stack.empty()); - state_stack.pop_front(); + assert(! context.state_stack.empty()); + context.state_stack.pop_front(); } void instance_t::parse() @@ -251,8 +235,6 @@ void instance_t::parse() return; linenum = 0; - errors = 0; - count = 0; curr_pos = in.tellg(); while (in.good() && ! in.eof()) { @@ -281,15 +263,15 @@ void instance_t::parse() if (caught_signal != NONE_CAUGHT) throw; - string context = error_context(); - if (! context.empty()) - std::cerr << context << std::endl; + string err_context = error_context(); + if (! err_context.empty()) + std::cerr << err_context << std::endl; if (! current_context.empty()) std::cerr << current_context << std::endl; std::cerr << _("Error: ") << err.what() << std::endl; - errors++; + context.errors++; } } @@ -345,8 +327,10 @@ void instance_t::read_next_directive() break; } - case '#': // comment line - case ';': // comment line + case ';': // comments + case '#': + case '*': + case '|': break; case '-': // option setting @@ -445,13 +429,14 @@ void instance_t::clock_in_directive(char * line, position.beg_line = linenum; position.end_pos = curr_pos; position.end_line = linenum; + position.sequence = context.sequence++; time_xact_t event(position, parse_datetime(datetime, current_year), - p ? top_account()->find_account(p) : NULL, + p ? context.top_account()->find_account(p) : NULL, n ? n : "", end ? end : ""); - timelog.clock_in(event); + context.timelog.clock_in(event); } void instance_t::clock_out_directive(char * line, @@ -474,14 +459,15 @@ void instance_t::clock_out_directive(char * line, position.beg_line = linenum; position.end_pos = curr_pos; position.end_line = linenum; + position.sequence = context.sequence++; time_xact_t event(position, parse_datetime(datetime, current_year), - p ? top_account()->find_account(p) : NULL, + p ? context.top_account()->find_account(p) : NULL, n ? n : "", end ? end : ""); - timelog.clock_out(event); - count++; + context.timelog.clock_out(event); + context.count++; } #endif // TIMELOG_SUPPORT @@ -490,14 +476,14 @@ void instance_t::default_commodity_directive(char * line) { amount_t amt(skip_ws(line + 1)); VERIFY(amt.valid()); - amount_t::current_pool->default_commodity = &amt.commodity(); + commodity_pool_t::current_pool->default_commodity = &amt.commodity(); amt.commodity().add_flags(COMMODITY_KNOWN); } void instance_t::default_account_directive(char * line) { - journal.bucket = top_account()->find_account(skip_ws(line + 1)); - journal.bucket->add_flags(ACCOUNT_KNOWN); + context.journal.bucket = context.top_account()->find_account(skip_ws(line + 1)); + context.journal.bucket->add_flags(ACCOUNT_KNOWN); } void instance_t::price_conversion_directive(char * line) @@ -510,8 +496,8 @@ void instance_t::price_conversion_directive(char * line) void instance_t::price_xact_directive(char * line) { - optional<price_point_t> point = - amount_t::current_pool->parse_price_directive(skip_ws(line + 1)); + optional<std::pair<commodity_t *, price_point_t> > point = + commodity_pool_t::current_pool->parse_price_directive(skip_ws(line + 1)); if (! point) throw parse_error(_("Pricing entry failed to parse")); } @@ -523,7 +509,7 @@ void instance_t::nomarket_directive(char * line) commodity_t::parse_symbol(p, symbol); if (commodity_t * commodity = - amount_t::current_pool->find_or_create(symbol)) + commodity_pool_t::current_pool->find_or_create(symbol)) commodity->add_flags(COMMODITY_NOMARKET | COMMODITY_KNOWN); } @@ -541,14 +527,13 @@ void instance_t::option_directive(char * line) *p++ = '\0'; } - if (! process_option(pathname.string(), line + 2, scope, p, line)) + if (! process_option(pathname.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; - std::size_t lnum = linenum; + istream_pos_type pos= line_beg_pos; bool reveal_context = true; @@ -557,19 +542,20 @@ void instance_t::automated_xact_directive(char * line) std::auto_ptr<auto_xact_t> ae (new auto_xact_t(query_t(string(skip_ws(line + 1)), keep_details_t(true, true, true)))); + ae->pos = position_t(); + ae->pos->pathname = pathname; + ae->pos->beg_pos = line_beg_pos; + ae->pos->beg_line = linenum; + ae->pos->sequence = context.sequence++; reveal_context = false; - if (parse_posts(top_account(), *ae.get(), true)) { + if (parse_posts(context.top_account(), *ae.get(), true)) { reveal_context = true; - journal.auto_xacts.push_back(ae.get()); + context.journal.auto_xacts.push_back(ae.get()); - ae->journal = &journal; - ae->pos = position_t(); - ae->pos->pathname = pathname; - ae->pos->beg_pos = pos; - ae->pos->beg_line = lnum; + ae->journal = &context.journal; ae->pos->end_pos = curr_pos; ae->pos->end_line = linenum; @@ -588,29 +574,29 @@ void instance_t::automated_xact_directive(char * line) void instance_t::period_xact_directive(char * line) { - istream_pos_type pos = line_beg_pos; - std::size_t lnum = linenum; + istream_pos_type pos = line_beg_pos; bool reveal_context = true; try { std::auto_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->sequence = context.sequence++; reveal_context = false; - if (parse_posts(top_account(), *pe.get())) { + if (parse_posts(context.top_account(), *pe.get())) { reveal_context = true; - pe->journal = &journal; + pe->journal = &context.journal; if (pe->finalize()) { - journal.extend_xact(pe.get()); - journal.period_xacts.push_back(pe.get()); + context.journal.extend_xact(pe.get()); + context.journal.period_xacts.push_back(pe.get()); - pe->pos = position_t(); - pe->pos->pathname = pathname; - pe->pos->beg_pos = pos; - pe->pos->beg_line = lnum; pe->pos->end_pos = curr_pos; pe->pos->end_line = linenum; @@ -635,12 +621,12 @@ 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, top_account())) { + if (xact_t * xact = parse_xact(line, len, context.top_account())) { std::auto_ptr<xact_t> manager(xact); - if (journal.add_xact(xact)) { + if (context.journal.add_xact(xact)) { manager.release(); // it's owned by the journal now - count++; + context.count++; } // It's perfectly valid for the journal to reject the xact, which it will // do if the xact has no substantive effect (for example, a checking @@ -656,20 +642,26 @@ void instance_t::include_directive(char * line) { path filename; + DEBUG("textual.include", "include: " << 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('/'); if (pos == string::npos) pos = pathname.string().rfind('\\'); - if (pos != string::npos) + if (pos != string::npos) { filename = path(string(pathname.string(), 0, pos + 1)) / line; + DEBUG("textual.include", "normalized path: " << filename.string()); + } else { + filename = path(string(".")) / line; + } } else { filename = line; } filename = resolve_path(filename); - - DEBUG("textual.include", "Line " << linenum << ": " << - "Including path '" << filename << "'"); + DEBUG("textual.include", "resolved path: " << filename.string()); if (! exists(filename)) throw_(std::runtime_error, @@ -677,22 +669,14 @@ void instance_t::include_directive(char * line) ifstream stream(filename); - instance_t instance(state_stack, -#if defined(TIMELOG_SUPPORT) - timelog, -#endif - stream, scope, journal, master, - &filename, strict, this); + instance_t instance(context, stream, master, &filename, this); instance.parse(); - - errors += instance.errors; - count += instance.count; } void instance_t::master_account_directive(char * line) { - if (account_t * acct = top_account()->find_account(line)) - state_stack.push_front(acct); + if (account_t * acct = context.top_account()->find_account(line)) + context.state_stack.push_front(acct); else assert(! "Failed to create account"); } @@ -701,18 +685,21 @@ void instance_t::end_directive(char * kind) { string name(kind); - if ((name.empty() || name == "account") && ! front_is_account()) + 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 account' directive does not match open tag directive")); - else if (name == "tag" && ! front_is_string()) + _("'end tag' directive does not match open directive")); + else if (name == "fixed" && ! context.front_is_fixed_rate()) throw_(std::runtime_error, - _("'end tag' directive does not match open account directive")); + _("'end fixed' directive does not match open directive")); - if (state_stack.size() <= 1) + if (context.state_stack.size() <= 1) throw_(std::runtime_error, _("'end' found, but no enclosing tag or account directive")); else - state_stack.pop_front(); + context.state_stack.pop_front(); } void instance_t::alias_directive(char * line) @@ -729,13 +716,25 @@ void instance_t::alias_directive(char * line) // 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 = top_account()->find_account(e); + 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); } } +void instance_t::fixed_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)) { + context.state_stack.push_front(fixed_rate_t(price_point->first, + price_point->second.price)); + } else { + throw_(std::runtime_error, _("Error in fixed directive")); + } +} + void instance_t::tag_directive(char * line) { string tag(trim_ws(line)); @@ -743,13 +742,13 @@ void instance_t::tag_directive(char * line) if (tag.find(':') == string::npos) tag = string(":") + tag + ":"; - state_stack.push_front(tag); + context.state_stack.push_front(tag); } void instance_t::define_directive(char * line) { expr_t def(skip_ws(line)); - def.compile(scope); // causes definitions to be established + def.compile(context.scope); // causes definitions to be established } bool instance_t::general_directive(char * line) @@ -797,6 +796,13 @@ bool instance_t::general_directive(char * line) } break; + case 'f': + if (std::strcmp(p, "fixed") == 0) { + fixed_directive(arg); + return true; + } + break; + case 'i': if (std::strcmp(p, "include") == 0) { include_directive(arg); @@ -810,6 +816,13 @@ bool instance_t::general_directive(char * line) return true; } break; + + case 'y': + if (std::strcmp(p, "year") == 0) { + year_directive(arg); + return true; + } + break; } if (expr_t::ptr_op_t op = lookup(symbol_t::DIRECTIVE, p)) { @@ -826,7 +839,6 @@ post_t * instance_t::parse_post(char * line, std::streamsize len, account_t * account, xact_t * xact, - bool honor_strict, bool defer_expr) { TRACE_START(post_details, 1, "Time spent parsing postings:"); @@ -838,6 +850,7 @@ post_t * instance_t::parse_post(char * line, post->pos->pathname = pathname; post->pos->beg_pos = line_beg_pos; post->pos->beg_line = linenum; + post->pos->sequence = context.sequence++; char buf[MAX_LINE + 1]; std::strcpy(buf, line); @@ -909,7 +922,7 @@ post_t * instance_t::parse_post(char * line, if (! post->account) post->account = account->find_account(name); - if (honor_strict && strict && ! post->account->has_flags(ACCOUNT_KNOWN)) { + if (context.strict && ! post->account->has_flags(ACCOUNT_KNOWN)) { if (post->_state == item_t::UNCLEARED) warning_(_("\"%1\", line %2: Unknown account '%3'") << pathname << linenum << post->account->fullname()); @@ -929,17 +942,32 @@ 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(scope, stream, post->amount, &post->amount_expr, - post.get(), PARSE_NO_REDUCE | PARSE_SINGLE | - PARSE_NO_ASSIGN, defer_expr); - - if (! post->amount.is_null() && honor_strict && strict && - post->amount.has_commodity() && - ! post->amount.commodity().has_flags(COMMODITY_KNOWN)) { - if (post->_state == item_t::UNCLEARED) - warning_(_("\"%1\", line %2: Unknown commodity '%3'") - << pathname << linenum << post->amount.commodity()); - post->amount.commodity().add_flags(COMMODITY_KNOWN); + 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 << linenum << post->amount.commodity()); + post->amount.commodity().add_flags(COMMODITY_KNOWN); + } + + 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; + } + } + } + } } DEBUG("textual.parse", "line " << linenum << ": " @@ -976,9 +1004,8 @@ 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(scope, cstream, *post->cost, NULL, post.get(), - PARSE_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_ASSIGN, - defer_expr); + parse_amount_expr(cstream, context.scope, *post.get(), *post->cost, + PARSE_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_ASSIGN); if (post->cost->sign() < 0) throw parse_error(_("A posting's cost may not be negative")); @@ -993,6 +1020,9 @@ post_t * instance_t::parse_post(char * line, *post->cost *= post->amount; post->cost->set_commodity(cost_commodity); } + else if (post->amount.sign() < 0) { + post->cost->in_place_negate(); + } DEBUG("textual.parse", "line " << linenum << ": " << "Total cost is " << *post->cost); @@ -1028,9 +1058,9 @@ 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(scope, stream, *post->assigned_amount, NULL, - post.get(), PARSE_SINGLE | PARSE_NO_MIGRATE, - defer_expr); + parse_amount_expr(stream, context.scope, *post.get(), + *post->assigned_amount, + PARSE_SINGLE | PARSE_NO_MIGRATE); if (post->assigned_amount->is_null()) { if (post->amount.is_null()) @@ -1113,8 +1143,8 @@ post_t * instance_t::parse_post(char * line, post->pos->end_pos = curr_pos; post->pos->end_line = linenum; - if (! state_stack.empty()) { - foreach (const state_t& state, state_stack) + 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()); } @@ -1144,9 +1174,7 @@ bool instance_t::parse_posts(account_t * account, std::streamsize len = read_line(line); assert(len > 0); - if (post_t * post = - parse_post(line, len, account, NULL, /* honor_strict= */ false, - defer_expr)) { + if (post_t * post = parse_post(line, len, account, NULL, defer_expr)) { xact.add_post(post); added = true; } @@ -1169,6 +1197,7 @@ xact_t * instance_t::parse_xact(char * line, xact->pos->pathname = pathname; xact->pos->beg_pos = line_beg_pos; xact->pos->beg_line = linenum; + xact->pos->sequence = context.sequence++; bool reveal_context = true; @@ -1278,8 +1307,8 @@ xact_t * instance_t::parse_xact(char * line, xact->pos->end_pos = curr_pos; xact->pos->end_line = linenum; - if (! state_stack.empty()) { - foreach (const state_t& state, state_stack) + 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()); } @@ -1302,7 +1331,7 @@ 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 scope.lookup(kind, name); + return context.scope.lookup(kind, name); } std::size_t journal_t::parse(std::istream& in, @@ -1313,18 +1342,11 @@ std::size_t journal_t::parse(std::istream& in, { TRACE_START(parsing_total, 1, "Total time spent parsing text:"); - std::list<instance_t::state_t> state_stack; -#if defined(TIMELOG_SUPPORT) - time_log_t timelog(*this); -#endif + parse_context_t context(*this, scope); + context.strict = strict; - instance_t parsing_instance(state_stack, -#if defined(TIMELOG_SUPPORT) - timelog, -#endif - in, scope, *this, master, - original_file, strict); - parsing_instance.parse(); + instance_t instance(context, in, master, original_file); + instance.parse(); TRACE_STOP(parsing_total, 1); @@ -1336,10 +1358,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 (parsing_instance.errors > 0) - throw static_cast<int>(parsing_instance.errors); + if (context.errors > 0) + throw static_cast<int>(context.errors); - return parsing_instance.count; + return context.count; } } // namespace ledger diff --git a/src/times.cc b/src/times.cc index e3ccaff8..963639f1 100644 --- a/src/times.cc +++ b/src/times.cc @@ -224,7 +224,7 @@ namespace { when = date_t(year ? *year : CURRENT_DATE().year(), when.month(), when.day()); - if (when.month() > CURRENT_DATE().month()) + if (! year && when.month() > CURRENT_DATE().month()) when -= gregorian::years(1); } } @@ -824,15 +824,6 @@ date_interval_t date_parser_t::parse() break; } - case lexer_t::token_t::TOK_MONTH: { - date_t temp(today); - temp += gregorian::months(adjust); - inclusion_specifier = - date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()), - temp.month()); - break; - } - case lexer_t::token_t::TOK_WEEK: { date_t temp = date_duration_t::find_nearest(today, date_duration_t::WEEKS); @@ -852,10 +843,15 @@ date_interval_t date_parser_t::parse() } default: - tok.unexpected(); + case lexer_t::token_t::TOK_MONTH: { + date_t temp(today); + temp += gregorian::months(adjust); + inclusion_specifier = + date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()), + temp.month()); break; } - break; + } } case lexer_t::token_t::TOK_TODAY: @@ -1321,10 +1317,13 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() catch (...) {} } + start = begin; + string term; bool alnum = std::isalnum(*begin); - for (start = begin; (begin != end && ! std::isspace(*begin) && - alnum == std::isalnum(*begin)); begin++) + for (; (begin != end && ! std::isspace(*begin) && + ((alnum && static_cast<bool>(std::isalnum(*begin))) || + (! alnum && ! static_cast<bool>(std::isalnum(*begin))))); begin++) term.push_back(*begin); if (! term.empty()) { diff --git a/src/value.cc b/src/value.cc index f4df3329..7d079caf 100644 --- a/src/value.cc +++ b/src/value.cc @@ -861,7 +861,7 @@ bool value_t::is_less_than(const value_t& val) const return as_amount() < val.as_amount(); } catch (const amount_error&) { - return compare_amount_commodities()(&as_amount(), &val.as_amount()); + return commodity_t::compare_by_commodity()(&as_amount(), &val.as_amount()); } default: break; @@ -1114,18 +1114,18 @@ void value_t::in_place_cast(type_t cast_type) break; } - case BALANCE: + case BALANCE: { + const balance_t& bal(as_balance()); switch (cast_type) { case AMOUNT: { - const balance_t& temp(as_balance()); - if (temp.amounts.size() == 1) { + if (bal.amounts.size() == 1) { // Because we are changing the current balance value to an amount // value, and because set_amount takes a reference (and that memory is // about to be repurposed), we must pass in a copy. - set_amount(amount_t((*temp.amounts.begin()).second)); + set_amount(amount_t((*bal.amounts.begin()).second)); return; } - else if (temp.amounts.size() == 0) { + else if (bal.amounts.size() == 0) { set_amount(0L); return; } @@ -1135,10 +1135,17 @@ void value_t::in_place_cast(type_t cast_type) } break; } + case STRING: + if (bal.is_empty()) + set_string(""); + else + set_string(as_balance().to_string()); + return; default: break; } break; + } case STRING: switch (cast_type) { @@ -1353,7 +1360,8 @@ value_t value_t::exchange_commodities(const std::string& commodities, p; p = std::strtok(NULL, ",")) { if (commodity_t * commodity = - amount_t::current_pool->parse_price_expression(p, add_prices, moment)) { + commodity_pool_t::current_pool->parse_price_expression(p, add_prices, + moment)) { value_t result = value(false, moment, *commodity); if (! result.is_null()) return result; @@ -1523,10 +1531,10 @@ void value_t::annotate(const annotation_t& details) throw_(value_error, _("Cannot annotate %1") << label()); } -bool value_t::is_annotated() const +bool value_t::has_annotation() const { if (is_amount()) - return as_amount().is_annotated(); + return as_amount().has_annotation(); else throw_(value_error, _("Cannot determine whether %1 is annotated") << label()); diff --git a/src/value.h b/src/value.h index 2a420cd3..ffbb89e8 100644 --- a/src/value.h +++ b/src/value.h @@ -774,7 +774,7 @@ public: * Annotated commodity methods. */ void annotate(const annotation_t& details); - bool is_annotated() const; + bool has_annotation() const; annotation_t& annotation(); const annotation_t& annotation() const { diff --git a/src/xact.cc b/src/xact.cc index f2694976..623c5772 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -122,7 +122,7 @@ bool xact_base_t::finalize() amount_t& p(post->cost ? *post->cost : post->amount); if (! p.is_null()) { DEBUG("xact.finalize", "post must balance = " << p.reduced()); - if (! post->cost && post->amount.is_annotated() && + if (! post->cost && post->amount.has_annotation() && post->amount.annotation().price) { // If the amount has no cost, but is annotated with a per-unit // price, use the price times the amount as the cost @@ -221,7 +221,7 @@ bool xact_base_t::finalize() foreach (post_t * post, posts) { if (! post->amount.is_null()) { - if (post->amount.is_annotated()) + if (post->amount.has_annotation()) top_post = post; else if (! top_post) top_post = post; @@ -260,7 +260,7 @@ bool xact_base_t::finalize() foreach (post_t * post, posts) { if (post != top_post && post->must_balance() && ! post->amount.is_null() && - post->amount.is_annotated() && + post->amount.has_annotation() && post->amount.annotation().price) { amount_t temp = *post->amount.annotation().price * post->amount; if (total_cost.is_null()) { @@ -309,10 +309,11 @@ bool xact_base_t::finalize() _("A posting's cost must be of a different commodity than its amount")); cost_breakdown_t breakdown = - amount_t::current_pool->exchange(post->amount, *post->cost, false, - datetime_t(date(), time_duration(0, 0, 0, 0))); + commodity_pool_t::current_pool->exchange + (post->amount, *post->cost, false, + datetime_t(date(), time_duration(0, 0, 0, 0))); - if (post->amount.is_annotated() && + if (post->amount.has_annotation() && breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) { if (amount_t gain_loss = (breakdown.basis_cost - |