summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2012-04-26 16:39:25 -0500
committerJohn Wiegley <johnw@newartisans.com>2012-04-26 16:39:25 -0500
commit64a9b42381c26baf24e58b40f50f0b253e551811 (patch)
tree5447a29dff64c3a8b7be8100a01bcb4a2d73b0bb /src
parent7cc550fc22357e2ded194d3e65287c6b3317f5ae (diff)
parentb4407c10c0071365322b2963747bf42a57fd7304 (diff)
downloadfork-ledger-64a9b42381c26baf24e58b40f50f0b253e551811.tar.gz
fork-ledger-64a9b42381c26baf24e58b40f50f0b253e551811.tar.bz2
fork-ledger-64a9b42381c26baf24e58b40f50f0b253e551811.zip
Merge branch 'release/v3.0.0-20120426'
Diffstat (limited to 'src')
-rw-r--r--src/account.cc66
-rw-r--r--src/account.h44
-rw-r--r--src/accum.cc5
-rw-r--r--src/accum.h27
-rw-r--r--src/amount.cc99
-rw-r--r--src/amount.h37
-rw-r--r--src/annotate.cc163
-rw-r--r--src/annotate.h78
-rw-r--r--src/archive.cc2
-rw-r--r--src/archive.h2
-rw-r--r--src/balance.cc143
-rw-r--r--src/balance.h22
-rw-r--r--src/chain.cc24
-rw-r--r--src/chain.h5
-rw-r--r--src/commodity.cc517
-rw-r--r--src/commodity.h190
-rw-r--r--src/compare.cc4
-rw-r--r--src/compare.h2
-rw-r--r--src/context.h162
-rw-r--r--src/convert.cc101
-rw-r--r--src/convert.h2
-rw-r--r--src/csv.cc129
-rw-r--r--src/csv.h40
-rw-r--r--src/draft.cc31
-rw-r--r--src/draft.h29
-rw-r--r--src/emacs.cc2
-rw-r--r--src/emacs.h2
-rw-r--r--src/error.cc2
-rw-r--r--src/error.h8
-rw-r--r--src/expr.cc114
-rw-r--r--src/expr.h136
-rw-r--r--src/exprbase.h8
-rw-r--r--src/filters.cc333
-rw-r--r--src/filters.h107
-rw-r--r--src/flags.h2
-rw-r--r--src/format.cc289
-rw-r--r--src/format.h11
-rw-r--r--src/generate.cc25
-rw-r--r--src/generate.h4
-rw-r--r--src/global.cc39
-rw-r--r--src/global.h15
-rw-r--r--src/history.cc455
-rw-r--r--src/history.h135
-rw-r--r--src/item.cc89
-rw-r--r--src/item.h53
-rw-r--r--src/iterators.cc115
-rw-r--r--src/iterators.h107
-rw-r--r--src/journal.cc294
-rw-r--r--src/journal.h83
-rw-r--r--src/lookup.cc4
-rw-r--r--src/lookup.h2
-rw-r--r--src/main.cc34
-rw-r--r--src/mask.cc4
-rw-r--r--src/mask.h2
-rw-r--r--src/op.cc589
-rw-r--r--src/op.h68
-rw-r--r--src/option.cc3
-rw-r--r--src/option.h82
-rw-r--r--src/org.cc6
-rw-r--r--src/org.h2
-rw-r--r--src/output.cc10
-rw-r--r--src/output.h2
-rw-r--r--src/parser.cc79
-rw-r--r--src/parser.h4
-rw-r--r--src/pool.cc267
-rw-r--r--src/pool.h62
-rw-r--r--src/post.cc123
-rw-r--r--src/post.h47
-rw-r--r--src/precmd.cc28
-rw-r--r--src/precmd.h2
-rw-r--r--src/predicate.h2
-rw-r--r--src/print.cc112
-rw-r--r--src/print.h2
-rw-r--r--src/pstream.h11
-rw-r--r--src/py_account.cc2
-rw-r--r--src/py_amount.cc17
-rw-r--r--src/py_balance.cc10
-rw-r--r--src/py_commodity.cc80
-rw-r--r--src/py_expr.cc2
-rw-r--r--src/py_format.cc2
-rw-r--r--src/py_item.cc30
-rw-r--r--src/py_journal.cc111
-rw-r--r--src/py_post.cc26
-rw-r--r--src/py_session.cc76
-rw-r--r--src/py_times.cc2
-rw-r--r--src/py_utils.cc2
-rw-r--r--src/py_value.cc19
-rw-r--r--src/py_xact.cc15
-rw-r--r--src/pyfstream.h10
-rw-r--r--src/pyinterp.cc261
-rw-r--r--src/pyinterp.h61
-rw-r--r--src/pyledger.cc2
-rw-r--r--src/pyutils.h2
-rw-r--r--src/query.cc4
-rw-r--r--src/query.h15
-rw-r--r--src/quotes.cc4
-rw-r--r--src/quotes.h4
-rw-r--r--src/report.cc482
-rw-r--r--src/report.h814
-rw-r--r--src/scope.cc20
-rw-r--r--src/scope.h102
-rw-r--r--src/select.cc441
-rw-r--r--src/select.h (renamed from src/predicate.cc)25
-rw-r--r--src/series.h135
-rw-r--r--src/session.cc123
-rw-r--r--src/session.h45
-rw-r--r--src/stats.cc2
-rw-r--r--src/stats.h2
-rw-r--r--src/stream.cc2
-rw-r--r--src/stream.h2
-rw-r--r--src/system.hh.in37
-rw-r--r--src/temps.cc10
-rw-r--r--src/temps.h9
-rw-r--r--src/textual.cc1171
-rw-r--r--src/timelog.cc113
-rw-r--r--src/timelog.h15
-rw-r--r--src/times.cc33
-rw-r--r--src/times.h29
-rw-r--r--src/token.cc7
-rw-r--r--src/token.h2
-rw-r--r--src/unistring.h6
-rw-r--r--src/utils.cc135
-rw-r--r--src/utils.h52
-rw-r--r--src/value.cc166
-rw-r--r--src/value.h48
-rw-r--r--src/views.cc259
-rw-r--r--src/views.h457
-rw-r--r--src/xact.cc197
-rw-r--r--src/xact.h63
-rw-r--r--src/xml.cc3
-rw-r--r--src/xml.h2
131 files changed, 7854 insertions, 3811 deletions
diff --git a/src/account.cc b/src/account.cc
index e201be64..206e2350 100644
--- a/src/account.cc
+++ b/src/account.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -89,9 +89,13 @@ account_t * account_t::find_account(const string& acct_name,
if (has_flags(ACCOUNT_GENERATED))
account->add_flags(ACCOUNT_GENERATED);
- std::pair<accounts_map::iterator, bool> result
- = accounts.insert(accounts_map::value_type(first, account));
+#if defined(DEBUG_ON)
+ std::pair<accounts_map::iterator, bool> result =
+#endif
+ accounts.insert(accounts_map::value_type(first, account));
+#if defined(DEBUG_ON)
assert(result.second);
+#endif
} else {
account = (*i).second;
}
@@ -137,7 +141,10 @@ void account_t::add_post(post_t * post)
bool account_t::remove_post(post_t * post)
{
- assert(! posts.empty());
+ // It's possible that 'post' wasn't yet in this account, but try to
+ // remove it anyway. This can happen if there is an error during
+ // parsing, when the posting knows what it's account is, but
+ // xact_t::finalize has not yet added that posting to the account.
posts.remove(post);
post->account = NULL;
return true;
@@ -243,6 +250,10 @@ namespace {
return long(account.depth);
}
+ value_t get_note(account_t& account) {
+ return account.note ? string_value(*account.note) : NULL_VALUE;
+ }
+
value_t ignore(account_t&) {
return false;
}
@@ -279,6 +290,26 @@ namespace {
return account.self_details().latest_cleared_post;
}
+ value_t get_earliest(account_t& account)
+ {
+ return account.self_details().earliest_post;
+ }
+ value_t get_earliest_checkin(account_t& account)
+ {
+ return (! account.self_details().earliest_checkin.is_not_a_date_time() ?
+ value_t(account.self_details().earliest_checkin) : NULL_VALUE);
+ }
+
+ value_t get_latest(account_t& account)
+ {
+ return account.self_details().latest_post;
+ }
+ value_t get_latest_checkout(account_t& account)
+ {
+ return (! account.self_details().latest_checkout.is_not_a_date_time() ?
+ value_t(account.self_details().latest_checkout) : NULL_VALUE);
+ }
+
template <value_t (*Func)(account_t&)>
value_t get_wrapper(call_scope_t& args) {
return (*Func)(args.context<account_t>());
@@ -351,6 +382,13 @@ expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(get_wrapper<&get_depth_spacer>);
break;
+ case 'e':
+ if (fn_name == "earliest")
+ return WRAP_FUNCTOR(get_wrapper<&get_earliest>);
+ else if (fn_name == "earliest_checkin")
+ return WRAP_FUNCTOR(get_wrapper<&get_earliest_checkin>);
+ break;
+
case 'i':
if (fn_name == "is_account")
return WRAP_FUNCTOR(get_wrapper<&get_true>);
@@ -359,15 +397,21 @@ expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind,
break;
case 'l':
- if (fn_name == "latest_cleared")
- return WRAP_FUNCTOR(get_wrapper<&get_latest_cleared>);
- else if (fn_name[1] == '\0')
+ if (fn_name[1] == '\0')
return WRAP_FUNCTOR(get_wrapper<&get_depth>);
+ else if (fn_name == "latest_cleared")
+ return WRAP_FUNCTOR(get_wrapper<&get_latest_cleared>);
+ else if (fn_name == "latest")
+ return WRAP_FUNCTOR(get_wrapper<&get_latest>);
+ else if (fn_name == "latest_checkout")
+ return WRAP_FUNCTOR(get_wrapper<&get_latest_checkout>);
break;
case 'n':
if (fn_name[1] == '\0')
return WRAP_FUNCTOR(get_wrapper<&get_subcount>);
+ else if (fn_name == "note")
+ return WRAP_FUNCTOR(get_wrapper<&get_note>);
break;
case 'p':
@@ -613,6 +657,14 @@ void account_t::xdata_t::details_t::update(post_t& post,
if (! is_valid(latest_post) || post.date() > latest_post)
latest_post = post.date();
+ if (post.checkin && (earliest_checkin.is_not_a_date_time() ||
+ *post.checkin < earliest_checkin))
+ earliest_checkin = *post.checkin;
+
+ if (post.checkout && (latest_checkout.is_not_a_date_time() ||
+ *post.checkout > latest_checkout))
+ latest_checkout = *post.checkout;
+
if (post.state() == item_t::CLEARED) {
posts_cleared_count++;
diff --git a/src/account.h b/src/account.h
index 7a632b35..c0e3e1f7 100644
--- a/src/account.h
+++ b/src/account.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -67,15 +67,23 @@ public:
unsigned short depth;
accounts_map accounts;
posts_list posts;
+ optional<expr_t> value_expr;
mutable string _fullname;
+#ifdef DOCUMENT_MODEL
+ mutable void * data;
+#endif
account_t(account_t * _parent = NULL,
const string& _name = "",
const optional<string>& _note = none)
: supports_flags<>(), scope_t(), parent(_parent),
name(_name), note(_note),
- depth(static_cast<unsigned short>(parent ? parent->depth + 1 : 0)) {
+ depth(static_cast<unsigned short>(parent ? parent->depth + 1 : 0))
+#ifdef DOCUMENT_MODEL
+ , data(NULL)
+#endif
+ {
TRACE_CTOR(account_t, "account_t *, const string&, const string&");
}
account_t(const account_t& other)
@@ -84,10 +92,14 @@ public:
name(other.name),
note(other.note),
depth(other.depth),
- accounts(other.accounts) {
+ accounts(other.accounts)
+#ifdef DOCUMENT_MODEL
+ , data(NULL)
+#endif
+ {
TRACE_CTOR(account_t, "copy");
}
- ~account_t();
+ virtual ~account_t();
virtual string description() {
return string(_("account ")) + fullname();
@@ -169,6 +181,9 @@ public:
date_t latest_post;
date_t latest_cleared_post;
+ datetime_t earliest_checkin;
+ datetime_t latest_checkout;
+
std::set<path> filenames;
std::set<string> accounts_referenced;
std::set<string> payees_referenced;
@@ -185,7 +200,26 @@ public:
posts_cleared_count(0),
posts_last_7_count(0),
posts_last_30_count(0),
- posts_this_month_count(0) {}
+ posts_this_month_count(0) {
+ TRACE_CTOR(account_t::xdata_t::details_t, "");
+ }
+ // A copy copies nothing
+ details_t(const details_t&)
+ : calculated(false),
+ gathered(false),
+
+ posts_count(0),
+ posts_virtuals_count(0),
+ posts_cleared_count(0),
+ posts_last_7_count(0),
+ posts_last_30_count(0),
+ posts_this_month_count(0)
+ {
+ TRACE_CTOR(account_t::xdata_t::details_t, "copy");
+ }
+ ~details_t() throw() {
+ TRACE_DTOR(account_t::xdata_t::details_t);
+ }
details_t& operator+=(const details_t& other);
diff --git a/src/accum.cc b/src/accum.cc
index 8f3d5185..3add051b 100644
--- a/src/accum.cc
+++ b/src/accum.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -35,6 +35,9 @@
namespace ledger {
+straccstream _accum;
+std::ostringstream _accum_buffer;
+
std::streamsize straccbuf::xsputn(const char * s, std::streamsize num)
{
if (index == 0) {
diff --git a/src/accum.h b/src/accum.h
index 236a7714..628a6b36 100644
--- a/src/accum.h
+++ b/src/accum.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -51,7 +51,12 @@ protected:
std::string::size_type index;
public:
- straccbuf() : index(0) {}
+ straccbuf() : index(0) {
+ TRACE_CTOR(straccbuf, "");
+ }
+ ~straccbuf() throw() {
+ TRACE_DTOR(straccbuf);
+ }
protected:
virtual std::streamsize xsputn(const char * s, std::streamsize num);
@@ -66,8 +71,12 @@ protected:
public:
straccstream() : std::ostream(0) {
+ TRACE_CTOR(straccstream, "");
rdbuf(&buf);
}
+ ~straccstream() throw() {
+ TRACE_DTOR(straccstream);
+ }
void clear() {
std::ostream::clear();
@@ -83,6 +92,20 @@ public:
#define ACCUM(obj) (static_cast<const straccstream&>(obj).str())
+extern straccstream _accum;
+extern std::ostringstream _accum_buffer;
+
+inline string str_helper_func() {
+ string buf = _accum_buffer.str();
+ _accum_buffer.clear();
+ _accum_buffer.str("");
+ return buf;
+}
+
+#define STR(msg) \
+ ((_accum_buffer << ACCUM(_accum << msg)), \
+ _accum.clear(), str_helper_func())
+
} // namespace ledger
#endif // _ACCUM_H
diff --git a/src/amount.cc b/src/amount.cc
index 85afc3d8..5e933215 100644
--- a/src/amount.cc
+++ b/src/amount.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -63,16 +63,16 @@ struct amount_t::bigint_t : public supports_flags<>
#define MP(bigint) ((bigint)->val)
bigint_t() : prec(0), refc(1) {
- TRACE_CTOR(bigint_t, "");
mpq_init(val);
+ TRACE_CTOR(bigint_t, "");
}
bigint_t(const bigint_t& other)
: supports_flags<>(static_cast<uint_least8_t>
(other.flags() & ~BIGINT_BULK_ALLOC)),
prec(other.prec), refc(1) {
- TRACE_CTOR(bigint_t, "copy");
mpq_init(val);
mpq_set(val, other.val);
+ TRACE_CTOR(bigint_t, "copy");
}
~bigint_t() {
TRACE_DTOR(bigint_t);
@@ -120,11 +120,13 @@ namespace {
{
char * buf = NULL;
try {
+#if defined(DEBUG_ON)
IF_DEBUG("amount.convert") {
char * tbuf = mpq_get_str(NULL, 10, quant);
DEBUG("amount.convert", "Rational to convert = " << tbuf);
std::free(tbuf);
}
+#endif
// Convert the rational number to a floating-point, extending the
// floating-point to a large enough size to get a precise answer.
@@ -347,24 +349,24 @@ void amount_t::_release()
amount_t::amount_t(const double val) : commodity_(NULL)
{
- TRACE_CTOR(amount_t, "const double");
quantity = new bigint_t;
mpq_set_d(MP(quantity), val);
quantity->prec = extend_by_digits; // an approximation
+ TRACE_CTOR(amount_t, "const double");
}
amount_t::amount_t(const unsigned long val) : commodity_(NULL)
{
- TRACE_CTOR(amount_t, "const unsigned long");
quantity = new bigint_t;
mpq_set_ui(MP(quantity), val, 1);
+ TRACE_CTOR(amount_t, "const unsigned long");
}
amount_t::amount_t(const long val) : commodity_(NULL)
{
- TRACE_CTOR(amount_t, "const long");
quantity = new bigint_t;
mpq_set_si(MP(quantity), val, 1);
+ TRACE_CTOR(amount_t, "const long");
}
@@ -393,11 +395,11 @@ int amount_t::compare(const amount_t& amt) const
throw_(amount_error, _("Cannot compare two uninitialized amounts"));
}
- if (has_commodity() && amt.has_commodity() &&
- commodity() != amt.commodity())
+ if (has_commodity() && amt.has_commodity() && commodity() != amt.commodity()) {
throw_(amount_error,
- _("Cannot compare amounts with different commodities: %1 and %2")
- << commodity().symbol() << amt.commodity().symbol());
+ _("Cannot compare amounts with different commodities: '%1' and '%2'")
+ << commodity() << amt.commodity());
+ }
return mpq_cmp(MP(quantity), MP(amt.quantity));
}
@@ -428,12 +430,11 @@ amount_t& amount_t::operator+=(const amount_t& amt)
throw_(amount_error, _("Cannot add two uninitialized amounts"));
}
- if (has_commodity() && amt.has_commodity() &&
- commodity() != amt.commodity())
+ if (has_commodity() && amt.has_commodity() && commodity() != amt.commodity()) {
throw_(amount_error,
- _("Adding amounts with different commodities: %1 != %2")
- << (has_commodity() ? commodity().symbol() : _("NONE"))
- << (amt.has_commodity() ? amt.commodity().symbol() : _("NONE")));
+ _("Adding amounts with different commodities: '%1' != '%2'")
+ << commodity() << amt.commodity());
+ }
_dup();
@@ -459,12 +460,11 @@ amount_t& amount_t::operator-=(const amount_t& amt)
throw_(amount_error, _("Cannot subtract two uninitialized amounts"));
}
- if (has_commodity() && amt.has_commodity() &&
- commodity() != amt.commodity())
+ if (has_commodity() && amt.has_commodity() && commodity() != amt.commodity()) {
throw_(amount_error,
- _("Subtracting amounts with different commodities: %1 != %2")
- << (has_commodity() ? commodity().symbol() : _("NONE"))
- << (amt.has_commodity() ? amt.commodity().symbol() : _("NONE")));
+ _("Subtracting amounts with different commodities: '%1' != '%2'")
+ << commodity() << amt.commodity());
+ }
_dup();
@@ -605,16 +605,13 @@ void amount_t::in_place_negate()
}
}
-amount_t amount_t::inverted() const
+void amount_t::in_place_invert()
{
if (! quantity)
throw_(amount_error, _("Cannot invert an uninitialized amount"));
- amount_t t(*this);
- t._dup();
- mpq_inv(MP(t.quantity), MP(t.quantity));
-
- return t;
+ _dup();
+ mpq_inv(MP(quantity), MP(quantity));
}
void amount_t::in_place_round()
@@ -729,24 +726,24 @@ void amount_t::in_place_unreduce()
}
optional<amount_t>
-amount_t::value(const optional<datetime_t>& moment,
- const optional<commodity_t&>& in_terms_of) const
+amount_t::value(const datetime_t& moment,
+ const commodity_t * in_terms_of) const
{
if (quantity) {
#if defined(DEBUG_ON)
- DEBUG("commodity.prices.find",
+ DEBUG("commodity.price.find",
"amount_t::value of " << commodity().symbol());
- if (moment)
- DEBUG("commodity.prices.find",
- "amount_t::value: moment = " << *moment);
+ if (! moment.is_not_a_date_time())
+ DEBUG("commodity.price.find",
+ "amount_t::value: moment = " << moment);
if (in_terms_of)
- DEBUG("commodity.prices.find",
+ DEBUG("commodity.price.find",
"amount_t::value: in_terms_of = " << in_terms_of->symbol());
#endif
if (has_commodity() &&
(in_terms_of || ! commodity().has_flags(COMMODITY_PRIMARY))) {
optional<price_point_t> point;
- optional<commodity_t&> comm(in_terms_of);
+ const commodity_t * comm(in_terms_of);
if (has_annotation() && annotation().price) {
if (annotation().has_flags(ANNOTATION_PRICE_FIXATED)) {
@@ -756,14 +753,14 @@ amount_t::value(const optional<datetime_t>& moment,
"amount_t::value: fixated price = " << point->price);
}
else if (! comm) {
- comm = annotation().price->commodity();
+ comm = annotation().price->commodity_ptr();
}
}
- if (! point) {
- if (comm && commodity().referent() == comm->referent())
- return *this;
+ if (comm && commodity().referent() == comm->referent())
+ return with_commodity(comm->referent());
+ if (! point) {
point = commodity().find_price(comm, moment);
// Whether a price was found or not, check whether we should attempt
@@ -788,7 +785,7 @@ amount_t::value(const optional<datetime_t>& moment,
return none;
}
-amount_t amount_t::price() const
+optional<amount_t> amount_t::price() const
{
if (has_annotation() && annotation().price) {
amount_t tmp(*annotation().price);
@@ -796,7 +793,7 @@ amount_t amount_t::price() const
DEBUG("amount.price", "Returning price of " << *this << " = " << tmp);
return tmp;
}
- return *this;
+ return none;
}
@@ -868,10 +865,10 @@ bool amount_t::fits_in_long() const
return mpfr_fits_slong_p(tempf, GMP_RNDN);
}
-commodity_t& amount_t::commodity() const
+commodity_t * amount_t::commodity_ptr() const
{
- return (has_commodity() ?
- *commodity_ : *commodity_pool_t::current_pool->null_commodity);
+ return (commodity_ ?
+ commodity_ : commodity_pool_t::current_pool->null_commodity);
}
bool amount_t::has_commodity() const
@@ -1030,12 +1027,12 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
}
// Allocate memory for the amount's quantity value. We have to
- // monitor the allocation in an auto_ptr because this function gets
+ // monitor the allocation in a unique_ptr because this function gets
// called sometimes from amount_t's constructor; and if there is an
// exeception thrown by any of the function calls after this point,
// the destructor will never be called and the memory never freed.
- std::auto_ptr<bigint_t> new_quantity;
+ unique_ptr<bigint_t> new_quantity;
if (quantity) {
if (quantity->refc > 1)
@@ -1061,10 +1058,6 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
if (! commodity_)
commodity_ = commodity_pool_t::current_pool->create(symbol);
assert(commodity_);
-
- if (details)
- commodity_ =
- commodity_pool_t::current_pool->find_or_create(*commodity_, details);
}
// Quickly scan through and verify the correctness of the amount's use of
@@ -1200,6 +1193,14 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
if (! flags.has_flags(PARSE_NO_REDUCE))
in_place_reduce(); // will not throw an exception
+ if (commodity_ && details) {
+ if (details.has_flags(ANNOTATION_PRICE_NOT_PER_UNIT)) {
+ assert(details.price);
+ *details.price /= this->abs();
+ }
+ set_commodity(*commodity_pool_t::current_pool->find_or_create(*commodity_, details));
+ }
+
VERIFY(valid());
return true;
diff --git a/src/amount.h b/src/amount.h
index f7e877a7..cd77a79a 100644
--- a/src/amount.h
+++ b/src/amount.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -155,17 +155,17 @@ public:
commodity_t::null_commodity. The number may be of infinite
precision. */
explicit amount_t(const string& val) : quantity(NULL) {
- TRACE_CTOR(amount_t, "const string&");
parse(val);
+ TRACE_CTOR(amount_t, "const string&");
}
/** Parse a pointer to a C string as an (optionally commoditized)
amount. If no commodity is present, the resulting commodity is \c
commodity_t::null_commodity. The number may be of infinite
precision. */
explicit amount_t(const char * val) : quantity(NULL) {
- TRACE_CTOR(amount_t, "const char *");
assert(val);
parse(val);
+ TRACE_CTOR(amount_t, "const char *");
}
/*@}*/
@@ -195,21 +195,21 @@ public:
same memory used by the original via reference counting. The \c
amount_t::bigint_t class in amount.cc maintains the reference. */
amount_t(const amount_t& amt) : quantity(NULL) {
- TRACE_CTOR(amount_t, "copy");
if (amt.quantity)
_copy(amt);
else
commodity_ = NULL;
+ TRACE_CTOR(amount_t, "copy");
}
/** Copy an amount object, applying the given commodity annotation
details afterward. This is equivalent to doing a normal copy
(@see amount_t(const amount_t&)) and then calling
amount_t::annotate(). */
amount_t(const amount_t& amt, const annotation_t& details) : quantity(NULL) {
- TRACE_CTOR(amount_t, "const amount_t&, const annotation_t&");
assert(amt.quantity);
_copy(amt);
annotate(details);
+ TRACE_CTOR(amount_t, "const amount_t&, const annotation_t&");
}
/** Assign an amount object. This is like copying if the amount was
null beforehand, otherwise the previous value's reference is must
@@ -327,7 +327,12 @@ public:
return *this;
}
- amount_t inverted() const;
+ amount_t inverted() const {
+ amount_t temp(*this);
+ temp.in_place_invert();
+ return temp;
+ }
+ void in_place_invert();
/** Yields an amount whose display precision when output is truncated
to the display precision of its commodity. This is normally the
@@ -399,10 +404,10 @@ public:
$100.00.
*/
optional<amount_t>
- value(const optional<datetime_t>& moment = none,
- const optional<commodity_t&>& in_terms_of = none) const;
+ value(const datetime_t& moment = datetime_t(),
+ const commodity_t * in_terms_of = NULL) const;
- amount_t price() const;
+ optional<amount_t> price() const;
/*@}*/
@@ -528,7 +533,10 @@ public:
number() returns a commodity-less version of an amount. This is
useful for accessing just the numeric portion of an amount.
*/
- commodity_t& commodity() const;
+ commodity_t * commodity_ptr() const;
+ commodity_t& commodity() const {
+ return *commodity_ptr();
+ }
bool has_commodity() const;
void set_commodity(commodity_t& comm) {
@@ -536,6 +544,15 @@ public:
*this = 0L;
commodity_ = &comm;
}
+ amount_t with_commodity(const commodity_t& comm) const {
+ if (commodity_ == &comm) {
+ return *this;
+ } else {
+ amount_t tmp(*this);
+ tmp.set_commodity(const_cast<commodity_t&>(comm));
+ return tmp;
+ }
+ }
void clear_commodity() {
commodity_ = NULL;
}
diff --git a/src/annotate.cc b/src/annotate.cc
index 8ba46f4f..41e7a752 100644
--- a/src/annotate.cc
+++ b/src/annotate.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -33,11 +33,51 @@
#include "amount.h"
#include "commodity.h"
+#include "expr.h"
#include "annotate.h"
#include "pool.h"
namespace ledger {
+bool annotation_t::operator<(const annotation_t& rhs) const
+{
+ if (! price && rhs.price) return true;
+ if (price && ! rhs.price) return false;
+ if (! date && rhs.date) return true;
+ if (date && ! rhs.date) return false;
+ if (! tag && rhs.tag) return true;
+ if (tag && ! rhs.tag) return false;
+
+ if (! value_expr && rhs.value_expr) return true;
+ if (value_expr && ! rhs.value_expr) return false;
+
+ if (price) {
+ if (price->commodity().symbol() < rhs.price->commodity().symbol())
+ return true;
+ if (price->commodity().symbol() > rhs.price->commodity().symbol())
+ return false;
+
+ if (*price < *rhs.price) return true;
+ if (*price > *rhs.price) return false;
+ }
+ if (date) {
+ if (*date < *rhs.date) return true;
+ if (*date > *rhs.date) return false;
+ }
+ if (tag) {
+ if (*tag < *rhs.tag) return true;
+ if (*tag > *rhs.tag) return false;
+ }
+ if (value_expr) {
+ DEBUG("annotate.less", "Comparing (" << value_expr->text()
+ << ") < (" << rhs.value_expr->text());
+ if (value_expr->text() < rhs.value_expr->text()) return true;
+ //if (value_expr->text() > rhs.value_expr->text()) return false;
+ }
+
+ return false;
+}
+
void annotation_t::parse(std::istream& in)
{
do {
@@ -52,6 +92,12 @@ void annotation_t::parse(std::istream& in)
throw_(amount_error, _("Commodity specifies more than one price"));
in.get(c);
+ c = static_cast<char>(in.peek());
+ if (c == '{') {
+ in.get(c);
+ add_flags(ANNOTATION_PRICE_NOT_PER_UNIT);
+ }
+
c = peek_next_nonws(in);
if (c == '=') {
in.get(c);
@@ -59,10 +105,18 @@ void annotation_t::parse(std::istream& in)
}
READ_INTO(in, buf, 255, c, c != '}');
- if (c == '}')
+ if (c == '}') {
in.get(c);
- else
- throw_(amount_error, _("Commodity price lacks closing brace"));
+ if (has_flags(ANNOTATION_PRICE_NOT_PER_UNIT)) {
+ c = static_cast<char>(in.peek());
+ if (c != '}')
+ throw_(amount_error, _("Commodity lot price lacks double closing brace"));
+ else
+ in.get(c);
+ }
+ } else {
+ throw_(amount_error, _("Commodity lot price lacks closing brace"));
+ }
amount_t temp;
temp.parse(buf, PARSE_NO_MIGRATE);
@@ -84,17 +138,46 @@ void annotation_t::parse(std::istream& in)
date = parse_date(buf);
}
else if (c == '(') {
- if (tag)
- throw_(amount_error, _("Commodity specifies more than one tag"));
-
in.get(c);
- READ_INTO(in, buf, 255, c, c != ')');
- if (c == ')')
- in.get(c);
- else
- throw_(amount_error, _("Commodity tag lacks closing parenthesis"));
+ c = static_cast<char>(in.peek());
+ if (c == '@') {
+ in.clear();
+ in.seekg(pos, std::ios::beg);
+ break;
+ }
+ else if (c == '(') {
+ if (value_expr)
+ throw_(amount_error,
+ _("Commodity specifies more than one valuation expresion"));
- tag = buf;
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ')');
+ if (c == ')') {
+ in.get(c);
+ c = static_cast<char>(in.peek());
+ if (c == ')')
+ in.get(c);
+ else
+ throw_(amount_error,
+ _("Commodity valuation expression lacks closing parentheses"));
+ } else {
+ throw_(amount_error,
+ _("Commodity valuation expression lacks closing parentheses"));
+ }
+
+ value_expr = expr_t(buf);
+ } else {
+ if (tag)
+ throw_(amount_error, _("Commodity specifies more than one tag"));
+
+ READ_INTO(in, buf, 255, c, c != ')');
+ if (c == ')')
+ in.get(c);
+ else
+ throw_(amount_error, _("Commodity tag lacks closing parenthesis"));
+
+ tag = buf;
+ }
}
else {
in.clear();
@@ -128,6 +211,9 @@ void annotation_t::print(std::ostream& out, bool keep_base,
if (tag &&
(! no_computed_annotations || ! has_flags(ANNOTATION_TAG_CALCULATED)))
out << " (" << *tag << ')';
+
+ if (value_expr && ! has_flags(ANNOTATION_VALUE_EXPR_CALCULATED))
+ out << " ((" << *value_expr << "))";
}
bool keep_details_t::keep_all(const commodity_t& comm) const
@@ -157,6 +243,54 @@ bool annotated_commodity_t::operator==(const commodity_t& comm) const
return true;
}
+optional<price_point_t>
+annotated_commodity_t::find_price(const commodity_t * commodity,
+ const datetime_t& moment,
+ const datetime_t& oldest) const
+{
+ DEBUG("commodity.price.find",
+ "annotated_commodity_t::find_price(" << symbol() << ")");
+
+ datetime_t when;
+ if (! moment.is_not_a_date_time())
+ when = moment;
+ else if (epoch)
+ when = *epoch;
+ else
+ when = CURRENT_TIME();
+
+ DEBUG("commodity.price.find", "reference time: " << when);
+
+ const commodity_t * target = NULL;
+ if (commodity)
+ target = commodity;
+
+ if (details.price) {
+ DEBUG("commodity.price.find", "price annotation: " << *details.price);
+
+ if (details.has_flags(ANNOTATION_PRICE_FIXATED)) {
+ DEBUG("commodity.price.find",
+ "amount_t::value: fixated price = " << *details.price);
+ return price_point_t(when, *details.price);
+ }
+ else if (! target) {
+ DEBUG("commodity.price.find", "setting target commodity from price");
+ target = details.price->commodity_ptr();
+ }
+ }
+
+#if defined(DEBUG_ON)
+ if (target)
+ DEBUG("commodity.price.find", "target commodity: " << target->symbol());
+#endif
+
+ if (details.value_expr)
+ return find_price_from_expr(const_cast<expr_t&>(*details.value_expr),
+ commodity, when);
+
+ return commodity_t::find_price(target, when, oldest);
+}
+
commodity_t&
annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
{
@@ -192,8 +326,7 @@ annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
if ((keep_price && details.price) ||
(keep_date && details.date) ||
- (keep_tag && details.tag))
- {
+ (keep_tag && details.tag)) {
new_comm = pool().find_or_create
(referent(), annotation_t(keep_price ? details.price : none,
keep_date ? details.date : none,
diff --git a/src/annotate.h b/src/annotate.h
index b590ca45..37ee0685 100644
--- a/src/annotate.h
+++ b/src/annotate.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -46,29 +46,39 @@
#ifndef _ANNOTATE_H
#define _ANNOTATE_H
+#include "expr.h"
+
namespace ledger {
struct annotation_t : public supports_flags<>,
public equality_comparable<annotation_t>
{
-#define ANNOTATION_PRICE_CALCULATED 0x01
-#define ANNOTATION_PRICE_FIXATED 0x02
-#define ANNOTATION_DATE_CALCULATED 0x04
-#define ANNOTATION_TAG_CALCULATED 0x08
+#define ANNOTATION_PRICE_CALCULATED 0x01
+#define ANNOTATION_PRICE_FIXATED 0x02
+#define ANNOTATION_PRICE_NOT_PER_UNIT 0x04
+#define ANNOTATION_DATE_CALCULATED 0x08
+#define ANNOTATION_TAG_CALCULATED 0x10
+#define ANNOTATION_VALUE_EXPR_CALCULATED 0x20
optional<amount_t> price;
optional<date_t> date;
optional<string> tag;
-
- explicit annotation_t(const optional<amount_t>& _price = none,
- const optional<date_t>& _date = none,
- const optional<string>& _tag = none)
- : supports_flags<>(), price(_price), date(_date), tag(_tag) {
- TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string");
+ optional<expr_t> value_expr;
+
+ explicit annotation_t(const optional<amount_t>& _price = none,
+ const optional<date_t>& _date = none,
+ const optional<string>& _tag = none,
+ const optional<expr_t>& _value_expr = none)
+ : supports_flags<>(), price(_price), date(_date), tag(_tag),
+ value_expr(_value_expr) {
+ TRACE_CTOR(annotation_t,
+ "optional<amount_t> + date_t + string + expr_t");
}
annotation_t(const annotation_t& other)
: supports_flags<>(other.flags()),
- price(other.price), date(other.date), tag(other.tag) {
+ price(other.price), date(other.date), tag(other.tag),
+ value_expr(other.value_expr)
+ {
TRACE_CTOR(annotation_t, "copy");
}
~annotation_t() {
@@ -76,17 +86,20 @@ struct annotation_t : public supports_flags<>,
}
operator bool() const {
- return price || date || tag;
+ return price || date || tag || value_expr;
}
+ bool operator<(const annotation_t& rhs) const;
bool operator==(const annotation_t& rhs) const {
return (price == rhs.price &&
- date == rhs.date &&
- tag == rhs.tag);
+ date == rhs.date &&
+ tag == rhs.tag &&
+ (value_expr && rhs.value_expr ?
+ value_expr->text() == rhs.value_expr->text() :
+ value_expr == rhs.value_expr));
}
void parse(std::istream& in);
-
void print(std::ostream& out, bool keep_base = false,
bool no_computed_annotations = false) const;
@@ -132,6 +145,12 @@ inline void to_xml(std::ostream& out, const annotation_t& details)
push_xml y(out, "tag");
out << y.guard(*details.tag);
}
+
+ if (details.value_expr)
+ {
+ push_xml y(out, "value-expr");
+ out << y.guard(details.value_expr->text());
+ }
}
struct keep_details_t
@@ -207,8 +226,9 @@ protected:
explicit annotated_commodity_t(commodity_t * _ptr,
const annotation_t& _details)
: commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) {
- TRACE_CTOR(annotated_commodity_t, "commodity_t *, annotation_t");
annotated = true;
+ qualified_symbol = _ptr->qualified_symbol;
+ TRACE_CTOR(annotated_commodity_t, "commodity_t *, annotation_t");
}
public:
@@ -230,7 +250,31 @@ public:
return *ptr;
}
+ virtual optional<expr_t> value_expr() const {
+ if (details.value_expr)
+ return details.value_expr;
+ return commodity_t::value_expr();
+ }
+
+ optional<price_point_t>
+ virtual find_price(const commodity_t * commodity = NULL,
+ const datetime_t& moment = datetime_t(),
+ const datetime_t& oldest = datetime_t()) const;
+
virtual commodity_t& strip_annotations(const keep_details_t& what_to_keep);
+
+ virtual void print(std::ostream& out, bool elide_quotes = false,
+ bool print_annotations = false) const {
+ if (print_annotations) {
+ std::ostringstream buf;
+ commodity_t::print(buf, elide_quotes);
+ write_annotations(buf);
+ out << buf.str();
+ } else {
+ commodity_t::print(out, elide_quotes);
+ }
+ }
+
virtual void write_annotations(std::ostream& out,
bool no_computed_annotations = false) const;
diff --git a/src/archive.cc b/src/archive.cc
index 28760512..72ec0419 100644
--- a/src/archive.cc
+++ b/src/archive.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/archive.h b/src/archive.h
index 1ebf3496..4ce5e0e7 100644
--- a/src/archive.h
+++ b/src/archive.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/balance.cc b/src/balance.cc
index 7ce9d994..ded3d38a 100644
--- a/src/balance.cc
+++ b/src/balance.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -41,23 +41,23 @@ namespace ledger {
balance_t::balance_t(const double val)
{
- TRACE_CTOR(balance_t, "const double");
amounts.insert
(amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val));
+ TRACE_CTOR(balance_t, "const double");
}
balance_t::balance_t(const unsigned long val)
{
- TRACE_CTOR(balance_t, "const unsigned long");
amounts.insert
(amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val));
+ TRACE_CTOR(balance_t, "const unsigned long");
}
balance_t::balance_t(const long val)
{
- TRACE_CTOR(balance_t, "const long");
amounts.insert
(amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val));
+ TRACE_CTOR(balance_t, "const long");
}
balance_t& balance_t::operator+=(const balance_t& bal)
@@ -185,8 +185,8 @@ balance_t& balance_t::operator/=(const amount_t& amt)
}
optional<balance_t>
-balance_t::value(const optional<datetime_t>& moment,
- const optional<commodity_t&>& in_terms_of) const
+balance_t::value(const datetime_t& moment,
+ const commodity_t * in_terms_of) const
{
balance_t temp;
bool resolved = false;
@@ -202,16 +202,6 @@ balance_t::value(const optional<datetime_t>& moment,
return resolved ? temp : optional<balance_t>();
}
-balance_t balance_t::price() const
-{
- balance_t temp;
-
- foreach (const amounts_map::value_type& pair, amounts)
- temp += pair.second.price();
-
- return temp;
-}
-
optional<amount_t>
balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const
{
@@ -250,51 +240,100 @@ balance_t::strip_annotations(const keep_details_t& what_to_keep) const
return temp;
}
-void balance_t::print(std::ostream& out,
- const int first_width,
- const int latter_width,
- const uint_least8_t flags) const
+void balance_t::map_sorted_amounts(function<void(const amount_t&)> fn) const
{
- bool first = true;
- int lwidth = latter_width;
+ if (! amounts.empty()) {
+ if (amounts.size() == 1) {
+ const amount_t& amount((*amounts.begin()).second);
+ if (amount)
+ fn(amount);
+ }
+ else {
+ typedef std::vector<const amount_t *> amounts_array;
+ amounts_array sorted;
- if (lwidth == -1)
- lwidth = first_width;
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (pair.second)
+ sorted.push_back(&pair.second);
- typedef std::vector<const amount_t *> amounts_array;
- amounts_array sorted;
+ std::stable_sort(sorted.begin(), sorted.end(),
+ commodity_t::compare_by_commodity());
- foreach (const amounts_map::value_type& pair, amounts)
- if (pair.second)
- sorted.push_back(&pair.second);
+ foreach (const amount_t * amount, sorted)
+ fn(*amount);
+ }
+ }
+}
- std::stable_sort(sorted.begin(), sorted.end(),
- commodity_t::compare_by_commodity());
+namespace {
+ struct print_amount_from_balance
+ {
+ std::ostream& out;
+ bool& first;
+ int fwidth;
+ int lwidth;
+ uint_least8_t flags;
+
+ explicit print_amount_from_balance(std::ostream& _out,
+ bool& _first,
+ int _fwidth, int _lwidth,
+ uint_least8_t _flags)
+ : out(_out), first(_first), fwidth(_fwidth), lwidth(_lwidth),
+ flags(_flags) {
+ TRACE_CTOR(print_amount_from_balance,
+ "ostream&, int, int, uint_least8_t");
+ }
+ print_amount_from_balance(const print_amount_from_balance& other)
+ : out(other.out), first(other.first), fwidth(other.fwidth),
+ lwidth(other.lwidth), flags(other.flags) {
+ TRACE_CTOR(print_amount_from_balance, "copy");
+ }
+ ~print_amount_from_balance() throw() {
+ TRACE_DTOR(print_amount_from_balance);
+ }
- foreach (const amount_t * amount, sorted) {
- int width;
- if (! first) {
- out << std::endl;
- width = lwidth;
- } else {
- first = false;
- width = first_width;
+ void operator()(const amount_t& amount) {
+ int width;
+ if (! first) {
+ out << std::endl;
+ width = lwidth;
+ } else {
+ first = false;
+ width = fwidth;
+ }
+
+ std::ostringstream buf;
+ amount.print(buf, flags);
+
+ justify(out, buf.str(), width,
+ flags & AMOUNT_PRINT_RIGHT_JUSTIFY,
+ flags & AMOUNT_PRINT_COLORIZE && amount.sign() < 0);
}
- std::ostringstream buf;
- amount->print(buf, flags);
- justify(out, buf.str(), width, flags & AMOUNT_PRINT_RIGHT_JUSTIFY,
- flags & AMOUNT_PRINT_COLORIZE && amount->sign() < 0);
- }
+ void close() {
+ out.width(fwidth);
+ if (flags & AMOUNT_PRINT_RIGHT_JUSTIFY)
+ out << std::right;
+ else
+ out << std::left;
+ out << 0;
+ }
+ };
+}
- if (first) {
- out.width(first_width);
- if (flags & AMOUNT_PRINT_RIGHT_JUSTIFY)
- out << std::right;
- else
- out << std::left;
- out << 0;
- }
+void balance_t::print(std::ostream& out,
+ const int first_width,
+ const int latter_width,
+ const uint_least8_t flags) const
+{
+ bool first = true;
+ print_amount_from_balance
+ amount_printer(out, first, first_width,
+ latter_width == 1 ? first_width : latter_width, flags);
+ map_sorted_amounts(amount_printer);
+
+ if (first)
+ amount_printer.close();
}
void to_xml(std::ostream& out, const balance_t& bal)
diff --git a/src/balance.h b/src/balance.h
index ac22f3e7..704b4072 100644
--- a/src/balance.h
+++ b/src/balance.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -108,26 +108,26 @@ public:
TRACE_CTOR(balance_t, "");
}
balance_t(const amount_t& amt) {
- TRACE_CTOR(balance_t, "const amount_t&");
if (amt.is_null())
throw_(balance_error,
_("Cannot initialize a balance from an uninitialized amount"));
if (! amt.is_realzero())
amounts.insert(amounts_map::value_type(&amt.commodity(), amt));
+ TRACE_CTOR(balance_t, "const amount_t&");
}
balance_t(const double val);
balance_t(const unsigned long val);
balance_t(const long val);
explicit balance_t(const string& val) {
- TRACE_CTOR(balance_t, "const string&");
amount_t temp(val);
amounts.insert(amounts_map::value_type(&temp.commodity(), temp));
+ TRACE_CTOR(balance_t, "const string&");
}
explicit balance_t(const char * val) {
- TRACE_CTOR(balance_t, "const char *");
amount_t temp(val);
amounts.insert(amounts_map::value_type(&temp.commodity(), temp));
+ TRACE_CTOR(balance_t, "const char *");
}
/**
@@ -384,10 +384,8 @@ public:
}
optional<balance_t>
- value(const optional<datetime_t>& moment = none,
- const optional<commodity_t&>& in_terms_of = none) const;
-
- balance_t price() const;
+ value(const datetime_t& moment = datetime_t(),
+ const commodity_t * in_terms_of = NULL) const;
/**
* Truth tests. An balance may be truth test in two ways:
@@ -509,6 +507,14 @@ public:
balance_t strip_annotations(const keep_details_t& what_to_keep) const;
/**
+ * Iteration primitives. `map_sorted_amounts' allows one to visit
+ * each amount in balance in the proper order for displaying to the
+ * user. Mostly used by `print' and other routinse where the sort
+ * order of the amounts' commodities is significant.
+ */
+ void map_sorted_amounts(function<void(const amount_t&)> fn) const;
+
+ /**
* Printing methods. A balance may be output to a stream using the
* `print' method. There is also a global operator<< defined which
* simply calls print for a balance on the given stream. There is
diff --git a/src/chain.cc b/src/chain.cc
index 450e3758..52d52f14 100644
--- a/src/chain.cc
+++ b/src/chain.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -88,10 +88,9 @@ post_handler_ptr chain_pre_post_handlers(post_handler_ptr base_handler,
predicate_t(report.HANDLER(forecast_while_).str(),
report.what_to_keep()),
report,
- report.HANDLED(forecast_years_) ?
- static_cast<std::size_t>
- (report.HANDLER(forecast_years_).value.to_long()) :
- 5UL);
+ (report.HANDLED(forecast_years_) ?
+ lexical_cast<std::size_t>
+ (report.HANDLER(forecast_years_).value) : 5UL));
forecast_handler->add_period_xacts(report.session.journal->period_xacts);
handler.reset(forecast_handler);
@@ -115,10 +114,13 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler,
predicate_t only_predicate;
display_filter_posts * display_filter = NULL;
- assert(report.HANDLED(amount_));
expr_t& expr(report.HANDLER(amount_).expr);
expr.set_context(&report);
+ report.HANDLER(total_).expr.set_context(&report);
+ report.HANDLER(display_amount_).expr.set_context(&report);
+ report.HANDLER(display_total_).expr.set_context(&report);
+
if (! for_accounts_report) {
// Make sure only forecast postings which match are allowed through
if (report.HANDLED(forecast_while_)) {
@@ -134,9 +136,9 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler,
handler.reset
(new truncate_xacts(handler,
report.HANDLED(head_) ?
- report.HANDLER(head_).value.to_int() : 0,
+ lexical_cast<int>(report.HANDLER(head_).value) : 0,
report.HANDLED(tail_) ?
- report.HANDLER(tail_).value.to_int() : 0));
+ lexical_cast<int>(report.HANDLER(tail_).value) : 0));
// display_filter_posts adds virtual posts to the list to account
// for changes in value of commodities, which otherwise would affect
@@ -205,7 +207,7 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler,
// day_of_week_posts is like period_posts, except that it reports
// all the posts that fall on each subsequent day of the week.
if (report.HANDLED(equity))
- handler.reset(new posts_as_equity(handler, expr));
+ handler.reset(new posts_as_equity(handler, report, expr));
else if (report.HANDLED(subtotal))
handler.reset(new subtotal_posts(handler, expr));
}
@@ -217,13 +219,11 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler,
// interval_posts groups posts together based on a time period, such as
// weekly or monthly.
- if (report.HANDLED(period_)) {
+ if (report.HANDLED(period_))
handler.reset(new interval_posts(handler, expr,
report.HANDLER(period_).str(),
report.HANDLED(exact),
report.HANDLED(empty)));
- handler.reset(new sort_posts(handler, "date"));
- }
if (report.HANDLED(date_))
handler.reset(new transfer_details(handler, transfer_details::SET_DATE,
diff --git a/src/chain.h b/src/chain.h
index 7bd76712..15ae12ba 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -50,8 +50,9 @@ class post_t;
class account_t;
template <typename T>
-struct item_handler : public noncopyable
+class item_handler : public noncopyable
{
+protected:
shared_ptr<item_handler> handler;
public:
diff --git a/src/commodity.cc b/src/commodity.cc
index 5fd54d11..a72d85c8 100644
--- a/src/commodity.cc
+++ b/src/commodity.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -35,430 +35,162 @@
#include "commodity.h"
#include "annotate.h"
#include "pool.h"
+#include "scope.h"
namespace ledger {
bool commodity_t::decimal_comma_by_default = false;
-void commodity_t::history_t::add_price(commodity_t& source,
- const datetime_t& date,
- const amount_t& price,
- const bool reflexive)
+void commodity_t::add_price(const datetime_t& date, const amount_t& price,
+ const bool reflexive)
{
- DEBUG("commodity.prices.add", "add_price to " << source
- << (reflexive ? " (secondary)" : " (primary)")
- << " : " << date << ", " << price);
-
- history_map::iterator i = prices.find(date);
- if (i != prices.end()) {
- (*i).second = price;
- } else {
- std::pair<history_map::iterator, bool> result
- = prices.insert(history_map::value_type(date, price));
- assert(result.second);
- }
-
if (reflexive) {
- amount_t inverse = price.inverted();
- inverse.set_commodity(const_cast<commodity_t&>(source));
- price.commodity().add_price(date, inverse, false);
+ DEBUG("history.find", "Marking "
+ << price.commodity().symbol() << " as a primary commodity");
+ price.commodity().add_flags(COMMODITY_PRIMARY);
} else {
- DEBUG("commodity.prices.add",
- "marking commodity " << source.symbol() << " as primary");
- source.add_flags(COMMODITY_PRIMARY);
+ DEBUG("history.find", "Marking " << symbol() << " as a primary commodity");
+ add_flags(COMMODITY_PRIMARY);
}
-}
-bool commodity_t::history_t::remove_price(const datetime_t& date)
-{
- DEBUG("commodity.prices.add", "remove_price: " << date);
+ DEBUG("history.find", "Adding price: " << symbol()
+ << " for " << price << " on " << date);
- history_map::size_type n = prices.erase(date);
- if (n > 0)
- return true;
- return false;
+ pool().commodity_price_history.add_price(referent(), date, price);
+
+ base->price_map.clear(); // a price was added, invalid the map
}
-void commodity_t::varied_history_t::
- add_price(commodity_t& source,
- const datetime_t& date,
- const amount_t& price,
- const bool reflexive)
+void commodity_t::remove_price(const datetime_t& date, commodity_t& commodity)
{
- optional<history_t&> hist = history(price.commodity());
- if (! hist) {
- std::pair<history_by_commodity_map::iterator, bool> result
- = histories.insert(history_by_commodity_map::value_type
- (&price.commodity(), history_t()));
- assert(result.second);
-
- hist = (*result.first).second;
- }
- assert(hist);
+ pool().commodity_price_history.remove_price(referent(), commodity, date);
- hist->add_price(source, date, price, reflexive);
+ DEBUG("history.find", "Removing price: " << symbol() << " on " << date);
+
+ base->price_map.clear(); // a price was added, invalid the map
}
-bool commodity_t::varied_history_t::remove_price(const datetime_t& date,
- commodity_t& comm)
+void commodity_t::map_prices(function<void(datetime_t, const amount_t&)> fn,
+ const datetime_t& moment,
+ const datetime_t& _oldest,
+ bool bidirectionally)
{
- DEBUG("commodity.prices.add", "varied_remove_price: " << date << ", " << comm);
+ datetime_t when;
+ if (! moment.is_not_a_date_time())
+ when = moment;
+ else if (epoch)
+ when = *epoch;
+ else
+ when = CURRENT_TIME();
- if (optional<history_t&> hist = history(comm))
- return hist->remove_price(date);
- return false;
+ pool().commodity_price_history.map_prices(fn, referent(), when, _oldest,
+ bidirectionally);
}
optional<price_point_t>
-commodity_t::history_t::find_price(const optional<datetime_t>& moment,
- const optional<datetime_t>& oldest
-#if defined(DEBUG_ON)
- , const int indent
-#endif
- ) const
+commodity_t::find_price_from_expr(expr_t& expr, const commodity_t * commodity,
+ const datetime_t& moment) const
{
- price_point_t point;
- bool found = false;
-
-#if defined(DEBUG_ON)
-#define DEBUG_INDENT(cat, indent) \
- do { \
- if (SHOW_DEBUG(cat)) \
- for (int _i = 0; _i < indent; _i++) \
- ledger::_log_buffer << " "; \
- } while (false)
-#else
-#define DEBUG_INDENT(cat, indent)
-#endif
-
#if defined(DEBUG_ON)
- DEBUG_INDENT("commodity.prices.find", indent);
- if (moment)
- DEBUG("commodity.prices.find", "find price nearest before or on: " << *moment);
- else
- DEBUG("commodity.prices.find", "find any price");
-
- if (oldest) {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "but no older than: " << *oldest);
+ if (SHOW_DEBUG("commodity.price.find")) {
+ ledger::_log_buffer << "valuation expr: ";
+ expr.dump(ledger::_log_buffer);
+ DEBUG("commodity.price.find", "");
}
#endif
+ value_t result(expr.calc(*scope_t::default_scope));
- if (prices.size() == 0) {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "there are no prices in this history");
- return none;
- }
-
- if (! moment) {
- history_map::const_reverse_iterator r = prices.rbegin();
- point.when = (*r).first;
- point.price = (*r).second;
- found = true;
+ if (is_expr(result)) {
+ value_t call_args;
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "using most recent price");
- } else {
- history_map::const_iterator i = prices.upper_bound(*moment);
- if (i == prices.end()) {
- history_map::const_reverse_iterator r = prices.rbegin();
- point.when = (*r).first;
- point.price = (*r).second;
- found = true;
-
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "using last price");
- } else {
- point.when = (*i).first;
- if (*moment < point.when) {
- if (i != prices.begin()) {
- --i;
- point.when = (*i).first;
- point.price = (*i).second;
- found = true;
- }
- } else {
- point.price = (*i).second;
- found = true;
- }
+ call_args.push_back(string_value(base_symbol()));
+ call_args.push_back(moment);
+ if (commodity)
+ call_args.push_back(string_value(commodity->symbol()));
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "using found price");
- }
+ result = as_expr(result)->call(call_args, *scope_t::default_scope);
}
- if (! found) {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "could not find a price");
- return none;
- }
- else if (moment && point.when > *moment) {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "price is too young ");
- return none;
- }
- else if (oldest && point.when < *oldest) {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "price is too old ");
- return none;
- }
- else {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find",
- "returning price: " << point.when << ", " << point.price);
- return point;
- }
+ return price_point_t(moment, result.to_amount());
}
optional<price_point_t>
-commodity_t::varied_history_t::find_price(const commodity_t& source,
- const optional<commodity_t&>& commodity,
- const optional<datetime_t>& moment,
- const optional<datetime_t>& oldest
-#if defined(DEBUG_ON)
- , const int indent
-#endif
- ) const
+commodity_t::find_price(const commodity_t * commodity,
+ const datetime_t& moment,
+ const datetime_t& oldest) const
{
- optional<price_point_t> point;
- optional<datetime_t> limit = oldest;
-
-#if defined(VERIFY_ON)
- if (commodity) {
- VERIFY(source != *commodity);
- VERIFY(! commodity->has_annotation());
- VERIFY(source.referent() != commodity->referent());
- }
-#endif
-
-#if defined(DEBUG_ON)
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "varied_find_price for: " << source);
+ DEBUG("commodity.price.find", "commodity_t::find_price(" << symbol() << ")");
- DEBUG_INDENT("commodity.prices.find", indent);
+ const commodity_t * target = NULL;
if (commodity)
- DEBUG("commodity.prices.find", "looking for: commodity '" << *commodity << "'");
- else
- DEBUG("commodity.prices.find", "looking for: any commodity");
-
- if (moment) {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "time index: " << *moment);
- }
-
- if (oldest) {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "only consider prices younger than: " << *oldest);
- }
-#endif
-
- // Either we couldn't find a history for the target commodity, or we
- // couldn't find a price. In either case, search all histories known
- // to this commodity for a price which we can calculate in terms of
- // the goal commodity.
- price_point_t best;
- bool found = false;
-
- foreach (const history_by_commodity_map::value_type& hist, histories) {
- commodity_t& comm(*hist.first);
- if (comm == source)
- continue;
-
- DEBUG_INDENT("commodity.prices.find", indent + 1);
- DEBUG("commodity.prices.find",
- "searching for price via commodity '" << comm << "'");
-
- point = hist.second.find_price(moment, limit
-#if defined(DEBUG_ON)
- , indent + 2
-#endif
- );
- assert(! point || point->price.commodity() == comm);
-
- if (point) {
- optional<price_point_t> xlat;
+ target = commodity;
+ else if (pool().default_commodity)
+ target = &*pool().default_commodity;
- if (commodity && comm != *commodity) {
- DEBUG_INDENT("commodity.prices.find", indent + 1);
- DEBUG("commodity.prices.find", "looking for translation price");
-
- xlat = comm.find_price(commodity, moment, limit, true
-#if defined(DEBUG_ON)
- , indent + 2
-#endif
- );
- if (xlat) {
- DEBUG_INDENT("commodity.prices.find", indent + 1);
- DEBUG("commodity.prices.find", "found translated price "
- << xlat->price << " from " << xlat->when);
-
- point->price = xlat->price * point->price;
- if (xlat->when < point->when) {
- point->when = xlat->when;
-
- DEBUG_INDENT("commodity.prices.find", indent + 1);
- DEBUG("commodity.prices.find",
- "adjusting date of result back to " << point->when);
- }
- } else {
- DEBUG_INDENT("commodity.prices.find", indent + 1);
- DEBUG("commodity.prices.find", "saw no translated price there");
- continue;
- }
- }
-
- assert(! commodity || point->price.commodity() == *commodity);
-
- DEBUG_INDENT("commodity.prices.find", indent + 1);
- DEBUG("commodity.prices.find",
- "saw a price there: " << point->price << " from " << point->when);
+ if (target && this == target)
+ return none;
- if (! limit || point->when > *limit) {
- limit = point->when;
- best = *point;
- found = true;
+ base_t::memoized_price_entry entry(moment, oldest,
+ commodity ? commodity : NULL);
- DEBUG_INDENT("commodity.prices.find", indent + 1);
- DEBUG("commodity.prices.find",
- "search limit adjusted to " << *limit);
- }
- } else {
- DEBUG_INDENT("commodity.prices.find", indent + 1);
- DEBUG("commodity.prices.find", "saw no price there");
+ DEBUG("commodity.price.find", "looking for memoized args: "
+ << (! moment.is_not_a_date_time() ? format_datetime(moment) : "NONE") << ", "
+ << (! oldest.is_not_a_date_time() ? format_datetime(oldest) : "NONE") << ", "
+ << (commodity ? commodity->symbol() : "NONE"));
+ {
+ base_t::memoized_price_map::iterator i = base->price_map.find(entry);
+ if (i != base->price_map.end()) {
+ DEBUG("commodity.price.find", "found! returning: "
+ << ((*i).second ? (*i).second->price : amount_t(0L)));
+ return (*i).second;
}
}
- if (found) {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.download",
- "found price " << best.price << " from " << best.when);
- return best;
- }
- return none;
-}
-
-optional<commodity_t::history_t&>
-commodity_t::varied_history_t::history(const optional<commodity_t&>& commodity)
-{
- commodity_t * comm = NULL;
- if (! commodity) {
- if (histories.size() > 1)
- return none;
- comm = (*histories.begin()).first;
- } else {
- comm = &(*commodity);
+ datetime_t when;
+ if (! moment.is_not_a_date_time())
+ when = moment;
+ else if (epoch)
+ when = *epoch;
+ else
+ when = CURRENT_TIME();
+
+ if (base->value_expr)
+ return find_price_from_expr(*base->value_expr, commodity, when);
+
+ optional<price_point_t>
+ point(target ?
+ pool().commodity_price_history.find_price(referent(), *target,
+ when, oldest) :
+ pool().commodity_price_history.find_price(referent(), when, oldest));
+
+ // Record this price point in the memoization map
+ if (base->price_map.size() > base_t::max_price_map_size) {
+ DEBUG("history.find",
+ "price map has grown too large, clearing it by half");
+ for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++)
+ base->price_map.erase(base->price_map.begin());
}
- history_by_commodity_map::iterator i = histories.find(comm);
- if (i != histories.end())
- return (*i).second;
-
- return none;
-}
-
-optional<price_point_t>
-commodity_t::find_price(const optional<commodity_t&>& commodity,
- const optional<datetime_t>& moment,
- const optional<datetime_t>& oldest,
- const bool nested
-#if defined(DEBUG_ON)
- , const int indent
-#endif
- ) const
-{
- if (! has_flags(COMMODITY_WALKED) && base->varied_history) {
- optional<base_t::time_and_commodity_t> pair;
-#if defined(VERIFY_ON)
- optional<price_point_t> checkpoint;
- bool found = false;
-#endif
-
- if (! nested) {
- pair = base_t::time_and_commodity_t
- (base_t::optional_time_pair_t(moment, oldest),
- commodity ? &(*commodity) : NULL);
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "looking for memoized args: "
- << (moment ? format_datetime(*moment) : "NONE") << ", "
- << (oldest ? format_datetime(*oldest) : "NONE") << ", "
- << (commodity ? commodity->symbol() : "NONE"));
-
- base_t::memoized_price_map::iterator i = base->price_map.find(*pair);
- if (i != base->price_map.end()) {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "found! returning: "
- << ((*i).second ? (*i).second->price : amount_t(0L)));
-#if defined(VERIFY_ON)
- IF_VERIFY() {
- found = true;
- checkpoint = (*i).second;
- } else
-#endif // defined(VERIFY_ON)
- return (*i).second;
- }
- }
-
- optional<price_point_t> point;
-
- const_cast<commodity_t&>(*this).add_flags(COMMODITY_WALKED);
- try {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find", "manually finding price...");
+ DEBUG("history.find",
+ "remembered: " << (point ? point->price : amount_t(0L)));
+ base->price_map.insert(base_t::memoized_price_map::value_type(entry, point));
- point = base->varied_history->find_price(*this, commodity,
- moment, oldest
-#if defined(DEBUG_ON)
- , indent
-#endif
- );
- }
- catch (...) {
- const_cast<commodity_t&>(*this).drop_flags(COMMODITY_WALKED);
- throw;
- }
- const_cast<commodity_t&>(*this).drop_flags(COMMODITY_WALKED);
-
-#if defined(VERIFY_ON)
- if (DO_VERIFY() && found) {
- VERIFY(checkpoint == point);
- return checkpoint;
- }
-#endif // defined(VERIFY_ON)
-
- if (! nested && pair) {
- if (base->price_map.size() > base_t::max_price_map_size) {
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find",
- "price map has grown too large, clearing it by half");
-
- for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++)
- base->price_map.erase(base->price_map.begin());
- }
-
- DEBUG_INDENT("commodity.prices.find", indent);
- DEBUG("commodity.prices.find",
- "remembered: " << (point ? point->price : amount_t(0L)));
- base->price_map.insert
- (base_t::memoized_price_map::value_type(*pair, point));
- }
- return point;
- }
- return none;
+ return point;
}
optional<price_point_t>
commodity_t::check_for_updated_price(const optional<price_point_t>& point,
- const optional<datetime_t>& moment,
- const optional<commodity_t&>& in_terms_of)
+ const datetime_t& moment,
+ const commodity_t* in_terms_of)
{
if (pool().get_quotes && ! has_flags(COMMODITY_NOMARKET)) {
bool exceeds_leeway = true;
if (point) {
time_duration_t::sec_type seconds_diff;
- if (moment) {
- seconds_diff = (*moment - point->when).total_seconds();
- DEBUG("commodity.download", "moment = " << *moment);
+ if (! moment.is_not_a_date_time()) {
+ seconds_diff = (moment - point->when).total_seconds();
+ DEBUG("commodity.download", "moment = " << moment);
DEBUG("commodity.download", "slip.moment = " << seconds_diff);
} else {
seconds_diff = (TRUE_CURRENT_TIME() - point->when).total_seconds();
@@ -474,10 +206,10 @@ commodity_t::check_for_updated_price(const optional<price_point_t>& point,
DEBUG("commodity.download",
"attempting to download a more current quote...");
if (optional<price_point_t> quote =
- pool().get_commodity_quote(*this, in_terms_of)) {
+ pool().get_commodity_quote(referent(), in_terms_of)) {
if (! in_terms_of ||
(quote->price.has_commodity() &&
- quote->price.commodity() == *in_terms_of))
+ quote->price.commodity_ptr() == in_terms_of))
return quote;
}
}
@@ -485,6 +217,16 @@ commodity_t::check_for_updated_price(const optional<price_point_t>& point,
return point;
}
+commodity_t& commodity_t::nail_down(const expr_t& expr)
+{
+ annotation_t new_details;
+
+ new_details.value_expr = expr;
+ new_details.add_flags(ANNOTATION_VALUE_EXPR_CALCULATED);
+
+ return *pool().find_or_create(symbol(), new_details);
+}
+
commodity_t::operator bool() const
{
return this != pool().null_commodity;
@@ -641,7 +383,7 @@ void commodity_t::parse_symbol(char *& p, string& symbol)
throw_(amount_error, _("Failed to parse commodity"));
}
-void commodity_t::print(std::ostream& out, bool elide_quotes) const
+void commodity_t::print(std::ostream& out, bool elide_quotes, bool) const
{
string sym = symbol();
if (elide_quotes && has_flags(COMMODITY_STYLE_SEPARATED) &&
@@ -740,6 +482,15 @@ bool commodity_t::compare_by_commodity::operator()(const amount_t * left,
if (aleftcomm.details.tag && arightcomm.details.tag)
return *aleftcomm.details.tag < *arightcomm.details.tag;
+ if (! aleftcomm.details.value_expr && arightcomm.details.value_expr)
+ return true;
+ if (aleftcomm.details.value_expr && ! arightcomm.details.value_expr)
+ return false;
+
+ if (aleftcomm.details.value_expr && arightcomm.details.value_expr)
+ return (aleftcomm.details.value_expr->text() <
+ arightcomm.details.value_expr->text());
+
assert(false);
return true;
}
@@ -767,28 +518,6 @@ void to_xml(std::ostream& out, const commodity_t& comm,
if (commodity_details) {
if (comm.has_annotation())
to_xml(out, as_annotated_commodity(comm).details);
-
- if (comm.varied_history()) {
- push_xml y(out, "varied-history");
-
- foreach (const commodity_t::history_by_commodity_map::value_type& pair,
- comm.varied_history()->histories) {
- {
- push_xml z(out, "symbol");
- out << y.guard(pair.first->symbol());
- }
- {
- push_xml z(out, "history");
-
- foreach (const commodity_t::history_map::value_type& inner_pair,
- pair.second.prices) {
- push_xml w(out, "price-point");
- to_xml(out, inner_pair.first);
- to_xml(out, inner_pair.second);
- }
- }
- }
- }
}
}
diff --git a/src/commodity.h b/src/commodity.h
index d7747b2a..bfbabe6b 100644
--- a/src/commodity.h
+++ b/src/commodity.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -47,6 +47,8 @@
#ifndef _COMMODITY_H
#define _COMMODITY_H
+#include "expr.h"
+
namespace ledger {
struct keep_details_t;
@@ -85,78 +87,6 @@ class commodity_t
: public delegates_flags<uint_least16_t>,
public equality_comparable1<commodity_t, noncopyable>
{
-public:
- typedef std::map<const datetime_t, amount_t> history_map;
-
- struct history_t
- {
- history_map prices;
-
- void add_price(commodity_t& source,
- const datetime_t& date,
- const amount_t& price,
- const bool reflexive = true);
- bool remove_price(const datetime_t& date);
-
- optional<price_point_t>
- find_price(const optional<datetime_t>& moment = none,
- const optional<datetime_t>& oldest = none
-#if defined(DEBUG_ON)
- , const int indent = 0
-#endif
- ) const;
-
-#if defined(HAVE_BOOST_SERIALIZATION)
- private:
- /** Serialization. */
-
- friend class boost::serialization::access;
-
- template<class Archive>
- void serialize(Archive& ar, const unsigned int /* version */) {
- ar & prices;
- }
-#endif // HAVE_BOOST_SERIALIZATION
- };
-
- typedef std::map<commodity_t *, history_t> history_by_commodity_map;
-
- struct varied_history_t
- {
- history_by_commodity_map histories;
-
- void add_price(commodity_t& source,
- const datetime_t& date,
- const amount_t& price,
- const bool reflexive = true);
- bool remove_price(const datetime_t& date, commodity_t& commodity);
-
- optional<price_point_t>
- find_price(const commodity_t& source,
- const optional<commodity_t&>& commodity = none,
- const optional<datetime_t>& moment = none,
- const optional<datetime_t>& oldest = none
-#if defined(DEBUG_ON)
- , const int indent = 0
-#endif
- ) const;
-
- optional<history_t&>
- history(const optional<commodity_t&>& commodity = none);
-
-#if defined(HAVE_BOOST_SERIALIZATION)
- private:
- /** Serialization. */
-
- friend class boost::serialization::access;
-
- template<class Archive>
- void serialize(Archive& ar, const unsigned int /* version */) {
- ar & histories;
- }
-#endif // HAVE_BOOST_SERIALIZATION
- };
-
protected:
friend class commodity_pool_t;
friend class annotated_commodity_t;
@@ -178,37 +108,34 @@ protected:
#define COMMODITY_SAW_ANN_PRICE_FLOAT 0x400
#define COMMODITY_SAW_ANN_PRICE_FIXATED 0x800
- string symbol;
- amount_t::precision_t precision;
- optional<string> name;
- optional<string> note;
- optional<varied_history_t> varied_history;
- optional<amount_t> smaller;
- optional<amount_t> larger;
-
- typedef std::pair<optional<datetime_t>,
- optional<datetime_t> > optional_time_pair_t;
- typedef std::pair<optional_time_pair_t,
- commodity_t *> time_and_commodity_t;
- typedef std::map<time_and_commodity_t,
+ string symbol;
+ optional<std::size_t> graph_index;
+ amount_t::precision_t precision;
+ optional<string> name;
+ optional<string> note;
+ optional<amount_t> smaller;
+ optional<amount_t> larger;
+ optional<expr_t> value_expr;
+
+ typedef tuple<datetime_t, datetime_t,
+ const commodity_t *> memoized_price_entry;
+ typedef std::map<memoized_price_entry,
optional<price_point_t> > memoized_price_map;
- static const std::size_t max_price_map_size = 16;
+ static const std::size_t max_price_map_size = 8;
mutable memoized_price_map price_map;
- mutable bool searched;
-
public:
explicit base_t(const string& _symbol)
: supports_flags<uint_least16_t>
(commodity_t::decimal_comma_by_default ?
static_cast<uint_least16_t>(COMMODITY_STYLE_DECIMAL_COMMA) :
static_cast<uint_least16_t>(COMMODITY_STYLE_DEFAULTS)),
- symbol(_symbol), precision(0), searched(false) {
- TRACE_CTOR(base_t, "const string&");
+ symbol(_symbol), precision(0) {
+ TRACE_CTOR(commodity_t::base_t, "const string&");
}
virtual ~base_t() {
- TRACE_DTOR(base_t);
+ TRACE_DTOR(commodity_t::base_t);
}
#if defined(HAVE_BOOST_SERIALIZATION)
@@ -228,7 +155,6 @@ protected:
ar & precision;
ar & name;
ar & note;
- ar & varied_history;
ar & smaller;
ar & larger;
}
@@ -239,7 +165,6 @@ protected:
commodity_pool_t * parent_;
optional<string> qualified_symbol;
- optional<string> mapping_key_;
bool annotated;
explicit commodity_t(commodity_pool_t * _parent,
@@ -263,6 +188,9 @@ public:
return comm == *this;
return base.get() == comm.base.get();
}
+ bool operator==(const string& name) const {
+ return base_symbol() == name;
+ }
static bool symbol_needs_quotes(const string& symbol);
@@ -293,11 +221,11 @@ public:
return qualified_symbol ? *qualified_symbol : base_symbol();
}
- string mapping_key() const {
- if (mapping_key_)
- return *mapping_key_;
- else
- return base_symbol();
+ optional<std::size_t> graph_index() const {;
+ return base->graph_index;
+ }
+ void set_graph_index(const optional<std::size_t>& arg = none) {
+ base->graph_index = arg;
}
optional<string> name() const {
@@ -335,53 +263,37 @@ public:
base->larger = arg;
}
- optional<varied_history_t&> varied_history() {
- if (base->varied_history)
- return *base->varied_history;
- return none;
+ virtual optional<expr_t> value_expr() const {
+ return base->value_expr;
}
- optional<const varied_history_t&> varied_history() const {
- if (base->varied_history)
- return *base->varied_history;
- return none;
+ void set_value_expr(const optional<expr_t>& expr = none) {
+ base->value_expr = expr;
}
- optional<history_t&> history(const optional<commodity_t&>& commodity);
+ void add_price(const datetime_t& date, const amount_t& price,
+ const bool reflexive = true);
+ void remove_price(const datetime_t& date, commodity_t& commodity);
- // These methods provide a transparent pass-through to the underlying
- // base->varied_history object.
+ void map_prices(function<void(datetime_t, const amount_t&)> fn,
+ const datetime_t& moment = datetime_t(),
+ const datetime_t& _oldest = datetime_t(),
+ bool bidirectionally = false);
- void add_price(const datetime_t& date, const amount_t& price,
- const bool reflexive = true) {
- if (! base->varied_history)
- base->varied_history = varied_history_t();
- base->varied_history->add_price(*this, date, price, reflexive);
- DEBUG("commodity.prices.find", "Price added, clearing price_map");
- base->price_map.clear(); // a price was added, invalid the map
- }
- bool remove_price(const datetime_t& date, commodity_t& commodity) {
- if (base->varied_history) {
- base->varied_history->remove_price(date, commodity);
- DEBUG("commodity.prices.find", "Price removed, clearing price_map");
- base->price_map.clear(); // a price was added, invalid the map
- }
- return false;
- }
+ optional<price_point_t>
+ find_price_from_expr(expr_t& expr, const commodity_t * commodity,
+ const datetime_t& moment) const;
optional<price_point_t>
- find_price(const optional<commodity_t&>& commodity = none,
- const optional<datetime_t>& moment = none,
- const optional<datetime_t>& oldest = none,
- const bool nested = false
-#if defined(DEBUG_ON)
- , const int indent = 0
-#endif
- ) const;
+ virtual find_price(const commodity_t * commodity = NULL,
+ const datetime_t& moment = datetime_t(),
+ const datetime_t& oldest = datetime_t()) const;
optional<price_point_t>
check_for_updated_price(const optional<price_point_t>& point,
- const optional<datetime_t>& moment,
- const optional<commodity_t&>& in_terms_of);
+ const datetime_t& moment,
+ const commodity_t * in_terms_of);
+
+ commodity_t& nail_down(const expr_t& expr);
// Methods related to parsing, reading, writing, etc., the commodity
// itself.
@@ -394,7 +306,8 @@ public:
return temp;
}
- void print(std::ostream& out, bool elide_quotes = false) const;
+ virtual void print(std::ostream& out, bool elide_quotes = false,
+ bool print_annotations = false) const;
bool valid() const;
struct compare_by_commodity {
@@ -423,14 +336,13 @@ private:
ar & base;
ar & parent_;
ar & qualified_symbol;
- ar & mapping_key_;
ar & annotated;
}
#endif // HAVE_BOOST_SERIALIZATION
};
inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) {
- comm.print(out);
+ comm.print(out, false, true);
return out;
}
diff --git a/src/compare.cc b/src/compare.cc
index 12114c7d..e2a298c2 100644
--- a/src/compare.cc
+++ b/src/compare.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -44,7 +44,7 @@ void push_sort_value(std::list<sort_value_t>& sort_values,
if (node->kind == expr_t::op_t::O_CONS) {
while (node && node->kind == expr_t::op_t::O_CONS) {
push_sort_value(sort_values, node->left(), scope);
- node = node->right();
+ node = node->has_right() ? node->right() : NULL;
}
} else {
bool inverted = false;
diff --git a/src/compare.h b/src/compare.h
index 0e7bf5e5..e1abbca1 100644
--- a/src/compare.h
+++ b/src/compare.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/context.h b/src/context.h
new file mode 100644
index 00000000..45bb9990
--- /dev/null
+++ b/src/context.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup data
+ */
+
+/**
+ * @file context.h
+ * @author John Wiegley
+ *
+ * @ingroup data
+ */
+#ifndef _CONTEXT_H
+#define _CONTEXT_H
+
+#include "utils.h"
+#include "times.h"
+
+namespace ledger {
+
+class journal_t;
+class account_t;
+class scope_t;
+
+class parse_context_t
+{
+public:
+ static const std::size_t MAX_LINE = 4096;
+
+ shared_ptr<std::istream> stream;
+
+ path pathname;
+ path current_directory;
+ journal_t * journal;
+ account_t * master;
+ scope_t * scope;
+ char linebuf[MAX_LINE + 1];
+ istream_pos_type line_beg_pos;
+ istream_pos_type curr_pos;
+ std::size_t linenum;
+ std::size_t errors;
+ std::size_t count;
+ std::size_t sequence;
+
+ explicit parse_context_t(const path& cwd)
+ : current_directory(cwd), master(NULL), scope(NULL),
+ linenum(0), errors(0), count(0), sequence(1) {}
+
+ explicit parse_context_t(shared_ptr<std::istream> _stream,
+ const path& cwd)
+ : stream(_stream), current_directory(cwd), master(NULL),
+ scope(NULL), linenum(0), errors(0), count(0), sequence(1) {}
+
+ parse_context_t(const parse_context_t& context)
+ : stream(context.stream),
+ pathname(context.pathname),
+ current_directory(context.current_directory),
+ journal(context.journal),
+ master(context.master),
+ scope(context.scope),
+ line_beg_pos(context.line_beg_pos),
+ curr_pos(context.curr_pos),
+ linenum(context.linenum),
+ errors(context.errors),
+ count(context.count),
+ sequence(context.sequence) {
+ std::memcpy(linebuf, context.linebuf, MAX_LINE);
+ }
+
+ string location() const {
+ return file_context(pathname, linenum);
+ }
+
+ void warning(const string& what) const {
+ warning_func(location() + what);
+ }
+};
+
+inline parse_context_t open_for_reading(const path& pathname,
+ const path& cwd)
+{
+ path filename = resolve_path(pathname);
+
+ if (! exists(filename))
+ throw_(std::runtime_error,
+ _("Cannot read journal file %1") << filename);
+
+#if BOOST_VERSION >= 104600 && BOOST_FILESYSTEM_VERSION >= 3
+ path parent(filesystem::absolute(pathname, cwd).parent_path());
+#else
+ path parent(filesystem::complete(pathname, cwd).parent_path());
+#endif
+ shared_ptr<std::istream> stream(new ifstream(filename));
+ parse_context_t context(stream, parent);
+ context.pathname = filename;
+ return context;
+}
+
+class parse_context_stack_t
+{
+ std::list<parse_context_t> parsing_context;
+
+public:
+ void push() {
+ parsing_context.push_front(parse_context_t(filesystem::current_path()));
+ }
+ void push(shared_ptr<std::istream> stream,
+ const path& cwd = filesystem::current_path()) {
+ parsing_context.push_front(parse_context_t(stream, cwd));
+ }
+ void push(const path& pathname,
+ const path& cwd = filesystem::current_path()) {
+ parsing_context.push_front(open_for_reading(pathname, cwd));
+ }
+
+ void push(const parse_context_t& context) {
+ parsing_context.push_front(context);
+ }
+
+ void pop() {
+ assert(! parsing_context.empty());
+ parsing_context.pop_front();
+ }
+
+ parse_context_t& get_current() {
+ assert(! parsing_context.empty());
+ return parsing_context.front();
+ }
+};
+
+} // namespace ledger
+
+#endif // _CONTEXT_H
diff --git a/src/convert.cc b/src/convert.cc
index 493fbb7a..e8ca241e 100644
--- a/src/convert.cc
+++ b/src/convert.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -56,75 +56,50 @@ value_t convert_command(call_scope_t& args)
account_t * bucket = journal.master->find_account(bucket_name);
account_t * unknown = journal.master->find_account(_("Expenses:Unknown"));
- // Make an amounts mapping for the account under consideration
-
- typedef std::map<value_t, std::list<post_t *> > post_map_t;
- post_map_t post_map;
-
- xacts_iterator journal_iter(journal);
- while (xact_t * xact = *journal_iter++) {
- post_t * post = NULL;
- xact_posts_iterator xact_iter(*xact);
- while ((post = *xact_iter++) != NULL) {
- if (post->account == bucket)
- break;
- }
- if (post) {
- post_map_t::iterator i = post_map.find(post->amount);
- if (i == post_map.end()) {
- std::list<post_t *> post_list;
- post_list.push_back(post);
- post_map.insert(post_map_t::value_type(post->amount, post_list));
- } else {
- (*i).second.push_back(post);
- }
- }
- }
-
// Create a flat list
xacts_list current_xacts(journal.xacts_begin(), journal.xacts_end());
// Read in the series of transactions from the CSV file
print_xacts formatter(report);
- ifstream data(path(args.get<string>(0)));
- csv_reader reader(data);
+ path csv_file_path(args.get<string>(0));
- while (xact_t * xact = reader.read_xact(journal, bucket)) {
- if (report.HANDLED(invert)) {
- foreach (post_t * post, xact->posts)
- post->amount.in_place_negate();
- }
+ report.session.parsing_context.push(csv_file_path);
+ parse_context_t& context(report.session.parsing_context.get_current());
+ context.journal = &journal;
+ context.master = bucket;
- bool matched = false;
- if (! xact->posts.front()->amount.is_null()) {
- post_map_t::iterator i = post_map.find(- xact->posts.front()->amount);
- if (i != post_map.end()) {
- std::list<post_t *>& post_list((*i).second);
- foreach (post_t * post, post_list) {
- if (xact->code && post->xact->code &&
- *xact->code == *post->xact->code) {
- matched = true;
- break;
- }
- else if (xact->actual_date() == post->actual_date()) {
- matched = true;
- break;
- }
- }
+ csv_reader reader(context);
+
+ try {
+ while (xact_t * xact = reader.read_xact(report.HANDLED(rich_data))) {
+ if (report.HANDLED(invert)) {
+ foreach (post_t * post, xact->posts)
+ post->amount.in_place_negate();
}
- }
- if (matched) {
- DEBUG("convert.csv", "Ignored xact with code: " << *xact->code);
- checked_delete(xact); // ignore it
- }
- else {
+ string ref = (xact->has_tag(_("UUID")) ?
+ xact->get_tag(_("UUID"))->to_string() :
+ sha1sum(reader.get_last_line()));
+
+ checksum_map_t::const_iterator entry = journal.checksum_map.find(ref);
+ if (entry != journal.checksum_map.end()) {
+ INFO(file_context(reader.get_pathname(),
+ reader.get_linenum())
+ << "Ignoring known UUID " << ref);
+ checked_delete(xact); // ignore it
+ continue;
+ }
+
+ if (report.HANDLED(rich_data) && ! xact->has_tag(_("UUID")))
+ xact->set_tag(_("UUID"), string_value(ref));
+
if (xact->posts.front()->account == NULL) {
- // jww (2010-03-07): Bind this logic to an option: --auto-match
if (account_t * acct =
- lookup_probable_account(xact->payee, current_xacts.rbegin(),
- current_xacts.rend(), bucket).second)
+ (report.HANDLED(auto_match) ?
+ lookup_probable_account(xact->payee, current_xacts.rbegin(),
+ current_xacts.rend(), bucket).second :
+ NULL))
xact->posts.front()->account = acct;
else
xact->posts.front()->account = unknown;
@@ -141,8 +116,16 @@ value_t convert_command(call_scope_t& args)
formatter(*post);
}
}
+ formatter.flush();
+ }
+ catch (const std::exception&) {
+ add_error_context(_("While parsing file %1")
+ << file_context(reader.get_pathname(),
+ reader.get_linenum()));
+ add_error_context(_("While parsing CSV line:"));
+ add_error_context(line_context(reader.get_last_line()));
+ throw;
}
- formatter.flush();
// If not, transform the payee according to regexps
diff --git a/src/convert.h b/src/convert.h
index 6d02f24a..de958108 100644
--- a/src/convert.h
+++ b/src/convert.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/csv.cc b/src/csv.cc
index f5b72ecf..1e55129e 100644
--- a/src/csv.cc
+++ b/src/csv.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -40,27 +40,27 @@
namespace ledger {
-string csv_reader::read_field(std::istream& sin)
+string csv_reader::read_field(std::istream& in)
{
string field;
char c;
- if (sin.peek() == '"' || sin.peek() == '|') {
- sin.get(c);
+ if (in.peek() == '"' || in.peek() == '|') {
+ in.get(c);
char x;
- while (sin.good() && ! sin.eof()) {
- sin.get(x);
+ while (in.good() && ! in.eof()) {
+ in.get(x);
if (x == '\\') {
- sin.get(x);
+ in.get(x);
}
- else if (x == '"' && sin.peek() == '"') {
- sin.get(x);
+ else if (x == '"' && in.peek() == '"') {
+ in.get(x);
}
else if (x == c) {
if (x == '|')
- sin.unget();
- else if (sin.peek() == ',')
- sin.get(c);
+ in.unget();
+ else if (in.peek() == ',')
+ in.get(c);
break;
}
if (x != '\0')
@@ -68,36 +68,36 @@ string csv_reader::read_field(std::istream& sin)
}
}
else {
- while (sin.good() && ! sin.eof()) {
- sin.get(c);
- if (c == ',')
- break;
- if (c != '\0')
- field += c;
+ while (in.good() && ! in.eof()) {
+ in.get(c);
+ if (in.good()) {
+ if (c == ',')
+ break;
+ if (c != '\0')
+ field += c;
+ }
}
}
trim(field);
return field;
}
-char * csv_reader::next_line(std::istream& sin)
+char * csv_reader::next_line(std::istream& in)
{
- static char linebuf[MAX_LINE + 1];
+ while (in.good() && ! in.eof() && in.peek() == '#')
+ in.getline(context.linebuf, parse_context_t::MAX_LINE);
- while (sin.good() && ! sin.eof() && sin.peek() == '#')
- sin.getline(linebuf, MAX_LINE);
-
- if (! sin.good() || sin.eof())
+ if (! in.good() || in.eof())
return NULL;
- sin.getline(linebuf, MAX_LINE);
+ in.getline(context.linebuf, parse_context_t::MAX_LINE);
- return linebuf;
+ return context.linebuf;
}
-void csv_reader::read_index(std::istream& sin)
+void csv_reader::read_index(std::istream& in)
{
- char * line = next_line(sin);
+ char * line = next_line(in);
if (! line)
return;
@@ -109,8 +109,8 @@ void csv_reader::read_index(std::istream& sin)
if (date_mask.match(field))
index.push_back(FIELD_DATE);
- else if (date_eff_mask.match(field))
- index.push_back(FIELD_DATE_EFF);
+ else if (date_aux_mask.match(field))
+ index.push_back(FIELD_DATE_AUX);
else if (code_mask.match(field))
index.push_back(FIELD_CODE);
else if (payee_mask.match(field))
@@ -130,60 +130,52 @@ void csv_reader::read_index(std::istream& sin)
}
}
-xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket)
+xact_t * csv_reader::read_xact(bool rich_data)
{
- restart:
- char * line = next_line(in);
+ char * line = next_line(*context.stream.get());
if (! line || index.empty())
return NULL;
+ context.linenum++;
std::istringstream instr(line);
- std::auto_ptr<xact_t> xact(new xact_t);
- std::auto_ptr<post_t> post(new post_t);
+ unique_ptr<xact_t> xact(new xact_t);
+ unique_ptr<post_t> post(new post_t);
xact->set_state(item_t::CLEARED);
xact->pos = position_t();
- xact->pos->pathname = "jww (2010-03-05): unknown";
- xact->pos->beg_pos = in.tellg();
- xact->pos->beg_line = 0;
- xact->pos->sequence = 0;
+ xact->pos->pathname = context.pathname;
+ xact->pos->beg_pos = context.stream->tellg();
+ xact->pos->beg_line = context.linenum;
+ xact->pos->sequence = context.sequence++;
post->xact = xact.get();
-#if 0
post->pos = position_t();
- post->pos->pathname = pathname;
- post->pos->beg_pos = line_beg_pos;
- post->pos->beg_line = linenum;
+ post->pos->pathname = context.pathname;
+ post->pos->beg_pos = context.stream->tellg();
+ post->pos->beg_line = context.linenum;
post->pos->sequence = context.sequence++;
-#endif
post->set_state(item_t::CLEARED);
post->account = NULL;
std::vector<int>::size_type n = 0;
amount_t amt;
- string total;
+ string total;
+ string field;
while (instr.good() && ! instr.eof()) {
- string field = read_field(instr);
+ field = read_field(instr);
switch (index[n]) {
case FIELD_DATE:
- if (field.empty())
- goto restart;
- try {
- xact->_date = parse_date(field);
- }
- catch (date_error&) {
- goto restart;
- }
+ xact->_date = parse_date(field);
break;
- case FIELD_DATE_EFF:
- xact->_date_eff = parse_date(field);
+ case FIELD_DATE_AUX:
+ xact->_date_aux = parse_date(field);
break;
case FIELD_CODE:
@@ -193,7 +185,7 @@ xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket)
case FIELD_PAYEE: {
bool found = false;
- foreach (payee_mapping_t& value, journal.payee_mappings) {
+ foreach (payee_mapping_t& value, context.journal->payee_mappings) {
DEBUG("csv.mappings", "Looking for payee mapping: " << value.first);
if (value.first.match(field)) {
xact->payee = value.second;
@@ -243,16 +235,15 @@ xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket)
n++;
}
-#if 0
- xact->set_tag(_("Imported"),
- string(format_date(CURRENT_DATE(), FMT_WRITTEN)));
- xact->set_tag(_("Original"), string(line));
- xact->set_tag(_("SHA1"), string(sha1sum(line)));
-#endif
+ if (rich_data) {
+ xact->set_tag(_("Imported"),
+ string_value(format_date(CURRENT_DATE(), FMT_WRITTEN)));
+ xact->set_tag(_("CSV"), string_value(line));
+ }
// Translate the account name, if we have enough information to do so
- foreach (account_mapping_t& value, journal.account_mappings) {
+ foreach (account_mapping_t& value, context.journal->account_mappings) {
if (value.first.match(xact->payee)) {
post->account = value.second;
break;
@@ -267,16 +258,14 @@ xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket)
post->xact = xact.get();
-#if 0
post->pos = position_t();
- post->pos->pathname = pathname;
- post->pos->beg_pos = line_beg_pos;
- post->pos->beg_line = linenum;
+ post->pos->pathname = context.pathname;
+ post->pos->beg_pos = context.stream->tellg();
+ post->pos->beg_line = context.linenum;
post->pos->sequence = context.sequence++;
-#endif
post->set_state(item_t::CLEARED);
- post->account = bucket;
+ post->account = context.master;
if (! amt.is_null())
post->amount = - amt;
diff --git a/src/csv.h b/src/csv.h
index 5ff8b59e..7d5098d2 100644
--- a/src/csv.h
+++ b/src/csv.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -43,6 +43,7 @@
#define _CSV_H
#include "value.h"
+#include "context.h"
namespace ledger {
@@ -52,13 +53,11 @@ class account_t;
class csv_reader
{
- static const std::size_t MAX_LINE = 1024;
-
- std::istream& in;
+ parse_context_t context;
enum headers_t {
FIELD_DATE = 0,
- FIELD_DATE_EFF,
+ FIELD_DATE_AUX,
FIELD_CODE,
FIELD_PAYEE,
FIELD_AMOUNT,
@@ -70,7 +69,7 @@ class csv_reader
};
mask_t date_mask;
- mask_t date_eff_mask;
+ mask_t date_aux_mask;
mask_t code_mask;
mask_t payee_mask;
mask_t amount_mask;
@@ -80,29 +79,40 @@ class csv_reader
std::vector<int> index;
std::vector<string> names;
- std::vector<string> fields;
-
- typedef std::map<string, string> string_map;
public:
- csv_reader(std::istream& _in)
- : in(_in),
+ csv_reader(parse_context_t& _context)
+ : context(_context),
date_mask("date"),
- date_eff_mask("posted( ?date)?"),
+ date_aux_mask("posted( ?date)?"),
code_mask("code"),
payee_mask("(payee|desc(ription)?|title)"),
amount_mask("amount"),
cost_mask("cost"),
total_mask("total"),
note_mask("note") {
- read_index(in);
+ read_index(*context.stream.get());
+ TRACE_CTOR(csv_reader, "parse_context_t&");
+ }
+ ~csv_reader() {
+ TRACE_DTOR(csv_reader);
}
+ void read_index(std::istream& in);
string read_field(std::istream& in);
char * next_line(std::istream& in);
- void read_index(std::istream& in);
- xact_t * read_xact(journal_t& journal, account_t * bucket);
+ xact_t * read_xact(bool rich_data);
+
+ const char * get_last_line() const {
+ return context.linebuf;
+ }
+ path get_pathname() const {
+ return context.pathname;
+ }
+ std::size_t get_linenum() const {
+ return context.linenum;
+ }
};
} // namespace ledger
diff --git a/src/draft.cc b/src/draft.cc
index 2b8b45f7..43c214cb 100644
--- a/src/draft.cc
+++ b/src/draft.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -67,10 +67,8 @@ void draft_t::xact_template_t::dump(std::ostream& out) const
<< std::endl;
} else {
foreach (const post_template_t& post, posts) {
- straccstream accum;
out << std::endl
- << ACCUM(accum << _("[Posting \"%1\"]")
- << (post.from ? _("from") : _("to")))
+ << STR(_("[Posting \"%1\"]") << (post.from ? _("from") : _("to")))
<< std::endl;
if (post.account_mask)
@@ -111,7 +109,14 @@ void draft_t::parse_args(const value_t& args)
}
else if (check_for_date &&
bool(weekday = string_to_day_of_week(what[0]))) {
+#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
short dow = static_cast<short>(*weekday);
+#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7
+#pragma GCC diagnostic pop
+#endif
date_t date = CURRENT_DATE() - date_duration(1);
while (date.day_of_week() != dow)
date -= date_duration(1);
@@ -235,7 +240,7 @@ xact_t * draft_t::insert(journal_t& journal)
throw std::runtime_error(_("'xact' command requires at least a payee"));
xact_t * matching = NULL;
- std::auto_ptr<xact_t> added(new xact_t);
+ unique_ptr<xact_t> added(new xact_t);
if (xact_t * xact =
lookup_probable_account(tmpl->payee_mask.str(), journal.xacts.rbegin(),
@@ -318,7 +323,7 @@ xact_t * draft_t::insert(journal_t& journal)
}
foreach (xact_template_t::post_template_t& post, tmpl->posts) {
- std::auto_ptr<post_t> new_post;
+ unique_ptr<post_t> new_post;
commodity_t * found_commodity = NULL;
@@ -502,7 +507,6 @@ value_t template_command(call_scope_t& args)
out << std::endl << std::endl;
draft_t draft(args.value());
-
out << _("--- Transaction template ---") << std::endl;
draft.dump(out);
@@ -512,15 +516,16 @@ value_t template_command(call_scope_t& args)
value_t xact_command(call_scope_t& args)
{
report_t& report(find_scope<report_t>(args));
- draft_t draft(args.value());
+ draft_t draft(args.value());
- xact_t * new_xact = draft.insert(*report.session.journal.get());
+ unique_ptr<xact_t> new_xact(draft.insert(*report.session.journal.get()));
+ if (new_xact.get()) {
+ // Only consider actual postings for the "xact" command
+ report.HANDLER(limit_).on("#xact", "actual");
- // Only consider actual postings for the "xact" command
- report.HANDLER(limit_).on(string("#xact"), "actual");
+ report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact.get());
+ }
- if (new_xact)
- report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact);
return true;
}
diff --git a/src/draft.h b/src/draft.h
index 59039f77..9023e6da 100644
--- a/src/draft.h
+++ b/src/draft.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -68,12 +68,31 @@ class draft_t : public expr_base_t<value_t>
optional<string> cost_operator;
optional<amount_t> cost;
- post_template_t() : from(false) {}
+ post_template_t() : from(false) {
+ TRACE_CTOR(post_template_t, "");
+ }
+ ~post_template_t() throw() {
+ TRACE_DTOR(post_template_t);
+ }
};
std::list<post_template_t> posts;
- xact_template_t() {}
+ xact_template_t() {
+ TRACE_CTOR(xact_template_t, "");
+ }
+ xact_template_t(const xact_template_t& other)
+ : date(other.date),
+ code(other.code),
+ note(other.note),
+ payee_mask(other.payee_mask),
+ posts(other.posts)
+ {
+ TRACE_CTOR(xact_template_t, "copy");
+ }
+ ~xact_template_t() throw() {
+ TRACE_DTOR(xact_template_t);
+ }
void dump(std::ostream& out) const;
};
@@ -82,11 +101,11 @@ class draft_t : public expr_base_t<value_t>
public:
draft_t(const value_t& args) : base_type() {
- TRACE_CTOR(draft_t, "value_t");
if (! args.empty())
parse_args(args);
+ TRACE_CTOR(draft_t, "value_t");
}
- virtual ~draft_t() {
+ virtual ~draft_t() throw() {
TRACE_DTOR(draft_t);
}
diff --git a/src/emacs.cc b/src/emacs.cc
index 5048a348..41c67cc6 100644
--- a/src/emacs.cc
+++ b/src/emacs.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/emacs.h b/src/emacs.h
index 97292728..a018ce68 100644
--- a/src/emacs.h
+++ b/src/emacs.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/error.cc b/src/error.cc
index 88adfbdb..4a16f4e3 100644
--- a/src/error.cc
+++ b/src/error.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/error.h b/src/error.h
index b9960b03..86d9de76 100644
--- a/src/error.h
+++ b/src/error.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -101,6 +101,12 @@ string source_context(const path& file,
virtual ~name() throw() {} \
}
+struct error_count {
+ std::size_t count;
+ explicit error_count(std::size_t _count) : count(_count) {}
+ const char * what() const { return ""; }
+};
+
} // namespace ledger
#endif // _ERROR_H
diff --git a/src/expr.cc b/src/expr.cc
index b3d4abcd..25967207 100644
--- a/src/expr.cc
+++ b/src/expr.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -37,6 +37,59 @@
namespace ledger {
+expr_t::expr_t() : base_type()
+{
+ TRACE_CTOR(expr_t, "");
+}
+
+expr_t::expr_t(const expr_t& other) : base_type(other), ptr(other.ptr)
+{
+ TRACE_CTOR(expr_t, "copy");
+}
+expr_t::expr_t(ptr_op_t _ptr, scope_t * _context)
+ : base_type(_context), ptr(_ptr)
+{
+ TRACE_CTOR(expr_t, "const ptr_op_t&, scope_t *");
+}
+
+expr_t::expr_t(const string& _str, const parse_flags_t& flags)
+ : base_type()
+{
+ if (! _str.empty())
+ parse(_str, flags);
+ TRACE_CTOR(expr_t, "string, parse_flags_t");
+}
+
+expr_t::expr_t(std::istream& in, const parse_flags_t& flags)
+ : base_type()
+{
+ parse(in, flags);
+ TRACE_CTOR(expr_t, "std::istream&, parse_flags_t");
+}
+
+expr_t::~expr_t() {
+ TRACE_DTOR(expr_t);
+}
+
+expr_t& expr_t::operator=(const expr_t& _expr)
+{
+ if (this != &_expr) {
+ base_type::operator=(_expr);
+ ptr = _expr.ptr;
+ }
+ return *this;
+}
+
+expr_t::operator bool() const throw()
+{
+ return ptr.get() != NULL;
+}
+
+expr_t::ptr_op_t expr_t::get_op() throw()
+{
+ return ptr;
+}
+
void expr_t::parse(std::istream& in, const parse_flags_t& flags,
const optional<string>& original_string)
{
@@ -163,6 +216,65 @@ void expr_t::dump(std::ostream& out) const
if (ptr) ptr->dump(out, 0);
}
+bool merged_expr_t::check_for_single_identifier(const string& expr)
+{
+ bool single_identifier = true;
+ for (const char * p = expr.c_str(); *p; ++p)
+ if (! std::isalnum(*p) || *p == '_') {
+ single_identifier = false;
+ break;
+ }
+
+ if (single_identifier) {
+ set_base_expr(expr);
+ exprs.clear();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void merged_expr_t::compile(scope_t& scope)
+{
+ if (exprs.empty()) {
+ parse(base_expr);
+ } else {
+ std::ostringstream buf;
+
+ buf << "__tmp_" << term << "=(" << term << "=(" << base_expr << ")";
+ foreach (const string& expr, exprs) {
+ if (merge_operator == ";")
+ buf << merge_operator << term << "=" << expr;
+ else
+ buf << merge_operator << "(" << expr << ")";
+ }
+ buf << ";" << term << ");__tmp_" << term;
+
+ DEBUG("expr.merged.compile", "Compiled expr: " << buf.str());
+ parse(buf.str());
+ }
+
+ expr_t::compile(scope);
+}
+
+expr_t::ptr_op_t as_expr(const value_t& val)
+{
+ VERIFY(val.is_any());
+ return val.as_any<expr_t::ptr_op_t>();
+}
+
+void set_expr(value_t& val, expr_t::ptr_op_t op)
+{
+ val.set_any(op);
+}
+
+value_t expr_value(expr_t::ptr_op_t op)
+{
+ value_t temp;
+ temp.set_any(op);
+ return temp;
+}
+
value_t source_command(call_scope_t& args)
{
std::istream * in = NULL;
diff --git a/src/expr.h b/src/expr.h
index c4cd5dc5..645b5cf9 100644
--- a/src/expr.h
+++ b/src/expr.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -58,53 +58,33 @@ public:
typedef intrusive_ptr<op_t> ptr_op_t;
typedef intrusive_ptr<const op_t> const_ptr_op_t;
+ enum check_expr_kind_t {
+ EXPR_GENERAL,
+ EXPR_ASSERTION,
+ EXPR_CHECK
+ };
+
+ typedef std::pair<expr_t, check_expr_kind_t> check_expr_pair;
+ typedef std::list<check_expr_pair> check_expr_list;
+
protected:
ptr_op_t ptr;
public:
- expr_t() : base_type() {
- TRACE_CTOR(expr_t, "");
- }
- expr_t(const expr_t& other)
- : base_type(other), ptr(other.ptr) {
- TRACE_CTOR(expr_t, "copy");
- }
- expr_t(ptr_op_t _ptr, scope_t * _context = NULL)
- : base_type(_context), ptr(_ptr) {
- TRACE_CTOR(expr_t, "const ptr_op_t&, scope_t *");
- }
+ expr_t();
+ expr_t(const expr_t& other);
+ expr_t(ptr_op_t _ptr, scope_t * _context = NULL);
- expr_t(const string& _str, const parse_flags_t& flags = PARSE_DEFAULT)
- : base_type() {
- TRACE_CTOR(expr_t, "string, parse_flags_t");
- if (! _str.empty())
- parse(_str, flags);
- }
- expr_t(std::istream& in, const parse_flags_t& flags = PARSE_DEFAULT)
- : base_type() {
- TRACE_CTOR(expr_t, "std::istream&, parse_flags_t");
- parse(in, flags);
- }
+ expr_t(const string& _str, const parse_flags_t& flags = PARSE_DEFAULT);
+ expr_t(std::istream& in, const parse_flags_t& flags = PARSE_DEFAULT);
- virtual ~expr_t() {
- TRACE_DTOR(expr_t);
- }
+ virtual ~expr_t();
- expr_t& operator=(const expr_t& _expr) {
- if (this != &_expr) {
- base_type::operator=(_expr);
- ptr = _expr.ptr;
- }
- return *this;
- }
+ expr_t& operator=(const expr_t& _expr);
- virtual operator bool() const throw() {
- return ptr.get() != NULL;
- }
+ virtual operator bool() const throw();
- ptr_op_t get_op() throw() {
- return ptr;
- }
+ ptr_op_t get_op() throw();
void parse(const string& str, const parse_flags_t& flags = PARSE_DEFAULT) {
std::istringstream stream(str);
@@ -147,21 +127,73 @@ private:
inline bool is_expr(const value_t& val) {
return val.is_any() && val.as_any().type() == typeid(expr_t::ptr_op_t);
}
-inline expr_t::ptr_op_t as_expr(const value_t& val) {
- VERIFY(val.is_any());
- return val.as_any<expr_t::ptr_op_t>();
-}
-inline void set_expr(value_t& val, expr_t::ptr_op_t op) {
- val.set_any(op);
-}
-inline value_t expr_value(expr_t::ptr_op_t op) {
- value_t temp;
- temp.set_any(op);
- return temp;
-}
-class call_scope_t;
+expr_t::ptr_op_t as_expr(const value_t& val);
+void set_expr(value_t& val, expr_t::ptr_op_t op);
+value_t expr_value(expr_t::ptr_op_t op);
+
+// A merged expression allows one to set an expression term, "foo", and
+// a base expression, "bar", and then merge in later expressions that
+// utilize foo. For example:
+//
+// foo: bar
+// merge: foo * 10
+// merge: foo + 20
+//
+// When this expression is finally compiled, the base and merged
+// elements are written into this:
+//
+// __tmp=(foo=bar; foo=foo*10; foo=foo+20);__tmp
+//
+// This allows users to select flags like -O, -B or -I at any time, and
+// also combine flags such as -V and -A.
+
+class merged_expr_t : public expr_t
+{
+public:
+ string term;
+ string base_expr;
+ string merge_operator;
+ std::list<string> exprs;
+
+ merged_expr_t(const string& _term, const string& expr,
+ const string& merge_op = ";")
+ : expr_t(), term(_term), base_expr(expr), merge_operator(merge_op) {
+ TRACE_CTOR(merged_expr_t, "string, string, string");
+ }
+ virtual ~merged_expr_t() {
+ TRACE_DTOR(merged_expr_t);
+ }
+
+ void set_term(const string& _term) {
+ term = _term;
+ }
+ void set_base_expr(const string& expr) {
+ base_expr = expr;
+ }
+ void set_merge_operator(const string& merge_op) {
+ merge_operator = merge_op;
+ }
+
+ bool check_for_single_identifier(const string& expr);
+
+ void prepend(const string& expr) {
+ if (! check_for_single_identifier(expr))
+ exprs.push_front(expr);
+ }
+ void append(const string& expr) {
+ if (! check_for_single_identifier(expr))
+ exprs.push_back(expr);
+ }
+ void remove(const string& expr) {
+ exprs.remove(expr);
+ }
+
+ virtual void compile(scope_t& scope);
+};
+
+class call_scope_t;
value_t source_command(call_scope_t& scope);
} // namespace ledger
diff --git a/src/exprbase.h b/src/exprbase.h
index e0e2824f..bea25320 100644
--- a/src/exprbase.h
+++ b/src/exprbase.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -113,7 +113,7 @@ public:
return ! str.empty();
}
- virtual string text() {
+ virtual string text() const throw() {
return str;
}
void set_text(const string& txt) {
@@ -163,7 +163,7 @@ public:
}
#endif // defined(DEBUG_ON)
- DEBUG("expr.calc.when", "Compiling: " << str);
+ DEBUG("expr.compile", "Compiling: " << str);
compile(scope);
#if defined(DEBUG_ON)
@@ -174,7 +174,7 @@ public:
#endif // defined(DEBUG_ON)
}
- DEBUG("expr.calc.when", "Calculating: " << str);
+ DEBUG("expr.calc", "Calculating: " << str);
return real_calc(scope);
}
diff --git a/src/filters.cc b/src/filters.cc
index 331073eb..9589958c 100644
--- a/src/filters.cc
+++ b/src/filters.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -37,6 +37,7 @@
#include "report.h"
#include "compare.h"
#include "pool.h"
+#include "history.h"
namespace ledger {
@@ -262,6 +263,8 @@ void anonymize_posts::operator()(post_t& post)
xact.payee = to_hex(message_digest);
xact.note = none;
+ } else {
+ xact.journal = post.xact->journal;
}
std::list<string> account_names;
@@ -337,9 +340,10 @@ namespace {
const bool act_date_p = true,
const value_t& total = value_t(),
const bool direct_amount = false,
- const bool mark_visited = false)
+ const bool mark_visited = false,
+ const bool bidir_link = true)
{
- post_t& post = temps.create_post(*xact, account);
+ post_t& post = temps.create_post(*xact, account, bidir_link);
post.add_flags(ITEM_GENERATED);
// If the account for this post is all virtual, then report the post as
@@ -509,8 +513,8 @@ display_filter_posts::display_filter_posts(post_handler_ptr handler,
display_total_expr(report.HANDLER(display_total_).expr),
show_rounding(_show_rounding)
{
- TRACE_CTOR(display_filter_posts, "post_handler_ptr, report_t&, bool");
create_accounts();
+ TRACE_CTOR(display_filter_posts, "post_handler_ptr, report_t&, bool");
}
bool display_filter_posts::output_rounding(post_t& post)
@@ -519,7 +523,8 @@ bool display_filter_posts::output_rounding(post_t& post)
value_t new_display_total;
if (show_rounding) {
- new_display_total = display_total_expr.calc(bound_scope);
+ new_display_total = (display_total_expr.calc(bound_scope)
+ .strip_annotations(report.what_to_keep()));
DEBUG("filters.changed_value.rounding",
"rounding.new_display_total = " << new_display_total);
@@ -536,7 +541,8 @@ bool display_filter_posts::output_rounding(post_t& post)
return true;
}
- if (value_t repriced_amount = display_amount_expr.calc(bound_scope)) {
+ if (value_t repriced_amount = (display_amount_expr.calc(bound_scope)
+ .strip_annotations(report.what_to_keep()))) {
if (! last_display_total.is_null()) {
DEBUG("filters.changed_value.rounding",
"rounding.repriced_amount = " << repriced_amount);
@@ -561,7 +567,9 @@ bool display_filter_posts::output_rounding(post_t& post)
/* date= */ date_t(),
/* act_date_p= */ true,
/* total= */ precise_display_total,
- /* direct_amount= */ true);
+ /* direct_amount= */ true,
+ /* mark_visited= */ false,
+ /* bidir_link= */ false);
}
}
if (show_rounding)
@@ -590,13 +598,11 @@ changed_value_posts::changed_value_posts
report.HANDLER(display_total_).expr),
display_total_expr(report.HANDLER(display_total_).expr),
changed_values_only(report.HANDLED(revalued_only)),
+ historical_prices_only(report.HANDLED(historical)),
for_accounts_report(_for_accounts_report),
show_unrealized(_show_unrealized), last_post(NULL),
display_filter(_display_filter)
{
- TRACE_CTOR(changed_value_posts,
- "post_handler_ptr, report_t&, bool, bool, display_filter_posts *");
-
string gains_equity_account_name;
if (report.HANDLED(unrealized_gains_))
gains_equity_account_name = report.HANDLER(unrealized_gains_).str();
@@ -616,14 +622,19 @@ changed_value_posts::changed_value_posts
losses_equity_account->add_flags(ACCOUNT_GENERATED);
create_accounts();
+
+ TRACE_CTOR(changed_value_posts,
+ "post_handler_ptr, report_t&, bool, bool, display_filter_posts *");
}
void changed_value_posts::flush()
{
if (last_post && last_post->date() <= report.terminus.date()) {
- if (! for_accounts_report)
- output_intermediate_prices(*last_post, report.terminus.date());
- output_revaluation(*last_post, report.terminus.date());
+ if (! historical_prices_only) {
+ if (! for_accounts_report)
+ output_intermediate_prices(*last_post, report.terminus.date());
+ output_revaluation(*last_post, report.terminus.date());
+ }
last_post = NULL;
}
item_handler<post_t>::flush();
@@ -688,6 +699,19 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
}
}
+namespace {
+ struct insert_prices_in_map {
+ price_map_t& all_prices;
+
+ insert_prices_in_map(price_map_t& _all_prices)
+ : all_prices(_all_prices) {}
+
+ void operator()(datetime_t& date, const amount_t& price) {
+ all_prices.insert(price_map_t::value_type(date, price));
+ }
+ };
+}
+
void changed_value_posts::output_intermediate_prices(post_t& post,
const date_t& current)
{
@@ -754,36 +778,19 @@ void changed_value_posts::output_intermediate_prices(post_t& post,
// fall through...
case value_t::BALANCE: {
- commodity_t::history_map all_prices;
+ price_map_t all_prices;
foreach (const balance_t::amounts_map::value_type& amt_comm,
- display_total.as_balance().amounts) {
- if (optional<commodity_t::varied_history_t&> hist =
- amt_comm.first->varied_history()) {
- foreach
- (const commodity_t::history_by_commodity_map::value_type& comm_hist,
- hist->histories) {
- foreach (const commodity_t::history_map::value_type& price,
- comm_hist.second.prices) {
- if (price.first.date() > post.value_date() &&
- price.first.date() < current) {
- DEBUG("filters.revalued", post.value_date() << " < "
- << price.first.date() << " < " << current);
- DEBUG("filters.revalued", "inserting "
- << price.second << " at " << price.first.date());
- all_prices.insert(price);
- }
- }
- }
- }
- }
+ display_total.as_balance().amounts)
+ amt_comm.first->map_prices(insert_prices_in_map(all_prices),
+ datetime_t(current),
+ datetime_t(post.value_date()), true);
// Choose the last price from each day as the price to use
typedef std::map<const date_t, bool> date_map;
date_map pricing_dates;
- BOOST_REVERSE_FOREACH
- (const commodity_t::history_map::value_type& price, all_prices) {
+ BOOST_REVERSE_FOREACH(const price_map_t::value_type& price, all_prices) {
// This insert will fail if a later price has already been inserted
// for that date.
DEBUG("filters.revalued",
@@ -808,7 +815,7 @@ void changed_value_posts::output_intermediate_prices(post_t& post,
void changed_value_posts::operator()(post_t& post)
{
if (last_post) {
- if (! for_accounts_report)
+ if (! for_accounts_report && ! historical_prices_only)
output_intermediate_prices(*last_post, post.value_date());
output_revaluation(*last_post, post.value_date());
}
@@ -836,10 +843,17 @@ void subtotal_posts::report_subtotal(const char * spec_fmt,
foreach (post_t * post, component_posts) {
date_t date = post->date();
date_t value_date = post->value_date();
+#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
if (! range_start || date < *range_start)
range_start = date;
if (! range_finish || value_date > *range_finish)
range_finish = value_date;
+#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7
+#pragma GCC diagnostic pop
+#endif
}
}
component_posts.clear();
@@ -879,15 +893,39 @@ void subtotal_posts::operator()(post_t& post)
account_t * acct = post.reported_account();
assert(acct);
+#if 0
+ // jww (2012-04-06): The problem with doing this early is that
+ // fn_display_amount will recalculate this again. For example, if you
+ // use --invert, it will invert both here and in the display amount,
+ // effectively negating it.
+ bind_scope_t bound_scope(*amount_expr.get_context(), post);
+ value_t amount(amount_expr.calc(bound_scope));
+#else
+ value_t amount(post.amount);
+#endif
+
+ post.xdata().compound_value = amount;
+ post.xdata().add_flags(POST_EXT_COMPOUND);
+
values_map::iterator i = values.find(acct->fullname());
if (i == values.end()) {
- value_t temp;
- post.add_to_value(temp, amount_expr);
- std::pair<values_map::iterator, bool> result
- = values.insert(values_pair(acct->fullname(), acct_value_t(acct, temp)));
+#if defined(DEBUG_ON)
+ std::pair<values_map::iterator, bool> result =
+#endif
+ values.insert(values_pair
+ (acct->fullname(),
+ acct_value_t(acct, amount, post.has_flags(POST_VIRTUAL),
+ post.has_flags(POST_MUST_BALANCE))));
+#if defined(DEBUG_ON)
assert(result.second);
+#endif
} else {
- post.add_to_value((*i).second.value, amount_expr);
+ if (post.has_flags(POST_VIRTUAL) != (*i).second.is_virtual)
+ throw_(std::logic_error,
+ _("'equity' cannot accept virtual and "
+ "non-virtual postings to the same account"));
+
+ add_or_set_value((*i).second.value, amount);
}
// If the account for this post is all virtual, mark it as
@@ -904,55 +942,139 @@ void subtotal_posts::operator()(post_t& post)
void interval_posts::report_subtotal(const date_interval_t& ival)
{
- if (last_post && ival) {
- if (exact_periods)
- subtotal_posts::report_subtotal();
- else
- subtotal_posts::report_subtotal(NULL, ival);
- }
+ if (exact_periods)
+ subtotal_posts::report_subtotal();
+ else
+ subtotal_posts::report_subtotal(NULL, ival);
+}
- last_post = NULL;
+namespace {
+ struct sort_posts_by_date {
+ bool operator()(post_t * left, post_t * right) const {
+ return left->date() < right->date();
+ }
+ };
}
void interval_posts::operator()(post_t& post)
{
- if (! interval.find_period(post.date()))
- return;
+ // If there is a duration (such as weekly), we must generate the
+ // report in two passes. Otherwise, we only have to check whether the
+ // post falls within the reporting period.
if (interval.duration) {
- if (last_interval && interval != last_interval) {
- report_subtotal(last_interval);
+ all_posts.push_back(&post);
+ }
+ else if (interval.find_period(post.date())) {
+ item_handler<post_t>::operator()(post);
+ }
+}
- if (generate_empty_posts) {
- for (++last_interval; interval != last_interval; ++last_interval) {
- // Generate a null posting, so the intervening periods can be
- // seen when -E is used, or if the calculated amount ends up being
- // non-zero
- xact_t& null_xact = temps.create_xact();
- null_xact._date = last_interval.inclusive_end();
+void interval_posts::flush()
+{
+ if (! interval.duration) {
+ item_handler<post_t>::flush();
+ return;
+ }
- post_t& null_post = temps.create_post(null_xact, empty_account);
- null_post.add_flags(POST_CALCULATED);
- null_post.amount = 0L;
+ // Sort all the postings we saw by date ascending
+ std::stable_sort(all_posts.begin(), all_posts.end(),
+ sort_posts_by_date());
- last_post = &null_post;
- subtotal_posts::operator()(null_post);
+ // Determine the beginning interval by using the earliest post
+ if (! interval.find_period(all_posts.front()->date()))
+ throw_(std::logic_error, _("Failed to find period for interval report"));
- report_subtotal(last_interval);
- }
- assert(interval == last_interval);
- } else {
- last_interval = interval;
- }
+ // Walk the interval forward reporting all posts within each one
+ // before moving on, until we reach the end of all_posts
+ bool saw_posts = false;
+ for (std::deque<post_t *>::iterator i = all_posts.begin();
+ i != all_posts.end(); ) {
+ post_t * post(*i);
+
+ DEBUG("filters.interval",
+ "Considering post " << post->date() << " = " << post->amount);
+#if defined(DEBUG_ON)
+ DEBUG("filters.interval", "interval is:");
+ debug_interval(interval);
+#endif
+ assert(! interval.finish || post->date() < *interval.finish);
+
+ if (interval.within_period(post->date())) {
+ DEBUG("filters.interval", "Calling subtotal_posts::operator()");
+ subtotal_posts::operator()(*post);
+ ++i;
+ saw_posts = true;
} else {
- last_interval = interval;
+ if (saw_posts) {
+ DEBUG("filters.interval",
+ "Calling subtotal_posts::report_subtotal()");
+ report_subtotal(interval);
+ saw_posts = false;
+ }
+ else if (generate_empty_posts) {
+ // Generate a null posting, so the intervening periods can be
+ // seen when -E is used, or if the calculated amount ends up
+ // being non-zero
+ xact_t& null_xact = temps.create_xact();
+ null_xact._date = interval.inclusive_end();
+
+ post_t& null_post = temps.create_post(null_xact, empty_account);
+ null_post.add_flags(POST_CALCULATED);
+ null_post.amount = 0L;
+
+ subtotal_posts::operator()(null_post);
+ report_subtotal(interval);
+ }
+
+ DEBUG("filters.interval", "Advancing interval");
+ ++interval;
}
- subtotal_posts::operator()(post);
- } else {
- item_handler<post_t>::operator()(post);
}
- last_post = &post;
+ // If the last postings weren't reported, do so now.
+ if (saw_posts) {
+ DEBUG("filters.interval",
+ "Calling subtotal_posts::report_subtotal() at end");
+ report_subtotal(interval);
+ }
+
+ // Tell our parent class to flush
+ subtotal_posts::flush();
+}
+
+namespace {
+ struct create_post_from_amount
+ {
+ post_handler_ptr handler;
+ xact_t& xact;
+ account_t& balance_account;
+ temporaries_t& temps;
+
+ explicit create_post_from_amount(post_handler_ptr _handler,
+ xact_t& _xact,
+ account_t& _balance_account,
+ temporaries_t& _temps)
+ : handler(_handler), xact(_xact),
+ balance_account(_balance_account), temps(_temps) {
+ TRACE_CTOR(create_post_from_amount,
+ "post_handler_ptr, xact_t&, account_t&, temporaries_t&");
+ }
+ create_post_from_amount(const create_post_from_amount& other)
+ : handler(other.handler), xact(other.xact),
+ balance_account(other.balance_account), temps(other.temps) {
+ TRACE_CTOR(create_post_from_amount, "copy");
+ }
+ ~create_post_from_amount() throw() {
+ TRACE_DTOR(create_post_from_amount);
+ }
+
+ void operator()(const amount_t& amount) {
+ post_t& balance_post = temps.create_post(xact, &balance_account);
+ balance_post.amount = - amount;
+ (*handler)(balance_post);
+ }
+ };
}
void posts_as_equity::report_subtotal()
@@ -971,40 +1093,47 @@ void posts_as_equity::report_subtotal()
value_t total = 0L;
foreach (values_map::value_type& pair, values) {
- if (pair.second.value.is_balance()) {
- foreach (const balance_t::amounts_map::value_type& amount_pair,
- pair.second.value.as_balance().amounts)
- handle_value(/* value= */ amount_pair.second,
+ value_t value(pair.second.value.strip_annotations(report.what_to_keep()));
+ if (! value.is_zero()) {
+ if (value.is_balance()) {
+ foreach (const balance_t::amounts_map::value_type& amount_pair,
+ value.as_balance_lval().amounts) {
+ if (! amount_pair.second.is_zero())
+ handle_value(/* value= */ amount_pair.second,
+ /* account= */ pair.second.account,
+ /* xact= */ &xact,
+ /* temps= */ temps,
+ /* handler= */ handler,
+ /* date= */ finish,
+ /* act_date_p= */ false);
+ }
+ } else {
+ handle_value(/* value= */ value.to_amount(),
/* account= */ pair.second.account,
/* xact= */ &xact,
/* temps= */ temps,
/* handler= */ handler,
/* date= */ finish,
/* act_date_p= */ false);
- } else {
- handle_value(/* value= */ pair.second.value,
- /* account= */ pair.second.account,
- /* xact= */ &xact,
- /* temps= */ temps,
- /* handler= */ handler,
- /* date= */ finish,
- /* act_date_p= */ false);
+ }
}
- total += pair.second.value;
+
+ if (! pair.second.is_virtual || pair.second.must_balance)
+ total += value;
}
values.clear();
- if (total.is_balance()) {
- foreach (const balance_t::amounts_map::value_type& pair,
- total.as_balance().amounts) {
- post_t& balance_post = temps.create_post(xact, balance_account);
- balance_post.amount = - pair.second;
- (*handler)(balance_post);
- }
- } else {
- post_t& balance_post = temps.create_post(xact, balance_account);
- balance_post.amount = - total.to_amount();
- (*handler)(balance_post);
+ // This last part isn't really needed, since an Equity:Opening
+ // Balances posting with a null amount will automatically balance with
+ // all the other postings generated. But it does make the full
+ // balancing amount clearer to the user.
+ if (! total.is_zero()) {
+ create_post_from_amount post_creator(handler, xact,
+ *balance_account, temps);
+ if (total.is_balance())
+ total.as_balance_lval().map_sorted_amounts(post_creator);
+ else
+ post_creator(total.to_amount());
}
}
@@ -1346,8 +1475,6 @@ inject_posts::inject_posts(post_handler_ptr handler,
account_t * master)
: item_handler<post_t>(handler)
{
- TRACE_CTOR(inject_posts, "post_handler_ptr, string, account_t *");
-
scoped_array<char> buf(new char[tag_list.length() + 1]);
std::strcpy(buf.get(), tag_list.c_str());
@@ -1364,6 +1491,8 @@ inject_posts::inject_posts(post_handler_ptr handler,
tags_list.push_back
(tags_list_pair(q, tag_mapping_pair(account, tag_injected_set())));
}
+
+ TRACE_CTOR(inject_posts, "post_handler_ptr, string, account_t *");
}
void inject_posts::operator()(post_t& post)
diff --git a/src/filters.h b/src/filters.h
index e7a2eefa..1dad8852 100644
--- a/src/filters.h
+++ b/src/filters.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -65,18 +65,18 @@ protected:
value_to_posts_map posts_map;
post_handler_ptr post_chain;
report_t& report;
- expr_t group_by_expr;
+ expr_t& group_by_expr;
custom_flusher_t preflush_func;
optional<custom_flusher_t> postflush_func;
public:
post_splitter(post_handler_ptr _post_chain,
report_t& _report,
- expr_t _group_by_expr)
+ expr_t& _group_by_expr)
: post_chain(_post_chain), report(_report),
group_by_expr(_group_by_expr) {
+ preflush_func = bind(&post_splitter::print_title, this, _1);
TRACE_CTOR(post_splitter, "scope_t&, post_handler_ptr, expr_t");
- preflush_func = bind(&post_splitter::print_title, this, _1);
}
virtual ~post_splitter() {
TRACE_DTOR(post_splitter);
@@ -154,9 +154,7 @@ class pass_down_posts : public item_handler<post_t>
public:
pass_down_posts(post_handler_ptr handler, Iterator& iter)
: item_handler<post_t>(handler) {
- TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator");
-
- while (post_t * post = *iter++) {
+ while (post_t * post = *iter) {
try {
item_handler<post_t>::operator()(*post);
}
@@ -164,9 +162,12 @@ public:
add_error_context(item_context(*post, _("While handling posting")));
throw;
}
+ iter.increment();
}
item_handler<post_t>::flush();
+
+ TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator");
}
virtual ~pass_down_posts() {
@@ -372,6 +373,7 @@ public:
}
virtual ~anonymize_posts() {
TRACE_DTOR(anonymize_posts);
+ handler.reset();
}
void render_commodity(amount_t& amt);
@@ -446,11 +448,12 @@ public:
only_predicate(_only_predicate), count(0),
last_xact(NULL), last_post(NULL),
only_collapse_if_zero(_only_collapse_if_zero), report(_report) {
- TRACE_CTOR(collapse_posts, "post_handler_ptr, ...");
create_accounts();
+ TRACE_CTOR(collapse_posts, "post_handler_ptr, ...");
}
virtual ~collapse_posts() {
TRACE_DTOR(collapse_posts);
+ handler.reset();
}
void create_accounts() {
@@ -496,8 +499,7 @@ public:
const bool _also_matching = false)
: item_handler<post_t>(handler),
also_matching(_also_matching) {
- TRACE_CTOR(related_posts,
- "post_handler_ptr, const bool");
+ TRACE_CTOR(related_posts, "post_handler_ptr, const bool");
}
virtual ~related_posts() throw() {
TRACE_DTOR(related_posts);
@@ -521,8 +523,8 @@ class display_filter_posts : public item_handler<post_t>
// later in the chain.
report_t& report;
- expr_t display_amount_expr;
- expr_t display_total_expr;
+ expr_t& display_amount_expr;
+ expr_t& display_total_expr;
bool show_rounding;
value_t last_display_total;
temporaries_t temps;
@@ -539,6 +541,7 @@ public:
virtual ~display_filter_posts() {
TRACE_DTOR(display_filter_posts);
+ handler.reset();
}
void create_accounts() {
@@ -569,9 +572,10 @@ class changed_value_posts : public item_handler<post_t>
// later in the chain.
report_t& report;
- expr_t total_expr;
- expr_t display_total_expr;
+ expr_t& total_expr;
+ expr_t& display_total_expr;
bool changed_values_only;
+ bool historical_prices_only;
bool for_accounts_report;
bool show_unrealized;
post_t * last_post;
@@ -595,6 +599,7 @@ public:
virtual ~changed_value_posts() {
TRACE_DTOR(changed_value_posts);
+ handler.reset();
}
void create_accounts() {
@@ -635,15 +640,22 @@ protected:
public:
account_t * account;
value_t value;
+ bool is_virtual;
+ bool must_balance;
- acct_value_t(account_t * a) : account(a) {
- TRACE_CTOR(acct_value_t, "account_t *");
+ acct_value_t(account_t * a, bool _is_virtual = false,
+ bool _must_balance = false)
+ : account(a), is_virtual(_is_virtual), must_balance(_must_balance) {
+ TRACE_CTOR(acct_value_t, "account_t *, bool, bool");
}
- acct_value_t(account_t * a, value_t& v) : account(a), value(v) {
- TRACE_CTOR(acct_value_t, "account_t *, value_t&");
+ acct_value_t(account_t * a, value_t& v, bool _is_virtual = false,
+ bool _must_balance = false)
+ : account(a), value(v), is_virtual(_is_virtual),
+ must_balance(_must_balance) {
+ TRACE_CTOR(acct_value_t, "account_t *, value_t&, bool, bool");
}
acct_value_t(const acct_value_t& av)
- : account(av.account), value(av.value) {
+ : account(av.account), value(av.value), is_virtual(av.is_virtual) {
TRACE_CTOR(acct_value_t, "copy");
}
~acct_value_t() throw() {
@@ -655,11 +667,11 @@ protected:
typedef std::pair<string, acct_value_t> values_pair;
protected:
- expr_t& amount_expr;
- values_map values;
- optional<string> date_format;
- temporaries_t temps;
- std::list<post_t *> component_posts;
+ expr_t& amount_expr;
+ values_map values;
+ optional<string> date_format;
+ temporaries_t temps;
+ std::deque<post_t *> component_posts;
public:
subtotal_posts(post_handler_ptr handler, expr_t& _amount_expr,
@@ -671,6 +683,7 @@ public:
}
virtual ~subtotal_posts() {
TRACE_DTOR(subtotal_posts);
+ handler.reset();
}
void report_subtotal(const char * spec_fmt = NULL,
@@ -697,12 +710,12 @@ class interval_posts : public subtotal_posts
{
date_interval_t start_interval;
date_interval_t interval;
- date_interval_t last_interval;
- post_t * last_post;
account_t * empty_account;
bool exact_periods;
bool generate_empty_posts;
+ std::deque<post_t *> all_posts;
+
interval_posts();
public:
@@ -713,12 +726,11 @@ public:
bool _exact_periods = false,
bool _generate_empty_posts = false)
: subtotal_posts(_handler, amount_expr), start_interval(_interval),
- interval(start_interval), last_post(NULL),
- exact_periods(_exact_periods),
+ interval(start_interval), exact_periods(_exact_periods),
generate_empty_posts(_generate_empty_posts) {
+ create_accounts();
TRACE_CTOR(interval_posts,
"post_handler_ptr, expr_t&, date_interval_t, bool, bool");
- create_accounts();
}
virtual ~interval_posts() throw() {
TRACE_DTOR(interval_posts);
@@ -728,21 +740,27 @@ public:
empty_account = &temps.create_account(_("<None>"));
}
- void report_subtotal(const date_interval_t& interval);
+ void report_subtotal(const date_interval_t& ival);
- virtual void flush() {
- if (last_post && interval.duration) {
- if (interval.is_valid())
- report_subtotal(interval);
- subtotal_posts::flush();
- }
+#if defined(DEBUG_ON)
+ void debug_interval(const date_interval_t& ival) {
+ if (ival.start)
+ DEBUG("filters.interval", "start = " << *ival.start);
+ else
+ DEBUG("filters.interval", "no start");
+
+ if (ival.finish)
+ DEBUG("filters.interval", "finish = " << *ival.finish);
+ else
+ DEBUG("filters.interval", "no finish");
}
+#endif
+
virtual void operator()(post_t& post);
+ virtual void flush();
virtual void clear() {
- interval = start_interval;
- last_interval = date_interval_t();
- last_post = NULL;
+ interval = start_interval;
subtotal_posts::clear();
create_accounts();
@@ -751,6 +769,7 @@ public:
class posts_as_equity : public subtotal_posts
{
+ report_t& report;
post_t * last_post;
account_t * equity_account;
account_t * balance_account;
@@ -758,10 +777,11 @@ class posts_as_equity : public subtotal_posts
posts_as_equity();
public:
- posts_as_equity(post_handler_ptr _handler, expr_t& amount_expr)
- : subtotal_posts(_handler, amount_expr) {
- TRACE_CTOR(posts_as_equity, "post_handler_ptr, expr_t&");
+ posts_as_equity(post_handler_ptr _handler, report_t& _report,
+ expr_t& amount_expr)
+ : subtotal_posts(_handler, amount_expr), report(_report) {
create_accounts();
+ TRACE_CTOR(posts_as_equity, "post_handler_ptr, expr_t&");
}
virtual ~posts_as_equity() throw() {
TRACE_DTOR(posts_as_equity);
@@ -844,6 +864,7 @@ public:
}
virtual ~transfer_details() {
TRACE_DTOR(transfer_details);
+ handler.reset();
}
virtual void operator()(post_t& post);
@@ -903,6 +924,7 @@ public:
virtual ~generate_posts() {
TRACE_DTOR(generate_posts);
+ handler.reset();
}
void add_period_xacts(period_xacts_list& period_xacts);
@@ -990,6 +1012,7 @@ class inject_posts : public item_handler<post_t>
virtual ~inject_posts() throw() {
TRACE_DTOR(inject_posts);
+ handler.reset();
}
virtual void operator()(post_t& post);
diff --git a/src/flags.h b/src/flags.h
index 09b7eec4..e2046c08 100644
--- a/src/flags.h
+++ b/src/flags.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/format.cc b/src/format.cc
index 93e7ea08..8c3cbc14 100644
--- a/src/format.cc
+++ b/src/format.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -70,6 +70,27 @@ void format_t::element_t::dump(std::ostream& out) const
}
namespace {
+ struct format_mapping_t {
+ char letter;
+ const char * expr;
+ } single_letter_mappings[] = {
+ { 'd', "date" },
+ { 'S', "filename" },
+ { 'B', "beg_pos" },
+ { 'b', "beg_line" },
+ { 'E', "end_pos" },
+ { 'e', "end_line" },
+ { 'X', "cleared" },
+ { 'Y', "xact.cleared" },
+ { 'C', "code" },
+ { 'P', "payee" },
+ { 'a', "account.name" },
+ { 'A', "account" },
+ { 't', "justify(scrub(display_amount), $min, $max, $left, color)" },
+ { 'T', "justify(scrub(display_total), $min, $max, $left, color)" },
+ { 'N', "note" },
+ };
+
expr_t parse_single_expression(const char *& p, bool single_expr = true)
{
string temp(p);
@@ -92,12 +113,19 @@ namespace {
}
return expr;
}
+
+ inline expr_t::ptr_op_t ident_node(const string& ident)
+ {
+ expr_t::ptr_op_t node(new expr_t::op_t(expr_t::op_t::IDENT));
+ node->set_ident(ident);
+ return node;
+ }
}
format_t::element_t * format_t::parse_elements(const string& fmt,
const optional<format_t&>& tmpl)
{
- std::auto_ptr<element_t> result;
+ unique_ptr<element_t> result;
element_t * current = NULL;
@@ -172,122 +200,170 @@ format_t::element_t * format_t::parse_elements(const string& fmt,
current->min_width = current->max_width;
}
- switch (*p) {
- case '%':
- current->type = element_t::STRING;
- current->data = string("%");
- break;
+ if (std::isalpha(*p)) {
+ bool found = false;
+ for (std::size_t i = 0; i < (sizeof(single_letter_mappings) /
+ sizeof(format_mapping_t)); i++) {
+ if (*p == single_letter_mappings[i].letter) {
+ std::ostringstream expr;
+ for (const char * ptr = single_letter_mappings[i].expr; *ptr; ){
+ if (*ptr == '$') {
+ const char * beg = ++ptr;
+ while (*ptr && std::isalpha(*ptr))
+ ++ptr;
+ string::size_type klen = static_cast<string::size_type>(ptr - beg);
+ string keyword(beg, 0, klen);
+ if (keyword == "min")
+ expr << (current->min_width > 0 ?
+ static_cast<int>(current->min_width) : -1);
+ else if (keyword == "max")
+ expr << (current->max_width > 0 ?
+ static_cast<int>(current->max_width) : -1);
+ else if (keyword == "left")
+ expr << (current->has_flags(ELEMENT_ALIGN_LEFT) ? "false" : "true");
+#if defined(DEBUG_ON)
+ else
+ assert("Unrecognized format substitution keyword" == NULL);
+#endif
+ } else {
+ expr << *ptr++;
+ }
+ }
+ current->type = element_t::EXPR;
+ current->data = expr_t(expr.str());
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ throw_(format_error, _("Unrecognized formatting character: %1") << *p);
+ } else {
+ switch (*p) {
+ case '%':
+ current->type = element_t::STRING;
+ current->data = string("%");
+ break;
- case '$': {
- if (! tmpl)
- throw_(format_error, _("Prior field reference, but no template"));
+ case '$': {
+ if (! tmpl)
+ throw_(format_error, _("Prior field reference, but no template"));
- p++;
- if (*p == '0' || (! std::isdigit(*p) &&
- *p != 'A' && *p != 'B' && *p != 'C' &&
- *p != 'D' && *p != 'E' && *p != 'F'))
- throw_(format_error, _("%$ field reference must be a digit from 1-9"));
+ p++;
+ if (*p == '0' || (! std::isdigit(*p) &&
+ *p != 'A' && *p != 'B' && *p != 'C' &&
+ *p != 'D' && *p != 'E' && *p != 'F'))
+ throw_(format_error, _("%$ field reference must be a digit from 1-9"));
- int index = std::isdigit(*p) ? *p - '0' : (*p - 'A' + 10);
- element_t * tmpl_elem = tmpl->elements.get();
+ int index = std::isdigit(*p) ? *p - '0' : (*p - 'A' + 10);
+ element_t * tmpl_elem = tmpl->elements.get();
- for (int i = 1; i < index && tmpl_elem; i++) {
- tmpl_elem = tmpl_elem->next.get();
- while (tmpl_elem && tmpl_elem->type != element_t::EXPR)
+ for (int i = 1; i < index && tmpl_elem; i++) {
tmpl_elem = tmpl_elem->next.get();
- }
+ while (tmpl_elem && tmpl_elem->type != element_t::EXPR)
+ tmpl_elem = tmpl_elem->next.get();
+ }
- if (! tmpl_elem)
- throw_(format_error, _("%$ reference to a non-existent prior field"));
+ if (! tmpl_elem)
+ throw_(format_error, _("%$ reference to a non-existent prior field"));
- *current = *tmpl_elem;
- break;
- }
+ *current = *tmpl_elem;
+ break;
+ }
- case '(':
- case '{': {
- bool format_amount = *p == '{';
- if (format_amount) p++;
+ case '(':
+ case '{': {
+ bool format_amount = *p == '{';
- current->type = element_t::EXPR;
- current->data = parse_single_expression(p, ! format_amount);
+ current->type = element_t::EXPR;
+ current->data = parse_single_expression(p);
- // Wrap the subexpression in calls to justify and scrub
- if (format_amount) {
- if (! *p || *(p + 1) != '}')
- throw_(format_error, _("Expected closing brace"));
- else
- p++;
+ // Wrap the subexpression in calls to justify and scrub
+ if (! format_amount)
+ break;
expr_t::ptr_op_t op = boost::get<expr_t>(current->data).get_op();
- expr_t::ptr_op_t amount_op;
- expr_t::ptr_op_t colorize_op;
- if (op->kind == expr_t::op_t::O_CONS) {
- amount_op = op->left();
- colorize_op = op->right();
- } else {
- amount_op = op;
+ expr_t::ptr_op_t call2_node(new expr_t::op_t(expr_t::op_t::O_CALL));
+ {
+ call2_node->set_left(ident_node("justify"));
+
+ {
+ expr_t::ptr_op_t args3_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ {
+ {
+ expr_t::ptr_op_t call1_node(new expr_t::op_t(expr_t::op_t::O_CALL));
+ {
+ call1_node->set_left(ident_node("scrub"));
+ call1_node->set_right(op->kind == expr_t::op_t::O_CONS ? op->left() : op);
+ }
+
+ args3_node->set_left(call1_node);
+ }
+
+ expr_t::ptr_op_t args2_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ {
+ {
+ expr_t::ptr_op_t arg1_node(new expr_t::op_t(expr_t::op_t::VALUE));
+ arg1_node->set_value(current->min_width > 0 ?
+ long(current->min_width) : -1);
+
+ args2_node->set_left(arg1_node);
+ }
+
+ {
+ expr_t::ptr_op_t args1_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ {
+ {
+ expr_t::ptr_op_t arg2_node(new expr_t::op_t(expr_t::op_t::VALUE));
+ arg2_node->set_value(current->max_width > 0 ?
+ long(current->max_width) : -1);
+
+ args1_node->set_left(arg2_node);
+ }
+
+ {
+ expr_t::ptr_op_t arg3_node(new expr_t::op_t(expr_t::op_t::VALUE));
+ arg3_node->set_value(! current->has_flags(ELEMENT_ALIGN_LEFT));
+
+ args1_node->set_right(arg3_node);
+ }
+ }
+
+ args2_node->set_right(args1_node);
+ }
+
+ args3_node->set_right(args2_node);
+ }
+ }
+
+ call2_node->set_right(args3_node);
+ }
}
- expr_t::ptr_op_t scrub_node(new expr_t::op_t(expr_t::op_t::IDENT));
- scrub_node->set_ident("scrub");
-
- expr_t::ptr_op_t call1_node(new expr_t::op_t(expr_t::op_t::O_CALL));
- call1_node->set_left(scrub_node);
- call1_node->set_right(amount_op);
-
- expr_t::ptr_op_t arg1_node(new expr_t::op_t(expr_t::op_t::VALUE));
- expr_t::ptr_op_t arg2_node(new expr_t::op_t(expr_t::op_t::VALUE));
- expr_t::ptr_op_t arg3_node(new expr_t::op_t(expr_t::op_t::VALUE));
-
- arg1_node->set_value(current->min_width > 0 ?
- long(current->min_width) : -1);
- arg2_node->set_value(current->max_width > 0 ?
- long(current->max_width) : -1);
- arg3_node->set_value(! current->has_flags(ELEMENT_ALIGN_LEFT));
-
current->min_width = 0;
current->max_width = 0;
- expr_t::ptr_op_t args1_node(new expr_t::op_t(expr_t::op_t::O_CONS));
- args1_node->set_left(arg2_node);
- args1_node->set_right(arg3_node);
-
- expr_t::ptr_op_t args2_node(new expr_t::op_t(expr_t::op_t::O_CONS));
- args2_node->set_left(arg1_node);
- args2_node->set_right(args1_node);
-
- expr_t::ptr_op_t args3_node(new expr_t::op_t(expr_t::op_t::O_CONS));
- args3_node->set_left(call1_node);
- args3_node->set_right(args2_node);
-
- expr_t::ptr_op_t seq1_node(new expr_t::op_t(expr_t::op_t::O_SEQ));
- seq1_node->set_left(args3_node);
-
- expr_t::ptr_op_t justify_node(new expr_t::op_t(expr_t::op_t::IDENT));
- justify_node->set_ident("justify");
-
- expr_t::ptr_op_t call2_node(new expr_t::op_t(expr_t::op_t::O_CALL));
- call2_node->set_left(justify_node);
- call2_node->set_right(seq1_node);
-
string prev_expr = boost::get<expr_t>(current->data).text();
- if (colorize_op) {
- expr_t::ptr_op_t ansify_if_node(new expr_t::op_t(expr_t::op_t::IDENT));
- ansify_if_node->set_ident("ansify_if");
-
- expr_t::ptr_op_t args4_node(new expr_t::op_t(expr_t::op_t::O_CONS));
- args4_node->set_left(call2_node);
- args4_node->set_right(colorize_op);
-
- expr_t::ptr_op_t seq2_node(new expr_t::op_t(expr_t::op_t::O_SEQ));
- seq2_node->set_left(args4_node);
+ expr_t::ptr_op_t colorize_op;
+ if (op->kind == expr_t::op_t::O_CONS)
+ colorize_op = op->has_right() ? op->right() : NULL;
+ if (colorize_op) {
expr_t::ptr_op_t call3_node(new expr_t::op_t(expr_t::op_t::O_CALL));
- call3_node->set_left(ansify_if_node);
- call3_node->set_right(seq2_node);
+ {
+ call3_node->set_left(ident_node("ansify_if"));
+
+ {
+ expr_t::ptr_op_t args4_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ {
+ args4_node->set_left(call2_node); // from above
+ args4_node->set_right(colorize_op);
+ }
+
+ call3_node->set_right(args4_node);
+ }
+ }
current->data = expr_t(call3_node);
} else {
@@ -295,12 +371,12 @@ format_t::element_t * format_t::parse_elements(const string& fmt,
}
boost::get<expr_t>(current->data).set_text(prev_expr);
+ break;
}
- break;
- }
- default:
- throw_(format_error, _("Unrecognized formatting character: %1") << *p);
+ default:
+ throw_(format_error, _("Unrecognized formatting character: %1") << *p);
+ }
}
}
@@ -342,7 +418,6 @@ string format_t::real_calc(scope_t& scope)
case element_t::EXPR: {
expr_t& expr(boost::get<expr_t>(elem->data));
try {
-
expr.compile(scope);
value_t value;
@@ -524,6 +599,7 @@ string format_t::truncate(const unistring& ustr,
index = 0;
#endif
std::size_t counter = lens.size();
+ std::list<string>::iterator x = parts.begin();
for (std::list<std::size_t>::iterator i = lens.begin();
i != lens.end();
i++) {
@@ -553,12 +629,21 @@ string format_t::truncate(const unistring& ustr,
if (adjust > 0) {
DEBUG("format.abbrev",
"Reducing segment " << ++index << " by " << adjust << " chars");
+ while (std::isspace((*x)[*i - adjust - 1]) && adjust < *i) {
+ DEBUG("format.abbrev",
+ "Segment ends in whitespace, adjusting down");
+ ++adjust;
+ }
(*i) -= adjust;
DEBUG("format.abbrev",
"Segment " << index << " is now " << *i << " chars wide");
- overflow -= adjust;
+ if (adjust > overflow)
+ overflow = 0;
+ else
+ overflow -= adjust;
DEBUG("format.abbrev", "Overflow is now " << overflow << " chars");
}
+ ++x;
}
DEBUG("format.abbrev",
"Overflow ending this time at " << overflow << " chars");
diff --git a/src/format.h b/src/format.h
index f30b8184..cc48bdda 100644
--- a/src/format.h
+++ b/src/format.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -51,11 +51,11 @@ class unistring;
DECLARE_EXCEPTION(format_error, std::runtime_error);
-class format_t : public expr_base_t<string>
+class format_t : public expr_base_t<string>, public noncopyable
{
typedef expr_base_t<string> base_type;
- struct element_t : public supports_flags<>
+ struct element_t : public supports_flags<>, public noncopyable
{
#define ELEMENT_ALIGN_LEFT 0x01
@@ -74,9 +74,6 @@ class format_t : public expr_base_t<string>
~element_t() throw() {
TRACE_DTOR(element_t);
}
- element_t(const element_t& elem) : supports_flags<>() {
- *this = elem;
- }
element_t& operator=(const element_t& elem) {
if (this != &elem) {
@@ -128,9 +125,9 @@ public:
}
format_t(const string& _str, scope_t * context = NULL)
: base_type(context) {
- TRACE_CTOR(format_t, "const string&");
if (! _str.empty())
parse_format(_str);
+ TRACE_CTOR(format_t, "const string&");
}
virtual ~format_t() {
TRACE_DTOR(format_t);
diff --git a/src/generate.cc b/src/generate.cc
index 185e23e7..8769c99c 100644
--- a/src/generate.cc
+++ b/src/generate.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -63,16 +63,15 @@ generate_posts_iterator::generate_posts_iterator
neg_number_range(-10000, -1), neg_number_gen(rnd_gen, neg_number_range),
pos_number_range(1, 10000), pos_number_gen(rnd_gen, pos_number_range)
{
- TRACE_CTOR(generate_posts_iterator, "bool");
-
std::ostringstream next_date_buf;
generate_date(next_date_buf);
next_date = parse_date(next_date_buf.str());
- std::ostringstream next_eff_date_buf;
- generate_date(next_eff_date_buf);
- next_eff_date = parse_date(next_eff_date_buf.str());
+ std::ostringstream next_aux_date_buf;
+ generate_date(next_aux_date_buf);
+ next_aux_date = parse_date(next_aux_date_buf.str());
+ TRACE_CTOR(generate_posts_iterator, "bool");
}
void generate_posts_iterator::generate_string(std::ostream& out, int len,
@@ -326,8 +325,8 @@ void generate_posts_iterator::generate_xact(std::ostream& out)
next_date += gregorian::days(six_gen());
if (truth_gen()) {
out << '=';
- out << format_date(next_eff_date, FMT_WRITTEN);
- next_eff_date += gregorian::days(six_gen());
+ out << format_date(next_aux_date, FMT_WRITTEN);
+ next_aux_date += gregorian::days(six_gen());
}
out << ' ';
@@ -360,9 +359,15 @@ void generate_posts_iterator::increment()
DEBUG("generate.post", "The post we intend to parse:\n" << buf.str());
- std::istringstream in(buf.str());
try {
- if (session.journal->parse(in, session) != 0) {
+ shared_ptr<std::istringstream> in(new std::istringstream(buf.str()));
+
+ parse_context_stack_t parsing_context;
+ parsing_context.push(in);
+ parsing_context.get_current().journal = session.journal.get();
+ parsing_context.get_current().scope = &session;
+
+ if (session.journal->read(parsing_context) != 0) {
VERIFY(session.journal->xacts.back()->valid());
posts.reset(*session.journal->xacts.back());
post = *posts++;
diff --git a/src/generate.h b/src/generate.h
index 47abcd94..1b22004b 100644
--- a/src/generate.h
+++ b/src/generate.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -57,7 +57,7 @@ class generate_posts_iterator
std::size_t quantity;
bool allow_invalid;
date_t next_date;
- date_t next_eff_date;
+ date_t next_aux_date;
mt19937 rnd_gen;
diff --git a/src/global.cc b/src/global.cc
index c0698376..6dc8d150 100644
--- a/src/global.cc
+++ b/src/global.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -47,8 +47,6 @@ static bool args_only = false;
global_scope_t::global_scope_t(char ** envp)
{
- TRACE_CTOR(global_scope_t, "");
-
epoch = CURRENT_TIME();
#if defined(HAVE_BOOST_PYTHON)
@@ -68,8 +66,9 @@ global_scope_t::global_scope_t(char ** envp)
// a GUI were calling into Ledger it would have one session object per
// open document, with a separate report_t object for each report it
// generated.
- report_stack.push_front(new report_t(session()));
+ report_stack.push_front(new report_t(*session_ptr));
scope_t::default_scope = &report();
+ scope_t::empty_scope = &empty_scope;
// Read the user's options, in the following order:
//
@@ -88,6 +87,8 @@ global_scope_t::global_scope_t(char ** envp)
} else {
session().HANDLER(price_db_).off();
}
+
+ TRACE_CTOR(global_scope_t, "");
}
global_scope_t::~global_scope_t()
@@ -112,9 +113,12 @@ void global_scope_t::read_init()
if (exists(init_file)) {
TRACE_START(init, 1, "Read initialization file");
- ifstream init(init_file);
+ parse_context_stack_t parsing_context;
+ parsing_context.push(init_file);
+ parsing_context.get_current().journal = session().journal.get();
+ parsing_context.get_current().scope = &report();
- if (session().journal->read(init_file, NULL, &report()) > 0 ||
+ if (session().journal->read(parsing_context) > 0 ||
session().journal->auto_xacts.size() > 0 ||
session().journal->period_xacts.size() > 0) {
throw_(parse_error, _("Transactions found in initialization file '%1'")
@@ -189,7 +193,7 @@ void global_scope_t::execute_command(strings_list args, bool at_repl)
is_precommand = true;
// If it is not a pre-command, then parse the user's ledger data at this
- // time if not done alreday (i.e., if not at a REPL). Then patch up the
+ // time if not done already (i.e., if not at a REPL). Then patch up the
// report options based on the command verb.
if (! is_precommand) {
@@ -268,6 +272,7 @@ void global_scope_t::report_options(report_t& report, std::ostream& out)
HANDLER(trace_).report(out);
HANDLER(verbose).report(out);
HANDLER(verify).report(out);
+ HANDLER(verify_memory).report(out);
out << std::endl << "[Session scope options]" << std::endl;
report.session.report_options(out);
@@ -311,6 +316,7 @@ option_t<global_scope_t> * global_scope_t::lookup_option(const char * p)
case 'v':
OPT_(verbose);
else OPT(verify);
+ else OPT(verify_memory);
else OPT(version);
break;
}
@@ -448,29 +454,36 @@ void handle_debug_options(int argc, char * argv[])
if (std::strcmp(argv[i], "--args-only") == 0) {
args_only = true;
}
+ else if (std::strcmp(argv[i], "--verify-memory") == 0) {
+#if defined(VERIFY_ON)
+ verify_enabled = true;
+
+ _log_level = LOG_DEBUG;
+ _log_category = "memory\\.counts";
+#endif
+ }
else if (std::strcmp(argv[i], "--verify") == 0) {
#if defined(VERIFY_ON)
- verify_enabled = true; // global in utils.h
+ verify_enabled = true;
#endif
}
else if (std::strcmp(argv[i], "--verbose") == 0 ||
std::strcmp(argv[i], "-v") == 0) {
#if defined(LOGGING_ON)
- _log_level = LOG_INFO; // global in utils.h
+ _log_level = LOG_INFO;
#endif
}
else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) {
#if defined(DEBUG_ON)
- _log_level = LOG_DEBUG; // global in utils.h
- _log_category = argv[i + 1]; // global in utils.h
+ _log_level = LOG_DEBUG;
+ _log_category = argv[i + 1];
i++;
#endif
}
else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) {
#if defined(TRACING_ON)
- _log_level = LOG_TRACE; // global in utils.h
+ _log_level = LOG_TRACE;
try {
- // global in utils.h
_trace_level = boost::lexical_cast<uint8_t>(argv[i + 1]);
}
catch (const boost::bad_lexical_cast&) {
diff --git a/src/global.h b/src/global.h
index 6504230d..5786bb89 100644
--- a/src/global.h
+++ b/src/global.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -50,11 +50,17 @@ class global_scope_t : public noncopyable, public scope_t
{
shared_ptr<session_t> session_ptr;
ptr_list<report_t> report_stack;
+ empty_scope_t empty_scope;
public:
global_scope_t(char ** envp);
~global_scope_t();
+ void quick_close() {
+ if (! report_stack.empty())
+ report_stack.front().quick_close();
+ }
+
virtual string description() {
return _("global scope");
}
@@ -82,6 +88,7 @@ public:
void pop_report() {
assert(! report_stack.empty());
report_stack.pop_front();
+
// There should always be the "default report" waiting on the stack.
assert(! report_stack.empty());
scope_t::default_scope = &report();
@@ -113,7 +120,7 @@ public:
out <<
"Ledger " << ledger::version << _(", the command-line accounting tool");
out <<
- _("\n\nCopyright (c) 2003-2010, John Wiegley. All rights reserved.\n\n\
+ _("\n\nCopyright (c) 2003-2012, John Wiegley. All rights reserved.\n\n\
This program is made available under the terms of the BSD Public License.\n\
See LICENSE file included with the distribution for details and disclaimer.");
out << std::endl;
@@ -139,7 +146,6 @@ See LICENSE file included with the distribution for details and disclaimer.");
OPTION__
(global_scope_t, init_file_, // -i
-
CTOR(global_scope_t, init_file_) {
if (const char * home_var = std::getenv("HOME"))
on(none, (path(home_var) / ".ledgerrc").string());
@@ -152,10 +158,11 @@ See LICENSE file included with the distribution for details and disclaimer.");
OPTION(global_scope_t, trace_);
OPTION(global_scope_t, verbose);
OPTION(global_scope_t, verify);
+ OPTION(global_scope_t, verify_memory);
OPTION_(global_scope_t, version, DO() { // -v
parent->show_version_info(std::cout);
- throw int(0); // exit immediately
+ throw error_count(0); // exit immediately
});
};
diff --git a/src/history.cc b/src/history.cc
new file mode 100644
index 00000000..d94ec647
--- /dev/null
+++ b/src/history.cc
@@ -0,0 +1,455 @@
+/*
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "history.h"
+
+template <typename T>
+struct f_max : public std::binary_function<T, T, bool> {
+ T operator()(const T& x, const T& y) const {
+ return std::max(x, y);
+ }
+};
+
+namespace ledger {
+
+template <typename EdgeWeightMap,
+ typename PricePointMap,
+ typename PriceRatioMap>
+class recent_edge_weight
+{
+public:
+ EdgeWeightMap weight;
+ PricePointMap price_point;
+ PriceRatioMap ratios;
+
+ datetime_t reftime;
+ datetime_t oldest;
+
+ recent_edge_weight() { }
+ recent_edge_weight(EdgeWeightMap _weight,
+ PricePointMap _price_point,
+ PriceRatioMap _ratios,
+ const datetime_t& _reftime,
+ const datetime_t& _oldest = datetime_t())
+ : weight(_weight), price_point(_price_point), ratios(_ratios),
+ reftime(_reftime), oldest(_oldest) { }
+
+ template <typename Edge>
+ bool operator()(const Edge& e) const
+ {
+#if defined(DEBUG_ON)
+ DEBUG("history.find", " reftime = " << reftime);
+ if (! oldest.is_not_a_date_time()) {
+ DEBUG("history.find", " oldest = " << oldest);
+ }
+#endif
+
+ const price_map_t& prices(get(ratios, e));
+ if (prices.empty()) {
+ DEBUG("history.find", " prices map is empty for this edge");
+ return false;
+ }
+
+ price_map_t::const_iterator low = prices.upper_bound(reftime);
+ if (low != prices.end() && low == prices.begin()) {
+ DEBUG("history.find", " don't use this edge");
+ return false;
+ } else {
+ --low;
+ assert(((*low).first <= reftime));
+
+ if (! oldest.is_not_a_date_time() && (*low).first < oldest) {
+ DEBUG("history.find", " edge is out of range");
+ return false;
+ }
+
+ long secs = (reftime - (*low).first).total_seconds();
+ assert(secs >= 0);
+
+ put(weight, e, secs);
+ put(price_point, e, price_point_t((*low).first, (*low).second));
+
+ DEBUG("history.find", " using edge at price point "
+ << (*low).first << " " << (*low).second);
+ return true;
+ }
+ }
+};
+
+typedef filtered_graph
+ <commodity_history_t::Graph,
+ recent_edge_weight<commodity_history_t::EdgeWeightMap,
+ commodity_history_t::PricePointMap,
+ commodity_history_t::PriceRatioMap> > FGraph;
+
+typedef property_map<FGraph, vertex_name_t>::type FNameMap;
+typedef property_map<FGraph, vertex_index_t>::type FIndexMap;
+typedef iterator_property_map
+ <commodity_history_t::vertex_descriptor*, FIndexMap,
+ commodity_history_t::vertex_descriptor,
+ commodity_history_t::vertex_descriptor&> FPredecessorMap;
+typedef iterator_property_map<long*, FIndexMap, long, long&> FDistanceMap;
+
+void commodity_history_t::add_commodity(commodity_t& comm)
+{
+ if (! comm.graph_index()) {
+ comm.set_graph_index(num_vertices(price_graph));
+ add_vertex(/* vertex_name= */ &comm, price_graph);
+ }
+}
+
+void commodity_history_t::add_price(const commodity_t& source,
+ const datetime_t& when,
+ const amount_t& price)
+{
+ assert(source != price.commodity());
+
+ vertex_descriptor sv = vertex(*source.graph_index(), price_graph);
+ vertex_descriptor tv = vertex(*price.commodity().graph_index(), price_graph);
+
+ std::pair<edge_descriptor, bool> e1 = edge(sv, tv, price_graph);
+ if (! e1.second)
+ e1 = add_edge(sv, tv, price_graph);
+
+ price_map_t& prices(get(ratiomap, e1.first));
+
+ std::pair<price_map_t::iterator, bool> result =
+ prices.insert(price_map_t::value_type(when, price));
+ if (! result.second) {
+ // There is already an entry for this moment, so update it
+ (*result.first).second = price;
+ }
+}
+
+void commodity_history_t::remove_price(const commodity_t& source,
+ const commodity_t& target,
+ const datetime_t& date)
+{
+ assert(source != target);
+
+ vertex_descriptor sv = vertex(*source.graph_index(), price_graph);
+ vertex_descriptor tv = vertex(*target.graph_index(), price_graph);
+
+ std::pair<Graph::edge_descriptor, bool> e1 = edge(sv, tv, price_graph);
+ if (e1.second) {
+ price_map_t& prices(get(ratiomap, e1.first));
+
+ // jww (2012-03-04): If it fails, should we give a warning?
+ prices.erase(date);
+
+ if (prices.empty())
+ remove_edge(e1.first, price_graph);
+ }
+}
+
+void commodity_history_t::map_prices(function<void(datetime_t,
+ const amount_t&)> fn,
+ const commodity_t& source,
+ const datetime_t& moment,
+ const datetime_t& oldest,
+ bool bidirectionally)
+{
+ DEBUG("history.map", "Mapping prices for source commodity: " << source);
+
+ vertex_descriptor sv = vertex(*source.graph_index(), price_graph);
+
+ FGraph fg(price_graph,
+ recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap>
+ (get(edge_weight, price_graph), pricemap, ratiomap,
+ moment, oldest));
+
+ FNameMap namemap(get(vertex_name, fg));
+
+ graph_traits<FGraph>::adjacency_iterator f_vi, f_vend;
+ for (tie(f_vi, f_vend) = adjacent_vertices(sv, fg); f_vi != f_vend; ++f_vi) {
+ std::pair<Graph::edge_descriptor, bool> edgePair = edge(sv, *f_vi, fg);
+ Graph::edge_descriptor edge = edgePair.first;
+
+ const price_map_t& prices(get(ratiomap, edge));
+
+ foreach (const price_map_t::value_type& pair, prices) {
+ const datetime_t& when(pair.first);
+
+ DEBUG("history.map", "Price " << pair.second << " on " << when);
+
+ if ((oldest.is_not_a_date_time() || when >= oldest) && when <= moment) {
+ if (pair.second.commodity() == source) {
+ if (bidirectionally) {
+ amount_t price(pair.second);
+ price.in_place_invert();
+ if (source == *get(namemap, sv))
+ price.set_commodity(const_cast<commodity_t&>(*get(namemap, *f_vi)));
+ else
+ price.set_commodity(const_cast<commodity_t&>(*get(namemap, sv)));
+ DEBUG("history.map", "Inverted price is " << price);
+ DEBUG("history.map", "fn(" << when << ", " << price << ")");
+ fn(when, price);
+ }
+ } else {
+ DEBUG("history.map", "fn(" << when << ", " << pair.second << ")");
+ fn(when, pair.second);
+ }
+ }
+ }
+ }
+}
+
+optional<price_point_t>
+commodity_history_t::find_price(const commodity_t& source,
+ const datetime_t& moment,
+ const datetime_t& oldest)
+{
+ vertex_descriptor sv = vertex(*source.graph_index(), price_graph);
+
+ FGraph fg(price_graph,
+ recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap>
+ (get(edge_weight, price_graph), pricemap, ratiomap,
+ moment, oldest));
+
+ FNameMap namemap(get(vertex_name, fg));
+
+ DEBUG("history.find", "sv commodity = " << get(namemap, sv)->symbol());
+#if defined(DEBUG_ON)
+ if (source.has_flags(COMMODITY_PRIMARY))
+ DEBUG("history.find", "sv commodity is primary");
+#endif
+ DEBUG("history.find", "tv commodity = none ");
+
+ datetime_t most_recent = moment;
+ amount_t price;
+
+ graph_traits<FGraph>::adjacency_iterator f_vi, f_vend;
+ for (tie(f_vi, f_vend) = adjacent_vertices(sv, fg); f_vi != f_vend; ++f_vi) {
+ std::pair<Graph::edge_descriptor, bool> edgePair = edge(sv, *f_vi, fg);
+ Graph::edge_descriptor edge = edgePair.first;
+
+ DEBUG("history.find", "u commodity = " << get(namemap, sv)->symbol());
+ DEBUG("history.find", "v commodity = " << get(namemap, *f_vi)->symbol());
+
+ const price_point_t& point(get(pricemap, edge));
+
+ if (price.is_null() || point.when > most_recent) {
+ most_recent = point.when;
+ price = point.price;
+ }
+
+ DEBUG("history.find", "price was = " << price.unrounded());
+
+ if (price.commodity() == source) {
+ price.in_place_invert();
+ if (source == *get(namemap, sv))
+ price.set_commodity(const_cast<commodity_t&>(*get(namemap, *f_vi)));
+ else
+ price.set_commodity(const_cast<commodity_t&>(*get(namemap, sv)));
+ }
+
+ DEBUG("history.find", "price is = " << price.unrounded());
+ }
+
+ if (price.is_null()) {
+ DEBUG("history.find", "there is no final price");
+ return none;
+ } else {
+ DEBUG("history.find", "final price is = " << price.unrounded());
+ return price_point_t(most_recent, price);
+ }
+}
+
+optional<price_point_t>
+commodity_history_t::find_price(const commodity_t& source,
+ const commodity_t& target,
+ const datetime_t& moment,
+ const datetime_t& oldest)
+{
+ assert(source != target);
+
+ vertex_descriptor sv = vertex(*source.graph_index(), price_graph);
+ vertex_descriptor tv = vertex(*target.graph_index(), price_graph);
+
+ FGraph fg(price_graph,
+ recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap>
+ (get(edge_weight, price_graph), pricemap, ratiomap,
+ moment, oldest));
+
+ FNameMap namemap(get(vertex_name, fg));
+
+ DEBUG("history.find", "sv commodity = " << get(namemap, sv)->symbol());
+ DEBUG("history.find", "tv commodity = " << get(namemap, tv)->symbol());
+
+ std::size_t vector_len(num_vertices(fg));
+ std::vector<vertex_descriptor> predecessors(vector_len);
+ std::vector<long> distances(vector_len);
+
+ FPredecessorMap predecessorMap(&predecessors[0]);
+ FDistanceMap distanceMap(&distances[0]);
+
+ dijkstra_shortest_paths(fg, /* start= */ sv,
+ predecessor_map(predecessorMap)
+ .distance_map(distanceMap)
+ .distance_combine(f_max<long>()));
+
+ // Extract the shortest path and performance the calculations
+ datetime_t least_recent = moment;
+ amount_t price;
+
+ const commodity_t * last_target = &target;
+
+#if defined(REVERSE_PREDECESSOR_MAP)
+ typedef tuple<const commodity_t *, const commodity_t *,
+ const price_point_t *> results_tuple;
+ std::vector<results_tuple> results;
+ bool results_reversed = false;
+#endif
+
+ vertex_descriptor v = tv;
+ for (vertex_descriptor u = predecessorMap[v];
+ u != v;
+ v = u, u = predecessorMap[v])
+ {
+ std::pair<Graph::edge_descriptor, bool> edgePair_uv = edge(u, v, fg);
+ std::pair<Graph::edge_descriptor, bool> edgePair_vu = edge(v, u, fg);
+
+ Graph::edge_descriptor edge_uv = edgePair_uv.first;
+ Graph::edge_descriptor edge_vu = edgePair_vu.first;
+
+ const price_point_t& point_uv(get(pricemap, edge_uv));
+ const price_point_t& point_vu(get(pricemap, edge_vu));
+
+ const price_point_t& point(point_vu.when > point_uv.when ?
+ point_vu : point_uv);
+
+ const commodity_t * u_comm = get(namemap, u);
+ const commodity_t * v_comm = get(namemap, v);
+
+#if defined(REVERSE_PREDECESSOR_MAP)
+ if (v == tv && u_comm != last_target && v_comm != last_target)
+ results_reversed = true;
+
+ results.push_back(results_tuple(u_comm, v_comm, &point));
+ }
+
+ if (results_reversed)
+ std::reverse(results.begin(), results.end());
+
+ foreach (const results_tuple& edge, results) {
+ const commodity_t * u_comm = edge.get<0>();
+ const commodity_t * v_comm = edge.get<1>();
+ const price_point_t& point(*edge.get<2>());
+#else
+ assert(u_comm == last_target || v_comm == last_target);
+#endif
+
+ bool first_run = false;
+ if (price.is_null()) {
+ least_recent = point.when;
+ first_run = true;
+ }
+ else if (point.when < least_recent) {
+ least_recent = point.when;
+ }
+
+ DEBUG("history.find", "u commodity = " << u_comm->symbol());
+ DEBUG("history.find", "v commodity = " << v_comm->symbol());
+ DEBUG("history.find", "last target = " << last_target->symbol());
+
+ // Determine which direction we are converting in
+ amount_t pprice(point.price);
+ DEBUG("history.find", "pprice = " << pprice.unrounded());
+
+ if (! first_run) {
+ DEBUG("history.find", "price was = " << price.unrounded());
+ if (pprice.commodity_ptr() != last_target)
+ price *= pprice.inverted();
+ else
+ price *= pprice;
+ }
+ else if (pprice.commodity_ptr() != last_target) {
+ price = pprice.inverted();
+ }
+ else {
+ price = pprice;
+ }
+ DEBUG("history.find", "price is = " << price.unrounded());
+
+ if (last_target == v_comm)
+ last_target = u_comm;
+ else
+ last_target = v_comm;
+
+ DEBUG("history.find", "last target now = " << last_target->symbol());
+ }
+
+ if (price.is_null()) {
+ DEBUG("history.find", "there is no final price");
+ return none;
+ } else {
+ price.set_commodity(const_cast<commodity_t&>(target));
+ DEBUG("history.find", "final price is = " << price.unrounded());
+
+ return price_point_t(least_recent, price);
+ }
+}
+
+template <class Name>
+class label_writer {
+public:
+ label_writer(Name _name) : name(_name) {
+ TRACE_CTOR(label_writer<Name>, "Name");
+ }
+ ~label_writer() throw() {
+ TRACE_DTOR(label_writer<Name>);
+ }
+
+ template <class VertexOrEdge>
+ void operator()(std::ostream& out, const VertexOrEdge& v) const {
+ out << "[label=\"" << name[v]->symbol() << "\"]";
+ }
+
+private:
+ Name name;
+};
+
+void commodity_history_t::print_map(std::ostream& out, const datetime_t& moment)
+{
+ if (moment.is_not_a_date_time()) {
+ write_graphviz(out, price_graph,
+ label_writer<NameMap>(get(vertex_name, price_graph)));
+ } else {
+ FGraph fg(price_graph,
+ recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap>
+ (get(edge_weight, price_graph), pricemap, ratiomap, moment));
+ write_graphviz(out, fg, label_writer<FNameMap>(get(vertex_name, fg)));
+ }
+}
+
+} // namespace ledger
diff --git a/src/history.h b/src/history.h
new file mode 100644
index 00000000..af0d90f9
--- /dev/null
+++ b/src/history.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup math
+ */
+
+/**
+ * @file history.h
+ * @author John Wiegley
+ *
+ * @ingroup math
+ *
+ * @brief Types for managing commodity historys
+ *
+ * Long.
+ */
+#ifndef _HISTORY_H
+#define _HISTORY_H
+
+#include "amount.h"
+#include "commodity.h"
+
+namespace boost {
+ enum edge_price_point_t { edge_price_point };
+ enum edge_price_ratio_t { edge_price_ratio };
+ BOOST_INSTALL_PROPERTY(edge, price_point);
+ BOOST_INSTALL_PROPERTY(edge, price_ratio);
+}
+
+namespace ledger {
+
+typedef std::map<datetime_t, amount_t> price_map_t;
+
+class commodity_history_t : public noncopyable
+{
+public:
+ typedef adjacency_list
+ <vecS, // Store all edges in a vector
+ vecS, // Store all vertices in a vector
+ undirectedS, // Relations are both ways
+
+ // All vertices are commodities
+ property<vertex_name_t, const commodity_t *,
+ property<vertex_index_t, std::size_t> >,
+
+ // All edges are weights computed as the absolute difference between
+ // the reference time of a search and a known price point. A
+ // filtered_graph is used to select the recent price point to the
+ // reference time before performing the search.
+ property<edge_weight_t, long,
+ property<edge_price_ratio_t, price_map_t,
+ property<edge_price_point_t, price_point_t> > >,
+
+ // Graph itself has a std::string name
+ property<graph_name_t, std::string>
+ > Graph;
+
+ Graph price_graph;
+
+ typedef graph_traits<Graph>::vertex_descriptor vertex_descriptor;
+ typedef graph_traits<Graph>::edge_descriptor edge_descriptor;
+
+ typedef property_map<Graph, vertex_name_t>::type NameMap;
+ typedef property_map<Graph, edge_weight_t>::type EdgeWeightMap;
+ typedef property_map<Graph, edge_price_point_t>::type PricePointMap;
+ typedef property_map<Graph, edge_price_ratio_t>::type PriceRatioMap;
+
+ PricePointMap pricemap;
+ PriceRatioMap ratiomap;
+
+ commodity_history_t()
+ : pricemap(get(edge_price_point, price_graph)),
+ ratiomap(get(edge_price_ratio, price_graph)) {}
+
+ void add_commodity(commodity_t& comm);
+
+ void add_price(const commodity_t& source,
+ const datetime_t& when,
+ const amount_t& price);
+ void remove_price(const commodity_t& source,
+ const commodity_t& target,
+ const datetime_t& date);
+
+ void map_prices(function<void(datetime_t, const amount_t&)> fn,
+ const commodity_t& source,
+ const datetime_t& moment,
+ const datetime_t& _oldest = datetime_t(),
+ bool bidirectionally = false);
+
+ optional<price_point_t>
+ find_price(const commodity_t& source,
+ const datetime_t& moment,
+ const datetime_t& oldest = datetime_t());
+
+ optional<price_point_t>
+ find_price(const commodity_t& source,
+ const commodity_t& target,
+ const datetime_t& moment,
+ const datetime_t& oldest = datetime_t());
+
+ void print_map(std::ostream& out, const datetime_t& moment = datetime_t());
+};
+
+} // namespace ledger
+
+#endif // _HISTORY_H
diff --git a/src/item.cc b/src/item.cc
index 056aa04c..17d46652 100644
--- a/src/item.cc
+++ b/src/item.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -35,7 +35,7 @@
namespace ledger {
-bool item_t::use_effective_date = false;
+bool item_t::use_aux_date = false;
bool item_t::has_tag(const string& tag, bool) const
{
@@ -72,7 +72,7 @@ bool item_t::has_tag(const mask_t& tag_mask,
return false;
}
- optional<value_t> item_t::get_tag(const string& tag, bool) const
+optional<value_t> item_t::get_tag(const string& tag, bool) const
{
DEBUG("item.meta", "Getting item tag: " << tag);
if (metadata) {
@@ -103,6 +103,16 @@ optional<value_t> item_t::get_tag(const mask_t& tag_mask,
return none;
}
+namespace {
+ struct CaseInsensitiveKeyCompare
+ : public std::binary_function<string, string, bool>
+ {
+ bool operator()(const string& s1, const string& s2) const {
+ return boost::algorithm::ilexicographical_compare(s1, s2);
+ }
+ };
+}
+
item_t::string_map::iterator
item_t::set_tag(const string& tag,
const optional<value_t>& value,
@@ -111,15 +121,14 @@ item_t::set_tag(const string& tag,
assert(! tag.empty());
if (! metadata)
- metadata = string_map();
+ metadata = string_map(CaseInsensitiveKeyCompare());
DEBUG("item.meta", "Setting tag '" << tag << "' to value '"
<< (value ? *value : string_value("<none>")) << "'");
optional<value_t> data = value;
- if (data &&
- (data->is_null() ||
- (data->is_string() && data->as_string().empty())))
+ if (data && (data->is_null() ||
+ (data->is_string() && data->as_string().empty())))
data = none;
string_map::iterator i = metadata->find(tag);
@@ -150,7 +159,7 @@ void item_t::parse_tags(const char * p,
if (char * pp = std::strchr(buf, '=')) {
*pp++ = '\0';
- _date_eff = parse_date(pp);
+ _date_aux = parse_date(pp);
}
if (buf[0])
_date = parse_date(buf);
@@ -172,19 +181,7 @@ void item_t::parse_tags(const char * p,
q = std::strtok(NULL, " \t")) {
const string::size_type len = std::strlen(q);
if (len < 2) continue;
- if (! tag.empty()) {
- string_map::iterator i;
- string field(p + (q - buf.get()));
- if (by_value) {
- bind_scope_t bound_scope(scope, *this);
- i = set_tag(tag, expr_t(field).calc(bound_scope), overwrite_existing);
- } else {
- i = set_tag(tag, string_value(field), overwrite_existing);
- }
- (*i).second.second = true;
- break;
- }
- else if (q[0] == ':' && q[len - 1] == ':') { // a series of tags
+ if (q[0] == ':' && q[len - 1] == ':') { // a series of tags
for (char * r = std::strtok(q + 1, ":");
r;
r = std::strtok(NULL, ":")) {
@@ -199,6 +196,18 @@ void item_t::parse_tags(const char * p,
index = 2;
}
tag = string(q, len - index);
+
+ string_map::iterator i;
+ string field(p + len + index);
+ trim(field);
+ if (by_value) {
+ bind_scope_t bound_scope(scope, *this);
+ i = set_tag(tag, expr_t(field).calc(bound_scope), overwrite_existing);
+ } else {
+ i = set_tag(tag, string_value(field), overwrite_existing);
+ }
+ (*i).second.second = true;
+ break;
}
first = false;
}
@@ -239,12 +248,12 @@ namespace {
value_t get_date(item_t& item) {
return item.date();
}
- value_t get_actual_date(item_t& item) {
- return item.actual_date();
+ value_t get_primary_date(item_t& item) {
+ return item.primary_date();
}
- value_t get_effective_date(item_t& item) {
- if (optional<date_t> effective = item.effective_date())
- return *effective;
+ value_t get_aux_date(item_t& item) {
+ if (optional<date_t> aux_date = item.aux_date())
+ return *aux_date;
return NULL_VALUE;
}
value_t get_note(item_t& item) {
@@ -338,7 +347,10 @@ namespace {
}
value_t get_seq(item_t& item) {
- return item.pos ? long(item.pos->sequence) : 0L;
+ return long(item.seq());
+ }
+ value_t get_id(item_t& item) {
+ return string_value(item.id());
}
value_t get_addr(item_t& item) {
@@ -386,6 +398,13 @@ value_t get_comment(item_t& item)
}
}
+void item_t::define(const symbol_t::kind_t, const string& name,
+ expr_t::ptr_op_t def)
+{
+ bind_scope_t bound_scope(*scope_t::default_scope, *this);
+ set_tag(name, def->calc(bound_scope));
+}
+
expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind,
const string& name)
{
@@ -397,9 +416,11 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind,
if (name == "actual")
return WRAP_FUNCTOR(get_wrapper<&get_actual>);
else if (name == "actual_date")
- return WRAP_FUNCTOR(get_wrapper<&get_actual_date>);
+ return WRAP_FUNCTOR(get_wrapper<&get_primary_date>);
else if (name == "addr")
return WRAP_FUNCTOR(get_wrapper<&get_addr>);
+ else if (name == "aux_date")
+ return WRAP_FUNCTOR(get_wrapper<&get_aux_date>);
break;
case 'b':
@@ -429,7 +450,7 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind,
else if (name == "end_pos")
return WRAP_FUNCTOR(get_wrapper<&get_end_pos>);
else if (name == "effective_date")
- return WRAP_FUNCTOR(get_wrapper<&get_effective_date>);
+ return WRAP_FUNCTOR(get_wrapper<&get_aux_date>);
break;
case 'f':
@@ -447,6 +468,8 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind,
case 'i':
if (name == "is_account")
return WRAP_FUNCTOR(get_wrapper<&ignore>);
+ else if (name == "id")
+ return WRAP_FUNCTOR(get_wrapper<&get_id>);
break;
case 'm':
@@ -464,10 +487,12 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(get_wrapper<&get_pending>);
else if (name == "parent")
return WRAP_FUNCTOR(get_wrapper<&ignore>);
+ else if (name == "primary_date")
+ return WRAP_FUNCTOR(get_wrapper<&get_primary_date>);
break;
case 's':
- if (name == "status")
+ if (name == "status" || name == "state")
return WRAP_FUNCTOR(get_wrapper<&get_status>);
else if (name == "seq")
return WRAP_FUNCTOR(get_wrapper<&get_seq>);
@@ -481,6 +506,8 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind,
case 'u':
if (name == "uncleared")
return WRAP_FUNCTOR(get_wrapper<&get_uncleared>);
+ else if (name == "uuid")
+ return WRAP_FUNCTOR(get_wrapper<&get_id>);
break;
case 'v':
@@ -532,7 +559,7 @@ string item_context(const item_t& item, const string& desc)
if (! (len > 0))
return empty_string;
- assert(len < 8192);
+ assert(len < 1024 * 1024);
std::ostringstream out;
diff --git a/src/item.h b/src/item.h
index 79d2f23b..8d9a79ee 100644
--- a/src/item.h
+++ b/src/item.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -60,8 +60,8 @@ struct position_t
TRACE_CTOR(position_t, "");
}
position_t(const position_t& pos) {
- TRACE_CTOR(position_t, "copy");
*this = pos;
+ TRACE_CTOR(position_t, "copy");
}
~position_t() throw() {
TRACE_DTOR(position_t);
@@ -100,18 +100,20 @@ private:
class item_t : public supports_flags<uint_least16_t>, public scope_t
{
public:
-#define ITEM_NORMAL 0x00 // no flags at all, a basic posting
-#define ITEM_GENERATED 0x01 // posting was not found in a journal
-#define ITEM_TEMP 0x02 // posting is a managed temporary
+#define ITEM_NORMAL 0x00 // no flags at all, a basic posting
+#define ITEM_GENERATED 0x01 // posting was not found in a journal
+#define ITEM_TEMP 0x02 // posting is a managed temporary
+#define ITEM_NOTE_ON_NEXT_LINE 0x04 // did we see a note on the next line?
enum state_t { UNCLEARED = 0, CLEARED, PENDING };
typedef std::pair<optional<value_t>, bool> tag_data_t;
- typedef std::map<string, tag_data_t> string_map;
+ typedef std::map<string, tag_data_t,
+ function<bool(string, string)> > string_map;
state_t _state;
optional<date_t> _date;
- optional<date_t> _date_eff;
+ optional<date_t> _date_aux;
optional<string> note;
optional<position_t> pos;
optional<string_map> metadata;
@@ -123,20 +125,20 @@ public:
}
item_t(const item_t& item) : supports_flags<uint_least16_t>(), scope_t()
{
- TRACE_CTOR(item_t, "copy");
copy_details(item);
+ TRACE_CTOR(item_t, "copy");
}
virtual ~item_t() {
TRACE_DTOR(item_t);
}
- void copy_details(const item_t& item)
+ virtual void copy_details(const item_t& item)
{
set_flags(item.flags());
set_state(item.state());
_date = item._date;
- _date_eff = item._date_eff;
+ _date_aux = item._date_aux;
note = item.note;
pos = item.pos;
metadata = item.metadata;
@@ -149,6 +151,19 @@ public:
return ! (*this == xact);
}
+ string id() const {
+ if (optional<value_t> ref = get_tag(_("UUID"))) {
+ return ref->to_string();
+ } else {
+ std::ostringstream buf;
+ buf << seq();
+ return buf.str();
+ }
+ }
+ std::size_t seq() const {
+ return pos ? pos->sequence : 0L;
+ }
+
virtual bool has_tag(const string& tag,
bool inherit = true) const;
virtual bool has_tag(const mask_t& tag_mask,
@@ -173,7 +188,7 @@ public:
scope_t& scope,
bool overwrite_existing = true);
- static bool use_effective_date;
+ static bool use_aux_date;
virtual bool has_date() const {
return _date;
@@ -181,17 +196,17 @@ public:
virtual date_t date() const {
assert(_date);
- if (use_effective_date)
- if (optional<date_t> effective = effective_date())
- return *effective;
+ if (use_aux_date)
+ if (optional<date_t> aux = aux_date())
+ return *aux;
return *_date;
}
- virtual date_t actual_date() const {
+ virtual date_t primary_date() const {
assert(_date);
return *_date;
}
- virtual optional<date_t> effective_date() const {
- return _date_eff;
+ virtual optional<date_t> aux_date() const {
+ return _date_aux;
}
void set_state(state_t new_state) {
@@ -201,6 +216,8 @@ public:
return _state;
}
+ virtual void define(const symbol_t::kind_t, const string&,
+ expr_t::ptr_op_t);
virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
const string& name);
@@ -218,7 +235,7 @@ private:
ar & boost::serialization::base_object<scope_t>(*this);
ar & _state;
ar & _date;
- ar & _date_eff;
+ ar & _date_aux;
ar & note;
ar & pos;
ar & metadata;
diff --git a/src/iterators.cc b/src/iterators.cc
index b398646e..7cc1291a 100644
--- a/src/iterators.cc
+++ b/src/iterators.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -75,6 +75,65 @@ void journal_posts_iterator::increment()
}
}
+namespace {
+ struct create_price_xact
+ {
+ journal_t& journal;
+ account_t * account;
+ temporaries_t& temps;
+ xacts_list& xact_temps;
+
+ std::map<string, xact_t *> xacts_by_commodity;
+
+ create_price_xact(journal_t& _journal, account_t * _account,
+ temporaries_t& _temps, xacts_list& _xact_temps)
+ : journal(_journal), account(_account), temps(_temps),
+ xact_temps(_xact_temps) {
+ TRACE_CTOR(create_price_xact,
+ "journal_t&, account_t *, temporaries_t&, xacts_list&");
+ }
+ ~create_price_xact() throw() {
+ TRACE_DTOR(create_price_xact);
+ }
+
+ void operator()(datetime_t& date, const amount_t& price) {
+ xact_t * xact;
+ string symbol = price.commodity().symbol();
+
+ std::map<string, xact_t *>::iterator i =
+ xacts_by_commodity.find(symbol);
+ if (i != xacts_by_commodity.end()) {
+ xact = (*i).second;
+ } else {
+ xact = &temps.create_xact();
+ xact_temps.push_back(xact);
+ xact->payee = symbol;
+ xact->_date = date.date();
+ xacts_by_commodity.insert
+ (std::pair<string, xact_t *>(symbol, xact));
+ xact->journal = &journal;
+ }
+
+ bool post_already_exists = false;
+
+ foreach (post_t * post, xact->posts) {
+ if (post->date() == date.date() && post->amount == price) {
+ post_already_exists = true;
+ break;
+ }
+ }
+
+ if (! post_already_exists) {
+ post_t& temp = temps.create_post(*xact, account);
+ temp._date = date.date();
+ temp.amount = price;
+
+ temp.xdata().datetime = date;
+ }
+ }
+ };
+}
+
void posts_commodities_iterator::reset(journal_t& journal)
{
journal_posts.reset(journal);
@@ -85,57 +144,13 @@ void posts_commodities_iterator::reset(journal_t& journal)
commodity_t& comm(post->amount.commodity());
if (comm.flags() & COMMODITY_NOMARKET)
continue;
- commodities.insert(&comm);
+ commodities.insert(&comm.referent());
}
- std::map<string, xact_t *> xacts_by_commodity;
-
- foreach (commodity_t * comm, commodities) {
- if (optional<commodity_t::varied_history_t&> history =
- comm->varied_history()) {
- account_t * account = journal.master->find_account(comm->symbol());
-
- foreach (commodity_t::history_by_commodity_map::value_type& pair,
- history->histories) {
- foreach (commodity_t::history_map::value_type& hpair,
- pair.second.prices) {
- xact_t * xact;
- string symbol = hpair.second.commodity().symbol();
-
- std::map<string, xact_t *>::iterator i =
- xacts_by_commodity.find(symbol);
- if (i != xacts_by_commodity.end()) {
- xact = (*i).second;
- } else {
- xact = &temps.create_xact();
- xact_temps.push_back(xact);
- xact->payee = symbol;
- xact->_date = hpair.first.date();
- xacts_by_commodity.insert
- (std::pair<string, xact_t *>(symbol, xact));
- }
-
- bool post_already_exists = false;
-
- foreach (post_t * post, xact->posts) {
- if (post->_date == hpair.first.date() &&
- post->amount == hpair.second) {
- post_already_exists = true;
- break;
- }
- }
-
- if (! post_already_exists) {
- post_t& temp = temps.create_post(*xact, account);
- temp._date = hpair.first.date();
- temp.amount = hpair.second;
-
- temp.xdata().datetime = hpair.first;
- }
- }
- }
- }
- }
+ foreach (commodity_t * comm, commodities)
+ comm->map_prices
+ (create_price_xact(journal, journal.master->find_account(comm->symbol()),
+ temps, xact_temps));
xacts.reset(xact_temps.begin(), xact_temps.end());
diff --git a/src/iterators.h b/src/iterators.h
index 93782400..53814666 100644
--- a/src/iterators.h
+++ b/src/iterators.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -58,7 +58,15 @@ class iterator_facade_base
typedef Value node_base;
public:
- iterator_facade_base() : m_node(NULL) {}
+ iterator_facade_base() : m_node(NULL) {
+ TRACE_CTOR(iterator_facade_base, "");
+ }
+ iterator_facade_base(const iterator_facade_base& i) : m_node(i.m_node) {
+ TRACE_CTOR(iterator_facade_base, "copy");
+ }
+ ~iterator_facade_base() throw() {
+ TRACE_DTOR(iterator_facade_base);
+ }
explicit iterator_facade_base(node_base p) : m_node(p) {}
@@ -87,12 +95,24 @@ class xact_posts_iterator
bool posts_uninitialized;
public:
- xact_posts_iterator() : posts_uninitialized(true) {}
+ xact_posts_iterator() : posts_uninitialized(true) {
+ TRACE_CTOR(xact_posts_iterator, "");
+ }
xact_posts_iterator(xact_t& xact)
: posts_uninitialized(true) {
reset(xact);
+ TRACE_CTOR(xact_posts_iterator, "xact_t&");
+ }
+ xact_posts_iterator(const xact_posts_iterator& i)
+ : iterator_facade_base<xact_posts_iterator, post_t *,
+ boost::forward_traversal_tag>(i),
+ posts_i(i.posts_i), posts_end(i.posts_end),
+ posts_uninitialized(i.posts_uninitialized) {
+ TRACE_CTOR(xact_posts_iterator, "copy");
+ }
+ ~xact_posts_iterator() throw() {
+ TRACE_DTOR(xact_posts_iterator);
}
- ~xact_posts_iterator() throw() {}
void reset(xact_t& xact) {
posts_i = xact.posts.begin();
@@ -121,15 +141,28 @@ public:
bool xacts_uninitialized;
- xacts_iterator() : xacts_uninitialized(true) {}
+ xacts_iterator() : xacts_uninitialized(true) {
+ TRACE_CTOR(xacts_iterator, "");
+ }
xacts_iterator(journal_t& journal) : xacts_uninitialized(false) {
reset(journal);
+ TRACE_CTOR(xacts_iterator, "journal_t&");
}
xacts_iterator(xacts_list::iterator beg,
xacts_list::iterator end) : xacts_uninitialized(false) {
reset(beg, end);
+ TRACE_CTOR(xacts_iterator, "xacts_list::iterator, xacts_list::iterator");
+ }
+ xacts_iterator(const xacts_iterator& i)
+ : iterator_facade_base<xacts_iterator, xact_t *,
+ boost::forward_traversal_tag>(i),
+ xacts_i(i.xacts_i), xacts_end(i.xacts_end),
+ xacts_uninitialized(i.xacts_uninitialized) {
+ TRACE_CTOR(xacts_iterator, "copy");
+ }
+ ~xacts_iterator() throw() {
+ TRACE_DTOR(xacts_iterator);
}
- ~xacts_iterator() throw() {}
void reset(journal_t& journal);
@@ -150,11 +183,22 @@ class journal_posts_iterator
xact_posts_iterator posts;
public:
- journal_posts_iterator() {}
+ journal_posts_iterator() {
+ TRACE_CTOR(journal_posts_iterator, "");
+ }
journal_posts_iterator(journal_t& journal) {
reset(journal);
+ TRACE_CTOR(journal_posts_iterator, "journal_t&");
+ }
+ journal_posts_iterator(const journal_posts_iterator& i)
+ : iterator_facade_base<journal_posts_iterator, post_t *,
+ boost::forward_traversal_tag>(i),
+ xacts(i.xacts), posts(i.posts) {
+ TRACE_CTOR(journal_posts_iterator, "copy");
+ }
+ ~journal_posts_iterator() throw() {
+ TRACE_DTOR(journal_posts_iterator);
}
- ~journal_posts_iterator() throw() {}
void reset(journal_t& journal);
@@ -169,15 +213,27 @@ protected:
journal_posts_iterator journal_posts;
xacts_iterator xacts;
xact_posts_iterator posts;
- temporaries_t temps;
xacts_list xact_temps;
+ temporaries_t temps;
public:
- posts_commodities_iterator() {}
+ posts_commodities_iterator() {
+ TRACE_CTOR(posts_commodities_iterator, "");
+ }
posts_commodities_iterator(journal_t& journal) {
reset(journal);
+ TRACE_CTOR(posts_commodities_iterator, "journal_t&");
+ }
+ posts_commodities_iterator(const posts_commodities_iterator& i)
+ : iterator_facade_base<posts_commodities_iterator, post_t *,
+ boost::forward_traversal_tag>(i),
+ journal_posts(i.journal_posts), xacts(i.xacts), posts(i.posts),
+ xact_temps(i.xact_temps), temps(i.temps) {
+ TRACE_CTOR(posts_commodities_iterator, "copy");
+ }
+ ~posts_commodities_iterator() throw() {
+ TRACE_DTOR(posts_commodities_iterator);
}
- ~posts_commodities_iterator() throw() {}
void reset(journal_t& journal);
@@ -192,12 +248,23 @@ class basic_accounts_iterator
std::list<accounts_map::const_iterator> accounts_end;
public:
- basic_accounts_iterator() {}
+ basic_accounts_iterator() {
+ TRACE_CTOR(basic_accounts_iterator, "");
+ }
basic_accounts_iterator(account_t& account) {
push_back(account);
increment();
+ TRACE_CTOR(basic_accounts_iterator, "account_t&");
+ }
+ basic_accounts_iterator(const basic_accounts_iterator& i)
+ : iterator_facade_base<basic_accounts_iterator, account_t *,
+ boost::forward_traversal_tag>(i),
+ accounts_i(i.accounts_i), accounts_end(i.accounts_end) {
+ TRACE_CTOR(basic_accounts_iterator, "copy");
+ }
+ ~basic_accounts_iterator() throw() {
+ TRACE_DTOR(basic_accounts_iterator);
}
- ~basic_accounts_iterator() throw() {}
void increment();
@@ -227,8 +294,20 @@ public:
: sort_cmp(_sort_cmp), flatten_all(_flatten_all) {
push_back(account);
increment();
+ TRACE_CTOR(sorted_accounts_iterator, "account_t&, expr_t, bool");
+ }
+ sorted_accounts_iterator(const sorted_accounts_iterator& i)
+ : iterator_facade_base<sorted_accounts_iterator, account_t *,
+ boost::forward_traversal_tag>(i),
+ sort_cmp(i.sort_cmp), flatten_all(i.flatten_all),
+ accounts_list(i.accounts_list),
+ sorted_accounts_i(i.sorted_accounts_i),
+ sorted_accounts_end(i.sorted_accounts_end) {
+ TRACE_CTOR(sorted_accounts_iterator, "copy");
+ }
+ ~sorted_accounts_iterator() throw() {
+ TRACE_DTOR(sorted_accounts_iterator);
}
- ~sorted_accounts_iterator() throw() {}
void increment();
diff --git a/src/journal.cc b/src/journal.cc
index 0691954f..49d86ff0 100644
--- a/src/journal.cc
+++ b/src/journal.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -32,33 +32,37 @@
#include <system.hh>
#include "journal.h"
+#include "context.h"
#include "amount.h"
#include "commodity.h"
#include "pool.h"
#include "xact.h"
+#include "post.h"
#include "account.h"
namespace ledger {
journal_t::journal_t()
{
- TRACE_CTOR(journal_t, "");
initialize();
+ TRACE_CTOR(journal_t, "");
}
+#if 0
journal_t::journal_t(const path& pathname)
{
- TRACE_CTOR(journal_t, "path");
initialize();
read(pathname);
+ TRACE_CTOR(journal_t, "path");
}
journal_t::journal_t(const string& str)
{
- TRACE_CTOR(journal_t, "string");
initialize();
read(str);
+ TRACE_CTOR(journal_t, "string");
}
+#endif
journal_t::~journal_t()
{
@@ -80,9 +84,18 @@ journal_t::~journal_t()
void journal_t::initialize()
{
- master = new account_t;
- bucket = NULL;
- was_loaded = false;
+ master = new account_t;
+ bucket = NULL;
+ fixed_accounts = false;
+ fixed_payees = false;
+ fixed_commodities = false;
+ fixed_metadata = false;
+ current_context = NULL;
+ was_loaded = false;
+ force_checking = false;
+ check_payees = false;
+ day_break = false;
+ checking_style = CHECK_PERMISSIVE;
}
void journal_t::add_account(account_t * acct)
@@ -105,6 +118,201 @@ account_t * journal_t::find_account_re(const string& regexp)
return master->find_account_re(regexp);
}
+account_t * journal_t::register_account(const string& name, post_t * post,
+ account_t * master_account)
+{
+ account_t * result = NULL;
+
+ // If there any account aliases, substitute before creating an account
+ // object.
+ if (account_aliases.size() > 0) {
+ accounts_map::const_iterator i = account_aliases.find(name);
+ if (i != account_aliases.end())
+ result = (*i).second;
+ }
+
+ // Create the account object and associate it with the journal; this
+ // is registering the account.
+ if (! result)
+ result = master_account->find_account(name);
+
+ // If the account name being registered is "Unknown", check whether
+ // the payee indicates an account that should be used.
+ if (result->name == _("Unknown")) {
+ foreach (account_mapping_t& value, payees_for_unknown_accounts) {
+ if (value.first.match(post->xact->payee)) {
+ result = value.second;
+ break;
+ }
+ }
+ }
+
+ // Now that we have an account, make certain that the account is
+ // "known", if the user has requested validation of that fact.
+ if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) {
+ if (! result->has_flags(ACCOUNT_KNOWN)) {
+ if (! post) {
+ if (force_checking)
+ fixed_accounts = true;
+ result->add_flags(ACCOUNT_KNOWN);
+ }
+ else if (! fixed_accounts && post->_state != item_t::UNCLEARED) {
+ result->add_flags(ACCOUNT_KNOWN);
+ }
+ else if (checking_style == CHECK_WARNING) {
+ current_context->warning(STR(_("Unknown account '%1'")
+ << result->fullname()));
+ }
+ else if (checking_style == CHECK_ERROR) {
+ throw_(parse_error, _("Unknown account '%1'") << result->fullname());
+ }
+ }
+ }
+
+ return result;
+}
+
+string journal_t::register_payee(const string& name, xact_t * xact)
+{
+ string payee;
+
+ if (check_payees &&
+ (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR)) {
+ std::set<string>::iterator i = known_payees.find(name);
+
+ if (i == known_payees.end()) {
+ if (! xact) {
+ if (force_checking)
+ fixed_payees = true;
+ known_payees.insert(name);
+ }
+ else if (! fixed_payees && xact->_state != item_t::UNCLEARED) {
+ known_payees.insert(name);
+ }
+ else if (checking_style == CHECK_WARNING) {
+ current_context->warning(STR(_("Unknown payee '%1'") << name));
+ }
+ else if (checking_style == CHECK_ERROR) {
+ throw_(parse_error, _("Unknown payee '%1'") << name);
+ }
+ }
+ }
+
+ foreach (payee_mapping_t& value, payee_mappings) {
+ if (value.first.match(name)) {
+ payee = value.second;
+ break;
+ }
+ }
+
+ return payee.empty() ? name : payee;
+}
+
+void journal_t::register_commodity(commodity_t& comm,
+ variant<int, xact_t *, post_t *> context)
+{
+ if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) {
+ if (! comm.has_flags(COMMODITY_KNOWN)) {
+ if (context.which() == 0) {
+ if (force_checking)
+ fixed_commodities = true;
+ comm.add_flags(COMMODITY_KNOWN);
+ }
+ else if (! fixed_commodities &&
+ ((context.which() == 1 &&
+ boost::get<xact_t *>(context)->_state != item_t::UNCLEARED) ||
+ (context.which() == 2 &&
+ boost::get<post_t *>(context)->_state != item_t::UNCLEARED))) {
+ comm.add_flags(COMMODITY_KNOWN);
+ }
+ else if (checking_style == CHECK_WARNING) {
+ current_context->warning(STR(_("Unknown commodity '%1'") << comm));
+ }
+ else if (checking_style == CHECK_ERROR) {
+ throw_(parse_error, _("Unknown commodity '%1'") << comm);
+ }
+ }
+ }
+}
+
+void journal_t::register_metadata(const string& key, const value_t& value,
+ variant<int, xact_t *, post_t *> context)
+{
+ if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) {
+ std::set<string>::iterator i = known_tags.find(key);
+
+ if (i == known_tags.end()) {
+ if (context.which() == 0) {
+ if (force_checking)
+ fixed_metadata = true;
+ known_tags.insert(key);
+ }
+ else if (! fixed_metadata &&
+ ((context.which() == 1 &&
+ boost::get<xact_t *>(context)->_state != item_t::UNCLEARED) ||
+ (context.which() == 2 &&
+ boost::get<post_t *>(context)->_state != item_t::UNCLEARED))) {
+ known_tags.insert(key);
+ }
+ else if (checking_style == CHECK_WARNING) {
+ current_context->warning(STR(_("Unknown metadata tag '%1'") << key));
+ }
+ else if (checking_style == CHECK_ERROR) {
+ throw_(parse_error, _("Unknown metadata tag '%1'") << key);
+ }
+ }
+ }
+
+ if (! value.is_null()) {
+ std::pair<tag_check_exprs_map::iterator,
+ tag_check_exprs_map::iterator> range =
+ tag_check_exprs.equal_range(key);
+
+ for (tag_check_exprs_map::iterator i = range.first;
+ i != range.second;
+ ++i) {
+ bind_scope_t bound_scope
+ (*current_context->scope,
+ context.which() == 1 ?
+ static_cast<scope_t&>(*boost::get<xact_t *>(context)) :
+ static_cast<scope_t&>(*boost::get<post_t *>(context)));
+ value_scope_t val_scope(bound_scope, value);
+
+ if (! (*i).second.first.calc(val_scope).to_boolean()) {
+ if ((*i).second.second == expr_t::EXPR_ASSERTION)
+ throw_(parse_error,
+ _("Metadata assertion failed for (%1: %2): %3")
+ << key << value << (*i).second.first);
+ else
+ current_context->warning
+ (STR(_("Metadata check failed for (%1: %2): %3")
+ << key << value << (*i).second.first));
+ }
+ }
+ }
+}
+
+namespace {
+ void check_all_metadata(journal_t& journal,
+ variant<int, xact_t *, post_t *> context)
+ {
+ xact_t * xact = context.which() == 1 ? boost::get<xact_t *>(context) : NULL;
+ post_t * post = context.which() == 2 ? boost::get<post_t *>(context) : NULL;
+
+ if ((xact || post) && xact ? xact->metadata : post->metadata) {
+ foreach (const item_t::string_map::value_type& pair,
+ xact ? *xact->metadata : *post->metadata) {
+ const string& key(pair.first);
+
+ if (optional<value_t> value = pair.second.first)
+ journal.register_metadata(key, *value, context);
+ else
+ journal.register_metadata(key, NULL_VALUE, context);
+ }
+ }
+ }
+}
+
bool journal_t::add_xact(xact_t * xact)
{
xact->journal = this;
@@ -115,6 +323,29 @@ bool journal_t::add_xact(xact_t * xact)
}
extend_xact(xact);
+ check_all_metadata(*this, xact);
+
+ foreach (post_t * post, xact->posts) {
+ extend_post(*post, *this);
+ check_all_metadata(*this, post);
+ }
+
+ // If a transaction with this UUID has already been seen, simply do
+ // not add this one to the journal. However, all automated checks
+ // will have been performed by extend_xact, so asserts can still be
+ // applied to it.
+ if (optional<value_t> ref = xact->get_tag(_("UUID"))) {
+ std::pair<checksum_map_t::iterator, bool> result
+ = checksum_map.insert(checksum_map_t::value_type(ref->to_string(), xact));
+ if (! result.second) {
+ // jww (2012-02-27): Confirm that the xact in
+ // (*result.first).second is exact match in its significant
+ // details to xact.
+ xact->journal = NULL;
+ return false;
+ }
+ }
+
xacts.push_back(xact);
return true;
@@ -123,7 +354,7 @@ bool journal_t::add_xact(xact_t * xact)
void journal_t::extend_xact(xact_base_t * xact)
{
foreach (auto_xact_t * auto_xact, auto_xacts)
- auto_xact->extend_xact(*xact);
+ auto_xact->extend_xact(*xact, *current_context);
}
bool journal_t::remove_xact(xact_t * xact)
@@ -144,28 +375,36 @@ bool journal_t::remove_xact(xact_t * xact)
return true;
}
-std::size_t journal_t::read(std::istream& in,
- const path& pathname,
- account_t * master_alt,
- scope_t * scope)
+std::size_t journal_t::read(parse_context_stack_t& context)
{
std::size_t count = 0;
try {
- if (! scope)
- scope = scope_t::default_scope;
+ parse_context_t& current(context.get_current());
+ current_context = &current;
+
+ current.count = 0;
+ if (! current.scope)
+ current.scope = scope_t::default_scope;
- if (! scope)
+ if (! current.scope)
throw_(std::runtime_error,
_("No default scope in which to read journal file '%1'")
- << pathname);
+ << current.pathname);
- value_t strict = expr_t("strict").calc(*scope);
+ if (! current.master)
+ current.master = master;
- count = parse(in, *scope, master_alt ? master_alt : master,
- &pathname, strict.to_boolean());
+ count = read_textual(context);
+ if (count > 0) {
+ if (! current.pathname.empty())
+ sources.push_back(fileinfo_t(current.pathname));
+ else
+ sources.push_back(fileinfo_t());
+ }
}
catch (...) {
clear_xdata();
+ current_context = NULL;
throw;
}
@@ -177,23 +416,6 @@ std::size_t journal_t::read(std::istream& in,
return count;
}
-std::size_t journal_t::read(const path& pathname,
- account_t * master_account,
- scope_t * scope)
-{
- path filename = resolve_path(pathname);
-
- if (! exists(filename))
- throw_(std::runtime_error,
- _("Cannot read journal file '%1'") << filename);
-
- ifstream stream(filename);
- std::size_t count = read(stream, filename, master_account, scope);
- if (count > 0)
- sources.push_back(fileinfo_t(filename));
- return count;
-}
-
bool journal_t::has_xdata()
{
foreach (xact_t * xact, xacts)
diff --git a/src/journal.h b/src/journal.h
index ca6b6e4f..a7a84447 100644
--- a/src/journal.h
+++ b/src/journal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -45,6 +45,7 @@
#include "utils.h"
#include "times.h"
#include "mask.h"
+#include "expr.h"
namespace ledger {
@@ -52,17 +53,22 @@ class xact_base_t;
class xact_t;
class auto_xact_t;
class period_xact_t;
+class post_t;
class account_t;
-class scope_t;
-
-typedef std::list<xact_t *> xacts_list;
-typedef std::list<auto_xact_t *> auto_xacts_list;
-typedef std::list<period_xact_t *> period_xacts_list;
-
-typedef std::pair<mask_t, string> payee_mapping_t;
-typedef std::list<payee_mapping_t> payee_mappings_t;
-typedef std::pair<mask_t, account_t *> account_mapping_t;
-typedef std::list<account_mapping_t> account_mappings_t;
+class parse_context_t;
+class parse_context_stack_t;
+
+typedef std::list<xact_t *> xacts_list;
+typedef std::list<auto_xact_t *> auto_xacts_list;
+typedef std::list<period_xact_t *> period_xacts_list;
+typedef std::pair<mask_t, string> payee_mapping_t;
+typedef std::list<payee_mapping_t> payee_mappings_t;
+typedef std::pair<mask_t, account_t *> account_mapping_t;
+typedef std::list<account_mapping_t> account_mappings_t;
+typedef std::map<const string, account_t *> accounts_map;
+typedef std::map<string, xact_t *> checksum_map_t;
+typedef std::multimap<string,
+ expr_t::check_expr_pair> tag_check_exprs_map;
class journal_t : public noncopyable
{
@@ -79,9 +85,9 @@ public:
}
fileinfo_t(const path& _filename)
: filename(_filename), from_stream(false) {
- TRACE_CTOR(journal_t::fileinfo_t, "const path&");
size = file_size(*filename);
modtime = posix_time::from_time_t(last_write_time(*filename));
+ TRACE_CTOR(journal_t::fileinfo_t, "const path&");
}
fileinfo_t(const fileinfo_t& info)
: filename(info.filename), size(info.size),
@@ -115,13 +121,36 @@ public:
auto_xacts_list auto_xacts;
period_xacts_list period_xacts;
std::list<fileinfo_t> sources;
+ std::set<string> known_payees;
+ std::set<string> known_tags;
+ bool fixed_accounts;
+ bool fixed_payees;
+ bool fixed_commodities;
+ bool fixed_metadata;
+ bool was_loaded;
+ bool force_checking;
+ bool check_payees;
+ bool day_break;
payee_mappings_t payee_mappings;
account_mappings_t account_mappings;
- bool was_loaded;
+ accounts_map account_aliases;
+ account_mappings_t payees_for_unknown_accounts;
+ checksum_map_t checksum_map;
+ tag_check_exprs_map tag_check_exprs;
+ optional<expr_t> value_expr;
+ parse_context_t * current_context;
+
+ enum checking_style_t {
+ CHECK_PERMISSIVE,
+ CHECK_WARNING,
+ CHECK_ERROR
+ } checking_style;
journal_t();
+#if 0
journal_t(const path& pathname);
journal_t(const string& str);
+#endif
~journal_t();
void initialize();
@@ -133,13 +162,19 @@ public:
return sources.end();
}
- // These four methods are delegated to the current session, since all
- // accounts processed are gathered together at the session level.
void add_account(account_t * acct);
bool remove_account(account_t * acct);
account_t * find_account(const string& name, bool auto_create = true);
account_t * find_account_re(const string& regexp);
+ account_t * register_account(const string& name, post_t * post,
+ account_t * master = NULL);
+ string register_payee(const string& name, xact_t * xact);
+ void register_commodity(commodity_t& comm,
+ variant<int, xact_t *, post_t *> context);
+ void register_metadata(const string& key, const value_t& value,
+ variant<int, xact_t *, post_t *> context);
+
bool add_xact(xact_t * xact);
void extend_xact(xact_base_t * xact);
bool remove_xact(xact_t * xact);
@@ -163,25 +198,16 @@ public:
return period_xacts.end();
}
- std::size_t read(std::istream& in,
- const path& pathname,
- account_t * master = NULL,
- scope_t * scope = NULL);
- std::size_t read(const path& pathname,
- account_t * master = NULL,
- scope_t * scope = NULL);
-
- std::size_t parse(std::istream& in,
- scope_t& session_scope,
- account_t * master = NULL,
- const path * original_file = NULL,
- bool strict = false);
+ std::size_t read(parse_context_stack_t& context);
bool has_xdata();
void clear_xdata();
bool valid() const;
+private:
+ std::size_t read_textual(parse_context_stack_t& context);
+
#if defined(HAVE_BOOST_SERIALIZATION)
private:
/** Serialization. */
@@ -198,6 +224,7 @@ private:
ar & sources;
ar & payee_mappings;
ar & account_mappings;
+ ar & checksum_map;
}
#endif // HAVE_BOOST_SERIALIZATION
};
diff --git a/src/lookup.cc b/src/lookup.cc
index 452727d6..6f0c33ea 100644
--- a/src/lookup.cc
+++ b/src/lookup.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -186,12 +186,14 @@ lookup_probable_account(const string& ident,
if (in_order_match && pos - index < 3)
addend++;
+#if 0
#if !defined(HAVE_BOOST_REGEX_UNICODE)
if (pos == 0 || (pos > 0 && !std::isalnum(value_key[pos - 1])))
addend++;
#else
// jww (2010-03-07): Not yet implemented
#endif
+#endif
last_match_pos = pos;
}
diff --git a/src/lookup.h b/src/lookup.h
index 8e83b84e..ba64b0b5 100644
--- a/src/lookup.h
+++ b/src/lookup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/main.cc b/src/main.cc
index 820f920d..0130d5c6 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -58,6 +58,7 @@ int main(int argc, char * argv[], char * envp[])
// --verbose ; turns on logging
// --debug CATEGORY ; turns on debug logging
// --trace LEVEL ; turns on trace logging
+ // --memory ; turns on memory usage tracing
handle_debug_options(argc, argv);
#if defined(VERIFY_ON)
IF_VERIFY() initialize_memory_tracing();
@@ -80,13 +81,12 @@ int main(int argc, char * argv[], char * envp[])
::textdomain("ledger");
#endif
- std::auto_ptr<global_scope_t> global_scope;
+ global_scope_t * global_scope = NULL;
try {
// Create the session object, which maintains nearly all state relating to
// this invocation of Ledger; and register all known journal parsers.
- global_scope.reset(new global_scope_t(envp));
-
+ global_scope = new global_scope_t(envp);
global_scope->session().set_flush_on_next_data_file(true);
// Construct an STL-style argument list from the process command arguments
@@ -95,8 +95,7 @@ int main(int argc, char * argv[], char * envp[])
args.push_back(argv[i]);
// Look for options and a command verb in the command-line arguments
- bind_scope_t bound_scope(*global_scope.get(), global_scope->report());
-
+ bind_scope_t bound_scope(*global_scope, global_scope->report());
args = global_scope->read_command_arguments(bound_scope, args);
if (global_scope->HANDLED(script_)) {
@@ -112,8 +111,8 @@ int main(int argc, char * argv[], char * envp[])
char * p = skip_ws(line);
if (*p && *p != '#')
- status = global_scope->execute_command_wrapper(split_arguments(p),
- true);
+ status =
+ global_scope->execute_command_wrapper(split_arguments(p), true);
}
}
else if (! args.empty()) {
@@ -187,30 +186,35 @@ int main(int argc, char * argv[], char * envp[])
}
}
catch (const std::exception& err) {
- if (global_scope.get())
+ if (global_scope)
global_scope->report_error(err);
else
std::cerr << "Exception during initialization: " << err.what()
<< std::endl;
}
- catch (int _status) {
- status = _status; // used for a "quick" exit, and is used only
- // if help text (such as --help) was displayed
+ catch (const error_count& errors) {
+ // used for a "quick" exit, and is used only if help text (such as
+ // --help) was displayed
+ status = static_cast<int>(errors.count);
}
// If memory verification is being performed (which can be very slow), clean
// up everything by closing the session and deleting the session object, and
// then shutting down the memory tracing subsystem. Otherwise, let it all
// leak because we're about to exit anyway.
+#if defined(VERIFY_ON)
IF_VERIFY() {
- global_scope.reset();
+ checked_delete(global_scope);
INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
#if defined(VERIFY_ON)
shutdown_memory_tracing();
#endif
- } else {
- INFO("Ledger ended");
+ } else
+#endif
+ {
+ global_scope->quick_close();
+ INFO("Ledger ended"); // let global_scope leak!
}
// Return the final status to the operating system, either 1 for error or 0
diff --git a/src/mask.cc b/src/mask.cc
index 52907cfe..afb68ca0 100644
--- a/src/mask.cc
+++ b/src/mask.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -37,8 +37,8 @@ namespace ledger {
mask_t::mask_t(const string& pat) : expr()
{
- TRACE_CTOR(mask_t, "const string&");
*this = pat;
+ TRACE_CTOR(mask_t, "const string&");
}
mask_t& mask_t::operator=(const string& pat)
diff --git a/src/mask.h b/src/mask.h
index e72347ad..15929b2e 100644
--- a/src/mask.h
+++ b/src/mask.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/op.cc b/src/op.cc
index 537f5371..8be0796a 100644
--- a/src/op.cc
+++ b/src/op.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -38,31 +38,41 @@
namespace ledger {
-namespace {
- value_t split_cons_expr(expr_t::ptr_op_t op)
- {
- if (op->kind == expr_t::op_t::O_CONS) {
- value_t seq;
- seq.push_back(expr_value(op->left()));
-
- expr_t::ptr_op_t next = op->right();
- while (next) {
- expr_t::ptr_op_t value_op;
- if (next->kind == expr_t::op_t::O_CONS) {
- value_op = next->left();
- next = next->right();
- } else {
- value_op = next;
- next = NULL;
- }
- seq.push_back(expr_value(value_op));
+void intrusive_ptr_add_ref(const expr_t::op_t * op)
+{
+ op->acquire();
+}
+
+void intrusive_ptr_release(const expr_t::op_t * op)
+{
+ op->release();
+}
+
+value_t split_cons_expr(expr_t::ptr_op_t op)
+{
+ if (op->kind == expr_t::op_t::O_CONS) {
+ value_t seq;
+ seq.push_back(expr_value(op->left()));
+
+ expr_t::ptr_op_t next = op->right();
+ while (next) {
+ expr_t::ptr_op_t value_op;
+ if (next->kind == expr_t::op_t::O_CONS) {
+ value_op = next->left();
+ next = next->has_right() ? next->right() : NULL;
+ } else {
+ value_op = next;
+ next = NULL;
}
- return seq;
- } else {
- return expr_value(op);
+ seq.push_back(expr_value(value_op));
}
+ return seq;
+ } else {
+ return expr_value(op);
}
+}
+namespace {
inline void check_type_context(scope_t& scope, value_t& result)
{
if (scope.type_required() &&
@@ -76,12 +86,31 @@ namespace {
}
}
-expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth)
+expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth,
+ scope_t * param_scope)
{
- if (is_ident()) {
- DEBUG("expr.compile", "lookup: " << as_ident());
+ scope_t * scope_ptr = &scope;
+ unique_ptr<scope_t> bound_scope;
+ expr_t::ptr_op_t result;
+
+#if defined(DEBUG_ON)
+ if (SHOW_DEBUG("expr.compile")) {
+ for (int i = 0; i < depth; i++)
+ ledger::_log_buffer << '.';
+ DEBUG("expr.compile", "");
+ }
+#endif
+
+ assert(kind < LAST);
- if (ptr_op_t def = scope.lookup(symbol_t::FUNCTION, as_ident())) {
+ if (is_ident()) {
+ DEBUG("expr.compile", "Lookup: " << as_ident() << " in " << scope_ptr);
+ ptr_op_t def;
+ if (param_scope)
+ def = param_scope->lookup(symbol_t::FUNCTION, as_ident());
+ if (! def)
+ def = scope_ptr->lookup(symbol_t::FUNCTION, as_ident());
+ if (def) {
// Identifier references are first looked up at the point of
// definition, and then at the point of every use if they could
// not be found there.
@@ -91,70 +120,144 @@ expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope, const int depth)
def->dump(*_log_stream, 0);
}
#endif // defined(DEBUG_ON)
- return copy(def);
+ result = copy(def);
}
else if (left()) {
- return copy();
+ result = copy();
+ }
+ else {
+ result = this;
}
- return this;
}
-
- if (kind < TERMINALS)
- return this;
-
- if (kind == O_DEFINE) {
+ else if (is_scope()) {
+ shared_ptr<scope_t> subscope(new symbol_scope_t(*scope_t::empty_scope));
+ set_scope(subscope);
+ bound_scope.reset(new bind_scope_t(*scope_ptr, *subscope.get()));
+ scope_ptr = bound_scope.get();
+ }
+ else if (kind < TERMINALS) {
+ result = this;
+ }
+ else if (kind == O_DEFINE) {
switch (left()->kind) {
- case IDENT:
- scope.define(symbol_t::FUNCTION, left()->as_ident(), right());
+ case IDENT: {
+ ptr_op_t node(right()->compile(*scope_ptr, depth + 1, param_scope));
+
+ DEBUG("expr.compile",
+ "Defining " << left()->as_ident() << " in " << scope_ptr);
+ scope_ptr->define(symbol_t::FUNCTION, left()->as_ident(), node);
break;
+ }
+
case O_CALL:
if (left()->left()->is_ident()) {
ptr_op_t node(new op_t(op_t::O_LAMBDA));
node->set_left(left()->right());
node->set_right(right());
- scope.define(symbol_t::FUNCTION, left()->left()->as_ident(), node);
- } else {
- throw_(compile_error, _("Invalid function definition"));
+ node = node->compile(*scope_ptr, depth + 1, param_scope);
+
+ DEBUG("expr.compile",
+ "Defining " << left()->left()->as_ident() << " in " << scope_ptr);
+ scope_ptr->define(symbol_t::FUNCTION, left()->left()->as_ident(), node);
+ break;
}
- break;
+ // fall through...
+
default:
throw_(compile_error, _("Invalid function definition"));
}
- return wrap_value(value_t());
+ result = wrap_value(NULL_VALUE);
+ }
+ else if (kind == O_LAMBDA) {
+ symbol_scope_t params(param_scope ? *param_scope : *scope_t::empty_scope);
+
+ for (ptr_op_t sym = left();
+ sym;
+ sym = sym->has_right() ? sym->right() : NULL) {
+ ptr_op_t varname = sym->kind == O_CONS ? sym->left() : sym;
+
+ if (! varname->is_ident()) {
+ std::ostringstream buf;
+ varname->dump(buf, 0);
+ throw_(calc_error,
+ _("Invalid function or lambda parameter: %1") << buf.str());
+ } else {
+ DEBUG("expr.compile",
+ "Defining function parameter " << varname->as_ident());
+ params.define(symbol_t::FUNCTION, varname->as_ident(),
+ new op_t(PLUG));
+ }
+ }
+
+ ptr_op_t rhs(right()->compile(*scope_ptr, depth + 1, &params));
+ if (rhs == right())
+ result = this;
+ else
+ result = copy(left(), rhs);
}
- ptr_op_t lhs(left()->compile(scope, depth));
- ptr_op_t rhs(kind > UNARY_OPERATORS && has_right() ?
- (kind == O_LOOKUP ? right() :
- right()->compile(scope, depth)) : NULL);
+ if (! result) {
+ if (! left())
+ throw_(calc_error, _("Syntax error"));
+
+ ptr_op_t lhs(left()->compile(*scope_ptr, depth + 1, param_scope));
+ ptr_op_t rhs(kind > UNARY_OPERATORS && has_right() ?
+ (kind == O_LOOKUP ? right() :
+ right()->compile(*scope_ptr, depth + 1, param_scope)) : NULL);
+
+ if (lhs == left() && (! rhs || rhs == right())) {
+ result = this;
+ } else {
+ ptr_op_t intermediate(copy(lhs, rhs));
+
+ // Reduce constants immediately if possible
+ if ((! lhs || lhs->is_value()) && (! rhs || rhs->is_value()))
+ result = wrap_value(intermediate->calc(*scope_ptr, NULL, depth + 1));
+ else
+ result = intermediate;
+ }
+ }
- if (lhs == left() && (! rhs || rhs == right()))
- return this;
+#if defined(DEBUG_ON)
+ if (SHOW_DEBUG("expr.compile")) {
+ for (int i = 0; i < depth; i++)
+ ledger::_log_buffer << '.';
+ DEBUG("expr.compile", "");
+ }
+#endif
- ptr_op_t intermediate(copy(lhs, rhs));
+ return result;
+}
- // Reduce constants immediately if possible
- if ((! lhs || lhs->is_value()) && (! rhs || rhs->is_value()))
- return wrap_value(intermediate->calc(scope, NULL, depth));
+namespace {
+ expr_t::ptr_op_t lookup_ident(expr_t::ptr_op_t op, scope_t& scope)
+ {
+ expr_t::ptr_op_t def = op->left();
- return intermediate;
+ // If no definition was pre-compiled for this identifier, look it up
+ // in the current scope.
+ if (! def || def->kind == expr_t::op_t::PLUG) {
+ DEBUG("scope.symbols", "Looking for IDENT '" << op->as_ident() << "'");
+ def = scope.lookup(symbol_t::FUNCTION, op->as_ident());
+ }
+ if (! def)
+ throw_(calc_error, _("Unknown identifier '%1'") << op->as_ident());
+ return def;
+ }
}
value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
{
-#if defined(DEBUG_ON)
- bool skip_debug = false;
-#endif
try {
value_t result;
#if defined(DEBUG_ON)
- if (! skip_debug && SHOW_DEBUG("expr.calc")) {
+ if (SHOW_DEBUG("expr.calc")) {
for (int i = 0; i < depth; i++)
ledger::_log_buffer << '.';
- ledger::_log_buffer << op_context(this) << " => ...";
+ ledger::_log_buffer << op_context(this) << " => ...";
DEBUG("expr.calc", "");
}
#endif
@@ -165,77 +268,38 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
break;
case O_DEFINE:
- //result = left()->calc(scope, locus, depth + 1);
result = NULL_VALUE;
break;
- case IDENT: {
- ptr_op_t definition = left();
- if (! definition) {
- // If no definition was pre-compiled for this identifier, look it
- // up in the current scope.
- definition = scope.lookup(symbol_t::FUNCTION, as_ident());
+ case IDENT:
+ if (ptr_op_t definition = lookup_ident(this, scope)) {
+ // Evaluating an identifier is the same as calling its definition
+ // directly
+ result = definition->calc(scope, locus, depth + 1);
+ check_type_context(scope, result);
}
- if (! definition)
- throw_(calc_error, _("Unknown identifier '%1'") << as_ident());
-
- // Evaluating an identifier is the same as calling its definition
- // directly, so we create an empty call_scope_t to reflect the scope for
- // this implicit call.
- call_scope_t call_args(scope, locus, depth);
- result = definition->compile(call_args, depth + 1)
- ->calc(call_args, locus, depth + 1);
- check_type_context(scope, result);
break;
- }
case FUNCTION: {
- // Evaluating a FUNCTION is the same as calling it directly; this happens
- // when certain functions-that-look-like-variables (such as "amount") are
- // resolved.
- call_scope_t call_args(scope, locus, depth);
+ // Evaluating a FUNCTION is the same as calling it directly; this
+ // happens when certain functions-that-look-like-variables (such as
+ // "amount") are resolved.
+ call_scope_t call_args(scope, locus, depth + 1);
result = as_function()(call_args);
check_type_context(scope, result);
-#if defined(DEBUG_ON)
- skip_debug = true;
-#endif
break;
}
- case O_LAMBDA: {
- call_scope_t& call_args(downcast<call_scope_t>(scope));
- std::size_t args_count(call_args.size());
- std::size_t args_index(0);
- symbol_scope_t call_scope(call_args);
- ptr_op_t sym(left());
-
- for (; sym; sym = sym->has_right() ? sym->right() : NULL) {
- ptr_op_t varname = sym;
- if (sym->kind == O_CONS)
- varname = sym->left();
-
- if (! varname->is_ident()) {
- throw_(calc_error, _("Invalid function definition"));
- }
- else if (args_index == args_count) {
- call_scope.define(symbol_t::FUNCTION, varname->as_ident(),
- wrap_value(NULL_VALUE));
- }
- else {
- DEBUG("expr.compile",
- "Defining function parameter " << varname->as_ident());
- call_scope.define(symbol_t::FUNCTION, varname->as_ident(),
- wrap_value(call_args[args_index++]));
- }
+ case SCOPE:
+ assert(! is_scope_unset());
+ if (is_scope_unset()) {
+ symbol_scope_t subscope(scope);
+ result = left()->calc(subscope, locus, depth + 1);
+ } else {
+ bind_scope_t bound_scope(scope, *as_scope());
+ result = left()->calc(bound_scope, locus, depth + 1);
}
-
- if (args_index < args_count)
- throw_(calc_error,
- _("Too few arguments in function call (saw %1)") << args_count);
-
- result = right()->calc(call_scope, locus, depth + 1);
break;
- }
case O_LOOKUP: {
context_scope_t context_scope(scope, value_t::SCOPE);
@@ -252,43 +316,14 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
break;
}
- case O_CALL: {
- call_scope_t call_args(scope, locus, depth);
- if (has_right())
- call_args.set_args(split_cons_expr(right()));
-
- ptr_op_t func = left();
- const string& name(func->as_ident());
-
- func = func->left();
- if (! func)
- func = scope.lookup(symbol_t::FUNCTION, name);
- if (! func)
- throw_(calc_error, _("Calling unknown function '%1'") << name);
-
-#if defined(DEBUG_ON)
- if (! skip_debug && SHOW_DEBUG("expr.calc")) {
- for (int i = 0; i < depth; i++)
- ledger::_log_buffer << '.';
- ledger::_log_buffer << " args: ";
- if (call_args.args.is_sequence()) {
- foreach (value_t& arg, call_args)
- ledger::_log_buffer << arg << " ";
- } else {
- ledger::_log_buffer << call_args.args[0] << " ";
- }
- DEBUG("expr.calc", "");
- }
-#endif
-
- if (func->is_function())
- result = func->as_function()(call_args);
- else
- result = func->calc(call_args, locus, depth + 1);
-
+ case O_CALL:
+ result = calc_call(scope, locus, depth);
check_type_context(scope, result);
break;
- }
+
+ case O_LAMBDA:
+ result = expr_value(this);
+ break;
case O_MATCH:
result = (right()->calc(scope, locus, depth + 1).as_mask()
@@ -358,7 +393,6 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
case O_QUERY:
assert(right());
assert(right()->kind == O_COLON);
-
if (value_t temp = left()->calc(scope, locus, depth + 1))
result = right()->left()->calc(scope, locus, depth + 1);
else
@@ -370,63 +404,19 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
break;
case O_CONS:
- result = left()->calc(scope, locus, depth + 1);
- DEBUG("op.cons", "car = " << result);
-
- if (has_right()) {
- value_t temp;
- temp.push_back(result);
-
- ptr_op_t next = right();
- while (next) {
- ptr_op_t value_op;
- if (next->kind == O_CONS) {
- value_op = next->left();
- next = next->right();
- } else {
- value_op = next;
- next = NULL;
- }
- temp.push_back(value_op->calc(scope, locus, depth + 1));
- DEBUG("op.cons", "temp now = " << temp);
- }
- result = temp;
- }
+ result = calc_cons(scope, locus, depth);
break;
- case O_SEQ: {
- symbol_scope_t seq_scope(scope);
-
- // An O_SEQ is very similar to an O_CONS except that only the last result
- // value in the series is kept. O_CONS builds up a list.
- //
- // Another feature of O_SEQ is that it pushes a new symbol scope onto the
- // stack.
- result = left()->calc(seq_scope, locus, depth + 1);
-
- if (has_right()) {
- ptr_op_t next = right();
- while (next) {
- ptr_op_t value_op;
- if (next->kind == O_SEQ) {
- value_op = next->left();
- next = next->right();
- } else {
- value_op = next;
- next = NULL;
- }
- result = value_op->calc(seq_scope, locus, depth + 1);
- }
- }
+ case O_SEQ:
+ result = calc_seq(scope, locus, depth);
break;
- }
default:
throw_(calc_error, _("Unexpected expr node '%1'") << op_context(this));
}
#if defined(DEBUG_ON)
- if (! skip_debug && SHOW_DEBUG("expr.calc")) {
+ if (SHOW_DEBUG("expr.calc")) {
for (int i = 0; i < depth; i++)
ledger::_log_buffer << '.';
ledger::_log_buffer << op_context(this) << " => ";
@@ -446,6 +436,184 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
}
namespace {
+ expr_t::ptr_op_t find_definition(expr_t::ptr_op_t op, scope_t& scope,
+ expr_t::ptr_op_t * locus, const int depth,
+ int recursion_depth = 0)
+ {
+ // If the object we are apply call notation to is a FUNCTION value
+ // or a O_LAMBDA expression, then this is the object we want to
+ // call.
+ if (op->is_function() || op->kind == expr_t::op_t::O_LAMBDA)
+ return op;
+
+ if (recursion_depth > 256)
+ throw_(value_error, _("Function recursion_depth too deep (> 256)"));
+
+ // If it's an identifier, look up its definition and see if it's a
+ // function.
+ if (op->is_ident())
+ return find_definition(lookup_ident(op, scope), scope,
+ locus, depth, recursion_depth + 1);
+
+ // Value objects might be callable if they contain an expression.
+ if (op->is_value()) {
+ value_t def(op->as_value());
+ if (is_expr(def))
+ return find_definition(as_expr(def), scope, locus, depth,
+ recursion_depth + 1);
+ else
+ throw_(value_error, _("Cannot call %1 as a function") << def.label());
+ }
+
+ // Resolve ordinary expressions.
+ return find_definition(expr_t::op_t::wrap_value(op->calc(scope, locus,
+ depth + 1)),
+ scope, locus, depth + 1, recursion_depth + 1);
+ }
+
+ value_t call_lambda(expr_t::ptr_op_t func, scope_t& scope,
+ call_scope_t& call_args, expr_t::ptr_op_t * locus,
+ const int depth)
+ {
+ std::size_t args_index(0);
+ std::size_t args_count(call_args.size());
+
+ symbol_scope_t args_scope(*scope_t::empty_scope);
+
+ for (expr_t::ptr_op_t sym = func->left();
+ sym;
+ sym = sym->has_right() ? sym->right() : NULL) {
+ expr_t::ptr_op_t varname =
+ sym->kind == expr_t::op_t::O_CONS ? sym->left() : sym;
+ if (! varname->is_ident()) {
+ throw_(calc_error, _("Invalid function definition"));
+ }
+ else if (args_index == args_count) {
+ DEBUG("expr.calc", "Defining function argument as null: "
+ << varname->as_ident());
+ args_scope.define(symbol_t::FUNCTION, varname->as_ident(),
+ expr_t::op_t::wrap_value(NULL_VALUE));
+ }
+ else {
+ DEBUG("expr.calc", "Defining function argument from call_args: "
+ << varname->as_ident());
+ args_scope.define(symbol_t::FUNCTION, varname->as_ident(),
+ expr_t::op_t::wrap_value(call_args[args_index++]));
+ }
+ }
+
+ if (args_index < args_count)
+ throw_(calc_error,
+ _("Too few arguments in function call (saw %1, wanted %2)")
+ << args_count << args_index);
+
+ if (func->right()->is_scope()) {
+ bind_scope_t outer_scope(scope, *func->right()->as_scope());
+ bind_scope_t bound_scope(outer_scope, args_scope);
+
+ return func->right()->left()->calc(bound_scope, locus, depth + 1);
+ } else {
+ return func->right()->calc(args_scope, locus, depth + 1);
+ }
+ }
+}
+
+
+value_t expr_t::op_t::call(const value_t& args, scope_t& scope,
+ ptr_op_t * locus, const int depth)
+{
+ call_scope_t call_args(scope, locus, depth + 1);
+ call_args.set_args(args);
+
+ if (is_function())
+ return as_function()(call_args);
+ else if (kind == O_LAMBDA)
+ return call_lambda(this, scope, call_args, locus, depth);
+ else
+ return find_definition(this, scope, locus, depth)
+ ->calc(call_args, locus, depth);
+}
+
+value_t expr_t::op_t::calc_call(scope_t& scope, ptr_op_t * locus,
+ const int depth)
+{
+ ptr_op_t func = left();
+ string name = func->is_ident() ? func->as_ident() : "<value expr>";
+
+ func = find_definition(func, scope, locus, depth);
+
+ call_scope_t call_args(scope, locus, depth + 1);
+ if (has_right())
+ call_args.set_args(split_cons_expr(right()));
+
+ try {
+ if (func->is_function()) {
+ return func->as_function()(call_args);
+ } else {
+ assert(func->kind == O_LAMBDA);
+ return call_lambda(func, scope, call_args, locus, depth);
+ }
+ }
+ catch (const std::exception&) {
+ add_error_context(_("While calling function '%1 %2':" << name
+ << call_args.args));
+ throw;
+ }
+}
+
+value_t expr_t::op_t::calc_cons(scope_t& scope, ptr_op_t * locus,
+ const int depth)
+{
+ value_t result = left()->calc(scope, locus, depth + 1);
+ if (has_right()) {
+ value_t temp;
+ temp.push_back(result);
+
+ ptr_op_t next = right();
+ while (next) {
+ ptr_op_t value_op;
+ if (next->kind == O_CONS) {
+ value_op = next->left();
+ next = next->has_right() ? next->right() : NULL;
+ } else {
+ value_op = next;
+ next = NULL;
+ }
+ temp.push_back(value_op->calc(scope, locus, depth + 1));
+ }
+ result = temp;
+ }
+ return result;
+}
+
+value_t expr_t::op_t::calc_seq(scope_t& scope, ptr_op_t * locus,
+ const int depth)
+{
+ // An O_SEQ is very similar to an O_CONS except that only the last
+ // result value in the series is kept. O_CONS builds up a list.
+ //
+ // Another feature of O_SEQ is that it pushes a new symbol scope onto
+ // the stack. We evaluate the left side here to catch any
+ // side-effects, such as definitions in the case of 'x = 1; x'.
+ value_t result = left()->calc(scope, locus, depth + 1);
+ if (has_right()) {
+ ptr_op_t next = right();
+ while (next) {
+ ptr_op_t value_op;
+ if (next->kind == O_SEQ) {
+ value_op = next->left();
+ next = next->right();
+ } else {
+ value_op = next;
+ next = NULL;
+ }
+ result = value_op->calc(scope, locus, depth + 1);
+ }
+ }
+ return result;
+}
+
+namespace {
bool print_cons(std::ostream& out, const expr_t::const_ptr_op_t op,
const expr_t::op_t::context_t& context)
{
@@ -476,9 +644,8 @@ namespace {
if (op->has_right()) {
out << "; ";
-
- if (op->right()->kind == expr_t::op_t::O_CONS)
- found = print_cons(out, op->right(), context);
+ if (op->right()->kind == expr_t::op_t::O_SEQ)
+ found = print_seq(out, op->right(), context);
else if (op->right()->print(out, context))
found = true;
}
@@ -515,6 +682,11 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const
out << "<FUNCTION>";
break;
+ case SCOPE:
+ if (left() && left()->print(out, context))
+ found = true;
+ break;
+
case O_NOT:
out << "! ";
if (left() && left()->print(out, context))
@@ -625,7 +797,6 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const
case O_CONS:
found = print_cons(out, this, context);
break;
-
case O_SEQ:
found = print_seq(out, this, context);
break;
@@ -713,6 +884,10 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const
out << " ";
switch (kind) {
+ case PLUG:
+ out << "PLUG";
+ break;
+
case VALUE:
out << "VALUE: ";
as_value().dump(out);
@@ -726,6 +901,14 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const
out << "FUNCTION";
break;
+ case SCOPE:
+ out << "SCOPE: ";
+ if (is_scope_unset())
+ out << "null";
+ else
+ out << as_scope().get();
+ break;
+
case O_DEFINE: out << "O_DEFINE"; break;
case O_LOOKUP: out << "O_LOOKUP"; break;
case O_LAMBDA: out << "O_LAMBDA"; break;
@@ -765,7 +948,7 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const
// An identifier is a special non-terminal, in that its left() can
// hold the compiled definition of the identifier.
- if (kind > TERMINALS || is_ident()) {
+ if (kind > TERMINALS || is_scope() || is_ident()) {
if (left()) {
left()->dump(out, depth + 1);
if (kind > UNARY_OPERATORS && has_right())
diff --git a/src/op.h b/src/op.h
index 6f7d7904..973ba67f 100644
--- a/src/op.h
+++ b/src/op.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -58,21 +58,25 @@ private:
mutable short refc;
ptr_op_t left_;
- variant<ptr_op_t, // used by all binary operators
+ variant<boost::blank,
+ ptr_op_t, // used by all binary operators
value_t, // used by constant VALUE
string, // used by constant IDENT
- expr_t::func_t // used by terminal FUNCTION
+ expr_t::func_t, // used by terminal FUNCTION
+ shared_ptr<scope_t> // used by terminal SCOPE
> data;
public:
enum kind_t {
// Constants
+ PLUG,
VALUE,
IDENT,
CONSTANTS,
FUNCTION,
+ SCOPE,
TERMINALS,
@@ -173,7 +177,7 @@ public:
return kind == FUNCTION;
}
expr_t::func_t& as_function_lval() {
- assert(kind == FUNCTION);
+ assert(is_function());
return boost::get<expr_t::func_t>(data);
}
const expr_t::func_t& as_function() const {
@@ -183,21 +187,41 @@ public:
data = val;
}
+ bool is_scope() const {
+ return kind == SCOPE;
+ }
+ bool is_scope_unset() const {
+ return data.which() == 0;
+ }
+ shared_ptr<scope_t> as_scope_lval() {
+ assert(is_scope());
+ return boost::get<shared_ptr<scope_t> >(data);
+ }
+ const shared_ptr<scope_t> as_scope() const {
+ return const_cast<op_t *>(this)->as_scope_lval();
+ }
+ void set_scope(shared_ptr<scope_t> val) {
+ data = val;
+ }
+
+ // These three functions must use 'kind == IDENT' rather than
+ // 'is_ident()', because they are called before the `data' member gets
+ // set, which is_ident() tests.
ptr_op_t& left() {
- assert(kind > TERMINALS || kind == IDENT);
+ assert(kind > TERMINALS || kind == IDENT || is_scope());
return left_;
}
const ptr_op_t& left() const {
- assert(kind > TERMINALS || kind == IDENT);
+ assert(kind > TERMINALS || kind == IDENT || is_scope());
return left_;
}
void set_left(const ptr_op_t& expr) {
- assert(kind > TERMINALS || kind == IDENT);
+ assert(kind > TERMINALS || kind == IDENT || is_scope());
left_ = expr;
}
ptr_op_t& as_op_lval() {
- assert(kind > TERMINALS || kind == IDENT);
+ assert(kind > TERMINALS || is_ident());
return boost::get<ptr_op_t>(data);
}
const ptr_op_t& as_op() const {
@@ -219,7 +243,7 @@ public:
bool has_right() const {
if (kind < TERMINALS)
return false;
- return as_op();
+ return data.which() != 0 && as_op();
}
private:
@@ -237,12 +261,8 @@ private:
checked_delete(this);
}
- friend inline void intrusive_ptr_add_ref(const op_t * op) {
- op->acquire();
- }
- friend inline void intrusive_ptr_release(const op_t * op) {
- op->release();
- }
+ friend void intrusive_ptr_add_ref(const op_t * op);
+ friend void intrusive_ptr_release(const op_t * op);
ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const {
ptr_op_t node(new_node(kind, _left, _right));
@@ -255,10 +275,14 @@ public:
static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL,
ptr_op_t _right = NULL);
- ptr_op_t compile(scope_t& scope, const int depth = 0);
+ ptr_op_t compile(scope_t& scope, const int depth = 0,
+ scope_t * param_scope = NULL);
value_t calc(scope_t& scope, ptr_op_t * locus = NULL,
const int depth = 0);
+ value_t call(const value_t& args, scope_t& scope,
+ ptr_op_t * locus = NULL, const int depth = 0);
+
struct context_t
{
ptr_op_t expr_op;
@@ -284,6 +308,12 @@ public:
static ptr_op_t wrap_value(const value_t& val);
static ptr_op_t wrap_functor(expr_t::func_t fobj);
+ static ptr_op_t wrap_scope(shared_ptr<scope_t> sobj);
+
+private:
+ value_t calc_call(scope_t& scope, ptr_op_t * locus, const int depth);
+ value_t calc_cons(scope_t& scope, ptr_op_t * locus, const int depth);
+ value_t calc_seq(scope_t& scope, ptr_op_t * locus, const int depth);
#if defined(HAVE_BOOST_SERIALIZATION)
private:
@@ -295,13 +325,13 @@ private:
void serialize(Archive& ar, const unsigned int /* version */) {
ar & refc;
ar & kind;
- if (Archive::is_loading::value || ! left_ || left_->kind != FUNCTION) {
+ if (Archive::is_loading::value || ! left_ || ! left_->is_function()) {
ar & left_;
} else {
ptr_op_t temp_op;
ar & temp_op;
}
- if (Archive::is_loading::value || kind == VALUE || kind == IDENT ||
+ if (Archive::is_loading::value || is_value() || is_ident() ||
(kind > UNARY_OPERATORS &&
(! has_right() || ! right()->is_function()))) {
ar & data;
@@ -341,6 +371,8 @@ expr_t::op_t::wrap_functor(expr_t::func_t fobj) {
string op_context(const expr_t::ptr_op_t op,
const expr_t::ptr_op_t locus = NULL);
+value_t split_cons_expr(expr_t::ptr_op_t op);
+
} // namespace ledger
#endif // _OP_H
diff --git a/src/option.cc b/src/option.cc
index 2843c775..418980bd 100644
--- a/src/option.cc
+++ b/src/option.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -89,7 +89,6 @@ namespace {
catch (const std::exception&) {
if (name[0] == '-')
add_error_context(_("While parsing option '%1'") << name);
-
else
add_error_context(_("While parsing environent variable '%1'") << name);
throw;
diff --git a/src/option.h b/src/option.h
index 8f89d081..772f2b01 100644
--- a/src/option.h
+++ b/src/option.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -61,16 +61,16 @@ protected:
option_t& operator=(const option_t&);
public:
- T * parent;
- value_t value;
- bool wants_arg;
+ T * parent;
+ string value;
+ bool wants_arg;
option_t(const char * _name, const char _ch = '\0')
: name(_name), name_len(std::strlen(name)), ch(_ch),
handled(false), parent(NULL), value(),
wants_arg(name[name_len - 1] == '_') {
- TRACE_CTOR(option_t, "const char *, const char");
DEBUG("option.names", "Option: " << name);
+ TRACE_CTOR(option_t, "const char *, const char");
}
option_t(const option_t& other)
: name(other.name),
@@ -94,7 +94,8 @@ public:
out << std::right << desc();
if (wants_arg) {
out << " = ";
- value.print(out, 42);
+ out.width(42);
+ out << value;
} else {
out.width(45);
out << ' ';
@@ -123,43 +124,48 @@ public:
return handled;
}
- string& str() {
+ string str() const {
assert(handled);
- if (! value)
+ if (value.empty())
throw_(std::runtime_error, _("No argument provided for %1") << desc());
- return value.as_string_lval();
+ return value;
}
- string str() const {
- assert(handled);
- if (! value)
- throw_(std::runtime_error, _("No argument provided for %1") << desc());
- return value.as_string();
+ void on(const char * whence) {
+ on(string(whence));
}
+ void on(const optional<string>& whence) {
+ handler_thunk(whence);
- void on_only(const optional<string>& whence) {
handled = true;
source = whence;
}
- void on(const optional<string>& whence, const string& str) {
- on_with(whence, string_value(str));
+
+ void on(const char * whence, const string& str) {
+ on(string(whence), str);
}
- virtual void on_with(const optional<string>& whence,
- const value_t& val) {
+ void on(const optional<string>& whence, const string& str) {
+ string before = value;
+
+ handler_thunk(whence, str);
+
+ if (value == before)
+ value = str;
+
handled = true;
- value = val;
source = whence;
}
void off() {
handled = false;
- value = value_t();
+ value = "";
source = none;
}
- virtual void handler_thunk(call_scope_t&) {}
+ virtual void handler_thunk(const optional<string>&) {}
+ virtual void handler_thunk(const optional<string>&, const string&) {}
- virtual void handler(call_scope_t& args) {
+ value_t handler(call_scope_t& args) {
if (wants_arg) {
if (args.size() < 2)
throw_(std::runtime_error, _("No argument provided for %1") << desc());
@@ -167,7 +173,7 @@ public:
throw_(std::runtime_error, _("To many arguments provided for %1") << desc());
else if (! args[0].is_string())
throw_(std::runtime_error, _("Context argument for %1 not a string") << desc());
- on_with(args.get<string>(0), args[1]);
+ on(args.get<string>(0), args.get<string>(1));
}
else if (args.size() < 1) {
throw_(std::runtime_error, _("No argument provided for %1") << desc());
@@ -176,27 +182,18 @@ public:
throw_(std::runtime_error, _("Context argument for %1 not a string") << desc());
}
else {
- on_only(args.get<string>(0));
+ on(args.get<string>(0));
}
-
- handler_thunk(args);
- }
-
- virtual value_t handler_wrapper(call_scope_t& args) {
- handler(args);
return true;
}
virtual value_t operator()(call_scope_t& args) {
if (! args.empty()) {
args.push_front(string_value("?expr"));
- return handler_wrapper(args);
+ return handler(args);
}
else if (wants_arg) {
- if (handled)
- return value;
- else
- return NULL_VALUE;
+ return string_value(value);
}
else {
return handled;
@@ -213,17 +210,18 @@ public:
name ## option_t() : option_t<type>(#name), base
#define DECL1(type, name, vartype, var, value) \
vartype var ; \
- name ## option_t() : option_t<type>(#name), var(value)
+ name ## option_t() : option_t<type>(#name), var value
-#define DO() virtual void handler_thunk(call_scope_t&)
-#define DO_(var) virtual void handler_thunk(call_scope_t& var)
+#define DO() virtual void handler_thunk(const optional<string>& whence)
+#define DO_(var) virtual void handler_thunk(const optional<string>& whence, \
+ const string& var)
#define END(name) name ## handler
#define COPY_OPT(name, other) name ## handler(other.name ## handler)
#define MAKE_OPT_HANDLER(type, x) \
- expr_t::op_t::wrap_functor(bind(&option_t<type>::handler_wrapper, x, _1))
+ expr_t::op_t::wrap_functor(bind(&option_t<type>::handler, x, _1))
#define MAKE_OPT_FUNCTOR(type, x) \
expr_t::op_t::wrap_functor(bind(&option_t<type>::operator(), x, _1))
@@ -284,6 +282,10 @@ inline bool is_eq(const char * p, const char * n) {
} \
END(name)
+#define OTHER(name) \
+ parent->HANDLER(name).parent = parent; \
+ parent->HANDLER(name)
+
bool process_option(const string& whence, const string& name, scope_t& scope,
const char * arg, const string& varname);
diff --git a/src/org.cc b/src/org.cc
index 7c8e8c0d..70427321 100644
--- a/src/org.cc
+++ b/src/org.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -45,8 +45,6 @@ posts_to_org_table::posts_to_org_table(report_t& _report,
const optional<string>& _prepend_format)
: report(_report), last_xact(NULL), last_post(NULL)
{
- TRACE_CTOR(posts_to_org_table, "report&, optional<string>");
-
first_line_format.parse_format
("|%(format_date(date))"
"|%(code)"
@@ -79,6 +77,8 @@ posts_to_org_table::posts_to_org_table(report_t& _report,
if (_prepend_format)
prepend_format.parse_format(*_prepend_format);
+
+ TRACE_CTOR(posts_to_org_table, "report&, optional<string>");
}
void posts_to_org_table::flush()
diff --git a/src/org.h b/src/org.h
index ed023be2..0b34b610 100644
--- a/src/org.h
+++ b/src/org.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/output.cc b/src/output.cc
index b26881a3..742000bd 100644
--- a/src/output.cc
+++ b/src/output.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -47,8 +47,6 @@ format_posts::format_posts(report_t& _report,
: report(_report), prepend_width(_prepend_width),
last_xact(NULL), last_post(NULL), first_report_title(true)
{
- TRACE_CTOR(format_posts, "report&, const string&, bool");
-
const char * f = format.c_str();
if (const char * p = std::strstr(f, "%/")) {
@@ -70,6 +68,8 @@ format_posts::format_posts(report_t& _report,
if (_prepend_format)
prepend_format.parse_format(*_prepend_format);
+
+ TRACE_CTOR(format_posts, "report&, const string&, bool");
}
void format_posts::flush()
@@ -131,8 +131,6 @@ format_accounts::format_accounts(report_t& _report,
: report(_report), prepend_width(_prepend_width), disp_pred(),
first_report_title(true)
{
- TRACE_CTOR(format_accounts, "report&, const string&");
-
const char * f = format.c_str();
if (const char * p = std::strstr(f, "%/")) {
@@ -154,6 +152,8 @@ format_accounts::format_accounts(report_t& _report,
if (_prepend_format)
prepend_format.parse_format(*_prepend_format);
+
+ TRACE_CTOR(format_accounts, "report&, const string&");
}
std::size_t format_accounts::post_account(account_t& account, const bool flat)
diff --git a/src/output.h b/src/output.h
index ac3925c4..281f69b6 100644
--- a/src/output.h
+++ b/src/output.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/parser.cc b/src/parser.cc
index a18fa552..360ac93d 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -54,20 +54,6 @@ expr_t::parser_t::parse_value_term(std::istream& in,
node = new op_t(op_t::IDENT);
node->set_ident(ident);
-
- // An identifier followed by ( represents a function call
- tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
- if (tok.kind == token_t::LPAREN) {
- op_t::kind_t kind = op_t::O_CALL;
- ptr_op_t call_node(new op_t(kind));
- call_node->set_left(node);
- node = call_node;
-
- push_token(tok); // let the parser see it again
- node->set_right(parse_value_expr(in, tflags.plus_flags(PARSE_SINGLE)));
- } else {
- push_token(tok);
- }
break;
}
@@ -85,8 +71,9 @@ expr_t::parser_t::parse_value_term(std::istream& in,
return node;
}
+
expr_t::ptr_op_t
-expr_t::parser_t::parse_dot_expr(std::istream& in,
+expr_t::parser_t::parse_call_expr(std::istream& in,
const parse_flags_t& tflags) const
{
ptr_op_t node(parse_value_term(in, tflags));
@@ -94,11 +81,36 @@ expr_t::parser_t::parse_dot_expr(std::istream& in,
if (node && ! tflags.has_flags(PARSE_SINGLE)) {
while (true) {
token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
+ if (tok.kind == token_t::LPAREN) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_CALL);
+ node->set_left(prev);
+ push_token(tok); // let the parser see the '(' again
+ node->set_right(parse_value_expr(in, tflags.plus_flags(PARSE_SINGLE)));
+ } else {
+ push_token(tok);
+ break;
+ }
+ }
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_dot_expr(std::istream& in,
+ const parse_flags_t& tflags) const
+{
+ ptr_op_t node(parse_call_expr(in, tflags));
+
+ if (node && ! tflags.has_flags(PARSE_SINGLE)) {
+ while (true) {
+ token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
if (tok.kind == token_t::DOT) {
ptr_op_t prev(node);
node = new op_t(op_t::O_LOOKUP);
node->set_left(prev);
- node->set_right(parse_value_term(in, tflags));
+ node->set_right(parse_call_expr(in, tflags));
if (! node->right())
throw_(parse_error,
_("%1 operator not followed by argument") << tok.symbol);
@@ -434,7 +446,6 @@ expr_t::parser_t::parse_comma_expr(std::istream& in,
ptr_op_t prev(node);
node = new op_t(op_t::O_CONS);
node->set_left(prev);
-
next = node;
}
@@ -471,7 +482,9 @@ expr_t::parser_t::parse_lambda_expr(std::istream& in,
ptr_op_t prev(node);
node = new op_t(op_t::O_LAMBDA);
node->set_left(prev);
- node->set_right(parse_querycolon_expr(in, tflags));
+ ptr_op_t scope(new op_t(op_t::SCOPE));
+ scope->set_left(parse_querycolon_expr(in, tflags));
+ node->set_right(scope);
} else {
push_token(tok);
}
@@ -493,7 +506,9 @@ expr_t::parser_t::parse_assign_expr(std::istream& in,
ptr_op_t prev(node);
node = new op_t(op_t::O_DEFINE);
node->set_left(prev);
- node->set_right(parse_lambda_expr(in, tflags));
+ ptr_op_t scope(new op_t(op_t::SCOPE));
+ scope->set_left(parse_lambda_expr(in, tflags));
+ node->set_right(scope);
} else {
push_token(tok);
}
@@ -509,24 +524,20 @@ expr_t::parser_t::parse_value_expr(std::istream& in,
ptr_op_t node(parse_assign_expr(in, tflags));
if (node && ! tflags.has_flags(PARSE_SINGLE)) {
- ptr_op_t next;
+ ptr_op_t chain;
while (true) {
token_t& tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
-
if (tok.kind == token_t::SEMI) {
- if (! next) {
- ptr_op_t prev(node);
- node = new op_t(op_t::O_SEQ);
- node->set_left(prev);
-
- next = node;
+ ptr_op_t seq(new op_t(op_t::O_SEQ));
+ if (! chain) {
+ seq->set_left(node);
+ node = seq;
+ } else {
+ seq->set_left(chain->right());
+ chain->set_right(seq);
}
-
- ptr_op_t chain(new op_t(op_t::O_SEQ));
- chain->set_left(parse_assign_expr(in, tflags));
-
- next->set_right(chain);
- next = chain;
+ seq->set_right(parse_assign_expr(in, tflags));
+ chain = seq;
} else {
push_token(tok);
break;
diff --git a/src/parser.h b/src/parser.h
index 09e12d95..db16a919 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -81,6 +81,8 @@ class expr_t::parser_t : public noncopyable
ptr_op_t parse_value_term(std::istream& in,
const parse_flags_t& flags) const;
+ ptr_op_t parse_call_expr(std::istream& in,
+ const parse_flags_t& flags) const;
ptr_op_t parse_dot_expr(std::istream& in,
const parse_flags_t& flags) const;
ptr_op_t parse_unary_expr(std::istream& in,
diff --git a/src/pool.cc b/src/pool.cc
index 65edbd6a..61b5bef2 100644
--- a/src/pool.cc
+++ b/src/pool.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -35,6 +35,7 @@
#include "commodity.h"
#include "annotate.h"
#include "pool.h"
+#include "history.h"
#include "quotes.h"
namespace ledger {
@@ -46,16 +47,16 @@ commodity_pool_t::commodity_pool_t()
quote_leeway(86400), get_quotes(false),
get_commodity_quote(commodity_quote_from_script)
{
- TRACE_CTOR(commodity_pool_t, "");
null_commodity = create("");
null_commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
+ TRACE_CTOR(commodity_pool_t, "");
}
commodity_t * commodity_pool_t::create(const string& symbol)
{
shared_ptr<commodity_t::base_t>
base_commodity(new commodity_t::base_t(symbol));
- std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity));
+ shared_ptr<commodity_t> commodity(new commodity_t(this, base_commodity));
DEBUG("pool.commodities", "Creating base commodity " << symbol);
@@ -66,25 +67,19 @@ commodity_t * commodity_pool_t::create(const string& symbol)
*commodity->qualified_symbol += "\"";
}
- DEBUG("pool.commodities",
- "Creating commodity '" << commodity->symbol() << "'");
+ DEBUG("pool.commodities", "Creating commodity '" << symbol << "'");
- std::pair<commodities_map::iterator, bool> result
- = commodities.insert(commodities_map::value_type(commodity->mapping_key(),
- commodity.get()));
+#if defined(DEBUG_ON)
+ std::pair<commodities_map::iterator, bool> result =
+#endif
+ commodities.insert(commodities_map::value_type(symbol, commodity));
+#if defined(DEBUG_ON)
assert(result.second);
+#endif
- return commodity.release();
-}
-
-commodity_t * commodity_pool_t::find_or_create(const string& symbol)
-{
- DEBUG("pool.commodities", "Find-or-create commodity " << symbol);
+ commodity_price_history.add_commodity(*commodity.get());
- commodity_t * commodity = find(symbol);
- if (commodity)
- return commodity;
- return create(symbol);
+ return commodity.get();
}
commodity_t * commodity_pool_t::find(const string& symbol)
@@ -93,91 +88,111 @@ commodity_t * commodity_pool_t::find(const string& symbol)
commodities_map::const_iterator i = commodities.find(symbol);
if (i != commodities.end())
- return (*i).second;
+ return (*i).second.get();
return NULL;
}
-commodity_t *
-commodity_pool_t::create(const string& symbol, const annotation_t& details)
+commodity_t * commodity_pool_t::find_or_create(const string& symbol)
{
- commodity_t * new_comm = create(symbol);
- if (! new_comm)
- return NULL;
-
- if (details)
- return find_or_create(*new_comm, details);
- else
- return new_comm;
+ DEBUG("pool.commodities", "Find-or-create commodity " << symbol);
+ if (commodity_t * commodity = find(symbol))
+ return commodity;
+ return create(symbol);
}
-string commodity_pool_t::make_qualified_name(const commodity_t& comm,
- const annotation_t& details)
+commodity_t * commodity_pool_t::alias(const string& name, commodity_t& referent)
{
- assert(details);
+ commodities_map::const_iterator i = commodities.find(referent.base_symbol());
+ assert(i != commodities.end());
- if (details.price && details.price->sign() < 0)
- throw_(amount_error, _("A commodity's price may not be negative"));
+ std::pair<commodities_map::iterator, bool> result
+ = commodities.insert(commodities_map::value_type(name, (*i).second));
+ assert(result.second);
- std::ostringstream name;
- comm.print(name);
- details.print(name, comm.pool().keep_base);
+ return (*result.first).second.get();
+}
-#if defined(DEBUG_ON)
- if (comm.qualified_symbol)
- DEBUG("pool.commodities", "make_qualified_name for "
- << *comm.qualified_symbol << std::endl << details);
-#endif
- DEBUG("pool.commodities", "qualified_name is " << name.str());
+commodity_t *
+commodity_pool_t::create(const string& symbol, const annotation_t& details)
+{
+ DEBUG("pool.commodities", "commodity_pool_t::create[ann] "
+ << "symbol " << symbol << std::endl << details);
- return name.str();
+ if (details)
+ return create(*find_or_create(symbol), details);
+ else
+ return create(symbol);
}
commodity_t *
commodity_pool_t::find(const string& symbol, const annotation_t& details)
{
- commodity_t * comm = find(symbol);
- if (! comm)
+ DEBUG("pool.commodities", "commodity_pool_t::find[ann] "
+ << "symbol " << symbol << std::endl << details);
+
+ annotated_commodities_map::const_iterator i =
+ annotated_commodities.find
+ (annotated_commodities_map::key_type(symbol, details));
+ if (i != annotated_commodities.end()) {
+ DEBUG("pool.commodities", "commodity_pool_t::find[ann] found "
+ << "symbol " << (*i).second->base_symbol() << std::endl
+ << as_annotated_commodity(*(*i).second.get()).details);
+ return (*i).second.get();
+ } else {
return NULL;
+ }
+}
- if (details) {
- string name = make_qualified_name(*comm, details);
+commodity_t *
+commodity_pool_t::find_or_create(const string& symbol,
+ const annotation_t& details)
+{
+ DEBUG("pool.commodities", "commodity_pool_t::find_or_create[ann] "
+ << "symbol " << symbol << std::endl << details);
- if (commodity_t * ann_comm = find(name)) {
+ if (details) {
+ if (commodity_t * ann_comm = find(symbol, details)) {
assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
return ann_comm;
+ } else {
+ return create(symbol, details);
}
- return NULL;
} else {
- return comm;
+ return find_or_create(symbol);
}
}
commodity_t *
-commodity_pool_t::find_or_create(const string& symbol,
- const annotation_t& details)
+commodity_pool_t::find_or_create(commodity_t& comm, const annotation_t& details)
{
- commodity_t * comm = find_or_create(symbol);
- if (! comm)
- return NULL;
+ DEBUG("pool.commodities", "commodity_pool_t::find_or_create[ann:comm] "
+ << "symbol " << comm.base_symbol() << std::endl << details);
- if (details)
- return find_or_create(*comm, details);
- else
- return comm;
+ if (details) {
+ if (commodity_t * ann_comm = find(comm.base_symbol(), details)) {
+ assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
+ return ann_comm;
+ } else {
+ return create(comm, details);
+ }
+ } else {
+ return &comm;
+ }
}
-commodity_t *
+annotated_commodity_t *
commodity_pool_t::create(commodity_t& comm,
- const annotation_t& details,
- const string& mapping_key)
+ const annotation_t& details)
{
+ DEBUG("pool.commodities", "commodity_pool_t::create[ann:comm] "
+ << "symbol " << comm.base_symbol() << std::endl << details);
+
assert(comm);
assert(! comm.has_annotation());
assert(details);
- assert(! mapping_key.empty());
- unique_ptr<commodity_t> commodity
- (new annotated_commodity_t(&comm, details));
+ shared_ptr<annotated_commodity_t>
+ commodity(new annotated_commodity_t(&comm, details));
comm.add_flags(COMMODITY_SAW_ANNOTATED);
if (details.price) {
@@ -187,39 +202,21 @@ commodity_pool_t::create(commodity_t& comm,
comm.add_flags(COMMODITY_SAW_ANN_PRICE_FLOAT);
}
- commodity->qualified_symbol = comm.symbol();
- assert(! commodity->qualified_symbol->empty());
-
DEBUG("pool.commodities", "Creating annotated commodity "
- << "symbol " << commodity->symbol()
- << " key " << mapping_key << std::endl << details);
+ << "symbol " << commodity->base_symbol()
+ << std::endl << details);
- // Add the fully annotated name to the map, so that this symbol may
- // quickly be found again.
- commodity->mapping_key_ = mapping_key;
-
- std::pair<commodities_map::iterator, bool> result
- = commodities.insert(commodities_map::value_type(mapping_key,
- commodity.get()));
+#if defined(DEBUG_ON)
+ std::pair<annotated_commodities_map::iterator, bool> result =
+#endif
+ annotated_commodities.insert(annotated_commodities_map::value_type
+ (annotated_commodities_map::key_type
+ (comm.base_symbol(), details), commodity));
+#if defined(DEBUG_ON)
assert(result.second);
+#endif
- return commodity.release();
-}
-
-commodity_t * commodity_pool_t::find_or_create(commodity_t& comm,
- const annotation_t& details)
-{
- assert(comm);
- assert(details);
-
- string name = make_qualified_name(comm, details);
- assert(! name.empty());
-
- if (commodity_t * ann_comm = find(name)) {
- assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
- return ann_comm;
- }
- return create(comm, details, name);
+ return commodity.get();
}
void commodity_pool_t::exchange(commodity_t& commodity,
@@ -240,6 +237,7 @@ cost_breakdown_t
commodity_pool_t::exchange(const amount_t& amount,
const amount_t& cost,
const bool is_per_unit,
+ const bool add_price,
const optional<datetime_t>& moment,
const optional<string>& tag)
{
@@ -261,6 +259,9 @@ commodity_pool_t::exchange(const amount_t& amount,
amount_t per_unit_cost =
(is_per_unit || amount.is_realzero()) ? cost.abs() : (cost / amount).abs();
+ if (! cost.has_commodity())
+ per_unit_cost.clear_commodity();
+
DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost);
// Do not record commodity exchanges where amount's commodity has a
@@ -269,8 +270,10 @@ commodity_pool_t::exchange(const amount_t& amount,
if (! per_unit_cost.is_realzero() &&
(current_annotation == NULL ||
! (current_annotation->price &&
- current_annotation->has_flags(ANNOTATION_PRICE_FIXATED))))
+ current_annotation->has_flags(ANNOTATION_PRICE_FIXATED))) &&
+ commodity.referent() != per_unit_cost.commodity().referent()) {
exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME());
+ }
cost_breakdown_t breakdown;
breakdown.final_cost = ! is_per_unit ? cost : cost * amount.abs();
@@ -382,76 +385,4 @@ commodity_pool_t::parse_price_expression(const std::string& str,
return NULL;
}
-void commodity_pool_t::print_pricemap(std::ostream& out,
- const keep_details_t& keep,
- const optional<datetime_t>& moment)
-{
- typedef std::map<commodity_t *, commodity_t *> comm_map_t;
-
- comm_map_t comm_map;
-
- foreach (const commodities_map::value_type& comm_pair, commodities) {
- commodity_t * comm(&comm_pair.second->strip_annotations(keep));
- comm_map.insert(comm_map_t::value_type(comm, NULL));
- }
-
- out << "digraph commodities {\n";
-
- foreach (const comm_map_t::value_type& comm_pair, comm_map) {
- commodity_t * comm(comm_pair.first);
- if (comm->has_flags(COMMODITY_BUILTIN))
- continue;
-
- out << " ";
- if (commodity_t::symbol_needs_quotes(comm->symbol()))
- out << comm->symbol() << ";\n";
- else
- out << "\"" << comm->symbol() << "\";\n";
-
- if (! comm->has_flags(COMMODITY_NOMARKET) &&
- (! commodity_pool_t::current_pool->default_commodity ||
- comm != commodity_pool_t::current_pool->default_commodity)) {
- if (optional<commodity_t::varied_history_t&> vhist =
- comm->varied_history()) {
- foreach (const commodity_t::history_by_commodity_map::value_type& pair,
- vhist->histories) {
- datetime_t most_recent;
- amount_t most_recent_amt;
- foreach (const commodity_t::history_map::value_type& inner_pair,
- pair.second.prices) {
- if ((most_recent.is_not_a_date_time() ||
- inner_pair.first > most_recent) &&
- (! moment || inner_pair.first <= moment)) {
- most_recent = inner_pair.first;
- most_recent_amt = inner_pair.second;
- }
- }
-
- if (! most_recent.is_not_a_date_time()) {
- out << " ";
- if (commodity_t::symbol_needs_quotes(comm->symbol()))
- out << comm->symbol();
- else
- out << "\"" << comm->symbol() << "\"";
-
- out << " -> ";
-
- if (commodity_t::symbol_needs_quotes(pair.first->symbol()))
- out << pair.first->symbol();
- else
- out << "\"" << pair.first->symbol() << "\"";
-
- out << " [label=\""
- << most_recent_amt.number() << "\\n"
- << format_date(most_recent.date(), FMT_WRITTEN)
- << "\" fontcolor=\"#008e28\"];\n";
- }
- }
- }
- }
- }
-
- out << "}\n";
-}
-
} // namespace ledger
diff --git a/src/pool.h b/src/pool.h
index 4b935f69..eb630781 100644
--- a/src/pool.h
+++ b/src/pool.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -46,6 +46,9 @@
#ifndef _POOL_H
#define _POOL_H
+#include "history.h"
+#include "annotate.h"
+
namespace ledger {
struct cost_breakdown_t
@@ -64,50 +67,47 @@ public:
* explicitly by calling the create methods of commodity_pool_t, or
* implicitly by parsing a commoditized amount.
*/
- typedef std::map<string, commodity_t *> commodities_map;
-
- commodities_map commodities;
- commodity_t * null_commodity;
- commodity_t * default_commodity;
-
- bool keep_base; // --base
+ typedef std::map<string, shared_ptr<commodity_t> > commodities_map;
+ typedef std::map<std::pair<string, annotation_t>,
+ shared_ptr<annotated_commodity_t> > annotated_commodities_map;
- optional<path> price_db; // --price-db=
- long quote_leeway; // --leeway=
- bool get_quotes; // --download
+ commodities_map commodities;
+ annotated_commodities_map annotated_commodities;
+ commodity_history_t commodity_price_history;
+ commodity_t * null_commodity;
+ commodity_t * default_commodity;
- static shared_ptr<commodity_pool_t> current_pool;
+ bool keep_base; // --base
+ optional<path> price_db; // --price-db=
+ long quote_leeway; // --leeway=
+ bool get_quotes; // --download
function<optional<price_point_t>
- (commodity_t& commodity, const optional<commodity_t&>& in_terms_of)>
+ (commodity_t& commodity, const commodity_t * in_terms_of)>
get_commodity_quote;
- explicit commodity_pool_t();
+ static shared_ptr<commodity_pool_t> current_pool;
+ explicit commodity_pool_t();
virtual ~commodity_pool_t() {
TRACE_DTOR(commodity_pool_t);
- foreach (commodities_map::value_type& pair, commodities)
- checked_delete(pair.second);
}
- string make_qualified_name(const commodity_t& comm,
- const annotation_t& details);
-
commodity_t * create(const string& symbol);
commodity_t * find(const string& name);
commodity_t * find_or_create(const string& symbol);
+ commodity_t * alias(const string& name, commodity_t& referent);
- commodity_t * create(const string& symbol, const annotation_t& details);
- commodity_t * find(const string& symbol, const annotation_t& details);
+ commodity_t * create(const string& symbol,
+ const annotation_t& details);
+ commodity_t * find(const string& symbol,
+ const annotation_t& details);
commodity_t * find_or_create(const string& symbol,
const annotation_t& details);
+ commodity_t * find_or_create(commodity_t& comm, const annotation_t& details);
- commodity_t * create(commodity_t& comm,
- const annotation_t& details,
- const string& mapping_key);
-
- commodity_t * find_or_create(commodity_t& comm,
- const annotation_t& details);
+ annotated_commodity_t * create(commodity_t& comm,
+ const annotation_t& details);
// Exchange one commodity for another, while recording the factored price.
@@ -118,6 +118,7 @@ public:
cost_breakdown_t exchange(const amount_t& amount,
const amount_t& cost,
const bool is_per_unit = false,
+ const bool add_price = true,
const optional<datetime_t>& moment = none,
const optional<string>& tag = none);
@@ -131,12 +132,6 @@ public:
const bool add_prices = true,
const optional<datetime_t>& moment = none);
- // Output the commodity price map for a given date as a DOT file
-
- void print_pricemap(std::ostream& out,
- const keep_details_t& keep,
- const optional<datetime_t>& moment = none);
-
#if defined(HAVE_BOOST_SERIALIZATION)
private:
/** Serialization. */
@@ -147,6 +142,7 @@ private:
void serialize(Archive& ar, const unsigned int /* version */) {
ar & current_pool;
ar & commodities;
+ ar & annotated_commodities;
ar & null_commodity;
ar & default_commodity;
ar & keep_base;
diff --git a/src/post.cc b/src/post.cc
index b40e31f0..0564eaca 100644
--- a/src/post.cc
+++ b/src/post.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -36,6 +36,7 @@
#include "account.h"
#include "journal.h"
#include "format.h"
+#include "pool.h"
namespace ledger {
@@ -48,9 +49,9 @@ bool post_t::has_tag(const string& tag, bool inherit) const
return false;
}
-bool post_t::has_tag(const mask_t& tag_mask,
+bool post_t::has_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask,
- bool inherit) const
+ bool inherit) const
{
if (item_t::has_tag(tag_mask, value_mask))
return true;
@@ -68,9 +69,9 @@ optional<value_t> post_t::get_tag(const string& tag, bool inherit) const
return none;
}
-optional<value_t> post_t::get_tag(const mask_t& tag_mask,
+optional<value_t> post_t::get_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask,
- bool inherit) const
+ bool inherit) const
{
if (optional<value_t> value = item_t::get_tag(tag_mask, value_mask))
return value;
@@ -91,21 +92,15 @@ date_t post_t::date() const
if (xdata_ && is_valid(xdata_->date))
return xdata_->date;
- if (item_t::use_effective_date) {
- if (_date_eff)
- return *_date_eff;
- else if (xact && xact->_date_eff)
- return *xact->_date_eff;
+ if (item_t::use_aux_date) {
+ if (optional<date_t> aux = aux_date())
+ return *aux;
}
- if (! _date) {
- assert(xact);
- return xact->date();
- }
- return *_date;
+ return primary_date();
}
-date_t post_t::actual_date() const
+date_t post_t::primary_date() const
{
if (xdata_ && is_valid(xdata_->date))
return xdata_->date;
@@ -117,11 +112,11 @@ date_t post_t::actual_date() const
return *_date;
}
-optional<date_t> post_t::effective_date() const
+optional<date_t> post_t::aux_date() const
{
- optional<date_t> date = item_t::effective_date();
+ optional<date_t> date = item_t::aux_date();
if (! date && xact)
- return xact->effective_date();
+ return xact->aux_date();
return date;
}
@@ -173,7 +168,8 @@ namespace {
return string_value(post.payee());
}
- value_t get_note(post_t& post) {
+ value_t get_note(post_t& post)
+ {
if (post.note || post.xact->note) {
string note = post.note ? *post.note : empty_string;
note += post.xact->note ? *post.xact->note : empty_string;
@@ -186,14 +182,9 @@ namespace {
value_t get_magnitude(post_t& post) {
return post.xact->magnitude();
}
- value_t get_idstring(post_t& post) {
- return string_value(post.xact->idstring());
- }
- value_t get_id(post_t& post) {
- return string_value(post.xact->id());
- }
- value_t get_amount(post_t& post) {
+ value_t get_amount(post_t& post)
+ {
if (post.has_xdata() && post.xdata().has_flags(POST_EXT_COMPOUND))
return post.xdata().compound_value;
else if (post.amount.is_null())
@@ -220,7 +211,8 @@ namespace {
}
}
- value_t get_commodity_is_primary(post_t& post) {
+ value_t get_commodity_is_primary(post_t& post)
+ {
if (post.has_xdata() &&
post.xdata().has_flags(POST_EXT_COMPOUND))
return post.xdata().compound_value.to_amount()
@@ -245,6 +237,15 @@ namespace {
return post.amount;
}
+ value_t get_price(post_t& post) {
+ if (post.amount.is_null())
+ return 0L;
+ if (post.amount.has_annotation() && post.amount.annotation().price)
+ return *post.amount.price();
+ else
+ return get_cost(post);
+ }
+
value_t get_total(post_t& post) {
if (post.xdata_ && ! post.xdata_->total.is_null())
return post.xdata_->total;
@@ -347,7 +348,14 @@ namespace {
return post.date();
}
value_t get_datetime(post_t& post) {
- return post.xdata().datetime;
+ return (! post.xdata().datetime.is_not_a_date_time() ?
+ post.xdata().datetime : datetime_t(post.date()));
+ }
+ value_t get_checkin(post_t& post) {
+ return post.checkin ? *post.checkin : NULL_VALUE;
+ }
+ value_t get_checkout(post_t& post) {
+ return post.checkout ? *post.checkout : NULL_VALUE;
}
template <value_t (*Func)(post_t&)>
@@ -440,6 +448,10 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(get_wrapper<&get_is_calculated>);
else if (name == "commodity")
return WRAP_FUNCTOR(&get_commodity);
+ else if (name == "checkin")
+ return WRAP_FUNCTOR(get_wrapper<&get_checkin>);
+ else if (name == "checkout")
+ return WRAP_FUNCTOR(get_wrapper<&get_checkout>);
break;
case 'd':
@@ -459,10 +471,6 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
case 'i':
if (name == "index")
return WRAP_FUNCTOR(get_wrapper<&get_count>);
- else if (name == "id")
- return WRAP_FUNCTOR(get_wrapper<&get_id>);
- else if (name == "idstring")
- return WRAP_FUNCTOR(get_wrapper<&get_idstring>);
break;
case 'm':
@@ -484,6 +492,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(get_wrapper<&get_payee>);
else if (name == "primary")
return WRAP_FUNCTOR(get_wrapper<&get_commodity_is_primary>);
+ else if (name == "price")
+ return WRAP_FUNCTOR(get_wrapper<&get_price>);
else if (name == "parent")
return WRAP_FUNCTOR(get_wrapper<&get_xact>);
break;
@@ -616,7 +626,8 @@ bool post_t::valid() const
void post_t::add_to_value(value_t& value, const optional<expr_t&>& expr) const
{
if (xdata_ && xdata_->has_flags(POST_EXT_COMPOUND)) {
- add_or_set_value(value, xdata_->compound_value);
+ if (! xdata_->compound_value.is_null())
+ add_or_set_value(value, xdata_->compound_value);
}
else if (expr) {
bind_scope_t bound_scope(*expr->get_context(),
@@ -647,6 +658,44 @@ void post_t::set_reported_account(account_t * acct)
acct->xdata().reported_posts.push_back(this);
}
+void extend_post(post_t& post, journal_t& journal)
+{
+ commodity_t& comm(post.amount.commodity());
+
+ annotation_t * details =
+ (comm.has_annotation() ?
+ &as_annotated_commodity(comm).details : NULL);
+
+ if (! details || ! details->value_expr) {
+ optional<expr_t> value_expr;
+
+ if (optional<value_t> data = post.get_tag(_("Value")))
+ value_expr = expr_t(data->to_string());
+
+ if (! value_expr)
+ value_expr = post.account->value_expr;
+
+ if (! value_expr)
+ value_expr = post.amount.commodity().value_expr();
+
+ if (! value_expr)
+ value_expr = journal.value_expr;
+
+ if (value_expr) {
+ if (! details) {
+ annotation_t new_details;
+ new_details.value_expr = value_expr;
+
+ commodity_t * new_comm =
+ commodity_pool_t::current_pool->find_or_create(comm, new_details);
+ post.amount.set_commodity(*new_comm);
+ } else {
+ details->value_expr = value_expr;
+ }
+ }
+ }
+}
+
void to_xml(std::ostream& out, const post_t& post)
{
push_xml x(out, "posting", true);
@@ -667,9 +716,9 @@ void to_xml(std::ostream& out, const post_t& post)
push_xml y(out, "date");
to_xml(out, *post._date, false);
}
- if (post._date_eff) {
- push_xml y(out, "effective-date");
- to_xml(out, *post._date_eff, false);
+ if (post._date_aux) {
+ push_xml y(out, "aux-date");
+ to_xml(out, *post._date_aux, false);
}
if (post.account) {
diff --git a/src/post.h b/src/post.h
index e626bca1..78928f23 100644
--- a/src/post.h
+++ b/src/post.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -58,20 +58,22 @@ public:
#define POST_COST_CALCULATED 0x0080 // posting's cost was calculated
#define POST_COST_IN_FULL 0x0100 // cost specified using @@
#define POST_COST_FIXATED 0x0200 // cost is fixed using = indicator
-#define POST_ANONYMIZED 0x0400 // a temporary, anonymous posting
+#define POST_COST_VIRTUAL 0x0400 // cost is virtualized: (@)
+#define POST_ANONYMIZED 0x0800 // a temporary, anonymous posting
- xact_t * xact; // only set for posts of regular xacts
- account_t * account;
+ xact_t * xact; // only set for posts of regular xacts
+ account_t * account;
- amount_t amount; // can be null until finalization
- optional<expr_t> amount_expr;
- optional<amount_t> cost;
- optional<amount_t> assigned_amount;
+ amount_t amount; // can be null until finalization
+ optional<expr_t> amount_expr;
+ optional<amount_t> cost;
+ optional<amount_t> assigned_amount;
+ optional<datetime_t> checkin;
+ optional<datetime_t> checkout;
post_t(account_t * _account = NULL,
flags_t _flags = ITEM_NORMAL)
- : item_t(_flags),
- xact(NULL), account(_account)
+ : item_t(_flags), xact(NULL), account(_account)
{
TRACE_CTOR(post_t, "account_t *, flags_t");
}
@@ -79,8 +81,7 @@ public:
const amount_t& _amount,
flags_t _flags = ITEM_NORMAL,
const optional<string>& _note = none)
- : item_t(_flags, _note),
- xact(NULL), account(_account), amount(_amount)
+ : item_t(_flags, _note), xact(NULL), account(_account), amount(_amount)
{
TRACE_CTOR(post_t, "account_t *, const amount_t&, flags_t, const optional<string>&");
}
@@ -91,8 +92,11 @@ public:
amount(post.amount),
cost(post.cost),
assigned_amount(post.assigned_amount),
+ checkin(post.checkin),
+ checkout(post.checkout),
xdata_(post.xdata_)
{
+ copy_details(post);
TRACE_CTOR(post_t, "copy");
}
virtual ~post_t() {
@@ -116,15 +120,15 @@ public:
bool inherit = true) const;
virtual optional<value_t> get_tag(const string& tag,
- bool inherit = true) const;
+ bool inherit = true) const;
virtual optional<value_t> get_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask = none,
- bool inherit = true) const;
+ bool inherit = true) const;
virtual date_t value_date() const;
virtual date_t date() const;
- virtual date_t actual_date() const;
- virtual optional<date_t> effective_date() const;
+ virtual date_t primary_date() const;
+ virtual optional<date_t> aux_date() const;
string payee() const;
@@ -140,6 +144,12 @@ public:
std::size_t xact_id() const;
std::size_t account_id() const;
+ virtual void copy_details(const item_t& item) {
+ const post_t& post(dynamic_cast<const post_t&>(item));
+ xdata_ = post.xdata_;
+ item_t::copy_details(item);
+ }
+
bool valid() const;
struct xdata_t : public supports_flags<uint_least16_t>
@@ -230,7 +240,7 @@ public:
{
bool operator()(const post_t * left, const post_t * right) const {
gregorian::date_duration duration =
- left->actual_date() - right->actual_date();
+ left->primary_date() - right->primary_date();
if (duration.days() == 0) {
return ((left->pos ? left->pos->sequence : 0) <
(right->pos ? right->pos->sequence : 0));
@@ -259,6 +269,9 @@ private:
#endif // HAVE_BOOST_SERIALIZATION
};
+class journal_t;
+void extend_post(post_t& post, journal_t& journal);
+
void to_xml(std::ostream& out, const post_t& post);
} // namespace ledger
diff --git a/src/precmd.cc b/src/precmd.cc
index 663b638d..fe0836bc 100644
--- a/src/precmd.cc
+++ b/src/precmd.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -43,22 +43,6 @@
namespace ledger {
namespace {
- string join_args(call_scope_t& args)
- {
- std::ostringstream buf;
- bool first = true;
-
- for (std::size_t i = 0; i < args.size(); i++) {
- if (first)
- first = false;
- else
- buf << ' ';
- buf << args[i];
- }
-
- return buf.str();
- }
-
post_t * get_sample_xact(report_t& report)
{
{
@@ -83,8 +67,14 @@ namespace {
out << _("--- Context is first posting of the following transaction ---")
<< std::endl << str << std::endl;
{
- std::istringstream in(str);
- report.session.journal->parse(in, report.session);
+ shared_ptr<std::istringstream> in(new std::istringstream(str));
+
+ parse_context_stack_t parsing_context;
+ parsing_context.push(in);
+ parsing_context.get_current().journal = report.session.journal.get();
+ parsing_context.get_current().scope = &report.session;
+
+ report.session.journal->read(parsing_context);
report.session.journal->clear_xdata();
}
}
diff --git a/src/precmd.h b/src/precmd.h
index 277933c3..1c52d8a7 100644
--- a/src/precmd.h
+++ b/src/precmd.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/predicate.h b/src/predicate.h
index 673f1d5d..7d58dc2f 100644
--- a/src/predicate.h
+++ b/src/predicate.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/print.cc b/src/print.cc
index b7f72bf0..79d83161 100644
--- a/src/print.cc
+++ b/src/print.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -41,14 +41,47 @@
namespace ledger {
namespace {
+ bool post_has_simple_amount(const post_t& post)
+ {
+ // Is the amount the result of a computation, i.e., it wasn't
+ // explicit specified by the user?
+ if (post.has_flags(POST_CALCULATED))
+ return false;
+
+ // Is the amount still empty? This shouldn't be true by this point,
+ // but we check anyway for safety.
+ if (post.amount.is_null())
+ return false;
+
+ // Is the amount a complex expression. If so, the first 'if' should
+ // have triggered.
+ if (post.amount_expr)
+ return false;
+
+ // Is there a balance assignment? If so, don't elide the amount as
+ // that can change the semantics.
+ if (post.assigned_amount)
+ return false;
+
+ // Does it have an explicitly specified cost (i.e., one that wasn't
+ // calculated for the user)? If so, don't elide the amount!
+ if (post.cost && ! post.has_flags(POST_COST_CALCULATED))
+ return false;
+
+ return true;
+ }
+
void print_note(std::ostream& out,
const string& note,
+ const bool note_on_next_line,
const std::size_t columns,
const std::size_t prior_width)
{
- // The 4 is for four leading spaces at the beginning of the posting, and
- // the 3 is for two spaces and a semi-colon before the note.
- if (columns > 0 && note.length() > columns - (prior_width + 3))
+ // The 3 is for two spaces and a semi-colon before the note.
+ if (note_on_next_line ||
+ (columns > 0 &&
+ (columns <= prior_width + 3 ||
+ note.length() > columns - (prior_width + 3))))
out << "\n ;";
else
out << " ;";
@@ -79,11 +112,11 @@ namespace {
std::ostringstream buf;
- buf << format_date(item_t::use_effective_date ?
- xact.date() : xact.actual_date(),
+ buf << format_date(item_t::use_aux_date ?
+ xact.date() : xact.primary_date(),
format_type, format);
- if (! item_t::use_effective_date && xact.effective_date())
- buf << '=' << format_date(*xact.effective_date(),
+ if (! item_t::use_aux_date && xact.aux_date())
+ buf << '=' << format_date(*xact.aux_date(),
format_type, format);
buf << ' ';
@@ -100,10 +133,11 @@ namespace {
std::size_t columns =
(report.HANDLED(columns_) ?
- static_cast<std::size_t>(report.HANDLER(columns_).value.to_long()) : 80);
+ lexical_cast<std::size_t>(report.HANDLER(columns_).str()) : 80);
if (xact.note)
- print_note(out, *xact.note, columns, unistring(leader).length());
+ print_note(out, *xact.note, xact.has_flags(ITEM_NOTE_ON_NEXT_LINE),
+ columns, unistring(leader).length());
out << '\n';
if (xact.metadata) {
@@ -119,7 +153,12 @@ namespace {
}
}
+ std::size_t count = xact.posts.size();
+ std::size_t index = 0;
+
foreach (post_t * post, xact.posts) {
+ index++;
+
if (! report.HANDLED(generated) &&
(post->has_flags(ITEM_TEMP | ITEM_GENERATED) &&
! post->has_flags(POST_ANONYMIZED)))
@@ -152,8 +191,8 @@ namespace {
unistring name(pbuf.str());
std::size_t account_width =
- (report.HANDLER(account_width_).specified ?
- static_cast<std::size_t>(report.HANDLER(account_width_).value.to_long()) : 36);
+ (report.HANDLED(account_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(account_width_).str()) : 36);
if (account_width < name.length())
account_width = name.length();
@@ -163,25 +202,32 @@ namespace {
std::string::size_type slip =
(static_cast<std::string::size_type>(account_width) -
static_cast<std::string::size_type>(name.length()));
- if (slip > 0) {
- out.width(static_cast<std::streamsize>(slip));
- out << ' ';
- }
-
- std::ostringstream amtbuf;
string amt;
if (post->amount_expr) {
amt = post->amount_expr->text();
- } else {
- int amount_width =
- (report.HANDLER(amount_width_).specified ?
- report.HANDLER(amount_width_).value.to_int() : 12);
+ }
+ else if (count == 2 && index == 2 &&
+ post_has_simple_amount(*post) &&
+ post_has_simple_amount(*(*xact.posts.begin())) &&
+ ((*xact.posts.begin())->amount.commodity() ==
+ post->amount.commodity())) {
+ // If there are two postings and they both simple amount, and
+ // they are both of the same commodity, don't bother printing
+ // the second amount as it's always just an inverse of the
+ // first.
+ }
+ else {
+ std::size_t amount_width =
+ (report.HANDLED(amount_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(amount_width_).str()) :
+ 12);
std::ostringstream amt_str;
- value_t(post->amount).print(amt_str, amount_width, -1,
- AMOUNT_PRINT_RIGHT_JUSTIFY |
- AMOUNT_PRINT_NO_COMPUTED_ANNOTATIONS);
+ value_t(post->amount).print(amt_str, static_cast<int>(amount_width),
+ -1, AMOUNT_PRINT_RIGHT_JUSTIFY |
+ (report.HANDLED(generated) ? 0 :
+ AMOUNT_PRINT_NO_COMPUTED_ANNOTATIONS));
amt = amt_str.str();
}
@@ -191,6 +237,7 @@ namespace {
(static_cast<std::string::size_type>(amt.length()) -
static_cast<std::string::size_type>(trimmed_amt.length()));
+ std::ostringstream amtbuf;
if (slip + amt_slip < 2)
amtbuf << string(2 - (slip + amt_slip), ' ');
amtbuf << amt;
@@ -208,15 +255,22 @@ namespace {
amtbuf << " = " << *post->assigned_amount;
string trailer = amtbuf.str();
- out << trailer;
-
- account_width += unistring(trailer).length();
+ if (! trailer.empty()) {
+ if (slip > 0) {
+ out.width(static_cast<std::streamsize>(slip));
+ out << ' ';
+ }
+ out << trailer;
+
+ account_width += unistring(trailer).length();
+ }
} else {
out << pbuf.str();
}
if (post->note)
- print_note(out, *post->note, columns, 4 + account_width);
+ print_note(out, *post->note, post->has_flags(ITEM_NOTE_ON_NEXT_LINE),
+ columns, 4 + account_width);
out << '\n';
}
}
diff --git a/src/print.h b/src/print.h
index 527f1912..42bfc8b6 100644
--- a/src/print.h
+++ b/src/print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/pstream.h b/src/pstream.h
index 8134495d..6e38158a 100644
--- a/src/pstream.h
+++ b/src/pstream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -61,9 +61,14 @@ class ptristream : public std::istream
if (*ptr && len == 0)
len = std::strlen(ptr);
- setg(ptr, // beginning of putback area
- ptr, // read position
+ setg(ptr, // beginning of putback area
+ ptr, // read position
ptr+len); // end position
+
+ TRACE_CTOR(ptrinbuf, "char *, std::size_t");
+ }
+ ~ptrinbuf() throw() {
+ TRACE_DTOR(ptrinbuf);
}
protected:
diff --git a/src/py_account.cc b/src/py_account.cc
index 5ef86871..64a7ae54 100644
--- a/src/py_account.cc
+++ b/src/py_account.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/py_amount.cc b/src/py_amount.cc
index 9ce4a02d..0aa8fee8 100644
--- a/src/py_amount.cc
+++ b/src/py_amount.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -48,14 +48,19 @@ namespace {
return amount.value(CURRENT_TIME());
}
boost::optional<amount_t> py_value_1(const amount_t& amount,
- commodity_t& in_terms_of) {
+ const commodity_t * in_terms_of) {
return amount.value(CURRENT_TIME(), in_terms_of);
}
boost::optional<amount_t> py_value_2(const amount_t& amount,
- commodity_t& in_terms_of,
- datetime_t& moment) {
+ const commodity_t * in_terms_of,
+ const datetime_t& moment) {
return amount.value(moment, in_terms_of);
}
+ boost::optional<amount_t> py_value_2d(const amount_t& amount,
+ const commodity_t * in_terms_of,
+ const date_t& moment) {
+ return amount.value(datetime_t(moment), in_terms_of);
+ }
void py_parse_2(amount_t& amount, object in, unsigned char flags) {
if (PyFile_Check(in.ptr())) {
@@ -238,6 +243,7 @@ internal precision."))
.def("value", py_value_0)
.def("value", py_value_1, args("in_terms_of"))
.def("value", py_value_2, args("in_terms_of", "moment"))
+ .def("value", py_value_2d, args("in_terms_of", "moment"))
.def("price", &amount_t::price)
@@ -263,10 +269,11 @@ internal precision."))
.add_property("commodity",
make_function(&amount_t::commodity,
- return_value_policy<reference_existing_object>()),
+ return_internal_reference<>()),
make_function(&amount_t::set_commodity,
with_custodian_and_ward<1, 2>()))
.def("has_commodity", &amount_t::has_commodity)
+ .def("with_commodity", &amount_t::with_commodity)
.def("clear_commodity", &amount_t::clear_commodity)
.def("number", &amount_t::number)
diff --git a/src/py_balance.cc b/src/py_balance.cc
index 0140a625..2ae546f1 100644
--- a/src/py_balance.cc
+++ b/src/py_balance.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -48,12 +48,12 @@ namespace {
return balance.value(CURRENT_TIME());
}
boost::optional<balance_t> py_value_1(const balance_t& balance,
- commodity_t& in_terms_of) {
+ const commodity_t * in_terms_of) {
return balance.value(CURRENT_TIME(), in_terms_of);
}
boost::optional<balance_t> py_value_2(const balance_t& balance,
- commodity_t& in_terms_of,
- datetime_t& moment) {
+ const commodity_t * in_terms_of,
+ const datetime_t& moment) {
return balance.value(moment, in_terms_of);
}
@@ -201,8 +201,6 @@ void export_balance()
.def("value", py_value_1, args("in_terms_of"))
.def("value", py_value_2, args("in_terms_of", "moment"))
- .def("price", &balance_t::price)
-
.def("__nonzero__", &balance_t::is_nonzero)
.def("is_nonzero", &balance_t::is_nonzero)
.def("is_zero", &balance_t::is_zero)
diff --git a/src/py_commodity.cc b/src/py_commodity.cc
index 6d8a29b3..b283efcc 100644
--- a/src/py_commodity.cc
+++ b/src/py_commodity.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -96,14 +96,15 @@ namespace {
pool.exchange(commodity, per_unit_cost, moment);
}
- cost_breakdown_t py_exchange_5(commodity_pool_t& pool,
+ cost_breakdown_t py_exchange_7(commodity_pool_t& pool,
const amount_t& amount,
const amount_t& cost,
const bool is_per_unit,
+ const bool add_prices,
const boost::optional<datetime_t>& moment,
const boost::optional<string>& tag)
{
- return pool.exchange(amount, cost, is_per_unit, moment, tag);
+ return pool.exchange(amount, cost, is_per_unit, add_prices, moment, tag);
}
commodity_t * py_pool_getitem(commodity_pool_t& pool, const string& symbol)
@@ -113,9 +114,9 @@ namespace {
if (i == pool.commodities.end()) {
PyErr_SetString(PyExc_ValueError,
(string("Could not find commodity ") + symbol).c_str());
- throw boost::python::error_already_set();
+ throw_error_already_set();
}
- return (*i).second;
+ return (*i).second.get();
}
python::list py_pool_keys(commodity_pool_t& pool) {
@@ -168,13 +169,15 @@ namespace {
py_pool_commodities_values_begin(commodity_pool_t& pool) {
return make_transform_iterator
(pool.commodities.begin(),
- bind(&commodity_pool_t::commodities_map::value_type::second, _1));
+ bind(&shared_ptr<commodity_t>::get,
+ bind(&commodity_pool_t::commodities_map::value_type::second, _1)));
}
commodities_map_seconds_iterator
py_pool_commodities_values_end(commodity_pool_t& pool) {
return make_transform_iterator
(pool.commodities.end(),
- bind(&commodity_pool_t::commodities_map::value_type::second, _1));
+ bind(&shared_ptr<commodity_t>::get,
+ bind(&commodity_pool_t::commodities_map::value_type::second, _1)));
}
void py_add_price_2(commodity_t& commodity,
@@ -255,8 +258,10 @@ void export_commodity()
make_getter(&commodity_pool_t::keep_base),
make_setter(&commodity_pool_t::keep_base))
.add_property("price_db",
- make_getter(&commodity_pool_t::price_db),
- make_setter(&commodity_pool_t::price_db))
+ make_getter(&commodity_pool_t::price_db,
+ return_value_policy<return_by_value>()),
+ make_setter(&commodity_pool_t::price_db,
+ return_value_policy<return_by_value>()))
.add_property("quote_leeway",
make_getter(&commodity_pool_t::quote_leeway),
make_setter(&commodity_pool_t::quote_leeway))
@@ -267,44 +272,38 @@ void export_commodity()
make_getter(&commodity_pool_t::get_commodity_quote),
make_setter(&commodity_pool_t::get_commodity_quote))
- .def("make_qualified_name", &commodity_pool_t::make_qualified_name)
+ .def("create", py_create_1, return_internal_reference<>())
+ .def("create", py_create_2, return_internal_reference<>())
- .def("create", py_create_1,
- return_value_policy<reference_existing_object>())
- .def("create", py_create_2,
- return_value_policy<reference_existing_object>())
+ .def("find_or_create", py_find_or_create_1, return_internal_reference<>())
+ .def("find_or_create", py_find_or_create_2, return_internal_reference<>())
- .def("find_or_create", py_find_or_create_1,
- return_value_policy<reference_existing_object>())
- .def("find_or_create", py_find_or_create_2,
- return_value_policy<reference_existing_object>())
-
- .def("find", py_find_1, return_value_policy<reference_existing_object>())
- .def("find", py_find_2, return_value_policy<reference_existing_object>())
+ .def("find", py_find_1, return_internal_reference<>())
+ .def("find", py_find_2, return_internal_reference<>())
.def("exchange", py_exchange_2, with_custodian_and_ward<1, 2>())
.def("exchange", py_exchange_3, with_custodian_and_ward<1, 2>())
- .def("exchange", py_exchange_5)
+ .def("exchange", py_exchange_7)
.def("parse_price_directive", &commodity_pool_t::parse_price_directive)
.def("parse_price_expression", &commodity_pool_t::parse_price_expression,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("__getitem__", py_pool_getitem,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("keys", py_pool_keys)
.def("has_key", py_pool_contains)
.def("__contains__", py_pool_contains)
.def("__iter__",
- python::range<return_value_policy<reference_existing_object> >
+ python::range<return_internal_reference<> >
(py_pool_commodities_begin, py_pool_commodities_end))
.def("iteritems",
- python::range<return_value_policy<reference_existing_object> >
+ python::range<return_internal_reference<> >
(py_pool_commodities_begin, py_pool_commodities_end))
.def("iterkeys", python::range<>(py_pool_commodities_keys_begin,
py_pool_commodities_keys_end))
.def("itervalues",
- python::range<return_value_policy<reference_existing_object> >
+ python::range<return_internal_reference<> >
(py_pool_commodities_values_begin, py_pool_commodities_values_end))
;
@@ -349,21 +348,20 @@ void export_commodity()
.add_property("referent",
make_function(py_commodity_referent,
- return_value_policy<reference_existing_object>()))
+ return_internal_reference<>()))
.def("has_annotation", &commodity_t::has_annotation)
.def("strip_annotations", py_strip_annotations_0,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("strip_annotations", py_strip_annotations_1,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("write_annotations", &commodity_t::write_annotations)
.def("pool", &commodity_t::pool,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.add_property("base_symbol", &commodity_t::base_symbol)
.add_property("symbol", &commodity_t::symbol)
- .add_property("mapping_key", &commodity_t::mapping_key)
.add_property("name", &commodity_t::name, &commodity_t::set_name)
.add_property("note", &commodity_t::note, &commodity_t::set_note)
@@ -394,11 +392,15 @@ void export_commodity()
.add_property("price", py_price, py_set_price)
.add_property("date",
- make_getter(&annotation_t::date),
- make_setter(&annotation_t::date))
+ make_getter(&annotation_t::date,
+ return_value_policy<return_by_value>()),
+ make_setter(&annotation_t::date,
+ return_value_policy<return_by_value>()))
.add_property("tag",
- make_getter(&annotation_t::tag),
- make_setter(&annotation_t::tag))
+ make_getter(&annotation_t::tag,
+ return_value_policy<return_by_value>()),
+ make_setter(&annotation_t::tag,
+ return_value_policy<return_by_value>()))
.def("__nonzero__", &annotation_t::operator bool)
@@ -441,12 +443,12 @@ void export_commodity()
.add_property("referent",
make_function(py_annotated_commodity_referent,
- return_value_policy<reference_existing_object>()))
+ return_internal_reference<>()))
.def("strip_annotations", py_strip_ann_annotations_0,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("strip_annotations", py_strip_ann_annotations_1,
- return_value_policy<reference_existing_object>())
+ return_internal_reference<>())
.def("write_annotations", &annotated_commodity_t::write_annotations)
;
}
diff --git a/src/py_expr.cc b/src/py_expr.cc
index 027125e2..dd9df1f5 100644
--- a/src/py_expr.cc
+++ b/src/py_expr.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/py_format.cc b/src/py_format.cc
index fc2103c7..482eaf5b 100644
--- a/src/py_format.cc
+++ b/src/py_format.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/py_item.cc b/src/py_item.cc
index 51d9e50c..893ddcfa 100644
--- a/src/py_item.cc
+++ b/src/py_item.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -120,14 +120,20 @@ void export_item()
#endif
.add_property("note",
- make_getter(&item_t::note),
- make_setter(&item_t::note))
+ make_getter(&item_t::note,
+ return_value_policy<return_by_value>()),
+ make_setter(&item_t::note,
+ return_value_policy<return_by_value>()))
.add_property("pos",
- make_getter(&item_t::pos),
- make_setter(&item_t::pos))
+ make_getter(&item_t::pos,
+ return_value_policy<return_by_value>()),
+ make_setter(&item_t::pos,
+ return_value_policy<return_by_value>()))
.add_property("metadata",
- make_getter(&item_t::metadata),
- make_setter(&item_t::metadata))
+ make_getter(&item_t::metadata,
+ return_value_policy<return_by_value>()),
+ make_setter(&item_t::metadata,
+ return_value_policy<return_by_value>()))
.def("copy_details", &item_t::copy_details)
@@ -149,13 +155,13 @@ void export_item()
.def("parse_tags", &item_t::parse_tags)
.def("append_note", &item_t::append_note)
- .add_static_property("use_effective_date",
- make_getter(&item_t::use_effective_date),
- make_setter(&item_t::use_effective_date))
+ .add_static_property("use_aux_date",
+ make_getter(&item_t::use_aux_date),
+ make_setter(&item_t::use_aux_date))
.add_property("date", &item_t::date, make_setter(&item_t::_date))
- .add_property("effective_date", &item_t::effective_date,
- make_setter(&item_t::_date_eff))
+ .add_property("aux_date", &item_t::aux_date,
+ make_setter(&item_t::_date_aux))
.add_property("state", &item_t::state, &item_t::set_state)
diff --git a/src/py_journal.cc b/src/py_journal.cc
index bd781225..50a52be9 100644
--- a/src/py_journal.cc
+++ b/src/py_journal.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -135,51 +135,57 @@ namespace {
return journal.find_account(name, auto_create);
}
+#if 0
std::size_t py_read(journal_t& journal, const string& pathname)
{
- return journal.read(pathname);
+ return journal.read(context_stack);
}
+#endif
struct collector_wrapper
{
- journal_t& journal;
- report_t report;
- collect_posts * posts_collector;
- post_handler_ptr chain;
+ journal_t& journal;
+ report_t report;
+
+ post_handler_ptr posts_collector;
collector_wrapper(journal_t& _journal, report_t& base)
: journal(_journal), report(base),
- posts_collector(new collect_posts) {}
+ posts_collector(new collect_posts) {
+ TRACE_CTOR(collector_wrapper, "journal_t&, report_t&");
+ }
~collector_wrapper() {
+ TRACE_DTOR(collector_wrapper);
journal.clear_xdata();
}
std::size_t length() const {
- return posts_collector->length();
+ return dynamic_cast<collect_posts *>(posts_collector.get())->length();
}
std::vector<post_t *>::iterator begin() {
- return posts_collector->begin();
+ return dynamic_cast<collect_posts *>(posts_collector.get())->begin();
}
std::vector<post_t *>::iterator end() {
- return posts_collector->end();
+ return dynamic_cast<collect_posts *>(posts_collector.get())->end();
}
};
- shared_ptr<collector_wrapper>
- py_collect(journal_t& journal, const string& query)
+ shared_ptr<collector_wrapper> py_query(journal_t& journal,
+ const string& query)
{
if (journal.has_xdata()) {
PyErr_SetString(PyExc_RuntimeError,
- _("Cannot have multiple journal collections open at once"));
+ _("Cannot have more than one active journal query"));
throw_error_already_set();
}
report_t& current_report(downcast<report_t>(*scope_t::default_scope));
- shared_ptr<collector_wrapper> coll(new collector_wrapper(journal,
- current_report));
- unique_ptr<journal_t> save_journal(current_report.session.journal.release());
- current_report.session.journal.reset(&journal);
+ shared_ptr<collector_wrapper>
+ coll(new collector_wrapper(journal, current_report));
+
+ unique_ptr<journal_t> save_journal(coll->report.session.journal.release());
+ coll->report.session.journal.reset(&coll->journal);
try {
strings_list remaining =
@@ -189,60 +195,48 @@ namespace {
value_t args;
foreach (const string& arg, remaining)
args.push_back(string_value(arg));
- coll->report.parse_query_args(args, "@Journal.collect");
+ coll->report.parse_query_args(args, "@Journal.query");
- journal_posts_iterator walker(coll->journal);
- coll->chain =
- chain_post_handlers(post_handler_ptr(coll->posts_collector),
- coll->report);
- pass_down_posts<journal_posts_iterator>(coll->chain, walker);
+ coll->report.posts_report(coll->posts_collector);
}
catch (...) {
- current_report.session.journal.release();
- current_report.session.journal.reset(save_journal.release());
+ coll->report.session.journal.release();
+ coll->report.session.journal.reset(save_journal.release());
throw;
}
- current_report.session.journal.release();
- current_report.session.journal.reset(save_journal.release());
+ coll->report.session.journal.release();
+ coll->report.session.journal.reset(save_journal.release());
return coll;
}
post_t * posts_getitem(collector_wrapper& collector, long i)
{
- post_t * post =
- collector.posts_collector->posts[static_cast<std::string::size_type>(i)];
- std::cerr << typeid(post).name() << std::endl;
- std::cerr << typeid(*post).name() << std::endl;
- std::cerr << typeid(post->account).name() << std::endl;
- std::cerr << typeid(*post->account).name() << std::endl;
- return post;
+ return dynamic_cast<collect_posts *>(collector.posts_collector.get())
+ ->posts[static_cast<std::size_t>(i)];
}
} // unnamed namespace
+#define EXC_TRANSLATOR(type) \
+ void exc_translate_ ## type(const type& err) { \
+ PyErr_SetString(PyExc_RuntimeError, err.what()); \
+ }
+
+EXC_TRANSLATOR(parse_error)
+EXC_TRANSLATOR(error_count)
+
void export_journal()
{
class_< item_handler<post_t>, shared_ptr<item_handler<post_t> >,
boost::noncopyable >("PostHandler")
;
- class_< collect_posts, bases<item_handler<post_t> >,
- shared_ptr<collect_posts>, boost::noncopyable >("PostCollector")
- .def("__len__", &collect_posts::length)
- .def("__iter__", python::range<return_internal_reference<1,
- with_custodian_and_ward_postcall<1, 0> > >
- (&collect_posts::begin, &collect_posts::end))
- ;
-
class_< collector_wrapper, shared_ptr<collector_wrapper>,
boost::noncopyable >("PostCollectorWrapper", no_init)
.def("__len__", &collector_wrapper::length)
- .def("__getitem__", posts_getitem, return_internal_reference<1,
- with_custodian_and_ward_postcall<0, 1> >())
- .def("__iter__",
- python::range<return_value_policy<reference_existing_object,
- with_custodian_and_ward_postcall<0, 1> > >
+ .def("__getitem__", posts_getitem, return_internal_reference<>())
+ .def("__iter__", python::range<return_internal_reference<> >
(&collector_wrapper::begin, &collector_wrapper::end))
;
@@ -264,9 +258,10 @@ void export_journal()
;
class_< journal_t, boost::noncopyable > ("Journal")
+#if 0
.def(init<path>())
.def(init<string>())
-
+#endif
.add_property("master",
make_getter(&journal_t::master,
return_internal_reference<1,
@@ -283,13 +278,13 @@ void export_journal()
.def("find_account", py_find_account_1,
return_internal_reference<1,
- with_custodian_and_ward_postcall<0, 1> >())
+ with_custodian_and_ward_postcall<1, 0> >())
.def("find_account", py_find_account_2,
return_internal_reference<1,
- with_custodian_and_ward_postcall<0, 1> >())
+ with_custodian_and_ward_postcall<1, 0> >())
.def("find_account_re", &journal_t::find_account_re,
return_internal_reference<1,
- with_custodian_and_ward_postcall<0, 1> >())
+ with_custodian_and_ward_postcall<1, 0> >())
.def("add_xact", &journal_t::add_xact)
.def("remove_xact", &journal_t::remove_xact)
@@ -298,7 +293,7 @@ void export_journal()
#if 0
.def("__getitem__", xacts_getitem,
return_internal_reference<1,
- with_custodian_and_ward_postcall<0, 1> >())
+ with_custodian_and_ward_postcall<1, 0> >())
#endif
.def("__iter__", python::range<return_internal_reference<> >
@@ -311,16 +306,22 @@ void export_journal()
(&journal_t::period_xacts_begin, &journal_t::period_xacts_end))
.def("sources", python::range<return_internal_reference<> >
(&journal_t::sources_begin, &journal_t::sources_end))
-
+#if 0
.def("read", py_read)
-
+#endif
.def("has_xdata", &journal_t::has_xdata)
.def("clear_xdata", &journal_t::clear_xdata)
- .def("collect", py_collect, with_custodian_and_ward_postcall<0, 1>())
+ .def("query", py_query)
.def("valid", &journal_t::valid)
;
+
+#define EXC_TRANSLATE(type) \
+ register_exception_translator<type>(&exc_translate_ ## type);
+
+ EXC_TRANSLATE(parse_error);
+ EXC_TRANSLATE(error_count);
}
} // namespace ledger
diff --git a/src/py_post.cc b/src/py_post.cc
index 62323eb1..692542a0 100644
--- a/src/py_post.cc
+++ b/src/py_post.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -116,7 +116,7 @@ void export_post()
make_setter(&post_t::xdata_t::datetime))
.add_property("account",
make_getter(&post_t::xdata_t::account,
- return_value_policy<reference_existing_object>()),
+ return_internal_reference<>()),
make_setter(&post_t::xdata_t::account,
with_custodian_and_ward<1, 2>()))
.add_property("sort_values",
@@ -132,6 +132,9 @@ void export_post()
class_< post_t, bases<item_t> > ("Posting")
//.def(init<account_t *>())
+ .def("id", &post_t::id)
+ .def("seq", &post_t::seq)
+
.add_property("xact",
make_getter(&post_t::xact,
return_internal_reference<>()),
@@ -146,11 +149,15 @@ void export_post()
make_getter(&post_t::amount),
make_setter(&post_t::amount))
.add_property("cost",
- make_getter(&post_t::cost),
- make_setter(&post_t::cost))
+ make_getter(&post_t::cost,
+ return_value_policy<return_by_value>()),
+ make_setter(&post_t::cost,
+ return_value_policy<return_by_value>()))
.add_property("assigned_amount",
- make_getter(&post_t::assigned_amount),
- make_setter(&post_t::assigned_amount))
+ make_getter(&post_t::assigned_amount,
+ return_value_policy<return_by_value>()),
+ make_setter(&post_t::assigned_amount,
+ return_value_policy<return_by_value>()))
.def("has_tag", py_has_tag_1s)
.def("has_tag", py_has_tag_1m)
@@ -159,8 +166,8 @@ void export_post()
.def("get_tag", py_get_tag_1m)
.def("get_tag", py_get_tag_2m)
- .def("date", &post_t::date)
- .def("effective_date", &post_t::effective_date)
+ .add_property("date", &post_t::date)
+ .add_property("aux_date", &post_t::aux_date)
.def("must_balance", &post_t::must_balance)
@@ -170,8 +177,7 @@ void export_post()
.def("has_xdata", &post_t::has_xdata)
.def("clear_xdata", &post_t::clear_xdata)
- .def("xdata", py_xdata,
- return_internal_reference<>())
+ .def("xdata", py_xdata, return_internal_reference<>())
//.def("add_to_value", &post_t::add_to_value)
.def("set_reported_account", &post_t::set_reported_account)
diff --git a/src/py_session.cc b/src/py_session.cc
new file mode 100644
index 00000000..f411d5e1
--- /dev/null
+++ b/src/py_session.cc
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "session.h"
+
+namespace ledger {
+
+using namespace boost::python;
+
+namespace {
+ journal_t * py_read_journal(const string& pathname)
+ {
+ return python_session->read_journal(path(pathname));
+ }
+
+ journal_t * py_read_journal_from_string(const string& data)
+ {
+ return python_session->read_journal_from_string(data);
+ }
+}
+
+void export_session()
+{
+ class_< session_t, boost::noncopyable > ("Session")
+ .def("read_journal", &session_t::read_journal,
+ return_internal_reference<>())
+ .def("read_journal_from_string", &session_t::read_journal_from_string,
+ return_internal_reference<>())
+ .def("read_journal_files", &session_t::read_journal_files,
+ return_internal_reference<>())
+ .def("close_journal_files", &session_t::close_journal_files)
+ ;
+
+ scope().attr("session") =
+ object(ptr(static_cast<session_t *>(python_session.get())));
+ scope().attr("read_journal") =
+ python::make_function(&py_read_journal,
+ return_internal_reference<>());
+ scope().attr("read_journal_from_string") =
+ python::make_function(&py_read_journal_from_string,
+ return_internal_reference<>());
+}
+
+} // namespace ledger
diff --git a/src/py_times.cc b/src/py_times.cc
index c2e0b8f8..17f9ec7e 100644
--- a/src/py_times.cc
+++ b/src/py_times.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/py_utils.cc b/src/py_utils.cc
index 710dca4b..45ffe545 100644
--- a/src/py_utils.cc
+++ b/src/py_utils.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/py_value.cc b/src/py_value.cc
index f8f36453..b931f008 100644
--- a/src/py_value.cc
+++ b/src/py_value.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -51,14 +51,19 @@ namespace {
return value.value(CURRENT_TIME());
}
boost::optional<value_t> py_value_1(const value_t& value,
- commodity_t& in_terms_of) {
+ const commodity_t * in_terms_of) {
return value.value(CURRENT_TIME(), in_terms_of);
}
boost::optional<value_t> py_value_2(const value_t& value,
- commodity_t& in_terms_of,
- datetime_t& moment) {
+ const commodity_t * in_terms_of,
+ const datetime_t& moment) {
return value.value(moment, in_terms_of);
}
+ boost::optional<value_t> py_value_2d(const value_t& value,
+ const commodity_t * in_terms_of,
+ const date_t& moment) {
+ return value.value(datetime_t(moment), in_terms_of);
+ }
PyObject * py_base_type(value_t& value)
{
@@ -147,7 +152,7 @@ void export_value()
.def(init<balance_t>())
.def(init<mask_t>())
.def(init<std::string>())
- // jww (2009-11-02): Need to support conversion eof value_t::sequence_t
+ // jww (2009-11-02): Need to support conversion of value_t::sequence_t
//.def(init<value_t::sequence_t>())
.def(init<value_t>())
@@ -265,9 +270,9 @@ void export_value()
.def("value", py_value_0)
.def("value", py_value_1, args("in_terms_of"))
.def("value", py_value_2, args("in_terms_of", "moment"))
+ .def("value", py_value_2d, args("in_terms_of", "moment"))
- .def("value", &value_t::value, value_overloads())
- .def("price", &value_t::price)
+ //.def("value", &value_t::value, value_overloads())
.def("exchange_commodities", &value_t::exchange_commodities,
exchange_commodities_overloads())
diff --git a/src/py_xact.cc b/src/py_xact.cc
index 604d8d59..3d792c7b 100644
--- a/src/py_xact.cc
+++ b/src/py_xact.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -76,6 +76,12 @@ namespace {
return **elem;
}
+ string py_xact_to_string(xact_t&)
+ {
+ // jww (2012-03-01): TODO
+ return empty_string;
+ }
+
} // unnamed namespace
using namespace boost::python;
@@ -107,6 +113,11 @@ void export_xact()
;
class_< xact_t, bases<xact_base_t> > ("Transaction")
+ .def("id", &xact_t::id)
+ .def("seq", &xact_t::seq)
+
+ .def("__str__", py_xact_to_string)
+
.add_property("code",
make_getter(&xact_t::code),
make_setter(&xact_t::code))
@@ -117,8 +128,6 @@ void export_xact()
.def("add_post", &xact_t::add_post, with_custodian_and_ward<1, 2>())
.def("magnitude", &xact_t::magnitude)
- .def("idstring", &xact_t::idstring)
- .def("id", &xact_t::id)
.def("lookup", &xact_t::lookup)
diff --git a/src/pyfstream.h b/src/pyfstream.h
index 49b072f2..18f28bc4 100644
--- a/src/pyfstream.h
+++ b/src/pyfstream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -86,8 +86,8 @@ protected:
public:
pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) {
- TRACE_CTOR(pyofstream, "PyFileObject *");
rdbuf(&buf);
+ TRACE_CTOR(pyofstream, "PyFileObject *");
}
~pyofstream() throw() {
TRACE_DTOR(pyofstream);
@@ -121,11 +121,11 @@ public:
* => force underflow()
*/
pyinbuf (PyFileObject * _fo) : fo(_fo) {
- TRACE_CTOR(pyinbuf, "PyFileObject *");
-
setg (buffer+pbSize, // beginning of putback area
buffer+pbSize, // read position
buffer+pbSize); // end position
+
+ TRACE_CTOR(pyinbuf, "PyFileObject *");
}
~pyinbuf() throw() {
TRACE_DTOR(pyinbuf);
@@ -191,8 +191,8 @@ protected:
public:
pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) {
- TRACE_CTOR(pyifstream, "PyFileObject *");
rdbuf(&buf);
+ TRACE_CTOR(pyifstream, "PyFileObject *");
}
~pyifstream() throw() {
TRACE_DTOR(pyifstream);
diff --git a/src/pyinterp.cc b/src/pyinterp.cc
index e0fd2d59..8d9c8c84 100644
--- a/src/pyinterp.cc
+++ b/src/pyinterp.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -32,6 +32,7 @@
#include <system.hh>
#include "pyinterp.h"
+#include "pyutils.h"
#include "account.h"
#include "xact.h"
#include "post.h"
@@ -51,6 +52,7 @@ void export_commodity();
void export_expr();
void export_format();
void export_item();
+void export_session();
void export_journal();
void export_post();
void export_times();
@@ -72,6 +74,7 @@ void initialize_for_python()
export_item();
export_post();
export_xact();
+ export_session();
export_journal();
}
@@ -81,16 +84,56 @@ struct python_run
python_run(python_interpreter_t * intepreter,
const string& str, int input_mode)
- : result(handle<>(borrowed(PyRun_String(str.c_str(), input_mode,
- intepreter->main_nspace.ptr(),
- intepreter->main_nspace.ptr())))) {}
+ : result
+ (handle<>
+ (borrowed
+ (PyRun_String(str.c_str(), input_mode,
+ intepreter->main_module->module_globals.ptr(),
+ intepreter->main_module->module_globals.ptr())))) {}
operator object() {
return result;
}
};
+python_module_t::python_module_t(const string& name)
+ : scope_t(), module_name(name), module_globals()
+{
+ import_module(name);
+}
+
+python_module_t::python_module_t(const string& name, python::object obj)
+ : scope_t(), module_name(name), module_globals()
+{
+ module_object = obj;
+ module_globals = extract<dict>(module_object.attr("__dict__"));
+}
+
+void python_module_t::import_module(const string& name, bool import_direct)
+{
+ object mod = python::import(name.c_str());
+ if (! mod)
+ throw_(std::runtime_error,
+ _("Module import failed (couldn't find %1)") << name);
+
+ dict globals = extract<dict>(mod.attr("__dict__"));
+ if (! globals)
+ throw_(std::runtime_error,
+ _("Module import failed (couldn't find %1)") << name);
+
+ if (! import_direct) {
+ module_object = mod;
+ module_globals = globals;
+ } else {
+ // Import all top-level entries directly into the namespace
+ module_globals.update(mod.attr("__dict__"));
+ }
+}
+
void python_interpreter_t::initialize()
{
+ if (is_initialized)
+ return;
+
TRACE_START(python_init, 1, "Initialized Python");
try {
@@ -101,15 +144,7 @@ void python_interpreter_t::initialize()
hack_system_paths();
- object main_module = python::import("__main__");
- if (! main_module)
- throw_(std::runtime_error,
- _("Python failed to initialize (couldn't find __main__)"));
-
- main_nspace = extract<dict>(main_module.attr("__dict__"));
- if (! main_nspace)
- throw_(std::runtime_error,
- _("Python failed to initialize (couldn't find __dict__)"));
+ main_module = import_module("__main__");
python::detail::init_module("ledger", &initialize_for_python);
@@ -167,58 +202,57 @@ void python_interpreter_t::hack_system_paths()
#endif
}
-object python_interpreter_t::import_into_main(const string& str)
+object python_interpreter_t::import_option(const string& str)
{
if (! is_initialized)
initialize();
- try {
- object mod = python::import(str.c_str());
- if (! mod)
- throw_(std::runtime_error,
- _("Failed to import Python module %1") << str);
-
- // Import all top-level entries directly into the main namespace
- main_nspace.update(mod.attr("__dict__"));
-
- return mod;
- }
- catch (const error_already_set&) {
- PyErr_Print();
- }
- return object();
-}
-
-object python_interpreter_t::import_option(const string& str)
-{
- path file(str);
-
python::object sys_module = python::import("sys");
python::object sys_dict = sys_module.attr("__dict__");
+ path file(str);
+ string name(str);
python::list paths(sys_dict["path"]);
+ if (contains(str, ".py")) {
#if BOOST_VERSION >= 103700
- paths.insert(0, file.parent_path().string());
- sys_dict["path"] = paths;
+ path& cwd(parsing_context.get_current().current_directory);
+#if BOOST_VERSION >= 104600 && BOOST_FILESYSTEM_VERSION >= 3
+ path parent(filesystem::absolute(file, cwd).parent_path());
+#else
+ path parent(filesystem::complete(file, cwd).parent_path());
+#endif
+ DEBUG("python.interp", "Adding " << parent << " to PYTHONPATH");
+ paths.insert(0, parent.string());
+ sys_dict["path"] = paths;
#if BOOST_VERSION >= 104600
- string name = file.filename().string();
- if (contains(name, ".py"))
name = file.stem().string();
#else
- string name = file.filename();
- if (contains(name, ".py"))
name = file.stem();
#endif
#else // BOOST_VERSION >= 103700
- paths.insert(0, file.branch_path().string());
- sys_dict["path"] = paths;
-
- string name = file.leaf();
+ paths.insert(0, file.branch_path().string());
+ sys_dict["path"] = paths;
+ name = file.leaf();
#endif // BOOST_VERSION >= 103700
+ }
- return python::import(python::str(name.c_str()));
+ try {
+ if (contains(str, ".py"))
+ main_module->import_module(name, true);
+ else
+ import_module(str);
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(std::runtime_error, _("Python failed to import: %1") << str);
+ }
+ catch (...) {
+ throw;
+ }
+
+ return object();
}
object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode)
@@ -346,13 +380,13 @@ value_t python_interpreter_t::server_command(call_scope_t& args)
functor_t func(main_function, "main");
try {
func(args);
+ return true;
}
catch (const error_already_set&) {
PyErr_Print();
throw_(std::runtime_error,
_("Error while invoking ledger.server's main() function"));
}
- return true;
} else {
throw_(std::runtime_error,
_("The ledger.server module is missing its main() function!"));
@@ -372,6 +406,40 @@ python_interpreter_t::lookup_option(const char * p)
return NULL;
}
+expr_t::ptr_op_t python_module_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ switch (kind) {
+ case symbol_t::FUNCTION:
+ DEBUG("python.interp", "Python lookup: " << name);
+ if (module_globals.has_key(name.c_str())) {
+ if (python::object obj = module_globals.get(name.c_str())) {
+ if (PyModule_Check(obj.ptr())) {
+ shared_ptr<python_module_t> mod;
+ python_module_map_t::iterator i =
+ python_session->modules_map.find(obj.ptr());
+ if (i == python_session->modules_map.end()) {
+ mod.reset(new python_module_t(name, obj));
+ python_session->modules_map.insert
+ (python_module_map_t::value_type(obj.ptr(), mod));
+ } else {
+ mod = (*i).second;
+ }
+ return expr_t::op_t::wrap_value(scope_value(mod.get()));
+ } else {
+ return WRAP_FUNCTOR(python_interpreter_t::functor_t(obj, name));
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
expr_t::ptr_op_t python_interpreter_t::lookup(const symbol_t::kind_t kind,
const string& name)
{
@@ -381,21 +449,18 @@ expr_t::ptr_op_t python_interpreter_t::lookup(const symbol_t::kind_t kind,
switch (kind) {
case symbol_t::FUNCTION:
- if (option_t<python_interpreter_t> * handler = lookup_option(name.c_str()))
- return MAKE_OPT_FUNCTOR(python_interpreter_t, handler);
-
- if (is_initialized && main_nspace.has_key(name.c_str())) {
- DEBUG("python.interp", "Python lookup: " << name);
-
- if (python::object obj = main_nspace.get(name.c_str()))
- return WRAP_FUNCTOR(functor_t(obj, name));
- }
+ if (is_initialized)
+ return main_module->lookup(kind, name);
break;
- case symbol_t::OPTION:
+ case symbol_t::OPTION: {
if (option_t<python_interpreter_t> * handler = lookup_option(name.c_str()))
return MAKE_OPT_HANDLER(python_interpreter_t, handler);
+
+ if (is_initialized)
+ return main_module->lookup(symbol_t::FUNCTION, string("option_") + name);
break;
+ }
case symbol_t::PRECOMMAND: {
const char * p = name.c_str();
@@ -420,29 +485,59 @@ expr_t::ptr_op_t python_interpreter_t::lookup(const symbol_t::kind_t kind,
}
namespace {
- void append_value(list& lst, const value_t& value)
+ object convert_value_to_python(const value_t& val)
{
- if (value.is_scope()) {
- const scope_t * scope = value.as_scope();
- if (const post_t * post = dynamic_cast<const post_t *>(scope))
- lst.append(ptr(post));
- else if (const xact_t * xact = dynamic_cast<const xact_t *>(scope))
- lst.append(ptr(xact));
- else if (const account_t * account =
- dynamic_cast<const account_t *>(scope))
- lst.append(ptr(account));
- else if (const period_xact_t * period_xact =
- dynamic_cast<const period_xact_t *>(scope))
- lst.append(ptr(period_xact));
- else if (const auto_xact_t * auto_xact =
- dynamic_cast<const auto_xact_t *>(scope))
- lst.append(ptr(auto_xact));
- else
- throw_(std::logic_error,
- _("Cannot downcast scoped object to specific type"));
- } else {
- lst.append(value);
+ switch (val.type()) {
+ case value_t::VOID: // a null value (i.e., uninitialized)
+ return object();
+ case value_t::BOOLEAN: // a boolean
+ return object(val.to_boolean());
+ case value_t::DATETIME: // a date and time (Boost posix_time)
+ return object(val.to_datetime());
+ case value_t::DATE: // a date (Boost gregorian::date)
+ return object(val.to_date());
+ case value_t::INTEGER: // a signed integer value
+ return object(val.to_long());
+ case value_t::AMOUNT: // a ledger::amount_t
+ return object(val.as_amount());
+ case value_t::BALANCE: // a ledger::balance_t
+ return object(val.as_balance());
+ case value_t::STRING: // a string object
+ return object(handle<>(borrowed(str_to_py_unicode(val.as_string()))));
+ case value_t::MASK: // a regular expression mask
+ return object(val);
+ case value_t::SEQUENCE: { // a vector of value_t objects
+ list arglist;
+ foreach (const value_t& elem, val.as_sequence())
+ arglist.append(elem);
+ return arglist;
+ }
+ case value_t::SCOPE: // a pointer to a scope
+ if (const scope_t * scope = val.as_scope()) {
+ if (const post_t * post = dynamic_cast<const post_t *>(scope))
+ return object(ptr(post));
+ else if (const xact_t * xact = dynamic_cast<const xact_t *>(scope))
+ return object(ptr(xact));
+ else if (const account_t * account =
+ dynamic_cast<const account_t *>(scope))
+ return object(ptr(account));
+ else if (const period_xact_t * period_xact =
+ dynamic_cast<const period_xact_t *>(scope))
+ return object(ptr(period_xact));
+ else if (const auto_xact_t * auto_xact =
+ dynamic_cast<const auto_xact_t *>(scope))
+ return object(ptr(auto_xact));
+ else
+ throw_(std::logic_error,
+ _("Cannot downcast scoped object to specific type"));
+ }
+ return object();
+ case value_t::ANY: // a pointer to an arbitrary object
+ return object(val);
}
+#if !defined(__clang__)
+ return object();
+#endif
}
}
@@ -453,6 +548,7 @@ value_t python_interpreter_t::functor_t::operator()(call_scope_t& args)
if (! PyCallable_Check(func.ptr())) {
extract<value_t> val(func);
+ DEBUG("python.interp", "Value of Python '" << name << "': " << val);
std::signal(SIGINT, sigint_handler);
if (val.check())
return val();
@@ -464,9 +560,9 @@ value_t python_interpreter_t::functor_t::operator()(call_scope_t& args)
// rather than a sequence of arguments?
if (args.value().is_sequence())
foreach (const value_t& value, args.value().as_sequence())
- append_value(arglist, value);
+ arglist.append(convert_value_to_python(value));
else
- append_value(arglist, args.value());
+ arglist.append(convert_value_to_python(args.value()));
if (PyObject * val =
PyObject_CallObject(func.ptr(), python::tuple(arglist).ptr())) {
@@ -474,11 +570,12 @@ value_t python_interpreter_t::functor_t::operator()(call_scope_t& args)
value_t result;
if (xval.check()) {
result = xval();
+ DEBUG("python.interp",
+ "Return from Python '" << name << "': " << result);
Py_DECREF(val);
} else {
Py_DECREF(val);
- throw_(calc_error,
- _("Could not evaluate Python variable '%1'") << name);
+ return NULL_VALUE;
}
std::signal(SIGINT, sigint_handler);
return result;
diff --git a/src/pyinterp.h b/src/pyinterp.h
index ea947c5a..556b1563 100644
--- a/src/pyinterp.h
+++ b/src/pyinterp.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -38,20 +38,52 @@
namespace ledger {
+class python_module_t : public scope_t, public noncopyable
+{
+public:
+ string module_name;
+ python::object module_object;
+ python::dict module_globals;
+
+ explicit python_module_t(const string& name);
+ explicit python_module_t(const string& name, python::object obj);
+
+ void import_module(const string& name, bool import_direct = false);
+
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+ void define_global(const string& name, python::object obj) {
+ module_globals[name] = obj;
+ }
+
+ virtual string description() {
+ return module_name;
+ }
+};
+
+typedef std::map<PyObject *, shared_ptr<python_module_t> > python_module_map_t;
+
class python_interpreter_t : public session_t
{
public:
- python::dict main_nspace;
bool is_initialized;
- python_interpreter_t()
- : session_t(), main_nspace(), is_initialized(false) {
+ shared_ptr<python_module_t> main_module;
+ python_module_map_t modules_map;
+
+ shared_ptr<python_module_t> import_module(const string& name) {
+ shared_ptr<python_module_t> mod(new python_module_t(name));
+ if (name != "__main__")
+ main_module->define_global(name, mod->module_object);
+ return mod;
+ }
+
+ python_interpreter_t() : session_t(), is_initialized(false) {
TRACE_CTOR(python_interpreter_t, "");
}
-
virtual ~python_interpreter_t() {
TRACE_DTOR(python_interpreter_t);
-
if (is_initialized)
Py_Finalize();
}
@@ -59,7 +91,6 @@ public:
void initialize();
void hack_system_paths();
- python::object import_into_main(const string& name);
python::object import_option(const string& name);
enum py_eval_mode_t {
@@ -68,14 +99,10 @@ public:
PY_EVAL_MULTI
};
- python::object eval(std::istream& in,
- py_eval_mode_t mode = PY_EVAL_EXPR);
- python::object eval(const string& str,
- py_eval_mode_t mode = PY_EVAL_EXPR);
- python::object eval(const char * c_str,
- py_eval_mode_t mode = PY_EVAL_EXPR) {
- string str(c_str);
- return eval(str, mode);
+ python::object eval(std::istream& in, py_eval_mode_t mode = PY_EVAL_EXPR);
+ python::object eval(const string& str, py_eval_mode_t mode = PY_EVAL_EXPR);
+ python::object eval(const char * c_str, py_eval_mode_t mode = PY_EVAL_EXPR) {
+ return eval(string(c_str), mode);
}
value_t python_command(call_scope_t& scope);
@@ -109,8 +136,8 @@ public:
virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
const string& name);
- OPTION_(python_interpreter_t, import_, DO_(args) {
- parent->import_option(args.get<string>(1));
+ OPTION_(python_interpreter_t, import_, DO_(str) {
+ parent->import_option(str);
});
};
diff --git a/src/pyledger.cc b/src/pyledger.cc
index 4a53532a..cf5e362e 100644
--- a/src/pyledger.cc
+++ b/src/pyledger.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/pyutils.h b/src/pyutils.h
index 7e016502..44bb6d90 100644
--- a/src/pyutils.h
+++ b/src/pyutils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/query.cc b/src/query.cc
index 812123cb..3fec708a 100644
--- a/src/query.cc
+++ b/src/query.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -146,7 +146,7 @@ query_t::lexer_t::next_token(query_t::lexer_t::token_t::kind_t tok_context)
case '\t':
case '\n':
case '\r':
- if (! multiple_args && ! consume_whitespace)
+ if (! multiple_args && ! consume_whitespace && ! consume_next_arg)
goto test_ident;
else
ident.push_back(*arg_i);
diff --git a/src/query.h b/src/query.h
index 8f7917b2..fe52eb35 100644
--- a/src/query.h
+++ b/src/query.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -153,8 +153,6 @@ public:
case TERM: return string("TERM(") + *value + ")";
case END_REACHED: return "END_REACHED";
}
- assert(false);
- return empty_string;
}
string symbol() const {
@@ -188,6 +186,9 @@ public:
assert(false);
return "<UNKNOWN>";
}
+#if !defined(__clang__)
+ return "<ERROR>";
+#endif
}
void unexpected();
@@ -203,10 +204,11 @@ public:
consume_whitespace(false), consume_next_arg(false),
multiple_args(_multiple_args)
{
- TRACE_CTOR(query_t::lexer_t, "");
assert(begin != end);
arg_i = (*begin).as_string().begin();
arg_end = (*begin).as_string().end();
+
+ TRACE_CTOR(query_t::lexer_t, "");
}
lexer_t(const lexer_t& lexer)
: begin(lexer.begin), end(lexer.end),
@@ -301,18 +303,19 @@ public:
query_t(const string& arg,
const keep_details_t& what_to_keep = keep_details_t(),
bool multiple_args = true) {
- TRACE_CTOR(query_t, "string, keep_details_t, bool");
if (! arg.empty()) {
value_t temp(string_value(arg));
parse_args(temp.to_sequence(), what_to_keep, multiple_args);
}
+ TRACE_CTOR(query_t, "string, keep_details_t, bool");
}
query_t(const value_t& args,
const keep_details_t& what_to_keep = keep_details_t(),
bool multiple_args = true) {
- TRACE_CTOR(query_t, "value_t, keep_details_t, bool");
if (! args.empty())
parse_args(args, what_to_keep, multiple_args);
+
+ TRACE_CTOR(query_t, "value_t, keep_details_t, bool");
}
virtual ~query_t() {
TRACE_DTOR(query_t);
diff --git a/src/quotes.cc b/src/quotes.cc
index 0cc8d06b..c33e0826 100644
--- a/src/quotes.cc
+++ b/src/quotes.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -40,7 +40,7 @@ namespace ledger {
optional<price_point_t>
commodity_quote_from_script(commodity_t& commodity,
- const optional<commodity_t&>& exchange_commodity)
+ const commodity_t * exchange_commodity)
{
DEBUG("commodity.download", "downloading quote for symbol " << commodity.symbol());
#if defined(DEBUG_ON)
diff --git a/src/quotes.h b/src/quotes.h
index 376d8918..56740e47 100644
--- a/src/quotes.h
+++ b/src/quotes.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -46,7 +46,7 @@ namespace ledger {
optional<price_point_t>
commodity_quote_from_script(commodity_t& commodity,
- const optional<commodity_t&>& exchange_commodity);
+ const commodity_t * exchange_commodity);
} // namespace ledger
diff --git a/src/report.cc b/src/report.cc
index f4dc450e..c7559cf7 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -41,6 +41,7 @@
#include "iterators.h"
#include "filters.h"
#include "precmd.h"
+#include "select.h"
#include "stats.h"
#include "generate.h"
#include "draft.h"
@@ -59,7 +60,7 @@ void report_t::normalize_options(const string& verb)
#ifdef HAVE_ISATTY
if (! HANDLED(force_color)) {
if (! HANDLED(no_color) && isatty(STDOUT_FILENO))
- HANDLER(color).on_only(string("?normalize"));
+ HANDLER(color).on("?normalize");
if (HANDLED(color) && ! isatty(STDOUT_FILENO))
HANDLER(color).off();
}
@@ -76,15 +77,14 @@ void report_t::normalize_options(const string& verb)
HANDLER(pager_).off();
}
- item_t::use_effective_date = (HANDLED(effective) &&
- ! HANDLED(actual_dates));
+ item_t::use_aux_date = (HANDLED(aux_date) && ! HANDLED(primary_date));
commodity_pool_t::current_pool->keep_base = HANDLED(base);
commodity_pool_t::current_pool->get_quotes = session.HANDLED(download);
if (session.HANDLED(price_exp_))
commodity_pool_t::current_pool->quote_leeway =
- session.HANDLER(price_exp_).value.as_long();
+ lexical_cast<long>(session.HANDLER(price_exp_).value) * 3600L;
if (session.HANDLED(price_db_))
commodity_pool_t::current_pool->price_db = session.HANDLER(price_db_).str();
@@ -107,39 +107,35 @@ void report_t::normalize_options(const string& verb)
if (! HANDLED(meta_width_)) {
string::size_type i = HANDLER(meta_).str().find(':');
if (i != string::npos) {
- HANDLED(meta_width_).on_with
- (string("?normalize"),
- lexical_cast<long>(string(HANDLER(meta_).str(), i + 1)));
- HANDLED(meta_).on(string("?normalize"),
+ HANDLED(meta_width_).on("?normalize",
+ string(HANDLER(meta_).str(), i + 1));
+ HANDLED(meta_).on("?normalize",
string(HANDLER(meta_).str(), 0, i));
}
}
if (HANDLED(meta_width_)) {
- HANDLER(prepend_format_).on
- (string("?normalize"),
- string("%(justify(truncated(tag(\"") +
- HANDLER(meta_).str() + "\"), " +
- HANDLED(meta_width_).value.to_string() + " - 1), " +
- HANDLED(meta_width_).value.to_string() + "))");
- meta_width = HANDLED(meta_width_).value.to_long();
+ HANDLER(prepend_format_)
+ .on("?normalize", string("%(justify(truncated(tag(\"") +
+ HANDLER(meta_).str() + "\"), " +
+ HANDLED(meta_width_).value + " - 1), " +
+ HANDLED(meta_width_).value + "))");
+ meta_width = lexical_cast<long>(HANDLED(meta_width_).value);
} else {
- HANDLER(prepend_format_).on(string("?normalize"), string("%(tag(\"") +
- HANDLER(meta_).str() + "\"))");
+ HANDLER(prepend_format_)
+ .on("?normalize", string("%(tag(\"") + HANDLER(meta_).str() + "\"))");
}
}
- if (! HANDLED(prepend_width_))
- HANDLER(prepend_width_).on_with(string("?normalize"), static_cast<long>(0));
if (verb == "print" || verb == "xact" || verb == "dump") {
- HANDLER(related).on_only(string("?normalize"));
- HANDLER(related_all).on_only(string("?normalize"));
+ HANDLER(related_all).parent = this;
+ HANDLER(related_all).on("?normalize");
}
else if (verb == "equity") {
- HANDLER(equity).on_only(string("?normalize"));
+ HANDLER(equity).on("?normalize");
}
if (verb[0] != 'b' && verb[0] != 'r')
- HANDLER(base).on_only(string("?normalize"));
+ HANDLER(base).on("?normalize");
// If a time period was specified with -p, check whether it also gave a
// begin and/or end to the report period (though these can be overridden
@@ -153,12 +149,10 @@ void report_t::normalize_options(const string& verb)
// to avoid option ordering issues were we to have done it during the
// initial parsing of the options.
if (HANDLED(amount_data)) {
- HANDLER(format_)
- .on_with(string("?normalize"), HANDLER(plot_amount_format_).value);
+ HANDLER(format_).on("?normalize", HANDLER(plot_amount_format_).value);
}
else if (HANDLED(total_data)) {
- HANDLER(format_)
- .on_with(string("?normalize"), HANDLER(plot_total_format_).value);
+ HANDLER(format_).on("?normalize", HANDLER(plot_total_format_).value);
}
// If the --exchange (-X) option was used, parse out any final price
@@ -169,9 +163,26 @@ void report_t::normalize_options(const string& verb)
terminus);
}
+ if (HANDLED(percent)) {
+ commodity_t::decimal_comma_by_default = false;
+ if (HANDLED(market)) {
+ HANDLER(total_)
+ .on("?normalize",
+ "(__tmp = market(parent.total, value_date, exchange);"
+ " ((is_account & parent & __tmp) ?"
+ " percent(scrub(market(total, value_date, exchange)), "
+ " scrub(__tmp)) : 0))");
+ }
+ }
+
+ if (HANDLED(immediate) && HANDLED(market)) {
+ HANDLER(amount_)
+ .on("?normalize", "market(amount_expr, value_date, exchange)");
+ }
+
long cols = 0;
if (HANDLED(columns_))
- cols = HANDLER(columns_).value.to_long();
+ cols = lexical_cast<long>(HANDLER(columns_).value);
else if (const char * columns = std::getenv("COLUMNS"))
cols = lexical_cast<long>(columns);
else
@@ -183,23 +194,21 @@ void report_t::normalize_options(const string& verb)
if (cols > 0) {
DEBUG("auto.columns", "cols = " << cols);
- if (! HANDLER(date_width_).specified)
- HANDLER(date_width_)
- .on_with(none, static_cast<long>(format_date(CURRENT_DATE(),
- FMT_PRINTED).length()));
-
- long date_width = HANDLER(date_width_).value.to_long();
- long payee_width = (HANDLER(payee_width_).specified ?
- HANDLER(payee_width_).value.to_long() :
- int(double(cols) * 0.263157));
- long account_width = (HANDLER(account_width_).specified ?
- HANDLER(account_width_).value.to_long() :
- int(double(cols) * 0.302631));
- long amount_width = (HANDLER(amount_width_).specified ?
- HANDLER(amount_width_).value.to_long() :
- int(double(cols) * 0.157894));
- long total_width = (HANDLER(total_width_).specified ?
- HANDLER(total_width_).value.to_long() :
+ long date_width = (HANDLED(date_width_) ?
+ lexical_cast<long>(HANDLER(date_width_).str()) :
+ static_cast<long>
+ (format_date(CURRENT_DATE(),FMT_PRINTED).length()));
+ long payee_width = (HANDLED(payee_width_) ?
+ lexical_cast<long>(HANDLER(payee_width_).str()) :
+ long(double(cols) * 0.263157));
+ long account_width = (HANDLED(account_width_) ?
+ lexical_cast<long>(HANDLER(account_width_).str()) :
+ long(double(cols) * 0.302631));
+ long amount_width = (HANDLED(amount_width_) ?
+ lexical_cast<long>(HANDLER(amount_width_).str()) :
+ long(double(cols) * 0.157894));
+ long total_width = (HANDLED(total_width_) ?
+ lexical_cast<long>(HANDLER(total_width_).str()) :
amount_width);
DEBUG("auto.columns", "date_width = " << date_width);
@@ -208,32 +217,46 @@ void report_t::normalize_options(const string& verb)
DEBUG("auto.columns", "amount_width = " << amount_width);
DEBUG("auto.columns", "total_width = " << total_width);
- if (! HANDLER(date_width_).specified &&
- ! HANDLER(payee_width_).specified &&
- ! HANDLER(account_width_).specified &&
- ! HANDLER(amount_width_).specified &&
- ! HANDLER(total_width_).specified) {
+ if (! HANDLED(date_width_) &&
+ ! HANDLED(payee_width_) &&
+ ! HANDLED(account_width_) &&
+ ! HANDLED(amount_width_) &&
+ ! HANDLED(total_width_)) {
long total = (4 /* the spaces between */ + date_width + payee_width +
- account_width + amount_width + total_width);
- if (total > cols) {
+ account_width + amount_width + total_width +
+ (HANDLED(dc) ? 1 + amount_width : 0));
+ while (total > cols && account_width > 5 && payee_width > 5) {
DEBUG("auto.columns", "adjusting account down");
- account_width -= total - cols;
+ if (total > cols) {
+ --account_width;
+ --total;
+ if (total > cols) {
+ --account_width;
+ --total;
+ }
+ }
+ if (total > cols) {
+ --payee_width;
+ --total;
+ }
DEBUG("auto.columns", "account_width now = " << account_width);
}
}
if (! HANDLED(meta_width_))
- HANDLER(meta_width_).on_with(string("?normalize"), 0L);
- if (! HANDLER(date_width_).specified)
- HANDLER(date_width_).on_with(string("?normalize"), date_width);
- if (! HANDLER(payee_width_).specified)
- HANDLER(payee_width_).on_with(string("?normalize"), payee_width);
- if (! HANDLER(account_width_).specified)
- HANDLER(account_width_).on_with(string("?normalize"), account_width);
- if (! HANDLER(amount_width_).specified)
- HANDLER(amount_width_).on_with(string("?normalize"), amount_width);
- if (! HANDLER(total_width_).specified)
- HANDLER(total_width_).on_with(string("?normalize"), total_width);
+ HANDLER(meta_width_).value = "0";
+ if (! HANDLED(prepend_width_))
+ HANDLER(prepend_width_).value = "0";
+ if (! HANDLED(date_width_))
+ HANDLER(date_width_).value = to_string(date_width);
+ if (! HANDLED(payee_width_))
+ HANDLER(payee_width_).value = to_string(payee_width);
+ if (! HANDLED(account_width_))
+ HANDLER(account_width_).value = to_string(account_width);
+ if (! HANDLED(amount_width_))
+ HANDLER(amount_width_).value = to_string(amount_width);
+ if (! HANDLED(total_width_))
+ HANDLER(total_width_).value = to_string(total_width);
}
}
@@ -256,7 +279,7 @@ void report_t::normalize_period()
if (! interval.duration)
HANDLER(period_).off();
else if (! HANDLED(sort_all_))
- HANDLER(sort_xacts_).on_only(string("?normalize"));
+ HANDLER(sort_xacts_).on("?normalize");
}
void report_t::parse_query_args(const value_t& args, const string& whence)
@@ -279,7 +302,7 @@ void report_t::parse_query_args(const value_t& args, const string& whence)
}
if (query.has_query(query_t::QUERY_BOLD)) {
- HANDLER(bold_if_).set_expr(whence, query.get_query(query_t::QUERY_BOLD));
+ HANDLER(bold_if_).on(whence, query.get_query(query_t::QUERY_BOLD));
DEBUG("report.predicate", "Bolding predicate = " << HANDLER(bold_if_).str());
}
@@ -298,7 +321,12 @@ namespace {
report_t& report;
posts_flusher(post_handler_ptr _handler, report_t& _report)
- : handler(_handler), report(_report) {}
+ : handler(_handler), report(_report) {
+ TRACE_CTOR(posts_flusher, "post_handler_ptr, report_t&");
+ }
+ ~posts_flusher() throw() {
+ TRACE_DTOR(posts_flusher);
+ }
void operator()(const value_t&) {
report.session.journal->clear_xdata();
@@ -310,7 +338,7 @@ void report_t::posts_report(post_handler_ptr handler)
{
handler = chain_post_handlers(handler, *this);
if (HANDLED(group_by_)) {
- std::auto_ptr<post_splitter>
+ unique_ptr<post_splitter>
splitter(new post_splitter(handler, *this, HANDLER(group_by_).expr));
splitter->set_postflush_func(posts_flusher(handler, *this));
handler = post_handler_ptr(splitter.release());
@@ -330,9 +358,9 @@ void report_t::generate_report(post_handler_ptr handler)
generate_posts_iterator walker
(session, HANDLED(seed_) ?
- static_cast<unsigned int>(HANDLER(seed_).value.to_long()) : 0,
+ lexical_cast<unsigned int>(HANDLER(seed_).str()) : 0,
HANDLED(head_) ?
- static_cast<unsigned int>(HANDLER(head_).value.to_long()) : 50);
+ lexical_cast<unsigned int>(HANDLER(head_).str()) : 50);
pass_down_posts<generate_posts_iterator>(handler, walker);
}
@@ -447,8 +475,20 @@ void report_t::commodities_report(post_handler_ptr handler)
{
handler = chain_handlers(handler, *this);
- posts_commodities_iterator walker(*session.journal.get());
- pass_down_posts<posts_commodities_iterator>(handler, walker);
+ posts_commodities_iterator * walker(new posts_commodities_iterator(*session.journal.get()));
+ try {
+ pass_down_posts<posts_commodities_iterator>(handler, *walker);
+ }
+ catch (...) {
+#if defined(VERIFY_ON)
+ IF_VERIFY() {
+ // If --verify was used, clean up the posts_commodities_iterator.
+ // Otherwise, just leak like a sieve.
+ checked_delete(walker);
+ }
+#endif
+ throw;
+ }
session.journal->clear_xdata();
}
@@ -514,20 +554,32 @@ value_t report_t::fn_should_bold(call_scope_t& scope)
value_t report_t::fn_market(call_scope_t& args)
{
- optional<datetime_t> moment = (args.has<datetime_t>(1) ?
- args.get<datetime_t>(1) :
- optional<datetime_t>());
value_t result;
+ value_t arg0 = args[0];
+
+ datetime_t moment;
+ if (args.has<datetime_t>(1))
+ moment = args.get<datetime_t>(1);
+
+ if (arg0.is_string()) {
+ amount_t tmp(1L);
+ commodity_t * commodity =
+ commodity_pool_t::current_pool->find_or_create(arg0.as_string());
+ tmp.set_commodity(*commodity);
+ arg0 = tmp;
+ }
+
+ string target_commodity;
if (args.has<string>(2))
- result = args[0].exchange_commodities(args.get<string>(2),
- /* add_prices= */ false, moment);
- else
- result = args[0].value(moment);
+ target_commodity = args.get<string>(2);
- if (! result.is_null())
- return result;
+ if (! target_commodity.empty())
+ result = arg0.exchange_commodities(target_commodity,
+ /* add_prices= */ false, moment);
+ else
+ result = arg0.value(moment);
- return args[0];
+ return ! result.is_null() ? result : arg0;
}
value_t report_t::fn_get_at(call_scope_t& args)
@@ -536,13 +588,20 @@ value_t report_t::fn_get_at(call_scope_t& args)
if (index == 0) {
if (! args[0].is_sequence())
return args[0];
- } else {
- if (! args[0].is_sequence())
- throw_(std::runtime_error,
- _("Attempting to get argument at index %1 from %2")
- << index << args[0].label());
}
- return args[0].as_sequence()[index];
+ else if (! args[0].is_sequence()) {
+ throw_(std::runtime_error,
+ _("Attempting to get argument at index %1 from %2")
+ << index << args[0].label());
+ }
+
+ value_t::sequence_t& seq(args[0].as_sequence_lval());
+ if (index >= seq.size())
+ throw_(std::runtime_error,
+ _("Attempting to get index %1 from %2 with %3 elements")
+ << index << args[0].label() << seq.size());
+
+ return seq[index];
}
value_t report_t::fn_is_seq(call_scope_t& scope)
@@ -703,6 +762,15 @@ value_t report_t::fn_format_date(call_scope_t& args)
return string_value(format_date(args.get<date_t>(0), FMT_PRINTED));
}
+value_t report_t::fn_format_datetime(call_scope_t& args)
+{
+ if (args.has<string>(1))
+ return string_value(format_datetime(args.get<datetime_t>(0), FMT_CUSTOM,
+ args.get<string>(1).c_str()));
+ else
+ return string_value(format_datetime(args.get<datetime_t>(0), FMT_PRINTED));
+}
+
value_t report_t::fn_ansify_if(call_scope_t& args)
{
if (args.has<string>(1)) {
@@ -732,14 +800,62 @@ value_t report_t::fn_percent(call_scope_t& args)
(args.get<amount_t>(0) / args.get<amount_t>(1)).number());
}
-value_t report_t::fn_price(call_scope_t& args)
+value_t report_t::fn_commodity(call_scope_t& args)
{
- return args[0].price();
+ return string_value(args.get<amount_t>(0).commodity().symbol());
}
-value_t report_t::fn_commodity(call_scope_t& args)
+value_t report_t::fn_nail_down(call_scope_t& args)
{
- return string_value(args.get<amount_t>(0).commodity().symbol());
+ value_t arg0(args[0]);
+ value_t arg1(args[1]);
+
+ switch (arg0.type()) {
+ case value_t::AMOUNT: {
+ amount_t tmp(arg0.as_amount());
+ if (tmp.has_commodity() && ! tmp.is_null() && ! tmp.is_realzero()) {
+ arg1 = arg1.strip_annotations(keep_details_t()).to_amount();
+ expr_t value_expr(is_expr(arg1) ?
+ as_expr(arg1) :
+ expr_t::op_t::wrap_value(arg1.unrounded() /
+ arg0.number()));
+ std::ostringstream buf;
+ value_expr.print(buf);
+ value_expr.set_text(buf.str());
+
+ tmp.set_commodity(tmp.commodity().nail_down(value_expr));
+ }
+ return tmp;
+ }
+
+ case value_t::BALANCE: {
+ balance_t tmp;
+ foreach (const balance_t::amounts_map::value_type& pair,
+ arg0.as_balance_lval().amounts) {
+ call_scope_t inner_args(*args.parent);
+ inner_args.push_back(pair.second);
+ inner_args.push_back(arg1);
+ tmp += fn_nail_down(inner_args).as_amount();
+ }
+ return tmp;
+ }
+
+ case value_t::SEQUENCE: {
+ value_t tmp;
+ foreach (value_t& value, arg0.as_sequence_lval()) {
+ call_scope_t inner_args(*args.parent);
+ inner_args.push_back(value);
+ inner_args.push_back(arg1);
+ tmp.push_back(fn_nail_down(inner_args));
+ }
+ return tmp;
+ }
+
+ default:
+ throw_(std::runtime_error, _("Attempting to nail down %1")
+ << args[0].label());
+ }
+ return arg0;
}
value_t report_t::fn_lot_date(call_scope_t& args)
@@ -879,11 +995,9 @@ value_t report_t::echo_command(call_scope_t& args)
value_t report_t::pricemap_command(call_scope_t& args)
{
std::ostream& out(output_stream);
-
- commodity_pool_t::current_pool->print_pricemap
- (out, what_to_keep(), args.has<string>(0) ?
- optional<datetime_t>(datetime_t(parse_date(args.get<string>(0)))) : none);
-
+ commodity_pool_t::current_pool->commodity_price_history.print_map
+ (out, args.has<string>(0) ?
+ datetime_t(parse_date(args.get<string>(0))) : datetime_t());
return true;
}
@@ -914,6 +1028,9 @@ option_t<report_t> * report_t::lookup_option(const char * p)
case 'G':
OPT_CH(gain);
break;
+ case 'H':
+ OPT_CH(historical);
+ break;
case 'I':
OPT_CH(price);
break;
@@ -960,12 +1077,13 @@ option_t<report_t> * report_t::lookup_option(const char * p)
OPT(abbrev_len_);
else OPT_(account_);
else OPT(actual);
- else OPT(actual_dates);
else OPT(add_budget);
else OPT(amount_);
else OPT(amount_data);
+ else OPT_ALT(primary_date, actual_dates);
else OPT(anon);
else OPT_ALT(color, ansi);
+ else OPT(auto_match);
else OPT(average);
else OPT(account_width_);
else OPT(amount_width_);
@@ -977,6 +1095,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT_(begin_);
else OPT(bold_if_);
else OPT(budget);
+ else OPT(budget_format_);
else OPT(by_payee);
break;
case 'c':
@@ -995,21 +1114,22 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(date_);
else OPT(date_format_);
else OPT(datetime_format_);
+ else OPT(dc);
else OPT(depth_);
else OPT(deviation);
else OPT_(display_);
else OPT(display_amount_);
else OPT(display_total_);
- else OPT_ALT(dow, days-of-week);
+ else OPT_ALT(dow, days_of_week);
else OPT(date_width_);
break;
case 'e':
- OPT(effective);
- else OPT(empty);
+ OPT(empty);
else OPT_(end_);
else OPT(equity);
else OPT(exact);
else OPT(exchange_);
+ else OPT_ALT(aux_date, effective);
break;
case 'f':
OPT(flat);
@@ -1021,17 +1141,19 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT_ALT(head_, first_);
break;
case 'g':
- OPT(gain);
+ OPT_ALT(gain, change);
else OPT(group_by_);
else OPT(group_title_format_);
else OPT(generated);
break;
case 'h':
OPT(head_);
+ else OPT(historical);
break;
case 'i':
OPT(invert);
else OPT(inject_);
+ else OPT(immediate);
break;
case 'j':
OPT_CH(amount_data);
@@ -1040,13 +1162,13 @@ option_t<report_t> * report_t::lookup_option(const char * p)
OPT_(limit_);
else OPT(lot_dates);
else OPT(lot_prices);
- else OPT(lot_tags);
+ else OPT_ALT(lot_notes, lot_tags);
else OPT(lots);
else OPT(lots_actual);
else OPT_ALT(tail_, last_);
break;
case 'm':
- OPT(market);
+ OPT_ALT(market, value);
else OPT(monthly);
else OPT(meta_);
else OPT(meta_width_);
@@ -1093,6 +1215,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(revalued);
else OPT(revalued_only);
else OPT(revalued_total_);
+ else OPT_ALT(rich_data, detail);
break;
case 's':
OPT(sort_);
@@ -1109,6 +1232,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(total_data);
else OPT(truncate_);
else OPT(total_width_);
+ else OPT(time_report);
break;
case 'u':
OPT(unbudgeted);
@@ -1219,12 +1343,14 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
else if (is_eq(p, "display_total"))
return MAKE_FUNCTOR(report_t::fn_display_total);
else if (is_eq(p, "date"))
- return MAKE_FUNCTOR(report_t::fn_now);
+ return MAKE_FUNCTOR(report_t::fn_today);
break;
case 'f':
if (is_eq(p, "format_date"))
return MAKE_FUNCTOR(report_t::fn_format_date);
+ else if (is_eq(p, "format_datetime"))
+ return MAKE_FUNCTOR(report_t::fn_format_datetime);
else if (is_eq(p, "format"))
return MAKE_FUNCTOR(report_t::fn_format);
else if (is_eq(p, "floor"))
@@ -1262,6 +1388,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(fn_null);
else if (is_eq(p, "now"))
return MAKE_FUNCTOR(report_t::fn_now);
+ else if (is_eq(p, "nail_down"))
+ return MAKE_FUNCTOR(report_t::fn_nail_down);
break;
case 'o':
@@ -1274,8 +1402,6 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(fn_false);
else if (is_eq(p, "percent"))
return MAKE_FUNCTOR(report_t::fn_percent);
- else if (is_eq(p, "price"))
- return MAKE_FUNCTOR(report_t::fn_price);
else if (is_eq(p, "print"))
return MAKE_FUNCTOR(report_t::fn_print);
break;
@@ -1378,79 +1504,98 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return MAKE_OPT_HANDLER(report_t, handler);
break;
+#define POSTS_REPORTER(formatter) \
+ WRAP_FUNCTOR(reporter<>(post_handler_ptr(formatter), *this, \
+ string("#") + p))
+
+ // Can't use WRAP_FUNCTOR here because the template arguments
+ // confuse the parser
+#define POSTS_REPORTER_(method, formatter) \
+ expr_t::op_t::wrap_functor \
+ (reporter<post_t, post_handler_ptr, method> \
+ (post_handler_ptr(formatter), *this, string("#") + p))
+
+#define FORMATTED_POSTS_REPORTER(format) \
+ POSTS_REPORTER \
+ (new format_posts \
+ (*this, report_format(HANDLER(format)), \
+ maybe_format(HANDLER(prepend_format_)), \
+ HANDLED(prepend_width_) ? \
+ lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0))
+
+#define FORMATTED_COMMODITIES_REPORTER(format) \
+ POSTS_REPORTER_ \
+ (&report_t::commodities_report, \
+ new format_posts \
+ (*this, report_format(HANDLER(format)), \
+ maybe_format(HANDLER(prepend_format_)), \
+ HANDLED(prepend_width_) ? \
+ lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0))
+
+#define ACCOUNTS_REPORTER(formatter) \
+ expr_t::op_t::wrap_functor(reporter<account_t, acct_handler_ptr, \
+ &report_t::accounts_report> \
+ (acct_handler_ptr(formatter), *this, \
+ string("#") + p))
+
+#define FORMATTED_ACCOUNTS_REPORTER(format) \
+ ACCOUNTS_REPORTER \
+ (new format_accounts \
+ (*this, report_format(HANDLER(format)), \
+ maybe_format(HANDLER(prepend_format_)), \
+ HANDLED(prepend_width_) ? \
+ lexical_cast<std::size_t>(HANDLER(prepend_width_).str()) : 0))
+
case symbol_t::COMMAND:
switch (*p) {
case 'a':
if (is_eq(p, "accounts")) {
- return WRAP_FUNCTOR(reporter<>(new report_accounts(*this), *this,
- "#accounts"));
+ return POSTS_REPORTER(new report_accounts(*this));
}
break;
case 'b':
if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) {
- return expr_t::op_t::wrap_functor
- (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
- (new format_accounts(*this, report_format(HANDLER(balance_format_)),
- maybe_format(HANDLER(prepend_format_)),
- HANDLER(prepend_width_).value.to_size_t()),
- *this, "#balance"));
+ return FORMATTED_ACCOUNTS_REPORTER(balance_format_);
}
else if (is_eq(p, "budget")) {
- HANDLER(amount_).set_expr(string("#budget"), "(amount, 0)");
+ HANDLER(amount_).on(string("#budget"), "(amount, 0)");
budget_flags |= BUDGET_WRAP_VALUES;
if (! (budget_flags & ~BUDGET_WRAP_VALUES))
budget_flags |= BUDGET_BUDGETED;
- return expr_t::op_t::wrap_functor
- (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
- (new format_accounts(*this, report_format(HANDLER(budget_format_)),
- maybe_format(HANDLER(prepend_format_)),
- HANDLER(prepend_width_).value.to_size_t()),
- *this, "#budget"));
+ return FORMATTED_ACCOUNTS_REPORTER(budget_format_);
}
break;
case 'c':
if (is_eq(p, "csv")) {
- return WRAP_FUNCTOR
- (reporter<>
- (new format_posts(*this, report_format(HANDLER(csv_format_)),
- maybe_format(HANDLER(prepend_format_)),
- HANDLER(prepend_width_).value.to_size_t()),
- *this, "#csv"));
+ return FORMATTED_POSTS_REPORTER(csv_format_);
}
else if (is_eq(p, "cleared")) {
- HANDLER(amount_).set_expr(string("#cleared"),
- "(amount, cleared ? amount : 0)");
- return expr_t::op_t::wrap_functor
- (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
- (new format_accounts(*this, report_format(HANDLER(cleared_format_)),
- maybe_format(HANDLER(prepend_format_)),
- HANDLER(prepend_width_).value.to_size_t()),
- *this, "#cleared"));
+ HANDLER(amount_).on(string("#cleared"),
+ "(amount, cleared ? amount : 0)");
+ return FORMATTED_ACCOUNTS_REPORTER(cleared_format_);
}
else if (is_eq(p, "convert")) {
return WRAP_FUNCTOR(convert_command);
}
else if (is_eq(p, "commodities")) {
- return WRAP_FUNCTOR(reporter<>(new report_commodities(*this), *this,
- "#commodities"));
+ return POSTS_REPORTER(new report_commodities(*this));
}
break;
case 'e':
if (is_eq(p, "equity")) {
- HANDLER(generated).on_only(string("#equity"));
- return WRAP_FUNCTOR(reporter<>(new print_xacts(*this), *this, "#equity"));
+ HANDLER(generated).on("#equity");
+ return POSTS_REPORTER(new print_xacts(*this));
}
else if (is_eq(p, "entry")) {
return WRAP_FUNCTOR(xact_command);
}
else if (is_eq(p, "emacs")) {
- return WRAP_FUNCTOR
- (reporter<>(new format_emacs_posts(output_stream), *this, "#emacs"));
+ return POSTS_REPORTER(new format_emacs_posts(output_stream));
}
else if (is_eq(p, "echo")) {
return MAKE_FUNCTOR(report_t::echo_command);
@@ -1459,51 +1604,32 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
case 'o':
if (is_eq(p, "org")) {
- return WRAP_FUNCTOR
- (reporter<>
- (new posts_to_org_table(*this, maybe_format(HANDLER(prepend_format_))),
- *this, "#org"));
+ return POSTS_REPORTER(new posts_to_org_table
+ (*this, maybe_format(HANDLER(prepend_format_))));
}
break;
case 'p':
if (*(p + 1) == '\0' || is_eq(p, "print")) {
- return WRAP_FUNCTOR
- (reporter<>(new print_xacts(*this, HANDLED(raw)), *this, "#print"));
+ return POSTS_REPORTER(new print_xacts(*this, HANDLED(raw)));
}
else if (is_eq(p, "prices")) {
- return expr_t::op_t::wrap_functor
- (reporter<post_t, post_handler_ptr, &report_t::commodities_report>
- (new format_posts(*this, report_format(HANDLER(prices_format_)),
- maybe_format(HANDLER(prepend_format_)),
- HANDLER(prepend_width_).value.to_size_t()),
- *this, "#prices"));
+ return FORMATTED_COMMODITIES_REPORTER(prices_format_);
}
- else if (is_eq(p, "pricedb")) {
- return expr_t::op_t::wrap_functor
- (reporter<post_t, post_handler_ptr, &report_t::commodities_report>
- (new format_posts(*this, report_format(HANDLER(pricedb_format_)),
- maybe_format(HANDLER(prepend_format_)),
- HANDLER(prepend_width_).value.to_size_t()),
- *this, "#pricedb"));
+ else if (is_eq(p, "pricedb") || is_eq(p, "pricesdb")) {
+ return FORMATTED_COMMODITIES_REPORTER(pricedb_format_);
}
else if (is_eq(p, "pricemap")) {
return MAKE_FUNCTOR(report_t::pricemap_command);
}
else if (is_eq(p, "payees")) {
- return WRAP_FUNCTOR(reporter<>(new report_payees(*this), *this,
- "#payees"));
+ return POSTS_REPORTER(new report_payees(*this));
}
break;
case 'r':
if (*(p + 1) == '\0' || is_eq(p, "reg") || is_eq(p, "register")) {
- return WRAP_FUNCTOR
- (reporter<>
- (new format_posts(*this, report_format(HANDLER(register_format_)),
- maybe_format(HANDLER(prepend_format_)),
- HANDLER(prepend_width_).value.to_size_t()),
- *this, "#register"));
+ return FORMATTED_POSTS_REPORTER(register_format_);
}
else if (is_eq(p, "reload")) {
return MAKE_FUNCTOR(report_t::reload_command);
@@ -1515,13 +1641,15 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(report_statistics);
else if (is_eq(p, "source"))
return WRAP_FUNCTOR(source_command);
+ else if (is_eq(p, "select"))
+ return WRAP_FUNCTOR(select_command);
break;
case 'x':
if (is_eq(p, "xact"))
return WRAP_FUNCTOR(xact_command);
else if (is_eq(p, "xml"))
- return WRAP_FUNCTOR(reporter<>(new format_xml(*this), *this, "#xml"));
+ return POSTS_REPORTER(new format_xml(*this));
break;
}
break;
@@ -1543,11 +1671,9 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(format_command);
break;
case 'g':
- if (is_eq(p, "generate")) {
- return expr_t::op_t::wrap_functor
- (reporter<post_t, post_handler_ptr, &report_t::generate_report>
- (new print_xacts(*this), *this, "#generate"));
- }
+ if (is_eq(p, "generate"))
+ return POSTS_REPORTER_(&report_t::generate_report,
+ new print_xacts(*this));
break;
case 'p':
if (is_eq(p, "parse"))
@@ -1559,6 +1685,10 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
if (is_eq(p, "query"))
return WRAP_FUNCTOR(query_command);
break;
+ case 's':
+ if (is_eq(p, "script"))
+ return WRAP_FUNCTOR(source_command);
+ break;
case 't':
if (is_eq(p, "template"))
return WRAP_FUNCTOR(template_command);
diff --git a/src/report.h b/src/report.h
index 5b403205..e7d68dda 100644
--- a/src/report.h
+++ b/src/report.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -119,9 +119,23 @@ public:
explicit report_t(session_t& _session)
: session(_session), terminus(CURRENT_TIME()),
- budget_flags(BUDGET_NO_BUDGET) {}
+ budget_flags(BUDGET_NO_BUDGET) {
+ TRACE_CTOR(report_t, "session_t&");
+ }
+ report_t(const report_t& report)
+ : session(report.session),
+ output_stream(report.output_stream),
+ terminus(report.terminus),
+ budget_flags(report.budget_flags) {
+ TRACE_CTOR(report_t, "copy");
+ }
virtual ~report_t() {
+ TRACE_DTOR(report_t);
+ output_stream.close();
+ }
+
+ void quick_close() {
output_stream.close();
}
@@ -167,10 +181,11 @@ public:
value_t fn_quoted(call_scope_t& scope);
value_t fn_join(call_scope_t& scope);
value_t fn_format_date(call_scope_t& scope);
+ value_t fn_format_datetime(call_scope_t& scope);
value_t fn_ansify_if(call_scope_t& scope);
value_t fn_percent(call_scope_t& scope);
- value_t fn_price(call_scope_t& scope);
value_t fn_commodity(call_scope_t& scope);
+ value_t fn_nail_down(call_scope_t& scope);
value_t fn_lot_date(call_scope_t& scope);
value_t fn_lot_price(call_scope_t& scope);
value_t fn_lot_tag(call_scope_t& scope);
@@ -215,7 +230,7 @@ public:
bool lots = HANDLED(lots) || HANDLED(lots_actual);
return keep_details_t(lots || HANDLED(lot_prices),
lots || HANDLED(lot_dates),
- lots || HANDLED(lot_tags),
+ lots || HANDLED(lot_notes),
HANDLED(lots_actual));
}
@@ -224,11 +239,12 @@ public:
HANDLER(abbrev_len_).report(out);
HANDLER(account_).report(out);
HANDLER(actual).report(out);
- HANDLER(actual_dates).report(out);
HANDLER(add_budget).report(out);
HANDLER(amount_).report(out);
HANDLER(amount_data).report(out);
HANDLER(anon).report(out);
+ HANDLER(auto_match).report(out);
+ HANDLER(aux_date).report(out);
HANDLER(average).report(out);
HANDLER(balance_format_).report(out);
HANDLER(base).report(out);
@@ -249,13 +265,13 @@ public:
HANDLER(date_).report(out);
HANDLER(date_format_).report(out);
HANDLER(datetime_format_).report(out);
+ HANDLER(dc).report(out);
HANDLER(depth_).report(out);
HANDLER(deviation).report(out);
HANDLER(display_).report(out);
HANDLER(display_amount_).report(out);
HANDLER(display_total_).report(out);
HANDLER(dow).report(out);
- HANDLER(effective).report(out);
HANDLER(empty).report(out);
HANDLER(end_).report(out);
HANDLER(equity).report(out);
@@ -272,12 +288,13 @@ public:
HANDLER(group_by_).report(out);
HANDLER(group_title_format_).report(out);
HANDLER(head_).report(out);
+ HANDLER(immediate).report(out);
HANDLER(inject_).report(out);
HANDLER(invert).report(out);
HANDLER(limit_).report(out);
HANDLER(lot_dates).report(out);
HANDLER(lot_prices).report(out);
- HANDLER(lot_tags).report(out);
+ HANDLER(lot_notes).report(out);
HANDLER(lots).report(out);
HANDLER(lots_actual).report(out);
HANDLER(market).report(out);
@@ -302,6 +319,7 @@ public:
HANDLER(price).report(out);
HANDLER(prices_format_).report(out);
HANDLER(pricedb_format_).report(out);
+ HANDLER(primary_date).report(out);
HANDLER(quantity).report(out);
HANDLER(quarterly).report(out);
HANDLER(raw).report(out);
@@ -312,6 +330,7 @@ public:
HANDLER(revalued).report(out);
HANDLER(revalued_only).report(out);
HANDLER(revalued_total_).report(out);
+ HANDLER(rich_data).report(out);
HANDLER(seed_).report(out);
HANDLER(sort_).report(out);
HANDLER(sort_all_).report(out);
@@ -319,6 +338,7 @@ public:
HANDLER(start_of_week_).report(out);
HANDLER(subtotal).report(out);
HANDLER(tail_).report(out);
+ HANDLER(time_report).report(out);
HANDLER(total_).report(out);
HANDLER(total_data).report(out);
HANDLER(truncate_).report(out);
@@ -351,252 +371,299 @@ public:
* Option handlers
*/
- OPTION__(report_t, abbrev_len_,
- CTOR(report_t, abbrev_len_) { on_with(none, 2L); });
+ OPTION__
+ (report_t, abbrev_len_,
+ CTOR(report_t, abbrev_len_) {
+ on(none, "2");
+ });
+
OPTION(report_t, account_);
OPTION_(report_t, actual, DO() { // -L
- parent->HANDLER(limit_).on(string("--actual"), "actual");
+ OTHER(limit_).on(whence, "actual");
});
- OPTION(report_t, actual_dates);
-
OPTION_(report_t, add_budget, DO() {
parent->budget_flags |= BUDGET_BUDGETED | BUDGET_UNBUDGETED;
});
OPTION__
(report_t, amount_, // -t
- expr_t expr;
- CTOR(report_t, amount_) {
- set_expr(none, "amount");
- }
- void set_expr(const optional<string>& whence, const string& str) {
- expr = str;
- on(whence, str);
- }
- DO_(args) {
- set_expr(args.get<string>(0), args.get<string>(1));
+ DECL1(report_t, amount_, merged_expr_t, expr, ("amount_expr", "amount")) {}
+ DO_(str) {
+ expr.append(str);
});
OPTION(report_t, amount_data); // -j
OPTION(report_t, anon);
+ OPTION(report_t, auto_match);
OPTION_(report_t, average, DO() { // -A
- parent->HANDLER(display_total_)
- .set_expr(string("--average"), "count>0?(total_expr/count):0");
+ OTHER(display_total_)
+ .on(whence, "count>0?(display_total/count):0");
});
- OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) {
- on(none,
- "%(ansify_if("
- " justify(scrub(display_total), 20, 20 + prepend_width, true, color),"
- " bold if should_bold))"
- " %(!options.flat ? depth_spacer : \"\")"
- "%-(ansify_if("
- " ansify_if(partial_account(options.flat), blue if color),"
- " bold if should_bold))\n%/"
- "%$1\n%/"
- "%(prepend_width ? \" \" * prepend_width : \"\")"
- "--------------------\n");
- });
+ OPTION__
+ (report_t, balance_format_,
+ CTOR(report_t, balance_format_) {
+ on(none,
+ "%(ansify_if("
+ " justify(scrub(display_total), 20,"
+ " 20 + int(prepend_width), true, color),"
+ " bold if should_bold))"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if("
+ " ansify_if(partial_account(options.flat), blue if color),"
+ " bold if should_bold))\n%/"
+ "%$1\n%/"
+ "%(prepend_width ? \" \" * int(prepend_width) : \"\")"
+ "--------------------\n");
+ });
OPTION(report_t, base);
OPTION_(report_t, basis, DO() { // -B
- parent->HANDLER(revalued).on_only(string("--basis"));
- parent->HANDLER(amount_).set_expr(string("--basis"), "rounded(cost)");
+ OTHER(revalued).on(whence);
+ OTHER(amount_).expr.set_base_expr("rounded(cost)");
});
- OPTION_(report_t, begin_, DO_(args) { // -b
- date_interval_t interval(args.get<string>(1));
- optional<date_t> begin = interval.begin();
- if (! begin)
+ OPTION_(report_t, begin_, DO_(str) { // -b
+ date_interval_t interval(str);
+ if (optional<date_t> begin = interval.begin()) {
+ string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
+ OTHER(limit_).on(whence, predicate);
+ } else {
throw_(std::invalid_argument,
- _("Could not determine beginning of period '%1'")
- << args.get<string>(1));
-
- string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
- parent->HANDLER(limit_).on(string("--begin"), predicate);
+ _("Could not determine beginning of period '%1'") << str);
+ }
});
- OPTION__
+ OPTION_
(report_t, bold_if_,
expr_t expr;
- CTOR(report_t, bold_if_) {}
- void set_expr(const optional<string>& whence, const string& str) {
+ DO_(str) {
expr = str;
- on(whence, str);
- }
- DO_(args) {
- set_expr(args.get<string>(0), args.get<string>(1));
});
OPTION_(report_t, budget, DO() {
parent->budget_flags |= BUDGET_BUDGETED;
});
- OPTION__(report_t, budget_format_, CTOR(report_t, budget_format_) {
- on(none,
- "%(justify(scrub(get_at(total_expr, 0)), 12, -1, true, color))"
- " %(justify(-scrub(get_at(total_expr, 1)), 12, "
- " 12 + 1 + 12, true, color))"
- " %(justify(scrub(get_at(total_expr, 1) + "
- " get_at(total_expr, 0)), 12, "
- " 12 + 1 + 12 + 1 + 12, true, color))"
- " %(ansify_if("
- " justify((get_at(total_expr, 1) ? "
- " (100% * scrub(get_at(total_expr, 0))) / "
- " -scrub(get_at(total_expr, 1)) : 0), "
- " 5, -1, true, false),"
- " magenta if (color and get_at(total_expr, 1) and "
- " (abs(quantity(scrub(get_at(total_expr, 0))) / "
- " quantity(scrub(get_at(total_expr, 1)))) >= 1))))"
- " %(!options.flat ? depth_spacer : \"\")"
- "%-(ansify_if(partial_account(options.flat), blue if color))\n"
- "%/%$1 %$2 %$3 %$4\n%/"
- "%(prepend_width ? \" \" * prepend_width : \"\")"
- "------------ ------------ ------------ -----\n");
- });
+ OPTION__
+ (report_t, budget_format_,
+ CTOR(report_t, budget_format_) {
+ on(none,
+ "%(justify(scrub(get_at(display_total, 0)), 12, -1, true, color))"
+ " %(justify(-scrub(get_at(display_total, 1)), 12, "
+ " 12 + 1 + 12, true, color))"
+ " %(justify(scrub(get_at(display_total, 1) + "
+ " get_at(display_total, 0)), 12, "
+ " 12 + 1 + 12 + 1 + 12, true, color))"
+ " %(ansify_if("
+ " justify((get_at(display_total, 1) ? "
+ " (100% * scrub(get_at(display_total, 0))) / "
+ " -scrub(get_at(display_total, 1)) : 0), "
+ " 5, -1, true, false),"
+ " magenta if (color and get_at(display_total, 1) and "
+ " (abs(quantity(scrub(get_at(display_total, 0))) / "
+ " quantity(scrub(get_at(display_total, 1)))) >= 1))))"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if(partial_account(options.flat), blue if color))\n"
+ "%/%$1 %$2 %$3 %$4\n%/"
+ "%(prepend_width ? \" \" * int(prepend_width) : \"\")"
+ "------------ ------------ ------------ -----\n");
+ });
OPTION(report_t, by_payee); // -P
OPTION_(report_t, cleared, DO() { // -C
- parent->HANDLER(limit_).on(string("--cleared"), "cleared");
+ OTHER(limit_).on(whence, "cleared");
});
- OPTION__(report_t, cleared_format_, CTOR(report_t, cleared_format_) {
- on(none,
- "%(justify(scrub(get_at(total_expr, 0)), 16, 16 + prepend_width, "
- " true, color)) %(justify(scrub(get_at(total_expr, 1)), 18, "
- " 36 + prepend_width, true, color))"
- " %(latest_cleared ? format_date(latest_cleared) : \" \")"
- " %(!options.flat ? depth_spacer : \"\")"
- "%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
- "%$1 %$2 %$3\n%/"
- "%(prepend_width ? \" \" * prepend_width : \"\")"
- "---------------- ---------------- ---------\n");
- });
+ OPTION__
+ (report_t, cleared_format_,
+ CTOR(report_t, cleared_format_) {
+ on(none,
+ "%(justify(scrub(get_at(display_total, 0)), 16, 16 + int(prepend_width), "
+ " true, color)) %(justify(scrub(get_at(display_total, 1)), 18, "
+ " 36 + int(prepend_width), true, color))"
+ " %(latest_cleared ? format_date(latest_cleared) : \" \")"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
+ "%$1 %$2 %$3\n%/"
+ "%(prepend_width ? \" \" * int(prepend_width) : \"\")"
+ "---------------- ---------------- ---------\n");
+ });
OPTION(report_t, color);
OPTION_(report_t, collapse, DO() { // -n
// Make sure that balance reports are collapsed too, but only apply it
// to account xacts
- parent->HANDLER(display_).on(string("--collapse"), "post|depth<=1");
+ OTHER(display_).on(whence, "post|depth<=1");
});
OPTION_(report_t, collapse_if_zero, DO() {
- parent->HANDLER(collapse).on_only(string("--collapse-if-zero"));
+ OTHER(collapse).on(whence);
});
OPTION(report_t, columns_);
OPTION(report_t, count);
- OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) {
- on(none,
- "%(quoted(date)),"
- "%(quoted(code)),"
- "%(quoted(payee)),"
- "%(quoted(display_account)),"
- "%(quoted(commodity)),"
- "%(quoted(quantity(scrub(display_amount)))),"
- "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))),"
- "%(quoted(join(note | xact.note)))\n");
- });
+ OPTION__
+ (report_t, csv_format_,
+ CTOR(report_t, csv_format_) {
+ on(none,
+ "%(quoted(date)),"
+ "%(quoted(code)),"
+ "%(quoted(payee)),"
+ "%(quoted(display_account)),"
+ "%(quoted(commodity)),"
+ "%(quoted(quantity(scrub(display_amount)))),"
+ "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))),"
+ "%(quoted(join(note | xact.note)))\n");
+ });
OPTION_(report_t, current, DO() { // -c
- parent->HANDLER(limit_).on(string("--current"), "date<=today");
+ OTHER(limit_).on(whence, "date<=today");
});
OPTION_(report_t, daily, DO() { // -D
- parent->HANDLER(period_).on(string("--daily"), "daily");
+ OTHER(period_).on(whence, "daily");
});
OPTION(report_t, date_);
OPTION(report_t, date_format_);
OPTION(report_t, datetime_format_);
- OPTION_(report_t, depth_, DO_(args) {
- parent->HANDLER(display_)
- .on(string("--depth"), string("depth<=") + args.get<string>(1));
+ OPTION_(report_t, dc, DO() {
+ OTHER(amount_).expr.set_base_expr
+ ("(amount > 0 ? amount : 0, amount < 0 ? amount : 0)");
+
+ OTHER(register_format_)
+ .on(none,
+ "%(ansify_if("
+ " ansify_if(justify(format_date(date), int(date_width)),"
+ " green if color and date > today),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), "
+ " bold if color and !cleared and actual),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " ansify_if(justify(truncated(display_account, int(account_width), "
+ " int(abbrev_len)), int(account_width)),"
+ " blue if color),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " justify(scrub(abs(get_at(display_amount, 0))), int(amount_width), "
+ " 3 + int(meta_width) + int(date_width) + int(payee_width)"
+ " + int(account_width) + int(amount_width) + int(prepend_width),"
+ " true, color),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " justify(scrub(abs(get_at(display_amount, 1))), int(amount_width), "
+ " 4 + int(meta_width) + int(date_width) + int(payee_width)"
+ " + int(account_width) + int(amount_width) + int(amount_width) + int(prepend_width),"
+ " true, color),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " justify(scrub(get_at(display_total, 0) + get_at(display_total, 1)), int(total_width), "
+ " 5 + int(meta_width) + int(date_width) + int(payee_width)"
+ " + int(account_width) + int(amount_width) + int(amount_width) + int(total_width)"
+ " + int(prepend_width), true, color),"
+ " bold if should_bold))\n%/"
+ "%(justify(\" \", int(date_width)))"
+ " %(ansify_if("
+ " justify(truncated(has_tag(\"Payee\") ? payee : \" \", "
+ " int(payee_width)), int(payee_width)),"
+ " bold if should_bold))"
+ " %$3 %$4 %$5 %$6\n");
+
+ OTHER(balance_format_)
+ .on(none,
+ "%(ansify_if("
+ " justify(scrub(abs(get_at(display_total, 0))), 14,"
+ " 14 + int(prepend_width), true, color),"
+ " bold if should_bold)) "
+ "%(ansify_if("
+ " justify(scrub(abs(get_at(display_total, 1))), 14,"
+ " 14 + 1 + int(prepend_width) + int(total_width), true, color),"
+ " bold if should_bold)) "
+ "%(ansify_if("
+ " justify(scrub(get_at(display_total, 0) + get_at(display_total, 1)), 14,"
+ " 14 + 2 + int(prepend_width) + int(total_width) + int(total_width), true, color),"
+ " bold if should_bold))"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if("
+ " ansify_if(partial_account(options.flat), blue if color),"
+ " bold if should_bold))\n%/"
+ "%$1 %$2 %$3\n%/"
+ "%(prepend_width ? \" \" * int(prepend_width) : \"\")"
+ "--------------------------------------------\n");
+ });
+
+ OPTION_(report_t, depth_, DO_(str) {
+ OTHER(display_).on(whence, string("depth<=") + str);
});
OPTION_(report_t, deviation, DO() {
- parent->HANDLER(display_total_)
- .set_expr(string("--deviation"), "amount_expr-total_expr/count");
+ OTHER(display_total_)
+ .on(whence, "display_amount-display_total");
});
- OPTION__
- (report_t, display_, // -d
- CTOR(report_t, display_) {}
- virtual void on_with(const optional<string>& whence, const value_t& text) {
- if (! handled)
- option_t<report_t>::on_with(whence, text);
- else
- option_t<report_t>::on_with(whence,
- string_value(string("(") + str() + ")&(" +
- text.as_string() + ")"));
+ OPTION_
+ (report_t, display_,
+ DO_(str) { // -d
+ if (handled)
+ value = string("(") + value + ")&(" + str + ")";
});
OPTION__
(report_t, display_amount_,
- expr_t expr;
- CTOR(report_t, display_amount_) {
- set_expr(none, "amount_expr");
- }
- void set_expr(const optional<string>& whence, const string& str) {
- expr = str;
- on(whence, str);
- }
- DO_(args) {
- set_expr(args.get<string>(0), args.get<string>(1));
+ DECL1(report_t, display_amount_, merged_expr_t, expr,
+ ("display_amount", "amount_expr")) {}
+ DO_(str) {
+ expr.append(str);
});
OPTION__
(report_t, display_total_,
- expr_t expr;
- CTOR(report_t, display_total_) {
- set_expr(none, "total_expr");
- }
- void set_expr(const optional<string>& whence, const string& str) {
- expr = str;
- on(whence, str);
- }
- DO_(args) {
- set_expr(args.get<string>(0), args.get<string>(1));
+ DECL1(report_t, display_total_, merged_expr_t, expr,
+ ("display_total", "total_expr")) {}
+ DO_(str) {
+ expr.append(str);
});
OPTION(report_t, dow);
- OPTION(report_t, effective);
+ OPTION(report_t, aux_date);
OPTION(report_t, empty); // -E
- OPTION_(report_t, end_, DO_(args) { // -e
- date_interval_t interval(args.get<string>(1));
+ OPTION_(report_t, end_, DO_(str) { // -e
// Use begin() here so that if the user says --end=2008, we end on
// 2008/01/01 instead of 2009/01/01 (which is what end() would
// return).
- optional<date_t> end = interval.begin();
- if (! end)
+ date_interval_t interval(str);
+ if (optional<date_t> end = interval.begin()) {
+ string predicate = "date<[" + to_iso_extended_string(*end) + "]";
+ OTHER(limit_).on(whence, predicate);
+
+ parent->terminus = datetime_t(*end);
+ } else {
throw_(std::invalid_argument,
_("Could not determine end of period '%1'")
- << args.get<string>(1));
-
- string predicate = "date<[" + to_iso_extended_string(*end) + "]";
- parent->HANDLER(limit_).on(string("--end"), predicate);
-
- parent->terminus = datetime_t(*end);
+ << str);
+ }
});
OPTION(report_t, equity);
OPTION(report_t, exact);
- OPTION_(report_t, exchange_, DO_(args) { // -X
- on_with(args.get<string>(0), args[1]);
- call_scope_t no_args(*parent);
- no_args.push_back(args[0]);
- parent->HANDLER(market).parent = parent;
- parent->HANDLER(market).handler(no_args);
+ OPTION_(report_t, exchange_, DO_() { // -X
+ // Using -X implies -V. The main difference is that now
+ // HANDLER(exchange_) contains the name of a commodity, which
+ // is accessed via the "exchange" value expression function.
+ OTHER(market).on(whence);
});
OPTION(report_t, flat);
@@ -607,115 +674,112 @@ public:
OPTION(report_t, format_); // -F
OPTION_(report_t, gain, DO() { // -G
- parent->HANDLER(revalued).on_only(string("--gain"));
- parent->HANDLER(amount_).set_expr(string("--gain"), "(amount, cost)");
+ OTHER(revalued).on(whence);
+ OTHER(amount_).expr.set_base_expr("(amount, cost)");
+
// Since we are displaying the amounts of revalued postings, they
// will end up being composite totals, and hence a pair of pairs.
- parent->HANDLER(display_amount_)
- .set_expr(string("--gain"),
- "use_direct_amount ? amount :"
- " (is_seq(get_at(amount_expr, 0)) ?"
- " get_at(get_at(amount_expr, 0), 0) :"
- " market(get_at(amount_expr, 0), value_date, exchange)"
- " - get_at(amount_expr, 1))");
- parent->HANDLER(revalued_total_)
- .set_expr(string("--gain"),
- "(market(get_at(total_expr, 0), value_date, exchange), "
- "get_at(total_expr, 1))");
- parent->HANDLER(display_total_)
- .set_expr(string("--gain"),
- "use_direct_amount ? total_expr :"
- " market(get_at(total_expr, 0), value_date, exchange)"
- " - get_at(total_expr, 1)");
+ OTHER(display_amount_)
+ .on(whence,
+ "use_direct_amount ? amount :"
+ " (is_seq(get_at(amount_expr, 0)) ?"
+ " get_at(get_at(amount_expr, 0), 0) :"
+ " market(get_at(amount_expr, 0), value_date, exchange)"
+ " - get_at(amount_expr, 1))");
+ OTHER(revalued_total_)
+ .on(whence,
+ "(market(get_at(total_expr, 0), value_date, exchange), "
+ "get_at(total_expr, 1))");
+ OTHER(display_total_)
+ .on(whence,
+ "use_direct_amount ? total_expr :"
+ " market(get_at(total_expr, 0), value_date, exchange)"
+ " - get_at(total_expr, 1)");
});
OPTION(report_t, generated);
- OPTION__
+ OPTION_
(report_t, group_by_,
expr_t expr;
- CTOR(report_t, group_by_) {}
- void set_expr(const optional<string>& whence, const string& str) {
+ DO_(str) {
expr = str;
- on(whence, str);
- }
- DO_(args) {
- set_expr(args.get<string>(0), args.get<string>(1));
});
- OPTION__(report_t, group_title_format_, CTOR(report_t, group_title_format_) {
- on(none, "%(value)\n");
- });
+ OPTION__
+ (report_t, group_title_format_,
+ CTOR(report_t, group_title_format_) {
+ on(none, "%(value)\n");
+ });
OPTION(report_t, head_);
+
+ OPTION_(report_t, historical, DO() { // -H
+ OTHER(market).on(whence);
+ OTHER(amount_)
+ .on(whence, "nail_down(amount_expr, "
+ "market(amount_expr, value_date, exchange))");
+ });
+
+ OPTION(report_t, immediate);
OPTION(report_t, inject_);
OPTION_(report_t, invert, DO() {
- parent->HANDLER(amount_).set_expr(string("--invert"), "-amount");
+ OTHER(amount_).on(whence, "-amount_expr");
});
- OPTION__
- (report_t, limit_, // -l
- CTOR(report_t, limit_) {}
- virtual void on_with(const optional<string>& whence, const value_t& text) {
- if (! handled)
- option_t<report_t>::on_with(whence, text);
- else
- option_t<report_t>::on_with(whence,
- string_value(string("(") + str() + ")&(" +
- text.as_string() + ")"));
+ OPTION_
+ (report_t, limit_,
+ DO_(str) { // -l
+ if (handled)
+ value = string("(") + value + ")&(" + str + ")";
});
OPTION(report_t, lot_dates);
OPTION(report_t, lot_prices);
- OPTION(report_t, lot_tags);
+ OPTION(report_t, lot_notes);
OPTION(report_t, lots);
OPTION(report_t, lots_actual);
OPTION_(report_t, market, DO() { // -V
- parent->HANDLER(revalued).on_only(string("--market"));
- parent->HANDLER(display_amount_)
- .set_expr(string("--market"),
- "market(amount_expr, value_date, exchange)");
- parent->HANDLER(display_total_)
- .set_expr(string("--market"),
- "market(total_expr, value_date, exchange)");
+ OTHER(revalued).on(whence);
+
+ OTHER(display_amount_)
+ .on(whence, "market(display_amount, value_date, exchange)");
+ OTHER(display_total_)
+ .on(whence, "market(display_total, value_date, exchange)");
});
OPTION(report_t, meta_);
OPTION_(report_t, monthly, DO() { // -M
- parent->HANDLER(period_).on(string("--monthly"), "monthly");
+ OTHER(period_).on(whence, "monthly");
});
OPTION_(report_t, no_color, DO() {
- parent->HANDLER(color).off();
+ OTHER(color).off();
});
OPTION(report_t, no_rounding);
OPTION(report_t, no_titles);
OPTION(report_t, no_total);
- OPTION_(report_t, now_, DO_(args) {
- date_interval_t interval(args.get<string>(1));
- optional<date_t> begin = interval.begin();
- if (! begin)
+ OPTION_(report_t, now_, DO_(str) {
+ date_interval_t interval(str);
+ if (optional<date_t> begin = interval.begin()) {
+ ledger::epoch = parent->terminus = datetime_t(*begin);
+ } else {
throw_(std::invalid_argument,
_("Could not determine beginning of period '%1'")
- << args.get<string>(1));
- ledger::epoch = parent->terminus = datetime_t(*begin);
+ << str);
+ }
});
- OPTION__
+ OPTION_
(report_t, only_,
- CTOR(report_t, only_) {}
- virtual void on_with(const optional<string>& whence, const value_t& text) {
- if (! handled)
- option_t<report_t>::on_with(whence, text);
- else
- option_t<report_t>::on_with(whence,
- string_value(string("(") + str() + ")&(" +
- text.as_string() + ")"));
+ DO_(str) {
+ if (handled)
+ value = string("(") + value + ")&(" + str + ")";
});
OPTION(report_t, output_); // -o
@@ -736,201 +800,197 @@ public:
setenv("LESS", "-FRSX", 0); // don't overwrite
}
}
- }
- virtual void on_with(const optional<string>& whence, const value_t& text) {
- string cmd(text.to_string());
- if (cmd == "" || cmd == "false" || cmd == "off" ||
- cmd == "none" || cmd == "no" || cmd == "disable")
- option_t<report_t>::off();
- else
- option_t<report_t>::on_with(whence, text);
});
#else // HAVE_ISATTY
- OPTION__
- (report_t, pager_,
- CTOR(report_t, pager_) {
- }
- virtual void on_with(const optional<string>& whence, const value_t& text) {
- string cmd(text.to_string());
- if (cmd == "" || cmd == "false" || cmd == "off" ||
- cmd == "none" || cmd == "no" || cmd == "disable")
- option_t<report_t>::off();
- else
- option_t<report_t>::on_with(whence, text);
- });
+ OPTION(report_t, pager_);
#endif // HAVE_ISATTY
+ OPTION_(report_t, no_pager, DO() {
+ OTHER(pager_).off();
+ });
+
OPTION(report_t, payee_);
OPTION_(report_t, pending, DO() { // -C
- parent->HANDLER(limit_).on(string("--pending"), "pending");
+ OTHER(limit_).on(whence, "pending");
});
OPTION_(report_t, percent, DO() { // -%
- parent->HANDLER(total_)
- .set_expr(string("--percent"),
- "((is_account&parent&parent.total)?"
- " percent(scrub(total), scrub(parent.total)):0)");
+ OTHER(total_)
+ .on(whence,
+ "((is_account&parent&parent.total)?"
+ " percent(scrub(total), scrub(parent.total)):0)");
});
- OPTION__
- (report_t, period_, // -p
- CTOR(report_t, period_) {}
- virtual void on_with(const optional<string>& whence, const value_t& text) {
- if (! handled)
- option_t<report_t>::on_with(whence, text);
- else
- option_t<report_t>::on_with(whence,
- string_value(text.as_string() + " " + str()));
+ OPTION_
+ (report_t, period_,
+ DO_(str) { // -p
+ if (handled)
+ value += string(" ") + str;
});
OPTION(report_t, pivot_);
- OPTION__(report_t, plot_amount_format_, CTOR(report_t, plot_amount_format_) {
- on(none,
- "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n");
- });
+ OPTION__
+ (report_t, plot_amount_format_,
+ CTOR(report_t, plot_amount_format_) {
+ on(none,
+ "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n");
+ });
- OPTION__(report_t, plot_total_format_, CTOR(report_t, plot_total_format_) {
- on(none,
- "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n");
- });
+ OPTION__
+ (report_t, plot_total_format_,
+ CTOR(report_t, plot_total_format_) {
+ on(none,
+ "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n");
+ });
OPTION(report_t, prepend_format_);
- OPTION_(report_t, prepend_width_, DO_(args) {
- value = args.get<long>(1);
- });
+ OPTION(report_t, prepend_width_);
OPTION_(report_t, price, DO() { // -I
- parent->HANDLER(display_amount_)
- .set_expr(string("--price"), "price(amount_expr)");
- parent->HANDLER(display_total_)
- .set_expr(string("--price"), "price(total_expr)");
+ OTHER(amount_).expr.set_base_expr("price");
});
- OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
- on(none,
- "%(date) %-8(display_account) %(justify(scrub(display_amount), 12, "
- " 2 + 9 + 8 + 12, true, color))\n");
- });
+ OPTION__
+ (report_t, prices_format_,
+ CTOR(report_t, prices_format_) {
+ on(none,
+ "%(date) %-8(display_account) %(justify(scrub(display_amount), 12, "
+ " 2 + 9 + 8 + 12, true, color))\n");
+ });
- OPTION__(report_t, pricedb_format_, CTOR(report_t, pricedb_format_) {
- on(none,
- "P %(datetime) %(display_account) %(scrub(display_amount))\n");
- });
+ OPTION__
+ (report_t, pricedb_format_,
+ CTOR(report_t, pricedb_format_) {
+ on(none,
+ "P %(datetime) %(display_account) %(scrub(display_amount))\n");
+ });
+
+ OPTION(report_t, primary_date);
OPTION_(report_t, quantity, DO() { // -O
- parent->HANDLER(revalued).off();
- parent->HANDLER(amount_).set_expr(string("--quantity"), "amount");
- parent->HANDLER(total_).set_expr(string("--quantity"), "total");
+ OTHER(revalued).off();
+
+ OTHER(amount_).expr.set_base_expr("amount");
+ OTHER(total_).expr.set_base_expr("total");
});
OPTION_(report_t, quarterly, DO() {
- parent->HANDLER(period_).on(string("--quarterly"), "quarterly");
+ OTHER(period_).on(whence, "quarterly");
});
OPTION(report_t, raw);
OPTION_(report_t, real, DO() { // -R
- parent->HANDLER(limit_).on(string("--real"), "real");
+ OTHER(limit_).on(whence, "real");
});
- OPTION__(report_t, register_format_, CTOR(report_t, register_format_) {
- on(none,
- "%(ansify_if("
- " ansify_if(justify(format_date(date), date_width),"
- " green if color and date > today),"
- " bold if should_bold))"
- " %(ansify_if("
- " ansify_if(justify(truncated(payee, payee_width), payee_width), "
- " bold if color and !cleared and actual),"
- " bold if should_bold))"
- " %(ansify_if("
- " ansify_if(justify(truncated(display_account, account_width, "
- " abbrev_len), account_width),"
- " blue if color),"
- " bold if should_bold))"
- " %(ansify_if("
- " justify(scrub(display_amount), amount_width, "
- " 3 + meta_width + date_width + payee_width"
- " + account_width + amount_width + prepend_width,"
- " true, color),"
- " bold if should_bold))"
- " %(ansify_if("
- " justify(scrub(display_total), total_width, "
- " 4 + meta_width + date_width + payee_width"
- " + account_width + amount_width + total_width"
- " + prepend_width, true, color),"
- " bold if should_bold))\n%/"
- "%(justify(\" \", date_width))"
- " %(ansify_if("
- " justify(truncated(has_tag(\"Payee\") ? payee : \" \", "
- " payee_width), payee_width),"
- " bold if should_bold))"
- " %$3 %$4 %$5\n");
- });
+ OPTION__
+ (report_t, register_format_,
+ CTOR(report_t, register_format_) {
+ on(none,
+ "%(ansify_if("
+ " ansify_if(justify(format_date(date), int(date_width)),"
+ " green if color and date > today),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), "
+ " bold if color and !cleared and actual),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " ansify_if(justify(truncated(display_account, int(account_width), "
+ " int(abbrev_len)), int(account_width)),"
+ " blue if color),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " justify(scrub(display_amount), int(amount_width), "
+ " 3 + int(meta_width) + int(date_width) + int(payee_width)"
+ " + int(account_width) + int(amount_width) + int(prepend_width),"
+ " true, color),"
+ " bold if should_bold))"
+ " %(ansify_if("
+ " justify(scrub(display_total), int(total_width), "
+ " 4 + int(meta_width) + int(date_width) + int(payee_width)"
+ " + int(account_width) + int(amount_width) + int(total_width)"
+ " + int(prepend_width), true, color),"
+ " bold if should_bold))\n%/"
+ "%(justify(\" \", int(date_width)))"
+ " %(ansify_if("
+ " justify(truncated(has_tag(\"Payee\") ? payee : \" \", "
+ " int(payee_width)), int(payee_width)),"
+ " bold if should_bold))"
+ " %$3 %$4 %$5\n");
+ });
OPTION(report_t, related); // -r
OPTION_(report_t, related_all, DO() {
- parent->HANDLER(related).on_only(string("--related-all"));
+ OTHER(related).on(whence);
});
OPTION(report_t, revalued);
OPTION(report_t, revalued_only);
- OPTION__
+ OPTION_
(report_t, revalued_total_,
expr_t expr;
- CTOR(report_t, revalued_total_) {}
- void set_expr(const optional<string>& whence, const string& str) {
+ DO_(str) {
expr = str;
- on(whence, str);
- }
- DO_(args) {
- set_expr(args.get<string>(0), args.get<string>(1));
});
+ OPTION(report_t, rich_data);
+
OPTION(report_t, seed_);
- OPTION_(report_t, sort_, DO_(args) { // -S
- on_with(args.get<string>(0), args[1]);
- parent->HANDLER(sort_xacts_).off();
- parent->HANDLER(sort_all_).off();
+ OPTION_(report_t, sort_, DO_(str) { // -S
+ OTHER(sort_xacts_).off();
+ OTHER(sort_all_).off();
});
- OPTION_(report_t, sort_all_, DO_(args) {
- parent->HANDLER(sort_).on_with(string("--sort-all"), args[1]);
- parent->HANDLER(sort_xacts_).off();
+ OPTION_(report_t, sort_all_, DO_(str) {
+ OTHER(sort_).on(whence, str);
+ OTHER(sort_xacts_).off();
});
- OPTION_(report_t, sort_xacts_, DO_(args) {
- parent->HANDLER(sort_).on_with(string("--sort-xacts"), args[1]);
- parent->HANDLER(sort_all_).off();
+ OPTION_(report_t, sort_xacts_, DO_(str) {
+ OTHER(sort_).on(whence, str);
+ OTHER(sort_all_).off();
});
OPTION(report_t, start_of_week_);
OPTION(report_t, subtotal); // -s
OPTION(report_t, tail_);
+ OPTION_(report_t, time_report, DO() {
+ OTHER(balance_format_)
+ .on(none,
+ "%(justify(earliest_checkin ? "
+ " format_datetime(earliest_checkin) : \"\", 19, -1, true)) "
+ "%(justify(latest_checkout ? "
+ " format_datetime(latest_checkout) : \"\", 19, -1, true)) "
+ "%(ansify_if("
+ " justify(scrub(display_total), 8,"
+ " 8 + 4 + 19 * 2, true, color), bold if should_bold))"
+ " %(!options.flat ? depth_spacer : \"\")"
+ "%-(ansify_if("
+ " ansify_if(partial_account(options.flat), blue if color),"
+ " bold if should_bold))\n%/"
+ "%$1 %$2 %$3\n%/"
+ "%(prepend_width ? \" \" * int(prepend_width) : \"\")"
+ "--------------------------------------------------\n");
+ });
+
OPTION__
(report_t, total_, // -T
- expr_t expr;
- CTOR(report_t, total_) {
- set_expr(none, "total");
- }
- void set_expr(const optional<string>& whence, const string& str) {
- expr = str;
- on(whence, str);
- }
- DO_(args) {
- set_expr(args.get<string>(0), args.get<string>(1));
+ DECL1(report_t, total_, merged_expr_t, expr, ("total_expr", "total")) {}
+ DO_(str) {
+ expr.append(str);
});
OPTION(report_t, total_data); // -J
- OPTION_(report_t, truncate_, DO_(args) {
- string style(args.get<string>(1));
+ OPTION_(report_t, truncate_, DO_(style) {
if (style == "leading")
format_t::default_style = format_t::TRUNCATE_LEADING;
else if (style == "middle")
@@ -948,7 +1008,7 @@ public:
});
OPTION_(report_t, uncleared, DO() { // -U
- parent->HANDLER(limit_).on(string("--uncleared"), "uncleared|pending");
+ OTHER(limit_).on(whence, "uncleared|pending");
});
OPTION(report_t, unrealized);
@@ -957,51 +1017,30 @@ public:
OPTION(report_t, unrealized_losses_);
OPTION_(report_t, unround, DO() {
- parent->HANDLER(display_amount_)
- .set_expr(string("--unround"), "unrounded(amount_expr)");
- parent->HANDLER(display_total_)
- .set_expr(string("--unround"), "unrounded(total_expr)");
+ OTHER(amount_).on(whence, "unrounded(amount_expr)");
+ OTHER(total_).on(whence, "unrounded(total_expr)");
});
OPTION_(report_t, weekly, DO() { // -W
- parent->HANDLER(period_).on(string("--weekly"), "weekly");
+ OTHER(period_).on(whence, "weekly");
});
OPTION_(report_t, wide, DO() { // -w
- parent->HANDLER(columns_).on_with(string("--wide"), 132L);
+ OTHER(columns_).on(whence, "132");
});
OPTION_(report_t, yearly, DO() { // -Y
- parent->HANDLER(period_).on(string("--yearly"), "yearly");
+ OTHER(period_).on(whence, "yearly");
});
- OPTION__(report_t, meta_width_,
- bool specified;
- CTOR(report_t, meta_width_) { specified = false; }
- DO_(args) { value = args.get<long>(1); specified = true; });
- OPTION__(report_t, date_width_,
- bool specified;
- CTOR(report_t, date_width_) { specified = false; }
- DO_(args) { value = args.get<long>(1); specified = true; });
- OPTION__(report_t, payee_width_,
- bool specified;
- CTOR(report_t, payee_width_) { specified = false; }
- DO_(args) { value = args.get<long>(1); specified = true; });
- OPTION__(report_t, account_width_,
- bool specified;
- CTOR(report_t, account_width_) { specified = false; }
- DO_(args) { value = args.get<long>(1); specified = true; });
- OPTION__(report_t, amount_width_,
- bool specified;
- CTOR(report_t, amount_width_) { specified = false; }
- DO_(args) { value = args.get<long>(1); specified = true; });
- OPTION__(report_t, total_width_,
- bool specified;
- CTOR(report_t, total_width_) { specified = false; }
- DO_(args) { value = args.get<long>(1); specified = true; });
+ OPTION(report_t, meta_width_);
+ OPTION(report_t, date_width_);
+ OPTION(report_t, payee_width_);
+ OPTION(report_t, account_width_);
+ OPTION(report_t, amount_width_);
+ OPTION(report_t, total_width_);
};
-
template <class Type = post_t,
class handler_ptr = post_handler_ptr,
void (report_t::*report_method)(handler_ptr) =
@@ -1014,9 +1053,18 @@ class reporter
string whence;
public:
- reporter(item_handler<Type> * _handler,
+ reporter(shared_ptr<item_handler<Type> > _handler,
report_t& _report, const string& _whence)
- : handler(_handler), report(_report), whence(_whence) {}
+ : handler(_handler), report(_report), whence(_whence) {
+ TRACE_CTOR(reporter, "item_handler<Type>, report_t&, string");
+ }
+ reporter(const reporter& other)
+ : handler(other.handler), report(other.report), whence(other.whence) {
+ TRACE_CTOR(reporter, "copy");
+ }
+ ~reporter() throw() {
+ TRACE_DTOR(reporter);
+ }
value_t operator()(call_scope_t& args)
{
diff --git a/src/scope.cc b/src/scope.cc
index e18b5a0a..00327159 100644
--- a/src/scope.cc
+++ b/src/scope.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -35,12 +35,14 @@
namespace ledger {
-scope_t * scope_t::default_scope = NULL;
+scope_t * scope_t::default_scope = NULL;
+empty_scope_t * scope_t::empty_scope = NULL;
void symbol_scope_t::define(const symbol_t::kind_t kind,
const string& name, expr_t::ptr_op_t def)
{
- DEBUG("scope.symbols", "Defining '" << name << "' = " << def);
+ DEBUG("scope.symbols",
+ "Defining '" << name << "' = " << def << " in " << this);
if (! symbols)
symbols = symbol_map();
@@ -52,8 +54,8 @@ void symbol_scope_t::define(const symbol_t::kind_t kind,
assert(i != symbols->end());
symbols->erase(i);
- result = symbols->insert(symbol_map::value_type(symbol_t(kind, name, def),
- def));
+ result = symbols->insert(symbol_map::value_type
+ (symbol_t(kind, name, def), def));
if (! result.second)
throw_(compile_error,
_("Redefinition of '%1' in the same scope") << name);
@@ -64,9 +66,12 @@ expr_t::ptr_op_t symbol_scope_t::lookup(const symbol_t::kind_t kind,
const string& name)
{
if (symbols) {
+ DEBUG("scope.symbols", "Looking for '" << name << "' in " << this);
symbol_map::const_iterator i = symbols->find(symbol_t(kind, name));
- if (i != symbols->end())
+ if (i != symbols->end()) {
+ DEBUG("scope.symbols", "Found '" << name << "' in " << this);
return (*i).second;
+ }
}
return child_scope_t::lookup(kind, name);
}
@@ -84,8 +89,7 @@ value_t& call_scope_t::resolve(const std::size_t index,
value = as_expr(value)->calc(scope, locus, depth);
if (required && ! value.is_type(context))
throw_(calc_error, _("Expected %1 for argument %2, but received %3")
- << value.label(context) << index
- << value.label());
+ << value.label(context) << index << value.label());
}
return value;
}
diff --git a/src/scope.h b/src/scope.h
index a7b3c5cb..c43d73d6 100644
--- a/src/scope.h
+++ b/src/scope.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -70,8 +70,7 @@ struct symbol_t
TRACE_CTOR(symbol_t, "symbol_t::kind_t, string");
}
symbol_t(const symbol_t& sym)
- : kind(sym.kind), name(sym.name),
- definition(sym.definition) {
+ : kind(sym.kind), name(sym.name), definition(sym.definition) {
TRACE_CTOR(symbol_t, "copy");
}
~symbol_t() throw() {
@@ -81,6 +80,9 @@ struct symbol_t
bool operator<(const symbol_t& sym) const {
return kind < sym.kind || name < sym.name;
}
+ bool operator==(const symbol_t& sym) const {
+ return kind == sym.kind || name == sym.name;
+ }
#if defined(HAVE_BOOST_SERIALIZATION)
private:
@@ -97,10 +99,13 @@ private:
#endif // HAVE_BOOST_SERIALIZATION
};
+class empty_scope_t;
+
class scope_t
{
public:
- static scope_t * default_scope;
+ static scope_t * default_scope;
+ static empty_scope_t * empty_scope;
explicit scope_t() {
TRACE_CTOR(scope_t, "");
@@ -134,6 +139,24 @@ private:
#endif // HAVE_BOOST_SERIALIZATION
};
+class empty_scope_t : public scope_t
+{
+public:
+ empty_scope_t() {
+ TRACE_CTOR(empty_scope_t, "");
+ }
+ ~empty_scope_t() throw() {
+ TRACE_DTOR(empty_scope_t);
+ }
+
+ virtual string description() {
+ return _("<empty>");
+ }
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t, const string&) {
+ return NULL;
+ }
+};
+
class child_scope_t : public noncopyable, public scope_t
{
public:
@@ -142,8 +165,7 @@ public:
explicit child_scope_t() : parent(NULL) {
TRACE_CTOR(child_scope_t, "");
}
- explicit child_scope_t(scope_t& _parent)
- : parent(&_parent) {
+ explicit child_scope_t(scope_t& _parent) : parent(&_parent) {
TRACE_CTOR(child_scope_t, "scope_t&");
}
virtual ~child_scope_t() {
@@ -187,6 +209,8 @@ public:
explicit bind_scope_t(scope_t& _parent,
scope_t& _grandchild)
: child_scope_t(_parent), grandchild(_grandchild) {
+ DEBUG("scope.symbols",
+ "Binding scope " << &_parent << " with " << &_grandchild);
TRACE_CTOR(bind_scope_t, "scope_t&, scope_t&");
}
virtual ~bind_scope_t() {
@@ -225,15 +249,19 @@ private:
};
template <typename T>
-T * search_scope(scope_t * ptr)
+T * search_scope(scope_t * ptr, bool prefer_direct_parents = false)
{
+ DEBUG("scope.search", "Searching scope " << ptr->description());
+
if (T * sought = dynamic_cast<T *>(ptr))
return sought;
if (bind_scope_t * scope = dynamic_cast<bind_scope_t *>(ptr)) {
- if (T * sought = search_scope<T>(&scope->grandchild))
+ if (T * sought = search_scope<T>(prefer_direct_parents ?
+ scope->parent : &scope->grandchild))
return sought;
- return search_scope<T>(scope->parent);
+ return search_scope<T>(prefer_direct_parents ?
+ &scope->grandchild : scope->parent);
}
else if (child_scope_t * child_scope = dynamic_cast<child_scope_t *>(ptr)) {
return search_scope<T>(child_scope->parent);
@@ -242,9 +270,21 @@ T * search_scope(scope_t * ptr)
}
template <typename T>
-inline T& find_scope(child_scope_t& scope, bool skip_this = true)
+inline T& find_scope(child_scope_t& scope, bool skip_this = true,
+ bool prefer_direct_parents = false)
+{
+ if (T * sought = search_scope<T>(skip_this ? scope.parent : &scope,
+ prefer_direct_parents))
+ return *sought;
+
+ throw_(std::runtime_error, _("Could not find scope"));
+ return reinterpret_cast<T&>(scope); // never executed
+}
+
+template <typename T>
+inline T& find_scope(scope_t& scope, bool prefer_direct_parents = false)
{
- if (T * sought = search_scope<T>(skip_this ? scope.parent : &scope))
+ if (T * sought = search_scope<T>(&scope, prefer_direct_parents))
return *sought;
throw_(std::runtime_error, _("Could not find scope"));
@@ -258,7 +298,7 @@ class symbol_scope_t : public child_scope_t
optional<symbol_map> symbols;
public:
- explicit symbol_scope_t() {
+ explicit symbol_scope_t() : child_scope_t() {
TRACE_CTOR(symbol_scope_t, "");
}
explicit symbol_scope_t(scope_t& _parent) : child_scope_t(_parent) {
@@ -347,13 +387,8 @@ protected:
class call_scope_t : public context_scope_t
{
-#if defined(DEBUG_ON)
public:
-#endif
value_t args;
-#if defined(DEBUG_ON)
-private:
-#endif
mutable void * ptr;
value_t& resolve(const std::size_t index,
@@ -370,17 +405,12 @@ public:
: context_scope_t(_parent, _parent.type_context(),
_parent.type_required()),
ptr(NULL), locus(_locus), depth(_depth) {
- TRACE_CTOR(call_scope_t,
- "scope_t&, value_t::type_t, bool, expr_t::ptr_op_t *, int");
+ TRACE_CTOR(call_scope_t, "scope_t&, expr_t::ptr_op_t *, const int");
}
virtual ~call_scope_t() {
TRACE_DTOR(call_scope_t);
}
- virtual string description() {
- return context_scope_t::description();
- }
-
void set_args(const value_t& _args) {
args = _args;
}
@@ -453,7 +483,7 @@ public:
#if defined(HAVE_BOOST_SERIALIZATION)
protected:
- explicit call_scope_t() {
+ explicit call_scope_t() : depth(0) {
TRACE_CTOR(call_scope_t, "");
}
@@ -635,6 +665,21 @@ call_scope_t::get<expr_t::ptr_op_t>(std::size_t index, bool) {
return args[index].as_any<expr_t::ptr_op_t>();
}
+inline string join_args(call_scope_t& args) {
+ std::ostringstream buf;
+ bool first = true;
+
+ for (std::size_t i = 0; i < args.size(); i++) {
+ if (first)
+ first = false;
+ else
+ buf << ' ';
+ buf << args[i];
+ }
+
+ return buf.str();
+}
+
class value_scope_t : public child_scope_t
{
value_t value;
@@ -645,7 +690,12 @@ class value_scope_t : public child_scope_t
public:
value_scope_t(scope_t& _parent, const value_t& _value)
- : child_scope_t(_parent), value(_value) {}
+ : child_scope_t(_parent), value(_value) {
+ TRACE_CTOR(value_scope_t, "scope_t&, value_t");
+ }
+ ~value_scope_t() throw() {
+ TRACE_DTOR(value_scope_t);
+ }
virtual string description() {
return parent->description();
@@ -660,7 +710,7 @@ public:
if (name == "value")
return MAKE_FUNCTOR(value_scope_t::get_value);
- return NULL;
+ return child_scope_t::lookup(kind, name);
}
};
diff --git a/src/select.cc b/src/select.cc
new file mode 100644
index 00000000..56bd3f2d
--- /dev/null
+++ b/src/select.cc
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "select.h"
+#include "journal.h"
+#include "account.h"
+#include "report.h"
+#include "output.h"
+#include "print.h"
+#include "chain.h"
+#include "filters.h"
+#include "scope.h"
+#include "op.h"
+
+namespace ledger {
+
+namespace {
+ bool get_principal_identifiers(expr_t::ptr_op_t expr, string& ident,
+ bool do_transforms = false)
+ {
+ bool result = true;
+
+ if (expr->is_ident()) {
+ string name(expr->as_ident());
+ if (name == "date" || name == "aux_date" || name == "payee") {
+ if (! ident.empty() &&
+ ! (name == "date" || name == "aux_date" || name == "payee"))
+ result = false;
+ ident = name;
+ }
+ else if (name == "account") {
+ if (! ident.empty() && ! (name == "account"))
+ result = false;
+ ident = name;
+ if (do_transforms)
+ expr->set_ident("display_account");
+ }
+ else if (name == "amount") {
+ if (! ident.empty() && ! (name == "amount"))
+ result = false;
+ ident = name;
+ if (do_transforms)
+ expr->set_ident("display_amount");
+ }
+ else if (name == "total") {
+ if (! ident.empty() && ! (name == "total"))
+ result = false;
+ ident = name;
+ if (do_transforms)
+ expr->set_ident("display_total");
+ }
+ }
+
+ if (expr->kind > expr_t::op_t::TERMINALS || expr->is_scope()) {
+ if (expr->left()) {
+ if (! get_principal_identifiers(expr->left(), ident, do_transforms))
+ result = false;
+ if (expr->kind > expr_t::op_t::UNARY_OPERATORS && expr->has_right())
+ if (! get_principal_identifiers(expr->right(), ident, do_transforms))
+ result = false;
+ }
+ }
+
+ return result;
+ }
+}
+
+value_t select_command(call_scope_t& args)
+{
+ string text = "select " + join_args(args);
+ if (text.empty())
+ throw std::logic_error(_("Usage: select TEXT"));
+
+ report_t& report(find_scope<report_t>(args));
+
+ // Our first step is to divide the select statement into its principal
+ // parts:
+ //
+ // SELECT <VALEXPR-LIST>
+ // FROM <NAME>
+ // WHERE <VALEXPR>
+ // DISPLAY <VALEXPR>
+ // COLLECT <VALEXPR>
+ // GROUP BY <VALEXPR>
+ // STYLE <NAME>
+
+ boost::regex select_re
+ ("(select|from|where|display|collect|group\\s+by|style)\\s+"
+ "(.+?)"
+ "(?=(\\s+(from|where|display|collect|group\\s+by|style)\\s+|$))",
+ boost::regex::perl | boost::regex::icase);
+
+ boost::regex from_accounts_re("from\\s+accounts\\>");
+ bool accounts_report = boost::regex_search(text, from_accounts_re);
+
+ boost::sregex_iterator m1(text.begin(), text.end(), select_re);
+ boost::sregex_iterator m2;
+
+ expr_t::ptr_op_t report_functor;
+ std::ostringstream formatter;
+
+ while (m1 != m2) {
+ const boost::match_results<string::const_iterator>& match(*m1);
+
+ string keyword(match[1]);
+ string arg(match[2]);
+
+ DEBUG("select.parse", "keyword: " << keyword);
+ DEBUG("select.parse", "arg: " << arg);
+
+ if (keyword == "select") {
+ expr_t args_expr(arg);
+ value_t columns(split_cons_expr(args_expr.get_op()));
+ bool first = true;
+ string thus_far = "";
+
+ std::size_t cols = 0;
+ if (report.HANDLED(columns_))
+ cols = lexical_cast<std::size_t>(report.HANDLER(columns_).value);
+ else if (const char * columns_env = std::getenv("COLUMNS"))
+ cols = lexical_cast<std::size_t>(columns_env);
+ else
+ cols = 80;
+
+ std::size_t date_width =
+ (report.HANDLED(date_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(date_width_).str()) :
+ static_cast<std::size_t>
+ (format_date(CURRENT_DATE(),FMT_PRINTED).length()));
+ std::size_t payee_width =
+ (report.HANDLED(payee_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(payee_width_).str()) :
+ std::size_t(double(cols) * 0.263157));
+ std::size_t account_width =
+ (report.HANDLED(account_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(account_width_).str()) :
+ std::size_t(double(cols) * 0.302631));
+ std::size_t amount_width =
+ (report.HANDLED(amount_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(amount_width_).str()) :
+ std::size_t(double(cols) * 0.157894));
+ std::size_t total_width =
+ (report.HANDLED(total_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(total_width_).str()) :
+ amount_width);
+ std::size_t meta_width =
+ (report.HANDLED(meta_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(meta_width_).str()) :
+ 10);
+
+ bool saw_date = false;
+ bool saw_payee = false;
+ bool saw_account = false;
+ bool saw_amount = false;
+ bool saw_total = false;
+ bool saw_meta = false;
+
+ std::size_t cols_needed = 0;
+ foreach (const value_t& column, columns.to_sequence()) {
+ string ident;
+ if (get_principal_identifiers(as_expr(column), ident)) {
+ if (ident == "date" || ident == "aux_date") {
+ cols_needed += date_width + 1;
+ saw_date = true;
+ }
+ else if (ident == "payee") {
+ cols_needed += payee_width + 1;
+ saw_payee = true;
+ }
+ else if (ident == "account") {
+ cols_needed += account_width + 1;
+ saw_account = true;
+ }
+ else if (ident == "amount") {
+ cols_needed += amount_width + 1;
+ saw_amount = true;
+ }
+ else if (ident == "total") {
+ cols_needed += total_width + 1;
+ saw_total = true;
+ }
+ else {
+ cols_needed += meta_width + 1;
+ saw_meta = true;
+ }
+ }
+ }
+
+ while ((saw_account || saw_payee) && cols_needed < cols) {
+ if (saw_account && cols_needed < cols) {
+ ++account_width;
+ ++cols_needed;
+ if (cols_needed < cols) {
+ ++account_width;
+ ++cols_needed;
+ }
+ }
+ if (saw_payee && cols_needed < cols) {
+ ++payee_width;
+ ++cols_needed;
+ }
+ }
+
+ while ((saw_account || saw_payee) && cols_needed > cols &&
+ account_width > 5 && payee_width > 5) {
+ DEBUG("auto.columns", "adjusting account down");
+ if (saw_account && cols_needed > cols) {
+ --account_width;
+ --cols_needed;
+ if (cols_needed > cols) {
+ --account_width;
+ --cols_needed;
+ }
+ }
+ if (saw_payee && cols_needed > cols) {
+ --payee_width;
+ --cols_needed;
+ }
+ DEBUG("auto.columns", "account_width now = " << account_width);
+ }
+
+ if (! report.HANDLED(date_width_))
+ report.HANDLER(date_width_).value = to_string(date_width);
+ if (! report.HANDLED(payee_width_))
+ report.HANDLER(payee_width_).value = to_string(payee_width);
+ if (! report.HANDLED(account_width_))
+ report.HANDLER(account_width_).value = to_string(account_width);
+ if (! report.HANDLED(amount_width_))
+ report.HANDLER(amount_width_).value = to_string(amount_width);
+ if (! report.HANDLED(total_width_))
+ report.HANDLER(total_width_).value = to_string(total_width);
+
+ foreach (const value_t& column, columns.to_sequence()) {
+ if (first)
+ first = false;
+ else
+ formatter << ' ';
+
+ formatter << "%(";
+
+ string ident;
+ if (get_principal_identifiers(as_expr(column), ident, true)) {
+ if (ident == "date" || ident == "aux_date") {
+ formatter << "ansify_if("
+ << "ansify_if(justify(format_date(";
+
+ as_expr(column)->print(formatter);
+
+ formatter << "), int(date_width)),";
+ formatter << "green if color and date > today),"
+ << "bold if should_bold)";
+
+ if (! thus_far.empty())
+ thus_far += " + ";
+ thus_far += "int(date_width) + 1";
+ }
+ else if (ident == "payee") {
+ formatter << "ansify_if("
+ << "ansify_if(justify(truncated(";
+
+ as_expr(column)->print(formatter);
+
+ formatter << ", int(payee_width)), int(payee_width)),";
+ formatter << "bold if color and !cleared and actual),"
+ << "bold if should_bold)";
+
+ if (! thus_far.empty())
+ thus_far += " + ";
+ thus_far += "int(payee_width) + 1";
+ }
+ else if (ident == "account") {
+ formatter << "ansify_if("
+ << "ansify_if(";
+
+ if (accounts_report) {
+ formatter << "partial_account(options.flat), blue if color),";
+ } else {
+ formatter << "justify(truncated(";
+ as_expr(column)->print(formatter);
+ formatter << ", int(account_width), int(abbrev_len)),"
+ << "int(account_width)),";
+ formatter << "true, color),";
+
+ if (! thus_far.empty())
+ thus_far += " + ";
+ thus_far += "int(account_width) + 1";
+ }
+
+ formatter << " bold if should_bold)";
+ }
+ else if (ident == "amount" || ident == "total") {
+ formatter << "ansify_if("
+ << "justify(scrub(";
+
+ as_expr(column)->print(formatter);
+
+ formatter << "), ";
+
+ if (ident == "amount")
+ formatter << "int(amount_width),";
+ else
+ formatter << "int(total_width),";
+
+ if (! thus_far.empty())
+ thus_far += " + ";
+
+ if (ident == "amount")
+ thus_far += "int(amount_width)";
+ else
+ thus_far += "int(total_width)";
+
+ if (thus_far.empty())
+ formatter << "-1";
+ else
+ formatter << thus_far;
+
+ formatter << ", true, color),"
+ << " bold if should_bold)";
+
+ thus_far += " + 1";
+ }
+ else {
+ formatter << "ansify_if("
+ << "justify(truncated(";
+
+ as_expr(column)->print(formatter);
+
+ formatter << ", int(meta_width or 10)), int(meta_width) or 10),";
+ formatter << "bold if should_bold)";
+
+ if (! thus_far.empty())
+ thus_far += " + ";
+ thus_far += "(int(meta_width) or 10) + 1";
+ }
+ }
+ formatter << ")";
+ }
+ formatter << "\\n";
+ }
+ else if (keyword == "from") {
+ DEBUG("select.parse", "formatter: " << formatter.str());
+
+ if (arg == "xacts" || arg == "txns" || arg == "transactions") {
+ report_functor = expr_t::op_t::wrap_functor
+ (reporter<>(post_handler_ptr(new print_xacts(report,
+ report.HANDLED(raw))),
+ report, string("#select")));
+ }
+ else if (arg == "posts" || arg == "postings") {
+ report_functor = expr_t::op_t::wrap_functor
+ (reporter<>(post_handler_ptr(new format_posts(report, formatter.str())),
+ report, string("#select")));
+ }
+ else if (arg == "accounts") {
+ report_functor = expr_t::op_t::wrap_functor
+ (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
+ (acct_handler_ptr(new format_accounts(report, formatter.str())),
+ report, string("#select")));
+ }
+ else if (arg == "commodities") {
+ report_functor = expr_t::op_t::wrap_functor
+ (reporter<post_t, post_handler_ptr, &report_t::commodities_report>
+ (post_handler_ptr(new format_posts(report, formatter.str())),
+ report, string("#select")));
+ }
+ }
+ else if (keyword == "where") {
+#if 0
+ query_t query;
+ keep_details_t keeper(true, true, true);
+ expr_t::ptr_op_t expr =
+ query.parse_args(string_value(arg).to_sequence(), keeper, false, true);
+ report.HANDLER(limit_).on("#select", query.get_query(query_t::QUERY_LIMIT));
+#else
+ report.HANDLER(limit_).on("#select", arg);
+#endif
+ }
+ else if (keyword == "display") {
+ report.HANDLER(display_).on("#select", arg);
+ }
+ else if (keyword == "collect") {
+ report.HANDLER(amount_).on("#select", arg);
+ }
+ else if (keyword == "group by") {
+ report.HANDLER(group_by_).on("#select", arg);
+ }
+ else if (keyword == "style") {
+ if (arg == "csv") {
+ }
+ else if (arg == "xml") {
+ }
+ else if (arg == "emacs") {
+ }
+ else if (arg == "org") {
+ }
+ }
+
+ ++m1;
+ }
+
+ if (! report_functor) {
+ report_functor = expr_t::op_t::wrap_functor
+ (reporter<>(post_handler_ptr(new format_posts(report, formatter.str())),
+ report, string("#select")));
+ }
+
+ call_scope_t call_args(report);
+ return report_functor->as_function()(call_args);
+}
+
+} // namespace ledger
diff --git a/src/predicate.cc b/src/select.h
index fd301a7d..54883d22 100644
--- a/src/predicate.cc
+++ b/src/select.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -29,12 +29,27 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include <system.hh>
+/**
+ * @addtogroup select
+ */
+
+/**
+ * @file select.h
+ * @author John Wiegley
+ *
+ * @ingroup select
+ */
+#ifndef _SELECT_H
+#define _SELECT_H
-#include "predicate.h"
-#include "query.h"
-#include "op.h"
+#include "utils.h"
+#include "value.h"
namespace ledger {
+class call_scope_t;
+value_t select_command(call_scope_t& args);
+
} // namespace ledger
+
+#endif // _SELECT_H
diff --git a/src/series.h b/src/series.h
deleted file mode 100644
index 40f34051..00000000
--- a/src/series.h
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * - Neither the name of New Artisans LLC nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/**
- * @addtogroup expr
- */
-
-/**
- * @file series.h
- * @author John Wiegley
- *
- * @ingroup expr
- */
-#ifndef _SERIES_H
-#define _SERIES_H
-
-#include "scope.h"
-
-namespace ledger {
-
-class expr_series_t
-{
-protected:
- scope_t * context;
-
-public:
- optional<std::list<expr_t> > exprs;
- expr_t default_expr;
- std::string variable;
-
- expr_series_t(const std::string& _variable)
- : context(NULL), default_expr(_variable), variable(_variable) {
- TRACE_CTOR(expr_series_t, "std::string");
- }
- expr_series_t(const expr_t& expr, const std::string& _variable)
- : context(const_cast<expr_t&>(expr).get_context()),
- default_expr(expr), variable(_variable) {
- TRACE_CTOR(expr_series_t, "expr_t, std::string");
- }
- expr_series_t(const expr_series_t& other)
- : context(other.context), exprs(other.exprs),
- default_expr(other.default_expr), variable(other.variable) {
- TRACE_CTOR(expr_series_t, "copy");
- }
- virtual ~expr_series_t() {
- TRACE_DTOR(expr_series_t);
- }
-
- scope_t * get_context() {
- return context;
- }
- void set_context(scope_t * scope) {
- context = scope;
- }
-
- bool empty() const {
- return ! exprs || exprs->empty();
- }
-
- void push_back(const expr_t& expr) {
- if (! exprs)
- exprs = std::list<expr_t>();
- exprs->push_back(expr);
- }
- void pop_back() {
- assert(exprs);
- exprs->pop_back();
- }
-
- void mark_uncompiled() {
- if (exprs)
- foreach (expr_t& expr, *exprs)
- expr.mark_uncompiled();
- else
- default_expr.mark_uncompiled();
- }
-
- void compile(scope_t& scope) {
- if (exprs)
- foreach (expr_t& expr, *exprs)
- expr.compile(scope);
- else
- default_expr.compile(scope);
- }
-
- value_t calc(scope_t& scope) {
- if (exprs) {
- value_t result;
- symbol_scope_t sym_scope(scope);
- std::size_t len(exprs->size());
-
- foreach (expr_t& expr, *exprs) {
- result = expr.calc(sym_scope);
- if (--len > 0)
- sym_scope.define(symbol_t::FUNCTION, variable,
- expr_t::op_t::wrap_value(result));
- }
- return result;
- } else {
- return default_expr.calc(scope);
- }
- }
-};
-
-} // namespace ledger
-
-#endif // _SERIES_H
diff --git a/src/session.cc b/src/session.cc
index 72e29895..9a77d341 100644
--- a/src/session.cc
+++ b/src/session.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -62,12 +62,14 @@ void set_session_context(session_t * session)
session_t::session_t()
: flush_on_next_data_file(false), journal(new journal_t)
{
- TRACE_CTOR(session_t, "");
-
if (const char * home_var = std::getenv("HOME"))
HANDLER(price_db_).on(none, (path(home_var) / ".pricedb").string());
else
HANDLER(price_db_).on(none, path("./.pricedb").string());
+
+ parsing_context.push();
+
+ TRACE_CTOR(session_t, "");
}
std::size_t session_t::read_data(const string& master_account)
@@ -89,14 +91,32 @@ std::size_t session_t::read_data(const string& master_account)
std::size_t xact_count = 0;
- account_t * acct = journal->master;
- if (! master_account.empty())
+ account_t * acct;
+ if (master_account.empty())
+ acct = journal->master;
+ else
acct = journal->find_account(master_account);
optional<path> price_db_path;
if (HANDLED(price_db_))
price_db_path = resolve_path(HANDLER(price_db_).str());
+ if (HANDLED(explicit))
+ journal->force_checking = true;
+ if (HANDLED(check_payees))
+ journal->check_payees = true;
+ if (HANDLED(day_break))
+ journal->day_break = true;
+
+ if (HANDLED(permissive))
+ journal->checking_style = journal_t::CHECK_PERMISSIVE;
+ else if (HANDLED(pedantic))
+ journal->checking_style = journal_t::CHECK_ERROR;
+ else if (HANDLED(strict))
+ journal->checking_style = journal_t::CHECK_WARNING;
+ else if (HANDLED(value_expr_))
+ journal->value_expr = HANDLER(value_expr_).str();
+
#if defined(HAVE_BOOST_SERIALIZATION)
optional<archive_t> cache;
if (HANDLED(cache_) && master_account.empty())
@@ -108,8 +128,17 @@ std::size_t session_t::read_data(const string& master_account)
#endif // HAVE_BOOST_SERIALIZATION
if (price_db_path) {
if (exists(*price_db_path)) {
- if (journal->read(*price_db_path) > 0)
- throw_(parse_error, _("Transactions not allowed in price history file"));
+ parsing_context.push(*price_db_path);
+ parsing_context.get_current().journal = journal.get();
+ try {
+ if (journal->read(parsing_context) > 0)
+ throw_(parse_error, _("Transactions not allowed in price history file"));
+ }
+ catch (...) {
+ parsing_context.pop();
+ throw;
+ }
+ parsing_context.pop();
}
}
@@ -128,12 +157,22 @@ std::size_t session_t::read_data(const string& master_account)
}
buffer.flush();
- std::istringstream buf_in(buffer.str());
- xact_count += journal->read(buf_in, "/dev/stdin", acct);
- journal->sources.push_back(journal_t::fileinfo_t());
+ shared_ptr<std::istream> stream(new std::istringstream(buffer.str()));
+ parsing_context.push(stream);
} else {
- xact_count += journal->read(pathname, acct);
+ parsing_context.push(pathname);
}
+
+ parsing_context.get_current().journal = journal.get();
+ parsing_context.get_current().master = acct;
+ try {
+ xact_count += journal->read(parsing_context);
+ }
+ catch (...) {
+ parsing_context.pop();
+ throw;
+ }
+ parsing_context.pop();
}
DEBUG("ledger.read", "xact_count [" << xact_count
@@ -154,7 +193,7 @@ std::size_t session_t::read_data(const string& master_account)
return journal->xacts.size();
}
-void session_t::read_journal_files()
+journal_t * session_t::read_journal_files()
{
INFO_START(journal, "Read journal file");
@@ -172,6 +211,37 @@ void session_t::read_journal_files()
#if defined(DEBUG_ON)
INFO("Found " << count << " transactions");
#endif
+
+ return journal.get();
+}
+
+journal_t * session_t::read_journal(const path& pathname)
+{
+ HANDLER(file_).data_files.clear();
+ HANDLER(file_).data_files.push_back(pathname);
+
+ return read_journal_files();
+}
+
+journal_t * session_t::read_journal_from_string(const string& data)
+{
+ HANDLER(file_).data_files.clear();
+
+ shared_ptr<std::istream> stream(new std::istringstream(data));
+ parsing_context.push(stream);
+
+ parsing_context.get_current().journal = journal.get();
+ parsing_context.get_current().master = journal->master;
+ try {
+ journal->read(parsing_context);
+ }
+ catch (...) {
+ parsing_context.pop();
+ throw;
+ }
+ parsing_context.pop();
+
+ return journal.get();
}
void session_t::close_journal_files()
@@ -202,6 +272,15 @@ value_t session_t::fn_max(call_scope_t& args)
return args[1] > args[0] ? args[1] : args[0];
}
+value_t session_t::fn_int(call_scope_t& args)
+{
+ return args[0].to_long();
+}
+value_t session_t::fn_str(call_scope_t& args)
+{
+ return string_value(args[0].to_string());
+}
+
value_t session_t::fn_lot_price(call_scope_t& args)
{
amount_t amt(args.get<amount_t>(1, false));
@@ -238,10 +317,15 @@ option_t<session_t> * session_t::lookup_option(const char * p)
break;
case 'c':
OPT(cache_);
+ else OPT(check_payees);
break;
case 'd':
OPT(download); // -Q
else OPT(decimal_comma);
+ else OPT(day_break);
+ break;
+ case 'e':
+ OPT(explicit);
break;
case 'f':
OPT_(file_); // -f
@@ -258,10 +342,15 @@ option_t<session_t> * session_t::lookup_option(const char * p)
case 'p':
OPT(price_db_);
else OPT(price_exp_);
+ else OPT(pedantic);
+ else OPT(permissive);
break;
case 's':
OPT(strict);
break;
+ case 'v':
+ OPT(value_expr_);
+ break;
}
return NULL;
}
@@ -288,6 +377,11 @@ expr_t::ptr_op_t session_t::lookup(const symbol_t::kind_t kind,
return MAKE_FUNCTOR(session_t::fn_lot_tag);
break;
+ case 'i':
+ if (is_eq(p, "int"))
+ return MAKE_FUNCTOR(session_t::fn_int);
+ break;
+
case 'm':
if (is_eq(p, "min"))
return MAKE_FUNCTOR(session_t::fn_min);
@@ -295,6 +389,11 @@ expr_t::ptr_op_t session_t::lookup(const symbol_t::kind_t kind,
return MAKE_FUNCTOR(session_t::fn_max);
break;
+ case 's':
+ if (is_eq(p, "str"))
+ return MAKE_FUNCTOR(session_t::fn_str);
+ break;
+
default:
break;
}
diff --git a/src/session.h b/src/session.h
index b8fd52f2..a0aba91b 100644
--- a/src/session.h
+++ b/src/session.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -44,6 +44,7 @@
#include "account.h"
#include "journal.h"
+#include "context.h"
#include "option.h"
#include "commodity.h"
@@ -57,11 +58,15 @@ class session_t : public symbol_scope_t
public:
bool flush_on_next_data_file;
- std::auto_ptr<journal_t> journal;
+
+ unique_ptr<journal_t> journal;
+ parse_context_stack_t parsing_context;
+ optional<expr_t> value_expr;
explicit session_t();
virtual ~session_t() {
TRACE_DTOR(session_t);
+ parsing_context.pop();
}
virtual string description() {
@@ -72,14 +77,18 @@ public:
flush_on_next_data_file = truth;
}
+ journal_t * read_journal(const path& pathname);
+ journal_t * read_journal_from_string(const string& data);
std::size_t read_data(const string& master_account = "");
- void read_journal_files();
+ journal_t * read_journal_files();
void close_journal_files();
value_t fn_account(call_scope_t& scope);
value_t fn_min(call_scope_t& scope);
value_t fn_max(call_scope_t& scope);
+ value_t fn_int(call_scope_t& scope);
+ value_t fn_str(call_scope_t& scope);
value_t fn_lot_price(call_scope_t& scope);
value_t fn_lot_date(call_scope_t& scope);
value_t fn_lot_tag(call_scope_t& scope);
@@ -87,14 +96,20 @@ public:
void report_options(std::ostream& out)
{
HANDLER(cache_).report(out);
+ HANDLER(check_payees).report(out);
+ HANDLER(day_break).report(out);
HANDLER(download).report(out);
HANDLER(decimal_comma).report(out);
HANDLER(file_).report(out);
HANDLER(input_date_format_).report(out);
+ HANDLER(explicit).report(out);
HANDLER(master_account_).report(out);
+ HANDLER(pedantic).report(out);
+ HANDLER(permissive).report(out);
HANDLER(price_db_).report(out);
HANDLER(price_exp_).report(out);
HANDLER(strict).report(out);
+ HANDLER(value_expr_).report(out);
}
option_t<session_t> * lookup_option(const char * p);
@@ -107,6 +122,8 @@ public:
*/
OPTION(session_t, cache_);
+ OPTION(session_t, check_payees);
+ OPTION(session_t, day_break);
OPTION(session_t, download); // -Q
OPTION_(session_t, decimal_comma, DO() {
@@ -115,33 +132,33 @@ public:
OPTION__
(session_t, price_exp_, // -Z
- CTOR(session_t, price_exp_) { value = 24L * 3600L; }
- DO_(args) {
- value = args.get<long>(1) * 60L;
- });
+ CTOR(session_t, price_exp_) { value = "24"; });
OPTION__
(session_t, file_, // -f
std::list<path> data_files;
CTOR(session_t, file_) {}
- DO_(args) {
- assert(args.size() == 2);
+ DO_(str) {
if (parent->flush_on_next_data_file) {
data_files.clear();
parent->flush_on_next_data_file = false;
}
- data_files.push_back(args.get<string>(1));
+ data_files.push_back(str);
});
- OPTION_(session_t, input_date_format_, DO_(args) {
- // This changes static variables inside times.h, which affects the basic
- // date parser.
- set_input_date_format(args.get<string>(1).c_str());
+ OPTION_(session_t, input_date_format_, DO_(str) {
+ // This changes static variables inside times.h, which affects the
+ // basic date parser.
+ set_input_date_format(str.c_str());
});
+ OPTION(session_t, explicit);
OPTION(session_t, master_account_);
+ OPTION(session_t, pedantic);
+ OPTION(session_t, permissive);
OPTION(session_t, price_db_);
OPTION(session_t, strict);
+ OPTION(session_t, value_expr_);
};
/**
diff --git a/src/stats.cc b/src/stats.cc
index 524f5a87..0966fee2 100644
--- a/src/stats.cc
+++ b/src/stats.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/stats.h b/src/stats.h
index b7bf94c5..7b00fec8 100644
--- a/src/stats.h
+++ b/src/stats.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/stream.cc b/src/stream.cc
index 5d4cf5e0..ce40bfcc 100644
--- a/src/stream.cc
+++ b/src/stream.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/stream.h b/src/stream.h
index 42c85534..c317ebdf 100644
--- a/src/stream.h
+++ b/src/stream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/system.hh.in b/src/system.hh.in
index 42a82e41..552a591a 100644
--- a/src/system.hh.in
+++ b/src/system.hh.in
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -138,38 +138,58 @@ typedef std::ostream::pos_type ostream_pos_type;
#include <boost/bind.hpp>
#include <boost/cast.hpp>
#include <boost/current_function.hpp>
+
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>
#include <boost/date_time/gregorian/gregorian_io.hpp>
+
#include <boost/filesystem/convenience.hpp>
#include <boost/filesystem/exception.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
+
#if !(defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)
#include <boost/foreach.hpp>
#endif
#include <boost/function.hpp>
+
+#include <boost/graph/adjacency_list.hpp>
+#include <boost/graph/filtered_graph.hpp>
+#include <boost/graph/dijkstra_shortest_paths.hpp>
+#include <boost/graph/graphviz.hpp>
+
#include <boost/intrusive_ptr.hpp>
+
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/write.hpp>
#define BOOST_IOSTREAMS_USE_DEPRECATED 1
#include <boost/iostreams/device/file_descriptor.hpp>
+
#include <boost/iterator/iterator_facade.hpp>
#include <boost/iterator/transform_iterator.hpp>
+
#include <boost/lexical_cast.hpp>
#include <boost/operators.hpp>
#include <boost/optional.hpp>
#include <boost/ptr_container/ptr_list.hpp>
+
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/variate_generator.hpp>
+
#if defined(HAVE_BOOST_REGEX_UNICODE)
#include <boost/regex/icu.hpp>
#else
#include <boost/regex.hpp>
#endif // HAVE_BOOST_REGEX_UNICODE
+
+#include <boost/tokenizer.hpp>
+
+#include <boost/tuple/tuple.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+
#include <boost/variant.hpp>
#include <boost/version.hpp>
@@ -226,12 +246,19 @@ void serialize(Archive& ar, boost::intrusive_ptr<T>& ptr, const unsigned int)
}
}
-template <class Archive, class T>
-void serialize(Archive&, boost::function<T>&, const unsigned int)
-{
+template <class Archive>
+void serialize(Archive&, boost::any&, const unsigned int) {
+ // jww (2012-03-29): Should we really ignore any fields entirely?
+ // These occur inside value_t::storage_t::data's variant.
}
template <class Archive>
+void serialize(Archive&, boost::blank&, const unsigned int) {}
+
+template <class Archive, class T>
+void serialize(Archive&, boost::function<T>&, const unsigned int) {}
+
+template <class Archive>
void serialize(Archive& ar, istream_pos_type& pos, const unsigned int)
{
ar & make_binary_object(&pos, sizeof(istream_pos_type));
@@ -257,4 +284,6 @@ void serialize(Archive& ar, istream_pos_type& pos, const unsigned int)
#include <boost/python/module_init.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
+#include <boost/iterator/indirect_iterator.hpp>
+
#endif // HAVE_BOOST_PYTHON
diff --git a/src/temps.cc b/src/temps.cc
index 365c33c5..881077f6 100644
--- a/src/temps.cc
+++ b/src/temps.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -81,7 +81,8 @@ post_t& temporaries_t::copy_post(post_t& origin, xact_t& xact,
return temp;
}
-post_t& temporaries_t::create_post(xact_t& xact, account_t * account)
+post_t& temporaries_t::create_post(xact_t& xact, account_t * account,
+ bool bidir_link)
{
if (! post_temps)
post_temps = std::list<post_t>();
@@ -93,7 +94,10 @@ post_t& temporaries_t::create_post(xact_t& xact, account_t * account)
temp.account = account;
temp.account->add_post(&temp);
- xact.add_post(&temp);
+ if (bidir_link)
+ xact.add_post(&temp);
+ else
+ temp.xact = &xact;
return temp;
}
diff --git a/src/temps.h b/src/temps.h
index 1e7eb69f..daa1493b 100644
--- a/src/temps.h
+++ b/src/temps.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -51,7 +51,11 @@ class temporaries_t
optional<std::list<account_t> > acct_temps;
public:
+ temporaries_t() {
+ TRACE_CTOR(temporaries_t, "");
+ }
~temporaries_t() {
+ TRACE_DTOR(temporaries_t);
clear();
}
@@ -62,7 +66,8 @@ public:
}
post_t& copy_post(post_t& origin, xact_t& xact,
account_t * account = NULL);
- post_t& create_post(xact_t& xact, account_t * account);
+ post_t& create_post(xact_t& xact, account_t * account,
+ bool bidir_link = true);
post_t& last_post() {
return post_temps->back();
}
diff --git a/src/textual.cc b/src/textual.cc
index c7c49e2a..d0e4dad2 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -32,6 +32,7 @@
#include <system.hh>
#include "journal.h"
+#include "context.h"
#include "xact.h"
#include "post.h"
#include "account.h"
@@ -39,7 +40,9 @@
#include "query.h"
#include "pstream.h"
#include "pool.h"
-#include "session.h"
+#if defined(HAVE_BOOST_PYTHON)
+#include "pyinterp.h"
+#endif
#define TIMELOG_SUPPORT 1
#if defined(TIMELOG_SUPPORT)
@@ -49,123 +52,150 @@
namespace ledger {
namespace {
- typedef std::pair<commodity_t *, amount_t> fixed_rate_t;
- typedef variant<account_t *, string, fixed_rate_t> state_t;
+ typedef std::pair<commodity_t *, amount_t> fixed_rate_t;
- class parse_context_t : public noncopyable
+ struct application_t
+ {
+ string label;
+ variant<optional<datetime_t>, account_t *, string, fixed_rate_t> value;
+
+ application_t(string _label, optional<datetime_t> epoch)
+ : label(_label), value(epoch) {}
+ application_t(string _label, account_t * acct)
+ : label(_label), value(acct) {}
+ application_t(string _label, string tag)
+ : label(_label), value(tag) {}
+ application_t(string _label, fixed_rate_t rate)
+ : label(_label), value(rate) {}
+ };
+
+ class instance_t : public noncopyable, public scope_t
{
public:
- journal_t& journal;
- scope_t& scope;
- std::list<state_t> state_stack;
+ parse_context_stack_t& context_stack;
+ parse_context_t& context;
+ std::istream& in;
+ instance_t * parent;
+ std::list<application_t> apply_stack;
#if defined(TIMELOG_SUPPORT)
- time_log_t timelog;
+ time_log_t timelog;
#endif
- bool strict;
- std::size_t count;
- std::size_t errors;
- std::size_t sequence;
-
- parse_context_t(journal_t& _journal, scope_t& _scope)
- : journal(_journal), scope(_scope), timelog(journal, scope),
- strict(false), count(0), errors(0), sequence(1) {
- timelog.context_count = &count;
- }
- bool front_is_account() {
- return state_stack.front().type() == typeid(account_t *);
- }
- bool front_is_string() {
- return state_stack.front().type() == typeid(string);
- }
- bool front_is_fixed_rate() {
- return state_stack.front().type() == typeid(fixed_rate_t);
- }
+ instance_t(parse_context_stack_t& _context_stack,
+ parse_context_t& _context,
+ instance_t * _parent = NULL)
+ : context_stack(_context_stack), context(_context),
+ in(*context.stream.get()), parent(_parent),
+ timelog(context) {}
- account_t * top_account() {
- foreach (state_t& state, state_stack)
- if (state.type() == typeid(account_t *))
- return boost::get<account_t *>(state);
- return NULL;
+ virtual string description() {
+ return _("textual parser");
}
- void close() {
- timelog.close();
+ template <typename T>
+ void get_applications(std::vector<T>& result) {
+ foreach (application_t& state, apply_stack) {
+ if (state.value.type() == typeid(T))
+ result.push_back(boost::get<T>(state.value));
+ }
+ if (parent)
+ parent->get_applications<T>(result);
}
- };
- class instance_t : public noncopyable, public scope_t
- {
- static const std::size_t MAX_LINE = 1024;
-
- public:
- parse_context_t& context;
- instance_t * parent;
- accounts_map account_aliases;
- const path * original_file;
- path pathname;
- std::istream& in;
- char linebuf[MAX_LINE + 1];
- std::size_t linenum;
- istream_pos_type line_beg_pos;
- istream_pos_type curr_pos;
- optional<datetime_t> prev_epoch;
-
- instance_t(parse_context_t& _context,
- std::istream& _in,
- const path * _original_file = NULL,
- instance_t * _parent = NULL);
-
- ~instance_t();
+ template <typename T>
+ optional<T> get_application() {
+ foreach (application_t& state, apply_stack) {
+ if (state.value.type() == typeid(T))
+ return boost::get<T>(state.value);
+ }
+ return parent ? parent->get_application<T>() : none;
+ }
- virtual string description() {
- return _("textual parser");
+ account_t * top_account() {
+ if (optional<account_t *> acct = get_application<account_t *>())
+ return *acct;
+ else
+ return NULL;
}
void parse();
+
std::streamsize read_line(char *& line);
+
bool peek_whitespace_line() {
return (in.good() && ! in.eof() &&
(in.peek() == ' ' || in.peek() == '\t'));
}
+#if defined(HAVE_BOOST_PYTHON)
+ bool peek_blank_line() {
+ return (in.good() && ! in.eof() &&
+ (in.peek() == '\n' || in.peek() == '\r'));
+ }
+#endif
- void read_next_directive();
+ void read_next_directive(bool& error_flag);
#if defined(TIMELOG_SUPPORT)
void clock_in_directive(char * line, bool capitalized);
void clock_out_directive(char * line, bool capitalized);
#endif
- void default_commodity_directive(char * line);
+ bool general_directive(char * line);
+
+ void account_directive(char * line);
+ void account_alias_directive(account_t * account, string alias);
+ void account_payee_directive(account_t * account, string payee);
+ void account_value_directive(account_t * account, string expr_str);
+ void account_default_directive(account_t * account);
+
void default_account_directive(char * line);
- void price_conversion_directive(char * line);
+ void alias_directive(char * line);
+
+ void payee_directive(char * line);
+ void payee_alias_directive(const string& payee, string alias);
+
+ void commodity_directive(char * line);
+ void commodity_alias_directive(commodity_t& comm, string alias);
+ void commodity_value_directive(commodity_t& comm, string expr_str);
+ void commodity_format_directive(commodity_t& comm, string format);
+ void commodity_nomarket_directive(commodity_t& comm);
+ void commodity_default_directive(commodity_t& comm);
+
+ void default_commodity_directive(char * line);
+
+ void tag_directive(char * line);
+
+ void apply_directive(char * line);
+ void apply_account_directive(char * line);
+ void apply_tag_directive(char * line);
+ void apply_rate_directive(char * line);
+ void apply_year_directive(char * line);
+ void end_apply_directive(char * line);
+
+ void xact_directive(char * line, std::streamsize len);
+ void period_xact_directive(char * line);
+ void automated_xact_directive(char * line);
void price_xact_directive(char * line);
+ void price_conversion_directive(char * line);
void nomarket_directive(char * line);
- void year_directive(char * line);
- void option_directive(char * line);
- void automated_xact_directive(char * line);
- void period_xact_directive(char * line);
- void xact_directive(char * line, std::streamsize len);
+
void include_directive(char * line);
- void master_account_directive(char * line);
- void end_directive(char * line);
- void alias_directive(char * line);
- void fixed_directive(char * line);
- void payee_mapping_directive(char * line);
- void account_mapping_directive(char * line);
- void tag_directive(char * line);
- void define_directive(char * line);
+ void option_directive(char * line);
+ void comment_directive(char * line);
+
+ void eval_directive(char * line);
void assert_directive(char * line);
void check_directive(char * line);
- void comment_directive(char * line);
- void expr_directive(char * line);
- bool general_directive(char * line);
+ void value_directive(char * line);
+
+ void import_directive(char * line);
+ void python_directive(char * line);
post_t * parse_post(char * line,
std::streamsize len,
account_t * account,
xact_t * xact,
- bool defer_expr = false);
+ bool defer_expr = false);
bool parse_posts(account_t * account,
xact_base_t& xact,
@@ -209,43 +239,27 @@ namespace {
}
}
-instance_t::instance_t(parse_context_t& _context,
- std::istream& _in,
- const path * _original_file,
- instance_t * _parent)
- : context(_context), parent(_parent), original_file(_original_file),
- pathname(original_file ? *original_file : "/dev/stdin"), in(_in)
-{
- TRACE_CTOR(instance_t, "...");
- DEBUG("times.epoch", "Saving epoch " << epoch);
- prev_epoch = epoch; // declared in times.h
-}
-
-instance_t::~instance_t()
-{
- TRACE_DTOR(instance_t);
- epoch = prev_epoch;
- DEBUG("times.epoch", "Restored epoch to " << epoch);
-}
-
void instance_t::parse()
{
- INFO("Parsing file '" << pathname.string() << "'");
+ INFO("Parsing file " << context.pathname);
- TRACE_START(instance_parse, 1,
- "Done parsing file '" << pathname.string() << "'");
+ TRACE_START(instance_parse, 1, "Done parsing file " << context.pathname);
if (! in.good() || in.eof())
return;
- linenum = 0;
- curr_pos = in.tellg();
+ context.linenum = 0;
+ context.curr_pos = in.tellg();
+
+ bool error_flag = false;
while (in.good() && ! in.eof()) {
try {
- read_next_directive();
+ read_next_directive(error_flag);
}
catch (const std::exception& err) {
+ error_flag = true;
+
string current_context = error_context();
if (parent) {
@@ -258,11 +272,9 @@ void instance_t::parse()
foreach (instance_t * instance, instances)
add_error_context(_("In file included from %1")
- << file_context(instance->pathname,
- instance->linenum));
+ << instance->context.location());
}
- add_error_context(_("While parsing file %1")
- << file_context(pathname, linenum));
+ add_error_context(_("While parsing file %1") << context.location());
if (caught_signal != NONE_CAUGHT)
throw;
@@ -287,48 +299,55 @@ std::streamsize instance_t::read_line(char *& line)
assert(in.good());
assert(! in.eof()); // no one should call us in that case
- line_beg_pos = curr_pos;
+ context.line_beg_pos = context.curr_pos;
check_for_signal();
- in.getline(linebuf, MAX_LINE);
+ in.getline(context.linebuf, parse_context_t::MAX_LINE);
std::streamsize len = in.gcount();
if (len > 0) {
- if (linenum == 0 && utf8::is_bom(linebuf))
- line = &linebuf[3];
- else
- line = linebuf;
+ context.linenum++;
- if (line[len - 1] == '\r') // strip Windows CRLF down to LF
- line[--len] = '\0';
+ context.curr_pos = context.line_beg_pos;
+ context.curr_pos += len;
- linenum++;
+ if (context.linenum == 0 && utf8::is_bom(context.linebuf)) {
+ line = &context.linebuf[3];
+ len -= 3;
+ } else {
+ line = context.linebuf;
+ }
- curr_pos = line_beg_pos;
- curr_pos += len;
+ --len;
+ while (len > 0 && std::isspace(line[len - 1])) // strip trailing whitespace
+ line[--len] = '\0';
- return len - 1; // LF is being silently dropped
+ return len;
}
return 0;
}
-void instance_t::read_next_directive()
+void instance_t::read_next_directive(bool& error_flag)
{
char * line;
std::streamsize len = read_line(line);
if (len == 0 || line == NULL)
return;
+ if (! std::isspace(line[0]))
+ error_flag = false;
+
switch (line[0]) {
case '\0':
assert(false); // shouldn't ever reach here
break;
case ' ':
- case '\t': {
+ case '\t':
+ if (! error_flag)
+ throw parse_error(_("Unexpected whitespace at beginning of line"));
break;
- }
case ';': // comments
case '#':
@@ -402,7 +421,7 @@ void instance_t::read_next_directive()
price_xact_directive(line);
break;
case 'Y': // set the current year
- year_directive(line);
+ apply_year_directive(line);
break;
}
}
@@ -426,19 +445,19 @@ void instance_t::clock_in_directive(char * line, bool /*capitalized*/)
end = NULL;
position_t position;
- position.pathname = pathname;
- position.beg_pos = line_beg_pos;
- position.beg_line = linenum;
- position.end_pos = curr_pos;
- position.end_line = linenum;
+ position.pathname = context.pathname;
+ position.beg_pos = context.line_beg_pos;
+ position.beg_line = context.linenum;
+ position.end_pos = context.curr_pos;
+ position.end_line = context.linenum;
position.sequence = context.sequence++;
time_xact_t event(position, parse_datetime(datetime),
- p ? context.top_account()->find_account(p) : NULL,
+ p ? top_account()->find_account(p) : NULL,
n ? n : "",
end ? end : "");
- context.timelog.clock_in(event);
+ timelog.clock_in(event);
}
void instance_t::clock_out_directive(char * line, bool /*capitalized*/)
@@ -455,20 +474,19 @@ void instance_t::clock_out_directive(char * line, bool /*capitalized*/)
end = NULL;
position_t position;
- position.pathname = pathname;
- position.beg_pos = line_beg_pos;
- position.beg_line = linenum;
- position.end_pos = curr_pos;
- position.end_line = linenum;
+ position.pathname = context.pathname;
+ position.beg_pos = context.line_beg_pos;
+ position.beg_line = context.linenum;
+ position.end_pos = context.curr_pos;
+ position.end_line = context.linenum;
position.sequence = context.sequence++;
time_xact_t event(position, parse_datetime(datetime),
- p ? context.top_account()->find_account(p) : NULL,
+ p ? top_account()->find_account(p) : NULL,
n ? n : "",
end ? end : "");
- context.timelog.clock_out(event);
- context.count++;
+ context.count += timelog.clock_out(event);
}
#endif // TIMELOG_SUPPORT
@@ -483,8 +501,8 @@ void instance_t::default_commodity_directive(char * line)
void instance_t::default_account_directive(char * line)
{
- context.journal.bucket = context.top_account()->find_account(skip_ws(line + 1));
- context.journal.bucket->add_flags(ACCOUNT_KNOWN);
+ context.journal->bucket = top_account()->find_account(skip_ws(line + 1));
+ context.journal->bucket->add_flags(ACCOUNT_KNOWN);
}
void instance_t::price_conversion_directive(char * line)
@@ -514,16 +532,6 @@ void instance_t::nomarket_directive(char * line)
commodity->add_flags(COMMODITY_NOMARKET | COMMODITY_KNOWN);
}
-void instance_t::year_directive(char * line)
-{
- unsigned short year(lexical_cast<unsigned short>(skip_ws(line + 1)));
- DEBUG("times.epoch", "Setting current year to " << year);
- // This must be set to the last day of the year, otherwise partial
- // dates like "11/01" will refer to last year's november, not the
- // current year.
- epoch = datetime_t(date_t(year, 12, 31));
-}
-
void instance_t::option_directive(char * line)
{
char * p = next_element(line);
@@ -533,13 +541,15 @@ void instance_t::option_directive(char * line)
*p++ = '\0';
}
- if (! process_option(pathname.string(), line + 2, context.scope, p, line))
+ path abs_path(filesystem::absolute(context.pathname,
+ context.current_directory));
+ if (! process_option(abs_path.string(), line + 2, *context.scope, p, line))
throw_(option_error, _("Illegal option --%1") << line + 2);
}
void instance_t::automated_xact_directive(char * line)
{
- istream_pos_type pos= line_beg_pos;
+ istream_pos_type pos = context.line_beg_pos;
bool reveal_context = true;
@@ -550,18 +560,17 @@ void instance_t::automated_xact_directive(char * line)
query.parse_args(string_value(skip_ws(line + 1)).to_sequence(),
keeper, false, true);
- std::auto_ptr<auto_xact_t> ae(new auto_xact_t(predicate_t(expr, keeper)));
+ unique_ptr<auto_xact_t> ae(new auto_xact_t(predicate_t(expr, keeper)));
ae->pos = position_t();
- ae->pos->pathname = pathname;
- ae->pos->beg_pos = line_beg_pos;
- ae->pos->beg_line = linenum;
+ ae->pos->pathname = context.pathname;
+ ae->pos->beg_pos = context.line_beg_pos;
+ ae->pos->beg_line = context.linenum;
ae->pos->sequence = context.sequence++;
post_t * last_post = NULL;
while (peek_whitespace_line()) {
std::streamsize len = read_line(line);
-
char * p = skip_ws(line);
if (! *p)
break;
@@ -576,59 +585,56 @@ void instance_t::automated_xact_directive(char * line)
item = ae.get();
// This is a trailing note, and possibly a metadata info tag
- item->append_note(p + 1, context.scope, true);
- item->pos->end_pos = curr_pos;
+ item->append_note(p + 1, *context.scope, true);
+ item->add_flags(ITEM_NOTE_ON_NEXT_LINE);
+ item->pos->end_pos = context.curr_pos;
item->pos->end_line++;
-
- // If there was no last_post yet, then deferred notes get applied to
- // the matched posting. Other notes get applied to the auto-generated
- // posting.
- ae->deferred_notes->back().apply_to_post = last_post;
}
else if ((remlen > 7 && *p == 'a' &&
std::strncmp(p, "assert", 6) == 0 && std::isspace(p[6])) ||
(remlen > 6 && *p == 'c' &&
std::strncmp(p, "check", 5) == 0 && std::isspace(p[5])) ||
(remlen > 5 && *p == 'e' &&
- std::strncmp(p, "expr", 4) == 0 && std::isspace(p[4]))) {
+ ((std::strncmp(p, "expr", 4) == 0 && std::isspace(p[4])) ||
+ (std::strncmp(p, "eval", 4) == 0 && std::isspace(p[4]))))) {
const char c = *p;
p = skip_ws(&p[*p == 'a' ? 6 : (*p == 'c' ? 5 : 4)]);
if (! ae->check_exprs)
- ae->check_exprs = auto_xact_t::check_expr_list();
+ ae->check_exprs = expr_t::check_expr_list();
ae->check_exprs->push_back
- (auto_xact_t::check_expr_pair(expr_t(p),
- c == 'a' ?
- auto_xact_t::EXPR_ASSERTION :
- (c == 'c' ?
- auto_xact_t::EXPR_CHECK :
- auto_xact_t::EXPR_GENERAL)));
+ (expr_t::check_expr_pair(expr_t(p),
+ c == 'a' ?
+ expr_t::EXPR_ASSERTION :
+ (c == 'c' ?
+ expr_t::EXPR_CHECK :
+ expr_t::EXPR_GENERAL)));
}
else {
reveal_context = false;
if (post_t * post =
- parse_post(p, len - (p - line), context.top_account(),
- NULL, true)) {
+ parse_post(p, len - (p - line), top_account(), NULL, true)) {
reveal_context = true;
ae->add_post(post);
- last_post = post;
+ ae->active_post = last_post = post;
}
reveal_context = true;
}
}
- context.journal.auto_xacts.push_back(ae.get());
+ context.journal->auto_xacts.push_back(ae.get());
- ae->journal = &context.journal;
- ae->pos->end_pos = curr_pos;
- ae->pos->end_line = linenum;
+ ae->journal = context.journal;
+ ae->pos->end_pos = context.curr_pos;
+ ae->pos->end_line = context.linenum;
ae.release();
}
catch (const std::exception&) {
if (reveal_context) {
add_error_context(_("While parsing automated transaction:"));
- add_error_context(source_context(pathname, pos, curr_pos, "> "));
+ add_error_context(source_context(context.pathname, pos,
+ context.curr_pos, "> "));
}
throw;
}
@@ -636,31 +642,31 @@ void instance_t::automated_xact_directive(char * line)
void instance_t::period_xact_directive(char * line)
{
- istream_pos_type pos = line_beg_pos;
+ istream_pos_type pos = context.line_beg_pos;
bool reveal_context = true;
try {
- std::auto_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1)));
+ unique_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1)));
pe->pos = position_t();
- pe->pos->pathname = pathname;
- pe->pos->beg_pos = line_beg_pos;
- pe->pos->beg_line = linenum;
+ pe->pos->pathname = context.pathname;
+ pe->pos->beg_pos = context.line_beg_pos;
+ pe->pos->beg_line = context.linenum;
pe->pos->sequence = context.sequence++;
reveal_context = false;
- if (parse_posts(context.top_account(), *pe.get())) {
+ if (parse_posts(top_account(), *pe.get())) {
reveal_context = true;
- pe->journal = &context.journal;
+ pe->journal = context.journal;
if (pe->finalize()) {
- context.journal.extend_xact(pe.get());
- context.journal.period_xacts.push_back(pe.get());
+ context.journal->extend_xact(pe.get());
+ context.journal->period_xacts.push_back(pe.get());
- pe->pos->end_pos = curr_pos;
- pe->pos->end_line = linenum;
+ pe->pos->end_pos = context.curr_pos;
+ pe->pos->end_line = context.linenum;
pe.release();
} else {
@@ -674,7 +680,8 @@ void instance_t::period_xact_directive(char * line)
catch (const std::exception&) {
if (reveal_context) {
add_error_context(_("While parsing periodic transaction:"));
- add_error_context(source_context(pathname, pos, curr_pos, "> "));
+ add_error_context(source_context(context.pathname, pos,
+ context.curr_pos, "> "));
}
throw;
}
@@ -684,10 +691,10 @@ void instance_t::xact_directive(char * line, std::streamsize len)
{
TRACE_START(xacts, 1, "Time spent handling transactions:");
- if (xact_t * xact = parse_xact(line, len, context.top_account())) {
- std::auto_ptr<xact_t> manager(xact);
+ if (xact_t * xact = parse_xact(line, len, top_account())) {
+ unique_ptr<xact_t> manager(xact);
- if (context.journal.add_xact(xact)) {
+ if (context.journal->add_xact(xact)) {
manager.release(); // it's owned by the journal now
context.count++;
}
@@ -709,12 +716,13 @@ void instance_t::include_directive(char * line)
if (line[0] != '/' && line[0] != '\\' && line[0] != '~') {
DEBUG("textual.include", "received a relative path");
- DEBUG("textual.include", "parent file path: " << pathname.string());
- string::size_type pos = pathname.string().rfind('/');
+ DEBUG("textual.include", "parent file path: " << context.pathname);
+ string pathstr(context.pathname.string());
+ string::size_type pos = pathstr.rfind('/');
if (pos == string::npos)
- pos = pathname.string().rfind('\\');
+ pos = pathstr.rfind('\\');
if (pos != string::npos) {
- filename = path(string(pathname.string(), 0, pos + 1)) / line;
+ filename = path(string(pathstr, 0, pos + 1)) / line;
DEBUG("textual.include", "normalized path: " << filename.string());
} else {
filename = path(string(".")) / line;
@@ -761,10 +769,42 @@ void instance_t::include_directive(char * line)
string base = (*iter).leaf();
#endif // BOOST_VERSION >= 103700
if (glob.match(base)) {
- path inner_file(*iter);
- ifstream stream(inner_file);
- instance_t instance(context, stream, &inner_file, this);
- instance.parse();
+ journal_t * journal = context.journal;
+ account_t * master = top_account();
+ scope_t * scope = context.scope;
+ std::size_t& errors = context.errors;
+ std::size_t& count = context.count;
+ std::size_t& sequence = context.sequence;
+
+ DEBUG("textual.include", "Including: " << *iter);
+ DEBUG("textual.include", "Master account: " << master->fullname());
+
+ context_stack.push(*iter);
+
+ context_stack.get_current().journal = journal;
+ context_stack.get_current().master = master;
+ context_stack.get_current().scope = scope;
+ try {
+ instance_t instance(context_stack,
+ context_stack.get_current(), this);
+ instance.apply_stack.push_front(application_t("account", master));
+ instance.parse();
+ }
+ catch (...) {
+ errors += context_stack.get_current().errors;
+ count += context_stack.get_current().count;
+ sequence += context_stack.get_current().sequence;
+
+ context_stack.pop();
+ throw;
+ }
+
+ errors += context_stack.get_current().errors;
+ count += context_stack.get_current().count;
+ sequence += context_stack.get_current().sequence;
+
+ context_stack.pop();
+
files_found = true;
}
}
@@ -773,156 +813,348 @@ void instance_t::include_directive(char * line)
if (! files_found)
throw_(std::runtime_error,
- _("File to include was not found: '%1'") << filename);
+ _("File to include was not found: %1") << filename);
}
-void instance_t::master_account_directive(char * line)
+void instance_t::apply_directive(char * line)
+{
+ char * b = next_element(line);
+ string keyword(line);
+ if (keyword == "account")
+ apply_account_directive(b);
+ else if (keyword == "tag")
+ apply_tag_directive(b);
+ else if (keyword == "fixed" || keyword == "rate")
+ apply_rate_directive(b);
+ else if (keyword == "year")
+ apply_year_directive(b);
+}
+
+void instance_t::apply_account_directive(char * line)
{
- if (account_t * acct = context.top_account()->find_account(line))
- context.state_stack.push_front(acct);
+ if (account_t * acct = top_account()->find_account(line))
+ apply_stack.push_front(application_t("account", acct));
#if !defined(NO_ASSERTS)
else
assert("Failed to create account" == NULL);
#endif
}
-void instance_t::end_directive(char * kind)
+void instance_t::apply_tag_directive(char * line)
{
- string name(kind ? kind : "");
+ string tag(trim_ws(line));
- if ((name.empty() || name == "account") && ! context.front_is_account())
- throw_(std::runtime_error,
- _("'end account' directive does not match open directive"));
- else if (name == "tag" && ! context.front_is_string())
- throw_(std::runtime_error,
- _("'end tag' directive does not match open directive"));
- else if (name == "fixed" && ! context.front_is_fixed_rate())
- throw_(std::runtime_error,
- _("'end fixed' directive does not match open directive"));
+ if (tag.find(':') == string::npos)
+ tag = string(":") + tag + ":";
- if (context.state_stack.size() <= 1)
+ apply_stack.push_front(application_t("tag", tag));
+}
+
+void instance_t::apply_rate_directive(char * line)
+{
+ if (optional<std::pair<commodity_t *, price_point_t> > price_point =
+ commodity_pool_t::current_pool->parse_price_directive(trim_ws(line), true)) {
+ apply_stack.push_front
+ (application_t("fixed", fixed_rate_t(price_point->first,
+ price_point->second.price)));
+ } else {
+ throw_(std::runtime_error, _("Error in fixed directive"));
+ }
+}
+
+void instance_t::apply_year_directive(char * line)
+{
+ apply_stack.push_front(application_t("year", epoch));
+
+ // This must be set to the last day of the year, otherwise partial
+ // dates like "11/01" will refer to last year's november, not the
+ // current year.
+ unsigned short year(lexical_cast<unsigned short>(skip_ws(line + 1)));
+ DEBUG("times.epoch", "Setting current year to " << year);
+ epoch = datetime_t(date_t(year, 12, 31));
+}
+
+void instance_t::end_apply_directive(char * kind)
+{
+ char * b = kind ? next_element(kind) : NULL;
+ string name(b ? b : "");
+
+ if (apply_stack.size() <= 1) {
+ if (name.empty()) {
+ throw_(std::runtime_error,
+ _("'end' or 'end apply' found, but no enclosing 'apply' directive"));
+ } else {
+ throw_(std::runtime_error,
+ _("'end apply %1' found, but no enclosing 'apply' directive")
+ << name);
+ }
+ }
+
+ if (! name.empty() && name != apply_stack.front().label)
throw_(std::runtime_error,
- _("'end' found, but no enclosing tag or account directive"));
- else
- context.state_stack.pop_front();
+ _("'end apply %1' directive does not match 'apply %2' directive")
+ << name << apply_stack.front().label);
+
+ if (apply_stack.front().value.type() == typeid(optional<datetime_t>))
+ epoch = boost::get<optional<datetime_t> >(apply_stack.front().value);
+
+ apply_stack.pop_front();
+}
+
+void instance_t::account_directive(char * line)
+{
+ istream_pos_type beg_pos = context.line_beg_pos;
+ std::size_t beg_linenum = context.linenum;
+
+ char * p = skip_ws(line);
+ account_t * account =
+ context.journal->register_account(p, NULL, top_account());
+ unique_ptr<auto_xact_t> ae;
+
+ while (peek_whitespace_line()) {
+ read_line(line);
+ char * q = skip_ws(line);
+ if (! *q)
+ break;
+
+ char * b = next_element(q);
+ string keyword(q);
+ if (keyword == "alias") {
+ account_alias_directive(account, b);
+ }
+ else if (keyword == "payee") {
+ account_payee_directive(account, b);
+ }
+ else if (keyword == "value") {
+ account_value_directive(account, b);
+ }
+ else if (keyword == "default") {
+ account_default_directive(account);
+ }
+ else if (keyword == "assert" || keyword == "check") {
+ keep_details_t keeper(true, true, true);
+ expr_t expr(string("account == \"") + account->fullname() + "\"");
+ predicate_t pred(expr.get_op(), keeper);
+
+ if (! ae.get()) {
+ ae.reset(new auto_xact_t(pred));
+
+ ae->pos = position_t();
+ ae->pos->pathname = context.pathname;
+ ae->pos->beg_pos = beg_pos;
+ ae->pos->beg_line = beg_linenum;
+ ae->pos->sequence = context.sequence++;
+ ae->check_exprs = expr_t::check_expr_list();
+ }
+
+ ae->check_exprs->push_back
+ (expr_t::check_expr_pair(expr_t(b),
+ keyword == "assert" ?
+ expr_t::EXPR_ASSERTION :
+ expr_t::EXPR_CHECK));
+ }
+ else if (keyword == "eval" || keyword == "expr") {
+ // jww (2012-02-27): Make account into symbol scopes so that this
+ // can be used to override definitions within the account.
+ bind_scope_t bound_scope(*context.scope, *account);
+ expr_t(b).calc(bound_scope);
+ }
+ else if (keyword == "note") {
+ account->note = b;
+ }
+ }
+
+ if (ae.get()) {
+ context.journal->auto_xacts.push_back(ae.get());
+
+ ae->journal = context.journal;
+ ae->pos->end_pos = in.tellg();
+ ae->pos->end_line = context.linenum;
+
+ ae.release();
+ }
+}
+
+void instance_t::account_alias_directive(account_t * account, string alias)
+{
+ // Once we have an alias name (alias) and the target account
+ // (account), add a reference to the account in the `account_aliases'
+ // map, which is used by the post parser to resolve alias references.
+ trim(alias);
+ std::pair<accounts_map::iterator, bool> result =
+ context.journal->account_aliases.insert
+ (accounts_map::value_type(alias, account));
+ if (! result.second)
+ (*result.first).second = account;
}
void instance_t::alias_directive(char * line)
{
- char * b = skip_ws(line);
- if (char * e = std::strchr(b, '=')) {
+ if (char * e = std::strchr(line, '=')) {
char * z = e - 1;
while (std::isspace(*z))
*z-- = '\0';
*e++ = '\0';
e = skip_ws(e);
- // Once we have an alias name (b) and the target account
- // name (e), add a reference to the account in the
- // `account_aliases' map, which is used by the post
- // parser to resolve alias references.
- account_t * acct = context.top_account()->find_account(e);
- std::pair<accounts_map::iterator, bool> result
- = account_aliases.insert(accounts_map::value_type(b, acct));
- assert(result.second);
+ account_alias_directive(top_account()->find_account(e), line);
}
}
-void instance_t::fixed_directive(char * line)
+void instance_t::account_payee_directive(account_t * account, string payee)
{
- if (optional<std::pair<commodity_t *, price_point_t> > price_point =
- commodity_pool_t::current_pool->parse_price_directive(trim_ws(line),
- true)) {
- context.state_stack.push_front(fixed_rate_t(price_point->first,
- price_point->second.price));
- } else {
- throw_(std::runtime_error, _("Error in fixed directive"));
- }
+ trim(payee);
+ context.journal->payees_for_unknown_accounts
+ .push_back(account_mapping_t(mask_t(payee), account));
}
-void instance_t::payee_mapping_directive(char * line)
+void instance_t::account_default_directive(account_t * account)
{
- char * payee = skip_ws(line);
- char * regex = next_element(payee, true);
+ context.journal->bucket = account;
+}
- if (regex)
- context.journal.payee_mappings.push_back
- (payee_mapping_t(mask_t(regex), payee));
+void instance_t::account_value_directive(account_t * account, string expr_str)
+{
+ account->value_expr = expr_t(expr_str);
+}
+
+void instance_t::payee_directive(char * line)
+{
+ string payee = context.journal->register_payee(line, NULL);
while (peek_whitespace_line()) {
-#if defined(NO_ASSERTS)
read_line(line);
-#else
- std::streamsize len = read_line(line);
- assert(len > 0);
-#endif
-
- regex = skip_ws(line);
- if (! *regex)
+ char * p = skip_ws(line);
+ if (! *p)
break;
- context.journal.payee_mappings.push_back
- (payee_mapping_t(mask_t(regex), payee));
+ char * b = next_element(p);
+ string keyword(p);
+ if (keyword == "alias")
+ payee_alias_directive(payee, b);
}
}
-void instance_t::account_mapping_directive(char * line)
+void instance_t::payee_alias_directive(const string& payee, string alias)
{
- char * account_name = skip_ws(line);
- char * payee_regex = next_element(account_name, true);
+ trim(alias);
+ context.journal->payee_mappings
+ .push_back(payee_mapping_t(mask_t(alias), payee));
+}
- if (payee_regex)
- context.journal.account_mappings.push_back
- (account_mapping_t(mask_t(payee_regex),
- context.top_account()->find_account(account_name)));
+void instance_t::commodity_directive(char * line)
+{
+ char * p = skip_ws(line);
+ string symbol;
+ commodity_t::parse_symbol(p, symbol);
- while (peek_whitespace_line()) {
-#if defined(NO_ASSERTS)
- read_line(line);
-#else
- std::streamsize len = read_line(line);
- assert(len > 0);
-#endif
+ if (commodity_t * commodity =
+ commodity_pool_t::current_pool->find_or_create(symbol)) {
+ context.journal->register_commodity(*commodity, 0);
- payee_regex = skip_ws(line);
- if (! *payee_regex)
- break;
+ while (peek_whitespace_line()) {
+ read_line(line);
+ char * q = skip_ws(line);
+ if (! *q)
+ break;
- context.journal.account_mappings.push_back
- (account_mapping_t(mask_t(payee_regex),
- context.top_account()->find_account(account_name)));
+ char * b = next_element(q);
+ string keyword(q);
+ if (keyword == "alias")
+ commodity_alias_directive(*commodity, b);
+ else if (keyword == "value")
+ commodity_value_directive(*commodity, b);
+ else if (keyword == "format")
+ commodity_format_directive(*commodity, b);
+ else if (keyword == "nomarket")
+ commodity_nomarket_directive(*commodity);
+ else if (keyword == "default")
+ commodity_default_directive(*commodity);
+ else if (keyword == "note")
+ commodity->set_note(string(b));
+ }
}
}
+void instance_t::commodity_alias_directive(commodity_t& comm, string alias)
+{
+ trim(alias);
+ commodity_pool_t::current_pool->alias(alias, comm);
+}
+
+void instance_t::commodity_value_directive(commodity_t& comm, string expr_str)
+{
+ comm.set_value_expr(expr_t(expr_str));
+}
+
+void instance_t::commodity_format_directive(commodity_t&, string format)
+{
+ // jww (2012-02-27): A format specified this way should turn off
+ // observational formatting.
+ trim(format);
+ amount_t amt;
+ amt.parse(format);
+ VERIFY(amt.valid());
+}
+
+void instance_t::commodity_nomarket_directive(commodity_t& comm)
+{
+ comm.add_flags(COMMODITY_NOMARKET);
+}
+
+void instance_t::commodity_default_directive(commodity_t& comm)
+{
+ commodity_pool_t::current_pool->default_commodity = &comm;
+}
+
void instance_t::tag_directive(char * line)
{
- string tag(trim_ws(line));
+ char * p = skip_ws(line);
+ context.journal->register_metadata(p, NULL_VALUE, 0);
- if (tag.find(':') == string::npos)
- tag = string(":") + tag + ":";
+ while (peek_whitespace_line()) {
+ read_line(line);
+ char * q = skip_ws(line);
+ if (! *q)
+ break;
- context.state_stack.push_front(tag);
+ char * b = next_element(q);
+ string keyword(q);
+ if (keyword == "assert" || keyword == "check") {
+ context.journal->tag_check_exprs.insert
+ (tag_check_exprs_map::value_type
+ (string(p), expr_t::check_expr_pair(expr_t(b),
+ keyword == "assert" ?
+ expr_t::EXPR_ASSERTION :
+ expr_t::EXPR_CHECK)));
+ }
+ }
}
-void instance_t::define_directive(char * line)
+void instance_t::eval_directive(char * line)
{
- expr_t def(skip_ws(line));
- def.compile(context.scope); // causes definitions to be established
+ expr_t expr(line);
+ expr.calc(*context.scope);
}
void instance_t::assert_directive(char * line)
{
expr_t expr(line);
- if (! expr.calc(context.scope).to_boolean())
+ if (! expr.calc(*context.scope).to_boolean())
throw_(parse_error, _("Assertion failed: %1") << line);
}
void instance_t::check_directive(char * line)
{
expr_t expr(line);
- if (! expr.calc(context.scope).to_boolean())
- warning_(_("Check failed: %1") << line);
+ if (! expr.calc(*context.scope).to_boolean())
+ context.warning(STR(_("Check failed: %1") << line));
+}
+
+void instance_t::value_directive(char * line)
+{
+ context.journal->value_expr = expr_t(line);
}
void instance_t::comment_directive(char * line)
@@ -936,12 +1168,71 @@ void instance_t::comment_directive(char * line)
}
}
-void instance_t::expr_directive(char * line)
+#if defined(HAVE_BOOST_PYTHON)
+
+void instance_t::import_directive(char * line)
{
- expr_t expr(line);
- expr.calc(context.scope);
+ string module_name(line);
+ trim(module_name);
+ python_session->import_option(module_name);
+}
+
+void instance_t::python_directive(char * line)
+{
+ std::ostringstream script;
+
+ if (line)
+ script << skip_ws(line) << '\n';
+
+ std::size_t indent = 0;
+
+ while (peek_whitespace_line() || peek_blank_line()) {
+ if (read_line(line) > 0) {
+ if (! indent) {
+ const char * p = line;
+ while (*p && std::isspace(*p)) {
+ ++indent;
+ ++p;
+ }
+ }
+
+ const char * p = line;
+ for (std::size_t i = 0; i < indent; i++) {
+ if (std::isspace(*p))
+ ++p;
+ else
+ break;
+ }
+
+ if (*p)
+ script << p << '\n';
+ }
+ }
+
+ if (! python_session->is_initialized)
+ python_session->initialize();
+
+ python_session->main_module->define_global
+ ("journal", python::object(python::ptr(context.journal)));
+ python_session->eval(script.str(), python_interpreter_t::PY_EVAL_MULTI);
+}
+
+#else
+
+void instance_t::import_directive(char *)
+{
+ throw_(parse_error,
+ _("'python' directive seen, but Python support is missing"));
+}
+
+void instance_t::python_directive(char *)
+{
+ throw_(parse_error,
+ _("'import' directive seen, but Python support is missing"));
}
+#endif // HAVE_BOOST_PYTHON
+
bool instance_t::general_directive(char * line)
{
char buf[8192];
@@ -957,13 +1248,17 @@ bool instance_t::general_directive(char * line)
switch (*p) {
case 'a':
if (std::strcmp(p, "account") == 0) {
- master_account_directive(arg);
+ account_directive(arg);
return true;
}
else if (std::strcmp(p, "alias") == 0) {
alias_directive(arg);
return true;
}
+ else if (std::strcmp(p, "apply") == 0) {
+ apply_directive(arg);
+ return true;
+ }
else if (std::strcmp(p, "assert") == 0) {
assert_directive(arg);
return true;
@@ -978,11 +1273,7 @@ bool instance_t::general_directive(char * line)
break;
case 'c':
- if (std::strcmp(p, "capture") == 0) {
- account_mapping_directive(arg);
- return true;
- }
- else if (std::strcmp(p, "check") == 0) {
+ if (std::strcmp(p, "check") == 0) {
check_directive(arg);
return true;
}
@@ -990,29 +1281,26 @@ bool instance_t::general_directive(char * line)
comment_directive(arg);
return true;
}
+ else if (std::strcmp(p, "commodity") == 0) {
+ commodity_directive(arg);
+ return true;
+ }
break;
case 'd':
if (std::strcmp(p, "def") == 0 || std::strcmp(p, "define") == 0) {
- define_directive(arg);
+ eval_directive(arg);
return true;
}
break;
case 'e':
if (std::strcmp(p, "end") == 0) {
- end_directive(arg);
+ end_apply_directive(arg);
return true;
}
- else if (std::strcmp(p, "expr") == 0) {
- expr_directive(arg);
- return true;
- }
- break;
-
- case 'f':
- if (std::strcmp(p, "fixed") == 0) {
- fixed_directive(arg);
+ else if (std::strcmp(p, "expr") == 0 || std::strcmp(p, "eval") == 0) {
+ eval_directive(arg);
return true;
}
break;
@@ -1022,11 +1310,19 @@ bool instance_t::general_directive(char * line)
include_directive(arg);
return true;
}
+ else if (std::strcmp(p, "import") == 0) {
+ import_directive(arg);
+ return true;
+ }
break;
case 'p':
if (std::strcmp(p, "payee") == 0) {
- payee_mapping_directive(arg);
+ payee_directive(arg);
+ return true;
+ }
+ else if (std::strcmp(p, "python") == 0) {
+ python_directive(arg);
return true;
}
break;
@@ -1042,9 +1338,9 @@ bool instance_t::general_directive(char * line)
}
break;
- case 'y':
- if (std::strcmp(p, "year") == 0) {
- year_directive(arg);
+ case 'v':
+ if (std::strcmp(p, "value") == 0) {
+ value_directive(arg);
return true;
}
break;
@@ -1068,16 +1364,16 @@ post_t * instance_t::parse_post(char * line,
{
TRACE_START(post_details, 1, "Time spent parsing postings:");
- std::auto_ptr<post_t> post(new post_t);
+ unique_ptr<post_t> post(new post_t);
post->xact = xact; // this could be NULL
post->pos = position_t();
- post->pos->pathname = pathname;
- post->pos->beg_pos = line_beg_pos;
- post->pos->beg_line = linenum;
+ post->pos->pathname = context.pathname;
+ post->pos->beg_pos = context.line_beg_pos;
+ post->pos->beg_line = context.linenum;
post->pos->sequence = context.sequence++;
- char buf[MAX_LINE + 1];
+ char buf[parse_context_t::MAX_LINE + 1];
std::strcpy(buf, line);
std::streamsize beg = 0;
@@ -1094,14 +1390,14 @@ post_t * instance_t::parse_post(char * line,
case '*':
post->set_state(item_t::CLEARED);
p = skip_ws(p + 1);
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Parsed the CLEARED flag");
break;
case '!':
post->set_state(item_t::PENDING);
p = skip_ws(p + 1);
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Parsed the PENDING flag");
break;
}
@@ -1124,44 +1420,23 @@ post_t * instance_t::parse_post(char * line,
if ((*p == '[' && *(e - 1) == ']') || (*p == '(' && *(e - 1) == ')')) {
post->add_flags(POST_VIRTUAL);
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Parsed a virtual account name");
if (*p == '[') {
post->add_flags(POST_MUST_BALANCE);
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Posting must balance");
}
p++; e--;
}
- string name(p, static_cast<std::string::size_type>(e - p));
- DEBUG("textual.parse", "line " << linenum << ": "
+ string name(p, static_cast<string::size_type>(e - p));
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Parsed account name " << name);
- if (account_aliases.size() > 0) {
- accounts_map::const_iterator i = account_aliases.find(name);
- if (i != account_aliases.end())
- post->account = (*i).second;
- }
- if (! post->account)
- post->account = account->find_account(name);
-
- if (context.strict && ! post->account->has_flags(ACCOUNT_KNOWN)) {
- if (post->_state == item_t::UNCLEARED)
- warning_(_("\"%1\", line %2: Unknown account '%3'")
- << pathname.string() << linenum << post->account->fullname());
- post->account->add_flags(ACCOUNT_KNOWN);
- }
-
- if (post->account->name == _("Unknown")) {
- foreach (account_mapping_t& value, context.journal.account_mappings) {
- if (value.first.match(xact->payee)) {
- post->account = value.second;
- break;
- }
- }
- }
+ post->account =
+ context.journal->register_account(name, post.get(), account);
// Parse the optional amount
@@ -1172,35 +1447,28 @@ post_t * instance_t::parse_post(char * line,
if (*next != '(') // indicates a value expression
post->amount.parse(stream, PARSE_NO_REDUCE);
else
- parse_amount_expr(stream, context.scope, *post.get(), post->amount,
+ parse_amount_expr(stream, *context.scope, *post.get(), post->amount,
PARSE_NO_REDUCE | PARSE_SINGLE | PARSE_NO_ASSIGN,
defer_expr, &post->amount_expr);
if (! post->amount.is_null() && post->amount.has_commodity()) {
- if (context.strict &&
- ! post->amount.commodity().has_flags(COMMODITY_KNOWN)) {
- if (post->_state == item_t::UNCLEARED)
- warning_(_("\"%1\", line %2: Unknown commodity '%3'")
- << pathname.string() << linenum << post->amount.commodity());
- post->amount.commodity().add_flags(COMMODITY_KNOWN);
- }
+ context.journal->register_commodity(post->amount.commodity(), post.get());
if (! post->amount.has_annotation()) {
- foreach (state_t& state, context.state_stack) {
- if (state.type() == typeid(fixed_rate_t)) {
- fixed_rate_t& rate(boost::get<fixed_rate_t>(state));
- if (*rate.first == post->amount.commodity()) {
- annotation_t details(rate.second);
- details.add_flags(ANNOTATION_PRICE_FIXATED);
- post->amount.annotate(details);
- break;
- }
+ std::vector<fixed_rate_t> rates;
+ get_applications<fixed_rate_t>(rates);
+ foreach (fixed_rate_t& rate, rates) {
+ if (*rate.first == post->amount.commodity()) {
+ annotation_t details(rate.second);
+ details.add_flags(ANNOTATION_PRICE_FIXATED);
+ post->amount.annotate(details);
+ break;
}
}
}
}
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "post amount = " << post->amount);
if (stream.eof()) {
@@ -1210,19 +1478,26 @@ post_t * instance_t::parse_post(char * line,
// Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST)
- if (*next == '@') {
- DEBUG("textual.parse", "line " << linenum << ": "
+ if (*next == '@' || (*next == '(' && *(next + 1) == '@')) {
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Found a price indicator");
- bool per_unit = true;
+ if (*next == '(') {
+ post->add_flags(POST_COST_VIRTUAL);
+ ++next;
+ }
+ bool per_unit = true;
if (*++next == '@') {
per_unit = false;
post->add_flags(POST_COST_IN_FULL);
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "And it's for a total price");
}
+ if (post->has_flags(POST_COST_VIRTUAL) && *(next + 1) == ')')
+ ++next;
+
beg = static_cast<std::streamsize>(++next - line);
p = skip_ws(next);
@@ -1243,7 +1518,7 @@ post_t * instance_t::parse_post(char * line,
if (*p != '(') // indicates a value expression
post->cost->parse(cstream, PARSE_NO_MIGRATE);
else
- parse_amount_expr(cstream, context.scope, *post.get(), *post->cost,
+ parse_amount_expr(cstream, *context.scope, *post.get(), *post->cost,
PARSE_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_ASSIGN);
if (post->cost->sign() < 0)
@@ -1266,9 +1541,9 @@ post_t * instance_t::parse_post(char * line,
if (fixed_cost)
post->add_flags(POST_COST_FIXATED);
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Total cost is " << *post->cost);
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Annotated amount is " << post->amount);
if (cstream.eof())
@@ -1285,7 +1560,7 @@ post_t * instance_t::parse_post(char * line,
// Parse the optional balance assignment
if (xact && next && *next == '=') {
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Found a balance assignment indicator");
beg = static_cast<std::streamsize>(++next - line);
@@ -1300,7 +1575,7 @@ post_t * instance_t::parse_post(char * line,
if (*p != '(') // indicates a value expression
post->assigned_amount->parse(stream, PARSE_NO_MIGRATE);
else
- parse_amount_expr(stream, context.scope, *post.get(),
+ parse_amount_expr(stream, *context.scope, *post.get(),
*post->assigned_amount,
PARSE_SINGLE | PARSE_NO_MIGRATE);
@@ -1311,17 +1586,17 @@ post_t * instance_t::parse_post(char * line,
throw parse_error(_("Balance assertion must evaluate to a constant"));
}
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "POST assign: parsed amt = " << *post->assigned_amount);
amount_t& amt(*post->assigned_amount);
value_t account_total
(post->account->amount().strip_annotations(keep_details_t()));
+ DEBUG("post.assign", "line " << context.linenum << ": "
+ << "account balance = " << account_total);
DEBUG("post.assign",
- "line " << linenum << ": " "account balance = " << account_total);
- DEBUG("post.assign",
- "line " << linenum << ": " "post amount = " << amt);
+ "line " << context.linenum << ": " << "post amount = " << amt);
amount_t diff = amt;
@@ -1341,9 +1616,9 @@ post_t * instance_t::parse_post(char * line,
}
DEBUG("post.assign",
- "line " << linenum << ": " << "diff = " << diff);
- DEBUG("textual.parse",
- "line " << linenum << ": " << "POST assign: diff = " << diff);
+ "line " << context.linenum << ": " << "diff = " << diff);
+ DEBUG("textual.parse", "line " << context.linenum << ": "
+ << "POST assign: diff = " << diff);
if (! diff.is_zero()) {
if (! post->amount.is_null()) {
@@ -1352,7 +1627,7 @@ post_t * instance_t::parse_post(char * line,
throw_(parse_error, _("Balance assertion off by %1") << diff);
} else {
post->amount = diff;
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Overwrite null posting");
}
}
@@ -1369,9 +1644,9 @@ post_t * instance_t::parse_post(char * line,
// Parse the optional note
if (next && *next == ';') {
- post->append_note(++next, context.scope, true);
+ post->append_note(++next, *context.scope, true);
next = line + len;
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Parsed a posting note");
}
@@ -1382,14 +1657,13 @@ post_t * instance_t::parse_post(char * line,
_("Unexpected char '%1' (Note: inline math requires parentheses)")
<< *next);
- post->pos->end_pos = curr_pos;
- post->pos->end_line = linenum;
+ post->pos->end_pos = context.curr_pos;
+ post->pos->end_line = context.linenum;
- if (! context.state_stack.empty()) {
- foreach (const state_t& state, context.state_stack)
- if (state.type() == typeid(string))
- post->parse_tags(boost::get<string>(state).c_str(), context.scope, true);
- }
+ std::vector<string> tags;
+ get_applications<string>(tags);
+ foreach (string& tag, tags)
+ post->parse_tags(tag.c_str(), *context.scope, true);
TRACE_STOP(post_details, 1);
@@ -1398,8 +1672,8 @@ post_t * instance_t::parse_post(char * line,
}
catch (const std::exception&) {
add_error_context(_("While parsing posting:"));
- add_error_context(line_context(buf, static_cast<std::string::size_type>(beg),
- static_cast<std::string::size_type>(len)));
+ add_error_context(line_context(buf, static_cast<string::size_type>(beg),
+ static_cast<string::size_type>(len)));
throw;
}
}
@@ -1415,11 +1689,12 @@ bool instance_t::parse_posts(account_t * account,
while (peek_whitespace_line()) {
char * line;
std::streamsize len = read_line(line);
- assert(len > 0);
-
- if (post_t * post = parse_post(line, len, account, NULL, defer_expr)) {
- xact.add_post(post);
- added = true;
+ char * p = skip_ws(line);
+ if (*p != ';') {
+ if (post_t * post = parse_post(line, len, account, NULL, defer_expr)) {
+ xact.add_post(post);
+ added = true;
+ }
}
}
@@ -1437,9 +1712,9 @@ xact_t * instance_t::parse_xact(char * line,
unique_ptr<xact_t> xact(new xact_t);
xact->pos = position_t();
- xact->pos->pathname = pathname;
- xact->pos->beg_pos = line_beg_pos;
- xact->pos->beg_line = linenum;
+ xact->pos->pathname = context.pathname;
+ xact->pos->beg_pos = context.line_beg_pos;
+ xact->pos->beg_line = context.linenum;
xact->pos->sequence = context.sequence++;
bool reveal_context = true;
@@ -1452,7 +1727,7 @@ xact_t * instance_t::parse_xact(char * line,
if (char * p = std::strchr(line, '=')) {
*p++ = '\0';
- xact->_date_eff = parse_date(p);
+ xact->_date_aux = parse_date(p);
}
xact->_date = parse_date(line);
@@ -1484,15 +1759,31 @@ xact_t * instance_t::parse_xact(char * line,
// Parse the description text
if (next && *next) {
- char * p = next_element(next, true);
- foreach (payee_mapping_t& value, context.journal.payee_mappings) {
- if (value.first.match(next)) {
- xact->payee = value.second;
+ char * p = next;
+ std::size_t spaces = 0;
+ std::size_t tabs = 0;
+ while (*p) {
+ if (*p == ' ') {
+ ++spaces;
+ }
+ else if (*p == '\t') {
+ ++tabs;
+ }
+ else if (*p == ';' && (tabs > 0 || spaces > 1)) {
+ char *q = p - 1;
+ while (q > next && std::isspace(*q))
+ --q;
+ if (q > next)
+ *(q + 1) = '\0';
break;
}
+ else {
+ spaces = 0;
+ tabs = 0;
+ }
+ ++p;
}
- if (xact->payee.empty())
- xact->payee = next;
+ xact->payee = context.journal->register_payee(next, xact.get());
next = p;
} else {
xact->payee = _("<Unspecified payee>");
@@ -1501,7 +1792,7 @@ xact_t * instance_t::parse_xact(char * line,
// Parse the xact note
if (next && *next == ';')
- xact->append_note(++next, context.scope, false);
+ xact->append_note(++next, *context.scope, false);
TRACE_STOP(xact_text, 1);
@@ -1513,7 +1804,6 @@ xact_t * instance_t::parse_xact(char * line,
while (peek_whitespace_line()) {
len = read_line(line);
-
char * p = skip_ws(line);
if (! *p)
break;
@@ -1528,8 +1818,9 @@ xact_t * instance_t::parse_xact(char * line,
if (*p == ';') {
// This is a trailing note, and possibly a metadata info tag
- item->append_note(p + 1, context.scope, true);
- item->pos->end_pos = curr_pos;
+ item->append_note(p + 1, *context.scope, true);
+ item->add_flags(ITEM_NOTE_ON_NEXT_LINE);
+ item->pos->end_pos = context.curr_pos;
item->pos->end_line++;
}
else if ((remlen > 7 && *p == 'a' &&
@@ -1541,7 +1832,7 @@ xact_t * instance_t::parse_xact(char * line,
const char c = *p;
p = skip_ws(&p[*p == 'a' ? 6 : (*p == 'c' ? 5 : 4)]);
expr_t expr(p);
- bind_scope_t bound_scope(context.scope, *item);
+ bind_scope_t bound_scope(*context.scope, *item);
if (c == 'e') {
expr.calc(bound_scope);
}
@@ -1549,7 +1840,7 @@ xact_t * instance_t::parse_xact(char * line,
if (c == 'a') {
throw_(parse_error, _("Transaction assertion failed: %1") << p);
} else {
- warning_(_("Transaction check failed: %1") << p);
+ context.warning(STR(_("Transaction check failed: %1") << p));
}
}
}
@@ -1568,7 +1859,7 @@ xact_t * instance_t::parse_xact(char * line,
#if 0
if (xact->_state == item_t::UNCLEARED) {
- item_t::state_t result = item_t::CLEARED;
+ item_t::application_t result = item_t::CLEARED;
foreach (post_t * post, xact->posts) {
if (post->_state == item_t::UNCLEARED) {
@@ -1582,15 +1873,13 @@ xact_t * instance_t::parse_xact(char * line,
}
#endif
- xact->pos->end_pos = curr_pos;
- xact->pos->end_line = linenum;
+ xact->pos->end_pos = context.curr_pos;
+ xact->pos->end_line = context.linenum;
- if (! context.state_stack.empty()) {
- foreach (const state_t& state, context.state_stack)
- if (state.type() == typeid(string))
- xact->parse_tags(boost::get<string>(state).c_str(), context.scope,
- false);
- }
+ std::vector<string> tags;
+ get_applications<string>(tags);
+ foreach (string& tag, tags)
+ xact->parse_tags(tag.c_str(), *context.scope, false);
TRACE_STOP(xact_details, 1);
@@ -1601,7 +1890,8 @@ xact_t * instance_t::parse_xact(char * line,
if (reveal_context) {
add_error_context(_("While parsing transaction:"));
add_error_context(source_context(xact->pos->pathname,
- xact->pos->beg_pos, curr_pos, "> "));
+ xact->pos->beg_pos,
+ context.curr_pos, "> "));
}
throw;
}
@@ -1610,27 +1900,18 @@ xact_t * instance_t::parse_xact(char * line,
expr_t::ptr_op_t instance_t::lookup(const symbol_t::kind_t kind,
const string& name)
{
- return context.scope.lookup(kind, name);
+ return context.scope->lookup(kind, name);
}
-std::size_t journal_t::parse(std::istream& in,
- scope_t& scope,
- account_t * master_account,
- const path * original_file,
- bool strict)
+std::size_t journal_t::read_textual(parse_context_stack_t& context_stack)
{
TRACE_START(parsing_total, 1, "Total time spent parsing text:");
-
- parse_context_t context(*this, scope);
- context.strict = strict;
- if (master_account || this->master)
- context.state_stack.push_front(master_account ?
- master_account : this->master);
-
- instance_t instance(context, in, original_file);
- instance.parse();
- context.close();
-
+ {
+ instance_t instance(context_stack, context_stack.get_current());
+ instance.apply_stack.push_front
+ (application_t("account", context_stack.get_current().master));
+ instance.parse();
+ }
TRACE_STOP(parsing_total, 1);
// These tracers were started in textual.cc
@@ -1641,10 +1922,10 @@ std::size_t journal_t::parse(std::istream& in,
TRACE_FINISH(instance_parse, 1); // report per-instance timers
TRACE_FINISH(parsing_total, 1);
- if (context.errors > 0)
- throw static_cast<int>(context.errors);
+ if (context_stack.get_current().errors > 0)
+ throw error_count(context_stack.get_current().errors);
- return context.count;
+ return context_stack.get_current().count;
}
} // namespace ledger
diff --git a/src/timelog.cc b/src/timelog.cc
index ee9a0b6c..e84e4188 100644
--- a/src/timelog.cc
+++ b/src/timelog.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -36,14 +36,48 @@
#include "post.h"
#include "account.h"
#include "journal.h"
+#include "context.h"
namespace ledger {
namespace {
- void clock_out_from_timelog(std::list<time_xact_t>& time_xacts,
- time_xact_t out_event,
- journal_t& journal,
- scope_t& scope)
+ void create_timelog_xact(const time_xact_t& in_event,
+ const time_xact_t& out_event,
+ parse_context_t& context)
+ {
+ unique_ptr<xact_t> curr(new xact_t);
+ curr->_date = in_event.checkin.date();
+ curr->code = out_event.desc; // if it wasn't used above
+ curr->payee = in_event.desc;
+ curr->pos = in_event.position;
+
+ if (! in_event.note.empty())
+ curr->append_note(in_event.note.c_str(), *context.scope);
+
+ char buf[32];
+ std::sprintf(buf, "%lds", long((out_event.checkin - in_event.checkin)
+ .total_seconds()));
+ amount_t amt;
+ amt.parse(buf);
+ VERIFY(amt.valid());
+
+ post_t * post = new post_t(in_event.account, amt, POST_VIRTUAL);
+ post->set_state(item_t::CLEARED);
+ post->pos = in_event.position;
+ post->checkin = in_event.checkin;
+ post->checkout = out_event.checkin;
+ curr->add_post(post);
+ in_event.account->add_post(post);
+
+ if (! context.journal->add_xact(curr.get()))
+ throw parse_error(_("Failed to record 'out' timelog transaction"));
+ else
+ curr.release();
+ }
+
+ std::size_t clock_out_from_timelog(std::list<time_xact_t>& time_xacts,
+ time_xact_t out_event,
+ parse_context_t& context)
{
time_xact_t event;
@@ -76,6 +110,11 @@ namespace {
(_("Timelog check-out event does not match any current check-ins"));
}
+ if (event.checkin.is_not_a_date_time())
+ throw parse_error(_("Timelog check-in has no corresponding check-out"));
+ if (out_event.checkin.is_not_a_date_time())
+ throw parse_error(_("Timelog check-out has no corresponding check-in"));
+
if (out_event.checkin < event.checkin)
throw parse_error
(_("Timelog check-out date less than corresponding check-in"));
@@ -88,32 +127,35 @@ namespace {
if (! out_event.note.empty() && event.note.empty())
event.note = out_event.note;
- std::auto_ptr<xact_t> curr(new xact_t);
- curr->_date = out_event.checkin.date();
- curr->code = out_event.desc; // if it wasn't used above
- curr->payee = event.desc;
- curr->pos = event.position;
-
- if (! event.note.empty())
- curr->append_note(event.note.c_str(), scope);
-
- char buf[32];
- std::sprintf(buf, "%lds", long((out_event.checkin - event.checkin)
- .total_seconds()));
- amount_t amt;
- amt.parse(buf);
- VERIFY(amt.valid());
-
- post_t * post = new post_t(event.account, amt, POST_VIRTUAL);
- post->set_state(item_t::CLEARED);
- post->pos = event.position;
- curr->add_post(post);
- event.account->add_post(post);
-
- if (! journal.add_xact(curr.get()))
- throw parse_error(_("Failed to record 'out' timelog transaction"));
- else
- curr.release();
+ if (! context.journal->day_break) {
+ create_timelog_xact(event, out_event, context);
+ return 1;
+ } else {
+ time_xact_t begin(event);
+ std::size_t xact_count = 0;
+
+ while (begin.checkin < out_event.checkin) {
+ DEBUG("timelog", "begin.checkin: " << begin.checkin);
+ datetime_t days_end(begin.checkin.date(), time_duration_t(23, 59, 59));
+ days_end += seconds(1);
+ DEBUG("timelog", "days_end: " << days_end);
+
+ if (out_event.checkin <= days_end) {
+ create_timelog_xact(begin, out_event, context);
+ ++xact_count;
+ break;
+ } else {
+ time_xact_t end(out_event);
+ end.checkin = days_end;
+ DEBUG("timelog", "end.checkin: " << end.checkin);
+ create_timelog_xact(begin, end, context);
+ ++xact_count;
+
+ begin.checkin = end.checkin;
+ }
+ }
+ return xact_count;
+ }
}
} // unnamed namespace
@@ -129,9 +171,8 @@ void time_log_t::close()
DEBUG("timelog", "Clocking out from account " << account->fullname());
clock_out_from_timelog(time_xacts,
time_xact_t(none, CURRENT_TIME(), account),
- journal, scope);
- if (context_count)
- (*context_count)++;
+ context);
+ context.count++;
}
assert(time_xacts.empty());
}
@@ -149,12 +190,12 @@ void time_log_t::clock_in(time_xact_t event)
time_xacts.push_back(event);
}
-void time_log_t::clock_out(time_xact_t event)
+std::size_t time_log_t::clock_out(time_xact_t event)
{
if (time_xacts.empty())
throw std::logic_error(_("Timelog check-out event without a check-in"));
- clock_out_from_timelog(time_xacts, event, journal, scope);
+ return clock_out_from_timelog(time_xacts, event, context);
}
} // namespace ledger
diff --git a/src/timelog.h b/src/timelog.h
index 020ae4f2..857952ff 100644
--- a/src/timelog.h
+++ b/src/timelog.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -50,6 +50,7 @@ namespace ledger {
class account_t;
class journal_t;
+class parse_context_t;
class time_xact_t
{
@@ -86,22 +87,18 @@ public:
class time_log_t : public boost::noncopyable
{
std::list<time_xact_t> time_xacts;
- journal_t& journal;
- scope_t& scope;
+ parse_context_t& context;
public:
- std::size_t * context_count;
-
- time_log_t(journal_t& _journal, scope_t& _scope)
- : journal(_journal), scope(_scope), context_count(NULL) {
- TRACE_CTOR(time_log_t, "journal_t&, scope_t&, std::size&");
+ time_log_t(parse_context_t& _context) : context(_context) {
+ TRACE_CTOR(time_log_t, "parse_context_t&");
}
~time_log_t() {
TRACE_DTOR(time_log_t);
}
void clock_in(time_xact_t event);
- void clock_out(time_xact_t event);
+ std::size_t clock_out(time_xact_t event);
void close();
};
diff --git a/src/times.cc b/src/times.cc
index 0384edf6..3c556a47 100644
--- a/src/times.cc
+++ b/src/times.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -173,6 +173,7 @@ namespace {
#else // USE_BOOST_FACETS
std::tm data;
std::memset(&data, 0, sizeof(std::tm));
+ data.tm_year = CURRENT_DATE().year() - 1900;
data.tm_mday = 1; // some formats have no day
if (strptime(str, fmt_str, &data))
return gregorian::date_from_tm(data);
@@ -196,6 +197,8 @@ namespace {
std::deque<shared_ptr<date_io_t> > readers;
+ bool convert_separators_to_slashes = true;
+
date_t parse_date_mask_routine(const char * date_str, date_io_t& io,
date_traits_t * traits = NULL)
{
@@ -204,9 +207,11 @@ namespace {
char buf[128];
std::strcpy(buf, date_str);
- for (char * p = buf; *p; p++)
- if (*p == '.' || *p == '-')
- *p = '/';
+ if (convert_separators_to_slashes) {
+ for (char * p = buf; *p; p++)
+ if (*p == '.' || *p == '-')
+ *p = '/';
+ }
date_t when = io.parse(buf);
@@ -1305,7 +1310,7 @@ void date_interval_t::stabilize(const optional<date_t>& date)
date_interval_t next_interval(*this);
++next_interval;
- if (next_interval.start && *next_interval.start < *date) {
+ if (next_interval.start && *next_interval.start <= *date) {
*this = next_interval;
} else {
end_of_duration = none;
@@ -1355,7 +1360,8 @@ void date_interval_t::stabilize(const optional<date_t>& date)
}
}
-bool date_interval_t::find_period(const date_t& date)
+bool date_interval_t::find_period(const date_t& date,
+ const bool allow_shift)
{
stabilize(date);
@@ -1397,6 +1403,12 @@ bool date_interval_t::find_period(const date_t& date)
DEBUG("times.interval", "date = " << date);
DEBUG("times.interval", "scan = " << scan);
DEBUG("times.interval", "end_of_scan = " << end_of_scan);
+#if defined(DEBUG_ON)
+ if (finish)
+ DEBUG("times.interval", "finish = " << *finish);
+ else
+ DEBUG("times.interval", "finish is not set");
+#endif
while (date >= scan && (! finish || scan < *finish)) {
if (date < end_of_scan) {
@@ -1411,11 +1423,19 @@ bool date_interval_t::find_period(const date_t& date)
return true;
}
+ else if (! allow_shift) {
+ break;
+ }
scan = duration->add(scan);
end_of_scan = duration->add(scan);
+
+ DEBUG("times.interval", "scan = " << scan);
+ DEBUG("times.interval", "end_of_scan = " << end_of_scan);
}
+ DEBUG("times.interval", "false: failed scan");
+
return false;
}
@@ -1759,6 +1779,7 @@ void set_date_format(const char * format)
void set_input_date_format(const char * format)
{
readers.push_front(shared_ptr<date_io_t>(new date_io_t(format, true)));
+ convert_separators_to_slashes = false;
}
void times_initialize()
diff --git a/src/times.h b/src/times.h
index 39945824..3bb95903 100644
--- a/src/times.h
+++ b/src/times.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -218,6 +218,9 @@ struct date_duration_t
case YEARS:
return date + gregorian::years(length);
}
+#if !defined(__clang__)
+ return date_t();
+#endif
}
date_t subtract(const date_t& date) const {
@@ -233,6 +236,9 @@ struct date_duration_t
case YEARS:
return date - gregorian::years(length);
}
+#if !defined(__clang__)
+ return date_t();
+#endif
}
string to_string() const {
@@ -301,13 +307,14 @@ public:
}
date_specifier_t(const date_t& date,
const optional<date_traits_t>& traits = none) {
- TRACE_CTOR(date_specifier_t, "date_t, date_traits_t");
if (! traits || traits->has_year)
year = date.year();
if (! traits || traits->has_month)
month = date.month();
if (! traits || traits->has_day)
day = date.day();
+
+ TRACE_CTOR(date_specifier_t, "date_t, date_traits_t");
}
date_specifier_t(const date_specifier_t& other)
: year(other.year), month(other.month),
@@ -532,8 +539,8 @@ public:
TRACE_CTOR(date_interval_t, "");
}
date_interval_t(const string& str) : aligned(false) {
- TRACE_CTOR(date_interval_t, "const string&");
parse(str);
+ TRACE_CTOR(date_interval_t, "const string&");
}
date_interval_t(const date_interval_t& other)
: range(other.range),
@@ -553,6 +560,10 @@ public:
return (start == other.start &&
(! start || *start == *other.start));
}
+ bool operator<(const date_interval_t& other) const {
+ return (start == other.start &&
+ (! start || *start < *other.start));
+ }
operator bool() const {
return is_valid();
@@ -574,10 +585,14 @@ public:
return start;
}
- /** Find the current or next period containing date. Returns true if the
- date_interval_t object has been altered to reflect the interval
- containing date, or false if no such period can be found. */
- bool find_period(const date_t& date = CURRENT_DATE());
+ /** Find the current or next period containing date. Returns false if
+ no such period can be found. If allow_shift is true, the default,
+ then the interval may be shifted in time to find the period. */
+ bool find_period(const date_t& date = CURRENT_DATE(),
+ const bool allow_shift = true);
+ bool within_period(const date_t& date = CURRENT_DATE()) {
+ return find_period(date, false);
+ }
optional<date_t> inclusive_end() const {
if (end_of_duration)
diff --git a/src/token.cc b/src/token.cc
index 77092d49..e5d6b218 100644
--- a/src/token.cc
+++ b/src/token.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -415,16 +415,13 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags)
try {
amount_t temp;
if (! temp.parse(in, parse_flags.plus_flags(PARSE_SOFT_FAIL))) {
- // If the amount had no commodity, it must be an unambiguous
- // variable reference
-
in.clear();
in.seekg(pos, std::ios::beg);
if (in.fail())
throw_(parse_error, _("Failed to reset input stream"));
c = static_cast<char>(in.peek());
- if (! std::isalpha(c))
+ if (! std::isalpha(c) && c != '_')
expected('\0', c);
parse_ident(in);
diff --git a/src/token.h b/src/token.h
index cbdf1258..01ff7ee9 100644
--- a/src/token.h
+++ b/src/token.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
diff --git a/src/unistring.h b/src/unistring.h
index 4be36b0d..b2278796 100644
--- a/src/unistring.h
+++ b/src/unistring.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -64,14 +64,14 @@ public:
}
unistring(const std::string& input)
{
- TRACE_CTOR(unistring, "std::string");
-
const char * p = input.c_str();
std::size_t len = input.length();
assert(len < 1024);
VERIFY(utf8::is_valid(p, p + len));
utf8::unchecked::utf8to32(p, p + len, std::back_inserter(utf32chars));
+
+ TRACE_CTOR(unistring, "std::string");
}
~unistring() {
TRACE_DTOR(unistring);
diff --git a/src/utils.cc b/src/utils.cc
index 42600db3..17118904 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -50,8 +50,8 @@ void debug_assert(const string& reason,
std::size_t line)
{
std::ostringstream buf;
- buf << "Assertion failed in \"" << file << "\", line " << line
- << ": " << func << ": " << reason;
+ buf << "Assertion failed in " << file_context(file, line)
+ << func << ": " << reason;
throw assertion_failed(buf.str());
}
@@ -270,13 +270,81 @@ void operator delete[](void * ptr, const std::nothrow_t&) throw() {
namespace ledger {
-inline void report_count_map(std::ostream& out, object_count_map& the_map)
-{
- foreach (object_count_map::value_type& pair, the_map)
- out << " " << std::right << std::setw(12) << pair.second.first
- << " " << std::right << std::setw(7) << pair.second.second
- << " " << std::left << pair.first
- << std::endl;
+namespace {
+ void stream_commified_number(std::ostream& out, std::size_t num)
+ {
+ std::ostringstream buf;
+ std::ostringstream obuf;
+
+ buf << num;
+
+ string number(buf.str());
+
+ int integer_digits = 0;
+ // Count the number of integer digits
+ for (const char * p = number.c_str(); *p; p++) {
+ if (*p == '.')
+ break;
+ else if (*p != '-')
+ integer_digits++;
+ }
+
+ for (const char * p = number.c_str(); *p; p++) {
+ if (*p == '.') {
+ obuf << *p;
+ assert(integer_digits <= 3);
+ }
+ else if (*p == '-') {
+ obuf << *p;
+ }
+ else {
+ obuf << *p;
+
+ if (integer_digits > 3 && --integer_digits % 3 == 0)
+ obuf << ',';
+ }
+ }
+
+ out << obuf.str();
+ }
+
+ void stream_memory_size(std::ostream& out, std::size_t size)
+ {
+ std::ostringstream obuf;
+
+ if (size > 10 * 1024 * 1024)
+ obuf << "\033[1m";
+ if (size > 100 * 1024 * 1024)
+ obuf << "\033[31m";
+
+ obuf << std::setw(7);
+
+ if (size < 1024)
+ obuf << size << 'b';
+ else if (size < (1024 * 1024))
+ obuf << int(double(size) / 1024.0) << 'K';
+ else if (size < (1024 * 1024 * 1024))
+ obuf << int(double(size) / (1024.0 * 1024.0)) << 'M';
+ else
+ obuf << int(double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G';
+
+ if (size > 10 * 1024 * 1024)
+ obuf << "\033[0m";
+
+ out << obuf.str();
+ }
+
+ void report_count_map(std::ostream& out, object_count_map& the_map)
+ {
+ foreach (object_count_map::value_type& pair, the_map) {
+ out << " " << std::right << std::setw(18);
+ stream_commified_number(out, pair.second.first);
+ out << " " << std::right << std::setw(7);
+ stream_memory_size(out, pair.second.second);
+ out << " " << std::left << pair.first
+ << std::endl;
+ }
+ }
}
std::size_t current_objects_size()
@@ -354,7 +422,7 @@ void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size)
void report_memory(std::ostream& out, bool report_all)
{
- if (! live_memory || ! memory_tracing_active) return;
+ if (! live_memory) return;
if (live_memory_count->size() > 0) {
out << "NOTE: There may be memory held by Boost "
@@ -366,11 +434,13 @@ void report_memory(std::ostream& out, bool report_all)
if (live_memory->size() > 0) {
out << "Live memory:" << std::endl;
- foreach (const memory_map::value_type& pair, *live_memory)
- out << " " << std::right << std::setw(12) << pair.first
- << " " << std::right << std::setw(7) << pair.second.second
- << " " << std::left << pair.second.first
+ foreach (const memory_map::value_type& pair, *live_memory) {
+ out << " " << std::right << std::setw(18) << pair.first
+ << " " << std::right << std::setw(7);
+ stream_memory_size(out, pair.second.second);
+ out << " " << std::left << pair.second.first
<< std::endl;
+ }
}
if (report_all && total_memory_count->size() > 0) {
@@ -386,11 +456,13 @@ void report_memory(std::ostream& out, bool report_all)
if (live_objects->size() > 0) {
out << "Live objects:" << std::endl;
- foreach (const objects_map::value_type& pair, *live_objects)
- out << " " << std::right << std::setw(12) << pair.first
- << " " << std::right << std::setw(7) << pair.second.second
- << " " << std::left << pair.second.first
+ foreach (const objects_map::value_type& pair, *live_objects) {
+ out << " " << std::right << std::setw(18) << pair.first
+ << " " << std::right << std::setw(7);
+ stream_memory_size(out, pair.second.second);
+ out << " " << std::left << pair.second.first
<< std::endl;
+ }
}
if (report_all) {
@@ -529,18 +601,6 @@ std::ostringstream _log_buffer;
uint8_t _trace_level;
#endif
-static inline void stream_memory_size(std::ostream& out, std::size_t size)
-{
- if (size < 1024)
- out << size << 'b';
- else if (size < (1024 * 1024))
- out << (double(size) / 1024.0) << 'K';
- else if (size < (1024 * 1024 * 1024))
- out << (double(size) / (1024.0 * 1024.0)) << 'M';
- else
- out << (double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G';
-}
-
static bool logger_has_run = false;
static ptime logger_start;
@@ -553,9 +613,6 @@ void logger_func(log_level_t level)
#if defined(VERIFY_ON)
IF_VERIFY()
*_log_stream << " TIME OBJSZ MEMSZ" << std::endl;
-#else
- IF_VERIFY()
- *_log_stream << " TIME" << std::endl;
#endif
}
@@ -603,12 +660,16 @@ void logger_func(log_level_t level)
namespace ledger {
-optional<std::string> _log_category;
+optional<std::string> _log_category;
+#if defined(HAVE_BOOST_REGEX_UNICODE)
+optional<boost::u32regex> _log_category_re;
+#else
+optional<boost::regex> _log_category_re;
+#endif
struct __maybe_enable_debugging {
__maybe_enable_debugging() {
- const char * p = std::getenv("LEDGER_DEBUG");
- if (p != NULL) {
+ if (const char * p = std::getenv("LEDGER_DEBUG")) {
_log_level = LOG_DEBUG;
_log_category = p;
}
diff --git a/src/utils.h b/src/utils.h
index e1a03d79..8f11f75a 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -171,7 +171,7 @@ void report_memory(std::ostream& out, bool report_all = false);
#else // ! VERIFY_ON
#define VERIFY(x)
-#define DO_VERIFY() true
+#define DO_VERIFY() false
#define TRACE_CTOR(cls, args)
#define TRACE_DTOR(cls)
@@ -278,6 +278,28 @@ extern string empty_string;
strings_list split_arguments(const char * line);
+inline string to_string(long num) {
+ std::ostringstream buf;
+ buf << num;
+ return buf.str();
+}
+
+inline string to_string(std::size_t num) {
+ std::ostringstream buf;
+ buf << num;
+ return buf.str();
+}
+
+inline string lowered(const string& str) {
+ string tmp(str);
+ to_lower(tmp);
+ return tmp;
+}
+
+inline string operator+(const char * left, const string& right) {
+ return string(left) + right;
+}
+
} // namespace ledger
/*@}*/
@@ -338,10 +360,32 @@ extern uint8_t _trace_level;
#if defined(DEBUG_ON)
-extern optional<std::string> _log_category;
+extern optional<std::string> _log_category;
+#if defined(HAVE_BOOST_REGEX_UNICODE)
+ extern optional<boost::u32regex> _log_category_re;
+#else
+ extern optional<boost::regex> _log_category_re;
+#endif
inline bool category_matches(const char * cat) {
- return _log_category && starts_with(cat, *_log_category);
+ if (_log_category) {
+ if (! _log_category_re) {
+ _log_category_re =
+#if defined(HAVE_BOOST_REGEX_UNICODE)
+ boost::make_u32regex(_log_category->c_str(),
+ boost::regex::perl | boost::regex::icase);
+#else
+ boost::regex(_log_category->c_str(),
+ boost::regex::perl | boost::regex::icase);
+#endif
+ }
+#if defined(HAVE_BOOST_REGEX_UNICODE)
+ return boost::u32regex_search(cat, *_log_category_re);
+#else
+ return boost::regex_search(cat, *_log_category_re);
+#endif
+ }
+ return false;
}
#define SHOW_DEBUG(cat) \
diff --git a/src/value.cc b/src/value.cc
index c62e6f32..2b1b561f 100644
--- a/src/value.cc
+++ b/src/value.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -724,7 +724,7 @@ value_t& value_t::operator/=(const value_t& val)
return *this;
case AMOUNT:
if (as_balance().single_amount()) {
- in_place_simplify();
+ in_place_cast(AMOUNT);
as_amount_lval() /= val.as_amount();
return *this;
}
@@ -742,7 +742,7 @@ value_t& value_t::operator/=(const value_t& val)
break;
}
- add_error_context(_("While dividing %1 by %2:") << val << *this);
+ add_error_context(_("While dividing %1 by %2:") << *this << val);
throw_(value_error, _("Cannot divide %1 by %2") << label() << val.label());
return *this;
@@ -869,7 +869,7 @@ bool value_t::is_less_than(const value_t& val) const
case INTEGER:
return as_long() < val.as_long();
case AMOUNT:
- return val.as_amount() >= as_long();
+ return val.as_amount() > as_long();
default:
break;
}
@@ -948,8 +948,7 @@ bool value_t::is_less_than(const value_t& val) const
break;
}
- add_error_context(_("While comparing if %1 is less than %2:")
- << *this << val);
+ add_error_context(_("While comparing if %1 is less than %2:") << *this << val);
throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label());
return *this;
@@ -990,7 +989,7 @@ bool value_t::is_greater_than(const value_t& val) const
case INTEGER:
return as_long() > val.as_long();
case AMOUNT:
- return val.as_amount() > as_long();
+ return val.as_amount() < as_long();
default:
break;
}
@@ -1064,8 +1063,7 @@ bool value_t::is_greater_than(const value_t& val) const
break;
}
- add_error_context(_("While comparing if %1 is greater than %2:")
- << *this << val);
+ add_error_context(_("While comparing if %1 is greater than %2:") << *this << val);
throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label());
return *this;
@@ -1228,7 +1226,7 @@ void value_t::in_place_cast(type_t cast_type)
case STRING:
switch (cast_type) {
case INTEGER: {
- if (all(as_string(), is_digit())) {
+ if (all(as_string(), is_any_of("-0123456789"))) {
set_long(lexical_cast<long>(as_string()));
return;
}
@@ -1251,13 +1249,23 @@ void value_t::in_place_cast(type_t cast_type)
}
break;
+ case MASK:
+ switch (cast_type) {
+ case STRING:
+ set_string(as_mask().str());
+ return;
+ default:
+ break;
+ }
+ break;
+
default:
break;
}
add_error_context(_("While converting %1:") << *this);
- throw_(value_error, _("Cannot convert %1 to %2")
- << label() << label(cast_type));
+ throw_(value_error,
+ _("Cannot convert %1 to %2") << label() << label(cast_type));
}
void value_t::in_place_negate()
@@ -1389,25 +1397,30 @@ bool value_t::is_zero() const
return false;
}
-value_t value_t::value(const optional<datetime_t>& moment,
- const optional<commodity_t&>& in_terms_of) const
+value_t value_t::value(const datetime_t& moment,
+ const commodity_t * in_terms_of) const
{
switch (type()) {
case INTEGER:
return NULL_VALUE;
case AMOUNT:
- if (optional<amount_t> val =
- as_amount().value(moment, in_terms_of))
+ if (optional<amount_t> val = as_amount().value(moment, in_terms_of))
return *val;
return NULL_VALUE;
case BALANCE:
- if (optional<balance_t> bal =
- as_balance().value(moment, in_terms_of))
+ if (optional<balance_t> bal = as_balance().value(moment, in_terms_of))
return *bal;
return NULL_VALUE;
+ case SEQUENCE: {
+ value_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp.push_back(value.value(moment, in_terms_of));
+ return temp;
+ }
+
default:
break;
}
@@ -1417,37 +1430,100 @@ value_t value_t::value(const optional<datetime_t>& moment,
return NULL_VALUE;
}
-value_t value_t::price() const
+value_t value_t::exchange_commodities(const std::string& commodities,
+ const bool add_prices,
+ const datetime_t& moment)
{
- switch (type()) {
- case AMOUNT:
- return as_amount().price();
- case BALANCE:
- return as_balance().price();
- default:
- return *this;
+ if (type() == SEQUENCE) {
+ value_t temp;
+ foreach (value_t& value, as_sequence_lval())
+ temp.push_back(value.exchange_commodities(commodities, add_prices, moment));
+ return temp;
}
-}
-value_t value_t::exchange_commodities(const std::string& commodities,
- const bool add_prices,
- const optional<datetime_t>& moment)
-{
- scoped_array<char> buf(new char[commodities.length() + 1]);
-
- std::strcpy(buf.get(), commodities.c_str());
-
- for (char * p = std::strtok(buf.get(), ",");
- p;
- p = std::strtok(NULL, ",")) {
- if (commodity_t * commodity =
- commodity_pool_t::current_pool->parse_price_expression(p, add_prices,
- moment)) {
- value_t result = value(moment, *commodity);
- if (! result.is_null())
- return result;
+ // If we are repricing to just a single commodity, with no price
+ // expression, skip the expensive logic below.
+ if (commodities.find(',') == string::npos &&
+ commodities.find('=') == string::npos)
+ return value(moment, commodity_pool_t::current_pool->find_or_create(commodities));
+
+ std::vector<commodity_t *> comms;
+ std::vector<bool> force;
+
+ typedef tokenizer<char_separator<char> > tokenizer;
+ tokenizer tokens(commodities, char_separator<char>(","));
+
+ foreach (const string& name, tokens) {
+ string::size_type name_len = name.length();
+
+ if (commodity_t * commodity = commodity_pool_t::current_pool
+ ->parse_price_expression(name[name_len - 1] == '!' ?
+ string(name, 0, name_len - 1) :
+ name, add_prices, moment)) {
+ DEBUG("commodity.exchange", "Pricing for commodity: " << commodity->symbol());
+ comms.push_back(&commodity->referent());
+ force.push_back(name[name_len - 1] == '!');
+ }
+ }
+
+ std::size_t index = 0;
+ foreach (commodity_t * comm, comms) {
+ switch (type()) {
+ case AMOUNT:
+ DEBUG("commodity.exchange", "We have an amount: " << as_amount_lval());
+ if (! force[index] &&
+ std::find(comms.begin(), comms.end(),
+ &as_amount_lval().commodity().referent()) != comms.end())
+ break;
+
+ DEBUG("commodity.exchange", "Referent doesn't match, pricing...");
+ if (optional<amount_t> val = as_amount_lval().value(moment, comm)) {
+ DEBUG("commodity.exchange", "Re-priced amount is: " << *val);
+ return *val;
+ }
+ DEBUG("commodity.exchange", "Was unable to find a price");
+ break;
+
+ case BALANCE: {
+ balance_t temp;
+ bool repriced = false;
+
+ DEBUG("commodity.exchange", "We have a balance: " << as_balance_lval());
+ foreach (const balance_t::amounts_map::value_type& pair,
+ as_balance_lval().amounts) {
+ DEBUG("commodity.exchange", "We have a balance amount of commodity: "
+ << pair.first->symbol() << " == "
+ << pair.second.commodity().symbol());
+ if (! force[index] &&
+ std::find(comms.begin(), comms.end(),
+ &pair.first->referent()) != comms.end()) {
+ temp += pair.second;
+ } else {
+ DEBUG("commodity.exchange", "Referent doesn't match, pricing...");
+ if (optional<amount_t> val = pair.second.value(moment, comm)) {
+ DEBUG("commodity.exchange", "Re-priced member amount is: " << *val);
+ temp += *val;
+ repriced = true;
+ } else {
+ DEBUG("commodity.exchange", "Was unable to find price");
+ temp += pair.second;
+ }
+ }
+ }
+
+ if (repriced) {
+ DEBUG("commodity.exchange", "Re-priced balance is: " << temp);
+ return temp;
+ }
}
+
+ default:
+ break;
+ }
+
+ ++index;
}
+
return *this;
}
@@ -1727,7 +1803,7 @@ void value_t::print(std::ostream& _out,
switch (type()) {
case VOID:
- out << "";
+ out << "(null)";
break;
case BOOLEAN:
diff --git a/src/value.h b/src/value.h
index f8495002..d128bb89 100644
--- a/src/value.h
+++ b/src/value.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -179,8 +179,8 @@ public:
*/
explicit storage_t(const storage_t& rhs)
: type(rhs.type), refc(0) {
- TRACE_CTOR(value_t::storage_t, "copy");
*this = rhs;
+ TRACE_CTOR(value_t::storage_t, "copy");
}
storage_t& operator=(const storage_t& rhs);
@@ -290,73 +290,75 @@ public:
}
value_t(const bool val) {
- TRACE_CTOR(value_t, "const bool");
set_boolean(val);
+ TRACE_CTOR(value_t, "const bool");
}
value_t(const datetime_t& val) {
- TRACE_CTOR(value_t, "const datetime_t&");
set_datetime(val);
+ TRACE_CTOR(value_t, "const datetime_t&");
}
value_t(const date_t& val) {
- TRACE_CTOR(value_t, "const date_t&");
set_date(val);
+ TRACE_CTOR(value_t, "const date_t&");
}
value_t(const long val) {
- TRACE_CTOR(value_t, "const long");
set_long(val);
+ TRACE_CTOR(value_t, "const long");
}
value_t(const unsigned long val) {
- TRACE_CTOR(value_t, "const unsigned long");
set_amount(val);
+ TRACE_CTOR(value_t, "const unsigned long");
}
value_t(const double val) {
- TRACE_CTOR(value_t, "const double");
set_amount(val);
+ TRACE_CTOR(value_t, "const double");
}
value_t(const amount_t& val) {
- TRACE_CTOR(value_t, "const amount_t&");
set_amount(val);
+ TRACE_CTOR(value_t, "const amount_t&");
}
value_t(const balance_t& val) {
- TRACE_CTOR(value_t, "const balance_t&");
set_balance(val);
+ TRACE_CTOR(value_t, "const balance_t&");
}
value_t(const mask_t& val) {
- TRACE_CTOR(value_t, "const mask_t&");
set_mask(val);
+ TRACE_CTOR(value_t, "const mask_t&");
}
explicit value_t(const string& val, bool literal = false) {
- TRACE_CTOR(value_t, "const string&, bool");
if (literal)
set_string(val);
else
set_amount(amount_t(val));
+
+ TRACE_CTOR(value_t, "const string&, bool");
}
explicit value_t(const char * val, bool literal = false) {
- TRACE_CTOR(value_t, "const char *");
if (literal)
set_string(val);
else
set_amount(amount_t(val));
+
+ TRACE_CTOR(value_t, "const char *");
}
value_t(const sequence_t& val) {
- TRACE_CTOR(value_t, "const sequence_t&");
set_sequence(val);
+ TRACE_CTOR(value_t, "const sequence_t&");
}
explicit value_t(scope_t * item) {
- TRACE_CTOR(value_t, "scope_t *");
set_scope(item);
+ TRACE_CTOR(value_t, "scope_t *");
}
#if 0
template <typename T>
explicit value_t(T& item) {
- TRACE_CTOR(value_t, "T&");
set_any(item);
+ TRACE_CTOR(value_t, "T&");
}
#endif
@@ -375,8 +377,8 @@ public:
* object. A true copy is only ever made prior to modification.
*/
value_t(const value_t& val) {
- TRACE_CTOR(value_t, "copy");
*this = val;
+ TRACE_CTOR(value_t, "copy");
}
value_t& operator=(const value_t& val) {
if (! (this == &val || storage == val.storage))
@@ -477,14 +479,12 @@ public:
void in_place_unreduce(); // exists for efficiency's sake
// Return the "market value" of a given value at a specific time.
- value_t value(const optional<datetime_t>& moment = none,
- const optional<commodity_t&>& in_terms_of = none) const;
-
- value_t price() const;
+ value_t value(const datetime_t& moment = datetime_t(),
+ const commodity_t * in_terms_of = NULL) const;
- value_t exchange_commodities(const std::string& commodities,
- const bool add_prices = false,
- const optional<datetime_t>& moment = none);
+ value_t exchange_commodities(const std::string& commodities,
+ const bool add_prices = false,
+ const datetime_t& moment = datetime_t());
/**
* Truth tests.
diff --git a/src/views.cc b/src/views.cc
new file mode 100644
index 00000000..bbd58ce2
--- /dev/null
+++ b/src/views.cc
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef DOCUMENT_MODEL
+
+#include <system.hh>
+
+#include "views.h"
+#include "report.h"
+#include "journal.h"
+#include "xact.h"
+#include "post.h"
+#include "account.h"
+
+namespace ledger {
+
+r_xact_ptr r_journal_t::create_xact(xact_t * xact)
+{
+ r_xact_ptr x = new r_xact_t(this, xact);
+ add_xact(x);
+ assert(xact->data == NULL);
+ xact->data = &x;
+ return x;
+}
+
+void r_journal_t::add_xact(r_xact_ptr xact)
+{
+ xacts.push_back(xact);
+}
+
+r_post_ptr r_journal_t::add_post(post_t * post)
+{
+ r_xact_ptr x;
+ if (post->xact->data)
+ x = *static_cast<r_xact_ptr *>(post->xact->data);
+ else
+ x = create_xact(post->xact);
+
+ r_post_ptr p = create_post(post, x, create_account(post->account));
+ return p;
+}
+
+void r_journal_t::add_post(r_post_ptr post)
+{
+ posts.push_back(post);
+}
+
+r_post_ptr r_journal_t::create_post(post_t * post, r_xact_ptr xact,
+ r_account_ptr account)
+{
+ r_post_ptr p = new r_post_t(this, post, xact, account);
+
+ add_post(p);
+ xact->add_post(p);
+ account->add_post(p);
+
+ return p;
+}
+
+r_post_ptr r_journal_t::create_post(r_post_ptr post, r_xact_ptr xact,
+ r_account_ptr account)
+{
+ r_post_ptr temp(new r_post_t(*post.get()));
+
+ add_post(temp);
+
+ temp->set_xact(xact);
+ xact->add_post(temp);
+
+ temp->set_account(account);
+ account->add_post(temp);
+
+ return temp;
+}
+
+r_account_ptr r_journal_t::create_account(account_t * account)
+{
+ return create_account(account->fullname());
+}
+
+r_account_ptr r_journal_t::create_account(const std::string& name)
+{
+ return master_ptr->create_account(name);
+}
+
+
+const optional<position_t> r_item_t::position() const
+{
+ return ptr()->pos;
+}
+
+date_t r_item_t::date() const
+{
+ return ptr()->date();
+}
+
+void r_item_t::set_date(const date_t& when)
+{
+}
+
+item_t::state_t r_item_t::state() const
+{
+ return ptr()->state();
+}
+
+void r_item_t::set_state(item_t::state_t val)
+{
+}
+
+string r_item_t::payee() const
+{
+ if (optional<value_t> desc = get_tag(_("Payee")))
+ return desc->as_string();
+ else
+ return empty_string;
+}
+
+void r_item_t::set_payee(const string& name)
+{
+}
+
+void r_item_t::define(const symbol_t::kind_t, const string& name,
+ expr_t::ptr_op_t def)
+{
+ bind_scope_t bound_scope(*scope_t::default_scope, *this);
+ set_tag(name, def->calc(bound_scope));
+}
+
+expr_t::ptr_op_t r_item_t::lookup(const symbol_t::kind_t kind,
+ const string& name)
+{
+ if (kind != symbol_t::FUNCTION)
+ return NULL;
+
+ switch (name[0]) {
+ }
+
+ return base_item->lookup(kind, name);
+}
+
+
+string r_xact_t::description()
+{
+ return ptr()->description();
+}
+
+void r_xact_t::add_post(r_post_ptr post)
+{
+ posts.push_back(post);
+}
+
+string r_xact_t::payee() const
+{
+ string desc(r_item_t::payee());
+ if (desc.empty())
+ return ptr()->payee;
+ else
+ return desc;
+}
+
+
+string r_post_t::description()
+{
+ return ptr()->description();
+}
+
+string r_post_t::payee() const
+{
+ string desc(r_item_t::payee());
+ if (desc.empty())
+ return const_cast<r_post_t *>(this)->xact()->payee();
+ else
+ return desc;
+}
+
+
+string r_account_t::description()
+{
+ return string(_("account ")) + fullname();
+}
+
+void r_account_t::add_post(r_post_ptr post)
+{
+ posts.push_back(post);
+}
+
+r_account_ptr r_account_t::create_account(const std::string& fname)
+{
+ string::size_type sep = fname.find(':');
+ string head, tail;
+ if (sep == string::npos) {
+ head = fname;
+ } else {
+ head = string(fname, 0, sep);
+ tail = string(fname, sep + 1);
+ }
+
+ std::pair<r_accounts_map::iterator, bool> result =
+ accounts.insert(r_accounts_map::value_type
+ (head, new r_account_t(journal_ptr, this, name)));
+
+ r_account_ptr acct((*result.first).second);
+ if (tail.empty())
+ return acct;
+ else
+ return acct->create_account(tail);
+}
+
+string r_account_t::fullname() const
+{
+ if (! _fullname.empty()) {
+ return _fullname;
+ } else {
+ r_account_ptr first = NULL;
+ string fname = name;
+
+ while (! first || first->parent_ptr) {
+ first = first ? first->parent_ptr : parent_ptr;
+ if (! first->name.empty())
+ fname = first->name + ":" + fname;
+ }
+
+ _fullname = fname;
+
+ return fname;
+ }
+}
+
+} // namespace ledger
+
+#endif /* DOCUMENT_MODEL */
diff --git a/src/views.h b/src/views.h
new file mode 100644
index 00000000..f9a007b7
--- /dev/null
+++ b/src/views.h
@@ -0,0 +1,457 @@
+/*
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @addtogroup views
+ */
+
+/**
+ * @file views.h
+ * @author John Wiegley
+ *
+ * @ingroup views
+ */
+#ifndef _VIEWS_H
+#define _VIEWS_H
+
+#include "utils.h"
+
+#ifdef DOCUMENT_MODEL
+
+#include "scope.h"
+#include "item.h"
+#include "report.h"
+#include "post.h"
+#include "predicate.h"
+
+namespace ledger {
+
+class journal_t;
+class xact_t;
+class post_t;
+class account_t;
+class report_t;
+
+class r_base_t : public supports_flags<uint_least16_t>,
+ public scope_t
+{
+public:
+ r_base_t() : refc(0) {
+ TRACE_CTOR(r_base_t, "");
+ }
+ r_base_t(const r_base_t& other) : refc(0) {
+ TRACE_CTOR(r_base_t, "copy");
+ }
+ virtual ~r_base_t() {
+ TRACE_DTOR(r_base_t);
+ }
+
+protected:
+ /**
+ * `refc' holds the current reference count for each object.
+ */
+ mutable int refc;
+
+ /**
+ * Reference counting methods. The intrusive_ptr_* methods are used
+ * by boost::intrusive_ptr to manage the calls to acquire and release.
+ */
+ void acquire() const {
+ VERIFY(refc >= 0);
+ refc++;
+ }
+ void release() const {
+ VERIFY(refc > 0);
+ if (--refc == 0)
+ checked_delete(this);
+ }
+
+ friend inline void intrusive_ptr_add_ref(r_base_t * r_ptr) {
+ r_ptr->acquire();
+ }
+ friend inline void intrusive_ptr_release(r_base_t * r_ptr) {
+ r_ptr->release();
+ }
+};
+
+class r_journal_t;
+class r_item_t;
+class r_xact_t;
+class r_post_t;
+class r_account_t;
+
+typedef intrusive_ptr<r_journal_t> r_journal_ptr;
+typedef intrusive_ptr<r_item_t> r_item_ptr;
+typedef intrusive_ptr<r_xact_t> r_xact_ptr;
+typedef intrusive_ptr<r_post_t> r_post_ptr;
+typedef intrusive_ptr<r_account_t> r_account_ptr;
+
+typedef std::list<r_xact_ptr> r_xacts_list;
+typedef std::list<r_post_ptr> r_posts_list;
+
+class r_journal_t : public r_base_t
+{
+ journal_t * base_journal;
+
+ journal_t * ptr() {
+ return base_journal;
+ }
+ const journal_t * ptr() const {
+ return base_journal;
+ }
+
+ r_account_ptr master_ptr;
+ r_xacts_list xacts;
+ r_posts_list posts;
+
+ void set_master(r_account_ptr ptr) {
+ master_ptr = ptr;
+ }
+
+public:
+ r_journal_t(journal_t * _journal, r_account_ptr _master)
+ : r_base_t(), base_journal(_journal), master_ptr(_master) {
+ TRACE_CTOR(r_journal_t, "journal_t *, account_t *");
+ }
+ r_journal_t(const r_journal_t& other)
+ : r_base_t(other),
+ base_journal(other.base_journal),
+ master_ptr(other.master_ptr),
+ xacts(other.xacts),
+ posts(other.posts) {
+ TRACE_CTOR(r_journal_t, "copy");
+ }
+ virtual ~r_journal_t() {
+ TRACE_DTOR(r_journal_t);
+ }
+
+ r_xact_ptr create_xact(xact_t * xact = NULL);
+
+ void add_xact(r_xact_ptr xact);
+
+ r_xacts_list::iterator xacts_begin() {
+ return xacts.begin();
+ }
+ r_xacts_list::iterator xacts_end() {
+ return xacts.end();
+ }
+
+ r_post_ptr add_post(post_t * post);
+ void add_post(r_post_ptr post);
+
+ r_post_ptr create_post(post_t * post = NULL, r_xact_ptr xact = NULL,
+ r_account_ptr account = NULL);
+ r_post_ptr create_post(r_post_ptr post, r_xact_ptr xact = NULL,
+ r_account_ptr account = NULL);
+
+ r_posts_list::iterator posts_begin() {
+ return posts.begin();
+ }
+ r_posts_list::iterator posts_end() {
+ return posts.end();
+ }
+
+ r_account_ptr create_account(account_t * account = NULL);
+ r_account_ptr create_account(const std::string& name);
+
+ friend void to_xml(std::ostream& out, r_journal_ptr journal);
+};
+
+class r_item_t : public r_base_t
+{
+protected:
+ item_t * base_item;
+
+ item_t * ptr() {
+ return base_item;
+ }
+ const item_t * ptr() const {
+ return base_item;
+ }
+
+ r_journal_ptr journal_ptr;
+
+public:
+ r_item_t(r_journal_ptr _journal_ptr, item_t * _item)
+ : r_base_t(), base_item(_item), journal_ptr(_journal_ptr) {
+ TRACE_CTOR(r_item_t, "r_journal_ptr, item_t *");
+ }
+ r_item_t(const r_item_t& other)
+ : r_base_t(other),
+ base_item(other.base_item),
+ journal_ptr(other.journal_ptr) {
+ TRACE_CTOR(r_item_t, "copy");
+ }
+ virtual ~r_item_t() {
+ TRACE_DTOR(r_item_t);
+ }
+
+ const optional<position_t> position() const;
+
+ string id() const {
+ return ptr()->id();
+ }
+ std::size_t seq() const {
+ return ptr()->seq();
+ }
+
+ date_t date() const;
+ void set_date(const date_t& when);
+
+ item_t::state_t state() const;
+ void set_state(item_t::state_t val);
+
+ string payee() const;
+ void set_payee(const string& name);
+
+ optional<string> note() const {
+ return ptr()->note;
+ }
+
+ bool has_tag(const string& tag) const {
+ return ptr()->has_tag(tag);
+ }
+ bool has_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask = none) const {
+ return ptr()->has_tag(tag_mask, value_mask);
+ }
+
+ optional<value_t> get_tag(const string& tag) const {
+ return ptr()->get_tag(tag);
+ }
+ optional<value_t> get_tag(const mask_t& tag_mask,
+ const optional<mask_t>& value_mask = none) const {
+ return ptr()->get_tag(tag_mask, value_mask);
+ }
+
+ void set_tag(const string& tag,
+ const optional<value_t>& value = none,
+ const bool overwrite_existing = true) {
+ ptr()->set_tag(tag, value, overwrite_existing);
+ }
+
+ /**
+ * Symbol scope methods.
+ */
+ virtual void define(const symbol_t::kind_t, const string&,
+ expr_t::ptr_op_t);
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& name);
+
+ friend class r_journal_t;
+ friend void to_xml(std::ostream& out, r_item_ptr item);
+};
+
+class r_xact_t : public r_item_t
+{
+ xact_t * ptr() {
+ return reinterpret_cast<xact_t *>(base_item);
+ }
+ const xact_t * ptr() const {
+ return reinterpret_cast<const xact_t *>(base_item);
+ }
+
+ r_posts_list posts;
+
+public:
+ r_xact_t(r_journal_ptr journal_ptr, xact_t * _xact)
+ : r_item_t(journal_ptr, reinterpret_cast<item_t *>(_xact)) {
+ TRACE_CTOR(r_xact_t, "r_journal_ptr, xact_t *");
+ }
+ r_xact_t(const r_xact_t& other)
+ : r_item_t(other),
+ posts(other.posts) {
+ TRACE_CTOR(r_xact_t, "copy");
+ }
+ virtual ~r_xact_t() {
+ TRACE_DTOR(r_xact_t);
+ }
+
+ virtual string description();
+
+ void add_post(r_post_ptr post);
+
+#if 0
+ r_post_ptr create_post(post_t * post = NULL, r_account_ptr account = NULL);
+ r_post_ptr create_post(r_post_ptr post, r_account_ptr account = NULL);
+#endif
+
+ r_posts_list::iterator posts_begin() {
+ return posts.begin();
+ }
+ r_posts_list::iterator posts_end() {
+ return posts.end();
+ }
+
+ string code() const;
+ string payee() const;
+
+ friend class r_journal_t;
+ friend void to_xml(std::ostream& out, r_xact_ptr xact);
+};
+
+class r_post_t : public r_item_t
+{
+ post_t * ptr() {
+ return reinterpret_cast<post_t *>(base_item);
+ }
+ const post_t * ptr() const {
+ return reinterpret_cast<const post_t *>(base_item);
+ }
+
+ r_xact_ptr xact_ptr;
+ r_account_ptr account_ptr;
+
+ void set_xact(r_xact_ptr ptr) {
+ xact_ptr = ptr;
+ }
+ void set_account(r_account_ptr ptr) {
+ account_ptr = ptr;
+ }
+
+public:
+ r_post_t(r_journal_ptr journal_ptr, post_t * _post,
+ r_xact_ptr _xact_ptr, r_account_ptr _account_ptr)
+ : r_item_t(journal_ptr, reinterpret_cast<item_t *>(_post)),
+ xact_ptr(_xact_ptr), account_ptr(_account_ptr) {
+ TRACE_CTOR(r_post_t, "r_journal_ptr, post_t *, r_xact_ptr, r_account_ptr");
+ }
+ r_post_t(const r_post_t& other)
+ : r_item_t(other),
+ xact_ptr(other.xact_ptr),
+ account_ptr(other.account_ptr) {
+ TRACE_CTOR(r_post_t, "copy");
+ }
+ virtual ~r_post_t() {
+ TRACE_DTOR(r_post_t);
+ }
+
+ virtual string description();
+
+ string payee() const;
+
+ r_xact_ptr xact();
+ r_account_ptr account();
+
+ value_t amount() const;
+ value_t cost() const;
+
+ std::size_t count() const;
+ value_t running_total() const;
+
+ optional<datetime_t> checkin() const;
+ optional<datetime_t> checkout() const;
+
+ friend class r_journal_t;
+ friend void to_xml(std::ostream& out, r_post_ptr post);
+};
+
+typedef std::map<string, r_account_ptr> r_accounts_map;
+
+class r_account_t : public r_base_t
+{
+ r_journal_ptr journal_ptr;
+ r_account_ptr parent_ptr;
+ r_accounts_map accounts;
+ r_posts_list posts;
+
+ string name;
+
+ mutable string _fullname;
+
+public:
+ r_account_t(r_journal_ptr _journal_ptr, r_account_ptr _parent_ptr,
+ string _name)
+ : r_base_t(), journal_ptr(_journal_ptr), parent_ptr(_parent_ptr),
+ name(_name) {
+ TRACE_CTOR(r_account_t, "r_journal_ptr, r_account_ptr, string");
+ }
+ r_account_t(const r_account_t& other)
+ : r_base_t(other),
+ journal_ptr(other.journal_ptr),
+ parent_ptr(other.parent_ptr),
+ accounts(other.accounts),
+ posts(other.posts),
+ name(other.name),
+ _fullname(other._fullname) {
+ TRACE_CTOR(r_account_t, "copy");
+ }
+ virtual ~r_account_t() {
+ TRACE_DTOR(r_account_t);
+ }
+
+ virtual string description();
+
+ void add_post(r_post_ptr post);
+
+ r_posts_list::iterator posts_begin() {
+ return posts.begin();
+ }
+ r_posts_list::iterator posts_end() {
+ return posts.end();
+ }
+
+ r_account_ptr create_account(const std::string& name);
+
+ string fullname() const;
+
+ /**
+ * Symbol scope methods.
+ */
+ virtual void define(const symbol_t::kind_t, const string&,
+ expr_t::ptr_op_t) {}
+ virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
+ const string& fname) {
+ return NULL;
+ }
+
+ friend class r_journal_t;
+ friend void to_xml(std::ostream& out, r_account_ptr account);
+};
+
+template <typename PostsIterator>
+void populate_journal(r_journal_ptr journal, report_t& report,
+ PostsIterator iter, predicate_t& pred)
+{
+ while (post_t * post = *iter) {
+ bind_scope_t bound_scope(report, *post);
+ if (pred.calc(bound_scope))
+ journal->add_post(post);
+
+ iter.increment();
+ }
+}
+
+} // namespace ledger
+
+#endif /* DOCUMENT_MODEL */
+
+#endif // _VIEWS_H
diff --git a/src/xact.cc b/src/xact.cc
index 596b39fa..226fd5ab 100644
--- a/src/xact.cc
+++ b/src/xact.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -35,6 +35,7 @@
#include "post.h"
#include "account.h"
#include "journal.h"
+#include "context.h"
#include "pool.h"
namespace ledger {
@@ -54,6 +55,9 @@ xact_base_t::~xact_base_t()
// If the posting is a temporary, it will be destructed when the
// temporary is.
assert(! post->has_flags(ITEM_TEMP));
+
+ if (post->account)
+ post->account->remove_post(post);
checked_delete(post);
}
}
@@ -108,6 +112,46 @@ value_t xact_base_t::magnitude() const
return halfbal;
}
+namespace {
+ inline bool account_ends_with_special_char(const string& name) {
+ string::size_type len(name.length());
+ return (std::isdigit(name[len - 1]) || name[len - 1] == ')' ||
+ name[len - 1] == '}' || name[len - 1] == ']');
+ }
+
+ struct add_balancing_post
+ {
+ bool first;
+ xact_base_t& xact;
+ post_t * null_post;
+
+ explicit add_balancing_post(xact_base_t& _xact, post_t * _null_post)
+ : first(true), xact(_xact), null_post(_null_post) {
+ TRACE_CTOR(add_balancing_post, "xact_base_t&, post_t *");
+ }
+ add_balancing_post(const add_balancing_post& other)
+ : first(other.first), xact(other.xact), null_post(other.null_post) {
+ TRACE_CTOR(add_balancing_post, "copy");
+ }
+ ~add_balancing_post() throw() {
+ TRACE_DTOR(add_balancing_post);
+ }
+
+ void operator()(const amount_t& amount) {
+ if (first) {
+ null_post->amount = amount.negated();
+ null_post->add_flags(POST_CALCULATED);
+ first = false;
+ } else {
+ unique_ptr<post_t> p(new post_t(null_post->account, amount.negated(),
+ ITEM_GENERATED | POST_CALCULATED));
+ p->set_state(null_post->state());
+ xact.add_post(p.release());
+ }
+ }
+ };
+}
+
bool xact_base_t::finalize()
{
// Scan through and compute the total balance for the xact. This is used
@@ -133,8 +177,19 @@ bool xact_base_t::finalize()
p.rounded().reduced() : p.reduced());
}
else if (null_post) {
- throw_(std::logic_error,
- _("Only one posting with null amount allowed per transaction"));
+ bool post_account_bad =
+ account_ends_with_special_char(post->account->fullname());
+ bool null_post_account_bad =
+ account_ends_with_special_char(null_post->account->fullname());
+
+ if (post_account_bad || null_post_account_bad)
+ throw_(std::logic_error,
+ _("Posting with null amount's account may be mispelled:\n \"%1\"")
+ << (post_account_bad ? post->account->fullname() :
+ null_post->account->fullname()));
+ else
+ throw_(std::logic_error,
+ _("Only one posting with null amount allowed per transaction"));
}
else {
null_post = post;
@@ -266,11 +321,13 @@ bool xact_base_t::finalize()
cost_breakdown_t breakdown =
commodity_pool_t::current_pool->exchange
- (post->amount, *post->cost, false,
+ (post->amount, *post->cost, false, ! post->has_flags(POST_COST_VIRTUAL),
datetime_t(date(), time_duration(0, 0, 0, 0)));
if (post->amount.has_annotation() && post->amount.annotation().price) {
if (breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) {
+ DEBUG("xact.finalize", "breakdown.basis_cost = " << breakdown.basis_cost);
+ DEBUG("xact.finalize", "breakdown.final_cost = " << breakdown.final_cost);
if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) {
DEBUG("xact.finalize", "gain_loss = " << gain_loss);
gain_loss.in_place_round();
@@ -323,43 +380,17 @@ bool xact_base_t::finalize()
// generated to balance them all.
DEBUG("xact.finalize", "there was a null posting");
-
- if (balance.is_balance()) {
- const balance_t& bal(balance.as_balance());
- typedef std::map<string, amount_t> sorted_amounts_map;
- sorted_amounts_map samp;
- foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) {
- std::pair<sorted_amounts_map::iterator, bool> result =
- samp.insert(sorted_amounts_map::value_type(pair.first->mapping_key(),
- pair.second));
- assert(result.second);
- }
-
- bool first = true;
- foreach (sorted_amounts_map::value_type& pair, samp) {
- if (first) {
- null_post->amount = pair.second.negated();
- null_post->add_flags(POST_CALCULATED);
- first = false;
- } else {
- post_t * p = new post_t(null_post->account, pair.second.negated(),
- ITEM_GENERATED | POST_CALCULATED);
- p->set_state(null_post->state());
- add_post(p);
- }
- }
- }
- else if (balance.is_amount()) {
- null_post->amount = balance.as_amount().negated();
- null_post->add_flags(POST_CALCULATED);
- }
- else if (balance.is_long()) {
- null_post->amount = amount_t(- balance.as_long());
- null_post->add_flags(POST_CALCULATED);
- }
- else if (! balance.is_null() && ! balance.is_realzero()) {
+ add_balancing_post post_adder(*this, null_post);
+
+ if (balance.is_balance())
+ balance.as_balance_lval().map_sorted_amounts(post_adder);
+ else if (balance.is_amount())
+ post_adder(balance.as_amount_lval());
+ else if (balance.is_long())
+ post_adder(balance.to_amount());
+ else if (! balance.is_null() && ! balance.is_realzero())
throw_(balance_error, _("Transaction does not balance"));
- }
+
balance = NULL_VALUE;
}
@@ -457,6 +488,9 @@ bool xact_base_t::verify()
xact_t::xact_t(const xact_t& e)
: xact_base_t(e), code(e.code), payee(e.payee)
+#ifdef DOCUMENT_MODEL
+ , data(NULL)
+#endif
{
TRACE_CTOR(xact_t, "copy");
}
@@ -467,30 +501,10 @@ void xact_t::add_post(post_t * post)
xact_base_t::add_post(post);
}
-string xact_t::idstring() const
-{
- std::ostringstream buf;
- buf << format_date(*_date, FMT_WRITTEN);
- buf << payee;
- magnitude().number().print(buf);
- return buf.str();
-}
-
-string xact_t::id() const
-{
- return sha1sum(idstring());
-}
-
namespace {
value_t get_magnitude(xact_t& xact) {
return xact.magnitude();
}
- value_t get_idstring(xact_t& xact) {
- return string_value(xact.idstring());
- }
- value_t get_id(xact_t& xact) {
- return string_value(xact.id());
- }
value_t get_code(xact_t& xact) {
if (xact.code)
@@ -554,13 +568,6 @@ expr_t::ptr_op_t xact_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(get_wrapper<&get_code>);
break;
- case 'i':
- if (name == "id")
- return WRAP_FUNCTOR(get_wrapper<&get_id>);
- else if (name == "idstring")
- return WRAP_FUNCTOR(get_wrapper<&get_idstring>);
- break;
-
case 'm':
if (name == "magnitude")
return WRAP_FUNCTOR(get_wrapper<&get_magnitude>);
@@ -592,7 +599,6 @@ bool xact_t::valid() const
}
namespace {
-
bool post_pred(expr_t::ptr_op_t op, post_t& post)
{
switch (op->kind) {
@@ -609,6 +615,9 @@ namespace {
else
break;
+ case expr_t::op_t::O_EQ:
+ return post_pred(op->left(), post) == post_pred(op->right(), post);
+
case expr_t::op_t::O_NOT:
return ! post_pred(op->left(), post);
@@ -631,10 +640,9 @@ namespace {
throw_(calc_error, _("Unhandled operator"));
return false;
}
+}
-} // unnamed namespace
-
-void auto_xact_t::extend_xact(xact_base_t& xact)
+void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context)
{
posts_list initial_posts(xact.posts.begin(), xact.posts.end());
@@ -646,6 +654,8 @@ void auto_xact_t::extend_xact(xact_base_t& xact)
if (initial_post->has_flags(ITEM_GENERATED))
continue;
+ bind_scope_t bound_scope(*scope_t::default_scope, *initial_post);
+
bool matches_predicate = false;
if (try_quick_match) {
try {
@@ -673,14 +683,13 @@ void auto_xact_t::extend_xact(xact_base_t& xact)
DEBUG("xact.extend.fail",
"The quick matcher failed, going back to regular eval");
try_quick_match = false;
- matches_predicate = predicate(*initial_post);
+ matches_predicate = predicate(bound_scope);
}
} else {
- matches_predicate = predicate(*initial_post);
+ matches_predicate = predicate(bound_scope);
}
- if (matches_predicate) {
- bind_scope_t bound_scope(*scope_t::default_scope, *initial_post);
+ if (matches_predicate) {
if (deferred_notes) {
foreach (deferred_tag_data_t& data, *deferred_notes) {
if (data.apply_to_post == NULL)
@@ -688,18 +697,19 @@ void auto_xact_t::extend_xact(xact_base_t& xact)
data.overwrite_existing);
}
}
+
if (check_exprs) {
- foreach (check_expr_pair& pair, *check_exprs) {
- if (pair.second == auto_xact_t::EXPR_GENERAL) {
+ foreach (expr_t::check_expr_pair& pair, *check_exprs) {
+ if (pair.second == expr_t::EXPR_GENERAL) {
pair.first.calc(bound_scope);
}
else if (! pair.first.calc(bound_scope).to_boolean()) {
- if (pair.second == auto_xact_t::EXPR_ASSERTION) {
+ if (pair.second == expr_t::EXPR_ASSERTION)
throw_(parse_error,
_("Transaction assertion failed: %1") << pair.first);
- } else {
- warning_(_("Transaction check failed: %1") << pair.first);
- }
+ else
+ context.warning(STR(_("Transaction check failed: %1")
+ << pair.first));
}
}
}
@@ -770,20 +780,25 @@ void auto_xact_t::extend_xact(xact_base_t& xact)
post_t * new_post = new post_t(account, amt);
new_post->copy_details(*post);
new_post->add_flags(ITEM_GENERATED);
-
- xact.add_post(new_post);
- new_post->account->add_post(new_post);
-
- if (new_post->must_balance())
- needs_further_verification = true;
+ new_post->account =
+ journal->register_account(account->fullname(), new_post,
+ journal->master);
if (deferred_notes) {
foreach (deferred_tag_data_t& data, *deferred_notes) {
- if (data.apply_to_post == post)
+ if (! data.apply_to_post || data.apply_to_post == post)
new_post->parse_tags(data.tag_data.c_str(), bound_scope,
data.overwrite_existing);
}
}
+
+ extend_post(*new_post, *journal);
+
+ xact.add_post(new_post);
+ new_post->account->add_post(new_post);
+
+ if (new_post->must_balance())
+ needs_further_verification = true;
}
}
}
@@ -817,9 +832,9 @@ void to_xml(std::ostream& out, const xact_t& xact)
push_xml y(out, "date");
to_xml(out, *xact._date, false);
}
- if (xact._date_eff) {
- push_xml y(out, "effective-date");
- to_xml(out, *xact._date_eff, false);
+ if (xact._date_aux) {
+ push_xml y(out, "aux-date");
+ to_xml(out, *xact._date_aux, false);
}
if (xact.code) {
diff --git a/src/xact.h b/src/xact.h
index 5d7912fc..59430285 100644
--- a/src/xact.h
+++ b/src/xact.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -49,6 +49,7 @@ namespace ledger {
class post_t;
class journal_t;
+class parse_context_t;
typedef std::list<post_t *> posts_list;
@@ -108,7 +109,15 @@ public:
optional<string> code;
string payee;
- xact_t() {
+#ifdef DOCUMENT_MODEL
+ mutable void * data;
+#endif
+
+ xact_t()
+#ifdef DOCUMENT_MODEL
+ : data(NULL)
+#endif
+ {
TRACE_CTOR(xact_t, "");
}
xact_t(const xact_t& e);
@@ -129,9 +138,6 @@ public:
virtual void add_post(post_t * post);
- string idstring() const;
- string id() const;
-
virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
const string& name);
@@ -155,21 +161,11 @@ private:
class auto_xact_t : public xact_base_t
{
public:
- predicate_t predicate;
- bool try_quick_match;
-
+ predicate_t predicate;
+ bool try_quick_match;
std::map<string, bool> memoized_results;
- enum xact_expr_kind_t {
- EXPR_GENERAL,
- EXPR_ASSERTION,
- EXPR_CHECK
- };
-
- typedef std::pair<expr_t, xact_expr_kind_t> check_expr_pair;
- typedef std::list<check_expr_pair> check_expr_list;
-
- optional<check_expr_list> check_exprs;
+ optional<expr_t::check_expr_list> check_exprs;
struct deferred_tag_data_t {
string tag_data;
@@ -180,22 +176,39 @@ public:
bool _overwrite_existing)
: tag_data(_tag_data), overwrite_existing(_overwrite_existing),
apply_to_post(NULL) {}
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ /** Serialization. */
+ deferred_tag_data_t() : apply_to_post(NULL) {}
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */) {
+ ar & tag_data;
+ ar & overwrite_existing;
+ ar & apply_to_post;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
};
typedef std::list<deferred_tag_data_t> deferred_notes_list;
optional<deferred_notes_list> deferred_notes;
+ post_t * active_post;
- auto_xact_t() : try_quick_match(true) {
+ auto_xact_t() : try_quick_match(true), active_post(NULL) {
TRACE_CTOR(auto_xact_t, "");
}
auto_xact_t(const auto_xact_t& other)
: xact_base_t(), predicate(other.predicate),
- try_quick_match(other.try_quick_match) {
+ try_quick_match(other.try_quick_match),
+ active_post(other.active_post) {
TRACE_CTOR(auto_xact_t, "copy");
}
auto_xact_t(const predicate_t& _predicate)
- : predicate(_predicate), try_quick_match(true)
+ : predicate(_predicate), try_quick_match(true), active_post(NULL)
{
TRACE_CTOR(auto_xact_t, "const predicate_t&");
}
@@ -214,15 +227,15 @@ public:
}
}
- virtual void parse_tags(const char * p,
- scope_t&,
- bool overwrite_existing = true) {
+ virtual void parse_tags(const char * p, scope_t&,
+ bool overwrite_existing = true) {
if (! deferred_notes)
deferred_notes = deferred_notes_list();
deferred_notes->push_back(deferred_tag_data_t(p, overwrite_existing));
+ deferred_notes->back().apply_to_post = active_post;
}
- virtual void extend_xact(xact_base_t& xact);
+ virtual void extend_xact(xact_base_t& xact, parse_context_t& context);
#if defined(HAVE_BOOST_SERIALIZATION)
private:
diff --git a/src/xml.cc b/src/xml.cc
index 15710d44..560db805 100644
--- a/src/xml.cc
+++ b/src/xml.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -52,6 +52,7 @@ namespace {
out << "\">\n";
out << "<name>" << acct->name << "</name>\n";
+ out << "<fullname>" << acct->fullname() << "</fullname>\n";
value_t total = acct->amount();
if (! total.is_null()) {
out << "<amount>\n";
diff --git a/src/xml.h b/src/xml.h
index 5d14dab3..871fd120 100644
--- a/src/xml.h
+++ b/src/xml.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2010, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are