diff options
author | John Wiegley <johnw@newartisans.com> | 2006-03-06 03:53:36 +0000 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2008-04-13 02:41:29 -0400 |
commit | b737cd8e6dd185beeae902caa4eee6c4cee8bc36 (patch) | |
tree | 967b198e0d52c8d0f2ae2215c90224a6ff48194d | |
parent | d02f74efea3e6b631810bfd3c3d8adcaa4299902 (diff) | |
download | fork-ledger-b737cd8e6dd185beeae902caa4eee6c4cee8bc36.tar.gz fork-ledger-b737cd8e6dd185beeae902caa4eee6c4cee8bc36.tar.bz2 fork-ledger-b737cd8e6dd185beeae902caa4eee6c4cee8bc36.zip |
Added a DATETIME value type.
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | NEWS | 53 | ||||
-rw-r--r-- | amount.cc | 58 | ||||
-rw-r--r-- | amount.h | 14 | ||||
-rw-r--r-- | balance.cc | 205 | ||||
-rw-r--r-- | balance.h | 139 | ||||
-rw-r--r-- | config.cc | 79 | ||||
-rw-r--r-- | config.h | 29 | ||||
-rw-r--r-- | datetime.cc | 2 | ||||
-rw-r--r-- | datetime.h | 74 | ||||
-rw-r--r-- | format.cc | 32 | ||||
-rw-r--r-- | format.h | 6 | ||||
-rw-r--r-- | main.cc | 123 | ||||
-rw-r--r-- | startup.cc | 2 | ||||
-rw-r--r-- | timing.h | 6 | ||||
-rw-r--r-- | valexpr.cc | 115 | ||||
-rw-r--r-- | valexpr.h | 5 | ||||
-rw-r--r-- | value.cc | 360 | ||||
-rw-r--r-- | value.h | 72 | ||||
-rw-r--r-- | walk.cc | 2 |
20 files changed, 990 insertions, 388 deletions
diff --git a/Makefile.am b/Makefile.am index c3acc1a1..ba96fd9b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -133,4 +133,4 @@ all-clean: maintainer-clean acconf.h.in aclocal.m4 autom4te config.guess config.sub \ configure depcomp install-sh libtool ltconfig ltmain.sh \ missing stamp texinfo.tex Makefile.in mkinstalldirs \ - elisp-comp elc-stamp + elisp-comp elc-stamp py-compile @@ -3,6 +3,59 @@ * 2.5 +- Add a new --only predicate, which occurs during transaction + processing between --limit and --display. Here is a summary of how + the three supported predicates are used: + + --limit "a>100" + + This flag limits computation to *only transactions whose amount + is greater than 100 of a given commodity*. It means that if you + scan your dining expenses, for example, only individual bills + greater than $100 would be caculated by the report. + + --only "a>100" + + This flag happens much later than --limit, and corresponding + more directly to what one normally expects. If --limit isn't + used, then ALL your dining expenses contribute to the report, + *but only those calculated transactions whose value is greater + than $100 are used*. This becomes important when doing a + monthly costs report, for example, because it makes the + following command possible: + + ledger -M --only "a>100" reg ^Expenses:Food + + This shows only *months* whose amount is greater than 100. If + --limit had been used, it would have been a monthly summary of + all individual dinner bills greater than 100 -- which is a very + different thing. + + --display "a>100" + + This predicate does not constrain calculation, but only display. + Consider the same command as above: + + ledger -M --display "a>100" reg ^Expenses:Food + + This displays only lines whose amount is greater than 100, *yet + the running total still includes amounts from all transactions*. + This command has more particular application, such as showing + the current month's checking register while still giving a + correct ending balance: + + ledger --display "d>[this month]" reg Checking + + Note that these predicates can be combined. Here is a report that + considers only food bills whose individual cost is greater than + $20, but shows the monthly total only if it is greater than $500. + Finally, we only display the months of the last year, but we + retain an accurate running total with respect to the entire ledger + file: + + ledger -M --limit "a>20" --only "a>200" \ + --display "year == yearof([last year])" reg ^Expenses:Food + - There have a few changes to value expression syntax. The most significant incompatibilities being: @@ -10,6 +10,12 @@ namespace ledger { +bool do_cleanup = true; + +bool amount_t::keep_price = false; +bool amount_t::keep_date = false; +bool amount_t::keep_tag = false; + #define BIGINT_BULK_ALLOC 0x0001 #define BIGINT_KEEP_PREC 0x0002 @@ -32,10 +38,7 @@ class amount_t::bigint_t { ref(1), index(0) { mpz_init_set(val, other.val); } - ~bigint_t() { - assert(ref == 0); - mpz_clear(val); - } + ~bigint_t(); }; unsigned int sizeof_bigint_t() { @@ -44,10 +47,16 @@ unsigned int sizeof_bigint_t() { #define MPZ(x) ((x)->val) -static mpz_t temp; -static mpz_t divisor; +static mpz_t temp; // these are the global temp variables +static mpz_t divisor; + static amount_t::bigint_t true_value; +inline amount_t::bigint_t::~bigint_t() { + assert(ref == 0 || (! do_cleanup && this == &true_value)); + mpz_clear(val); +} + base_commodities_map commodity_base_t::commodities; commodity_base_t::updater_t * commodity_base_t::updater = NULL; @@ -93,6 +102,9 @@ static struct _init_amounts { } ~_init_amounts() { + if (! do_cleanup) + return; + mpz_clear(temp); mpz_clear(divisor); @@ -1355,19 +1367,19 @@ void amount_t::annotate_commodity(const amount_t& price, DEBUG_PRINT("amounts.commodities", " Annotated amount is " << *this); } -amount_t amount_t::reduce_commodity(const bool keep_price, - const bool keep_date, - const bool keep_tag) const +amount_t amount_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) const { if (! commodity().annotated || - (keep_price && keep_date && keep_tag)) + (_keep_price && _keep_date && _keep_tag)) return *this; DEBUG_PRINT("amounts.commodities", "Reducing commodity for amount " << *this << std::endl - << " keep price " << keep_price << " " - << " keep date " << keep_date << " " - << " keep tag " << keep_tag); + << " keep price " << _keep_price << " " + << " keep date " << _keep_date << " " + << " keep tag " << _keep_tag); annotated_commodity_t& ann_comm(static_cast<annotated_commodity_t&>(commodity())); @@ -1375,13 +1387,13 @@ amount_t amount_t::reduce_commodity(const bool keep_price, commodity_t * new_comm; - if ((keep_price && ann_comm.price) || - (keep_date && ann_comm.date) || - (keep_tag && ! ann_comm.tag.empty())) + if ((_keep_price && ann_comm.price) || + (_keep_date && ann_comm.date) || + (_keep_tag && ! ann_comm.tag.empty())) { new_comm = annotated_commodity_t::find_or_create - (*ann_comm.base, keep_price ? ann_comm.price : amount_t(), - keep_date ? ann_comm.date : 0, keep_tag ? ann_comm.tag : ""); + (*ann_comm.base, _keep_price ? ann_comm.price : amount_t(), + _keep_date ? ann_comm.date : 0, _keep_tag ? ann_comm.tag : ""); } else { new_comm = commodity_t::find_or_create(ann_comm.base_symbol()); } @@ -1543,8 +1555,6 @@ amount_t commodity_base_t::value(const std::time_t moment) return price; } -std::string annotated_commodity_t::date_format = "%Y/%m/%d"; - void annotated_commodity_t::write_annotations(std::ostream& out, const amount_t& price, @@ -1554,12 +1564,8 @@ annotated_commodity_t::write_annotations(std::ostream& out, if (price) out << " {" << price << '}'; - if (date) { - char buf[128]; - std::strftime(buf, 127, annotated_commodity_t::date_format.c_str(), - std::localtime(&date)); - out << " [" << buf << ']'; - } + if (date) + out << " [" << datetime_t(date) << ']'; if (! tag.empty()) out << " (" << tag << ')'; @@ -15,6 +15,8 @@ namespace ledger { +extern bool do_cleanup; + class commodity_t; class amount_t @@ -22,6 +24,10 @@ class amount_t public: class bigint_t; + static bool keep_price; + static bool keep_date; + static bool keep_tag; + protected: void _init(); void _copy(const amount_t& amt); @@ -76,9 +82,9 @@ class amount_t void annotate_commodity(const amount_t& price, const std::time_t date = 0, const std::string& tag = ""); - amount_t reduce_commodity(const bool keep_price = false, - const bool keep_date = false, - const bool keep_tag = false) const; + amount_t strip_annotations(const bool _keep_price = keep_price, + const bool _keep_date = keep_date, + const bool _keep_tag = keep_tag) const; void clear_commodity() { commodity_ = NULL; } @@ -543,8 +549,6 @@ class annotated_commodity_t : public commodity_t std::time_t date; std::string tag; - static std::string date_format; - static void write_annotations(std::ostream& out, const amount_t& price, const std::time_t date, @@ -13,6 +13,17 @@ amount_t balance_t::amount(const commodity_t& commodity) const amounts_map::const_iterator i = amounts.begin(); return (*i).second; } + else if (amounts.size() > 1) { + // Try stripping annotations before giving an error. + balance_t temp(strip_annotations()); + if (temp.amounts.size() == 1) + return temp.amount(commodity); + + std::ostringstream errmsg; + errmsg << "Requested amount of a balance with multiple commodities: " + << *this; + throw amount_error(errmsg.str()); + } } else if (amounts.size() > 0) { amounts_map::const_iterator i = amounts.find(&commodity); @@ -63,15 +74,16 @@ std::time_t balance_t::date() const return temp; } -balance_t balance_t::reduce(const bool keep_price, const bool keep_date, - const bool keep_tag) const +balance_t balance_t::strip_annotations(const bool keep_price, + const bool keep_date, + const bool keep_tag) const { balance_t temp; for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - temp += (*i).second.reduce_commodity(keep_price, keep_date, keep_tag); + temp += (*i).second.strip_annotations(keep_price, keep_date, keep_tag); return temp; } @@ -176,42 +188,148 @@ void balance_t::write(std::ostream& out, balance_t& balance_t::operator*=(const balance_t& bal) { - if (! *this || ! bal) { - return (*this = 0L); + if (realzero() || bal.realzero()) { + return *this = 0L; } - else if (amounts.size() == 1 && bal.amounts.size() == 1) { + else if (bal.amounts.size() == 1) { return *this *= (*bal.amounts.begin()).second; } + else if (amounts.size() == 1) { + return *this = bal * *this; + } else { + // Since we would fail with an error at this point otherwise, try + // stripping annotations to see if we can come up with a + // reasonable result. The user will not notice any annotations + // missing (since they are viewing a stripped report anyway), only + // that some of their value expression may not see any pricing or + // date data because of this operation. + + balance_t temp(bal.strip_annotations()); + if (temp.amounts.size() == 1) + return *this *= temp; + temp = strip_annotations(); + if (temp.amounts.size() == 1) + return *this = bal * temp; + std::ostringstream errmsg; - errmsg << "It makes no sense to multiply two balances: " - << *this << " * " << bal; + errmsg << "Cannot multiply two balances: " << *this << " * " << bal; throw amount_error(errmsg.str()); } } -balance_t& balance_t::operator/=(const balance_t& bal) +balance_t& balance_t::operator*=(const amount_t& amt) { - if (! *this) { - return (*this = 0L); + if (realzero() || amt.realzero()) { + return *this = 0L; + } + else if (! amt.commodity()) { + // Multiplying by the null commodity causes all amounts to be + // increased by the same factor. + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second *= amt; + } + else if (amounts.size() == 1 && + (*amounts.begin()).first == &amt.commodity()) { + (*amounts.begin()).second *= amt; } - else if (! bal) { + else { + amounts_map::iterator i = amounts.find(&amt.commodity()); + if (i != amounts.end()) { + (*i).second *= amt; + } else { + // Try stripping annotations before giving an error. + balance_t temp(strip_annotations()); + if (temp.amounts.size() == 1 && + (*temp.amounts.begin()).first == &amt.commodity()) { + return *this = temp * amt; + } else { + i = temp.amounts.find(&amt.commodity()); + if (i != temp.amounts.end()) + return *this = temp * amt; + } + + std::ostringstream errmsg; + errmsg << "Attempt to multiply balance by a commodity" + << " not found in that balance: " + << *this << " * " << amt; + throw amount_error(errmsg.str()); + } + } + return *this; +} + +balance_t& balance_t::operator/=(const balance_t& bal) +{ + if (bal.realzero()) { std::ostringstream errmsg; errmsg << "Attempt to divide by zero: " << *this << " / " << bal; throw amount_error(errmsg.str()); } - else if (amounts.size() == 1 && bal.amounts.size() == 1) { + else if (realzero()) { + return *this = 0L; + } + else if (bal.amounts.size() == 1) { return *this /= (*bal.amounts.begin()).second; } else if (*this == bal) { - return (*this = 1L); + return *this = 1L; } else { + // Try stripping annotations before giving an error. + balance_t temp(bal.strip_annotations()); + if (temp.amounts.size() == 1) + return *this /= temp; + + std::ostringstream errmsg; + errmsg << "Cannot divide between two balances: " << *this << " / " << bal; + throw amount_error(errmsg.str()); + } +} + +balance_t& balance_t::operator/=(const amount_t& amt) +{ + if (amt.realzero()) { std::ostringstream errmsg; - errmsg << "It makes no sense to divide two balances: " - << *this << " / " << bal; + errmsg << "Attempt to divide by zero: " << *this << " / " << amt; throw amount_error(errmsg.str()); } + else if (realzero()) { + return *this = 0L; + } + else if (! amt.commodity()) { + // Dividing by the null commodity causes all amounts to be + // decreased by the same factor. + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second /= amt; + } + else if (amounts.size() == 1 && + (*amounts.begin()).first == &amt.commodity()) { + (*amounts.begin()).second /= amt; + } + else { + amounts_map::iterator i = amounts.find(&amt.commodity()); + if (i != amounts.end()) { + (*i).second /= amt; + } else { + // Try stripping annotations before giving an error. + balance_t temp(strip_annotations()); + if (temp.amounts.size() == 1 && + (*temp.amounts.begin()).first == &amt.commodity()) + return *this = temp / amt; + + std::ostringstream errmsg; + errmsg << "Attempt to divide balance by a commodity" + << " not found in that balance: " + << *this << " * " << amt; + throw amount_error(errmsg.str()); + } + } + return *this; } balance_t::operator amount_t() const @@ -223,6 +341,11 @@ balance_t::operator amount_t() const return amount_t(); } else { + // Try stripping annotations before giving an error. + balance_t temp(strip_annotations()); + if (temp.amounts.size() == 1) + return (*temp.amounts.begin()).second; + std::ostringstream errmsg; errmsg << "Cannot convert a balance with " << "multiple commodities to an amount: " << *this; @@ -230,27 +353,6 @@ balance_t::operator amount_t() const } } -balance_pair_t& balance_pair_t::operator/=(const balance_pair_t& bal_pair) -{ - if (bal_pair.cost && ! cost) - cost = new balance_t(quantity); - quantity /= bal_pair.quantity; - if (cost) - *cost /= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity; - return *this; -} - -balance_pair_t& balance_pair_t::add(const amount_t& amount, - const amount_t * a_cost) -{ - if (a_cost && ! cost) - cost = new balance_t(quantity); - quantity += amount; - if (cost) - *cost += a_cost ? *a_cost : amount; - return *this; -} - } // namespace ledger #ifdef USE_BOOST_PYTHON @@ -353,13 +455,19 @@ void export_balance() .def("__len__", balance_len) .def("__getitem__", balance_getitem) - .def("negate", &balance_t::negate) + .def("valid", &balance_t::valid) + + .def("realzero", &balance_t::realzero) .def("amount", &balance_t::amount) .def("value", &balance_t::value) .def("price", &balance_t::price) - .def("reduce", &balance_t::reduce) + .def("date", &balance_t::date) + .def("strip_annotations", &balance_t::strip_annotations) .def("write", &balance_t::write) - .def("valid", &balance_t::valid) + .def("abs", &balance_t::abs) + .def("round", &balance_t::round) + .def("negate", &balance_t::negate) + .def("negated", &balance_t::negated) ; class_< balance_pair_t > ("BalancePair") @@ -436,16 +544,23 @@ void export_balance() .def("__len__", balance_pair_len) .def("__getitem__", balance_pair_getitem) - .add_property("cost", - make_getter(&balance_pair_t::cost, - return_value_policy<reference_existing_object>())) + .def("valid", &balance_pair_t::valid) - .def("negate", &balance_pair_t::negate) + .def("realzero", &balance_pair_t::realzero) .def("amount", &balance_pair_t::amount) .def("value", &balance_pair_t::value) + .def("price", &balance_pair_t::price) + .def("date", &balance_pair_t::date) + .def("strip_annotations", &balance_pair_t::strip_annotations) .def("write", &balance_pair_t::write) + .def("abs", &balance_pair_t::abs) + .def("round", &balance_pair_t::round) + .def("negate", &balance_pair_t::negate) + .def("negated", &balance_pair_t::negated) - .def("valid", &balance_pair_t::valid) + .add_property("cost", + make_getter(&balance_pair_t::cost, + return_value_policy<reference_existing_object>())) ; } @@ -2,6 +2,7 @@ #define _BALANCE_H #include "amount.h" +#include "datetime.h" #include <map> #include <ctime> @@ -35,13 +36,13 @@ class balance_t *this += (*i).second; } balance_t(const amount_t& amt) { - if (amt) + if (! amt.realzero()) amounts.insert(amounts_pair(&amt.commodity(), amt)); } template <typename T> balance_t(T value) { amount_t amt(value); - if (amt) + if (! amt.realzero()) amounts.insert(amounts_pair(&amt.commodity(), amt)); } @@ -80,7 +81,7 @@ class balance_t amounts_map::iterator i = amounts.find(&amt.commodity()); if (i != amounts.end()) (*i).second += amt; - else if (amt) + else if (! amt.realzero()) amounts.insert(amounts_pair(&amt.commodity(), amt)); return *this; } @@ -148,53 +149,14 @@ class balance_t // multiplication and divide balance_t& operator*=(const balance_t& bal); - balance_t& operator*=(const amount_t& amt) { - // Multiplying by the null commodity causes all amounts to be - // increased by the same factor. - if (amt.realzero()) { - amounts.clear(); - } - else if (! amt.commodity()) { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second *= amt; - } - else if (amounts.size() == 1) { - (*amounts.begin()).second *= amt; - } - else { - amounts_map::iterator i = amounts.find(&amt.commodity()); - if (i != amounts.end()) - (*i).second *= amt; - } - return *this; - } + balance_t& operator*=(const amount_t& amt); template <typename T> balance_t& operator*=(T val) { return *this *= amount_t(val); } balance_t& operator/=(const balance_t& bal); - balance_t& operator/=(const amount_t& amt) { - // Dividing by the null commodity causes all amounts to be - // increased by the same factor. - if (! amt.commodity()) { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second /= amt; - } - else if (amounts.size() == 1) { - (*amounts.begin()).second /= amt; - } - else { - amounts_map::iterator i = amounts.find(&amt.commodity()); - if (i != amounts.end()) - (*i).second /= amt; - } - return *this; - } + balance_t& operator/=(const amount_t& amt); template <typename T> balance_t& operator/=(T val) { return *this /= amount_t(val); @@ -415,13 +377,27 @@ class balance_t return false; } - amount_t amount(const commodity_t& commodity) const; - balance_t value(const std::time_t moment) const; - balance_t price() const; + bool realzero() const { + if (amounts.size() == 0) + return true; + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (! (*i).second.realzero()) + return false; + return true; + } + + amount_t amount(const commodity_t& commodity = + *commodity_t::null_commodity) const; + balance_t value(const std::time_t moment = now) const; + balance_t price() const; std::time_t date() const; - balance_t reduce(const bool keep_price = false, - const bool keep_date = false, - const bool keep_tag = false) const; + + balance_t + strip_annotations(const bool keep_price = amount_t::keep_price, + const bool keep_date = amount_t::keep_date, + const bool keep_tag = amount_t::keep_tag) const; void write(std::ostream& out, const int first_width, const int latter_width = -1) const; @@ -440,17 +416,6 @@ class balance_t if ((*i).second.commodity()) (*i).second = (*i).second.round(); } - - bool realzero() const { - if (amounts.size() == 0) - return true; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (! (*i).second.realzero()) - return false; - return true; - } }; inline balance_t abs(const balance_t& bal) { @@ -496,7 +461,6 @@ class balance_pair_t delete cost; cost = NULL; } - quantity = bal_pair.quantity; if (bal_pair.cost) cost = new balance_t(*bal_pair.cost); @@ -651,7 +615,14 @@ class balance_pair_t return *this *= amount_t(val); } - balance_pair_t& operator/=(const balance_pair_t& bal_pair); + balance_pair_t& operator/=(const balance_pair_t& bal_pair) { + if (bal_pair.cost && ! cost) + cost = new balance_t(quantity); + quantity /= bal_pair.quantity; + if (cost) + *cost /= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity; + return *this; + } balance_pair_t& operator/=(const balance_t& bal) { quantity /= bal; if (cost) @@ -813,34 +784,60 @@ class balance_pair_t } // test for non-zero (use ! for zero) - operator bool() const { - return quantity; - } operator balance_t() const { return quantity; } operator amount_t() const { return quantity; } + operator bool() const { + return quantity; + } + + bool realzero() const { + return ((! cost || cost->realzero()) && quantity.realzero()); + } void abs() { quantity.abs(); if (cost) cost->abs(); } - amount_t amount(const commodity_t& commodity) const { + amount_t amount(const commodity_t& commodity = + *commodity_t::null_commodity) const { return quantity.amount(commodity); } - balance_t value(const std::time_t moment) const { + balance_t value(const std::time_t moment = now) const { return quantity.value(moment); } + balance_t price() const { + return quantity.price(); + } + std::time_t date() const { + return quantity.date(); + } + + balance_t + strip_annotations(const bool keep_price = amount_t::keep_price, + const bool keep_date = amount_t::keep_date, + const bool keep_tag = amount_t::keep_tag) const { + return quantity.strip_annotations(keep_price, keep_date, keep_tag); + } + void write(std::ostream& out, const int first_width, const int latter_width = -1) const { quantity.write(out, first_width, latter_width); } balance_pair_t& add(const amount_t& amount, - const amount_t * a_cost = NULL); + const amount_t * a_cost = NULL) { + if (a_cost && ! cost) + cost = new balance_t(quantity); + quantity += amount; + if (cost) + *cost += a_cost ? *a_cost : amount; + return *this; + } bool valid() { return quantity.valid() && (! cost || cost->valid()); @@ -850,10 +847,6 @@ class balance_pair_t quantity.round(); if (cost) cost->round(); } - - bool realzero() const { - return ((! cost || cost->realzero()) && quantity.realzero()); - } }; inline balance_pair_t abs(const balance_pair_t& bal_pair) { @@ -71,8 +71,9 @@ void config_t::reset() prices_format = "%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"; pricesdb_format = "P %[%Y/%m/%d %H:%M:%S] %A %t\n"; - predicate = ""; - display_predicate = ""; + predicate = ""; + secondary_predicate = ""; + display_predicate = ""; head_entries = 0; tail_entries = 0; @@ -91,6 +92,8 @@ void config_t::reset() show_revalued_only = false; download_quotes = false; debug_mode = false; + verbose_mode = false; + trace_mode = false; keep_price = false; keep_date = false; keep_tag = false; @@ -301,14 +304,12 @@ void config_t::process_options(const std::string& command, // Now setup the various formatting strings - if (! date_format.empty()) { - format_t::date_format = date_format; - annotated_commodity_t::date_format = date_format; - } + if (! date_format.empty()) + datetime_t::date_format = date_format; - format_t::keep_price = keep_price; - format_t::keep_date = keep_date; - format_t::keep_tag = keep_tag; + amount_t::keep_price = keep_price; + amount_t::keep_date = keep_date; + amount_t::keep_tag = keep_tag; } item_handler<transaction_t> * @@ -348,15 +349,21 @@ config_t::chain_xact_handlers(const std::string& command, // transactions which can be reconciled to a given balance // (calculated against the transactions which it receives). if (! reconcile_balance.empty()) { - value_t target_balance(reconcile_balance); - time_t cutoff = now; + std::time_t cutoff = now; if (! reconcile_date.empty()) parse_date(reconcile_date.c_str(), &cutoff); ptrs.push_back(formatter = - new reconcile_transactions(formatter, target_balance, - cutoff)); + new reconcile_transactions + (formatter, value_t(reconcile_balance), cutoff)); } + // filter_transactions will only pass through transactions + // matching the `secondary_predicate'. + if (! secondary_predicate.empty()) + ptrs.push_back(formatter = + new filter_transactions(formatter, + secondary_predicate)); + // sort_transactions will sort all the transactions it sees, based // on the `sort_order' value expression. if (! sort_string.empty()) @@ -718,6 +725,14 @@ OPT_BEGIN(debug, ":") { ::setenv("DEBUG_CLASS", optarg, 1); } OPT_END(debug); +OPT_BEGIN(verbose, "") { + config->verbose_mode = true; +} OPT_END(verbose); + +OPT_BEGIN(trace, "") { + config->trace_mode = true; +} OPT_END(trace); + ////////////////////////////////////////////////////////////////////// // // Report filtering @@ -1016,6 +1031,14 @@ OPT_BEGIN(limit, "l:") { config->predicate += ")"; } OPT_END(limit); +OPT_BEGIN(only, ":") { + if (! config->secondary_predicate.empty()) + config->secondary_predicate += "&"; + config->secondary_predicate += "("; + config->secondary_predicate += optarg; + config->secondary_predicate += ")"; +} OPT_END(only); + OPT_BEGIN(display, "d:") { if (! config->display_predicate.empty()) config->display_predicate += "&"; @@ -1153,6 +1176,7 @@ option_t config_options[CONFIG_OPTIONS_SIZE] = { { "market", 'V', false, opt_market, false }, { "monthly", 'M', false, opt_monthly, false }, { "no-cache", '\0', false, opt_no_cache, false }, + { "only", '\0', true, opt_only, false }, { "output", 'o', true, opt_output, false }, { "pager", '\0', true, opt_pager, false }, { "percentage", '%', false, opt_percentage, false }, @@ -1178,8 +1202,10 @@ option_t config_options[CONFIG_OPTIONS_SIZE] = { { "total", 'T', true, opt_total, false }, { "total-data", 'J', false, opt_total_data, false }, { "totals", '\0', false, opt_totals, false }, + { "trace", '\0', false, opt_trace, false }, { "unbudgeted", '\0', false, opt_unbudgeted, false }, { "uncleared", 'U', false, opt_uncleared, false }, + { "verbose", '\0', false, opt_verbose, false }, { "version", 'v', false, opt_version, false }, { "weekly", 'W', false, opt_weekly, false }, { "wide", 'w', false, opt_wide, false }, @@ -1189,4 +1215,31 @@ option_t config_options[CONFIG_OPTIONS_SIZE] = { { "yearly", 'Y', false, opt_yearly, false }, }; +////////////////////////////////////////////////////////////////////// + +void trace(const std::string& cat, const std::string& str) +{ + char buf[32]; + std::time_t now = std::time(NULL); + std::strftime(buf, 31, "%H:%M:%S", std::localtime(&now)); + + std::cerr << buf << " " << cat << ": " << str << std::endl; +} + +void trace_push(const std::string& cat, const std::string& str, + timing_t& timer) +{ + timer.start(); + trace(cat, str); +} + +void trace_pop(const std::string& cat, const std::string& str, + timing_t& timer) +{ + timer.stop(); + std::ostringstream out; + out << str << ": " << (double(timer.cumulative) / double(CLOCKS_PER_SEC)) << "s"; + trace(cat, out.str()); +} + } // namespace ledger @@ -2,6 +2,7 @@ #define _CONFIG_H #include "ledger.h" +#include "timing.h" #include <iostream> #include <memory> @@ -22,6 +23,7 @@ class config_t std::string output_file; std::string account; std::string predicate; + std::string secondary_predicate; std::string display_predicate; std::string report_period; std::string report_period_sort; @@ -66,6 +68,8 @@ class config_t bool use_cache; bool cache_dirty; bool debug_mode; + bool verbose_mode; + bool trace_mode; bool keep_price; bool keep_date; bool keep_tag; @@ -102,7 +106,7 @@ class config_t std::list<item_handler<transaction_t> *>& ptrs); }; -#define CONFIG_OPTIONS_SIZE 81 +#define CONFIG_OPTIONS_SIZE 84 extern option_t config_options[CONFIG_OPTIONS_SIZE]; void option_help(std::ostream& out); @@ -112,6 +116,29 @@ void option_help(std::ostream& out); #define OPT_END(tag) +////////////////////////////////////////////////////////////////////// + +void trace(const std::string& cat, const std::string& str); +void trace_push(const std::string& cat, const std::string& str, + timing_t& timer); +void trace_pop(const std::string& cat, const std::string& str, + timing_t& timer); + +#define TRACE(cat, msg) if (config.trace_mode) trace(#cat, msg) +#define TRACE_(cat, msg) if (trace_mode) trace(#cat, msg) + +#define TRACE_PUSH(cat, msg) \ + timing_t timer_ ## cat(#cat); \ + if (config.trace_mode) trace_push(#cat, msg, timer_ ## cat) +#define TRACE_PUSH_(cat, msg) \ + timing_t timer_ ## cat(#cat); \ + if (trace_mode) trace_push(#cat, msg, timer_ ## cat) + +#define TRACE_POP(cat, msg) \ + if (config.trace_mode) trace_pop(#cat, msg, timer_ ## cat) +#define TRACE_POP_(cat, msg) \ + if (trace_mode) trace_pop(#cat, msg, timer_ ## cat) + } // namespace ledger #endif // _CONFIG_H diff --git a/datetime.cc b/datetime.cc index cf26d0c9..f5d071c3 100644 --- a/datetime.cc +++ b/datetime.cc @@ -34,6 +34,8 @@ const char * formats[] = { NULL }; +std::string datetime_t::date_format = "%Y/%m/%d"; + std::time_t interval_t::first(const std::time_t moment) const { std::time_t quant = begin; @@ -4,6 +4,71 @@ #include <ctime> #include <sstream> +struct interval_t; + +struct datetime_t +{ + std::time_t when; + + static std::string date_format; + + datetime_t(const std::time_t _when) : when(_when) {} + + datetime_t& operator+=(const long secs) { + when += secs; + return *this; + } + datetime_t& operator-=(const long secs) { + when -= secs; + return *this; + } + + datetime_t& operator=(const interval_t& period); + datetime_t& operator+=(const interval_t& period); + +#define DEF_DATETIME_OP(OP) \ + bool operator OP(const datetime_t& other) { \ + return when OP other.when; \ + } + + DEF_DATETIME_OP(<) + DEF_DATETIME_OP(<=) + DEF_DATETIME_OP(>) + DEF_DATETIME_OP(>=) + DEF_DATETIME_OP(==) + DEF_DATETIME_OP(!=) + + operator bool() const { + return when != 0; + } + operator long() const { + return (long)when; + } + operator double() const { + return (double)when; + } + + int year() const { + struct std::tm * desc = std::localtime(&when); + return desc->tm_year + 1900; + } + int month() const { + struct std::tm * desc = std::localtime(&when); + return desc->tm_mon + 1; + } + int day() const { + struct std::tm * desc = std::localtime(&when); + return desc->tm_mday; + } +}; + +inline std::ostream& operator<<(std::ostream& out, const datetime_t& moment) { + char buf[32]; + std::strftime(buf, 31, datetime_t::date_format.c_str(), + std::localtime(&moment.when)); + out << buf; +} + struct interval_t { unsigned int years; @@ -36,6 +101,15 @@ struct interval_t void parse(std::istream& in); }; +inline datetime_t& datetime_t::operator=(const interval_t& period) { + when = period.first(); + return *this; +} +inline datetime_t& datetime_t::operator+=(const interval_t& period) { + when = period.increment(when); + return *this; +} + extern std::time_t now; extern int now_year; extern char input_format[128]; @@ -7,10 +7,6 @@ namespace ledger { -bool format_t::keep_price = false; -bool format_t::keep_date = false; -bool format_t::keep_tag = false; - std::string truncated(const std::string& str, unsigned int width, const int style) { @@ -72,8 +68,6 @@ std::string partial_account_name(const account_t& account) return name; } -std::string format_t::date_format = "%Y/%m/%d"; - element_t * format_t::parse_elements(const std::string& fmt) { std::auto_ptr<element_t> result; @@ -204,11 +198,11 @@ element_t * format_t::parse_elements(const std::string& fmt) case 'd': current->type = element_t::COMPLETE_DATE_STRING; - current->chars = format_t::date_format; + current->chars = datetime_t::date_format; break; case 'D': current->type = element_t::DATE_STRING; - current->chars = format_t::date_format; + current->chars = datetime_t::date_format; break; case 'S': current->type = element_t::SOURCE; break; @@ -310,15 +304,29 @@ void format_t::format(std::ostream& out_str, const details_t& details) const calc->compute(value, details); - if (! keep_price || ! keep_date || ! keep_tag) - value = value.reduce(keep_price, keep_date, keep_tag); + if (! amount_t::keep_price || + ! amount_t::keep_date || + ! amount_t::keep_tag) { + switch (value.type) { + case value_t::AMOUNT: + case value_t::BALANCE: + case value_t::BALANCE_PAIR: + value = value.strip_annotations(); + break; + default: + break; + } + } switch (value.type) { case value_t::BOOLEAN: - out << (*((bool *) value.data) ? "1" : "0"); + out << (*((bool *) value.data) ? "true" : "false"); break; case value_t::INTEGER: - out << *((unsigned int *) value.data); + out << *((long *) value.data); + break; + case value_t::DATETIME: + out << *((datetime_t *) value.data); break; case value_t::AMOUNT: out << *((amount_t *) value.data); @@ -72,12 +72,6 @@ struct format_t std::string format_string; element_t * elements; - static bool keep_price; - static bool keep_date; - static bool keep_tag; - - static std::string date_format; - format_t() : elements(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor format_t"); } @@ -10,6 +10,8 @@ #include <cstring> #include <ctime> +#include "acconf.h" + #ifdef HAVE_UNIX_PIPES #include <sys/types.h> #include <sys/wait.h> @@ -18,27 +20,11 @@ #endif #include "ledger.h" -#include "timing.h" using namespace ledger; -namespace { - TIMER_DEF_(setup); - TIMER_DEF_(parse); - TIMER_DEF_(process); - TIMER_DEF_(walk); - TIMER_DEF_(cleanup); - TIMER_DEF_(cache_write); -} - -int parse_and_report(int argc, char * argv[], char * envp[]) +int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) { - TIMER_START(setup); - - config_t config; - - std::auto_ptr<journal_t> journal(new journal_t); - // Configure the terminus for value expressions ledger::terminus = now; @@ -60,6 +46,8 @@ int parse_and_report(int argc, char * argv[], char * envp[]) config.use_cache = config.data_file.empty() && config.price_db.empty(); DEBUG_PRINT("ledger.config.cache", "1. use_cache = " << config.use_cache); + TRACE(main, "Processing options and environment variables"); + config.process_environment(envp, "LEDGER_"); #if 1 @@ -90,6 +78,14 @@ int parse_and_report(int argc, char * argv[], char * envp[]) config.use_cache = false; DEBUG_PRINT("ledger.config.cache", "2. use_cache = " << config.use_cache); + TRACE(main, std::string("Initialization file is ") + config.init_file); + TRACE(main, std::string("Price database is ") + config.price_db); + TRACE(main, std::string("Binary cache is ") + config.cache_file); + TRACE(main, std::string("Main journal is ") + config.data_file); + + TRACE(main, std::string("Based on option settings, binary cache ") + + (config.use_cache ? "WILL " : "will NOT ") + "be used"); + // Read the command word, canonicalize it to its one letter form, // then configure the system based on the kind of report to be // generated @@ -124,15 +120,23 @@ int parse_and_report(int argc, char * argv[], char * envp[]) } else if (command == "parse") { value_auto_ptr expr(ledger::parse_value_expr(*arg)); - if (config.debug_mode) { + if (config.verbose_mode) { ledger::dump_value_expr(std::cout, expr.get()); std::cout << std::endl; } value_t result = expr->compute(); - if (! config.keep_price || ! config.keep_date || ! config.keep_tag) - result = result.reduce(config.keep_price, config.keep_date, - config.keep_tag); + if (! config.keep_price || ! config.keep_date || ! config.keep_tag) { + switch (result.type) { + case value_t::AMOUNT: + case value_t::BALANCE: + case value_t::BALANCE_PAIR: + result = result.strip_annotations(); + break; + default: + break; + } + } std::cout << result << std::endl; return 0; } @@ -142,21 +146,19 @@ int parse_and_report(int argc, char * argv[], char * envp[]) else throw error(std::string("Unrecognized command '") + command + "'"); - TIMER_STOP(setup); - // Parse initialization files, ledger data, price database, etc. - TIMER_START(parse); + std::auto_ptr<journal_t> journal(new journal_t); - if (parse_ledger_data(config, journal.get()) == 0) - throw error("Please specify ledger file using -f" - " or LEDGER_FILE environment variable."); + { TRACE_PUSH(parser, "Parsing journal file"); - TIMER_STOP(parse); + if (parse_ledger_data(config, journal.get()) == 0) + throw error("Please specify ledger file using -f" + " or LEDGER_FILE environment variable."); - // process the command word and its following arguments + TRACE_POP(parser, "Finished parsing"); } - TIMER_START(process); + // process the command word and its following arguments std::string first_arg; if (command == "w") { @@ -165,6 +167,9 @@ int parse_and_report(int argc, char * argv[], char * envp[]) first_arg = *arg++; } + TRACE(options, std::string("Post-processing options ") + + "for command \"" + command + "\""); + config.process_options(command, arg, args.end()); std::auto_ptr<entry_t> new_entry; @@ -232,14 +237,22 @@ int parse_and_report(int argc, char * argv[], char * envp[]) if (command == "expr") { value_auto_ptr expr(ledger::parse_value_expr(*arg)); - if (config.debug_mode) { + if (config.verbose_mode) { ledger::dump_value_expr(std::cout, expr.get()); std::cout << std::endl; } value_t result = expr->compute(); - if (! config.keep_price || ! config.keep_date || ! config.keep_tag) - result = result.reduce(config.keep_price, config.keep_date, - config.keep_tag); + if (! config.keep_price || ! config.keep_date || ! config.keep_tag) { + switch (result.type) { + case value_t::AMOUNT: + case value_t::BALANCE: + case value_t::BALANCE_PAIR: + result = result.strip_annotations(); + break; + default: + break; + } + } std::cout << result << std::endl; return 0; } @@ -265,12 +278,8 @@ int parse_and_report(int argc, char * argv[], char * envp[]) else format = &config.print_format; - TIMER_STOP(process); - // Walk the entries based on the report type and the options - TIMER_START(walk); - item_handler<transaction_t> * formatter; std::list<item_handler<transaction_t> *> formatter_ptrs; @@ -290,9 +299,13 @@ int parse_and_report(int argc, char * argv[], char * envp[]) formatter = new format_transactions(*out, *format); if (command == "w") { + TRACE_PUSH(text_writer, "Writing journal file"); write_textual_journal(*journal, first_arg, *formatter, config.write_hdr_format, *out); + TRACE_POP(text_writer, "Finished writing"); } else { + TRACE_PUSH(main, "Walking journal entries"); + formatter = config.chain_xact_handlers(command, formatter, journal.get(), journal->master, formatter_ptrs); if (command == "e") @@ -304,11 +317,15 @@ int parse_and_report(int argc, char * argv[], char * envp[]) if (command != "P" && command != "D") formatter->flush(); + + TRACE_POP(main, "Finished entry walk"); } // For the balance and equity reports, output the sum totals. if (command == "b") { + TRACE_PUSH(main, "Walking journal accounts"); + format_account acct_formatter(*out, *format, config.display_predicate); sum_accounts(*journal->master); walk_accounts(*journal->master, acct_formatter, config.sort_string); @@ -322,19 +339,22 @@ int parse_and_report(int argc, char * argv[], char * envp[]) acct_formatter.format.format(*out, details_t(*journal->master)); } } + TRACE_POP(main, "Finished account walk"); } else if (command == "E") { + TRACE_PUSH(main, "Walking journal accounts"); + format_equity acct_formatter(*out, *format, config.display_predicate); sum_accounts(*journal->master); walk_accounts(*journal->master, acct_formatter, config.sort_string); acct_formatter.flush(); - } - TIMER_STOP(walk); - - TIMER_START(cleanup); + TRACE_POP(main, "Finished account walk"); + } #if DEBUG_LEVEL >= BETA + { TRACE_PUSH(cleanup, "Cleaning up allocated memory"); + clear_transaction_xdata xact_cleaner; walk_entries(journal->entries, xact_cleaner); @@ -349,17 +369,19 @@ int parse_and_report(int argc, char * argv[], char * envp[]) i != formatter_ptrs.end(); i++) delete *i; -#endif - TIMER_STOP(cleanup); + TRACE_POP(cleanup, "Finished cleaning"); } +#endif // Write out the binary cache, if need be if (config.use_cache && config.cache_dirty && ! config.cache_file.empty()) { - TIMER_START(cache_write); + TRACE_PUSH(binary_cache, "Writing journal file"); + std::ofstream stream(config.cache_file.c_str()); write_binary_journal(stream, journal.get()); - TIMER_STOP(cache_write); + + TRACE_POP(binary_cache, "Finished writing"); } #ifdef HAVE_UNIX_PIPES @@ -380,7 +402,14 @@ int parse_and_report(int argc, char * argv[], char * envp[]) int main(int argc, char * argv[], char * envp[]) { try { - return parse_and_report(argc, argv, envp); +#if DEBUG_LEVEL < BETA + ledger::do_cleanup = false; +#endif + config_t config; + TRACE_PUSH(main, "Starting Ledger " PACKAGE_VERSION); + int status = parse_and_report(config, argc, argv, envp); + TRACE_POP(main, "Ledger done"); + return status; } catch (const std::exception& err) { std::cerr << "Error: " << err.what() << std::endl; @@ -49,6 +49,8 @@ namespace { startup::~startup() { + if (! ledger::do_cleanup) + return; shutdown_parser_support(); } } @@ -20,6 +20,9 @@ class timing_t timing_t(const std::string& _symbol, const std::string& _category) : begin(0), cumulative(0), symbol(_symbol), category(_category) {} + timing_t(const std::string& _symbol) + : begin(0), cumulative(0), symbol(_symbol) {} + ~timing_t() { std::string cls = "timing.results."; cls += symbol; @@ -33,6 +36,9 @@ class timing_t line = _line; begin = std::clock(); } + void start() { + begin = std::clock(); + } void stop() { cumulative += std::clock() - begin; @@ -35,6 +35,7 @@ bool compute_amount(value_expr_t * expr, amount_t& amt, amt = *((amount_t *) result.data); break; + case value_t::DATETIME: case value_t::BALANCE: case value_t::BALANCE_PAIR: return false; @@ -68,8 +69,12 @@ value_expr_t::~value_expr_t() delete constant_a; break; - case CONSTANT_I: case CONSTANT_T: + assert(constant_t); + delete constant_t; + break; + + case CONSTANT_I: case CONSTANT_V: break; @@ -145,7 +150,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, result = constant_i; break; case CONSTANT_T: - result = long(constant_t); + result = *constant_t; break; case CONSTANT_A: result = *constant_a; @@ -155,7 +160,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, break; case F_NOW: - result = long(terminus); + result = datetime_t(terminus); break; case AMOUNT: @@ -262,37 +267,37 @@ void value_expr_t::compute(value_t& result, const details_t& details, case DATE: if (details.xact && transaction_has_xdata(*details.xact) && transaction_xdata_(*details.xact).date) - result = long(transaction_xdata_(*details.xact).date); + result = datetime_t(transaction_xdata_(*details.xact).date); else if (details.xact) - result = long(details.xact->date()); + result = datetime_t(details.xact->date()); else if (details.entry) - result = long(details.entry->date()); + result = datetime_t(details.entry->date()); else - result = long(terminus); + result = datetime_t(terminus); break; case ACT_DATE: if (details.xact && transaction_has_xdata(*details.xact) && transaction_xdata_(*details.xact).date) - result = long(transaction_xdata_(*details.xact).date); + result = datetime_t(transaction_xdata_(*details.xact).date); else if (details.xact) - result = long(details.xact->actual_date()); + result = datetime_t(details.xact->actual_date()); else if (details.entry) - result = long(details.entry->actual_date()); + result = datetime_t(details.entry->actual_date()); else - result = long(terminus); + result = datetime_t(terminus); break; case EFF_DATE: if (details.xact && transaction_has_xdata(*details.xact) && transaction_xdata_(*details.xact).date) - result = long(transaction_xdata_(*details.xact).date); + result = datetime_t(transaction_xdata_(*details.xact).date); else if (details.xact) - result = long(details.xact->effective_date()); + result = datetime_t(details.xact->effective_date()); else if (details.entry) - result = long(details.entry->effective_date()); + result = datetime_t(details.entry->effective_date()); else - result = long(terminus); + result = datetime_t(terminus); break; case CLEARED: @@ -373,11 +378,40 @@ void value_expr_t::compute(value_t& result, const details_t& details, index = 0; expr = find_leaf(context, 1, index); - amount_t moment; - if (compute_amount(expr, moment, NULL, context)) + value_t moment; + expr->compute(moment, details, context); + if (moment.type == value_t::DATETIME) { + result.cast(value_t::INTEGER); + moment.cast(value_t::INTEGER); result -= moment; - else + } else { throw compute_error("Invalid date passed to datecmp(value,date)"); + } + break; + } + + case F_YEAR: + case F_MONTH: + case F_DAY: { + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); + expr->compute(result, details, context); + + // jww (2006-03-05): Generate an error if result is not a DATETIME + std::time_t moment = (long)result; + struct std::tm * desc = std::localtime(&moment); + + switch (kind) { + case F_YEAR: + result = (long)desc->tm_year + 1900L; + break; + case F_MONTH: + result = (long)desc->tm_mon + 1L; + break; + case F_DAY: + result = (long)desc->tm_mday; + break; + } break; } @@ -580,13 +614,12 @@ void value_expr_t::compute(value_t& result, const details_t& details, index = 0; expr = find_leaf(context, 1, index); - - amount_t moment; - if (compute_amount(expr, moment, details.xact, context)) - result = result.value((long)moment); - else + value_t moment; + expr->compute(moment, details, context); + if (moment.type != value_t::DATETIME) throw compute_error("Invalid date passed to P(value,date)"); + result = result.value(*((datetime_t *)moment.data)); break; } @@ -756,14 +789,18 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope) c = peek_next_nonws(in); } + bool definition = false; if (c == '=') { in.get(c); if (peek_next_nonws(in) == '=') { in.unget(); c = '\0'; - goto parsed; // parse this as == operator + } else { + definition = true; } + } + if (definition) { std::auto_ptr<scope_t> params(new scope_t(scope)); int index = 0; @@ -970,7 +1007,7 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope) node.reset(new value_expr_t(value_expr_t::CONSTANT_T)); interval_t timespan(buf); - node->constant_t = timespan.first(); + node->constant_t = new datetime_t(timespan.first()); break; } @@ -1350,6 +1387,28 @@ void init_value_expr() node->set_right(new value_expr_t(value_expr_t::F_DATECMP)); globals->define("datecmp", node); + node = new value_expr_t(value_expr_t::O_DEF); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = 1; + node->set_right(new value_expr_t(value_expr_t::F_YEAR)); + globals->define("yearof", node); + + node = new value_expr_t(value_expr_t::O_DEF); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = 1; + node->set_right(new value_expr_t(value_expr_t::F_MONTH)); + globals->define("monthof", node); + + node = new value_expr_t(value_expr_t::O_DEF); + node->set_left(new value_expr_t(value_expr_t::CONSTANT_I)); + node->left->constant_i = 1; + node->set_right(new value_expr_t(value_expr_t::F_DAY)); + globals->define("dayof", node); + + value_auto_ptr year(parse_boolean_expr("year=yearof(d)", globals)); + value_auto_ptr month(parse_boolean_expr("month=monthof(d)", globals)); + value_auto_ptr day(parse_boolean_expr("day=dayof(d)", globals)); + // Macros node = parse_value_expr("P(a,d)"); globals->define("v", node); @@ -1437,7 +1496,7 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node, out << "CONSTANT_I - " << node->constant_i; break; case value_expr_t::CONSTANT_T: - out << "CONSTANT_T - [" << node->constant_t << ']'; + out << "CONSTANT_T - [" << *(node->constant_t) << ']'; break; case value_expr_t::CONSTANT_A: out << "CONSTANT_A - {" << *(node->constant_a) << '}'; @@ -1481,6 +1540,10 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node, case value_expr_t::F_VALUE: out << "F_VALUE"; break; case value_expr_t::F_PRICE: out << "F_PRICE"; break; case value_expr_t::F_DATE: out << "F_DATE"; break; + case value_expr_t::F_DATECMP: out << "F_DATECMP"; break; + case value_expr_t::F_YEAR: out << "F_YEAR"; break; + case value_expr_t::F_MONTH: out << "F_MONTH"; break; + case value_expr_t::F_DAY: out << "F_DAY"; break; case value_expr_t::O_NOT: out << "O_NOT"; break; case value_expr_t::O_ARG: out << "O_ARG"; break; @@ -113,6 +113,9 @@ struct value_expr_t F_PRICE, F_DATE, F_DATECMP, + F_YEAR, + F_MONTH, + F_DAY, F_CODE_MASK, F_PAYEE_MASK, F_NOTE_MASK, @@ -155,7 +158,7 @@ struct value_expr_t value_expr_t * left; union { - std::time_t constant_t; + datetime_t * constant_t; long constant_i; amount_t * constant_a; value_t * constant_v; @@ -64,6 +64,10 @@ value_t& value_t::operator=(const value_t& value) *((long *) data) = *((long *) value.data); break; + case DATETIME: + *((datetime_t *) data) = *((datetime_t *) value.data); + break; + case AMOUNT: new((amount_t *)data) amount_t(*((amount_t *) value.data)); break; @@ -88,14 +92,17 @@ value_t& value_t::operator=(const value_t& value) value_t& value_t::operator+=(const value_t& value) { + if (value.type == BOOLEAN) + throw value_error("Cannot add a boolean to a value"); + else if (value.type == DATETIME) + throw value_error("Cannot add a date/time to a value"); + switch (type) { case BOOLEAN: + throw value_error("Cannot add a value to a boolean"); + case INTEGER: - cast(INTEGER); switch (value.type) { - case BOOLEAN: - *((long *) data) += (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((long *) data) += *((long *) value.data); break; @@ -117,17 +124,28 @@ value_t& value_t::operator+=(const value_t& value) } break; - case AMOUNT: + case DATETIME: switch (value.type) { - case BOOLEAN: - if (*((bool *) value.data) && - ((amount_t *) data)->commodity()) { - cast(BALANCE); - return *this += value; - } - *((amount_t *) data) += (*((bool *) value.data) ? 1L : 0L); + case INTEGER: + *((datetime_t *) data) += *((long *) value.data); + break; + case AMOUNT: + *((datetime_t *) data) += long(*((amount_t *) value.data)); + break; + case BALANCE: + *((datetime_t *) data) += long(*((balance_t *) value.data)); + break; + case BALANCE_PAIR: + *((datetime_t *) data) += long(*((balance_pair_t *) value.data)); break; + default: + assert(0); + break; + } + break; + case AMOUNT: + switch (value.type) { case INTEGER: if (*((long *) value.data) && ((amount_t *) data)->commodity()) { @@ -164,9 +182,6 @@ value_t& value_t::operator+=(const value_t& value) case BALANCE: switch (value.type) { - case BOOLEAN: - *((balance_t *) data) += (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((balance_t *) data) += *((long *) value.data); break; @@ -188,9 +203,6 @@ value_t& value_t::operator+=(const value_t& value) case BALANCE_PAIR: switch (value.type) { - case BOOLEAN: - *((balance_pair_t *) data) += (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((balance_pair_t *) data) += *((long *) value.data); break; @@ -218,14 +230,17 @@ value_t& value_t::operator+=(const value_t& value) value_t& value_t::operator-=(const value_t& value) { + if (value.type == BOOLEAN) + throw value_error("Cannot subtract a boolean from a value"); + else if (value.type == DATETIME) + throw value_error("Cannot subtract a date/time from a value"); + switch (type) { case BOOLEAN: + throw value_error("Cannot subtract a value from a boolean"); + case INTEGER: - cast(INTEGER); switch (value.type) { - case BOOLEAN: - *((long *) data) -= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((long *) data) -= *((long *) value.data); break; @@ -249,15 +264,6 @@ value_t& value_t::operator-=(const value_t& value) case AMOUNT: switch (value.type) { - case BOOLEAN: - if (*((bool *) value.data) && - ((amount_t *) data)->commodity()) { - cast(BALANCE); - return *this -= value; - } - *((amount_t *) data) -= (*((bool *) value.data) ? 1L : 0L); - break; - case INTEGER: if (*((long *) value.data) && ((amount_t *) data)->commodity()) { @@ -294,9 +300,6 @@ value_t& value_t::operator-=(const value_t& value) case BALANCE: switch (value.type) { - case BOOLEAN: - *((balance_t *) data) -= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((balance_t *) data) -= *((long *) value.data); break; @@ -318,9 +321,6 @@ value_t& value_t::operator-=(const value_t& value) case BALANCE_PAIR: switch (value.type) { - case BOOLEAN: - *((balance_pair_t *) data) -= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((balance_pair_t *) data) -= *((long *) value.data); break; @@ -351,6 +351,11 @@ value_t& value_t::operator-=(const value_t& value) value_t& value_t::operator*=(const value_t& value) { + if (value.type == BOOLEAN) + throw value_error("Cannot multiply a boolean by a value"); + else if (value.type == DATETIME) + throw value_error("Cannot multiply a date/time by a value"); + if (value.realzero()) { *this = 0L; return *this; @@ -358,12 +363,10 @@ value_t& value_t::operator*=(const value_t& value) switch (type) { case BOOLEAN: + throw value_error("Cannot multiply a value by a boolean"); + case INTEGER: - cast(INTEGER); switch (value.type) { - case BOOLEAN: - *((long *) data) *= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((long *) data) *= *((long *) value.data); break; @@ -387,9 +390,6 @@ value_t& value_t::operator*=(const value_t& value) case AMOUNT: switch (value.type) { - case BOOLEAN: - *((amount_t *) data) *= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((amount_t *) data) *= *((long *) value.data); break; @@ -412,9 +412,6 @@ value_t& value_t::operator*=(const value_t& value) case BALANCE: switch (value.type) { - case BOOLEAN: - *((balance_t *) data) *= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((balance_t *) data) *= *((long *) value.data); break; @@ -436,9 +433,6 @@ value_t& value_t::operator*=(const value_t& value) case BALANCE_PAIR: switch (value.type) { - case BOOLEAN: - *((balance_pair_t *) data) *= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((balance_pair_t *) data) *= *((long *) value.data); break; @@ -466,14 +460,17 @@ value_t& value_t::operator*=(const value_t& value) value_t& value_t::operator/=(const value_t& value) { + if (value.type == BOOLEAN) + throw value_error("Cannot divide a boolean by a value"); + else if (value.type == DATETIME) + throw value_error("Cannot divide a date/time by a value"); + switch (type) { case BOOLEAN: + throw value_error("Cannot divide a value by a boolean"); + case INTEGER: - cast(INTEGER); switch (value.type) { - case BOOLEAN: - *((long *) data) /= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((long *) data) /= *((long *) value.data); break; @@ -497,9 +494,6 @@ value_t& value_t::operator/=(const value_t& value) case AMOUNT: switch (value.type) { - case BOOLEAN: - *((amount_t *) data) /= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((amount_t *) data) /= *((long *) value.data); break; @@ -522,9 +516,6 @@ value_t& value_t::operator/=(const value_t& value) case BALANCE: switch (value.type) { - case BOOLEAN: - *((balance_t *) data) /= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((balance_t *) data) /= *((long *) value.data); break; @@ -546,9 +537,6 @@ value_t& value_t::operator/=(const value_t& value) case BALANCE_PAIR: switch (value.type) { - case BOOLEAN: - *((balance_pair_t *) data) /= (*((bool *) value.data) ? 1L : 0L); - break; case INTEGER: *((balance_pair_t *) data) /= *((long *) value.data); break; @@ -586,6 +574,9 @@ bool value_t::operator OP(const value_t& value) \ case INTEGER: \ return *((bool *) data) OP bool(*((long *) value.data)); \ \ + case DATETIME: \ + return *((bool *) data) OP bool(*((datetime_t *) value.data)); \ + \ case AMOUNT: \ return *((bool *) data) OP bool(*((amount_t *) value.data)); \ \ @@ -608,8 +599,11 @@ bool value_t::operator OP(const value_t& value) \ ((long) *((bool *) value.data))); \ \ case INTEGER: \ + return (*((long *) data) OP *((long *) value.data)); \ + \ + case DATETIME: \ return (*((long *) data) OP \ - *((long *) value.data)); \ + ((long) *((datetime_t *) value.data))); \ \ case AMOUNT: \ return (amount_t(*((long *) data)) OP \ @@ -629,15 +623,46 @@ bool value_t::operator OP(const value_t& value) \ } \ break; \ \ + case DATETIME: \ + switch (value.type) { \ + case BOOLEAN: \ + throw value_error("Cannot compare a date/time to a boolean"); \ + \ + case INTEGER: \ + return (*((datetime_t *) data) OP \ + datetime_t(*((long *) value.data))); \ + \ + case DATETIME: \ + return (*((datetime_t *) data) OP \ + *((datetime_t *) value.data)); \ + \ + case AMOUNT: \ + throw value_error("Cannot compare a date/time to an amount"); \ + \ + case BALANCE: \ + throw value_error("Cannot compare a date/time to a balance"); \ + \ + case BALANCE_PAIR: \ + throw value_error("Cannot compare a date/time to a balance pair"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ case AMOUNT: \ switch (value.type) { \ case BOOLEAN: \ - return *((amount_t *) data) OP amount_t(*((bool *) value.data)); \ + throw value_error("Cannot compare an amount to a boolean"); \ \ case INTEGER: \ return (*((amount_t *) data) OP \ amount_t(*((long *) value.data))); \ \ + case DATETIME: \ + throw value_error("Cannot compare an amount to a date/time"); \ + \ case AMOUNT: \ return *((amount_t *) data) OP *((amount_t *) value.data); \ \ @@ -660,11 +685,14 @@ bool value_t::operator OP(const value_t& value) \ case BALANCE: \ switch (value.type) { \ case BOOLEAN: \ - return *((balance_t *) data) OP (long)*((bool *) value.data); \ + throw value_error("Cannot compare a balance to a boolean"); \ \ case INTEGER: \ return *((balance_t *) data) OP *((long *) value.data); \ \ + case DATETIME: \ + throw value_error("Cannot compare a balance to a date/time"); \ + \ case AMOUNT: \ return *((balance_t *) data) OP *((amount_t *) value.data); \ \ @@ -684,13 +712,15 @@ bool value_t::operator OP(const value_t& value) \ case BALANCE_PAIR: \ switch (value.type) { \ case BOOLEAN: \ - return (((balance_pair_t *) data)->quantity OP \ - (long)*((bool *) value.data)); \ + throw value_error("Cannot compare a balance pair to a boolean"); \ \ case INTEGER: \ return (((balance_pair_t *) data)->quantity OP \ *((long *) value.data)); \ \ + case DATETIME: \ + throw value_error("Cannot compare a balance pair to a date/time"); \ + \ case AMOUNT: \ return (((balance_pair_t *) data)->quantity OP \ *((amount_t *) value.data)); \ @@ -727,15 +757,42 @@ value_t::operator long() const { switch (type) { case BOOLEAN: - return *((bool *) data) ? 1L : 0L; + throw value_error("Cannot convert a boolean to an integer"); case INTEGER: return *((long *) data); + case DATETIME: + return *((datetime_t *) data); case AMOUNT: return *((amount_t *) data); case BALANCE: - throw value_error("Cannot convert a value balance to a long"); + throw value_error("Cannot convert a balance to an integer"); case BALANCE_PAIR: - throw value_error("Cannot convert a value balance pair to a long"); + throw value_error("Cannot convert a balance pair to an integer"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator datetime_t() const +{ + switch (type) { + case BOOLEAN: + throw value_error("Cannot convert a boolean to a date/time"); + case INTEGER: + return *((long *) data); + case DATETIME: + return *((datetime_t *) data); + case AMOUNT: + throw value_error("Cannot convert an amount to a date/time"); + case BALANCE: + throw value_error("Cannot convert a balance to a date/time"); + case BALANCE_PAIR: + throw value_error("Cannot convert a balance pair to a date/time"); default: assert(0); @@ -750,15 +807,17 @@ value_t::operator double() const { switch (type) { case BOOLEAN: - return *((bool *) data) ? 1.0 : 0.0; + throw value_error("Cannot convert a boolean to a double"); case INTEGER: return *((long *) data); + case DATETIME: + return *((datetime_t *) data); case AMOUNT: return *((amount_t *) data); case BALANCE: - throw value_error("Cannot convert a value balance to a double"); + throw value_error("Cannot convert a balance to a double"); case BALANCE_PAIR: - throw value_error("Cannot convert a value balance pair to a double"); + throw value_error("Cannot convert a balance pair to a double"); default: assert(0); @@ -776,17 +835,15 @@ void value_t::cast(type_t cast_type) case BOOLEAN: break; case INTEGER: - *((long *) data) = *((bool *) data); - break; + throw value_error("Cannot convert a boolean to an integer"); + case DATETIME: + throw value_error("Cannot convert a boolean to a date/time"); case AMOUNT: - new((amount_t *)data) amount_t(*((bool *) data)); - break; + throw value_error("Cannot convert a boolean to an amount"); case BALANCE: - new((balance_t *)data) balance_t(*((bool *) data)); - break; + throw value_error("Cannot convert a boolean to a balance"); case BALANCE_PAIR: - new((balance_pair_t *)data) balance_pair_t(*((bool *) data)); - break; + throw value_error("Cannot convert a boolean to a balance pair"); default: assert(0); @@ -801,6 +858,9 @@ void value_t::cast(type_t cast_type) break; case INTEGER: break; + case DATETIME: + *((datetime_t *) data) = datetime_t(*((long *) data)); + break; case AMOUNT: new((amount_t *)data) amount_t(*((long *) data)); break; @@ -817,6 +877,29 @@ void value_t::cast(type_t cast_type) } break; + case DATETIME: + switch (cast_type) { + case BOOLEAN: + *((bool *) data) = *((datetime_t *) data); + break; + case INTEGER: + *((long *) data) = *((datetime_t *) data); + break; + case DATETIME: + break; + case AMOUNT: + throw value_error("Cannot convert a date/time to an amount"); + case BALANCE: + throw value_error("Cannot convert a date/time to a balance"); + case BALANCE_PAIR: + throw value_error("Cannot convert a date/time to a balance pair"); + + default: + assert(0); + break; + } + break; + case AMOUNT: switch (cast_type) { case BOOLEAN: { @@ -831,6 +914,8 @@ void value_t::cast(type_t cast_type) *((long *)data) = temp; break; } + case DATETIME: + throw value_error("Cannot convert an amount to a date/time"); case AMOUNT: break; case BALANCE: { @@ -862,6 +947,9 @@ void value_t::cast(type_t cast_type) } case INTEGER: throw value_error("Cannot convert a balance to an integer"); + case DATETIME: + throw value_error("Cannot convert a balance to a date/time"); + case AMOUNT: { balance_t * temp = (balance_t *) data; if (temp->amounts.size() == 1) { @@ -903,6 +991,8 @@ void value_t::cast(type_t cast_type) } case INTEGER: throw value_error("Cannot convert a balance pair to an integer"); + case DATETIME: + throw value_error("Cannot convert a balance pair to a date/time"); case AMOUNT: { balance_t * temp = &((balance_pair_t *) data)->quantity; @@ -951,6 +1041,8 @@ void value_t::negate() case INTEGER: *((long *) data) = - *((long *) data); break; + case DATETIME: + throw value_error("Cannot negate a date/time"); case AMOUNT: ((amount_t *) data)->negate(); break; @@ -976,6 +1068,8 @@ void value_t::abs() if (*((long *) data) < 0) *((long *) data) = - *((long *) data); break; + case DATETIME: + break; case AMOUNT: ((amount_t *) data)->abs(); break; @@ -992,12 +1086,54 @@ void value_t::abs() } } +value_t value_t::value(const std::time_t moment) const +{ + switch (type) { + case BOOLEAN: + throw value_error("Cannot find the value of a boolean"); + case DATETIME: + throw value_error("Cannot find the value of a date/time"); + case INTEGER: + return *this; + case AMOUNT: + return ((amount_t *) data)->value(moment); + case BALANCE: + return ((balance_t *) data)->value(moment); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->quantity.value(moment); + } +} + +void value_t::round() +{ + switch (type) { + case BOOLEAN: + throw value_error("Cannot round a boolean"); + case DATETIME: + throw value_error("Cannot round a date/time"); + case INTEGER: + break; + case AMOUNT: + *((amount_t *) data) = ((amount_t *) data)->round(); + break; + case BALANCE: + ((balance_t *) data)->round(); + break; + case BALANCE_PAIR: + ((balance_pair_t *) data)->round(); + break; + } +} + value_t value_t::price() const { switch (type) { case BOOLEAN: + throw value_error("Cannot find the price of a boolean"); case INTEGER: return *this; + case DATETIME: + throw value_error("Cannot find the price of a date/time"); case AMOUNT: return ((amount_t *) data)->price(); @@ -1020,7 +1156,10 @@ value_t value_t::date() const { switch (type) { case BOOLEAN: + throw value_error("Cannot find the date of a boolean"); case INTEGER: + return 0L; + case DATETIME: return *this; case AMOUNT: @@ -1040,23 +1179,28 @@ value_t value_t::date() const return value_t(); } -value_t value_t::reduce(const bool keep_price, const bool keep_date, - const bool keep_tag) const +value_t value_t::strip_annotations(const bool keep_price, + const bool keep_date, + const bool keep_tag) const { switch (type) { case BOOLEAN: + throw value_error("Cannot strip commodity annotations from a boolean"); case INTEGER: return *this; + case DATETIME: + throw value_error("Cannot strip commodity annotations from a date/time"); case AMOUNT: - return ((amount_t *) data)->reduce_commodity(keep_price, keep_date, - keep_tag); + return ((amount_t *) data)->strip_annotations + (keep_price, keep_date, keep_tag); case BALANCE: - return ((balance_t *) data)->reduce(keep_price, keep_date, keep_tag); - + return ((balance_t *) data)->strip_annotations + (keep_price, keep_date, keep_tag); case BALANCE_PAIR: - return ((balance_pair_t *) data)->quantity.reduce(keep_price, keep_date, - keep_tag); + return ((balance_pair_t *) data)->quantity.strip_annotations + (keep_price, keep_date, keep_tag); + default: assert(0); break; @@ -1069,10 +1213,13 @@ value_t value_t::cost() const { switch (type) { case BOOLEAN: + throw value_error("Cannot find the cost of a boolean"); case INTEGER: case AMOUNT: case BALANCE: return *this; + case DATETIME: + throw value_error("Cannot find the cost of a date/time"); case BALANCE_PAIR: assert(((balance_pair_t *) data)->cost); @@ -1093,6 +1240,9 @@ value_t& value_t::add(const amount_t& amount, const amount_t * cost) { switch (type) { case BOOLEAN: + throw value_error("Cannot add an amount to a boolean"); + case DATETIME: + throw value_error("Cannot add an amount to a date/time"); case INTEGER: case AMOUNT: if (cost) { @@ -1150,6 +1300,7 @@ long value_len(value_t& value) switch (value.type) { case value_t::BOOLEAN: case value_t::INTEGER: + case value_t::DATETIME: case value_t::AMOUNT: return 1; @@ -1178,9 +1329,14 @@ amount_t value_getitem(value_t& value, int i) switch (value.type) { case value_t::BOOLEAN: + throw value_error("Cannot cast a boolean to an amount"); + case value_t::INTEGER: return long(value); + case value_t::DATETIME: + throw value_error("Cannot cast a date/time to an amount"); + case value_t::AMOUNT: return *((amount_t *) value.data); @@ -1213,6 +1369,7 @@ void export_value() .def(init<std::string>()) .def(init<double>()) .def(init<long>()) + .def(init<datetime_t>()) .def(self + self) .def(self + other<balance_pair_t>()) @@ -1301,12 +1458,14 @@ void export_value() .def(self < other<balance_t>()) .def(self < other<amount_t>()) .def(self < long()) + .def(self < other<datetime_t>()) .def(self < double()) .def(other<balance_pair_t>() < self) .def(other<balance_t>() < self) .def(other<amount_t>() < self) .def(long() < self) + .def(other<datetime_t>() < self) .def(double() < self) .def(self <= self) @@ -1314,12 +1473,14 @@ void export_value() .def(self <= other<balance_t>()) .def(self <= other<amount_t>()) .def(self <= long()) + .def(self <= other<datetime_t>()) .def(self <= double()) .def(other<balance_pair_t>() <= self) .def(other<balance_t>() <= self) .def(other<amount_t>() <= self) .def(long() <= self) + .def(other<datetime_t>() <= self) .def(double() <= self) .def(self > self) @@ -1327,12 +1488,14 @@ void export_value() .def(self > other<balance_t>()) .def(self > other<amount_t>()) .def(self > long()) + .def(self > other<datetime_t>()) .def(self > double()) .def(other<balance_pair_t>() > self) .def(other<balance_t>() > self) .def(other<amount_t>() > self) .def(long() > self) + .def(other<datetime_t>() > self) .def(double() > self) .def(self >= self) @@ -1340,12 +1503,14 @@ void export_value() .def(self >= other<balance_t>()) .def(self >= other<amount_t>()) .def(self >= long()) + .def(self >= other<datetime_t>()) .def(self >= double()) .def(other<balance_pair_t>() >= self) .def(other<balance_t>() >= self) .def(other<amount_t>() >= self) .def(long() >= self) + .def(other<datetime_t>() >= self) .def(double() >= self) .def(self == self) @@ -1353,12 +1518,14 @@ void export_value() .def(self == other<balance_t>()) .def(self == other<amount_t>()) .def(self == long()) + .def(self == other<datetime_t>()) .def(self == double()) .def(other<balance_pair_t>() == self) .def(other<balance_t>() == self) .def(other<amount_t>() == self) .def(long() == self) + .def(other<datetime_t>() == self) .def(double() == self) .def(self != self) @@ -1366,12 +1533,14 @@ void export_value() .def(self != other<balance_t>()) .def(self != other<amount_t>()) .def(self != long()) + .def(self != other<datetime_t>()) .def(self != double()) .def(other<balance_pair_t>() != self) .def(other<balance_t>() != self) .def(other<amount_t>() != self) .def(long() != self) + .def(other<datetime_t>() != self) .def(double() != self) .def(! self) @@ -1386,16 +1555,23 @@ void export_value() .def("__len__", value_len) .def("__getitem__", value_getitem) + .def("abs", &value_t::abs) .def("cast", &value_t::cast) - .def("negate", &value_t::negate) - .def("price", &value_t::price) .def("cost", &value_t::cost) + .def("price", &value_t::price) + .def("date", &value_t::date) + .def("strip_annotations", &value_t::strip_annotations) .def("add", &value_t::add, return_internal_reference<>()) + .def("value", &value_t::value) + .def("round", &value_t::round) + .def("negate", &value_t::negate) + .def("negated", &value_t::negated) ; enum_< value_t::type_t > ("ValueType") .value("BOOLEAN", value_t::BOOLEAN) .value("INTEGER", value_t::INTEGER) + .value("DATETIME", value_t::DATETIME) .value("AMOUNT", value_t::AMOUNT) .value("BALANCE", value_t::BALANCE) .value("BALANCE_PAIR", value_t::BALANCE_PAIR) @@ -36,6 +36,7 @@ class value_t enum type_t { BOOLEAN, INTEGER, + DATETIME, AMOUNT, BALANCE, BALANCE_PAIR @@ -57,6 +58,10 @@ class value_t *((long *) data) = value; type = INTEGER; } + value_t(const datetime_t value) { + *((datetime_t *) data) = value; + type = DATETIME; + } value_t(const unsigned long value) { new((amount_t *) data) amount_t(value); type = AMOUNT; @@ -108,6 +113,14 @@ class value_t } return *this; } + value_t& operator=(const datetime_t value) { + if ((datetime_t *) data != &value) { + destroy(); + *((datetime_t *) data) = value; + type = DATETIME; + } + return *this; + } value_t& operator=(const unsigned long value) { return *this = amount_t(value); } @@ -284,6 +297,8 @@ class value_t return ! *((bool *) data); case INTEGER: return *((long *) data) == 0; + case DATETIME: + return ! *((datetime_t *) data); case AMOUNT: return ((amount_t *) data)->realzero(); case BALANCE: @@ -299,46 +314,19 @@ class value_t return 0; } - void abs(); - void cast(type_t cast_type); - value_t cost() const; - value_t price() const; - value_t date() const; - value_t reduce(const bool keep_price = false, - const bool keep_date = false, - const bool keep_tag = false) const; - value_t& add(const amount_t& amount, const amount_t * cost = NULL); + void abs(); + void cast(type_t cast_type); + value_t cost() const; + value_t price() const; + value_t date() const; - value_t value(const std::time_t moment) const { - switch (type) { - case BOOLEAN: - case INTEGER: - return *this; - case AMOUNT: - return ((amount_t *) data)->value(moment); - case BALANCE: - return ((balance_t *) data)->value(moment); - case BALANCE_PAIR: - return ((balance_pair_t *) data)->quantity.value(moment); - } - } + value_t strip_annotations(const bool keep_price = amount_t::keep_price, + const bool keep_date = amount_t::keep_date, + const bool keep_tag = amount_t::keep_tag) const; - void round() { - switch (type) { - case BOOLEAN: - case INTEGER: - break; - case AMOUNT: - *((amount_t *) data) = ((amount_t *) data)->round(); - break; - case BALANCE: - ((balance_t *) data)->round(); - break; - case BALANCE_PAIR: - ((balance_pair_t *) data)->round(); - break; - } - } + value_t& add(const amount_t& amount, const amount_t * cost = NULL); + value_t value(const std::time_t moment) const; + void round(); }; #define DEF_VALUE_AUX_OP(OP) \ @@ -379,6 +367,8 @@ value_t::operator T() const return *((bool *) data); case INTEGER: return *((long *) data); + case DATETIME: + return *((datetime_t *) data); case AMOUNT: return *((amount_t *) data); case BALANCE: @@ -395,6 +385,7 @@ value_t::operator T() const } template <> value_t::operator long() const; +template <> value_t::operator datetime_t() const; template <> value_t::operator double() const; inline value_t abs(const value_t& value) { @@ -406,11 +397,14 @@ inline value_t abs(const value_t& value) { inline std::ostream& operator<<(std::ostream& out, const value_t& value) { switch (value.type) { case value_t::BOOLEAN: - out << *((bool *) value.data); + out << *((bool *) value.data) ? "true" : "false"; break; case value_t::INTEGER: out << *((long *) value.data); break; + case value_t::DATETIME: + out << *((datetime_t *) value.data); + break; case value_t::AMOUNT: out << *((amount_t *) value.data); break; @@ -340,7 +340,7 @@ void subtotal_transactions::report_subtotal(const char * spec_fmt) if (! spec_fmt) { std::string fmt = "- "; - fmt += format_t::date_format; + fmt += datetime_t::date_format; std::strftime(buf, 255, fmt.c_str(), std::localtime(&finish)); } else { std::strftime(buf, 255, spec_fmt, std::localtime(&finish)); |