diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/account.cc | 13 | ||||
-rw-r--r-- | src/account.h | 1 | ||||
-rw-r--r-- | src/amount.cc | 46 | ||||
-rw-r--r-- | src/amount.h | 11 | ||||
-rw-r--r-- | src/annotate.cc | 158 | ||||
-rw-r--r-- | src/annotate.h | 62 | ||||
-rw-r--r-- | src/balance.cc | 14 | ||||
-rw-r--r-- | src/balance.h | 6 | ||||
-rw-r--r-- | src/chain.cc | 16 | ||||
-rw-r--r-- | src/chain.h | 3 | ||||
-rw-r--r-- | src/commodity.cc | 123 | ||||
-rw-r--r-- | src/commodity.h | 49 | ||||
-rw-r--r-- | src/csv.cc | 4 | ||||
-rw-r--r-- | src/draft.cc | 13 | ||||
-rw-r--r-- | src/expr.cc | 112 | ||||
-rw-r--r-- | src/expr.h | 126 | ||||
-rw-r--r-- | src/exprbase.h | 6 | ||||
-rw-r--r-- | src/filters.cc | 15 | ||||
-rw-r--r-- | src/filters.h | 20 | ||||
-rw-r--r-- | src/format.cc | 4 | ||||
-rw-r--r-- | src/global.cc | 3 | ||||
-rw-r--r-- | src/global.h | 3 | ||||
-rw-r--r-- | src/history.cc | 218 | ||||
-rw-r--r-- | src/history.h | 132 | ||||
-rw-r--r-- | src/item.cc | 26 | ||||
-rw-r--r-- | src/iterators.h | 2 | ||||
-rw-r--r-- | src/journal.cc | 15 | ||||
-rw-r--r-- | src/journal.h | 7 | ||||
-rw-r--r-- | src/main.cc | 17 | ||||
-rw-r--r-- | src/op.cc | 398 | ||||
-rw-r--r-- | src/op.h | 27 | ||||
-rw-r--r-- | src/option.h | 78 | ||||
-rw-r--r-- | src/parser.cc | 48 | ||||
-rw-r--r-- | src/parser.h | 2 | ||||
-rw-r--r-- | src/pool.cc | 174 | ||||
-rw-r--r-- | src/pool.h | 53 | ||||
-rw-r--r-- | src/post.cc | 49 | ||||
-rw-r--r-- | src/post.h | 6 | ||||
-rw-r--r-- | src/predicate.cc | 40 | ||||
-rw-r--r-- | src/print.cc | 17 | ||||
-rw-r--r-- | src/py_amount.cc | 6 | ||||
-rw-r--r-- | src/py_balance.cc | 8 | ||||
-rw-r--r-- | src/py_commodity.cc | 18 | ||||
-rw-r--r-- | src/py_value.cc | 9 | ||||
-rw-r--r-- | src/pyinterp.cc | 3 | ||||
-rw-r--r-- | src/pyinterp.h | 4 | ||||
-rw-r--r-- | src/query.h | 3 | ||||
-rw-r--r-- | src/quotes.cc | 2 | ||||
-rw-r--r-- | src/quotes.h | 2 | ||||
-rw-r--r-- | src/report.cc | 435 | ||||
-rw-r--r-- | src/report.h | 746 | ||||
-rw-r--r-- | src/scope.cc | 7 | ||||
-rw-r--r-- | src/scope.h | 41 | ||||
-rw-r--r-- | src/series.h | 135 | ||||
-rw-r--r-- | src/session.cc | 24 | ||||
-rw-r--r-- | src/session.h | 27 | ||||
-rw-r--r-- | src/system.hh.in | 7 | ||||
-rw-r--r-- | src/textual.cc | 134 | ||||
-rw-r--r-- | src/timelog.cc | 2 | ||||
-rw-r--r-- | src/times.h | 6 | ||||
-rw-r--r-- | src/token.cc | 2 | ||||
-rw-r--r-- | src/utils.cc | 3 | ||||
-rw-r--r-- | src/utils.h | 16 | ||||
-rw-r--r-- | src/value.cc | 140 | ||||
-rw-r--r-- | src/value.h | 12 | ||||
-rw-r--r-- | src/xact.cc | 57 | ||||
-rw-r--r-- | src/xact.h | 14 |
67 files changed, 2330 insertions, 1650 deletions
diff --git a/src/account.cc b/src/account.cc index 40ddf70b..d772368c 100644 --- a/src/account.cc +++ b/src/account.cc @@ -89,9 +89,13 @@ account_t * account_t::find_account(const string& acct_name, if (has_flags(ACCOUNT_GENERATED)) account->add_flags(ACCOUNT_GENERATED); - std::pair<accounts_map::iterator, bool> result - = accounts.insert(accounts_map::value_type(first, account)); +#if defined(DEBUG_ON) + std::pair<accounts_map::iterator, bool> result = +#endif + accounts.insert(accounts_map::value_type(first, account)); +#if defined(DEBUG_ON) assert(result.second); +#endif } else { account = (*i).second; } @@ -137,7 +141,10 @@ void account_t::add_post(post_t * post) bool account_t::remove_post(post_t * post) { - assert(! posts.empty()); + // It's possible that 'post' wasn't yet in this account, but try to + // remove it anyway. This can happen if there is an error during + // parsing, when the posting knows what it's account is, but + // xact_t::finalize has not yet added that posting to the account. posts.remove(post); post->account = NULL; return true; diff --git a/src/account.h b/src/account.h index 8f0f915f..95e04079 100644 --- a/src/account.h +++ b/src/account.h @@ -67,6 +67,7 @@ public: unsigned short depth; accounts_map accounts; posts_list posts; + optional<expr_t> value_expr; mutable string _fullname; diff --git a/src/amount.cc b/src/amount.cc index 9704dd21..5fa58528 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -120,11 +120,13 @@ namespace { { char * buf = NULL; try { +#if defined(DEBUG_ON) IF_DEBUG("amount.convert") { char * tbuf = mpq_get_str(NULL, 10, quant); DEBUG("amount.convert", "Rational to convert = " << tbuf); std::free(tbuf); } +#endif // Convert the rational number to a floating-point, extending the // floating-point to a large enough size to get a precise answer. @@ -726,24 +728,24 @@ void amount_t::in_place_unreduce() } optional<amount_t> -amount_t::value(const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of) const +amount_t::value(const datetime_t& moment, + const commodity_t * in_terms_of) const { if (quantity) { #if defined(DEBUG_ON) - DEBUG("commodity.prices.find", + DEBUG("commodity.price.find", "amount_t::value of " << commodity().symbol()); - if (moment) - DEBUG("commodity.prices.find", - "amount_t::value: moment = " << *moment); + if (! moment.is_not_a_date_time()) + DEBUG("commodity.price.find", + "amount_t::value: moment = " << moment); if (in_terms_of) - DEBUG("commodity.prices.find", + DEBUG("commodity.price.find", "amount_t::value: in_terms_of = " << in_terms_of->symbol()); #endif if (has_commodity() && (in_terms_of || ! commodity().has_flags(COMMODITY_PRIMARY))) { optional<price_point_t> point; - optional<commodity_t&> comm(in_terms_of); + const commodity_t * comm(in_terms_of); if (has_annotation() && annotation().price) { if (annotation().has_flags(ANNOTATION_PRICE_FIXATED)) { @@ -753,7 +755,7 @@ amount_t::value(const optional<datetime_t>& moment, "amount_t::value: fixated price = " << point->price); } else if (! comm) { - comm = annotation().price->commodity(); + comm = annotation().price->commodity_ptr(); } } @@ -785,7 +787,7 @@ amount_t::value(const optional<datetime_t>& moment, return none; } -amount_t amount_t::price() const +optional<amount_t> amount_t::price() const { if (has_annotation() && annotation().price) { amount_t tmp(*annotation().price); @@ -793,7 +795,7 @@ amount_t amount_t::price() const DEBUG("amount.price", "Returning price of " << *this << " = " << tmp); return tmp; } - return *this; + return none; } @@ -865,10 +867,10 @@ bool amount_t::fits_in_long() const return mpfr_fits_slong_p(tempf, GMP_RNDN); } -commodity_t& amount_t::commodity() const +commodity_t * amount_t::commodity_ptr() const { - return (has_commodity() ? - *commodity_ : *commodity_pool_t::current_pool->null_commodity); + return (commodity_ ? + commodity_ : commodity_pool_t::current_pool->null_commodity); } bool amount_t::has_commodity() const @@ -1027,12 +1029,12 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) } // Allocate memory for the amount's quantity value. We have to - // monitor the allocation in an auto_ptr because this function gets + // monitor the allocation in a unique_ptr because this function gets // called sometimes from amount_t's constructor; and if there is an // exeception thrown by any of the function calls after this point, // the destructor will never be called and the memory never freed. - std::auto_ptr<bigint_t> new_quantity; + unique_ptr<bigint_t> new_quantity; if (quantity) { if (quantity->refc > 1) @@ -1058,10 +1060,6 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (! commodity_) commodity_ = commodity_pool_t::current_pool->create(symbol); assert(commodity_); - - if (details) - commodity_ = - commodity_pool_t::current_pool->find_or_create(*commodity_, details); } // Quickly scan through and verify the correctness of the amount's use of @@ -1197,6 +1195,14 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) if (! flags.has_flags(PARSE_NO_REDUCE)) in_place_reduce(); // will not throw an exception + if (commodity_ && details) { + if (details.has_flags(ANNOTATION_PRICE_NOT_PER_UNIT)) { + assert(details.price); + *details.price /= this->abs(); + } + set_commodity(*commodity_pool_t::current_pool->find_or_create(*commodity_, details)); + } + VERIFY(valid()); return true; diff --git a/src/amount.h b/src/amount.h index 1db59b7e..903a01cd 100644 --- a/src/amount.h +++ b/src/amount.h @@ -404,10 +404,10 @@ public: $100.00. */ optional<amount_t> - value(const optional<datetime_t>& moment = none, - const optional<commodity_t&>& in_terms_of = none) const; + value(const datetime_t& moment = datetime_t(), + const commodity_t * in_terms_of = NULL) const; - amount_t price() const; + optional<amount_t> price() const; /*@}*/ @@ -533,7 +533,10 @@ public: number() returns a commodity-less version of an amount. This is useful for accessing just the numeric portion of an amount. */ - commodity_t& commodity() const; + commodity_t * commodity_ptr() const; + commodity_t& commodity() const { + return *commodity_ptr(); + } bool has_commodity() const; void set_commodity(commodity_t& comm) { diff --git a/src/annotate.cc b/src/annotate.cc index cd1733ca..2b118e76 100644 --- a/src/annotate.cc +++ b/src/annotate.cc @@ -33,11 +33,47 @@ #include "amount.h" #include "commodity.h" +#include "expr.h" #include "annotate.h" #include "pool.h" namespace ledger { +bool annotation_t::operator<(const annotation_t& rhs) const +{ + if (! price && rhs.price) return true; + if (price && ! rhs.price) return false; + if (! date && rhs.date) return true; + if (date && ! rhs.date) return false; + if (! tag && rhs.tag) return true; + if (tag && ! rhs.tag) return false; + + if (! value_expr && rhs.value_expr) return true; + if (value_expr && ! rhs.value_expr) return false; + + if (price) { + if (price->commodity().symbol() < rhs.price->commodity().symbol()) + return true; + if (price->commodity().symbol() > rhs.price->commodity().symbol()) + return false; + if (*price < *rhs.price) return true; + if (*price > *rhs.price) return false; + } + if (date) { + if (*date < *rhs.date) return true; + if (*date > *rhs.date) return false; + } + if (tag) { + if (*tag < *rhs.tag) return true; + if (*tag > *rhs.tag) return false; + } + if (value_expr) { + if (value_expr->text() < rhs.value_expr->text()) return true; + if (value_expr->text() > rhs.value_expr->text()) return false; + } + return false; +} + void annotation_t::parse(std::istream& in) { do { @@ -52,6 +88,12 @@ void annotation_t::parse(std::istream& in) throw_(amount_error, _("Commodity specifies more than one price")); in.get(c); + c = static_cast<char>(in.peek()); + if (c == '{') { + in.get(c); + add_flags(ANNOTATION_PRICE_NOT_PER_UNIT); + } + c = peek_next_nonws(in); if (c == '=') { in.get(c); @@ -59,10 +101,18 @@ void annotation_t::parse(std::istream& in) } READ_INTO(in, buf, 255, c, c != '}'); - if (c == '}') + if (c == '}') { in.get(c); - else - throw_(amount_error, _("Commodity price lacks closing brace")); + if (has_flags(ANNOTATION_PRICE_NOT_PER_UNIT)) { + c = static_cast<char>(in.peek()); + if (c != '}') + throw_(amount_error, _("Commodity lot price lacks double closing brace")); + else + in.get(c); + } + } else { + throw_(amount_error, _("Commodity lot price lacks closing brace")); + } amount_t temp; temp.parse(buf, PARSE_NO_MIGRATE); @@ -84,17 +134,46 @@ void annotation_t::parse(std::istream& in) date = parse_date(buf); } else if (c == '(') { - if (tag) - throw_(amount_error, _("Commodity specifies more than one tag")); - in.get(c); - READ_INTO(in, buf, 255, c, c != ')'); - if (c == ')') - in.get(c); - else - throw_(amount_error, _("Commodity tag lacks closing parenthesis")); + c = static_cast<char>(in.peek()); + if (c == '@') { + in.clear(); + in.seekg(pos, std::ios::beg); + break; + } + else if (c == '(') { + if (value_expr) + throw_(amount_error, + _("Commodity specifies more than one valuation expresion")); - tag = buf; + in.get(c); + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') { + in.get(c); + c = static_cast<char>(in.peek()); + if (c == ')') + in.get(c); + else + throw_(amount_error, + _("Commodity valuation expression lacks closing parentheses")); + } else { + throw_(amount_error, + _("Commodity valuation expression lacks closing parentheses")); + } + + value_expr = expr_t(buf); + } else { + if (tag) + throw_(amount_error, _("Commodity specifies more than one tag")); + + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') + in.get(c); + else + throw_(amount_error, _("Commodity tag lacks closing parenthesis")); + + tag = buf; + } } else { in.clear(); @@ -128,6 +207,10 @@ void annotation_t::print(std::ostream& out, bool keep_base, if (tag && (! no_computed_annotations || ! has_flags(ANNOTATION_TAG_CALCULATED))) out << " (" << *tag << ')'; + + if (value_expr && (! no_computed_annotations || + ! has_flags(ANNOTATION_VALUE_EXPR_CALCULATED))) + out << " ((" << *value_expr << "))"; } bool keep_details_t::keep_all(const commodity_t& comm) const @@ -157,6 +240,54 @@ bool annotated_commodity_t::operator==(const commodity_t& comm) const return true; } +optional<price_point_t> +annotated_commodity_t::find_price(const commodity_t * commodity, + const datetime_t& moment, + const datetime_t& oldest) const +{ + DEBUG("commodity.price.find", + "annotated_commodity_t::find_price(" << symbol() << ")"); + + datetime_t when; + if (! moment.is_not_a_date_time()) + when = moment; + else if (epoch) + when = *epoch; + else + when = CURRENT_TIME(); + + DEBUG("commodity.price.find", "reference time: " << when); + + const commodity_t * target = NULL; + if (commodity) + target = commodity; + + if (details.price) { + DEBUG("commodity.price.find", "price annotation: " << *details.price); + + if (details.has_flags(ANNOTATION_PRICE_FIXATED)) { + DEBUG("commodity.price.find", + "amount_t::value: fixated price = " << *details.price); + return price_point_t(when, *details.price); + } + else if (! target) { + DEBUG("commodity.price.find", "setting target commodity from price"); + target = details.price->commodity_ptr(); + } + } + +#if defined(DEBUG_ON) + if (target) + DEBUG("commodity.price.find", "target commodity: " << target->symbol()); +#endif + + if (details.value_expr) + return find_price_from_expr(const_cast<expr_t&>(*details.value_expr), + commodity, when); + + return commodity_t::find_price(target, moment, oldest); +} + commodity_t& annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep) { @@ -192,7 +323,8 @@ annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep) if ((keep_price && details.price) || (keep_date && details.date) || - (keep_tag && details.tag)) + (keep_tag && details.tag) || + details.value_expr) { new_comm = pool().find_or_create (referent(), annotation_t(keep_price ? details.price : none, diff --git a/src/annotate.h b/src/annotate.h index 3c6db8e8..044ebc4d 100644 --- a/src/annotate.h +++ b/src/annotate.h @@ -46,29 +46,39 @@ #ifndef _ANNOTATE_H #define _ANNOTATE_H +#include "expr.h" + namespace ledger { struct annotation_t : public supports_flags<>, public equality_comparable<annotation_t> { -#define ANNOTATION_PRICE_CALCULATED 0x01 -#define ANNOTATION_PRICE_FIXATED 0x02 -#define ANNOTATION_DATE_CALCULATED 0x04 -#define ANNOTATION_TAG_CALCULATED 0x08 +#define ANNOTATION_PRICE_CALCULATED 0x01 +#define ANNOTATION_PRICE_FIXATED 0x02 +#define ANNOTATION_PRICE_NOT_PER_UNIT 0x04 +#define ANNOTATION_DATE_CALCULATED 0x08 +#define ANNOTATION_TAG_CALCULATED 0x10 +#define ANNOTATION_VALUE_EXPR_CALCULATED 0x20 optional<amount_t> price; optional<date_t> date; optional<string> tag; - - explicit annotation_t(const optional<amount_t>& _price = none, - const optional<date_t>& _date = none, - const optional<string>& _tag = none) - : supports_flags<>(), price(_price), date(_date), tag(_tag) { - TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string"); + optional<expr_t> value_expr; + + explicit annotation_t(const optional<amount_t>& _price = none, + const optional<date_t>& _date = none, + const optional<string>& _tag = none, + const optional<expr_t>& _value_expr = none) + : supports_flags<>(), price(_price), date(_date), tag(_tag), + value_expr(_value_expr) { + TRACE_CTOR(annotation_t, + "const optional<amount_t>& + date_t + string + expr_t"); } annotation_t(const annotation_t& other) : supports_flags<>(other.flags()), - price(other.price), date(other.date), tag(other.tag) { + price(other.price), date(other.date), tag(other.tag), + value_expr(other.value_expr) + { TRACE_CTOR(annotation_t, "copy"); } ~annotation_t() { @@ -76,17 +86,20 @@ struct annotation_t : public supports_flags<>, } operator bool() const { - return price || date || tag; + return price || date || tag || value_expr; } + bool operator<(const annotation_t& rhs) const; bool operator==(const annotation_t& rhs) const { - return (price == rhs.price && - date == rhs.date && - tag == rhs.tag); + return (price == rhs.price && + date == rhs.date && + tag == rhs.tag && + (value_expr && rhs.value_expr ? + value_expr->text() == rhs.value_expr->text() : + value_expr == rhs.value_expr)); } void parse(std::istream& in); - void print(std::ostream& out, bool keep_base = false, bool no_computed_annotations = false) const; @@ -132,6 +145,12 @@ inline void to_xml(std::ostream& out, const annotation_t& details) push_xml y(out, "tag"); out << y.guard(*details.tag); } + + if (details.value_expr) + { + push_xml y(out, "value-expr"); + out << y.guard(details.value_expr->text()); + } } struct keep_details_t @@ -230,6 +249,17 @@ public: return *ptr; } + virtual optional<expr_t> value_expr() const { + if (details.value_expr) + return details.value_expr; + return commodity_t::value_expr(); + } + + optional<price_point_t> + virtual find_price(const commodity_t * commodity = NULL, + const datetime_t& moment = datetime_t(), + const datetime_t& oldest = datetime_t()) const; + virtual commodity_t& strip_annotations(const keep_details_t& what_to_keep); virtual void write_annotations(std::ostream& out, bool no_computed_annotations = false) const; diff --git a/src/balance.cc b/src/balance.cc index 4fba7344..f87e8bbd 100644 --- a/src/balance.cc +++ b/src/balance.cc @@ -185,8 +185,8 @@ balance_t& balance_t::operator/=(const amount_t& amt) } optional<balance_t> -balance_t::value(const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of) const +balance_t::value(const datetime_t& moment, + const commodity_t * in_terms_of) const { balance_t temp; bool resolved = false; @@ -202,16 +202,6 @@ balance_t::value(const optional<datetime_t>& moment, return resolved ? temp : optional<balance_t>(); } -balance_t balance_t::price() const -{ - balance_t temp; - - foreach (const amounts_map::value_type& pair, amounts) - temp += pair.second.price(); - - return temp; -} - optional<amount_t> balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const { diff --git a/src/balance.h b/src/balance.h index 57e6ace4..5f0d52ed 100644 --- a/src/balance.h +++ b/src/balance.h @@ -384,10 +384,8 @@ public: } optional<balance_t> - value(const optional<datetime_t>& moment = none, - const optional<commodity_t&>& in_terms_of = none) const; - - balance_t price() const; + value(const datetime_t& moment = datetime_t(), + const commodity_t * in_terms_of = NULL) const; /** * Truth tests. An balance may be truth test in two ways: diff --git a/src/chain.cc b/src/chain.cc index fc1be5bd..44b3db82 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -88,10 +88,9 @@ post_handler_ptr chain_pre_post_handlers(post_handler_ptr base_handler, predicate_t(report.HANDLER(forecast_while_).str(), report.what_to_keep()), report, - report.HANDLED(forecast_years_) ? - static_cast<std::size_t> - (report.HANDLER(forecast_years_).value.to_long()) : - 5UL); + (report.HANDLED(forecast_years_) ? + lexical_cast<std::size_t> + (report.HANDLER(forecast_years_).value) : 5UL)); forecast_handler->add_period_xacts(report.session.journal->period_xacts); handler.reset(forecast_handler); @@ -115,10 +114,13 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler, predicate_t only_predicate; display_filter_posts * display_filter = NULL; - assert(report.HANDLED(amount_)); expr_t& expr(report.HANDLER(amount_).expr); expr.set_context(&report); + report.HANDLER(total_).expr.set_context(&report); + report.HANDLER(display_amount_).expr.set_context(&report); + report.HANDLER(display_total_).expr.set_context(&report); + if (! for_accounts_report) { // Make sure only forecast postings which match are allowed through if (report.HANDLED(forecast_while_)) { @@ -134,9 +136,9 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler, handler.reset (new truncate_xacts(handler, report.HANDLED(head_) ? - report.HANDLER(head_).value.to_int() : 0, + lexical_cast<int>(report.HANDLER(head_).value) : 0, report.HANDLED(tail_) ? - report.HANDLER(tail_).value.to_int() : 0)); + lexical_cast<int>(report.HANDLER(tail_).value) : 0)); // display_filter_posts adds virtual posts to the list to account // for changes in value of commodities, which otherwise would affect diff --git a/src/chain.h b/src/chain.h index 080c4231..15ae12ba 100644 --- a/src/chain.h +++ b/src/chain.h @@ -50,8 +50,9 @@ class post_t; class account_t; template <typename T> -struct item_handler : public noncopyable +class item_handler : public noncopyable { +protected: shared_ptr<item_handler> handler; public: diff --git a/src/commodity.cc b/src/commodity.cc index 7d473d74..8f0dc100 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -35,6 +35,7 @@ #include "commodity.h" #include "annotate.h" #include "pool.h" +#include "scope.h" namespace ledger { @@ -70,12 +71,12 @@ void commodity_t::remove_price(const datetime_t& date, commodity_t& commodity) } void commodity_t::map_prices(function<void(datetime_t, const amount_t&)> fn, - const optional<datetime_t>& moment, - const optional<datetime_t>& _oldest) + const datetime_t& moment, + const datetime_t& _oldest) { datetime_t when; - if (moment) - when = *moment; + if (! moment.is_not_a_date_time()) + when = moment; else if (epoch) when = *epoch; else @@ -85,78 +86,109 @@ void commodity_t::map_prices(function<void(datetime_t, const amount_t&)> fn, } optional<price_point_t> -commodity_t::find_price(const optional<commodity_t&>& commodity, - const optional<datetime_t>& moment, - const optional<datetime_t>& oldest) const +commodity_t::find_price_from_expr(expr_t& expr, const commodity_t * commodity, + const datetime_t& moment) const { - optional<commodity_t&> target; +#if defined(DEBUG_ON) + if (SHOW_DEBUG("commodity.price.find")) { + ledger::_log_buffer << "valuation expr: "; + expr.dump(ledger::_log_buffer); + DEBUG("commodity.price.find", ""); + } +#endif + value_t result(expr.calc(*scope_t::default_scope)); + + if (is_expr(result)) { + value_t call_args; + + call_args.push_back(string_value(base_symbol())); + call_args.push_back(moment); + if (commodity) + call_args.push_back(string_value(commodity->symbol())); + + result = as_expr(result)->call(call_args, *scope_t::default_scope); + } + + return price_point_t(moment, result.to_amount()); +} + +optional<price_point_t> +commodity_t::find_price(const commodity_t * commodity, + const datetime_t& moment, + const datetime_t& oldest) const +{ + DEBUG("commodity.price.find", "commodity_t::find_price(" << symbol() << ")"); + + const commodity_t * target = NULL; if (commodity) target = commodity; else if (pool().default_commodity) - target = *pool().default_commodity; + target = &*pool().default_commodity; - if (target && *this == *target) + if (target && this == target) return none; - optional<base_t::time_and_commodity_t> pair = - base_t::time_and_commodity_t(base_t::optional_time_pair_t(moment, oldest), - commodity ? &(*commodity) : NULL); + base_t::memoized_price_entry entry(moment, oldest, + commodity ? commodity : NULL); - DEBUG("history.find", "looking for memoized args: " - << (moment ? format_datetime(*moment) : "NONE") << ", " - << (oldest ? format_datetime(*oldest) : "NONE") << ", " + DEBUG("commodity.price.find", "looking for memoized args: " + << (! moment.is_not_a_date_time() ? format_datetime(moment) : "NONE") << ", " + << (! oldest.is_not_a_date_time() ? format_datetime(oldest) : "NONE") << ", " << (commodity ? commodity->symbol() : "NONE")); { - base_t::memoized_price_map::iterator i = base->price_map.find(*pair); + base_t::memoized_price_map::iterator i = base->price_map.find(entry); if (i != base->price_map.end()) { - DEBUG("history.find", "found! returning: " + DEBUG("commodity.price.find", "found! returning: " << ((*i).second ? (*i).second->price : amount_t(0L))); return (*i).second; } } datetime_t when; - if (moment) - when = *moment; + if (! moment.is_not_a_date_time()) + when = moment; else if (epoch) when = *epoch; else when = CURRENT_TIME(); - optional<price_point_t> point = - target ? - pool().commodity_price_history.find_price(*this, *target, when, oldest) : - pool().commodity_price_history.find_price(*this, when, oldest); - - if (pair) { - if (base->price_map.size() > base_t::max_price_map_size) { - DEBUG("history.find", - "price map has grown too large, clearing it by half"); - for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++) - base->price_map.erase(base->price_map.begin()); - } + if (base->value_expr) + return find_price_from_expr(*base->value_expr, commodity, when); + optional<price_point_t> + point(target ? + pool().commodity_price_history.find_price(*this, *target, + when, oldest) : + pool().commodity_price_history.find_price(*this, when, oldest)); + + // Record this price point in the memoization map + if (base->price_map.size() > base_t::max_price_map_size) { DEBUG("history.find", - "remembered: " << (point ? point->price : amount_t(0L))); - base->price_map.insert - (base_t::memoized_price_map::value_type(*pair, point)); + "price map has grown too large, clearing it by half"); + for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++) + base->price_map.erase(base->price_map.begin()); } + + DEBUG("history.find", + "remembered: " << (point ? point->price : amount_t(0L))); + base->price_map.insert(base_t::memoized_price_map::value_type(entry, point)); + return point; } optional<price_point_t> commodity_t::check_for_updated_price(const optional<price_point_t>& point, - const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of) + const datetime_t& moment, + const commodity_t* in_terms_of) { if (pool().get_quotes && ! has_flags(COMMODITY_NOMARKET)) { bool exceeds_leeway = true; if (point) { time_duration_t::sec_type seconds_diff; - if (moment) { - seconds_diff = (*moment - point->when).total_seconds(); - DEBUG("commodity.download", "moment = " << *moment); + if (! moment.is_not_a_date_time()) { + seconds_diff = (moment - point->when).total_seconds(); + DEBUG("commodity.download", "moment = " << moment); DEBUG("commodity.download", "slip.moment = " << seconds_diff); } else { seconds_diff = (TRUE_CURRENT_TIME() - point->when).total_seconds(); @@ -175,7 +207,7 @@ commodity_t::check_for_updated_price(const optional<price_point_t>& point, pool().get_commodity_quote(*this, in_terms_of)) { if (! in_terms_of || (quote->price.has_commodity() && - quote->price.commodity() == *in_terms_of)) + quote->price.commodity_ptr() == in_terms_of)) return quote; } } @@ -183,6 +215,15 @@ commodity_t::check_for_updated_price(const optional<price_point_t>& point, return point; } +commodity_t& commodity_t::nail_down(const expr_t& expr) +{ + annotation_t new_details; + new_details.value_expr = expr; + commodity_t * new_comm = + commodity_pool_t::current_pool->find_or_create(symbol(), new_details); + return *new_comm; +} + commodity_t::operator bool() const { return this != pool().null_commodity; diff --git a/src/commodity.h b/src/commodity.h index 524daaab..bd1aedb9 100644 --- a/src/commodity.h +++ b/src/commodity.h @@ -47,6 +47,8 @@ #ifndef _COMMODITY_H #define _COMMODITY_H +#include "expr.h" + namespace ledger { struct keep_details_t; @@ -113,15 +115,14 @@ protected: optional<string> note; optional<amount_t> smaller; optional<amount_t> larger; + optional<expr_t> value_expr; - typedef std::pair<optional<datetime_t>, - optional<datetime_t> > optional_time_pair_t; - typedef std::pair<optional_time_pair_t, - commodity_t *> time_and_commodity_t; - typedef std::map<time_and_commodity_t, + typedef tuple<datetime_t, datetime_t, + const commodity_t *> memoized_price_entry; + typedef std::map<memoized_price_entry, optional<price_point_t> > memoized_price_map; - static const std::size_t max_price_map_size = 16; + static const std::size_t max_price_map_size = 8; mutable memoized_price_map price_map; public: @@ -164,7 +165,6 @@ protected: commodity_pool_t * parent_; optional<string> qualified_symbol; - optional<string> mapping_key_; bool annotated; explicit commodity_t(commodity_pool_t * _parent, @@ -218,13 +218,6 @@ public: return qualified_symbol ? *qualified_symbol : base_symbol(); } - string mapping_key() const { - if (mapping_key_) - return *mapping_key_; - else - return base_symbol(); - } - optional<std::size_t> graph_index() const {; return base->graph_index; } @@ -267,23 +260,36 @@ public: base->larger = arg; } + virtual optional<expr_t> value_expr() const { + return base->value_expr; + } + void set_value_expr(const optional<expr_t>& expr = none) { + base->value_expr = expr; + } + void add_price(const datetime_t& date, const amount_t& price, const bool reflexive = true); void remove_price(const datetime_t& date, commodity_t& commodity); void map_prices(function<void(datetime_t, const amount_t&)> fn, - const optional<datetime_t>& moment = none, - const optional<datetime_t>& _oldest = none); + const datetime_t& moment = datetime_t(), + const datetime_t& _oldest = datetime_t()); + + optional<price_point_t> + find_price_from_expr(expr_t& expr, const commodity_t * commodity, + const datetime_t& moment) const; optional<price_point_t> - find_price(const optional<commodity_t&>& commodity = none, - const optional<datetime_t>& moment = none, - const optional<datetime_t>& oldest = none) const; + virtual find_price(const commodity_t * commodity = NULL, + const datetime_t& moment = datetime_t(), + const datetime_t& oldest = datetime_t()) const; optional<price_point_t> check_for_updated_price(const optional<price_point_t>& point, - const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of); + const datetime_t& moment, + const commodity_t * in_terms_of); + + commodity_t& nail_down(const expr_t& expr); // Methods related to parsing, reading, writing, etc., the commodity // itself. @@ -325,7 +331,6 @@ private: ar & base; ar & parent_; ar & qualified_symbol; - ar & mapping_key_; ar & annotated; } #endif // HAVE_BOOST_SERIALIZATION @@ -139,8 +139,8 @@ xact_t * csv_reader::read_xact(bool rich_data) std::istringstream instr(line); - std::auto_ptr<xact_t> xact(new xact_t); - std::auto_ptr<post_t> post(new post_t); + unique_ptr<xact_t> xact(new xact_t); + unique_ptr<post_t> post(new post_t); xact->set_state(item_t::CLEARED); diff --git a/src/draft.cc b/src/draft.cc index 7c95caf7..74a6f4d2 100644 --- a/src/draft.cc +++ b/src/draft.cc @@ -109,7 +109,14 @@ void draft_t::parse_args(const value_t& args) } else if (check_for_date && bool(weekday = string_to_day_of_week(what[0]))) { +#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif short dow = static_cast<short>(*weekday); +#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +#pragma GCC diagnostic pop +#endif date_t date = CURRENT_DATE() - date_duration(1); while (date.day_of_week() != dow) date -= date_duration(1); @@ -233,7 +240,7 @@ xact_t * draft_t::insert(journal_t& journal) throw std::runtime_error(_("'xact' command requires at least a payee")); xact_t * matching = NULL; - std::auto_ptr<xact_t> added(new xact_t); + unique_ptr<xact_t> added(new xact_t); if (xact_t * xact = lookup_probable_account(tmpl->payee_mask.str(), journal.xacts.rbegin(), @@ -316,7 +323,7 @@ xact_t * draft_t::insert(journal_t& journal) } foreach (xact_template_t::post_template_t& post, tmpl->posts) { - std::auto_ptr<post_t> new_post; + unique_ptr<post_t> new_post; commodity_t * found_commodity = NULL; @@ -515,7 +522,7 @@ value_t xact_command(call_scope_t& args) xact_t * new_xact = draft.insert(*report.session.journal.get()); // Only consider actual postings for the "xact" command - report.HANDLER(limit_).on(string("#xact"), "actual"); + report.HANDLER(limit_).on("#xact", "actual"); if (new_xact) report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact); diff --git a/src/expr.cc b/src/expr.cc index 74d16ecc..b22572f8 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -37,6 +37,59 @@ namespace ledger { +expr_t::expr_t() : base_type() +{ + TRACE_CTOR(expr_t, ""); +} + +expr_t::expr_t(const expr_t& other) : base_type(other), ptr(other.ptr) +{ + TRACE_CTOR(expr_t, "copy"); +} +expr_t::expr_t(ptr_op_t _ptr, scope_t * _context) + : base_type(_context), ptr(_ptr) +{ + TRACE_CTOR(expr_t, "const ptr_op_t&, scope_t *"); +} + +expr_t::expr_t(const string& _str, const parse_flags_t& flags) + : base_type() +{ + TRACE_CTOR(expr_t, "string, parse_flags_t"); + if (! _str.empty()) + parse(_str, flags); +} + +expr_t::expr_t(std::istream& in, const parse_flags_t& flags) + : base_type() +{ + TRACE_CTOR(expr_t, "std::istream&, parse_flags_t"); + parse(in, flags); +} + +expr_t::~expr_t() { + TRACE_DTOR(expr_t); +} + +expr_t& expr_t::operator=(const expr_t& _expr) +{ + if (this != &_expr) { + base_type::operator=(_expr); + ptr = _expr.ptr; + } + return *this; +} + +expr_t::operator bool() const throw() +{ + return ptr.get() != NULL; +} + +expr_t::ptr_op_t expr_t::get_op() throw() +{ + return ptr; +} + void expr_t::parse(std::istream& in, const parse_flags_t& flags, const optional<string>& original_string) { @@ -163,6 +216,65 @@ void expr_t::dump(std::ostream& out) const if (ptr) ptr->dump(out, 0); } +bool merged_expr_t::check_for_single_identifier(const string& expr) +{ + bool single_identifier = true; + for (const char * p = expr.c_str(); *p; ++p) + if (! std::isalnum(*p) || *p == '_') { + single_identifier = false; + break; + } + + if (single_identifier) { + set_base_expr(expr); + exprs.clear(); + return true; + } else { + return false; + } +} + +void merged_expr_t::compile(scope_t& scope) +{ + if (exprs.empty()) { + parse(base_expr); + } else { + std::ostringstream buf; + + buf << "__tmp_" << term << "=(" << term << "=(" << base_expr << ")"; + foreach (const string& expr, exprs) { + if (merge_operator == ";") + buf << merge_operator << term << "=" << expr; + else + buf << merge_operator << "(" << expr << ")"; + } + buf << ";" << term << ");__tmp_" << term; + + DEBUG("expr.merged.compile", "Compiled expr: " << buf.str()); + parse(buf.str()); + } + + expr_t::compile(scope); +} + +expr_t::ptr_op_t as_expr(const value_t& val) +{ + VERIFY(val.is_any()); + return val.as_any<expr_t::ptr_op_t>(); +} + +void set_expr(value_t& val, expr_t::ptr_op_t op) +{ + val.set_any(op); +} + +value_t expr_value(expr_t::ptr_op_t op) +{ + value_t temp; + temp.set_any(op); + return temp; +} + value_t source_command(call_scope_t& args) { std::istream * in = NULL; @@ -71,49 +71,20 @@ protected: ptr_op_t ptr; public: - expr_t() : base_type() { - TRACE_CTOR(expr_t, ""); - } - expr_t(const expr_t& other) - : base_type(other), ptr(other.ptr) { - TRACE_CTOR(expr_t, "copy"); - } - expr_t(ptr_op_t _ptr, scope_t * _context = NULL) - : base_type(_context), ptr(_ptr) { - TRACE_CTOR(expr_t, "const ptr_op_t&, scope_t *"); - } + expr_t(); + expr_t(const expr_t& other); + expr_t(ptr_op_t _ptr, scope_t * _context = NULL); - expr_t(const string& _str, const parse_flags_t& flags = PARSE_DEFAULT) - : base_type() { - TRACE_CTOR(expr_t, "string, parse_flags_t"); - if (! _str.empty()) - parse(_str, flags); - } - expr_t(std::istream& in, const parse_flags_t& flags = PARSE_DEFAULT) - : base_type() { - TRACE_CTOR(expr_t, "std::istream&, parse_flags_t"); - parse(in, flags); - } + expr_t(const string& _str, const parse_flags_t& flags = PARSE_DEFAULT); + expr_t(std::istream& in, const parse_flags_t& flags = PARSE_DEFAULT); - virtual ~expr_t() { - TRACE_DTOR(expr_t); - } + virtual ~expr_t(); - expr_t& operator=(const expr_t& _expr) { - if (this != &_expr) { - base_type::operator=(_expr); - ptr = _expr.ptr; - } - return *this; - } + expr_t& operator=(const expr_t& _expr); - virtual operator bool() const throw() { - return ptr.get() != NULL; - } + virtual operator bool() const throw(); - ptr_op_t get_op() throw() { - return ptr; - } + ptr_op_t get_op() throw(); void parse(const string& str, const parse_flags_t& flags = PARSE_DEFAULT) { std::istringstream stream(str); @@ -156,21 +127,74 @@ private: inline bool is_expr(const value_t& val) { return val.is_any() && val.as_any().type() == typeid(expr_t::ptr_op_t); } -inline expr_t::ptr_op_t as_expr(const value_t& val) { - VERIFY(val.is_any()); - return val.as_any<expr_t::ptr_op_t>(); -} -inline void set_expr(value_t& val, expr_t::ptr_op_t op) { - val.set_any(op); -} -inline value_t expr_value(expr_t::ptr_op_t op) { - value_t temp; - temp.set_any(op); - return temp; -} -class call_scope_t; +expr_t::ptr_op_t as_expr(const value_t& val); +void set_expr(value_t& val, expr_t::ptr_op_t op); +value_t expr_value(expr_t::ptr_op_t op); + +// A merged expression allows one to set an expression term, "foo", and +// a base expression, "bar", and then merge in later expressions that +// utilize foo. For example: +// +// foo: bar +// merge: foo * 10 +// merge: foo + 20 +// +// When this expression is finally compiled, the base and merged +// elements are written into this: +// +// __tmp=(foo=bar; foo=foo*10; foo=foo+20);__tmp +// +// This allows users to select flags like -O, -B or -I at any time, and +// also combine flags such as -V and -A. + +class merged_expr_t : public expr_t +{ +public: + string term; + string base_expr; + string merge_operator; + + std::list<string> exprs; + merged_expr_t(const string& _term, const string& expr, + const string& merge_op = ";") + : expr_t(), term(_term), base_expr(expr), merge_operator(merge_op) { + TRACE_CTOR(merged_expr_t, "string, string, string"); + } + + virtual ~merged_expr_t() { + TRACE_DTOR(merged_expr_t); + } + + void set_term(const string& _term) { + term = _term; + } + void set_base_expr(const string& expr) { + base_expr = expr; + } + void set_merge_operator(const string& merge_op) { + merge_operator = merge_op; + } + + bool check_for_single_identifier(const string& expr); + + void prepend(const string& expr) { + if (! check_for_single_identifier(expr)) + exprs.push_front(expr); + } + void append(const string& expr) { + if (! check_for_single_identifier(expr)) + exprs.push_back(expr); + } + void remove(const string& expr) { + exprs.remove(expr); + } + + virtual void compile(scope_t& scope); +}; + +class call_scope_t; value_t source_command(call_scope_t& scope); } // namespace ledger diff --git a/src/exprbase.h b/src/exprbase.h index 0b1ef243..bea25320 100644 --- a/src/exprbase.h +++ b/src/exprbase.h @@ -113,7 +113,7 @@ public: return ! str.empty(); } - virtual string text() { + virtual string text() const throw() { return str; } void set_text(const string& txt) { @@ -163,7 +163,7 @@ public: } #endif // defined(DEBUG_ON) - DEBUG("expr.calc.when", "Compiling: " << str); + DEBUG("expr.compile", "Compiling: " << str); compile(scope); #if defined(DEBUG_ON) @@ -174,7 +174,7 @@ public: #endif // defined(DEBUG_ON) } - DEBUG("expr.calc.when", "Calculating: " << str); + DEBUG("expr.calc", "Calculating: " << str); return real_calc(scope); } diff --git a/src/filters.cc b/src/filters.cc index 8543cddb..3a975920 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -833,10 +833,17 @@ void subtotal_posts::report_subtotal(const char * spec_fmt, foreach (post_t * post, component_posts) { date_t date = post->date(); date_t value_date = post->value_date(); +#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif if (! range_start || date < *range_start) range_start = date; if (! range_finish || value_date > *range_finish) range_finish = value_date; +#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +#pragma GCC diagnostic pop +#endif } } component_posts.clear(); @@ -880,9 +887,13 @@ void subtotal_posts::operator()(post_t& post) if (i == values.end()) { value_t temp; post.add_to_value(temp, amount_expr); - std::pair<values_map::iterator, bool> result - = values.insert(values_pair(acct->fullname(), acct_value_t(acct, temp))); +#if defined(DEBUG_ON) + std::pair<values_map::iterator, bool> result = +#endif + values.insert(values_pair(acct->fullname(), acct_value_t(acct, temp))); +#if defined(DEBUG_ON) assert(result.second); +#endif } else { post.add_to_value((*i).second.value, amount_expr); } diff --git a/src/filters.h b/src/filters.h index 22f2d2cb..af68cd7c 100644 --- a/src/filters.h +++ b/src/filters.h @@ -65,14 +65,14 @@ protected: value_to_posts_map posts_map; post_handler_ptr post_chain; report_t& report; - expr_t group_by_expr; + expr_t& group_by_expr; custom_flusher_t preflush_func; optional<custom_flusher_t> postflush_func; public: post_splitter(post_handler_ptr _post_chain, report_t& _report, - expr_t _group_by_expr) + expr_t& _group_by_expr) : post_chain(_post_chain), report(_report), group_by_expr(_group_by_expr) { TRACE_CTOR(post_splitter, "scope_t&, post_handler_ptr, expr_t"); @@ -372,6 +372,7 @@ public: } virtual ~anonymize_posts() { TRACE_DTOR(anonymize_posts); + handler.reset(); } void render_commodity(amount_t& amt); @@ -451,6 +452,7 @@ public: } virtual ~collapse_posts() { TRACE_DTOR(collapse_posts); + handler.reset(); } void create_accounts() { @@ -521,8 +523,8 @@ class display_filter_posts : public item_handler<post_t> // later in the chain. report_t& report; - expr_t display_amount_expr; - expr_t display_total_expr; + expr_t& display_amount_expr; + expr_t& display_total_expr; bool show_rounding; value_t last_display_total; temporaries_t temps; @@ -539,6 +541,7 @@ public: virtual ~display_filter_posts() { TRACE_DTOR(display_filter_posts); + handler.reset(); } void create_accounts() { @@ -569,8 +572,8 @@ class changed_value_posts : public item_handler<post_t> // later in the chain. report_t& report; - expr_t total_expr; - expr_t display_total_expr; + expr_t& total_expr; + expr_t& display_total_expr; bool changed_values_only; bool for_accounts_report; bool show_unrealized; @@ -595,6 +598,7 @@ public: virtual ~changed_value_posts() { TRACE_DTOR(changed_value_posts); + handler.reset(); } void create_accounts() { @@ -671,6 +675,7 @@ public: } virtual ~subtotal_posts() { TRACE_DTOR(subtotal_posts); + handler.reset(); } void report_subtotal(const char * spec_fmt = NULL, @@ -849,6 +854,7 @@ public: } virtual ~transfer_details() { TRACE_DTOR(transfer_details); + handler.reset(); } virtual void operator()(post_t& post); @@ -908,6 +914,7 @@ public: virtual ~generate_posts() { TRACE_DTOR(generate_posts); + handler.reset(); } void add_period_xacts(period_xacts_list& period_xacts); @@ -995,6 +1002,7 @@ class inject_posts : public item_handler<post_t> virtual ~inject_posts() throw() { TRACE_DTOR(inject_posts); + handler.reset(); } virtual void operator()(post_t& post); diff --git a/src/format.cc b/src/format.cc index 9824d5f7..79f94869 100644 --- a/src/format.cc +++ b/src/format.cc @@ -125,7 +125,7 @@ namespace { format_t::element_t * format_t::parse_elements(const string& fmt, const optional<format_t&>& tmpl) { - std::auto_ptr<element_t> result; + unique_ptr<element_t> result; element_t * current = NULL; @@ -221,8 +221,10 @@ format_t::element_t * format_t::parse_elements(const string& fmt, static_cast<int>(current->max_width) : -1); else if (keyword == "left") expr << (current->has_flags(ELEMENT_ALIGN_LEFT) ? "false" : "true"); +#if defined(DEBUG_ON) else assert("Unrecognized format substitution keyword" == NULL); +#endif } else { expr << *ptr++; } diff --git a/src/global.cc b/src/global.cc index 5b7bb1c1..d7742161 100644 --- a/src/global.cc +++ b/src/global.cc @@ -70,6 +70,7 @@ global_scope_t::global_scope_t(char ** envp) // generated. report_stack.push_front(new report_t(*session_ptr)); scope_t::default_scope = &report(); + scope_t::empty_scope = &empty_scope; // Read the user's options, in the following order: // @@ -192,7 +193,7 @@ void global_scope_t::execute_command(strings_list args, bool at_repl) is_precommand = true; // If it is not a pre-command, then parse the user's ledger data at this - // time if not done alreday (i.e., if not at a REPL). Then patch up the + // time if not done already (i.e., if not at a REPL). Then patch up the // report options based on the command verb. if (! is_precommand) { diff --git a/src/global.h b/src/global.h index 2cb7842e..392b03a9 100644 --- a/src/global.h +++ b/src/global.h @@ -50,6 +50,7 @@ class global_scope_t : public noncopyable, public scope_t { shared_ptr<session_t> session_ptr; ptr_list<report_t> report_stack; + empty_scope_t empty_scope; public: global_scope_t(char ** envp); @@ -82,6 +83,7 @@ public: void pop_report() { assert(! report_stack.empty()); report_stack.pop_front(); + // There should always be the "default report" waiting on the stack. assert(! report_stack.empty()); scope_t::default_scope = &report(); @@ -139,7 +141,6 @@ See LICENSE file included with the distribution for details and disclaimer."); OPTION__ (global_scope_t, init_file_, // -i - CTOR(global_scope_t, init_file_) { if (const char * home_var = std::getenv("HOME")) on(none, (path(home_var) / ".ledgerrc").string()); diff --git a/src/history.cc b/src/history.cc index c4f6b3fc..22ac4494 100644 --- a/src/history.cc +++ b/src/history.cc @@ -42,13 +42,89 @@ struct f_max : public std::binary_function<T, T, bool> { namespace ledger { +template <typename EdgeWeightMap, + typename PricePointMap, + typename PriceRatioMap> +class recent_edge_weight +{ +public: + EdgeWeightMap weight; + PricePointMap price_point; + PriceRatioMap ratios; + + datetime_t reftime; + datetime_t oldest; + + recent_edge_weight() { } + recent_edge_weight(EdgeWeightMap _weight, + PricePointMap _price_point, + PriceRatioMap _ratios, + const datetime_t& _reftime, + const datetime_t& _oldest = datetime_t()) + : weight(_weight), price_point(_price_point), ratios(_ratios), + reftime(_reftime), oldest(_oldest) { } + + template <typename Edge> + bool operator()(const Edge& e) const + { +#if defined(DEBUG_ON) + DEBUG("history.find", " reftime = " << reftime); + if (! oldest.is_not_a_date_time()) { + DEBUG("history.find", " oldest = " << oldest); + } +#endif + + const price_map_t& prices(get(ratios, e)); + if (prices.empty()) { + DEBUG("history.find", " prices map is empty for this edge"); + return false; + } + + price_map_t::const_iterator low = prices.upper_bound(reftime); + if (low != prices.end() && low == prices.begin()) { + DEBUG("history.find", " don't use this edge"); + return false; + } else { + --low; + assert(((*low).first <= reftime)); + + if (! oldest.is_not_a_date_time() && (*low).first < oldest) { + DEBUG("history.find", " edge is out of range"); + return false; + } + + long secs = (reftime - (*low).first).total_seconds(); + assert(secs >= 0); + + put(weight, e, secs); + put(price_point, e, price_point_t((*low).first, (*low).second)); + + DEBUG("history.find", " using edge at price point " + << (*low).first << " " << (*low).second); + return true; + } + } +}; + +typedef filtered_graph + <commodity_history_t::Graph, + recent_edge_weight<commodity_history_t::EdgeWeightMap, + commodity_history_t::PricePointMap, + commodity_history_t::PriceRatioMap> > FGraph; + +typedef property_map<FGraph, vertex_name_t>::type FNameMap; +typedef property_map<FGraph, vertex_index_t>::type FIndexMap; +typedef iterator_property_map + <commodity_history_t::vertex_descriptor*, FIndexMap, + commodity_history_t::vertex_descriptor, + commodity_history_t::vertex_descriptor&> FPredecessorMap; +typedef iterator_property_map<long*, FIndexMap, long, long&> FDistanceMap; + void commodity_history_t::add_commodity(commodity_t& comm) { if (! comm.graph_index()) { - std::size_t index = num_vertices(price_graph); - comm.set_graph_index(index); - const vertex_descriptor vert = add_vertex(&comm, price_graph); - put(indexmap, vert, index); + comm.set_graph_index(num_vertices(price_graph)); + add_vertex(/* vertex_name= */ &comm, price_graph); } } @@ -59,7 +135,10 @@ void commodity_history_t::add_price(const commodity_t& source, vertex_descriptor sv = vertex(*source.graph_index(), price_graph); vertex_descriptor tv = vertex(*price.commodity().graph_index(), price_graph); - std::pair<edge_descriptor, bool> e1 = add_edge(sv, tv, 0, price_graph); + std::pair<edge_descriptor, bool> e1 = edge(sv, tv, price_graph); + if (! e1.second) + e1 = add_edge(sv, tv, price_graph); + price_map_t& prices(get(ratiomap, e1.first)); std::pair<price_map_t::iterator, bool> result = @@ -67,9 +146,6 @@ void commodity_history_t::add_price(const commodity_t& source, if (! result.second) { // There is already an entry for this moment, so update it (*result.first).second = price; - } else { - last_reftime = none; // invalidate the FGraph cache - last_oldest = none; } } @@ -80,31 +156,30 @@ void commodity_history_t::remove_price(const commodity_t& source, vertex_descriptor sv = vertex(*source.graph_index(), price_graph); vertex_descriptor tv = vertex(*target.graph_index(), price_graph); - std::pair<edge_descriptor, bool> e1 = add_edge(sv, tv, 0, price_graph); - price_map_t& prices(get(ratiomap, e1.first)); + std::pair<Graph::edge_descriptor, bool> e1 = edge(sv, tv, price_graph); + if (e1.second) { + price_map_t& prices(get(ratiomap, e1.first)); - // jww (2012-03-04): If it fails, should we give a warning? - prices.erase(date); + // jww (2012-03-04): If it fails, should we give a warning? + prices.erase(date); - last_reftime = none; // invalidate the FGraph cache - last_oldest = none; + if (prices.empty()) + remove_edge(e1.first, price_graph); + } } void commodity_history_t::map_prices(function<void(datetime_t, const amount_t&)> fn, - const commodity_t& source, - const datetime_t& moment, - const optional<datetime_t>& _oldest) + const commodity_t& source, + const datetime_t& moment, + const datetime_t& oldest) { vertex_descriptor sv = vertex(*source.graph_index(), price_graph); - reftime = moment; - oldest = _oldest; - FGraph fg(price_graph, recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> (get(edge_weight, price_graph), pricemap, ratiomap, - &reftime, &last_reftime, &oldest, &last_oldest)); + moment, oldest)); FNameMap namemap(get(vertex_name, fg)); @@ -118,7 +193,7 @@ void commodity_history_t::map_prices(function<void(datetime_t, foreach (const price_map_t::value_type& pair, prices) { const datetime_t& when(pair.first); - if ((! _oldest || when >= *_oldest) && when <= moment) { + if ((oldest.is_not_a_date_time() || when >= oldest) && when <= moment) { if (pair.second.commodity() == source) { amount_t price(pair.second); price.in_place_invert(); @@ -134,19 +209,16 @@ void commodity_history_t::map_prices(function<void(datetime_t, } optional<price_point_t> -commodity_history_t::find_price(const commodity_t& source, - const datetime_t& moment, - const optional<datetime_t>& _oldest) +commodity_history_t::find_price(const commodity_t& source, + const datetime_t& moment, + const datetime_t& oldest) { vertex_descriptor sv = vertex(*source.graph_index(), price_graph); - reftime = moment; - oldest = _oldest; - FGraph fg(price_graph, recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> (get(edge_weight, price_graph), pricemap, ratiomap, - &reftime, &last_reftime, &oldest, &last_oldest)); + moment, oldest)); FNameMap namemap(get(vertex_name, fg)); @@ -188,9 +260,6 @@ commodity_history_t::find_price(const commodity_t& source, DEBUG("history.find", "price is = " << price.unrounded()); } - last_reftime = reftime; // invalidate the FGraph cache - last_oldest = oldest; - if (price.is_null()) { DEBUG("history.find", "there is no final price"); return none; @@ -201,32 +270,30 @@ commodity_history_t::find_price(const commodity_t& source, } optional<price_point_t> -commodity_history_t::find_price(const commodity_t& source, - const commodity_t& target, - const datetime_t& moment, - const optional<datetime_t>& _oldest) +commodity_history_t::find_price(const commodity_t& source, + const commodity_t& target, + const datetime_t& moment, + const datetime_t& oldest) { vertex_descriptor sv = vertex(*source.graph_index(), price_graph); vertex_descriptor tv = vertex(*target.graph_index(), price_graph); - reftime = moment; - oldest = _oldest; - FGraph fg(price_graph, recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> (get(edge_weight, price_graph), pricemap, ratiomap, - &reftime, &last_reftime, &oldest, &last_oldest)); + moment, oldest)); FNameMap namemap(get(vertex_name, fg)); DEBUG("history.find", "sv commodity = " << get(namemap, sv)->symbol()); DEBUG("history.find", "tv commodity = " << get(namemap, tv)->symbol()); - std::vector<vertex_descriptor> predecessors(num_vertices(fg)); - std::vector<long> distances(num_vertices(fg)); - - PredecessorMap predecessorMap(&predecessors[0]); - DistanceMap distanceMap(&distances[0]); + std::size_t vector_len(num_vertices(fg)); + std::vector<vertex_descriptor> predecessors(vector_len); + std::vector<long> distances(vector_len); + + FPredecessorMap predecessorMap(&predecessors[0]); + FDistanceMap distanceMap(&distances[0]); dijkstra_shortest_paths(fg, /* start= */ sv, predecessor_map(predecessorMap) @@ -235,10 +302,15 @@ commodity_history_t::find_price(const commodity_t& source, // Extract the shortest path and performance the calculations datetime_t least_recent = moment; - amount_t price; + amount_t price; const commodity_t * last_target = ⌖ + typedef tuple<const commodity_t *, const commodity_t *, + const price_point_t *> results_tuple; + std::vector<results_tuple> results; + bool results_reversed = false; + vertex_descriptor v = tv; for (vertex_descriptor u = predecessorMap[v]; u != v; @@ -247,8 +319,24 @@ commodity_history_t::find_price(const commodity_t& source, std::pair<Graph::edge_descriptor, bool> edgePair = edge(u, v, fg); Graph::edge_descriptor edge = edgePair.first; + const commodity_t * u_comm = get(namemap, u); + const commodity_t * v_comm = get(namemap, v); const price_point_t& point(get(pricemap, edge)); + if (v == tv && u_comm != last_target && v_comm != last_target) + results_reversed = true; + + results.push_back(results_tuple(u_comm, v_comm, &point)); + } + + if (results_reversed) + std::reverse(results.begin(), results.end()); + + foreach (const results_tuple& edge, results) { + const commodity_t * u_comm = edge.get<0>(); + const commodity_t * v_comm = edge.get<1>(); + const price_point_t& point(*edge.get<2>()); + bool first_run = false; if (price.is_null()) { least_recent = point.when; @@ -258,8 +346,8 @@ commodity_history_t::find_price(const commodity_t& source, least_recent = point.when; } - DEBUG("history.find", "u commodity = " << get(namemap, u)->symbol()); - DEBUG("history.find", "v commodity = " << get(namemap, v)->symbol()); + DEBUG("history.find", "u commodity = " << u_comm->symbol()); + DEBUG("history.find", "v commodity = " << v_comm->symbol()); DEBUG("history.find", "last target = " << last_target->symbol()); // Determine which direction we are converting in @@ -268,12 +356,12 @@ commodity_history_t::find_price(const commodity_t& source, if (! first_run) { DEBUG("history.find", "price was = " << price.unrounded()); - if (pprice.commodity() != *last_target) + if (pprice.commodity_ptr() != last_target) price *= pprice.inverted(); else price *= pprice; } - else if (pprice.commodity() != *last_target) { + else if (pprice.commodity_ptr() != last_target) { price = pprice.inverted(); } else { @@ -281,17 +369,14 @@ commodity_history_t::find_price(const commodity_t& source, } DEBUG("history.find", "price is = " << price.unrounded()); - if (*last_target == *get(namemap, v)) - last_target = get(namemap, u); + if (last_target == v_comm) + last_target = u_comm; else - last_target = get(namemap, v); + last_target = v_comm; DEBUG("history.find", "last target now = " << last_target->symbol()); } - last_reftime = reftime; // invalidate the FGraph cache - last_oldest = oldest; - if (price.is_null()) { DEBUG("history.find", "there is no final price"); return none; @@ -317,25 +402,16 @@ private: Name name; }; -void commodity_history_t::print_map(std::ostream& out, - const optional<datetime_t>& moment) +void commodity_history_t::print_map(std::ostream& out, const datetime_t& moment) { - if (moment) { - reftime = *moment; - oldest = none; - + if (moment.is_not_a_date_time()) { + write_graphviz(out, price_graph, + label_writer<NameMap>(get(vertex_name, price_graph))); + } else { FGraph fg(price_graph, recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> - (get(edge_weight, price_graph), pricemap, ratiomap, - &reftime, &last_reftime, &oldest, &last_oldest)); - + (get(edge_weight, price_graph), pricemap, ratiomap, moment)); write_graphviz(out, fg, label_writer<FNameMap>(get(vertex_name, fg))); - - last_reftime = reftime; - last_oldest = none; - } else { - write_graphviz(out, price_graph, - label_writer<NameMap>(get(vertex_name, price_graph))); } } diff --git a/src/history.h b/src/history.h index fc984c1b..71cbad0c 100644 --- a/src/history.h +++ b/src/history.h @@ -60,93 +60,12 @@ namespace ledger { typedef std::map<datetime_t, amount_t> price_map_t; -template <typename EdgeWeightMap, - typename PricePointMap, - typename PriceRatioMap> -class recent_edge_weight -{ -public: - EdgeWeightMap weight; - PricePointMap price_point; - PriceRatioMap ratios; - - datetime_t * reftime; - optional<datetime_t> * last_reftime; - optional<datetime_t> * oldest; - optional<datetime_t> * last_oldest; - - recent_edge_weight() { } - recent_edge_weight(EdgeWeightMap _weight, - PricePointMap _price_point, - PriceRatioMap _ratios, - datetime_t * _reftime, - optional<datetime_t> * _last_reftime, - optional<datetime_t> * _oldest, - optional<datetime_t> * _last_oldest) - : weight(_weight), price_point(_price_point), ratios(_ratios), - reftime(_reftime), last_reftime(_last_reftime), - oldest(_oldest), last_oldest(_last_oldest) { } - - template <typename Edge> - bool operator()(const Edge& e) const - { - DEBUG("history.find", " reftime = " << *reftime); - if (*last_reftime) - DEBUG("history.find", " last_reftime = " << **last_reftime); - if (*oldest) - DEBUG("history.find", " oldest = " << **oldest); - if (*last_oldest) - DEBUG("history.find", " last_oldest = " << **last_oldest); - -#if 0 - if (*last_reftime && *reftime == **last_reftime && - *oldest == *last_oldest) { - DEBUG("history.find", " using previous reftime"); - return get(weight, e) != std::numeric_limits<std::size_t>::max(); - } -#endif - - const price_map_t& prices(get(ratios, e)); - if (prices.empty()) { - DEBUG("history.find", " prices map is empty for this edge"); - put(weight, e, std::numeric_limits<std::size_t>::max()); - return false; - } - - price_map_t::const_iterator low = prices.upper_bound(*reftime); - if (low != prices.end() && low == prices.begin()) { - DEBUG("history.find", " don't use this edge"); - put(weight, e, std::numeric_limits<std::size_t>::max()); - return false; - } else { - --low; - assert(((*low).first <= *reftime)); - - if (*oldest && (*low).first < **oldest) { - DEBUG("history.find", " edge is out of range"); - put(weight, e, std::numeric_limits<std::size_t>::max()); - return false; - } - - long secs = (*reftime - (*low).first).total_seconds(); - assert(secs >= 0); - - put(weight, e, secs); - put(price_point, e, price_point_t((*low).first, (*low).second)); - - DEBUG("history.find", " using edge at price point " - << (*low).first << " " << (*low).second); - return true; - } - } -}; - class commodity_history_t : public noncopyable { public: typedef adjacency_list - <setS, // Store all edges as a set - setS, // Store all vertices in a set + <vecS, // Store all edges in a vector + vecS, // Store all vertices in a vector undirectedS, // Relations are both ways // All vertices are commodities @@ -170,36 +89,16 @@ public: typedef graph_traits<Graph>::vertex_descriptor vertex_descriptor; typedef graph_traits<Graph>::edge_descriptor edge_descriptor; - typedef property_map<Graph, vertex_index_t>::type IndexMap; - typedef property_map<Graph, vertex_name_t>::type NameMap; - - typedef iterator_property_map<vertex_descriptor*, IndexMap, - vertex_descriptor, - vertex_descriptor&> PredecessorMap; - typedef iterator_property_map<long*, IndexMap, long, long&> DistanceMap; - + typedef property_map<Graph, vertex_name_t>::type NameMap; typedef property_map<Graph, edge_weight_t>::type EdgeWeightMap; typedef property_map<Graph, edge_price_point_t>::type PricePointMap; typedef property_map<Graph, edge_price_ratio_t>::type PriceRatioMap; - IndexMap indexmap; PricePointMap pricemap; PriceRatioMap ratiomap; - typedef filtered_graph<Graph, recent_edge_weight<EdgeWeightMap, - PricePointMap, - PriceRatioMap> > FGraph; - typedef property_map<FGraph, vertex_name_t>::type FNameMap; - - // jww (2012-03-05): Prevents threading - mutable datetime_t reftime; - mutable optional<datetime_t> last_reftime; - mutable optional<datetime_t> oldest; - mutable optional<datetime_t> last_oldest; - commodity_history_t() - : indexmap(get(vertex_index, price_graph)), - pricemap(get(edge_price_point, price_graph)), + : pricemap(get(edge_price_point, price_graph)), ratiomap(get(edge_price_ratio, price_graph)) {} void add_commodity(commodity_t& comm); @@ -212,23 +111,22 @@ public: const datetime_t& date); void map_prices(function<void(datetime_t, const amount_t&)> fn, - const commodity_t& source, - const datetime_t& moment, - const optional<datetime_t>& _oldest = none); + const commodity_t& source, + const datetime_t& moment, + const datetime_t& _oldest = datetime_t()); optional<price_point_t> - find_price(const commodity_t& source, - const datetime_t& moment, - const optional<datetime_t>& oldest = none); + find_price(const commodity_t& source, + const datetime_t& moment, + const datetime_t& oldest = datetime_t()); optional<price_point_t> - find_price(const commodity_t& source, - const commodity_t& target, - const datetime_t& moment, - const optional<datetime_t>& oldest = none); + find_price(const commodity_t& source, + const commodity_t& target, + const datetime_t& moment, + const datetime_t& oldest = datetime_t()); - void print_map(std::ostream& out, - const optional<datetime_t>& moment = none); + void print_map(std::ostream& out, const datetime_t& moment = datetime_t()); }; } // namespace ledger diff --git a/src/item.cc b/src/item.cc index 3a2b0b60..4ddbe913 100644 --- a/src/item.cc +++ b/src/item.cc @@ -172,19 +172,7 @@ void item_t::parse_tags(const char * p, q = std::strtok(NULL, " \t")) { const string::size_type len = std::strlen(q); if (len < 2) continue; - if (! tag.empty()) { - string_map::iterator i; - string field(p + (q - buf.get())); - if (by_value) { - bind_scope_t bound_scope(scope, *this); - i = set_tag(tag, expr_t(field).calc(bound_scope), overwrite_existing); - } else { - i = set_tag(tag, string_value(field), overwrite_existing); - } - (*i).second.second = true; - break; - } - else if (q[0] == ':' && q[len - 1] == ':') { // a series of tags + if (q[0] == ':' && q[len - 1] == ':') { // a series of tags for (char * r = std::strtok(q + 1, ":"); r; r = std::strtok(NULL, ":")) { @@ -199,6 +187,18 @@ void item_t::parse_tags(const char * p, index = 2; } tag = string(q, len - index); + + string_map::iterator i; + string field(p + len + index); + trim(field); + if (by_value) { + bind_scope_t bound_scope(scope, *this); + i = set_tag(tag, expr_t(field).calc(bound_scope), overwrite_existing); + } else { + i = set_tag(tag, string_value(field), overwrite_existing); + } + (*i).second.second = true; + break; } first = false; } diff --git a/src/iterators.h b/src/iterators.h index 6d490259..5bb9de6f 100644 --- a/src/iterators.h +++ b/src/iterators.h @@ -169,8 +169,8 @@ protected: journal_posts_iterator journal_posts; xacts_iterator xacts; xact_posts_iterator posts; - temporaries_t temps; xacts_list xact_temps; + temporaries_t temps; public: posts_commodities_iterator() {} diff --git a/src/journal.cc b/src/journal.cc index 55c89dd9..37eacdaf 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -122,14 +122,21 @@ account_t * journal_t::register_account(const string& name, post_t * post, { account_t * result = NULL; + // If there any account aliases, substitute before creating an account + // object. if (account_aliases.size() > 0) { accounts_map::const_iterator i = account_aliases.find(name); if (i != account_aliases.end()) result = (*i).second; } + + // Create the account object and associate it with the journal; this + // is registering the account. if (! result) result = master_account->find_account(name); + // If the account name being registered is "Unknown", check whether + // the payee indicates an account that should be used. if (result->name == _("Unknown")) { foreach (account_mapping_t& value, payees_for_unknown_accounts) { if (value.first.match(post->xact->payee)) { @@ -139,6 +146,8 @@ account_t * journal_t::register_account(const string& name, post_t * post, } } + // Now that we have an account, make certain that the account is + // "known", if the user has requested validation of that fact. if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) { if (! result->has_flags(ACCOUNT_KNOWN)) { if (! post) { @@ -313,10 +322,12 @@ bool journal_t::add_xact(xact_t * xact) } extend_xact(xact); - check_all_metadata(*this, xact); - foreach (post_t * post, xact->posts) + + foreach (post_t * post, xact->posts) { + extend_post(*post, *this); check_all_metadata(*this, post); + } // If a transaction with this UUID has already been seen, simply do // not add this one to the journal. However, all automated checks diff --git a/src/journal.h b/src/journal.h index 8b750993..ca73c415 100644 --- a/src/journal.h +++ b/src/journal.h @@ -127,16 +127,17 @@ public: bool fixed_payees; bool fixed_commodities; bool fixed_metadata; + bool was_loaded; + bool force_checking; + bool check_payees; payee_mappings_t payee_mappings; account_mappings_t account_mappings; accounts_map account_aliases; account_mappings_t payees_for_unknown_accounts; checksum_map_t checksum_map; tag_check_exprs_map tag_check_exprs; + optional<expr_t> value_expr; parse_context_t * current_context; - bool was_loaded; - bool force_checking; - bool check_payees; enum checking_style_t { CHECK_PERMISSIVE, diff --git a/src/main.cc b/src/main.cc index aafbdbcb..dc0798e3 100644 --- a/src/main.cc +++ b/src/main.cc @@ -80,12 +80,12 @@ int main(int argc, char * argv[], char * envp[]) ::textdomain("ledger"); #endif - std::auto_ptr<global_scope_t> global_scope; + global_scope_t * global_scope = NULL; try { // Create the session object, which maintains nearly all state relating to // this invocation of Ledger; and register all known journal parsers. - global_scope.reset(new global_scope_t(envp)); + global_scope = new global_scope_t(envp); global_scope->session().set_flush_on_next_data_file(true); // Construct an STL-style argument list from the process command arguments @@ -94,7 +94,7 @@ int main(int argc, char * argv[], char * envp[]) args.push_back(argv[i]); // Look for options and a command verb in the command-line arguments - bind_scope_t bound_scope(*global_scope.get(), global_scope->report()); + bind_scope_t bound_scope(*global_scope, global_scope->report()); args = global_scope->read_command_arguments(bound_scope, args); if (global_scope->HANDLED(script_)) { @@ -185,7 +185,7 @@ int main(int argc, char * argv[], char * envp[]) } } catch (const std::exception& err) { - if (global_scope.get()) + if (global_scope) global_scope->report_error(err); else std::cerr << "Exception during initialization: " << err.what() @@ -201,15 +201,18 @@ int main(int argc, char * argv[], char * envp[]) // up everything by closing the session and deleting the session object, and // then shutting down the memory tracing subsystem. Otherwise, let it all // leak because we're about to exit anyway. +#if defined(VERIFY_ON) IF_VERIFY() { - global_scope.reset(); + checked_delete(global_scope); INFO("Ledger ended (Boost/libstdc++ may still hold memory)"); #if defined(VERIFY_ON) shutdown_memory_tracing(); #endif - } else { - INFO("Ledger ended"); + } else +#endif + { + INFO("Ledger ended"); // let global_scope leak! } // Return the final status to the operating system, either 1 for error or 0 @@ -38,6 +38,16 @@ namespace ledger { +void intrusive_ptr_add_ref(const expr_t::op_t * op) +{ + op->acquire(); +} + +void intrusive_ptr_release(const expr_t::op_t * op) +{ + op->release(); +} + namespace { value_t split_cons_expr(expr_t::ptr_op_t op) { @@ -76,10 +86,12 @@ namespace { } } -expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth) +expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth, + scope_t * param_scope) { - scope_t * scope_ptr = &scope; - expr_t::ptr_op_t result; + scope_t * scope_ptr = &scope; + unique_ptr<scope_t> bound_scope; + expr_t::ptr_op_t result; #if defined(DEBUG_ON) if (SHOW_DEBUG("expr.compile")) { @@ -93,7 +105,12 @@ expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth) if (is_ident()) { DEBUG("expr.compile", "Lookup: " << as_ident() << " in " << scope_ptr); - if (ptr_op_t def = scope_ptr->lookup(symbol_t::FUNCTION, as_ident())) { + ptr_op_t def; + if (param_scope) + def = param_scope->lookup(symbol_t::FUNCTION, as_ident()); + if (! def) + def = scope_ptr->lookup(symbol_t::FUNCTION, as_ident()); + if (def) { // Identifier references are first looked up at the point of // definition, and then at the point of every use if they could // not be found there. @@ -113,9 +130,10 @@ expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth) } } else if (is_scope()) { - shared_ptr<scope_t> subscope(new symbol_scope_t(scope)); + shared_ptr<scope_t> subscope(new symbol_scope_t(*scope_t::empty_scope)); set_scope(subscope); - scope_ptr = subscope.get(); + bound_scope.reset(new bind_scope_t(*scope_ptr, *subscope.get())); + scope_ptr = bound_scope.get(); } else if (kind < TERMINALS) { result = this; @@ -123,7 +141,7 @@ expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth) else if (kind == O_DEFINE) { switch (left()->kind) { case IDENT: { - ptr_op_t node(right()->compile(*scope_ptr, depth + 1)); + ptr_op_t node(right()->compile(*scope_ptr, depth + 1, param_scope)); DEBUG("expr.compile", "Defining " << left()->as_ident() << " in " << scope_ptr); @@ -136,12 +154,12 @@ expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth) ptr_op_t node(new op_t(op_t::O_LAMBDA)); node->set_left(left()->right()); node->set_right(right()); - node = node->compile(*scope_ptr, depth + 1); + + node = node->compile(*scope_ptr, depth + 1, param_scope); DEBUG("expr.compile", "Defining " << left()->left()->as_ident() << " in " << scope_ptr); - scope_ptr->define(symbol_t::FUNCTION, left()->left()->as_ident(), - node); + scope_ptr->define(symbol_t::FUNCTION, left()->left()->as_ident(), node); break; } // fall through... @@ -151,12 +169,39 @@ expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth) } result = wrap_value(NULL_VALUE); } + else if (kind == O_LAMBDA) { + symbol_scope_t params(param_scope ? *param_scope : *scope_t::empty_scope); + + for (ptr_op_t sym = left(); + sym; + sym = sym->has_right() ? sym->right() : NULL) { + ptr_op_t varname = sym->kind == O_CONS ? sym->left() : sym; + + if (! varname->is_ident()) { + std::ostringstream buf; + varname->dump(buf, 0); + throw_(calc_error, + _("Invalid function or lambda parameter: %1") << buf.str()); + } else { + DEBUG("expr.compile", + "Defining function parameter " << varname->as_ident()); + params.define(symbol_t::FUNCTION, varname->as_ident(), + new op_t(PLUG)); + } + } + + ptr_op_t rhs(right()->compile(*scope_ptr, depth + 1, ¶ms)); + if (rhs == right()) + result = this; + else + result = copy(left(), rhs); + } if (! result) { - ptr_op_t lhs(left()->compile(*scope_ptr, depth + 1)); + ptr_op_t lhs(left()->compile(*scope_ptr, depth + 1, param_scope)); ptr_op_t rhs(kind > UNARY_OPERATORS && has_right() ? (kind == O_LOOKUP ? right() : - right()->compile(*scope_ptr, depth + 1)) : NULL); + right()->compile(*scope_ptr, depth + 1, param_scope)) : NULL); if (lhs == left() && (! rhs || rhs == right())) { result = this; @@ -182,6 +227,23 @@ expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth) return result; } +namespace { + expr_t::ptr_op_t lookup_ident(expr_t::ptr_op_t op, scope_t& scope) + { + expr_t::ptr_op_t def = op->left(); + + // If no definition was pre-compiled for this identifier, look it up + // in the current scope. + if (! def || def->kind == expr_t::op_t::PLUG) { + DEBUG("scope.symbols", "Looking for IDENT '" << op->as_ident() << "'"); + def = scope.lookup(symbol_t::FUNCTION, op->as_ident()); + } + if (! def) + throw_(calc_error, _("Unknown identifier '%1'") << op->as_ident()); + return def; + } +} + value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) { try { @@ -206,23 +268,14 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) result = NULL_VALUE; break; - case IDENT: { - ptr_op_t definition = left(); - // If no definition was pre-compiled for this identifier, look it up - // in the current scope. - if (! definition) { - DEBUG("scope.symbols", "Looking for IDENT '" << as_ident() << "'"); - definition = scope.lookup(symbol_t::FUNCTION, as_ident()); + case IDENT: + if (ptr_op_t definition = lookup_ident(this, scope)) { + // Evaluating an identifier is the same as calling its definition + // directly + result = definition->calc(scope, locus, depth + 1); + check_type_context(scope, result); } - if (! definition) - throw_(calc_error, _("Unknown identifier '%1'") << as_ident()); - - // Evaluating an identifier is the same as calling its definition - // directly - result = definition->calc(scope, locus, depth + 1); - check_type_context(scope, result); break; - } case FUNCTION: { // Evaluating a FUNCTION is the same as calling it directly; this @@ -260,68 +313,14 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) break; } - case O_CALL: { - ptr_op_t func = left(); - const string& name(func->as_ident()); - - func = func->left(); - if (! func) - func = scope.lookup(symbol_t::FUNCTION, name); - if (! func) - throw_(calc_error, _("Calling unknown function '%1'") << name); - - call_scope_t call_args(scope, locus, depth + 1); - if (has_right()) - call_args.set_args(split_cons_expr(right())); - - try { - if (func->is_function()) - result = func->as_function()(call_args); - else - result = func->calc(call_args, locus, depth + 1); - } - catch (const std::exception&) { - add_error_context(_("While calling function '%1':" << name)); - throw; - } - + case O_CALL: + result = calc_call(scope, locus, depth); check_type_context(scope, result); break; - } - - case O_LAMBDA: { - call_scope_t& call_args(find_scope<call_scope_t>(scope, true)); - std::size_t args_count(call_args.size()); - std::size_t args_index(0); - symbol_scope_t call_scope(call_args); - - for (ptr_op_t sym = left(); - sym; - sym = sym->has_right() ? sym->right() : NULL) { - ptr_op_t varname = sym->kind == O_CONS ? sym->left() : sym; - if (! varname->is_ident()) { - throw_(calc_error, _("Invalid function definition")); - } - else if (args_index == args_count) { - call_scope.define(symbol_t::FUNCTION, varname->as_ident(), - wrap_value(NULL_VALUE)); - } - else { - DEBUG("expr.compile", - "Defining function parameter " << varname->as_ident()); - call_scope.define(symbol_t::FUNCTION, varname->as_ident(), - wrap_value(call_args[args_index++])); - } - } - - if (args_index < args_count) - throw_(calc_error, - _("Too few arguments in function call (saw %1, wanted %2)") - << args_count << args_index); - result = right()->calc(call_scope, locus, depth + 1); + case O_LAMBDA: + result = expr_value(this); break; - } case O_MATCH: result = (right()->calc(scope, locus, depth + 1).as_mask() @@ -402,51 +401,12 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) break; case O_CONS: - result = left()->calc(scope, locus, depth + 1); - if (has_right()) { - value_t temp; - temp.push_back(result); - - ptr_op_t next = right(); - while (next) { - ptr_op_t value_op; - if (next->kind == O_CONS) { - value_op = next->left(); - next = next->has_right() ? next->right() : NULL; - } else { - value_op = next; - next = NULL; - } - temp.push_back(value_op->calc(scope, locus, depth + 1)); - } - result = temp; - } + result = calc_cons(scope, locus, depth); break; - case O_SEQ: { - // An O_SEQ is very similar to an O_CONS except that only the last - // result value in the series is kept. O_CONS builds up a list. - // - // Another feature of O_SEQ is that it pushes a new symbol scope - // onto the stack. We evaluate the left side here to catch any - // side-effects, such as definitions in the case of 'x = 1; x'. - result = left()->calc(scope, locus, depth + 1); - if (has_right()) { - ptr_op_t next = right(); - while (next) { - ptr_op_t value_op; - if (next->kind == O_SEQ) { - value_op = next->left(); - next = next->right(); - } else { - value_op = next; - next = NULL; - } - result = value_op->calc(scope, locus, depth + 1); - } - } + case O_SEQ: + result = calc_seq(scope, locus, depth); break; - } default: throw_(calc_error, _("Unexpected expr node '%1'") << op_context(this)); @@ -473,6 +433,184 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth) } namespace { + expr_t::ptr_op_t find_definition(expr_t::ptr_op_t op, scope_t& scope, + expr_t::ptr_op_t * locus, const int depth, + int recursion_depth = 0) + { + // If the object we are apply call notation to is a FUNCTION value + // or a O_LAMBDA expression, then this is the object we want to + // call. + if (op->is_function() || op->kind == expr_t::op_t::O_LAMBDA) + return op; + + if (recursion_depth > 256) + throw_(value_error, _("Function recursion_depth too deep (> 256)")); + + // If it's an identifier, look up its definition and see if it's a + // function. + if (op->is_ident()) + return find_definition(lookup_ident(op, scope), scope, + locus, depth, recursion_depth + 1); + + // Value objects might be callable if they contain an expression. + if (op->is_value()) { + value_t def(op->as_value()); + if (is_expr(def)) + return find_definition(as_expr(def), scope, locus, depth, + recursion_depth + 1); + else + throw_(value_error, _("Cannot call %1 as a function") << def.label()); + } + + // Resolve ordinary expressions. + return find_definition(expr_t::op_t::wrap_value(op->calc(scope, locus, + depth + 1)), + scope, locus, depth + 1, recursion_depth + 1); + } + + value_t call_lambda(expr_t::ptr_op_t func, scope_t& scope, + call_scope_t& call_args, expr_t::ptr_op_t * locus, + const int depth) + { + std::size_t args_index(0); + std::size_t args_count(call_args.size()); + + symbol_scope_t args_scope(*scope_t::empty_scope); + + for (expr_t::ptr_op_t sym = func->left(); + sym; + sym = sym->has_right() ? sym->right() : NULL) { + expr_t::ptr_op_t varname = + sym->kind == expr_t::op_t::O_CONS ? sym->left() : sym; + if (! varname->is_ident()) { + throw_(calc_error, _("Invalid function definition")); + } + else if (args_index == args_count) { + DEBUG("expr.calc", "Defining function argument as null: " + << varname->as_ident()); + args_scope.define(symbol_t::FUNCTION, varname->as_ident(), + expr_t::op_t::wrap_value(NULL_VALUE)); + } + else { + DEBUG("expr.calc", "Defining function argument from call_args: " + << varname->as_ident()); + args_scope.define(symbol_t::FUNCTION, varname->as_ident(), + expr_t::op_t::wrap_value(call_args[args_index++])); + } + } + + if (args_index < args_count) + throw_(calc_error, + _("Too few arguments in function call (saw %1, wanted %2)") + << args_count << args_index); + + if (func->right()->is_scope()) { + bind_scope_t outer_scope(scope, *func->right()->as_scope()); + bind_scope_t bound_scope(outer_scope, args_scope); + + return func->right()->left()->calc(bound_scope, locus, depth + 1); + } else { + return func->right()->calc(args_scope, locus, depth + 1); + } + } +} + + +value_t expr_t::op_t::call(const value_t& args, scope_t& scope, + ptr_op_t * locus, const int depth) +{ + call_scope_t call_args(scope, locus, depth + 1); + call_args.set_args(args); + + if (is_function()) + return as_function()(call_args); + else if (kind == O_LAMBDA) + return call_lambda(this, scope, call_args, locus, depth); + else + return find_definition(this, scope, locus, depth) + ->calc(call_args, locus, depth); +} + +value_t expr_t::op_t::calc_call(scope_t& scope, ptr_op_t * locus, + const int depth) +{ + ptr_op_t func = left(); + string name = func->is_ident() ? func->as_ident() : "<value expr>"; + + func = find_definition(func, scope, locus, depth); + + call_scope_t call_args(scope, locus, depth + 1); + if (has_right()) + call_args.set_args(split_cons_expr(right())); + + try { + if (func->is_function()) { + return func->as_function()(call_args); + } else { + assert(func->kind == O_LAMBDA); + return call_lambda(func, scope, call_args, locus, depth); + } + } + catch (const std::exception&) { + add_error_context(_("While calling function '%1 %2':" << name + << call_args.args)); + throw; + } +} + +value_t expr_t::op_t::calc_cons(scope_t& scope, ptr_op_t * locus, + const int depth) +{ + value_t result = left()->calc(scope, locus, depth + 1); + if (has_right()) { + value_t temp; + temp.push_back(result); + + ptr_op_t next = right(); + while (next) { + ptr_op_t value_op; + if (next->kind == O_CONS) { + value_op = next->left(); + next = next->has_right() ? next->right() : NULL; + } else { + value_op = next; + next = NULL; + } + temp.push_back(value_op->calc(scope, locus, depth + 1)); + } + result = temp; + } + return result; +} + +value_t expr_t::op_t::calc_seq(scope_t& scope, ptr_op_t * locus, + const int depth) +{ + // An O_SEQ is very similar to an O_CONS except that only the last + // result value in the series is kept. O_CONS builds up a list. + // + // Another feature of O_SEQ is that it pushes a new symbol scope onto + // the stack. We evaluate the left side here to catch any + // side-effects, such as definitions in the case of 'x = 1; x'. + value_t result = left()->calc(scope, locus, depth + 1); + if (has_right()) { + ptr_op_t next = right(); + while (next) { + ptr_op_t value_op; + if (next->kind == O_SEQ) { + value_op = next->left(); + next = next->right(); + } else { + value_op = next; + next = NULL; + } + result = value_op->calc(scope, locus, depth + 1); + } + } + return result; +} + +namespace { bool print_cons(std::ostream& out, const expr_t::const_ptr_op_t op, const expr_t::op_t::context_t& context) { @@ -743,6 +881,10 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const out << " "; switch (kind) { + case PLUG: + out << "PLUG"; + break; + case VALUE: out << "VALUE: "; as_value().dump(out); @@ -803,7 +945,7 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const // An identifier is a special non-terminal, in that its left() can // hold the compiled definition of the identifier. - if (kind > TERMINALS || is_scope()) { + if (kind > TERMINALS || is_scope() || is_ident()) { if (left()) { left()->dump(out, depth + 1); if (kind > UNARY_OPERATORS && has_right()) @@ -69,6 +69,7 @@ private: public: enum kind_t { // Constants + PLUG, VALUE, IDENT, @@ -260,12 +261,8 @@ private: checked_delete(this); } - friend inline void intrusive_ptr_add_ref(const op_t * op) { - op->acquire(); - } - friend inline void intrusive_ptr_release(const op_t * op) { - op->release(); - } + friend void intrusive_ptr_add_ref(const op_t * op); + friend void intrusive_ptr_release(const op_t * op); ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const { ptr_op_t node(new_node(kind, _left, _right)); @@ -278,10 +275,14 @@ public: static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL, ptr_op_t _right = NULL); - ptr_op_t compile(scope_t& scope, const int depth = 0); + ptr_op_t compile(scope_t& scope, const int depth = 0, + scope_t * param_scope = NULL); value_t calc(scope_t& scope, ptr_op_t * locus = NULL, const int depth = 0); + value_t call(const value_t& args, scope_t& scope, + ptr_op_t * locus = NULL, const int depth = 0); + struct context_t { ptr_op_t expr_op; @@ -309,6 +310,11 @@ public: static ptr_op_t wrap_functor(expr_t::func_t fobj); static ptr_op_t wrap_scope(shared_ptr<scope_t> sobj); +private: + value_t calc_call(scope_t& scope, ptr_op_t * locus, const int depth); + value_t calc_cons(scope_t& scope, ptr_op_t * locus, const int depth); + value_t calc_seq(scope_t& scope, ptr_op_t * locus, const int depth); + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ @@ -359,13 +365,6 @@ expr_t::op_t::wrap_functor(expr_t::func_t fobj) { return temp; } -inline expr_t::ptr_op_t -expr_t::op_t::wrap_scope(shared_ptr<scope_t> sobj) { - ptr_op_t temp(new op_t(op_t::SCOPE)); - temp->set_scope(sobj); - return temp; -} - #define MAKE_FUNCTOR(x) expr_t::op_t::wrap_functor(bind(&x, this, _1)) #define WRAP_FUNCTOR(x) expr_t::op_t::wrap_functor(x) diff --git a/src/option.h b/src/option.h index dc1099db..36dba3a4 100644 --- a/src/option.h +++ b/src/option.h @@ -61,9 +61,9 @@ protected: option_t& operator=(const option_t&); public: - T * parent; - value_t value; - bool wants_arg; + T * parent; + string value; + bool wants_arg; option_t(const char * _name, const char _ch = '\0') : name(_name), name_len(std::strlen(name)), ch(_ch), @@ -94,7 +94,8 @@ public: out << std::right << desc(); if (wants_arg) { out << " = "; - value.print(out, 42); + out.width(42); + out << value; } else { out.width(45); out << ' '; @@ -123,43 +124,48 @@ public: return handled; } - string& str() { + string str() const { assert(handled); - if (! value) + if (value.empty()) throw_(std::runtime_error, _("No argument provided for %1") << desc()); - return value.as_string_lval(); + return value; } - string str() const { - assert(handled); - if (! value) - throw_(std::runtime_error, _("No argument provided for %1") << desc()); - return value.as_string(); + void on(const char * whence) { + on(string(whence)); } + void on(const optional<string>& whence) { + handler_thunk(whence); - void on_only(const optional<string>& whence) { handled = true; source = whence; } - void on(const optional<string>& whence, const string& str) { - on_with(whence, string_value(str)); + + void on(const char * whence, const string& str) { + on(string(whence), str); } - virtual void on_with(const optional<string>& whence, - const value_t& val) { + void on(const optional<string>& whence, const string& str) { + string before = value; + + handler_thunk(whence, str); + + if (value == before) + value = str; + handled = true; - value = val; source = whence; } void off() { handled = false; - value = value_t(); + value = ""; source = none; } - virtual void handler_thunk(call_scope_t&) {} + virtual void handler_thunk(const optional<string>&) {} + virtual void handler_thunk(const optional<string>&, const string&) {} - virtual void handler(call_scope_t& args) { + value_t handler(call_scope_t& args) { if (wants_arg) { if (args.size() < 2) throw_(std::runtime_error, _("No argument provided for %1") << desc()); @@ -167,7 +173,7 @@ public: throw_(std::runtime_error, _("To many arguments provided for %1") << desc()); else if (! args[0].is_string()) throw_(std::runtime_error, _("Context argument for %1 not a string") << desc()); - on_with(args.get<string>(0), args[1]); + on(args.get<string>(0), args.get<string>(1)); } else if (args.size() < 1) { throw_(std::runtime_error, _("No argument provided for %1") << desc()); @@ -176,27 +182,18 @@ public: throw_(std::runtime_error, _("Context argument for %1 not a string") << desc()); } else { - on_only(args.get<string>(0)); + on(args.get<string>(0)); } - - handler_thunk(args); - } - - virtual value_t handler_wrapper(call_scope_t& args) { - handler(args); return true; } virtual value_t operator()(call_scope_t& args) { if (! args.empty()) { args.push_front(string_value("?expr")); - return handler_wrapper(args); + return handler(args); } else if (wants_arg) { - if (handled) - return value; - else - return NULL_VALUE; + return string_value(value); } else { return handled; @@ -213,17 +210,18 @@ public: name ## option_t() : option_t<type>(#name), base #define DECL1(type, name, vartype, var, value) \ vartype var ; \ - name ## option_t() : option_t<type>(#name), var(value) + name ## option_t() : option_t<type>(#name), var value -#define DO() virtual void handler_thunk(call_scope_t&) -#define DO_(var) virtual void handler_thunk(call_scope_t& var) +#define DO() virtual void handler_thunk(const optional<string>& whence) +#define DO_(var) virtual void handler_thunk(const optional<string>& whence, \ + const string& var) #define END(name) name ## handler #define COPY_OPT(name, other) name ## handler(other.name ## handler) #define MAKE_OPT_HANDLER(type, x) \ - expr_t::op_t::wrap_functor(bind(&option_t<type>::handler_wrapper, x, _1)) + expr_t::op_t::wrap_functor(bind(&option_t<type>::handler, x, _1)) #define MAKE_OPT_FUNCTOR(type, x) \ expr_t::op_t::wrap_functor(bind(&option_t<type>::operator(), x, _1)) @@ -284,6 +282,10 @@ inline bool is_eq(const char * p, const char * n) { } \ END(name) +#define OTHER(name) \ + parent->HANDLER(name).parent = parent; \ + parent->HANDLER(name) + bool process_option(const string& whence, const string& name, scope_t& scope, const char * arg, const string& varname); diff --git a/src/parser.cc b/src/parser.cc index 2c9069d7..360ac93d 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -54,20 +54,6 @@ expr_t::parser_t::parse_value_term(std::istream& in, node = new op_t(op_t::IDENT); node->set_ident(ident); - - // An identifier followed by ( represents a function call - tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); - if (tok.kind == token_t::LPAREN) { - op_t::kind_t kind = op_t::O_CALL; - ptr_op_t call_node(new op_t(kind)); - call_node->set_left(node); - node = call_node; - - push_token(tok); // let the parser see it again - node->set_right(parse_value_expr(in, tflags.plus_flags(PARSE_SINGLE))); - } else { - push_token(tok); - } break; } @@ -85,8 +71,9 @@ expr_t::parser_t::parse_value_term(std::istream& in, return node; } + expr_t::ptr_op_t -expr_t::parser_t::parse_dot_expr(std::istream& in, +expr_t::parser_t::parse_call_expr(std::istream& in, const parse_flags_t& tflags) const { ptr_op_t node(parse_value_term(in, tflags)); @@ -94,11 +81,36 @@ expr_t::parser_t::parse_dot_expr(std::istream& in, if (node && ! tflags.has_flags(PARSE_SINGLE)) { while (true) { token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); + if (tok.kind == token_t::LPAREN) { + ptr_op_t prev(node); + node = new op_t(op_t::O_CALL); + node->set_left(prev); + push_token(tok); // let the parser see the '(' again + node->set_right(parse_value_expr(in, tflags.plus_flags(PARSE_SINGLE))); + } else { + push_token(tok); + break; + } + } + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_dot_expr(std::istream& in, + const parse_flags_t& tflags) const +{ + ptr_op_t node(parse_call_expr(in, tflags)); + + if (node && ! tflags.has_flags(PARSE_SINGLE)) { + while (true) { + token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT)); if (tok.kind == token_t::DOT) { ptr_op_t prev(node); node = new op_t(op_t::O_LOOKUP); node->set_left(prev); - node->set_right(parse_value_term(in, tflags)); + node->set_right(parse_call_expr(in, tflags)); if (! node->right()) throw_(parse_error, _("%1 operator not followed by argument") << tok.symbol); @@ -470,7 +482,9 @@ expr_t::parser_t::parse_lambda_expr(std::istream& in, ptr_op_t prev(node); node = new op_t(op_t::O_LAMBDA); node->set_left(prev); - node->set_right(parse_querycolon_expr(in, tflags)); + ptr_op_t scope(new op_t(op_t::SCOPE)); + scope->set_left(parse_querycolon_expr(in, tflags)); + node->set_right(scope); } else { push_token(tok); } diff --git a/src/parser.h b/src/parser.h index 75fd9a41..db16a919 100644 --- a/src/parser.h +++ b/src/parser.h @@ -81,6 +81,8 @@ class expr_t::parser_t : public noncopyable ptr_op_t parse_value_term(std::istream& in, const parse_flags_t& flags) const; + ptr_op_t parse_call_expr(std::istream& in, + const parse_flags_t& flags) const; ptr_op_t parse_dot_expr(std::istream& in, const parse_flags_t& flags) const; ptr_op_t parse_unary_expr(std::istream& in, diff --git a/src/pool.cc b/src/pool.cc index 2c094d47..0118a97d 100644 --- a/src/pool.cc +++ b/src/pool.cc @@ -56,7 +56,7 @@ commodity_t * commodity_pool_t::create(const string& symbol) { shared_ptr<commodity_t::base_t> base_commodity(new commodity_t::base_t(symbol)); - std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity)); + shared_ptr<commodity_t> commodity(new commodity_t(this, base_commodity)); DEBUG("pool.commodities", "Creating base commodity " << symbol); @@ -67,17 +67,29 @@ commodity_t * commodity_pool_t::create(const string& symbol) *commodity->qualified_symbol += "\""; } - DEBUG("pool.commodities", - "Creating commodity '" << commodity->symbol() << "'"); + DEBUG("pool.commodities", "Creating commodity '" << symbol << "'"); - std::pair<commodities_map::iterator, bool> result - = commodities.insert(commodities_map::value_type(commodity->mapping_key(), - commodity.get())); +#if defined(DEBUG_ON) + std::pair<commodities_map::iterator, bool> result = +#endif + commodities.insert(commodities_map::value_type(symbol, commodity)); +#if defined(DEBUG_ON) assert(result.second); +#endif commodity_price_history.add_commodity(*commodity.get()); - return commodity.release(); + return commodity.get(); +} + +commodity_t * commodity_pool_t::find(const string& symbol) +{ + DEBUG("pool.commodities", "Find commodity " << symbol); + + commodities_map::const_iterator i = commodities.find(symbol); + if (i != commodities.end()) + return (*i).second.get(); + return NULL; } commodity_t * commodity_pool_t::find_or_create(const string& symbol) @@ -88,97 +100,103 @@ commodity_t * commodity_pool_t::find_or_create(const string& symbol) return create(symbol); } -commodity_t * commodity_pool_t::find(const string& symbol) +commodity_t * commodity_pool_t::alias(const string& name, commodity_t& referent) { - DEBUG("pool.commodities", "Find commodity " << symbol); + commodities_map::const_iterator i = commodities.find(referent.symbol()); + assert(i != commodities.end()); - commodities_map::const_iterator i = commodities.find(symbol); - if (i != commodities.end()) - return (*i).second; - return NULL; + std::pair<commodities_map::iterator, bool> result + = commodities.insert(commodities_map::value_type(name, (*i).second)); + assert(result.second); + + return (*result.first).second.get(); } commodity_t * commodity_pool_t::create(const string& symbol, const annotation_t& details) { - commodity_t * new_comm = create(symbol); - if (! new_comm) - return NULL; + DEBUG("pool.commodities", "commodity_pool_t::create[ann] " + << "symbol " << symbol << std::endl << details); if (details) - return find_or_create(*new_comm, details); + return create(*find_or_create(symbol), details); else - return new_comm; + return create(symbol); } -string commodity_pool_t::make_qualified_name(const commodity_t& comm, - const annotation_t& details) +commodity_t * +commodity_pool_t::find(const string& symbol, const annotation_t& details) { - assert(details); - - if (details.price && details.price->sign() < 0) - throw_(amount_error, _("A commodity's price may not be negative")); - - std::ostringstream name; - comm.print(name); - details.print(name, comm.pool().keep_base); - -#if defined(DEBUG_ON) - if (comm.qualified_symbol) - DEBUG("pool.commodities", "make_qualified_name for " - << *comm.qualified_symbol << std::endl << details); -#endif - DEBUG("pool.commodities", "qualified_name is " << name.str()); + DEBUG("pool.commodities", "commodity_pool_t::find[ann] " + << "symbol " << symbol << std::endl << details); - return name.str(); + if (details) { + annotated_commodities_map::const_iterator i = + annotated_commodities.find + (annotated_commodities_map::key_type(symbol, details)); + if (i != annotated_commodities.end()) { + DEBUG("pool.commodities", "commodity_pool_t::find[ann] found " + << "symbol " << (*i).second->symbol() << std::endl + << as_annotated_commodity(*(*i).second.get()).details); + return (*i).second.get(); + } else { + return NULL; + } + } else { + return find(symbol); + } } commodity_t * -commodity_pool_t::find(const string& symbol, const annotation_t& details) +commodity_pool_t::find_or_create(const string& symbol, + const annotation_t& details) { - commodity_t * comm = find(symbol); - if (! comm) - return NULL; + DEBUG("pool.commodities", "commodity_pool_t::find_or_create[ann] " + << "symbol " << symbol << std::endl << details); if (details) { - string name = make_qualified_name(*comm, details); - - if (commodity_t * ann_comm = find(name)) { + if (commodity_t * ann_comm = find(symbol, details)) { assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); return ann_comm; + } else { + return create(symbol, details); } - return NULL; } else { - return comm; + return find_or_create(symbol); } } commodity_t * -commodity_pool_t::find_or_create(const string& symbol, - const annotation_t& details) +commodity_pool_t::find_or_create(commodity_t& comm, const annotation_t& details) { - commodity_t * comm = find_or_create(symbol); - if (! comm) - return NULL; + DEBUG("pool.commodities", "commodity_pool_t::find_or_create[ann:comm] " + << "symbol " << comm.symbol() << std::endl << details); - if (details) - return find_or_create(*comm, details); - else - return comm; + if (details) { + if (commodity_t * ann_comm = find(comm.symbol(), details)) { + assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); + return ann_comm; + } else { + return create(comm, details); + } + } else { + return &comm; + } } -commodity_t * +annotated_commodity_t * commodity_pool_t::create(commodity_t& comm, - const annotation_t& details, - const string& mapping_key) + const annotation_t& details) { + DEBUG("pool.commodities", "commodity_pool_t::create[ann:comm] " + << "symbol " << comm.symbol() << std::endl << details); + assert(comm); assert(! comm.has_annotation()); assert(details); - assert(! mapping_key.empty()); - unique_ptr<commodity_t> commodity - (new annotated_commodity_t(&comm, details)); + shared_ptr<annotated_commodity_t> + commodity(new annotated_commodity_t(&comm, details)); comm.add_flags(COMMODITY_SAW_ANNOTATED); if (details.price) { @@ -193,34 +211,19 @@ commodity_pool_t::create(commodity_t& comm, DEBUG("pool.commodities", "Creating annotated commodity " << "symbol " << commodity->symbol() - << " key " << mapping_key << std::endl << details); + << std::endl << details); - // Add the fully annotated name to the map, so that this symbol may - // quickly be found again. - commodity->mapping_key_ = mapping_key; - - std::pair<commodities_map::iterator, bool> result - = commodities.insert(commodities_map::value_type(mapping_key, - commodity.get())); +#if defined(DEBUG_ON) + std::pair<annotated_commodities_map::iterator, bool> result = +#endif + annotated_commodities.insert(annotated_commodities_map::value_type + (annotated_commodities_map::key_type + (comm.symbol(), details), commodity)); +#if defined(DEBUG_ON) assert(result.second); +#endif - return commodity.release(); -} - -commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, - const annotation_t& details) -{ - assert(comm); - assert(details); - - string name = make_qualified_name(comm, details); - assert(! name.empty()); - - if (commodity_t * ann_comm = find(name)) { - assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); - return ann_comm; - } - return create(comm, details, name); + return commodity.get(); } void commodity_pool_t::exchange(commodity_t& commodity, @@ -241,6 +244,7 @@ cost_breakdown_t commodity_pool_t::exchange(const amount_t& amount, const amount_t& cost, const bool is_per_unit, + const bool add_price, const optional<datetime_t>& moment, const optional<string>& tag) { @@ -47,6 +47,7 @@ #define _POOL_H #include "history.h" +#include "annotate.h" namespace ledger { @@ -66,51 +67,47 @@ public: * explicitly by calling the create methods of commodity_pool_t, or * implicitly by parsing a commoditized amount. */ - typedef std::map<string, commodity_t *> commodities_map; + typedef std::map<string, shared_ptr<commodity_t> > commodities_map; + typedef std::map<std::pair<string, annotation_t>, + shared_ptr<annotated_commodity_t> > annotated_commodities_map; - commodities_map commodities; - commodity_history_t commodity_price_history; - commodity_t * null_commodity; - commodity_t * default_commodity; + commodities_map commodities; + annotated_commodities_map annotated_commodities; + commodity_history_t commodity_price_history; + commodity_t * null_commodity; + commodity_t * default_commodity; - bool keep_base; // --base - - optional<path> price_db; // --price-db= - long quote_leeway; // --leeway= - bool get_quotes; // --download - - static shared_ptr<commodity_pool_t> current_pool; + bool keep_base; // --base + optional<path> price_db; // --price-db= + long quote_leeway; // --leeway= + bool get_quotes; // --download function<optional<price_point_t> - (commodity_t& commodity, const optional<commodity_t&>& in_terms_of)> + (commodity_t& commodity, const commodity_t * in_terms_of)> get_commodity_quote; - explicit commodity_pool_t(); + static shared_ptr<commodity_pool_t> current_pool; + explicit commodity_pool_t(); virtual ~commodity_pool_t() { TRACE_DTOR(commodity_pool_t); - foreach (commodities_map::value_type& pair, commodities) - checked_delete(pair.second); } - string make_qualified_name(const commodity_t& comm, - const annotation_t& details); - commodity_t * create(const string& symbol); commodity_t * find(const string& name); commodity_t * find_or_create(const string& symbol); + commodity_t * alias(const string& name, commodity_t& referent); - commodity_t * create(const string& symbol, const annotation_t& details); - commodity_t * find(const string& symbol, const annotation_t& details); + commodity_t * create(const string& symbol, + const annotation_t& details); + commodity_t * find(const string& symbol, + const annotation_t& details); commodity_t * find_or_create(const string& symbol, const annotation_t& details); + commodity_t * find_or_create(commodity_t& comm, const annotation_t& details); - commodity_t * create(commodity_t& comm, - const annotation_t& details, - const string& mapping_key); - - commodity_t * find_or_create(commodity_t& comm, - const annotation_t& details); + annotated_commodity_t * create(commodity_t& comm, + const annotation_t& details); // Exchange one commodity for another, while recording the factored price. @@ -121,6 +118,7 @@ public: cost_breakdown_t exchange(const amount_t& amount, const amount_t& cost, const bool is_per_unit = false, + const bool add_price = true, const optional<datetime_t>& moment = none, const optional<string>& tag = none); @@ -144,6 +142,7 @@ private: void serialize(Archive& ar, const unsigned int /* version */) { ar & current_pool; ar & commodities; + ar & annotated_commodities; ar & null_commodity; ar & default_commodity; ar & keep_base; diff --git a/src/post.cc b/src/post.cc index 191a9142..babb1292 100644 --- a/src/post.cc +++ b/src/post.cc @@ -36,6 +36,7 @@ #include "account.h" #include "journal.h" #include "format.h" +#include "pool.h" namespace ledger { @@ -239,6 +240,15 @@ namespace { return post.amount; } + value_t get_price(post_t& post) { + if (post.amount.is_null()) + return 0L; + if (post.amount.has_annotation() && post.amount.annotation().price) + return *post.amount.price(); + else + return get_cost(post); + } + value_t get_total(post_t& post) { if (post.xdata_ && ! post.xdata_->total.is_null()) return post.xdata_->total; @@ -474,6 +484,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(get_wrapper<&get_payee>); else if (name == "primary") return WRAP_FUNCTOR(get_wrapper<&get_commodity_is_primary>); + else if (name == "price") + return WRAP_FUNCTOR(get_wrapper<&get_price>); else if (name == "parent") return WRAP_FUNCTOR(get_wrapper<&get_xact>); break; @@ -638,6 +650,43 @@ void post_t::set_reported_account(account_t * acct) acct->xdata().reported_posts.push_back(this); } +void extend_post(post_t& post, journal_t& journal) +{ + commodity_t& comm(post.amount.commodity()); + + annotation_t * details = + (comm.has_annotation() ? + &as_annotated_commodity(comm).details : NULL); + + if (! details || ! details->value_expr) { + optional<expr_t> value_expr; + + if (optional<value_t> data = post.get_tag(_("Value"))) + value_expr = expr_t(data->to_string()); + + if (! value_expr) + value_expr = post.account->value_expr; + + if (! value_expr) + value_expr = post.amount.commodity().value_expr(); + + if (! value_expr) + value_expr = journal.value_expr; + + if (value_expr) { + if (! details) { + annotation_t new_details; + new_details.value_expr = value_expr; + commodity_t * new_comm = + commodity_pool_t::current_pool->find_or_create(comm, new_details); + post.amount.set_commodity(*new_comm); + } else { + details->value_expr = value_expr; + } + } + } +} + void to_xml(std::ostream& out, const post_t& post) { push_xml x(out, "posting", true); @@ -58,7 +58,8 @@ public: #define POST_COST_CALCULATED 0x0080 // posting's cost was calculated #define POST_COST_IN_FULL 0x0100 // cost specified using @@ #define POST_COST_FIXATED 0x0200 // cost is fixed using = indicator -#define POST_ANONYMIZED 0x0400 // a temporary, anonymous posting +#define POST_COST_VIRTUAL 0x0400 // cost is virtualized: (@) +#define POST_ANONYMIZED 0x0800 // a temporary, anonymous posting xact_t * xact; // only set for posts of regular xacts account_t * account; @@ -259,6 +260,9 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +class journal_t; +void extend_post(post_t& post, journal_t& journal); + void to_xml(std::ostream& out, const post_t& post); } // namespace ledger diff --git a/src/predicate.cc b/src/predicate.cc deleted file mode 100644 index 58d6c752..00000000 --- a/src/predicate.cc +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * - Neither the name of New Artisans LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include <system.hh> - -#include "predicate.h" -#include "query.h" -#include "op.h" - -namespace ledger { - -} // namespace ledger diff --git a/src/print.cc b/src/print.cc index c544c4e0..9e52ce95 100644 --- a/src/print.cc +++ b/src/print.cc @@ -133,7 +133,7 @@ namespace { std::size_t columns = (report.HANDLED(columns_) ? - static_cast<std::size_t>(report.HANDLER(columns_).value.to_long()) : 80); + lexical_cast<std::size_t>(report.HANDLER(columns_).str()) : 80); if (xact.note) print_note(out, *xact.note, xact.has_flags(ITEM_NOTE_ON_NEXT_LINE), @@ -191,8 +191,8 @@ namespace { unistring name(pbuf.str()); std::size_t account_width = - (report.HANDLER(account_width_).specified ? - static_cast<std::size_t>(report.HANDLER(account_width_).value.to_long()) : 36); + (report.HANDLED(account_width_) ? + lexical_cast<std::size_t>(report.HANDLER(account_width_).str()) : 36); if (account_width < name.length()) account_width = name.length(); @@ -218,13 +218,14 @@ namespace { // first. } else { - int amount_width = - (report.HANDLER(amount_width_).specified ? - report.HANDLER(amount_width_).value.to_int() : 12); + std::size_t amount_width = + (report.HANDLED(amount_width_) ? + lexical_cast<std::size_t>(report.HANDLER(amount_width_).str()) : + 12); std::ostringstream amt_str; - value_t(post->amount).print(amt_str, amount_width, -1, - AMOUNT_PRINT_RIGHT_JUSTIFY | + value_t(post->amount).print(amt_str, static_cast<int>(amount_width), + -1, AMOUNT_PRINT_RIGHT_JUSTIFY | AMOUNT_PRINT_NO_COMPUTED_ANNOTATIONS); amt = amt_str.str(); } diff --git a/src/py_amount.cc b/src/py_amount.cc index 25ec8e26..ea69dec5 100644 --- a/src/py_amount.cc +++ b/src/py_amount.cc @@ -48,12 +48,12 @@ namespace { return amount.value(CURRENT_TIME()); } boost::optional<amount_t> py_value_1(const amount_t& amount, - commodity_t& in_terms_of) { + const commodity_t * in_terms_of) { return amount.value(CURRENT_TIME(), in_terms_of); } boost::optional<amount_t> py_value_2(const amount_t& amount, - commodity_t& in_terms_of, - datetime_t& moment) { + const commodity_t * in_terms_of, + const datetime_t& moment) { return amount.value(moment, in_terms_of); } diff --git a/src/py_balance.cc b/src/py_balance.cc index 6c9ccb24..2ae546f1 100644 --- a/src/py_balance.cc +++ b/src/py_balance.cc @@ -48,12 +48,12 @@ namespace { return balance.value(CURRENT_TIME()); } boost::optional<balance_t> py_value_1(const balance_t& balance, - commodity_t& in_terms_of) { + const commodity_t * in_terms_of) { return balance.value(CURRENT_TIME(), in_terms_of); } boost::optional<balance_t> py_value_2(const balance_t& balance, - commodity_t& in_terms_of, - datetime_t& moment) { + const commodity_t * in_terms_of, + const datetime_t& moment) { return balance.value(moment, in_terms_of); } @@ -201,8 +201,6 @@ void export_balance() .def("value", py_value_1, args("in_terms_of")) .def("value", py_value_2, args("in_terms_of", "moment")) - .def("price", &balance_t::price) - .def("__nonzero__", &balance_t::is_nonzero) .def("is_nonzero", &balance_t::is_nonzero) .def("is_zero", &balance_t::is_zero) diff --git a/src/py_commodity.cc b/src/py_commodity.cc index b5230850..c75b5e64 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -96,14 +96,15 @@ namespace { pool.exchange(commodity, per_unit_cost, moment); } - cost_breakdown_t py_exchange_5(commodity_pool_t& pool, + cost_breakdown_t py_exchange_7(commodity_pool_t& pool, const amount_t& amount, const amount_t& cost, const bool is_per_unit, + const bool add_prices, const boost::optional<datetime_t>& moment, const boost::optional<string>& tag) { - return pool.exchange(amount, cost, is_per_unit, moment, tag); + return pool.exchange(amount, cost, is_per_unit, add_prices, moment, tag); } commodity_t * py_pool_getitem(commodity_pool_t& pool, const string& symbol) @@ -115,7 +116,7 @@ namespace { (string("Could not find commodity ") + symbol).c_str()); throw_error_already_set(); } - return (*i).second; + return (*i).second.get(); } python::list py_pool_keys(commodity_pool_t& pool) { @@ -168,13 +169,15 @@ namespace { py_pool_commodities_values_begin(commodity_pool_t& pool) { return make_transform_iterator (pool.commodities.begin(), - bind(&commodity_pool_t::commodities_map::value_type::second, _1)); + bind(&shared_ptr<commodity_t>::get, + bind(&commodity_pool_t::commodities_map::value_type::second, _1))); } commodities_map_seconds_iterator py_pool_commodities_values_end(commodity_pool_t& pool) { return make_transform_iterator (pool.commodities.end(), - bind(&commodity_pool_t::commodities_map::value_type::second, _1)); + bind(&shared_ptr<commodity_t>::get, + bind(&commodity_pool_t::commodities_map::value_type::second, _1))); } void py_add_price_2(commodity_t& commodity, @@ -267,8 +270,6 @@ void export_commodity() make_getter(&commodity_pool_t::get_commodity_quote), make_setter(&commodity_pool_t::get_commodity_quote)) - .def("make_qualified_name", &commodity_pool_t::make_qualified_name) - .def("create", py_create_1, return_internal_reference<>()) .def("create", py_create_2, return_internal_reference<>()) @@ -280,7 +281,7 @@ void export_commodity() .def("exchange", py_exchange_2, with_custodian_and_ward<1, 2>()) .def("exchange", py_exchange_3, with_custodian_and_ward<1, 2>()) - .def("exchange", py_exchange_5) + .def("exchange", py_exchange_7) .def("parse_price_directive", &commodity_pool_t::parse_price_directive) .def("parse_price_expression", &commodity_pool_t::parse_price_expression, @@ -359,7 +360,6 @@ void export_commodity() .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) diff --git a/src/py_value.cc b/src/py_value.cc index 949f2a49..efeb4340 100644 --- a/src/py_value.cc +++ b/src/py_value.cc @@ -51,12 +51,12 @@ namespace { return value.value(CURRENT_TIME()); } boost::optional<value_t> py_value_1(const value_t& value, - commodity_t& in_terms_of) { + const commodity_t * in_terms_of) { return value.value(CURRENT_TIME(), in_terms_of); } boost::optional<value_t> py_value_2(const value_t& value, - commodity_t& in_terms_of, - datetime_t& moment) { + const commodity_t * in_terms_of, + const datetime_t& moment) { return value.value(moment, in_terms_of); } @@ -266,8 +266,7 @@ void export_value() .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("value", &value_t::value, value_overloads()) .def("exchange_commodities", &value_t::exchange_commodities, exchange_commodities_overloads()) diff --git a/src/pyinterp.cc b/src/pyinterp.cc index d733c40d..8d9c8c84 100644 --- a/src/pyinterp.cc +++ b/src/pyinterp.cc @@ -535,6 +535,9 @@ namespace { case value_t::ANY: // a pointer to an arbitrary object return object(val); } +#if !defined(__clang__) + return object(); +#endif } } diff --git a/src/pyinterp.h b/src/pyinterp.h index 8699f69d..556b1563 100644 --- a/src/pyinterp.h +++ b/src/pyinterp.h @@ -136,8 +136,8 @@ public: virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, const string& name); - OPTION_(python_interpreter_t, import_, DO_(args) { - parent->import_option(args.get<string>(1)); + OPTION_(python_interpreter_t, import_, DO_(str) { + parent->import_option(str); }); }; diff --git a/src/query.h b/src/query.h index f95988a7..c694d099 100644 --- a/src/query.h +++ b/src/query.h @@ -186,6 +186,9 @@ public: assert(false); return "<UNKNOWN>"; } +#if !defined(__clang__) + return "<ERROR>"; +#endif } void unexpected(); diff --git a/src/quotes.cc b/src/quotes.cc index b29eb8bd..c33e0826 100644 --- a/src/quotes.cc +++ b/src/quotes.cc @@ -40,7 +40,7 @@ namespace ledger { optional<price_point_t> commodity_quote_from_script(commodity_t& commodity, - const optional<commodity_t&>& exchange_commodity) + const commodity_t * exchange_commodity) { DEBUG("commodity.download", "downloading quote for symbol " << commodity.symbol()); #if defined(DEBUG_ON) diff --git a/src/quotes.h b/src/quotes.h index 52092fbc..56740e47 100644 --- a/src/quotes.h +++ b/src/quotes.h @@ -46,7 +46,7 @@ namespace ledger { optional<price_point_t> commodity_quote_from_script(commodity_t& commodity, - const optional<commodity_t&>& exchange_commodity); + const commodity_t * exchange_commodity); } // namespace ledger diff --git a/src/report.cc b/src/report.cc index 7789ff80..1adbe9d0 100644 --- a/src/report.cc +++ b/src/report.cc @@ -59,7 +59,7 @@ void report_t::normalize_options(const string& verb) #ifdef HAVE_ISATTY if (! HANDLED(force_color)) { if (! HANDLED(no_color) && isatty(STDOUT_FILENO)) - HANDLER(color).on_only(string("?normalize")); + HANDLER(color).on("?normalize"); if (HANDLED(color) && ! isatty(STDOUT_FILENO)) HANDLER(color).off(); } @@ -83,7 +83,7 @@ void report_t::normalize_options(const string& verb) if (session.HANDLED(price_exp_)) commodity_pool_t::current_pool->quote_leeway = - session.HANDLER(price_exp_).value.as_long(); + lexical_cast<long>(session.HANDLER(price_exp_).value) * 3600L; if (session.HANDLED(price_db_)) commodity_pool_t::current_pool->price_db = session.HANDLER(price_db_).str(); @@ -106,39 +106,35 @@ void report_t::normalize_options(const string& verb) if (! HANDLED(meta_width_)) { string::size_type i = HANDLER(meta_).str().find(':'); if (i != string::npos) { - HANDLED(meta_width_).on_with - (string("?normalize"), - lexical_cast<long>(string(HANDLER(meta_).str(), i + 1))); - HANDLED(meta_).on(string("?normalize"), + HANDLED(meta_width_).on("?normalize", + string(HANDLER(meta_).str(), i + 1)); + HANDLED(meta_).on("?normalize", string(HANDLER(meta_).str(), 0, i)); } } if (HANDLED(meta_width_)) { - HANDLER(prepend_format_).on - (string("?normalize"), - string("%(justify(truncated(tag(\"") + - HANDLER(meta_).str() + "\"), " + - HANDLED(meta_width_).value.to_string() + " - 1), " + - HANDLED(meta_width_).value.to_string() + "))"); - meta_width = HANDLED(meta_width_).value.to_long(); + HANDLER(prepend_format_) + .on("?normalize", string("%(justify(truncated(tag(\"") + + HANDLER(meta_).str() + "\"), " + + HANDLED(meta_width_).value + " - 1), " + + HANDLED(meta_width_).value + "))"); + meta_width = lexical_cast<long>(HANDLED(meta_width_).value); } else { - HANDLER(prepend_format_).on(string("?normalize"), string("%(tag(\"") + - HANDLER(meta_).str() + "\"))"); + HANDLER(prepend_format_) + .on("?normalize", string("%(tag(\"") + HANDLER(meta_).str() + "\"))"); } } - if (! HANDLED(prepend_width_)) - HANDLER(prepend_width_).on_with(string("?normalize"), static_cast<long>(0)); if (verb == "print" || verb == "xact" || verb == "dump") { - HANDLER(related).on_only(string("?normalize")); - HANDLER(related_all).on_only(string("?normalize")); + HANDLER(related_all).parent = this; + HANDLER(related_all).on("?normalize"); } else if (verb == "equity") { - HANDLER(equity).on_only(string("?normalize")); + HANDLER(equity).on("?normalize"); } if (verb[0] != 'b' && verb[0] != 'r') - HANDLER(base).on_only(string("?normalize")); + HANDLER(base).on("?normalize"); // If a time period was specified with -p, check whether it also gave a // begin and/or end to the report period (though these can be overridden @@ -152,12 +148,10 @@ void report_t::normalize_options(const string& verb) // to avoid option ordering issues were we to have done it during the // initial parsing of the options. if (HANDLED(amount_data)) { - HANDLER(format_) - .on_with(string("?normalize"), HANDLER(plot_amount_format_).value); + HANDLER(format_).on("?normalize", HANDLER(plot_amount_format_).value); } else if (HANDLED(total_data)) { - HANDLER(format_) - .on_with(string("?normalize"), HANDLER(plot_total_format_).value); + HANDLER(format_).on("?normalize", HANDLER(plot_total_format_).value); } // If the --exchange (-X) option was used, parse out any final price @@ -168,9 +162,26 @@ void report_t::normalize_options(const string& verb) terminus); } + if (HANDLED(percent)) { + commodity_t::decimal_comma_by_default = false; + if (HANDLED(market)) { + HANDLER(total_) + .on("?normalize", + "(__tmp = market(parent.total, value_date, exchange);" + " ((is_account & parent & __tmp) ?" + " percent(scrub(market(total, value_date, exchange)), " + " scrub(__tmp)) : 0))"); + } + } + + if (HANDLED(immediate) && HANDLED(market)) { + HANDLER(amount_) + .on("?normalize", "market(amount_expr, value_date, exchange)"); + } + long cols = 0; if (HANDLED(columns_)) - cols = HANDLER(columns_).value.to_long(); + cols = lexical_cast<long>(HANDLER(columns_).value); else if (const char * columns = std::getenv("COLUMNS")) cols = lexical_cast<long>(columns); else @@ -182,23 +193,21 @@ void report_t::normalize_options(const string& verb) if (cols > 0) { DEBUG("auto.columns", "cols = " << cols); - if (! HANDLER(date_width_).specified) - HANDLER(date_width_) - .on_with(none, static_cast<long>(format_date(CURRENT_DATE(), - FMT_PRINTED).length())); - - long date_width = HANDLER(date_width_).value.to_long(); - long payee_width = (HANDLER(payee_width_).specified ? - HANDLER(payee_width_).value.to_long() : - int(double(cols) * 0.263157)); - long account_width = (HANDLER(account_width_).specified ? - HANDLER(account_width_).value.to_long() : - int(double(cols) * 0.302631)); - long amount_width = (HANDLER(amount_width_).specified ? - HANDLER(amount_width_).value.to_long() : - int(double(cols) * 0.157894)); - long total_width = (HANDLER(total_width_).specified ? - HANDLER(total_width_).value.to_long() : + long date_width = (HANDLED(date_width_) ? + lexical_cast<long>(HANDLER(date_width_).str()) : + static_cast<long> + (format_date(CURRENT_DATE(),FMT_PRINTED).length())); + long payee_width = (HANDLED(payee_width_) ? + lexical_cast<long>(HANDLER(payee_width_).str()) : + long(double(cols) * 0.263157)); + long account_width = (HANDLED(account_width_) ? + lexical_cast<long>(HANDLER(account_width_).str()) : + long(double(cols) * 0.302631)); + long amount_width = (HANDLED(amount_width_) ? + lexical_cast<long>(HANDLER(amount_width_).str()) : + long(double(cols) * 0.157894)); + long total_width = (HANDLED(total_width_) ? + lexical_cast<long>(HANDLER(total_width_).str()) : amount_width); DEBUG("auto.columns", "date_width = " << date_width); @@ -207,32 +216,46 @@ void report_t::normalize_options(const string& verb) DEBUG("auto.columns", "amount_width = " << amount_width); DEBUG("auto.columns", "total_width = " << total_width); - if (! HANDLER(date_width_).specified && - ! HANDLER(payee_width_).specified && - ! HANDLER(account_width_).specified && - ! HANDLER(amount_width_).specified && - ! HANDLER(total_width_).specified) { + if (! HANDLED(date_width_) && + ! HANDLED(payee_width_) && + ! HANDLED(account_width_) && + ! HANDLED(amount_width_) && + ! HANDLED(total_width_)) { long total = (4 /* the spaces between */ + date_width + payee_width + - account_width + amount_width + total_width); - if (total > cols) { + account_width + amount_width + total_width + + (HANDLED(dc) ? 1 + amount_width : 0)); + while (total > cols && account_width > 5 && payee_width > 5) { DEBUG("auto.columns", "adjusting account down"); - account_width -= total - cols; + if (total > cols) { + --account_width; + --total; + if (total > cols) { + --account_width; + --total; + } + } + if (total > cols) { + --payee_width; + --total; + } DEBUG("auto.columns", "account_width now = " << account_width); } } if (! HANDLED(meta_width_)) - HANDLER(meta_width_).on_with(string("?normalize"), 0L); - if (! HANDLER(date_width_).specified) - HANDLER(date_width_).on_with(string("?normalize"), date_width); - if (! HANDLER(payee_width_).specified) - HANDLER(payee_width_).on_with(string("?normalize"), payee_width); - if (! HANDLER(account_width_).specified) - HANDLER(account_width_).on_with(string("?normalize"), account_width); - if (! HANDLER(amount_width_).specified) - HANDLER(amount_width_).on_with(string("?normalize"), amount_width); - if (! HANDLER(total_width_).specified) - HANDLER(total_width_).on_with(string("?normalize"), total_width); + HANDLER(meta_width_).value = "0"; + if (! HANDLED(prepend_width_)) + HANDLER(prepend_width_).value = "0"; + if (! HANDLED(date_width_)) + HANDLER(date_width_).value = to_string(date_width); + if (! HANDLED(payee_width_)) + HANDLER(payee_width_).value = to_string(payee_width); + if (! HANDLED(account_width_)) + HANDLER(account_width_).value = to_string(account_width); + if (! HANDLED(amount_width_)) + HANDLER(amount_width_).value = to_string(amount_width); + if (! HANDLED(total_width_)) + HANDLER(total_width_).value = to_string(total_width); } } @@ -255,7 +278,7 @@ void report_t::normalize_period() if (! interval.duration) HANDLER(period_).off(); else if (! HANDLED(sort_all_)) - HANDLER(sort_xacts_).on_only(string("?normalize")); + HANDLER(sort_xacts_).on("?normalize"); } void report_t::parse_query_args(const value_t& args, const string& whence) @@ -278,7 +301,7 @@ void report_t::parse_query_args(const value_t& args, const string& whence) } if (query.has_query(query_t::QUERY_BOLD)) { - HANDLER(bold_if_).set_expr(whence, query.get_query(query_t::QUERY_BOLD)); + HANDLER(bold_if_).on(whence, query.get_query(query_t::QUERY_BOLD)); DEBUG("report.predicate", "Bolding predicate = " << HANDLER(bold_if_).str()); } @@ -309,7 +332,7 @@ void report_t::posts_report(post_handler_ptr handler) { handler = chain_post_handlers(handler, *this); if (HANDLED(group_by_)) { - std::auto_ptr<post_splitter> + unique_ptr<post_splitter> splitter(new post_splitter(handler, *this, HANDLER(group_by_).expr)); splitter->set_postflush_func(posts_flusher(handler, *this)); handler = post_handler_ptr(splitter.release()); @@ -329,9 +352,9 @@ void report_t::generate_report(post_handler_ptr handler) generate_posts_iterator walker (session, HANDLED(seed_) ? - static_cast<unsigned int>(HANDLER(seed_).value.to_long()) : 0, + lexical_cast<unsigned int>(HANDLER(seed_).str()) : 0, HANDLED(head_) ? - static_cast<unsigned int>(HANDLER(head_).value.to_long()) : 50); + lexical_cast<unsigned int>(HANDLER(head_).str()) : 50); pass_down_posts<generate_posts_iterator>(handler, walker); } @@ -513,20 +536,32 @@ value_t report_t::fn_should_bold(call_scope_t& scope) value_t report_t::fn_market(call_scope_t& args) { - optional<datetime_t> moment = (args.has<datetime_t>(1) ? - args.get<datetime_t>(1) : - optional<datetime_t>()); value_t result; + value_t arg0 = args[0]; + + datetime_t moment; + if (args.has<datetime_t>(1)) + moment = args.get<datetime_t>(1); + + if (arg0.is_string()) { + amount_t tmp(1L); + commodity_t * commodity = + commodity_pool_t::current_pool->find_or_create(arg0.as_string()); + tmp.set_commodity(*commodity); + arg0 = tmp; + } + + string target_commodity; if (args.has<string>(2)) - result = args[0].exchange_commodities(args.get<string>(2), - /* add_prices= */ false, moment); - else - result = args[0].value(moment); + target_commodity = args.get<string>(2); - if (! result.is_null()) - return result; + if (! target_commodity.empty()) + result = arg0.exchange_commodities(target_commodity, + /* add_prices= */ false, moment); + else + result = arg0.value(moment); - return args[0]; + return ! result.is_null() ? result : arg0; } value_t report_t::fn_get_at(call_scope_t& args) @@ -535,13 +570,20 @@ value_t report_t::fn_get_at(call_scope_t& args) if (index == 0) { if (! args[0].is_sequence()) return args[0]; - } else { - if (! args[0].is_sequence()) - throw_(std::runtime_error, - _("Attempting to get argument at index %1 from %2") - << index << args[0].label()); } - return args[0].as_sequence()[index]; + else if (! args[0].is_sequence()) { + throw_(std::runtime_error, + _("Attempting to get argument at index %1 from %2") + << index << args[0].label()); + } + + value_t::sequence_t& seq(args[0].as_sequence_lval()); + if (index >= seq.size()) + throw_(std::runtime_error, + _("Attempting to get index %1 from %2 with %3 elements") + << index << args[0].label() << seq.size()); + + return seq[index]; } value_t report_t::fn_is_seq(call_scope_t& scope) @@ -731,14 +773,60 @@ value_t report_t::fn_percent(call_scope_t& args) (args.get<amount_t>(0) / args.get<amount_t>(1)).number()); } -value_t report_t::fn_price(call_scope_t& args) +value_t report_t::fn_commodity(call_scope_t& args) { - return args[0].price(); + return string_value(args.get<amount_t>(0).commodity().symbol()); } -value_t report_t::fn_commodity(call_scope_t& args) +value_t report_t::fn_nail_down(call_scope_t& args) { - return string_value(args.get<amount_t>(0).commodity().symbol()); + value_t arg0(args[0]); + value_t arg1(args[1]); + + switch (arg0.type()) { + case value_t::AMOUNT: { + amount_t tmp(arg0.as_amount()); + if (tmp.has_commodity() && ! arg1.is_null()) { + expr_t value_expr(is_expr(arg1) ? + as_expr(arg1) : + expr_t::op_t::wrap_value(arg1.unrounded() / arg0)); + std::ostringstream buf; + value_expr.print(buf); + value_expr.set_text(buf.str()); + + tmp.set_commodity(tmp.commodity().nail_down(value_expr)); + } + return tmp; + } + + case value_t::BALANCE: { + balance_t tmp; + foreach (const balance_t::amounts_map::value_type& pair, + arg0.as_balance_lval().amounts) { + call_scope_t inner_args(*args.parent); + inner_args.push_back(pair.second); + inner_args.push_back(arg1); + tmp += fn_nail_down(inner_args).as_amount(); + } + return tmp; + } + + case value_t::SEQUENCE: { + value_t tmp; + foreach (value_t& value, arg0.as_sequence_lval()) { + call_scope_t inner_args(*args.parent); + inner_args.push_back(value); + inner_args.push_back(arg1); + tmp.push_back(fn_nail_down(inner_args)); + } + return tmp; + } + + default: + throw_(std::runtime_error, _("Attempting to nail down %1") + << args[0].label()); + } + return arg0; } value_t report_t::fn_lot_date(call_scope_t& args) @@ -880,7 +968,7 @@ value_t report_t::pricemap_command(call_scope_t& args) std::ostream& out(output_stream); commodity_pool_t::current_pool->commodity_price_history.print_map (out, args.has<string>(0) ? - optional<datetime_t>(datetime_t(parse_date(args.get<string>(0)))) : none); + datetime_t(parse_date(args.get<string>(0))) : datetime_t()); return true; } @@ -911,11 +999,9 @@ option_t<report_t> * report_t::lookup_option(const char * p) case 'G': OPT_CH(gain); break; -#if 0 case 'H': OPT_CH(historical); break; -#endif case 'I': OPT_CH(price); break; @@ -980,6 +1066,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT_(begin_); else OPT(bold_if_); else OPT(budget); + else OPT(budget_format_); else OPT(by_payee); break; case 'c': @@ -998,6 +1085,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(date_); else OPT(date_format_); else OPT(datetime_format_); + else OPT(dc); else OPT(depth_); else OPT(deviation); else OPT_(display_); @@ -1031,10 +1119,12 @@ option_t<report_t> * report_t::lookup_option(const char * p) break; case 'h': OPT(head_); + else OPT(historical); break; case 'i': OPT(invert); else OPT(inject_); + else OPT(immediate); break; case 'j': OPT_CH(amount_data); @@ -1043,7 +1133,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) OPT_(limit_); else OPT(lot_dates); else OPT(lot_prices); - else OPT(lot_tags); + else OPT_ALT(lot_notes, lot_tags); else OPT(lots); else OPT(lots_actual); else OPT_ALT(tail_, last_); @@ -1223,7 +1313,7 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, else if (is_eq(p, "display_total")) return MAKE_FUNCTOR(report_t::fn_display_total); else if (is_eq(p, "date")) - return MAKE_FUNCTOR(report_t::fn_now); + return MAKE_FUNCTOR(report_t::fn_today); break; case 'f': @@ -1266,6 +1356,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(fn_null); else if (is_eq(p, "now")) return MAKE_FUNCTOR(report_t::fn_now); + else if (is_eq(p, "nail_down")) + return MAKE_FUNCTOR(report_t::fn_nail_down); break; case 'o': @@ -1278,8 +1370,6 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(fn_false); else if (is_eq(p, "percent")) return MAKE_FUNCTOR(report_t::fn_percent); - else if (is_eq(p, "price")) - return MAKE_FUNCTOR(report_t::fn_price); else if (is_eq(p, "print")) return MAKE_FUNCTOR(report_t::fn_print); break; @@ -1382,98 +1472,98 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return MAKE_OPT_HANDLER(report_t, handler); break; +#define POSTS_REPORTER(formatter) \ + WRAP_FUNCTOR(reporter<>(post_handler_ptr(formatter), *this, \ + string("#") + p)) + + // Can't use WRAP_FUNCTOR here because the template arguments + // confuse the parser +#define POSTS_REPORTER_(method, formatter) \ + expr_t::op_t::wrap_functor \ + (reporter<post_t, post_handler_ptr, method> \ + (post_handler_ptr(formatter), *this, string("#") + p)) + +#define FORMATTED_POSTS_REPORTER(format) \ + POSTS_REPORTER \ + (new format_posts \ + (*this, report_format(HANDLER(format)), \ + maybe_format(HANDLER(prepend_format_)), \ + HANDLED(prepend_width_) ? \ + lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0)) + +#define FORMATTED_COMMODITIES_REPORTER(format) \ + POSTS_REPORTER_ \ + (&report_t::commodities_report, \ + new format_posts \ + (*this, report_format(HANDLER(format)), \ + maybe_format(HANDLER(prepend_format_)), \ + HANDLED(prepend_width_) ? \ + lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0)) + +#define ACCOUNTS_REPORTER(formatter) \ + expr_t::op_t::wrap_functor(reporter<account_t, acct_handler_ptr, \ + &report_t::accounts_report> \ + (acct_handler_ptr(formatter), *this, \ + string("#") + p)) + +#define FORMATTED_ACCOUNTS_REPORTER(format) \ + ACCOUNTS_REPORTER \ + (new format_accounts \ + (*this, report_format(HANDLER(format)), \ + maybe_format(HANDLER(prepend_format_)), \ + HANDLED(prepend_width_) ? \ + lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0)) + case symbol_t::COMMAND: switch (*p) { case 'a': if (is_eq(p, "accounts")) { - return WRAP_FUNCTOR(reporter<>(post_handler_ptr(new report_accounts(*this)), - *this, "#accounts")); + return POSTS_REPORTER(new report_accounts(*this)); } break; case 'b': if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) { - return expr_t::op_t::wrap_functor - (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> - (acct_handler_ptr(new format_accounts - (*this, report_format(HANDLER(balance_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t())), - *this, "#balance")); + return FORMATTED_ACCOUNTS_REPORTER(balance_format_); } else if (is_eq(p, "budget")) { - HANDLER(amount_).set_expr(string("#budget"), "(amount, 0)"); + HANDLER(amount_).on(string("#budget"), "(amount, 0)"); budget_flags |= BUDGET_WRAP_VALUES; if (! (budget_flags & ~BUDGET_WRAP_VALUES)) budget_flags |= BUDGET_BUDGETED; -#if 0 -#define POSTS_REPORT(formatter) - return WRAP_FUNCTOR(reporter<>(post_handler_ptr(formatter), *this, - string("#") + p)); - -#define ACCOUNTS_REPORT(formatter) - return WRAP_FUNCTOR(reporter<account_t, acct_handler_ptr, - &report_t::accounts_report> - (acct_handler_ptr(formatter), *this, - string("#") + p)); -#endif - - return expr_t::op_t::wrap_functor - (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> - (acct_handler_ptr(new format_accounts - (*this, report_format(HANDLER(budget_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t())), - *this, "#budget")); + return FORMATTED_ACCOUNTS_REPORTER(budget_format_); } break; case 'c': if (is_eq(p, "csv")) { - return WRAP_FUNCTOR - (reporter<> - (post_handler_ptr(new format_posts - (*this, report_format(HANDLER(csv_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t())), - *this, "#csv")); + return FORMATTED_POSTS_REPORTER(csv_format_); } else if (is_eq(p, "cleared")) { - HANDLER(amount_).set_expr(string("#cleared"), - "(amount, cleared ? amount : 0)"); - return expr_t::op_t::wrap_functor - (reporter<account_t, acct_handler_ptr, &report_t::accounts_report> - (acct_handler_ptr(new format_accounts - (*this, report_format(HANDLER(cleared_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t())), - *this, "#cleared")); + HANDLER(amount_).on(string("#cleared"), + "(amount, cleared ? amount : 0)"); + return FORMATTED_ACCOUNTS_REPORTER(cleared_format_); } else if (is_eq(p, "convert")) { return WRAP_FUNCTOR(convert_command); } else if (is_eq(p, "commodities")) { - return WRAP_FUNCTOR(reporter<> - (post_handler_ptr(new report_commodities(*this)), - *this, "#commodities")); + return POSTS_REPORTER(new report_commodities(*this)); } break; case 'e': if (is_eq(p, "equity")) { - HANDLER(generated).on_only(string("#equity")); - return WRAP_FUNCTOR(reporter<>(post_handler_ptr(new print_xacts(*this)), - *this, "#equity")); + HANDLER(generated).on("#equity"); + return POSTS_REPORTER(new print_xacts(*this)); } else if (is_eq(p, "entry")) { return WRAP_FUNCTOR(xact_command); } else if (is_eq(p, "emacs")) { - return WRAP_FUNCTOR - (reporter<>(post_handler_ptr(new format_emacs_posts(output_stream)), - *this, "#emacs")); + return POSTS_REPORTER(new format_emacs_posts(output_stream)); } else if (is_eq(p, "echo")) { return MAKE_FUNCTOR(report_t::echo_command); @@ -1482,56 +1572,32 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, case 'o': if (is_eq(p, "org")) { - return WRAP_FUNCTOR - (reporter<> - (post_handler_ptr(new posts_to_org_table - (*this, maybe_format(HANDLER(prepend_format_)))), - *this, "#org")); + return POSTS_REPORTER(new posts_to_org_table + (*this, maybe_format(HANDLER(prepend_format_)))); } break; case 'p': if (*(p + 1) == '\0' || is_eq(p, "print")) { - return WRAP_FUNCTOR - (reporter<>(post_handler_ptr(new print_xacts(*this, HANDLED(raw))), - *this, "#print")); + return POSTS_REPORTER(new print_xacts(*this, HANDLED(raw))); } else if (is_eq(p, "prices")) { - return expr_t::op_t::wrap_functor - (reporter<post_t, post_handler_ptr, &report_t::commodities_report> - (post_handler_ptr(new format_posts - (*this, report_format(HANDLER(prices_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t())), - *this, "#prices")); + return FORMATTED_COMMODITIES_REPORTER(prices_format_); } else if (is_eq(p, "pricedb")) { - return expr_t::op_t::wrap_functor - (reporter<post_t, post_handler_ptr, &report_t::commodities_report> - (post_handler_ptr(new format_posts - (*this, report_format(HANDLER(pricedb_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t())), - *this, "#pricedb")); + return FORMATTED_COMMODITIES_REPORTER(pricedb_format_); } else if (is_eq(p, "pricemap")) { return MAKE_FUNCTOR(report_t::pricemap_command); } else if (is_eq(p, "payees")) { - return WRAP_FUNCTOR(reporter<>(post_handler_ptr(new report_payees(*this)), - *this, "#payees")); + return POSTS_REPORTER(new report_payees(*this)); } break; case 'r': if (*(p + 1) == '\0' || is_eq(p, "reg") || is_eq(p, "register")) { - return WRAP_FUNCTOR - (reporter<> - (post_handler_ptr(new format_posts - (*this, report_format(HANDLER(register_format_)), - maybe_format(HANDLER(prepend_format_)), - HANDLER(prepend_width_).value.to_size_t())), - *this, "#register")); + return FORMATTED_POSTS_REPORTER(register_format_); } else if (is_eq(p, "reload")) { return MAKE_FUNCTOR(report_t::reload_command); @@ -1549,8 +1615,7 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, if (is_eq(p, "xact")) return WRAP_FUNCTOR(xact_command); else if (is_eq(p, "xml")) - return WRAP_FUNCTOR(reporter<>(post_handler_ptr(new format_xml(*this)), - *this, "#xml")); + return POSTS_REPORTER(new format_xml(*this)); break; } break; @@ -1572,11 +1637,9 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(format_command); break; case 'g': - if (is_eq(p, "generate")) { - return expr_t::op_t::wrap_functor - (reporter<post_t, post_handler_ptr, &report_t::generate_report> - (post_handler_ptr(new print_xacts(*this)), *this, "#generate")); - } + if (is_eq(p, "generate")) + return POSTS_REPORTER_(&report_t::generate_report, + new print_xacts(*this)); break; case 'p': if (is_eq(p, "parse")) diff --git a/src/report.h b/src/report.h index 03eee78b..a3825335 100644 --- a/src/report.h +++ b/src/report.h @@ -169,8 +169,8 @@ public: value_t fn_format_date(call_scope_t& scope); value_t fn_ansify_if(call_scope_t& scope); value_t fn_percent(call_scope_t& scope); - value_t fn_price(call_scope_t& scope); value_t fn_commodity(call_scope_t& scope); + value_t fn_nail_down(call_scope_t& scope); value_t fn_lot_date(call_scope_t& scope); value_t fn_lot_price(call_scope_t& scope); value_t fn_lot_tag(call_scope_t& scope); @@ -215,7 +215,7 @@ public: bool lots = HANDLED(lots) || HANDLED(lots_actual); return keep_details_t(lots || HANDLED(lot_prices), lots || HANDLED(lot_dates), - lots || HANDLED(lot_tags), + lots || HANDLED(lot_notes), HANDLED(lots_actual)); } @@ -250,6 +250,7 @@ public: HANDLER(date_).report(out); HANDLER(date_format_).report(out); HANDLER(datetime_format_).report(out); + HANDLER(dc).report(out); HANDLER(depth_).report(out); HANDLER(deviation).report(out); HANDLER(display_).report(out); @@ -272,12 +273,13 @@ public: HANDLER(group_by_).report(out); HANDLER(group_title_format_).report(out); HANDLER(head_).report(out); + HANDLER(immediate).report(out); HANDLER(inject_).report(out); HANDLER(invert).report(out); HANDLER(limit_).report(out); HANDLER(lot_dates).report(out); HANDLER(lot_prices).report(out); - HANDLER(lot_tags).report(out); + HANDLER(lot_notes).report(out); HANDLER(lots).report(out); HANDLER(lots_actual).report(out); HANDLER(market).report(out); @@ -353,12 +355,16 @@ public: * Option handlers */ - OPTION__(report_t, abbrev_len_, - CTOR(report_t, abbrev_len_) { on_with(none, 2L); }); + OPTION__ + (report_t, abbrev_len_, + CTOR(report_t, abbrev_len_) { + on(none, "2"); + }); + OPTION(report_t, account_); OPTION_(report_t, actual, DO() { // -L - parent->HANDLER(limit_).on(string("--actual"), "actual"); + OTHER(limit_).on(whence, "actual"); }); OPTION_(report_t, add_budget, DO() { @@ -367,16 +373,9 @@ public: OPTION__ (report_t, amount_, // -t - expr_t expr; - CTOR(report_t, amount_) { - set_expr(none, "amount"); - } - void set_expr(const optional<string>& whence, const string& str) { - expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); + DECL1(report_t, amount_, merged_expr_t, expr, ("amount_expr", "amount")) {} + DO_(str) { + expr.append(str); }); OPTION(report_t, amount_data); // -j @@ -384,220 +383,271 @@ public: OPTION(report_t, auto_match); OPTION_(report_t, average, DO() { // -A - parent->HANDLER(display_total_) - .set_expr(string("--average"), "count>0?(total_expr/count):0"); + OTHER(display_total_) + .on(whence, "count>0?(display_total/count):0"); }); - OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) { - on(none, - "%(ansify_if(" - " justify(scrub(display_total), 20, 20 + prepend_width, true, color)," - " bold if should_bold))" - " %(!options.flat ? depth_spacer : \"\")" - "%-(ansify_if(" - " ansify_if(partial_account(options.flat), blue if color)," - " bold if should_bold))\n%/" - "%$1\n%/" - "%(prepend_width ? \" \" * prepend_width : \"\")" - "--------------------\n"); - }); + OPTION__ + (report_t, balance_format_, + CTOR(report_t, balance_format_) { + on(none, + "%(ansify_if(" + " justify(scrub(display_total), 20," + " 20 + int(prepend_width), true, color)," + " bold if should_bold))" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(" + " ansify_if(partial_account(options.flat), blue if color)," + " bold if should_bold))\n%/" + "%$1\n%/" + "%(prepend_width ? \" \" * int(prepend_width) : \"\")" + "--------------------\n"); + }); OPTION(report_t, base); OPTION_(report_t, basis, DO() { // -B - parent->HANDLER(revalued).on_only(string("--basis")); - parent->HANDLER(amount_).set_expr(string("--basis"), "rounded(cost)"); + OTHER(revalued).on(whence); + OTHER(amount_).expr.set_base_expr("rounded(cost)"); }); - OPTION_(report_t, begin_, DO_(args) { // -b - date_interval_t interval(args.get<string>(1)); - optional<date_t> begin = interval.begin(); - if (! begin) + OPTION_(report_t, begin_, DO_(str) { // -b + date_interval_t interval(str); + if (optional<date_t> begin = interval.begin()) { + string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; + OTHER(limit_).on(whence, predicate); + } else { throw_(std::invalid_argument, - _("Could not determine beginning of period '%1'") - << args.get<string>(1)); - - string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; - parent->HANDLER(limit_).on(string("--begin"), predicate); + _("Could not determine beginning of period '%1'") << str); + } }); - OPTION__ + OPTION_ (report_t, bold_if_, expr_t expr; - CTOR(report_t, bold_if_) {} - void set_expr(const optional<string>& whence, const string& str) { + DO_(str) { expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); }); OPTION_(report_t, budget, DO() { parent->budget_flags |= BUDGET_BUDGETED; }); - OPTION__(report_t, budget_format_, CTOR(report_t, budget_format_) { - on(none, - "%(justify(scrub(get_at(total_expr, 0)), 12, -1, true, color))" - " %(justify(-scrub(get_at(total_expr, 1)), 12, " - " 12 + 1 + 12, true, color))" - " %(justify(scrub(get_at(total_expr, 1) + " - " get_at(total_expr, 0)), 12, " - " 12 + 1 + 12 + 1 + 12, true, color))" - " %(ansify_if(" - " justify((get_at(total_expr, 1) ? " - " (100% * scrub(get_at(total_expr, 0))) / " - " -scrub(get_at(total_expr, 1)) : 0), " - " 5, -1, true, false)," - " magenta if (color and get_at(total_expr, 1) and " - " (abs(quantity(scrub(get_at(total_expr, 0))) / " - " quantity(scrub(get_at(total_expr, 1)))) >= 1))))" - " %(!options.flat ? depth_spacer : \"\")" - "%-(ansify_if(partial_account(options.flat), blue if color))\n" - "%/%$1 %$2 %$3 %$4\n%/" - "%(prepend_width ? \" \" * prepend_width : \"\")" - "------------ ------------ ------------ -----\n"); - }); + OPTION__ + (report_t, budget_format_, + CTOR(report_t, budget_format_) { + on(none, + "%(justify(scrub(get_at(display_total, 0)), 12, -1, true, color))" + " %(justify(-scrub(get_at(display_total, 1)), 12, " + " 12 + 1 + 12, true, color))" + " %(justify(scrub(get_at(display_total, 1) + " + " get_at(display_total, 0)), 12, " + " 12 + 1 + 12 + 1 + 12, true, color))" + " %(ansify_if(" + " justify((get_at(display_total, 1) ? " + " (100% * scrub(get_at(display_total, 0))) / " + " -scrub(get_at(display_total, 1)) : 0), " + " 5, -1, true, false)," + " magenta if (color and get_at(display_total, 1) and " + " (abs(quantity(scrub(get_at(display_total, 0))) / " + " quantity(scrub(get_at(display_total, 1)))) >= 1))))" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(partial_account(options.flat), blue if color))\n" + "%/%$1 %$2 %$3 %$4\n%/" + "%(prepend_width ? \" \" * int(prepend_width) : \"\")" + "------------ ------------ ------------ -----\n"); + }); OPTION(report_t, by_payee); // -P OPTION_(report_t, cleared, DO() { // -C - parent->HANDLER(limit_).on(string("--cleared"), "cleared"); + OTHER(limit_).on(whence, "cleared"); }); - OPTION__(report_t, cleared_format_, CTOR(report_t, cleared_format_) { - on(none, - "%(justify(scrub(get_at(total_expr, 0)), 16, 16 + prepend_width, " - " true, color)) %(justify(scrub(get_at(total_expr, 1)), 18, " - " 36 + prepend_width, true, color))" - " %(latest_cleared ? format_date(latest_cleared) : \" \")" - " %(!options.flat ? depth_spacer : \"\")" - "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" - "%$1 %$2 %$3\n%/" - "%(prepend_width ? \" \" * prepend_width : \"\")" - "---------------- ---------------- ---------\n"); - }); + OPTION__ + (report_t, cleared_format_, + CTOR(report_t, cleared_format_) { + on(none, + "%(justify(scrub(get_at(display_total, 0)), 16, 16 + int(prepend_width), " + " true, color)) %(justify(scrub(get_at(display_total, 1)), 18, " + " 36 + int(prepend_width), true, color))" + " %(latest_cleared ? format_date(latest_cleared) : \" \")" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" + "%$1 %$2 %$3\n%/" + "%(prepend_width ? \" \" * int(prepend_width) : \"\")" + "---------------- ---------------- ---------\n"); + }); OPTION(report_t, color); OPTION_(report_t, collapse, DO() { // -n // Make sure that balance reports are collapsed too, but only apply it // to account xacts - parent->HANDLER(display_).on(string("--collapse"), "post|depth<=1"); + OTHER(display_).on(whence, "post|depth<=1"); }); OPTION_(report_t, collapse_if_zero, DO() { - parent->HANDLER(collapse).on_only(string("--collapse-if-zero")); + OTHER(collapse).on(whence); }); OPTION(report_t, columns_); OPTION(report_t, count); - OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) { - on(none, - "%(quoted(date))," - "%(quoted(code))," - "%(quoted(payee))," - "%(quoted(display_account))," - "%(quoted(commodity))," - "%(quoted(quantity(scrub(display_amount))))," - "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," - "%(quoted(join(note | xact.note)))\n"); - }); + OPTION__ + (report_t, csv_format_, + CTOR(report_t, csv_format_) { + on(none, + "%(quoted(date))," + "%(quoted(code))," + "%(quoted(payee))," + "%(quoted(display_account))," + "%(quoted(commodity))," + "%(quoted(quantity(scrub(display_amount))))," + "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," + "%(quoted(join(note | xact.note)))\n"); + }); OPTION_(report_t, current, DO() { // -c - parent->HANDLER(limit_).on(string("--current"), "date<=today"); + OTHER(limit_).on(whence, "date<=today"); }); OPTION_(report_t, daily, DO() { // -D - parent->HANDLER(period_).on(string("--daily"), "daily"); + OTHER(period_).on(whence, "daily"); }); OPTION(report_t, date_); OPTION(report_t, date_format_); OPTION(report_t, datetime_format_); - OPTION_(report_t, depth_, DO_(args) { - parent->HANDLER(display_) - .on(string("--depth"), string("depth<=") + args.get<string>(1)); + OPTION_(report_t, dc, DO() { + OTHER(amount_).expr.set_base_expr + ("(amount > 0 ? amount : 0, amount < 0 ? amount : 0)"); + + OTHER(register_format_) + .on(none, + "%(ansify_if(" + " ansify_if(justify(format_date(date), int(date_width))," + " green if color and date > today)," + " bold if should_bold))" + " %(ansify_if(" + " ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), " + " bold if color and !cleared and actual)," + " bold if should_bold))" + " %(ansify_if(" + " ansify_if(justify(truncated(display_account, int(account_width), " + " abbrev_len), int(account_width))," + " blue if color)," + " bold if should_bold))" + " %(ansify_if(" + " justify(scrub(abs(get_at(display_amount, 0))), int(amount_width), " + " 3 + int(meta_width) + int(date_width) + int(payee_width)" + " + int(account_width) + int(amount_width) + int(prepend_width)," + " true, color)," + " bold if should_bold))" + " %(ansify_if(" + " justify(scrub(abs(get_at(display_amount, 1))), int(amount_width), " + " 4 + int(meta_width) + int(date_width) + int(payee_width)" + " + int(account_width) + int(amount_width) + int(amount_width) + int(prepend_width)," + " true, color)," + " bold if should_bold))" + " %(ansify_if(" + " justify(scrub(get_at(display_total, 0) + get_at(display_total, 1)), int(total_width), " + " 5 + int(meta_width) + int(date_width) + int(payee_width)" + " + int(account_width) + int(amount_width) + int(amount_width) + int(total_width)" + " + int(prepend_width), true, color)," + " bold if should_bold))\n%/" + "%(justify(\" \", int(date_width)))" + " %(ansify_if(" + " justify(truncated(has_tag(\"Payee\") ? payee : \" \", " + " int(payee_width)), int(payee_width))," + " bold if should_bold))" + " %$3 %$4 %$5 %$6\n"); + + OTHER(balance_format_) + .on(none, + "%(ansify_if(" + " justify(scrub(abs(get_at(display_total, 0))), 14," + " 14 + int(prepend_width), true, color)," + " bold if should_bold)) " + "%(ansify_if(" + " justify(scrub(abs(get_at(display_total, 1))), 14," + " 14 + 1 + int(prepend_width) + int(total_width), true, color)," + " bold if should_bold)) " + "%(ansify_if(" + " justify(scrub(get_at(display_total, 0) + get_at(display_total, 1)), 14," + " 14 + 2 + int(prepend_width) + int(total_width) + int(total_width), true, color)," + " bold if should_bold))" + " %(!options.flat ? depth_spacer : \"\")" + "%-(ansify_if(" + " ansify_if(partial_account(options.flat), blue if color)," + " bold if should_bold))\n%/" + "%$1 %$2 %$3\n%/" + "%(prepend_width ? \" \" * int(prepend_width) : \"\")" + "--------------------------------------------\n"); + }); + + OPTION_(report_t, depth_, DO_(str) { + OTHER(display_).on(whence, string("depth<=") + str); }); OPTION_(report_t, deviation, DO() { - parent->HANDLER(display_total_) - .set_expr(string("--deviation"), "amount_expr-total_expr/count"); + OTHER(display_total_) + .on(whence, "display_amount-display_total"); }); - OPTION__ - (report_t, display_, // -d - CTOR(report_t, display_) {} - virtual void on_with(const optional<string>& whence, const value_t& text) { - if (! handled) - option_t<report_t>::on_with(whence, text); - else - option_t<report_t>::on_with(whence, - string_value(string("(") + str() + ")&(" + - text.as_string() + ")")); + OPTION_ + (report_t, display_, + DO_(str) { // -d + if (handled) + value = string("(") + value + ")&(" + str + ")"; }); OPTION__ (report_t, display_amount_, - expr_t expr; - CTOR(report_t, display_amount_) { - set_expr(none, "amount_expr"); - } - void set_expr(const optional<string>& whence, const string& str) { - expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); + DECL1(report_t, display_amount_, merged_expr_t, expr, + ("display_amount", "amount_expr")) {} + DO_(str) { + expr.append(str); }); OPTION__ (report_t, display_total_, - expr_t expr; - CTOR(report_t, display_total_) { - set_expr(none, "total_expr"); - } - void set_expr(const optional<string>& whence, const string& str) { - expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); + DECL1(report_t, display_total_, merged_expr_t, expr, + ("display_total", "total_expr")) {} + DO_(str) { + expr.append(str); }); OPTION(report_t, dow); OPTION(report_t, aux_date); OPTION(report_t, empty); // -E - OPTION_(report_t, end_, DO_(args) { // -e - date_interval_t interval(args.get<string>(1)); + OPTION_(report_t, end_, DO_(str) { // -e // Use begin() here so that if the user says --end=2008, we end on // 2008/01/01 instead of 2009/01/01 (which is what end() would // return). - optional<date_t> end = interval.begin(); - if (! end) + date_interval_t interval(str); + if (optional<date_t> end = interval.begin()) { + string predicate = "date<[" + to_iso_extended_string(*end) + "]"; + OTHER(limit_).on(whence, predicate); + + parent->terminus = datetime_t(*end); + } else { throw_(std::invalid_argument, _("Could not determine end of period '%1'") - << args.get<string>(1)); - - string predicate = "date<[" + to_iso_extended_string(*end) + "]"; - parent->HANDLER(limit_).on(string("--end"), predicate); - - parent->terminus = datetime_t(*end); + << str); + } }); OPTION(report_t, equity); OPTION(report_t, exact); - OPTION_(report_t, exchange_, DO_(args) { // -X - on_with(args.get<string>(0), args[1]); - call_scope_t no_args(*parent); - no_args.push_back(args[0]); - parent->HANDLER(market).parent = parent; - parent->HANDLER(market).handler(no_args); + OPTION_(report_t, exchange_, DO_() { // -X + // Using -X implies -V. The main difference is that now + // HANDLER(exchange_) contains the name of a commodity, which + // is accessed via the "exchange" value expression function. + OTHER(market).on(whence); }); OPTION(report_t, flat); @@ -608,115 +658,112 @@ public: OPTION(report_t, format_); // -F OPTION_(report_t, gain, DO() { // -G - parent->HANDLER(revalued).on_only(string("--gain")); - parent->HANDLER(amount_).set_expr(string("--gain"), "(amount, cost)"); + OTHER(revalued).on(whence); + OTHER(amount_).expr.set_base_expr("(amount, cost)"); + // Since we are displaying the amounts of revalued postings, they // will end up being composite totals, and hence a pair of pairs. - parent->HANDLER(display_amount_) - .set_expr(string("--gain"), - "use_direct_amount ? amount :" - " (is_seq(get_at(amount_expr, 0)) ?" - " get_at(get_at(amount_expr, 0), 0) :" - " market(get_at(amount_expr, 0), value_date, exchange)" - " - get_at(amount_expr, 1))"); - parent->HANDLER(revalued_total_) - .set_expr(string("--gain"), - "(market(get_at(total_expr, 0), value_date, exchange), " - "get_at(total_expr, 1))"); - parent->HANDLER(display_total_) - .set_expr(string("--gain"), - "use_direct_amount ? total_expr :" - " market(get_at(total_expr, 0), value_date, exchange)" - " - get_at(total_expr, 1)"); + OTHER(display_amount_) + .on(whence, + "use_direct_amount ? amount :" + " (is_seq(get_at(amount_expr, 0)) ?" + " get_at(get_at(amount_expr, 0), 0) :" + " market(get_at(amount_expr, 0), value_date, exchange)" + " - get_at(amount_expr, 1))"); + OTHER(revalued_total_) + .on(whence, + "(market(get_at(total_expr, 0), value_date, exchange), " + "get_at(total_expr, 1))"); + OTHER(display_total_) + .on(whence, + "use_direct_amount ? total_expr :" + " market(get_at(total_expr, 0), value_date, exchange)" + " - get_at(total_expr, 1)"); }); OPTION(report_t, generated); - OPTION__ + OPTION_ (report_t, group_by_, expr_t expr; - CTOR(report_t, group_by_) {} - void set_expr(const optional<string>& whence, const string& str) { + DO_(str) { expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); }); - OPTION__(report_t, group_title_format_, CTOR(report_t, group_title_format_) { - on(none, "%(value)\n"); - }); + OPTION__ + (report_t, group_title_format_, + CTOR(report_t, group_title_format_) { + on(none, "%(value)\n"); + }); OPTION(report_t, head_); + + OPTION_(report_t, historical, DO() { // -H + OTHER(market).on(whence); + OTHER(amount_) + .on(whence, "nail_down(amount_expr, " + "market(amount_expr, value_date, exchange))"); + }); + + OPTION(report_t, immediate); OPTION(report_t, inject_); OPTION_(report_t, invert, DO() { - parent->HANDLER(amount_).set_expr(string("--invert"), "-amount"); + OTHER(amount_).on(whence, "-amount"); }); - OPTION__ - (report_t, limit_, // -l - CTOR(report_t, limit_) {} - virtual void on_with(const optional<string>& whence, const value_t& text) { - if (! handled) - option_t<report_t>::on_with(whence, text); - else - option_t<report_t>::on_with(whence, - string_value(string("(") + str() + ")&(" + - text.as_string() + ")")); + OPTION_ + (report_t, limit_, + DO_(str) { // -l + if (handled) + value = string("(") + value + ")&(" + str + ")"; }); OPTION(report_t, lot_dates); OPTION(report_t, lot_prices); - OPTION(report_t, lot_tags); + OPTION(report_t, lot_notes); OPTION(report_t, lots); OPTION(report_t, lots_actual); OPTION_(report_t, market, DO() { // -V - parent->HANDLER(revalued).on_only(string("--market")); - parent->HANDLER(display_amount_) - .set_expr(string("--market"), - "market(amount_expr, value_date, exchange)"); - parent->HANDLER(display_total_) - .set_expr(string("--market"), - "market(total_expr, value_date, exchange)"); + OTHER(revalued).on(whence); + + OTHER(display_amount_) + .on(whence, "market(display_amount, value_date, exchange)"); + OTHER(display_total_) + .on(whence, "market(display_total, value_date, exchange)"); }); OPTION(report_t, meta_); OPTION_(report_t, monthly, DO() { // -M - parent->HANDLER(period_).on(string("--monthly"), "monthly"); + OTHER(period_).on(whence, "monthly"); }); OPTION_(report_t, no_color, DO() { - parent->HANDLER(color).off(); + OTHER(color).off(); }); OPTION(report_t, no_rounding); OPTION(report_t, no_titles); OPTION(report_t, no_total); - OPTION_(report_t, now_, DO_(args) { - date_interval_t interval(args.get<string>(1)); - optional<date_t> begin = interval.begin(); - if (! begin) + OPTION_(report_t, now_, DO_(str) { + date_interval_t interval(str); + if (optional<date_t> begin = interval.begin()) { + ledger::epoch = parent->terminus = datetime_t(*begin); + } else { throw_(std::invalid_argument, _("Could not determine beginning of period '%1'") - << args.get<string>(1)); - ledger::epoch = parent->terminus = datetime_t(*begin); + << str); + } }); - OPTION__ + OPTION_ (report_t, only_, - CTOR(report_t, only_) {} - virtual void on_with(const optional<string>& whence, const value_t& text) { - if (! handled) - option_t<report_t>::on_with(whence, text); - else - option_t<report_t>::on_with(whence, - string_value(string("(") + str() + ")&(" + - text.as_string() + ")")); + DO_(str) { + if (handled) + value = string("(") + value + ")&(" + str + ")"; }); OPTION(report_t, output_); // -o @@ -737,181 +784,162 @@ public: setenv("LESS", "-FRSX", 0); // don't overwrite } } - } - virtual void on_with(const optional<string>& whence, const value_t& text) { - string cmd(text.to_string()); - if (cmd == "" || cmd == "false" || cmd == "off" || - cmd == "none" || cmd == "no" || cmd == "disable") - option_t<report_t>::off(); - else - option_t<report_t>::on_with(whence, text); }); #else // HAVE_ISATTY - OPTION__ - (report_t, pager_, - CTOR(report_t, pager_) { - } - virtual void on_with(const optional<string>& whence, const value_t& text) { - string cmd(text.to_string()); - if (cmd == "" || cmd == "false" || cmd == "off" || - cmd == "none" || cmd == "no" || cmd == "disable") - option_t<report_t>::off(); - else - option_t<report_t>::on_with(whence, text); - }); + OPTION(report_t, pager_); #endif // HAVE_ISATTY + OPTION_(report_t, no_pager, DO() { + OTHER(pager_).off(); + }); + OPTION(report_t, payee_); OPTION_(report_t, pending, DO() { // -C - parent->HANDLER(limit_).on(string("--pending"), "pending"); + OTHER(limit_).on(whence, "pending"); }); OPTION_(report_t, percent, DO() { // -% - parent->HANDLER(total_) - .set_expr(string("--percent"), - "((is_account&parent&parent.total)?" - " percent(scrub(total), scrub(parent.total)):0)"); + OTHER(total_) + .on(whence, + "((is_account&parent&parent.total)?" + " percent(scrub(total), scrub(parent.total)):0)"); }); - OPTION__ - (report_t, period_, // -p - CTOR(report_t, period_) {} - virtual void on_with(const optional<string>& whence, const value_t& text) { - if (! handled) - option_t<report_t>::on_with(whence, text); - else - option_t<report_t>::on_with(whence, - string_value(text.as_string() + " " + str())); + OPTION_ + (report_t, period_, + DO_(str) { // -p + if (handled) + value += string(" ") + str; }); OPTION(report_t, pivot_); - OPTION__(report_t, plot_amount_format_, CTOR(report_t, plot_amount_format_) { - on(none, - "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n"); - }); + OPTION__ + (report_t, plot_amount_format_, + CTOR(report_t, plot_amount_format_) { + on(none, + "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n"); + }); - OPTION__(report_t, plot_total_format_, CTOR(report_t, plot_total_format_) { - on(none, - "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n"); - }); + OPTION__ + (report_t, plot_total_format_, + CTOR(report_t, plot_total_format_) { + on(none, + "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n"); + }); OPTION(report_t, prepend_format_); - OPTION_(report_t, prepend_width_, DO_(args) { - value = args.get<long>(1); - }); + OPTION(report_t, prepend_width_); OPTION_(report_t, price, DO() { // -I - parent->HANDLER(display_amount_) - .set_expr(string("--price"), "price(amount_expr)"); - parent->HANDLER(display_total_) - .set_expr(string("--price"), "price(total_expr)"); + OTHER(amount_).expr.set_base_expr("price"); }); - OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) { - on(none, - "%(date) %-8(display_account) %(justify(scrub(display_amount), 12, " - " 2 + 9 + 8 + 12, true, color))\n"); - }); + OPTION__ + (report_t, prices_format_, + CTOR(report_t, prices_format_) { + on(none, + "%(date) %-8(display_account) %(justify(scrub(display_amount), 12, " + " 2 + 9 + 8 + 12, true, color))\n"); + }); - OPTION__(report_t, pricedb_format_, CTOR(report_t, pricedb_format_) { - on(none, - "P %(datetime) %(display_account) %(scrub(display_amount))\n"); - }); + OPTION__ + (report_t, pricedb_format_, + CTOR(report_t, pricedb_format_) { + on(none, + "P %(datetime) %(display_account) %(scrub(display_amount))\n"); + }); OPTION(report_t, primary_date); OPTION_(report_t, quantity, DO() { // -O - parent->HANDLER(revalued).off(); - parent->HANDLER(amount_).set_expr(string("--quantity"), "amount"); - parent->HANDLER(total_).set_expr(string("--quantity"), "total"); + OTHER(revalued).off(); + + OTHER(amount_).expr.set_base_expr("amount"); + OTHER(total_).expr.set_base_expr("total"); }); OPTION_(report_t, quarterly, DO() { - parent->HANDLER(period_).on(string("--quarterly"), "quarterly"); + OTHER(period_).on(whence, "quarterly"); }); OPTION(report_t, raw); OPTION_(report_t, real, DO() { // -R - parent->HANDLER(limit_).on(string("--real"), "real"); + OTHER(limit_).on(whence, "real"); }); - OPTION__(report_t, register_format_, CTOR(report_t, register_format_) { - on(none, - "%(ansify_if(" - " ansify_if(justify(format_date(date), date_width)," - " green if color and date > today)," - " bold if should_bold))" - " %(ansify_if(" - " ansify_if(justify(truncated(payee, payee_width), payee_width), " - " bold if color and !cleared and actual)," - " bold if should_bold))" - " %(ansify_if(" - " ansify_if(justify(truncated(display_account, account_width, " - " abbrev_len), account_width)," - " blue if color)," - " bold if should_bold))" - " %(ansify_if(" - " justify(scrub(display_amount), amount_width, " - " 3 + meta_width + date_width + payee_width" - " + account_width + amount_width + prepend_width," - " true, color)," - " bold if should_bold))" - " %(ansify_if(" - " justify(scrub(display_total), total_width, " - " 4 + meta_width + date_width + payee_width" - " + account_width + amount_width + total_width" - " + prepend_width, true, color)," - " bold if should_bold))\n%/" - "%(justify(\" \", date_width))" - " %(ansify_if(" - " justify(truncated(has_tag(\"Payee\") ? payee : \" \", " - " payee_width), payee_width)," - " bold if should_bold))" - " %$3 %$4 %$5\n"); - }); + OPTION__ + (report_t, register_format_, + CTOR(report_t, register_format_) { + on(none, + "%(ansify_if(" + " ansify_if(justify(format_date(date), int(date_width))," + " green if color and date > today)," + " bold if should_bold))" + " %(ansify_if(" + " ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), " + " bold if color and !cleared and actual)," + " bold if should_bold))" + " %(ansify_if(" + " ansify_if(justify(truncated(display_account, int(account_width), " + " abbrev_len), int(account_width))," + " blue if color)," + " bold if should_bold))" + " %(ansify_if(" + " justify(scrub(display_amount), int(amount_width), " + " 3 + int(meta_width) + int(date_width) + int(payee_width)" + " + int(account_width) + int(amount_width) + int(prepend_width)," + " true, color)," + " bold if should_bold))" + " %(ansify_if(" + " justify(scrub(display_total), int(total_width), " + " 4 + int(meta_width) + int(date_width) + int(payee_width)" + " + int(account_width) + int(amount_width) + int(total_width)" + " + int(prepend_width), true, color)," + " bold if should_bold))\n%/" + "%(justify(\" \", int(date_width)))" + " %(ansify_if(" + " justify(truncated(has_tag(\"Payee\") ? payee : \" \", " + " int(payee_width)), int(payee_width))," + " bold if should_bold))" + " %$3 %$4 %$5\n"); + }); OPTION(report_t, related); // -r OPTION_(report_t, related_all, DO() { - parent->HANDLER(related).on_only(string("--related-all")); + OTHER(related).on(whence); }); OPTION(report_t, revalued); OPTION(report_t, revalued_only); - OPTION__ + OPTION_ (report_t, revalued_total_, expr_t expr; - CTOR(report_t, revalued_total_) {} - void set_expr(const optional<string>& whence, const string& str) { + DO_(str) { expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); }); OPTION(report_t, rich_data); OPTION(report_t, seed_); - OPTION_(report_t, sort_, DO_(args) { // -S - on_with(args.get<string>(0), args[1]); - parent->HANDLER(sort_xacts_).off(); - parent->HANDLER(sort_all_).off(); + OPTION_(report_t, sort_, DO_(str) { // -S + OTHER(sort_xacts_).off(); + OTHER(sort_all_).off(); }); - OPTION_(report_t, sort_all_, DO_(args) { - parent->HANDLER(sort_).on_with(string("--sort-all"), args[1]); - parent->HANDLER(sort_xacts_).off(); + OPTION_(report_t, sort_all_, DO_(str) { + OTHER(sort_).on(whence, str); + OTHER(sort_xacts_).off(); }); - OPTION_(report_t, sort_xacts_, DO_(args) { - parent->HANDLER(sort_).on_with(string("--sort-xacts"), args[1]); - parent->HANDLER(sort_all_).off(); + OPTION_(report_t, sort_xacts_, DO_(str) { + OTHER(sort_).on(whence, str); + OTHER(sort_all_).off(); }); OPTION(report_t, start_of_week_); @@ -920,22 +948,14 @@ public: OPTION__ (report_t, total_, // -T - expr_t expr; - CTOR(report_t, total_) { - set_expr(none, "total"); - } - void set_expr(const optional<string>& whence, const string& str) { - expr = str; - on(whence, str); - } - DO_(args) { - set_expr(args.get<string>(0), args.get<string>(1)); + DECL1(report_t, total_, merged_expr_t, expr, ("total_expr", "total")) {} + DO_(str) { + expr.append(str); }); OPTION(report_t, total_data); // -J - OPTION_(report_t, truncate_, DO_(args) { - string style(args.get<string>(1)); + OPTION_(report_t, truncate_, DO_(style) { if (style == "leading") format_t::default_style = format_t::TRUNCATE_LEADING; else if (style == "middle") @@ -953,7 +973,7 @@ public: }); OPTION_(report_t, uncleared, DO() { // -U - parent->HANDLER(limit_).on(string("--uncleared"), "uncleared|pending"); + OTHER(limit_).on(whence, "uncleared|pending"); }); OPTION(report_t, unrealized); @@ -962,48 +982,28 @@ public: OPTION(report_t, unrealized_losses_); OPTION_(report_t, unround, DO() { - parent->HANDLER(display_amount_) - .set_expr(string("--unround"), "unrounded(amount_expr)"); - parent->HANDLER(display_total_) - .set_expr(string("--unround"), "unrounded(total_expr)"); + OTHER(amount_).on(whence, "unrounded(amount_expr)"); + OTHER(total_).on(whence, "unrounded(total_expr)"); }); OPTION_(report_t, weekly, DO() { // -W - parent->HANDLER(period_).on(string("--weekly"), "weekly"); + OTHER(period_).on(whence, "weekly"); }); OPTION_(report_t, wide, DO() { // -w - parent->HANDLER(columns_).on_with(string("--wide"), 132L); + OTHER(columns_).on(whence, "132"); }); OPTION_(report_t, yearly, DO() { // -Y - parent->HANDLER(period_).on(string("--yearly"), "yearly"); + OTHER(period_).on(whence, "yearly"); }); - OPTION__(report_t, meta_width_, - bool specified; - CTOR(report_t, meta_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); - OPTION__(report_t, date_width_, - bool specified; - CTOR(report_t, date_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); - OPTION__(report_t, payee_width_, - bool specified; - CTOR(report_t, payee_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); - OPTION__(report_t, account_width_, - bool specified; - CTOR(report_t, account_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); - OPTION__(report_t, amount_width_, - bool specified; - CTOR(report_t, amount_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); - OPTION__(report_t, total_width_, - bool specified; - CTOR(report_t, total_width_) { specified = false; } - DO_(args) { value = args.get<long>(1); specified = true; }); + OPTION(report_t, meta_width_); + OPTION(report_t, date_width_); + OPTION(report_t, payee_width_); + OPTION(report_t, account_width_); + OPTION(report_t, amount_width_); + OPTION(report_t, total_width_); }; diff --git a/src/scope.cc b/src/scope.cc index b2a7b17b..00327159 100644 --- a/src/scope.cc +++ b/src/scope.cc @@ -35,7 +35,8 @@ namespace ledger { -scope_t * scope_t::default_scope = NULL; +scope_t * scope_t::default_scope = NULL; +empty_scope_t * scope_t::empty_scope = NULL; void symbol_scope_t::define(const symbol_t::kind_t kind, const string& name, expr_t::ptr_op_t def) @@ -53,8 +54,8 @@ void symbol_scope_t::define(const symbol_t::kind_t kind, assert(i != symbols->end()); symbols->erase(i); - result = symbols->insert(symbol_map::value_type(symbol_t(kind, name, def), - def)); + result = symbols->insert(symbol_map::value_type + (symbol_t(kind, name, def), def)); if (! result.second) throw_(compile_error, _("Redefinition of '%1' in the same scope") << name); diff --git a/src/scope.h b/src/scope.h index 785ce284..ccfc750b 100644 --- a/src/scope.h +++ b/src/scope.h @@ -70,8 +70,7 @@ struct symbol_t TRACE_CTOR(symbol_t, "symbol_t::kind_t, string"); } symbol_t(const symbol_t& sym) - : kind(sym.kind), name(sym.name), - definition(sym.definition) { + : kind(sym.kind), name(sym.name), definition(sym.definition) { TRACE_CTOR(symbol_t, "copy"); } ~symbol_t() throw() { @@ -81,6 +80,9 @@ struct symbol_t bool operator<(const symbol_t& sym) const { return kind < sym.kind || name < sym.name; } + bool operator==(const symbol_t& sym) const { + return kind == sym.kind || name == sym.name; + } #if defined(HAVE_BOOST_SERIALIZATION) private: @@ -97,10 +99,13 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +class empty_scope_t; + class scope_t { public: - static scope_t * default_scope; + static scope_t * default_scope; + static empty_scope_t * empty_scope; explicit scope_t() { TRACE_CTOR(scope_t, ""); @@ -134,6 +139,17 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +class empty_scope_t : public scope_t +{ +public: + virtual string description() { + return _("<empty>"); + } + virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t, const string&) { + return NULL; + } +}; + class child_scope_t : public noncopyable, public scope_t { public: @@ -142,8 +158,7 @@ public: explicit child_scope_t() : parent(NULL) { TRACE_CTOR(child_scope_t, ""); } - explicit child_scope_t(scope_t& _parent) - : parent(&_parent) { + explicit child_scope_t(scope_t& _parent) : parent(&_parent) { TRACE_CTOR(child_scope_t, "scope_t&"); } virtual ~child_scope_t() { @@ -229,6 +244,8 @@ private: template <typename T> T * search_scope(scope_t * ptr, bool prefer_direct_parents = false) { + DEBUG("scope.search", "Searching scope " << ptr->description()); + if (T * sought = dynamic_cast<T *>(ptr)) return sought; @@ -274,7 +291,7 @@ class symbol_scope_t : public child_scope_t optional<symbol_map> symbols; public: - explicit symbol_scope_t() { + explicit symbol_scope_t() : child_scope_t() { TRACE_CTOR(symbol_scope_t, ""); } explicit symbol_scope_t(scope_t& _parent) : child_scope_t(_parent) { @@ -363,13 +380,8 @@ protected: class call_scope_t : public context_scope_t { -#if defined(DEBUG_ON) public: -#endif value_t args; -#if defined(DEBUG_ON) -private: -#endif mutable void * ptr; value_t& resolve(const std::size_t index, @@ -386,17 +398,12 @@ public: : context_scope_t(_parent, _parent.type_context(), _parent.type_required()), ptr(NULL), locus(_locus), depth(_depth) { - TRACE_CTOR(call_scope_t, - "scope_t&, value_t::type_t, bool, expr_t::ptr_op_t *, int"); + TRACE_CTOR(call_scope_t, "scope_t&, expr_t::ptr_op_t *, const int"); } virtual ~call_scope_t() { TRACE_DTOR(call_scope_t); } - virtual string description() { - return context_scope_t::description(); - } - void set_args(const value_t& _args) { args = _args; } diff --git a/src/series.h b/src/series.h deleted file mode 100644 index 75b98194..00000000 --- a/src/series.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * - Neither the name of New Artisans LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @addtogroup expr - */ - -/** - * @file series.h - * @author John Wiegley - * - * @ingroup expr - */ -#ifndef _SERIES_H -#define _SERIES_H - -#include "scope.h" - -namespace ledger { - -class expr_series_t -{ -protected: - scope_t * context; - -public: - optional<std::list<expr_t> > exprs; - expr_t default_expr; - std::string variable; - - expr_series_t(const std::string& _variable) - : context(NULL), default_expr(_variable), variable(_variable) { - TRACE_CTOR(expr_series_t, "std::string"); - } - expr_series_t(const expr_t& expr, const std::string& _variable) - : context(const_cast<expr_t&>(expr).get_context()), - default_expr(expr), variable(_variable) { - TRACE_CTOR(expr_series_t, "expr_t, std::string"); - } - expr_series_t(const expr_series_t& other) - : context(other.context), exprs(other.exprs), - default_expr(other.default_expr), variable(other.variable) { - TRACE_CTOR(expr_series_t, "copy"); - } - virtual ~expr_series_t() { - TRACE_DTOR(expr_series_t); - } - - scope_t * get_context() { - return context; - } - void set_context(scope_t * scope) { - context = scope; - } - - bool empty() const { - return ! exprs || exprs->empty(); - } - - void push_back(const expr_t& expr) { - if (! exprs) - exprs = std::list<expr_t>(); - exprs->push_back(expr); - } - void pop_back() { - assert(exprs); - exprs->pop_back(); - } - - void mark_uncompiled() { - if (exprs) - foreach (expr_t& expr, *exprs) - expr.mark_uncompiled(); - else - default_expr.mark_uncompiled(); - } - - void compile(scope_t& scope) { - if (exprs) - foreach (expr_t& expr, *exprs) - expr.compile(scope); - else - default_expr.compile(scope); - } - - value_t calc(scope_t& scope) { - if (exprs) { - value_t result; - symbol_scope_t sym_scope(scope); - std::size_t len(exprs->size()); - - foreach (expr_t& expr, *exprs) { - result = expr.calc(sym_scope); - if (--len > 0) - sym_scope.define(symbol_t::FUNCTION, variable, - expr_t::op_t::wrap_value(result)); - } - return result; - } else { - return default_expr.calc(scope); - } - } -}; - -} // namespace ledger - -#endif // _SERIES_H diff --git a/src/session.cc b/src/session.cc index 3e7cdb3d..76061de7 100644 --- a/src/session.cc +++ b/src/session.cc @@ -112,6 +112,8 @@ std::size_t session_t::read_data(const string& master_account) journal->checking_style = journal_t::CHECK_ERROR; else if (HANDLED(strict)) journal->checking_style = journal_t::CHECK_WARNING; + else if (HANDLED(value_expr_)) + journal->value_expr = HANDLER(value_expr_).str(); #if defined(HAVE_BOOST_SERIALIZATION) optional<archive_t> cache; @@ -268,6 +270,15 @@ value_t session_t::fn_max(call_scope_t& args) return args[1] > args[0] ? args[1] : args[0]; } +value_t session_t::fn_int(call_scope_t& args) +{ + return args[0].to_long(); +} +value_t session_t::fn_str(call_scope_t& args) +{ + return string_value(args[0].to_string()); +} + value_t session_t::fn_lot_price(call_scope_t& args) { amount_t amt(args.get<amount_t>(1, false)); @@ -334,6 +345,9 @@ option_t<session_t> * session_t::lookup_option(const char * p) case 's': OPT(strict); break; + case 'v': + OPT(value_expr_); + break; } return NULL; } @@ -360,6 +374,11 @@ expr_t::ptr_op_t session_t::lookup(const symbol_t::kind_t kind, return MAKE_FUNCTOR(session_t::fn_lot_tag); break; + case 'i': + if (is_eq(p, "int")) + return MAKE_FUNCTOR(session_t::fn_int); + break; + case 'm': if (is_eq(p, "min")) return MAKE_FUNCTOR(session_t::fn_min); @@ -367,6 +386,11 @@ expr_t::ptr_op_t session_t::lookup(const symbol_t::kind_t kind, return MAKE_FUNCTOR(session_t::fn_max); break; + case 's': + if (is_eq(p, "str")) + return MAKE_FUNCTOR(session_t::fn_str); + break; + default: break; } diff --git a/src/session.h b/src/session.h index 879efeb6..962664ef 100644 --- a/src/session.h +++ b/src/session.h @@ -59,8 +59,9 @@ class session_t : public symbol_scope_t public: bool flush_on_next_data_file; - std::auto_ptr<journal_t> journal; - parse_context_stack_t parsing_context; + unique_ptr<journal_t> journal; + parse_context_stack_t parsing_context; + optional<expr_t> value_expr; explicit session_t(); virtual ~session_t() { @@ -86,6 +87,8 @@ public: value_t fn_account(call_scope_t& scope); value_t fn_min(call_scope_t& scope); value_t fn_max(call_scope_t& scope); + value_t fn_int(call_scope_t& scope); + value_t fn_str(call_scope_t& scope); value_t fn_lot_price(call_scope_t& scope); value_t fn_lot_date(call_scope_t& scope); value_t fn_lot_tag(call_scope_t& scope); @@ -105,6 +108,7 @@ public: HANDLER(price_db_).report(out); HANDLER(price_exp_).report(out); HANDLER(strict).report(out); + HANDLER(value_expr_).report(out); } option_t<session_t> * lookup_option(const char * p); @@ -126,28 +130,24 @@ public: OPTION__ (session_t, price_exp_, // -Z - CTOR(session_t, price_exp_) { value = 24L * 3600L; } - DO_(args) { - value = args.get<long>(1) * 60L; - }); + CTOR(session_t, price_exp_) { value = "24"; }); OPTION__ (session_t, file_, // -f std::list<path> data_files; CTOR(session_t, file_) {} - DO_(args) { - assert(args.size() == 2); + DO_(str) { if (parent->flush_on_next_data_file) { data_files.clear(); parent->flush_on_next_data_file = false; } - data_files.push_back(args.get<string>(1)); + data_files.push_back(str); }); - OPTION_(session_t, input_date_format_, DO_(args) { - // This changes static variables inside times.h, which affects the basic - // date parser. - set_input_date_format(args.get<string>(1).c_str()); + OPTION_(session_t, input_date_format_, DO_(str) { + // This changes static variables inside times.h, which affects the + // basic date parser. + set_input_date_format(str.c_str()); }); OPTION(session_t, explicit); @@ -156,6 +156,7 @@ public: OPTION(session_t, permissive); OPTION(session_t, price_db_); OPTION(session_t, strict); + OPTION(session_t, value_expr_); }; /** diff --git a/src/system.hh.in b/src/system.hh.in index 5e5a0c1d..a38deb1f 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -183,8 +183,13 @@ typedef std::ostream::pos_type ostream_pos_type; #include <boost/regex/icu.hpp> #else #include <boost/regex.hpp> - #endif // HAVE_BOOST_REGEX_UNICODE + +#include <boost/tokenizer.hpp> + +#include <boost/tuple/tuple.hpp> +#include <boost/tuple/tuple_comparison.hpp> + #include <boost/variant.hpp> #include <boost/version.hpp> diff --git a/src/textual.cc b/src/textual.cc index 95635184..a8cb844b 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -71,22 +71,22 @@ namespace { class instance_t : public noncopyable, public scope_t { - public: - parse_context_stack_t& context_stack; - parse_context_t& context; - std::istream& in; - instance_t * parent; - + parse_context_stack_t& context_stack; + parse_context_t& context; + std::istream& in; + instance_t * parent; std::list<application_t> apply_stack; #if defined(TIMELOG_SUPPORT) - time_log_t timelog; + time_log_t timelog; #endif instance_t(parse_context_stack_t& _context_stack, - parse_context_t& _context, instance_t * _parent = NULL) + parse_context_t& _context, + instance_t * _parent = NULL) : context_stack(_context_stack), context(_context), - in(*context.stream.get()), parent(_parent), timelog(context) {} + in(*context.stream.get()), parent(_parent), + timelog(context) {} virtual string description() { return _("textual parser"); @@ -107,10 +107,12 @@ namespace { return (in.good() && ! in.eof() && (in.peek() == ' ' || in.peek() == '\t')); } +#if defined(HAVE_BOOST_PYTHON) bool peek_blank_line() { return (in.good() && ! in.eof() && (in.peek() == '\n' || in.peek() == '\r')); } +#endif void read_next_directive(); @@ -124,6 +126,7 @@ namespace { void account_directive(char * line); void account_alias_directive(account_t * account, string alias); void account_payee_directive(account_t * account, string payee); + void account_value_directive(account_t * account, string expr_str); void account_default_directive(account_t * account); void default_account_directive(char * line); @@ -134,6 +137,7 @@ namespace { void commodity_directive(char * line); void commodity_alias_directive(commodity_t& comm, string alias); + void commodity_value_directive(commodity_t& comm, string expr_str); void commodity_format_directive(commodity_t& comm, string format); void commodity_nomarket_directive(commodity_t& comm); void commodity_default_directive(commodity_t& comm); @@ -163,10 +167,10 @@ namespace { void eval_directive(char * line); void assert_directive(char * line); void check_directive(char * line); + void value_directive(char * line); -#if defined(HAVE_BOOST_PYTHON) + void import_directive(char * line); void python_directive(char * line); -#endif post_t * parse_post(char * line, std::streamsize len, @@ -527,7 +531,7 @@ void instance_t::automated_xact_directive(char * line) query.parse_args(string_value(skip_ws(line + 1)).to_sequence(), keeper, false, true); - std::auto_ptr<auto_xact_t> ae(new auto_xact_t(predicate_t(expr, keeper))); + unique_ptr<auto_xact_t> ae(new auto_xact_t(predicate_t(expr, keeper))); ae->pos = position_t(); ae->pos->pathname = context.pathname; ae->pos->beg_pos = context.line_beg_pos; @@ -557,11 +561,6 @@ void instance_t::automated_xact_directive(char * line) item->add_flags(ITEM_NOTE_ON_NEXT_LINE); item->pos->end_pos = context.curr_pos; item->pos->end_line++; - - // If there was no last_post yet, then deferred notes get applied to - // the matched posting. Other notes get applied to the auto-generated - // posting. - ae->deferred_notes->back().apply_to_post = last_post; } else if ((remlen > 7 && *p == 'a' && std::strncmp(p, "assert", 6) == 0 && std::isspace(p[6])) || @@ -589,7 +588,7 @@ void instance_t::automated_xact_directive(char * line) parse_post(p, len - (p - line), top_account(), NULL, true)) { reveal_context = true; ae->add_post(post); - last_post = post; + ae->active_post = last_post = post; } reveal_context = true; } @@ -621,7 +620,7 @@ void instance_t::period_xact_directive(char * line) try { - std::auto_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1))); + unique_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1))); pe->pos = position_t(); pe->pos->pathname = context.pathname; pe->pos->beg_pos = context.line_beg_pos; @@ -665,7 +664,7 @@ 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())) { - std::auto_ptr<xact_t> manager(xact); + unique_ptr<xact_t> manager(xact); if (context.journal->add_xact(xact)) { manager.release(); // it's owned by the journal now @@ -874,7 +873,7 @@ void instance_t::account_directive(char * line) char * p = skip_ws(line); account_t * account = context.journal->register_account(p, NULL, top_account()); - std::auto_ptr<auto_xact_t> ae; + unique_ptr<auto_xact_t> ae; while (peek_whitespace_line()) { read_line(line); @@ -890,6 +889,9 @@ void instance_t::account_directive(char * line) else if (keyword == "payee") { account_payee_directive(account, b); } + else if (keyword == "value") { + account_value_directive(account, b); + } else if (keyword == "default") { account_default_directive(account); } @@ -943,10 +945,14 @@ void instance_t::account_alias_directive(account_t * account, string alias) // (account), add a reference to the account in the `account_aliases' // map, which is used by the post parser to resolve alias references. trim(alias); - std::pair<accounts_map::iterator, bool> result - = context.journal - ->account_aliases.insert(accounts_map::value_type(alias, account)); +#if defined(DEBUG_ON) + std::pair<accounts_map::iterator, bool> result = +#endif + context.journal->account_aliases.insert + (accounts_map::value_type(alias, account)); +#if defined(DEBUG_ON) assert(result.second); +#endif } void instance_t::alias_directive(char * line) @@ -975,6 +981,11 @@ void instance_t::account_default_directive(account_t * account) context.journal->bucket = account; } +void instance_t::account_value_directive(account_t * account, string expr_str) +{ + account->value_expr = expr_t(expr_str); +} + void instance_t::payee_directive(char * line) { string payee = context.journal->register_payee(line, NULL); @@ -1019,6 +1030,8 @@ void instance_t::commodity_directive(char * line) string keyword(q); if (keyword == "alias") commodity_alias_directive(*commodity, b); + else if (keyword == "value") + commodity_value_directive(*commodity, b); else if (keyword == "format") commodity_format_directive(*commodity, b); else if (keyword == "nomarket") @@ -1031,17 +1044,15 @@ void instance_t::commodity_directive(char * line) } } -void instance_t::commodity_alias_directive(commodity_t&, string) +void instance_t::commodity_alias_directive(commodity_t& comm, string alias) { -#if 0 trim(alias); - std::pair<commodity_pool_t::commodities_map::iterator, bool> result - = commodity_pool_t::current_pool->commodities.insert - (commodity_pool_t::commodities_map::value_type(alias, &comm)); - if (! result.second) - throw_(parse_error, - _("Cannot use existing commodity name as an alias: %1") << alias); -#endif + commodity_pool_t::current_pool->alias(alias, comm); +} + +void instance_t::commodity_value_directive(commodity_t& comm, string expr_str) +{ + comm.set_value_expr(expr_t(expr_str)); } void instance_t::commodity_format_directive(commodity_t&, string format) @@ -1109,6 +1120,11 @@ void instance_t::check_directive(char * line) context.warning(STR(_("Check failed: %1") << line)); } +void instance_t::value_directive(char * line) +{ + context.journal->value_expr = expr_t(line); +} + void instance_t::comment_directive(char * line) { while (in.good() && ! in.eof()) { @@ -1121,6 +1137,14 @@ void instance_t::comment_directive(char * line) } #if defined(HAVE_BOOST_PYTHON) + +void instance_t::import_directive(char * line) +{ + string module_name(line); + trim(module_name); + python_session->import_option(module_name); +} + void instance_t::python_directive(char * line) { std::ostringstream script; @@ -1160,6 +1184,21 @@ void instance_t::python_directive(char * line) ("journal", python::object(python::ptr(context.journal))); python_session->eval(script.str(), python_interpreter_t::PY_EVAL_MULTI); } + +#else + +void instance_t::import_directive(char *) +{ + throw_(parse_error, + _("'python' directive seen, but Python support is missing")); +} + +void instance_t::python_directive(char *) +{ + throw_(parse_error, + _("'import' directive seen, but Python support is missing")); +} + #endif // HAVE_BOOST_PYTHON bool instance_t::general_directive(char * line) @@ -1239,6 +1278,10 @@ bool instance_t::general_directive(char * line) include_directive(arg); return true; } + else if (std::strcmp(p, "import") == 0) { + import_directive(arg); + return true; + } break; case 'p': @@ -1247,12 +1290,7 @@ bool instance_t::general_directive(char * line) return true; } else if (std::strcmp(p, "python") == 0) { -#if defined(HAVE_BOOST_PYTHON) python_directive(arg); -#else - throw_(parse_error, - _("'python' directive seen, but Python support is missing")); -#endif return true; } break; @@ -1267,6 +1305,13 @@ bool instance_t::general_directive(char * line) return true; } break; + + case 'v': + if (std::strcmp(p, "value") == 0) { + value_directive(arg); + return true; + } + break; } if (expr_t::ptr_op_t op = lookup(symbol_t::DIRECTIVE, p)) { @@ -1287,7 +1332,7 @@ post_t * instance_t::parse_post(char * line, { TRACE_START(post_details, 1, "Time spent parsing postings:"); - std::auto_ptr<post_t> post(new post_t); + unique_ptr<post_t> post(new post_t); post->xact = xact; // this could be NULL post->pos = position_t(); @@ -1402,12 +1447,16 @@ post_t * instance_t::parse_post(char * line, // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST) - if (*next == '@') { + if (*next == '@' || (*next == '(' && *(next + 1) == '@')) { DEBUG("textual.parse", "line " << context.linenum << ": " << "Found a price indicator"); - bool per_unit = true; + if (*next == '(') { + post->add_flags(POST_COST_VIRTUAL); + ++next; + } + bool per_unit = true; if (*++next == '@') { per_unit = false; post->add_flags(POST_COST_IN_FULL); @@ -1415,6 +1464,9 @@ post_t * instance_t::parse_post(char * line, << "And it's for a total price"); } + if (post->has_flags(POST_COST_VIRTUAL) && *(next + 1) == ')') + ++next; + beg = static_cast<std::streamsize>(++next - line); p = skip_ws(next); diff --git a/src/timelog.cc b/src/timelog.cc index 67ea1015..b46d3922 100644 --- a/src/timelog.cc +++ b/src/timelog.cc @@ -88,7 +88,7 @@ namespace { if (! out_event.note.empty() && event.note.empty()) event.note = out_event.note; - std::auto_ptr<xact_t> curr(new xact_t); + unique_ptr<xact_t> curr(new xact_t); curr->_date = event.checkin.date(); curr->code = out_event.desc; // if it wasn't used above curr->payee = event.desc; diff --git a/src/times.h b/src/times.h index 6eadcad3..e3134665 100644 --- a/src/times.h +++ b/src/times.h @@ -218,6 +218,9 @@ struct date_duration_t case YEARS: return date + gregorian::years(length); } +#if !defined(__clang__) + return date_t(); +#endif } date_t subtract(const date_t& date) const { @@ -233,6 +236,9 @@ struct date_duration_t case YEARS: return date - gregorian::years(length); } +#if !defined(__clang__) + return date_t(); +#endif } string to_string() const { diff --git a/src/token.cc b/src/token.cc index 57b97eca..e5d6b218 100644 --- a/src/token.cc +++ b/src/token.cc @@ -421,7 +421,7 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) throw_(parse_error, _("Failed to reset input stream")); c = static_cast<char>(in.peek()); - if (! std::isalpha(c)) + if (! std::isalpha(c) && c != '_') expected('\0', c); parse_ident(in); diff --git a/src/utils.cc b/src/utils.cc index eb1d8009..628fb158 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -553,9 +553,6 @@ void logger_func(log_level_t level) #if defined(VERIFY_ON) IF_VERIFY() *_log_stream << " TIME OBJSZ MEMSZ" << std::endl; -#else - IF_VERIFY() - *_log_stream << " TIME" << std::endl; #endif } diff --git a/src/utils.h b/src/utils.h index e37f37aa..efa203c7 100644 --- a/src/utils.h +++ b/src/utils.h @@ -171,7 +171,7 @@ void report_memory(std::ostream& out, bool report_all = false); #else // ! VERIFY_ON #define VERIFY(x) -#define DO_VERIFY() true +#define DO_VERIFY() false #define TRACE_CTOR(cls, args) #define TRACE_DTOR(cls) @@ -278,6 +278,16 @@ extern string empty_string; strings_list split_arguments(const char * line); +inline string to_string(long num) { + std::ostringstream buf; + buf << num; + return buf.str(); +} + +inline string operator+(const char * left, const string& right) { + return string(left) + right; +} + } // namespace ledger /*@}*/ @@ -353,8 +363,8 @@ inline bool category_matches(const char * cat) { boost::make_u32regex(_log_category->c_str(), boost::regex::perl | boost::regex::icase); #else - boost::make_regex(_log_category->c_str(), - boost::regex::perl | boost::regex::icase); + boost::regex(_log_category->c_str(), + boost::regex::perl | boost::regex::icase); #endif } #if defined(HAVE_BOOST_REGEX_UNICODE) diff --git a/src/value.cc b/src/value.cc index 2446c97a..c14a7104 100644 --- a/src/value.cc +++ b/src/value.cc @@ -724,7 +724,7 @@ value_t& value_t::operator/=(const value_t& val) return *this; case AMOUNT: if (as_balance().single_amount()) { - in_place_simplify(); + in_place_cast(AMOUNT); as_amount_lval() /= val.as_amount(); return *this; } @@ -1228,7 +1228,7 @@ void value_t::in_place_cast(type_t cast_type) case STRING: switch (cast_type) { case INTEGER: { - if (all(as_string(), is_digit())) { + if (all(as_string(), is_any_of("-0123456789"))) { set_long(lexical_cast<long>(as_string())); return; } @@ -1266,8 +1266,8 @@ void value_t::in_place_cast(type_t cast_type) } add_error_context(_("While converting %1:") << *this); - throw_(value_error, _("Cannot convert %1 to %2") - << label() << label(cast_type)); + throw_(value_error, + _("Cannot convert %1 to %2") << label() << label(cast_type)); } void value_t::in_place_negate() @@ -1399,25 +1399,30 @@ bool value_t::is_zero() const return false; } -value_t value_t::value(const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of) const +value_t value_t::value(const datetime_t& moment, + const commodity_t * in_terms_of) const { switch (type()) { case INTEGER: return NULL_VALUE; case AMOUNT: - if (optional<amount_t> val = - as_amount().value(moment, in_terms_of)) + if (optional<amount_t> val = as_amount().value(moment, in_terms_of)) return *val; return NULL_VALUE; case BALANCE: - if (optional<balance_t> bal = - as_balance().value(moment, in_terms_of)) + if (optional<balance_t> bal = as_balance().value(moment, in_terms_of)) return *bal; return NULL_VALUE; + case SEQUENCE: { + value_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(value.value(moment, in_terms_of)); + return temp; + } + default: break; } @@ -1427,37 +1432,100 @@ value_t value_t::value(const optional<datetime_t>& moment, return NULL_VALUE; } -value_t value_t::price() const +value_t value_t::exchange_commodities(const std::string& commodities, + const bool add_prices, + const datetime_t& moment) { - switch (type()) { - case AMOUNT: - return as_amount().price(); - case BALANCE: - return as_balance().price(); - default: - return *this; + if (type() == SEQUENCE) { + value_t temp; + foreach (value_t& value, as_sequence_lval()) + temp.push_back(value.exchange_commodities(commodities, add_prices, moment)); + return temp; } -} -value_t value_t::exchange_commodities(const std::string& commodities, - const bool add_prices, - const optional<datetime_t>& moment) -{ - scoped_array<char> buf(new char[commodities.length() + 1]); - - std::strcpy(buf.get(), commodities.c_str()); - - for (char * p = std::strtok(buf.get(), ","); - p; - p = std::strtok(NULL, ",")) { - if (commodity_t * commodity = - commodity_pool_t::current_pool->parse_price_expression(p, add_prices, - moment)) { - value_t result = value(moment, *commodity); - if (! result.is_null()) - return result; + // If we are repricing to just a single commodity, with no price + // expression, skip the expensive logic below. + if (commodities.find(',') == string::npos && + commodities.find('=') == string::npos) + return value(moment, commodity_pool_t::current_pool->find_or_create(commodities)); + + std::vector<commodity_t *> comms; + std::vector<bool> force; + + typedef tokenizer<char_separator<char> > tokenizer; + tokenizer tokens(commodities, char_separator<char>(",")); + + foreach (const string& name, tokens) { + string::size_type name_len = name.length(); + + if (commodity_t * commodity = commodity_pool_t::current_pool + ->parse_price_expression(name[name_len - 1] == '!' ? + string(name, 0, name_len - 1) : + name, add_prices, moment)) { + DEBUG("commodity.exchange", "Pricing for commodity: " << commodity->symbol()); + comms.push_back(&commodity->referent()); + force.push_back(name[name_len - 1] == '!'); } } + + std::size_t index = 0; + foreach (commodity_t * comm, comms) { + switch (type()) { + case AMOUNT: + DEBUG("commodity.exchange", "We have an amount: " << as_amount_lval()); + if (! force[index] && + std::find(comms.begin(), comms.end(), + &as_amount_lval().commodity().referent()) != comms.end()) + break; + + DEBUG("commodity.exchange", "Referent doesn't match, pricing..."); + if (optional<amount_t> val = as_amount_lval().value(moment, comm)) { + DEBUG("commodity.exchange", "Re-priced amount is: " << *val); + return *val; + } + DEBUG("commodity.exchange", "Was unable to find a price"); + break; + + case BALANCE: { + balance_t temp; + bool repriced = false; + + DEBUG("commodity.exchange", "We have a balance: " << as_balance_lval()); + foreach (const balance_t::amounts_map::value_type& pair, + as_balance_lval().amounts) { + DEBUG("commodity.exchange", "We have a balance amount of commodity: " + << pair.first->symbol() << " == " + << pair.second.commodity().symbol()); + if (! force[index] && + std::find(comms.begin(), comms.end(), + &pair.first->referent()) != comms.end()) { + temp += pair.second; + } else { + DEBUG("commodity.exchange", "Referent doesn't match, pricing..."); + if (optional<amount_t> val = pair.second.value(moment, comm)) { + DEBUG("commodity.exchange", "Re-priced member amount is: " << *val); + temp += *val; + repriced = true; + } else { + DEBUG("commodity.exchange", "Was unable to find price"); + temp += pair.second; + } + } + } + + if (repriced) { + DEBUG("commodity.exchange", "Re-priced balance is: " << temp); + return temp; + } + } + + default: + break; + } + + ++index; + } + return *this; } diff --git a/src/value.h b/src/value.h index 1e4d0ce9..a95968c2 100644 --- a/src/value.h +++ b/src/value.h @@ -477,14 +477,12 @@ public: void in_place_unreduce(); // exists for efficiency's sake // Return the "market value" of a given value at a specific time. - value_t value(const optional<datetime_t>& moment = none, - const optional<commodity_t&>& in_terms_of = none) const; + value_t value(const datetime_t& moment = datetime_t(), + const commodity_t * in_terms_of = NULL) const; - value_t price() const; - - value_t exchange_commodities(const std::string& commodities, - const bool add_prices = false, - const optional<datetime_t>& moment = none); + value_t exchange_commodities(const std::string& commodities, + const bool add_prices = false, + const datetime_t& moment = datetime_t()); /** * Truth tests. diff --git a/src/xact.cc b/src/xact.cc index f05c6069..4e8e56fa 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -269,11 +269,13 @@ bool xact_base_t::finalize() cost_breakdown_t breakdown = commodity_pool_t::current_pool->exchange - (post->amount, *post->cost, false, + (post->amount, *post->cost, false, ! post->has_flags(POST_COST_VIRTUAL), datetime_t(date(), time_duration(0, 0, 0, 0))); if (post->amount.has_annotation() && post->amount.annotation().price) { if (breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) { + DEBUG("xact.finalize", "breakdown.basis_cost = " << breakdown.basis_cost); + DEBUG("xact.finalize", "breakdown.final_cost = " << breakdown.final_cost); if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) { DEBUG("xact.finalize", "gain_loss = " << gain_loss); gain_loss.in_place_round(); @@ -329,13 +331,24 @@ bool xact_base_t::finalize() if (balance.is_balance()) { const balance_t& bal(balance.as_balance()); - typedef std::map<string, amount_t> sorted_amounts_map; +#if 1 + typedef std::map<std::pair<string, annotation_t>, + amount_t> sorted_amounts_map; sorted_amounts_map samp; foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { +#if defined(DEBUG_ON) std::pair<sorted_amounts_map::iterator, bool> result = - samp.insert(sorted_amounts_map::value_type(pair.first->mapping_key(), - pair.second)); +#endif + samp.insert(sorted_amounts_map::value_type + (sorted_amounts_map::key_type + (pair.first->symbol(), + pair.first->has_annotation() ? + as_annotated_commodity(*pair.first).details : + annotation_t()), + pair.second)); +#if defined(DEBUG_ON) assert(result.second); +#endif } bool first = true; @@ -351,6 +364,21 @@ bool xact_base_t::finalize() add_post(p); } } +#else + bool first = true; + foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { + if (first) { + null_post->amount = pair.second.negated(); + null_post->add_flags(POST_CALCULATED); + first = false; + } else { + post_t * p = new post_t(null_post->account, pair.second.negated(), + ITEM_GENERATED | POST_CALCULATED); + p->set_state(null_post->state()); + add_post(p); + } + } +#endif } else if (balance.is_amount()) { null_post->amount = balance.as_amount().negated(); @@ -749,22 +777,25 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) post_t * new_post = new post_t(account, amt); new_post->copy_details(*post); new_post->add_flags(ITEM_GENERATED); - - // jww (2012-02-27): Do account directive assertions get applied - // to postings generated from automated transactions? - xact.add_post(new_post); - new_post->account->add_post(new_post); - - if (new_post->must_balance()) - needs_further_verification = true; + new_post->account = + journal->register_account(account->fullname(), new_post, + journal->master); if (deferred_notes) { foreach (deferred_tag_data_t& data, *deferred_notes) { - if (data.apply_to_post == post) + if (! data.apply_to_post || data.apply_to_post == post) new_post->parse_tags(data.tag_data.c_str(), bound_scope, data.overwrite_existing); } } + + extend_post(*new_post, *journal); + + xact.add_post(new_post); + new_post->account->add_post(new_post); + + if (new_post->must_balance()) + needs_further_verification = true; } } } @@ -173,17 +173,19 @@ public: typedef std::list<deferred_tag_data_t> deferred_notes_list; optional<deferred_notes_list> deferred_notes; + post_t * active_post; - auto_xact_t() : try_quick_match(true) { + auto_xact_t() : try_quick_match(true), active_post(NULL) { TRACE_CTOR(auto_xact_t, ""); } auto_xact_t(const auto_xact_t& other) : xact_base_t(), predicate(other.predicate), - try_quick_match(other.try_quick_match) { + try_quick_match(other.try_quick_match), + active_post(other.active_post) { TRACE_CTOR(auto_xact_t, "copy"); } auto_xact_t(const predicate_t& _predicate) - : predicate(_predicate), try_quick_match(true) + : predicate(_predicate), try_quick_match(true), active_post(NULL) { TRACE_CTOR(auto_xact_t, "const predicate_t&"); } @@ -202,12 +204,12 @@ public: } } - virtual void parse_tags(const char * p, - scope_t&, - bool overwrite_existing = true) { + virtual void parse_tags(const char * p, scope_t&, + bool overwrite_existing = true) { if (! deferred_notes) deferred_notes = deferred_notes_list(); deferred_notes->push_back(deferred_tag_data_t(p, overwrite_existing)); + deferred_notes->back().apply_to_post = active_post; } virtual void extend_xact(xact_base_t& xact, parse_context_t& context); |