diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rwxr-xr-x | acprep | 2 | ||||
-rw-r--r-- | amount.cc | 263 | ||||
-rw-r--r-- | amount.h | 92 | ||||
-rw-r--r-- | balance.cc | 116 | ||||
-rw-r--r-- | balance.h | 34 | ||||
-rw-r--r-- | binary.cc | 24 | ||||
-rw-r--r-- | config.cc | 25 | ||||
-rw-r--r-- | config.h | 3 | ||||
-rw-r--r-- | derive.cc | 28 | ||||
-rw-r--r-- | format.cc | 21 | ||||
-rw-r--r-- | format.h | 4 | ||||
-rw-r--r-- | gnucash.cc | 2 | ||||
-rw-r--r-- | journal.cc | 8 | ||||
-rw-r--r-- | journal.h | 11 | ||||
-rw-r--r-- | main.cc | 20 | ||||
-rw-r--r-- | ofx.cc | 6 | ||||
-rw-r--r-- | textual.cc | 406 | ||||
-rw-r--r-- | valexpr.cc | 96 | ||||
-rw-r--r-- | valexpr.h | 77 | ||||
-rw-r--r-- | value.cc | 777 | ||||
-rw-r--r-- | value.h | 96 |
22 files changed, 1364 insertions, 749 deletions
diff --git a/Makefile.am b/Makefile.am index 2af4a5eb..c3acc1a1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -47,7 +47,7 @@ endif if DEBUG libledger_la_CXXFLAGS += -DDEBUG_LEVEL=4 endif -libledger_la_LDFLAGS = -version-info 2:6 +libledger_la_LDFLAGS = -release 2.6 pkginclude_HEADERS = \ acconf.h \ @@ -17,7 +17,7 @@ fi autoconf INCDIRS="-I/sw/include -I/usr/local/include/boost-1_33 -I/usr/include/httpd/xml" -INCDIRS="$INCDIRS -I/sw/include/libofx" +#INCDIRS="$INCDIRS -I/sw/include/libofx" INCDIRS="$INCDIRS -I/sw/include/python2.4" INCDIRS="$INCDIRS -Wno-long-double" LIBDIRS="-L/sw/lib -L/usr/local/lib -L/sw/lib/python2.4/config" @@ -11,6 +11,7 @@ namespace ledger { #define BIGINT_BULK_ALLOC 0x0001 +#define BIGINT_KEEP_PREC 0x0002 class amount_t::bigint_t { public: @@ -27,7 +28,8 @@ class amount_t::bigint_t { mpz_init_set(val, _val); } bigint_t(const bigint_t& other) - : prec(other.prec), flags(0), ref(1), index(0) { + : prec(other.prec), flags(other.flags & BIGINT_KEEP_PREC), + ref(1), index(0) { mpz_init_set(val, other.val); } ~bigint_t() { @@ -51,6 +53,7 @@ base_commodities_map commodity_base_t::commodities; commodity_base_t::updater_t * commodity_base_t::updater = NULL; commodities_map commodity_t::commodities; +bool commodity_t::commodities_sorted = false; commodity_t * commodity_t::null_commodity; commodity_t * commodity_t::default_commodity = NULL; @@ -256,7 +259,7 @@ amount_t& amount_t::operator=(const std::string& value) amount_t& amount_t::operator=(const char * value) { - std::string valstr(value); + std::string valstr(value); std::istringstream str(valstr); parse(str); return *this; @@ -371,7 +374,8 @@ amount_t& amount_t::operator+=(const amount_t& amt) else if (quantity->prec < amt.quantity->prec) { _resize(amt.quantity->prec); mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - } else { + } + else { amount_t temp = amt; temp._resize(quantity->prec); mpz_add(MPZ(quantity), MPZ(quantity), MPZ(temp.quantity)); @@ -403,7 +407,8 @@ amount_t& amount_t::operator-=(const amount_t& amt) else if (quantity->prec < amt.quantity->prec) { _resize(amt.quantity->prec); mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - } else { + } + else { amount_t temp = amt; temp._resize(quantity->prec); mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(temp.quantity)); @@ -444,10 +449,10 @@ amount_t& amount_t::operator/=(const amount_t& amt) // Increase the value's precision, to capture fractional parts after // the divide. - mpz_ui_pow_ui(divisor, 10, amt.quantity->prec + 6); + mpz_ui_pow_ui(divisor, 10, amt.quantity->prec + 6U); mpz_mul(MPZ(quantity), MPZ(quantity), divisor); mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - quantity->prec += 6; + quantity->prec += 6U; unsigned int comm_prec = commodity().precision(); if (quantity->prec > comm_prec + 6U) { @@ -562,13 +567,20 @@ amount_t::operator double() const return std::atof(num.str().c_str()); } +bool amount_t::realzero() const +{ + if (! quantity) + return true; + return mpz_sgn(MPZ(quantity)) == 0; +} + amount_t amount_t::value(const std::time_t moment) const { if (quantity) { commodity_t& comm = commodity(); if (! (comm.flags() & COMMODITY_STYLE_NOMARKET)) if (amount_t amt = comm.value(moment)) - return (amt * *this).round(amt.commodity().precision()); + return (amt * *this).round(); } return *this; } @@ -587,6 +599,18 @@ amount_t amount_t::round(unsigned int prec) const return temp; } +amount_t amount_t::unround() const +{ + if (! quantity || quantity->flags & BIGINT_KEEP_PREC) + return *this; + + amount_t temp = *this; + temp._dup(); + temp.quantity->flags |= BIGINT_KEEP_PREC; + + return temp; +} + std::string amount_t::quantity_string() const { if (! quantity) @@ -610,7 +634,7 @@ std::string amount_t::quantity_string() const commodity_t& comm(commodity()); unsigned short precision; - if (! comm || comm.flags() & COMMODITY_STYLE_VARIABLE) { + if (! comm || quantity->flags & BIGINT_KEEP_PREC) { mpz_ui_pow_ui(divisor, 10, quantity->prec); mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); precision = quantity->prec; @@ -716,7 +740,7 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) commodity_t& comm(base.commodity()); unsigned short precision; - if (! comm || comm.flags() & COMMODITY_STYLE_VARIABLE) { + if (! comm || base.quantity->flags & BIGINT_KEEP_PREC) { mpz_ui_pow_ui(divisor, 10, base.quantity->prec); mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor); precision = base.quantity->prec; @@ -814,12 +838,26 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) if (precision) { out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); - out.width(precision); - out.fill('0'); - + std::ostringstream final; + final.width(precision); + final.fill('0'); char * p = mpz_get_str(NULL, 10, rquotient); - out << p; + final << p; std::free(p); + + const std::string& str(final.str()); + int i, len = str.length(); + const char * q = str.c_str(); + for (i = len; i > 0; i--) + if (q[i - 1] != '0') + break; + + if (i == len) + out << str; + else if (i < comm.precision()) + out << std::string(str, 0, comm.precision()); + else + out << std::string(str, 0, i); } if (comm.flags() & COMMODITY_STYLE_SUFFIXED) { @@ -859,9 +897,7 @@ void parse_quantity(std::istream& in, std::string& value) value = buf; } -void parse_commodity(std::istream& in, std::string& name, - std::string& symbol, std::string& price, - std::string& date, std::string& tag) +void parse_commodity(std::istream& in, std::string& symbol) { char buf[256]; char c = peek_next_nonws(in); @@ -876,79 +912,94 @@ void parse_commodity(std::istream& in, std::string& name, READ_INTO(in, buf, 255, c, ! std::isspace(c) && ! std::isdigit(c) && c != '-' && c != '.'); } - name = symbol = buf; + symbol = buf; +} - bool added_name = false; +void parse_annotations(std::istream& in, const std::string& symbol, + std::string& name, std::string& price, + std::string& date, std::string& tag) +{ + char buf[256]; - c = peek_next_nonws(in); + bool added_name = false; + bool handled = false; + do { + char c = peek_next_nonws(in); + if (c == '{') { + if (! price.empty()) + throw amount_error("Commodity specifies more than one price"); - if (c == '{') { - in.get(c); - READ_INTO(in, buf, 255, c, c != '}'); - if (c == '}') in.get(c); - else - throw amount_error("Commodity price lacks closing brace"); - price = buf; - if (! added_name) { - added_name = true; - if (commodity_t::needs_quotes(symbol)) { - name = "\""; - name += symbol; - name += "\""; + READ_INTO(in, buf, 255, c, c != '}'); + if (c == '}') + in.get(c); + else + throw amount_error("Commodity price lacks closing brace"); + price = buf; + if (! added_name) { + added_name = true; + if (commodity_t::needs_quotes(symbol)) { + name = "\""; + name += symbol; + name += "\""; + } } + name += " {"; + name += price; + name += "}"; } - name += " {"; - name += price; - name += "}"; - c = peek_next_nonws(in); - } + else if (c == '[') { + if (! date.empty()) + throw amount_error("Commodity specifies more than one date"); - if (c == '[') { - in.get(c); - READ_INTO(in, buf, 255, c, c != ']'); - if (c == ']') in.get(c); - else - throw amount_error("Commodity date lacks closing bracket"); - date = buf; - if (! added_name) { - added_name = true; - if (commodity_t::needs_quotes(symbol)) { - name = "\""; - name += symbol; - name += "\""; + READ_INTO(in, buf, 255, c, c != ']'); + if (c == ']') + in.get(c); + else + throw amount_error("Commodity date lacks closing bracket"); + date = buf; + if (! added_name) { + added_name = true; + if (commodity_t::needs_quotes(symbol)) { + name = "\""; + name += symbol; + name += "\""; + } } + name += " ["; + name += date; + name += "]"; } - name += " ["; - name += date; - name += "]"; - c = peek_next_nonws(in); - } + else if (c == '(') { + if (! tag.empty()) + throw amount_error("Commodity specifies more than one tag"); - if (c == '(') { - in.get(c); - READ_INTO(in, buf, 255, c, c != ')'); - if (c == ')') in.get(c); - else - throw amount_error("Commodity tag lacks closing parenthesis"); - tag = buf; - if (! added_name) { - added_name = true; - if (commodity_t::needs_quotes(symbol)) { - name = "\""; - name += symbol; - name += "\""; + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') + in.get(c); + else + throw amount_error("Commodity tag lacks closing parenthesis"); + tag = buf; + if (! added_name) { + added_name = true; + if (commodity_t::needs_quotes(symbol)) { + name = "\""; + name += symbol; + name += "\""; + } } + name += " ("; + name += tag; + name += ")"; } - name += " ("; - name += tag; - name += ")"; - c = peek_next_nonws(in); - } + else { + break; + } + } while (true); - DEBUG_PRINT("amounts.commodities", "Parsed commodity: " + DEBUG_PRINT("amounts.commodities", "Parsed commodity annotations: " << "name " << name << " " << "symbol " << symbol << std::endl << " price " << price << " " @@ -979,26 +1030,36 @@ void amount_t::parse(std::istream& in, unsigned short flags) c = peek_next_nonws(in); } + char n; if (std::isdigit(c) || c == '.') { parse_quantity(in, quant); - char n; if (! in.eof() && ((n = in.peek()) != '\n')) { if (std::isspace(n)) comm_flags |= COMMODITY_STYLE_SEPARATED; - parse_commodity(in, name, symbol, price, date, tag); + parse_commodity(in, symbol); + name = symbol; if (! symbol.empty()) comm_flags |= COMMODITY_STYLE_SUFFIXED; + + if (! in.eof() && ((n = in.peek()) != '\n')) + parse_annotations(in, symbol, name, price, date, tag); } } else { - parse_commodity(in, name, symbol, price, date, tag); + parse_commodity(in, symbol); + name = symbol; + + if (! in.eof() && ((n = in.peek()) != '\n')) { + if (std::isspace(in.peek())) + comm_flags |= COMMODITY_STYLE_SEPARATED; - if (std::isspace(in.peek())) - comm_flags |= COMMODITY_STYLE_SEPARATED; + parse_quantity(in, quant); - parse_quantity(in, quant); + if (! in.eof() && ((n = in.peek()) != '\n')) + parse_annotations(in, symbol, name, price, date, tag); + } } if (quant.empty()) @@ -1021,7 +1082,13 @@ void amount_t::parse(std::istream& in, unsigned short flags) assert(name == symbol); commodity_ = commodity_t::create(symbol); } - assert(commodity_); +#ifdef DEBUG_ENABLED + if (! commodity_) + std::cerr << "Failed to find commodity for name " + << name << " symbol " << symbol << std::endl; +#endif + if (! commodity_) + commodity_ = commodity_t::null_commodity; } // Determine the precision of the amount, based on the usage of @@ -1061,6 +1128,9 @@ void amount_t::parse(std::istream& in, unsigned short flags) commodity().set_precision(quantity->prec); } + if (flags & AMOUNT_PARSE_NO_MIGRATE) + quantity->flags |= BIGINT_KEEP_PREC; + // Now we have the final number. Remove commas and periods, if // necessary. @@ -1267,12 +1337,11 @@ void amount_t::annotate_commodity(const amount_t& price, << " date " << date << " " << " tag " << tag); - annotated_commodity_t * ann_comm = - annotated_commodity_t::find_or_create(*this_base, price, - date == 0 && this_ann ? - this_ann->date : date, - tag.empty() && this_ann ? - this_ann->tag : tag); + commodity_t * ann_comm = + annotated_commodity_t::find_or_create + (*this_base, ! price && this_ann ? this_ann->price : price, + date == 0 && this_ann ? this_ann->date : date, + tag.empty() && this_ann ? this_ann->tag : tag); if (ann_comm) set_commodity(*ann_comm); @@ -1510,7 +1579,7 @@ annotated_commodity_t::make_qualified_name(const commodity_t& comm, return name.str(); } -annotated_commodity_t * +commodity_t * annotated_commodity_t::create(const commodity_t& comm, const amount_t& price, const std::time_t date, @@ -1554,7 +1623,7 @@ annotated_commodity_t::create(const commodity_t& comm, return commodity.release(); } -annotated_commodity_t * +commodity_t * annotated_commodity_t::create(const std::string& symbol, const amount_t& price, const std::time_t date, @@ -1562,10 +1631,13 @@ annotated_commodity_t::create(const std::string& symbol, { commodity_t * comm = commodity_t::find_or_create(symbol); assert(comm); + if (! price && ! date && tag.empty()) + return comm; + return create(*comm, price, date, tag); } -annotated_commodity_t * +commodity_t * annotated_commodity_t::create(const std::string& symbol, const std::string& price, const std::string& date, @@ -1576,7 +1648,7 @@ annotated_commodity_t::create(const std::string& symbol, amount_t real_price; if (! price.empty()) - real_price.parse(price); + real_price.parse(price, AMOUNT_PARSE_NO_MIGRATE); std::time_t real_date = 0; if (! date.empty()) @@ -1585,7 +1657,7 @@ annotated_commodity_t::create(const std::string& symbol, return create(*comm, real_price, real_date, tag); } -annotated_commodity_t * +commodity_t * annotated_commodity_t::find_or_create(const commodity_t& comm, const amount_t& price, const std::time_t date, @@ -1594,10 +1666,8 @@ annotated_commodity_t::find_or_create(const commodity_t& comm, std::string name = make_qualified_name(comm, price, date, tag); commodity_t * base = commodity_t::find(name); - if (base) { - assert(base->annotated); - return static_cast<annotated_commodity_t *>(base); - } + if (base) + return base; base = commodity_t::find_or_create(comm.base_symbol()); return create(*base, price, date, tag, name); @@ -1729,7 +1799,6 @@ void export_amount() scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN; scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; scope().attr("COMMODITY_STYLE_NOMARKET") = COMMODITY_STYLE_NOMARKET; - scope().attr("COMMODITY_STYLE_VARIABLE") = COMMODITY_STYLE_VARIABLE; #if 0 class_< commodity_t > ("Commodity") @@ -2,6 +2,7 @@ #define _AMOUNT_H #include <map> +#include <stack> #include <string> #include <ctime> #include <cctype> @@ -82,6 +83,7 @@ class amount_t commodity_ = NULL; } amount_t price() const; + std::time_t date() const; bool null() const { return ! quantity && ! commodity_; @@ -100,6 +102,8 @@ class amount_t // general methods amount_t round(unsigned int prec) const; + amount_t round() const; + amount_t unround() const; // in-place arithmetic amount_t& operator+=(const amount_t& amt); @@ -187,6 +191,8 @@ class amount_t operator long() const; operator double() const; + bool realzero() const; + // comparisons between amounts bool operator<(const amount_t& amt) const; bool operator<=(const amount_t& amt) const; @@ -275,9 +281,38 @@ unsigned int sizeof_bigint_t(); void parse_quantity(std::istream& in, std::string& value); void parse_commodity(std::istream& in, std::string& symbol); +void parse_annotations(std::istream& in, const std::string& symbol, + std::string& name, std::string& price, + std::string& date, std::string& tag); void parse_conversion(const std::string& larger, const std::string& smaller); +inline bool is_quote_or_paren(char * p) { + return *p == '"' || *p == '{' || *p == '[' || *p == '('; +} + +inline char * scan_past_quotes_and_parens(char * expr) +{ + std::stack<char> paren_stack; + + for (const char * p = expr; *p; p++) { + if (*p == '"' || + ((*p == '(' || ((*p == '{' || *p == '[') && + paren_stack.top() != '(')) && + paren_stack.top() != '"')) { + paren_stack.push(*p); + } + else if ((*p == ')' && paren_stack.top() == '(') || + (*p == '}' && paren_stack.top() == '{') || + (*p == ']' && paren_stack.top() == '[') || + (*p == '"' && paren_stack.top() == '"')) { + paren_stack.pop(); + if (paren_stack.size() == 0) + break; + } + } +} + inline amount_t abs(const amount_t& amt) { return amt < 0 ? amt.negated() : amt; } @@ -296,8 +331,7 @@ inline std::istream& operator>>(std::istream& in, amount_t& amt) { #define COMMODITY_STYLE_EUROPEAN 0x0004 #define COMMODITY_STYLE_THOUSANDS 0x0008 #define COMMODITY_STYLE_NOMARKET 0x0010 -#define COMMODITY_STYLE_VARIABLE 0x0020 -#define COMMODITY_STYLE_BUILTIN 0x0040 +#define COMMODITY_STYLE_BUILTIN 0x0020 typedef std::map<const std::time_t, amount_t> history_map; typedef std::pair<const std::time_t, amount_t> history_pair; @@ -385,6 +419,7 @@ class commodity_t // This map remembers all commodities that have been defined. static commodities_map commodities; + static bool commodities_sorted; static commodity_t * null_commodity; static commodity_t * default_commodity; @@ -519,27 +554,23 @@ class annotated_commodity_t : public commodity_t const amount_t& price, const std::time_t date, const std::string& tag); - static - annotated_commodity_t * create(const commodity_t& comm, - const amount_t& price, - const std::time_t date, - const std::string& tag, - const std::string& entry_name = ""); - static - annotated_commodity_t * create(const std::string& symbol, - const amount_t& price, - const std::time_t date, - const std::string& tag); - static - annotated_commodity_t * create(const std::string& symbol, - const std::string& price, - const std::string& date, - const std::string& tag); - static - annotated_commodity_t * find_or_create(const commodity_t& comm, - const amount_t& price, - const std::time_t date, - const std::string& tag); + static commodity_t * create(const commodity_t& comm, + const amount_t& price, + const std::time_t date, + const std::string& tag, + const std::string& entry_name = ""); + static commodity_t * create(const std::string& symbol, + const amount_t& price, + const std::time_t date, + const std::string& tag); + static commodity_t * create(const std::string& symbol, + const std::string& price, + const std::string& date, + const std::string& tag); + static commodity_t * find_or_create(const commodity_t& comm, + const amount_t& price, + const std::time_t date, + const std::string& tag); explicit annotated_commodity_t() { annotated = true; @@ -555,6 +586,10 @@ inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { return out; } +inline amount_t amount_t::round() const { + return round(commodity().precision()); +} + inline commodity_t& amount_t::commodity() const { if (! commodity_) return *commodity_t::null_commodity; @@ -574,6 +609,17 @@ inline amount_t amount_t::price() const { } } +inline std::time_t amount_t::date() const { + if (commodity_ && commodity_->annotated) { + DEBUG_PRINT("amounts.commodities", + "Returning date of " << *this << " = " + << ((annotated_commodity_t *)commodity_)->date); + return ((annotated_commodity_t *)commodity_)->date; + } else { + return 0L; + } +} + class amount_error : public std::exception { std::string reason; public: @@ -46,6 +46,23 @@ balance_t balance_t::price() const return temp; } +std::time_t balance_t::date() const +{ + std::time_t temp = 0; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) { + std::time_t date = (*i).second.date(); + if (temp == 0 && date != 0) + temp = date; + else if (temp != date) + return 0; + } + + return temp; +} + balance_t balance_t::reduce(const bool keep_price, const bool keep_date, const bool keep_tag) const { @@ -61,7 +78,35 @@ balance_t balance_t::reduce(const bool keep_price, const bool keep_date, struct compare_amount_commodities { bool operator()(const amount_t * left, const amount_t * right) const { - return left->commodity().symbol() < right->commodity().symbol(); + commodity_t& leftcomm(left->commodity()); + commodity_t& rightcomm(right->commodity()); + + int cmp = leftcomm.symbol().compare(rightcomm.symbol()); + if (cmp != 0) + return cmp < 0; + + if (! leftcomm.annotated) { + assert(rightcomm.annotated); + return true; + } + else if (! rightcomm.annotated) { + assert(leftcomm.annotated); + return false; + } + else { + annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm)); + annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm)); + + amount_t val = aleftcomm.price - arightcomm.price; + if (val) + return val < 0; + + int diff = aleftcomm.date - arightcomm.date; + if (diff) + return diff < 0; + + return aleftcomm.tag < arightcomm.tag; + } } }; @@ -75,32 +120,51 @@ void balance_t::write(std::ostream& out, if (lwidth == -1) lwidth = first_width; - typedef std::deque<const amount_t *> amounts_deque; - amounts_deque sorted; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if ((*i).second) - sorted.push_back(&(*i).second); - - std::stable_sort(sorted.begin(), sorted.end(), compare_amount_commodities()); - - for (amounts_deque::const_iterator i = sorted.begin(); - i != sorted.end(); - i++) { - int width; - if (! first) { - out << std::endl; - width = lwidth; - } else { - first = false; - width = first_width; + if (commodity_t::commodities_sorted) { + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) { + int width; + if (! first) { + out << std::endl; + width = lwidth; + } else { + first = false; + width = first_width; + } + + out.width(width); + out.fill(' '); + out << std::right << (*i).second; + } + } else { + typedef std::deque<const amount_t *> amounts_deque; + amounts_deque sorted; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second) + sorted.push_back(&(*i).second); + + std::stable_sort(sorted.begin(), sorted.end(), compare_amount_commodities()); + + for (amounts_deque::const_iterator i = sorted.begin(); + i != sorted.end(); + i++) { + int width; + if (! first) { + out << std::endl; + width = lwidth; + } else { + first = false; + width = first_width; + } + + out.width(width); + out.fill(' '); + out << std::right << **i; } - - out.width(width); - out.fill(' '); - out << std::right << **i; } if (first) { @@ -97,10 +97,14 @@ class balance_t } balance_t& operator-=(const amount_t& amt) { amounts_map::iterator i = amounts.find(&amt.commodity()); - if (i != amounts.end()) + if (i != amounts.end()) { (*i).second -= amt; - else if (amt) - amounts.insert(amounts_pair(&amt.commodity(), amt)); + if ((*i).second.realzero()) + amounts.erase(&amt.commodity()); + } + else if (! amt.realzero()) { + amounts.insert(amounts_pair(&amt.commodity(), - amt)); + } return *this; } template <typename T> @@ -147,7 +151,10 @@ class balance_t balance_t& operator*=(const amount_t& amt) { // Multiplying by the null commodity causes all amounts to be // increased by the same factor. - if (! amt.commodity()) { + if (amt.realzero()) { + amounts.clear(); + } + else if (! amt.commodity()) { for (amounts_map::iterator i = amounts.begin(); i != amounts.end(); i++) @@ -411,6 +418,7 @@ class balance_t amount_t amount(const commodity_t& commodity) const; balance_t value(const std::time_t moment) 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; @@ -430,7 +438,18 @@ class balance_t i != amounts.end(); i++) if ((*i).second.commodity()) - (*i).second = (*i).second.round((*i).second.commodity().precision()); + (*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; } }; @@ -829,6 +848,11 @@ class balance_pair_t void round() { quantity.round(); + if (cost) cost->round(); + } + + bool realzero() const { + return ((! cost || cost->realzero()) && quantity.realzero()); } }; @@ -12,9 +12,9 @@ namespace ledger { static unsigned long binary_magic_number = 0xFFEED765; #ifdef DEBUG_ENABLED -static unsigned long format_version = 0x00020603; +static unsigned long format_version = 0x00020605; #else -static unsigned long format_version = 0x00020602; +static unsigned long format_version = 0x00020604; #endif static account_t ** accounts; @@ -323,13 +323,7 @@ inline void read_binary_transaction(char *& data, transaction_t * xact) if (*data++ == 1) { xact->cost = new amount_t; - - if (read_binary_number<char>(data) == 1) { - read_binary_value_expr(data, xact->cost_expr); - if (xact->cost_expr) xact->cost_expr->acquire(); - } else { - read_binary_amount(data, *xact->cost); - } + read_binary_amount(data, *xact->cost); } else { xact->cost = NULL; } @@ -348,8 +342,6 @@ inline void read_binary_transaction(char *& data, transaction_t * xact) if (xact->amount_expr) compute_amount(xact->amount_expr, xact->amount, xact); - if (xact->cost_expr) - compute_amount(xact->cost_expr, *xact->cost, xact); } inline void read_binary_entry_base(char *& data, entry_base_t * entry, @@ -884,13 +876,7 @@ void write_binary_transaction(std::ostream& out, transaction_t * xact, if (xact->cost && (! (ignore_calculated && xact->flags & TRANSACTION_CALCULATED))) { write_binary_number<char>(out, 1); - if (xact->cost_expr) { - write_binary_number<char>(out, 1); - write_binary_value_expr(out, xact->cost_expr); - } else { - write_binary_number<char>(out, 0); - write_binary_amount(out, *xact->cost); - } + write_binary_amount(out, *xact->cost); } else { write_binary_number<char>(out, 0); } @@ -917,7 +903,7 @@ void write_binary_entry_base(std::ostream& out, entry_base_t * entry) for (transactions_list::const_iterator i = entry->transactions.begin(); i != entry->transactions.end(); i++) - if ((*i)->amount_expr || (*i)->cost_expr) { + if ((*i)->amount_expr) { ignore_calculated = true; break; } @@ -91,6 +91,9 @@ void config_t::reset() show_revalued_only = false; download_quotes = false; debug_mode = false; + keep_price = false; + keep_date = false; + keep_tag = false; use_cache = false; cache_dirty = false; @@ -296,8 +299,16 @@ void config_t::process_options(const std::string& command, commodity_base_t::updater = new quotes_by_script(price_db, pricing_leeway, cache_dirty); - if (! date_format.empty()) - format_t::date_format = date_format; + // Now setup the various formatting strings + + if (! date_format.empty()) { + format_t::date_format = date_format; + annotated_commodity_t::date_format = date_format; + } + + format_t::keep_price = keep_price; + format_t::keep_date = keep_date; + format_t::keep_tag = keep_tag; } item_handler<transaction_t> * @@ -780,19 +791,21 @@ OPT_BEGIN(actual, "L") { } OPT_END(actual); OPT_BEGIN(lots, "") { - keep_price = keep_date = keep_tag = true; + config->keep_price = + config->keep_date = + config->keep_tag = true; } OPT_END(lots); OPT_BEGIN(lot_prices, "") { - keep_price = true; + config->keep_price = true; } OPT_END(lots_prices); OPT_BEGIN(lot_dates, "") { - keep_date = true; + config->keep_date = true; } OPT_END(lots_dates); OPT_BEGIN(lot_tags, "") { - keep_tag = true; + config->keep_tag = true; } OPT_END(lots_tags); ////////////////////////////////////////////////////////////////////// @@ -66,6 +66,9 @@ class config_t bool use_cache; bool cache_dirty; bool debug_mode; + bool keep_price; + bool keep_date; + bool keep_tag; config_t() { reset(); @@ -2,6 +2,7 @@ #include "datetime.h" #include "error.h" #include "mask.h" +#include "walk.h" #include <memory> @@ -47,10 +48,31 @@ entry_t * derive_new_entry(journal_t& journal, i++; } - if (i == end) + if (i == end) { added->add_transaction(new transaction_t(acct)); - else - added->add_transaction(new transaction_t(acct, amount_t(*i++))); + } else { + transaction_t * xact = new transaction_t(acct, amount_t(*i++)); + added->add_transaction(xact); + + if (! xact->amount.commodity()) { + // If the amount has no commodity, we can determine it given + // the account by creating a final for the account and then + // checking if it contains only a single commodity. An + // account to which only dollars are applied would imply that + // dollars are wanted now too. + + std::auto_ptr<item_handler<transaction_t> > formatter; + formatter.reset(new set_account_value); + walk_entries(journal.entries, *formatter.get()); + formatter->flush(); + + sum_accounts(*journal.master); + + value_t total = account_xdata(*acct).total; + if (total.type == value_t::AMOUNT) + xact->amount.set_commodity(((amount_t *) total.data)->commodity()); + } + } if (journal.basket) acct = journal.basket; @@ -7,6 +7,10 @@ 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) { @@ -306,6 +310,9 @@ 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); + switch (value.type) { case value_t::BOOLEAN: out << (*((bool *) value.data) ? "1" : "0"); @@ -341,20 +348,12 @@ void format_t::format(std::ostream& out_str, const details_t& details) const bool use_disp = false; if (details.xact->cost && details.xact->amount) { - amount_t unit_cost = *details.xact->cost / details.xact->amount; - - commodity_t& comm(unit_cost.commodity()); - bool has_flag = comm.flags() & COMMODITY_STYLE_VARIABLE; - if (! has_flag) - comm.add_flags(COMMODITY_STYLE_VARIABLE); - std::ostringstream stream; - stream << details.xact->amount << " @ " << unit_cost; + stream << details.xact->amount << " @ " + << amount_t(*details.xact->cost / + details.xact->amount).unround(); disp = stream.str(); use_disp = true; - - if (! has_flag) - comm.drop_flags(COMMODITY_STYLE_VARIABLE); } else if (details.entry) { unsigned int xacts_count = 0; @@ -72,6 +72,10 @@ 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) { @@ -182,7 +182,7 @@ static void endElement(void *userData, const char *name) if (default_commodity) { curr_quant.set_commodity(*default_commodity); - value = curr_quant.round(default_commodity->precision()); + value = curr_quant.round(); if (curr_value.commodity() == *default_commodity) curr_value = value; @@ -18,7 +18,6 @@ transaction_t::~transaction_t() DEBUG_PRINT("ledger.memory.dtors", "dtor transaction_t"); if (cost) delete cost; if (amount_expr) amount_expr->release(); - if (cost_expr) cost_expr->release(); } std::time_t transaction_t::actual_date() const @@ -130,8 +129,8 @@ bool entry_base_t::finalize() nxact->flags |= TRANSACTION_CALCULATED; } - // If one transaction of a two-line transaction is of a different - // commodity than the others, and it has no per-unit price, + // If the first transaction of a two-transaction entry is of a + // different commodity than the other, and it has no per-unit price, // determine its price by dividing the unit count into the value of // the balance. This is done for the last eligible commodity. @@ -158,6 +157,9 @@ bool entry_base_t::finalize() balance -= (*x)->amount; entry_t * entry = dynamic_cast<entry_t *>(this); + if ((*x)->amount.commodity().annotated) + throw error("Cannot self-balance an annotated commodity"); + (*x)->amount.annotate_commodity(abs(per_unit_cost), entry ? entry->actual_date() : 0, entry ? entry->code : ""); @@ -40,7 +40,6 @@ class transaction_t amount_t amount; value_expr_t * amount_expr; amount_t * cost; - value_expr_t * cost_expr; state_t state; unsigned short flags; std::string note; @@ -54,9 +53,9 @@ class transaction_t transaction_t(account_t * _account = NULL) : entry(NULL), _date(0), _date_eff(0), account(_account), - amount_expr(NULL), cost(NULL), cost_expr(NULL), - state(UNCLEARED), flags(TRANSACTION_NORMAL), - beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { + amount_expr(NULL), cost(NULL), state(UNCLEARED), + flags(TRANSACTION_NORMAL), beg_pos(0), beg_line(0), + end_pos(0), end_line(0), data(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); } transaction_t(account_t * _account, @@ -64,7 +63,7 @@ class transaction_t unsigned int _flags = TRANSACTION_NORMAL, const std::string& _note = "") : entry(NULL), _date(0), _date_eff(0), account(_account), - amount(_amount), amount_expr(NULL), cost(NULL), cost_expr(NULL), + amount(_amount), amount_expr(NULL), cost(NULL), state(UNCLEARED), flags(_flags), note(_note), beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); @@ -72,7 +71,7 @@ class transaction_t transaction_t(const transaction_t& xact) : entry(xact.entry), _date(0), _date_eff(0), account(xact.account), amount(xact.amount), amount_expr(NULL), - cost(xact.cost ? new amount_t(*xact.cost) : NULL), cost_expr(NULL), + cost(xact.cost ? new amount_t(*xact.cost) : NULL), state(xact.state), flags(xact.flags), note(xact.note), beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); @@ -128,8 +128,11 @@ int parse_and_report(int argc, char * argv[], char * envp[]) ledger::dump_value_expr(std::cout, expr.get()); std::cout << std::endl; } - value_t result; - expr->compute(result, details_t()); + + 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); std::cout << result << std::endl; return 0; } @@ -233,8 +236,10 @@ int parse_and_report(int argc, char * argv[], char * envp[]) ledger::dump_value_expr(std::cout, expr.get()); std::cout << std::endl; } - value_t result; - expr->compute(result, details_t()); + 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); std::cout << result << std::endl; return 0; } @@ -344,22 +349,19 @@ int parse_and_report(int argc, char * argv[], char * envp[]) i != formatter_ptrs.end(); i++) delete *i; - formatter_ptrs.clear(); #endif TIMER_STOP(cleanup); // Write out the binary cache, if need be - TIMER_START(cache_write); - if (config.use_cache && config.cache_dirty && ! config.cache_file.empty()) { + TIMER_START(cache_write); std::ofstream stream(config.cache_file.c_str()); write_binary_journal(stream, journal.get()); + TIMER_STOP(cache_write); } - TIMER_STOP(cache_write); - #ifdef HAVE_UNIX_PIPES if (! config.pager.empty()) { delete out; @@ -37,7 +37,7 @@ int ofx_proc_account_cb(struct OfxAccountData data, void * account_data) ofx_accounts.insert(accounts_pair(data.account_id, account)); if (data.currency_valid) { - commodity_t * commodity = commodity_t::find_commodity(data.currency, true); + commodity_t * commodity = commodity_t::find_or_create(data.currency); commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED); commodities_map::iterator i = ofx_account_currencies.find(data.account_id); @@ -77,7 +77,7 @@ int ofx_proc_transaction_cb(struct OfxTransactionData data, if (data.unique_id_valid) { commodities_map::iterator s = ofx_securities.find(data.unique_id); assert(s != ofx_securities.end()); - xact->amount = stream.str() + " " + (*s).second->symbol; + xact->amount = stream.str() + " " + (*s).second->base_symbol(); } else { xact->amount = stream.str() + " " + default_commodity->base_symbol(); } @@ -137,7 +137,7 @@ int ofx_proc_security_cb(struct OfxSecurityData data, void * security_data) else return -1; - commodity_t * commodity = commodity_t::find_commodity(symbol, true); + commodity_t * commodity = commodity_t::find_or_create(symbol); commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED); if (data.secname_valid) @@ -64,265 +64,225 @@ inline char * next_element(char * buf, bool variable = false) return NULL; } -static inline bool is_mathchr(const char c) { - return (c == '(' || c == ')' || - c == '+' || c == '-' || - c == '*' || c == '/'); -} - -static inline void copy_wsbuf(char *& q, char *& wq, char * wsbuf) { - *wq = '\0'; - std::strcpy(q, wsbuf); - q += std::strlen(wsbuf); - wq = wsbuf; -} - -static char * parse_inline_math(const char * expr) -{ - char * buf = new char[std::strlen(expr) * 2]; - char * q = buf; - char wsbuf[64]; - char * wq = wsbuf; - bool in_math = true; - bool could = true; - - *q++ = '('; - - for (const char * p = expr; *p; p++) { - if (std::isspace(*p)) { - *wq++ = *p; - } else { - bool saw_math = is_mathchr(*p); - if (in_math && ! saw_math) { - copy_wsbuf(q, wq, wsbuf); - *q++ = '{'; - in_math = could = false; - } - else if (! in_math && saw_math && could) { - *q++ = '}'; - copy_wsbuf(q, wq, wsbuf); - in_math = true; - } - else if (wq != wsbuf) { - copy_wsbuf(q, wq, wsbuf); - } - - if (! in_math && std::isdigit(*p)) - could = true; - - *q++ = *p; - } - } - - if (! in_math) - *q++ = '}'; - - *q++ = ')'; - *q++ = '\0'; - - DEBUG_PRINT("ledger.textual.inlinemath", - "Parsed '" << expr << "' as '" << buf << "'"); - - return buf; -} - -value_expr_t * parse_amount(const char * text, amount_t& amt, - unsigned short flags, transaction_t& xact) -{ - char * altbuf = NULL; - - if (*text && *text != '(' && *text != '-') { - bool in_quote = false; - bool seen_digit = false; - for (const char * p = text + 1; *p; p++) - if (*p == '"') { - in_quote = ! in_quote; - } - else if (! in_quote) { - if (is_mathchr(*p)) { - if (*p == '-' && ! seen_digit) - continue; - text = altbuf = parse_inline_math(text); - break; - } - else if (std::isdigit(*p)) { - seen_digit = true; - } - } - } - - value_expr_t * expr = NULL; - - if (*text != '(') { - amt.parse(text, flags); - - if (altbuf) - delete[] altbuf; - } else { - expr = parse_value_expr(text); - - if (altbuf) - delete[] altbuf; - - if (! compute_amount(expr, amt, &xact)) - throw parse_error(path, linenum, "Value expression yields a balance"); - } - - return expr; -} - transaction_t * parse_transaction(char * line, account_t * account, entry_t * entry = NULL) { + std::istringstream in(line); + // The account will be determined later... std::auto_ptr<transaction_t> xact(new transaction_t(NULL)); if (entry) xact->entry = entry; - // The call to `next_element' will skip past the account name, and - // return a pointer to the beginning of the amount. Once we know - // where the amount is, we can strip off any transaction note, and - // parse it. + // Parse the state flag - char * amount = NULL; - char * price = NULL; - bool per_unit = true; - - char * p = skip_ws(line); - switch (*p) { + char p = peek_next_nonws(in); + switch (p) { case '*': xact->state = transaction_t::CLEARED; - p = skip_ws(++p); + in.get(p); + p = peek_next_nonws(in); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed the CLEARED flag"); break; case '!': xact->state = transaction_t::PENDING; - p = skip_ws(++p); + in.get(p); + p = peek_next_nonws(in); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed the PENDING flag"); break; } - if (amount = next_element(p, true)) { - amount = skip_ws(amount); - if (! *amount) - amount = NULL; - else { - if (char * note_str = std::strchr(amount, ';')) { - if (amount == note_str) - amount = NULL; - - *note_str++ = '\0'; - note_str = skip_ws(note_str); - - if (char * b = std::strchr(note_str, '[')) - if (char * e = std::strchr(note_str, ']')) { - char buf[256]; - std::strncpy(buf, b + 1, e - b); - buf[e - b] = '\0'; - - if (char * p = std::strchr(buf, '=')) { - *p++ = '\0'; - if (! quick_parse_date(p, &xact->_date_eff)) - throw parse_error(path, linenum, - "Failed to parse effective date"); - } - - if (buf[0] && ! quick_parse_date(buf, &xact->_date)) - throw parse_error(path, linenum, "Failed to parse date"); - } - - xact->note = skip_ws(note_str); - } - - if (amount) { - bool in_quote = false; - int paren_depth = 0; - for (char * q = amount; *q; q++) { - if (*q == '"') { - in_quote = ! in_quote; - } - else if (! in_quote) { - if (*q == '(') - paren_depth++; - else if (*q == ')') - paren_depth--; - else if (paren_depth == 0 && *q == '@') { - price = q; - break; - } - } - } - - if (price) { - if (price == amount) - throw parse_error(path, linenum, "Cost specified without amount"); + // Parse the account name - *price++ = '\0'; - if (*price == '@') { - per_unit = false; - price++; - } - price = skip_ws(price); - } - } - } + unsigned long account_beg = in.tellg(); + unsigned long account_end = account_beg; + while (! in.eof()) { + in.get(p); + if (in.eof() || (std::isspace(p) && + (p == '\t' || std::isspace(in.peek())))) + break; + account_end++; } - char * q = p + std::strlen(p) - 1; - while (q >= p && std::isspace(*q)) - *q-- = '\0'; + if (account_beg == account_end) + throw parse_error(path, linenum, "No account was specified"); - if (*p == '[' || *p == '(') { + char * b = &line[account_beg]; + char * e = &line[account_end]; + if ((*b == '[' && *(e - 1) == ']') || + (*b == '(' && *(e - 1) == ')')) { xact->flags |= TRANSACTION_VIRTUAL; - if (*p == '[') + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a virtual account name"); + if (*b == '[') { xact->flags |= TRANSACTION_BALANCE; - p++; - - char * e = p + (std::strlen(p) - 1); - assert(*e == ')' || *e == ']'); - *e = '\0'; + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a balanced virtual account name"); + } + b++; e--; } + std::string name(b, e - b); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed account name " << name); if (account_aliases.size() > 0) { - accounts_map::const_iterator i = account_aliases.find(p); + accounts_map::const_iterator i = account_aliases.find(name); if (i != account_aliases.end()) xact->account = (*i).second; } if (! xact->account) - xact->account = account->find_account(p); - - // If an amount (and optional price) were seen, parse them now - if (amount) { - xact->amount_expr = parse_amount(amount, xact->amount, - AMOUNT_PARSE_NO_REDUCE, *xact); - if (xact->amount_expr) - xact->amount_expr->acquire(); - - if (price) { - xact->cost = new amount_t; - xact->cost_expr = parse_amount(price, *xact->cost, - AMOUNT_PARSE_NO_MIGRATE, *xact); - if (xact->cost_expr) - xact->cost_expr->acquire(); - - if (per_unit) { - if (! xact->amount.commodity().annotated) - xact->amount.annotate_commodity(*xact->cost, + xact->account = account->find_account(name); + + // Parse the optional amount + + if (in.good() && ! in.eof()) { + p = peek_next_nonws(in); + if (in.eof()) + goto finished; + if (p == ';') + goto parse_note; + if (p == '(') { + xact->amount_expr = parse_value_expr(in)->acquire(); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed an amount expression"); +#ifdef DEBUG_ENABLED + DEBUG_IF("ledger.textual.parse") { + if (_debug_stream) { + ledger::dump_value_expr(*_debug_stream, xact->amount_expr); + *_debug_stream << std::endl; + } + } +#endif + if (! compute_amount(xact->amount_expr, xact->amount, xact.get())) + throw parse_error(path, linenum, + "Value expression for amount failed to compute"); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "The computed amount is " << xact->amount); + } else { + xact->amount.parse(in, AMOUNT_PARSE_NO_REDUCE); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed amount " << xact->amount); + + // Parse any inline math + p = peek_next_nonws(in); + while (in.good() && ! in.eof() && (p == '-' || p == '+')) { + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed inline math operator " << p); + in.get(p); + amount_t temp; + temp.parse(in, AMOUNT_PARSE_NO_REDUCE); + switch (p) { + case '-': + xact->amount -= temp; + break; + case '+': + xact->amount += temp; + break; + } + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Calculated amount is " << xact->amount); + p = peek_next_nonws(in); + } + } + } + + // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST) + + if (in.good() && ! in.eof()) { + p = peek_next_nonws(in); + if (p == '@') { + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Found a price indicator"); + bool per_unit = true; + in.get(p); + if (in.peek() == '@') { + in.get(p); + per_unit = false; + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "And it's for a total price"); + } + + if (in.good() && ! in.eof()) { + xact->cost = new amount_t; + + p = peek_next_nonws(in); + if (p == '(') + throw parse_error(path, linenum, + "A transaction's cost may not be a value expression"); + + xact->cost->parse(in, AMOUNT_PARSE_NO_MIGRATE); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed cost " << *xact->cost); + + amount_t per_unit_cost(*xact->cost); + if (per_unit) + *xact->cost *= xact->amount; + else + per_unit_cost /= xact->amount; + + if (! xact->amount.commodity().annotated) { + xact->amount.annotate_commodity(per_unit_cost, xact->entry->actual_date(), xact->entry->code); - *xact->cost *= xact->amount; - *xact->cost = xact->cost->round(xact->cost->commodity().precision()); - } - else if (! xact->amount.commodity().annotated) { - amount_t cost(*xact->cost); - cost /= xact->amount; - cost = cost.round(cost.commodity().precision()); + } else { + annotated_commodity_t& ann(static_cast<annotated_commodity_t&> + (xact->amount.commodity())); + xact->amount.annotate_commodity(! ann.price ? per_unit_cost : amount_t(), + ! ann.date ? xact->entry->actual_date() : 0, + ann.tag.empty() ? xact->entry->code : ""); + } - xact->amount.annotate_commodity(cost, xact->entry->actual_date(), - xact->entry->code); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Total cost is " << *xact->cost); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Per-unit cost is " << per_unit_cost); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Annotated amount is " << xact->amount); } } - xact->amount.reduce(); } + xact->amount.reduce(); + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Reduced amount is " << xact->amount); + + // Parse the optional note + + parse_note: + if (in.good() && ! in.eof()) { + p = peek_next_nonws(in); + if (p == ';') { + in.get(p); + p = peek_next_nonws(in); + xact->note = &line[in.tellg()]; + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a note '" << xact->note << "'"); + + if (char * b = std::strchr(xact->note.c_str(), '[')) + if (char * e = std::strchr(xact->note.c_str(), ']')) { + char buf[256]; + std::strncpy(buf, b + 1, e - b - 1); + buf[e - b - 1] = '\0'; + + DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a transaction date " << buf); + + if (char * p = std::strchr(buf, '=')) { + *p++ = '\0'; + if (! quick_parse_date(p, &xact->_date_eff)) + throw parse_error(path, linenum, + "Failed to parse effective date"); + } + + if (buf[0] && ! quick_parse_date(buf, &xact->_date)) + throw parse_error(path, linenum, "Failed to parse date"); + } + } + } + + finished: return xact.release(); } @@ -771,7 +731,7 @@ unsigned int textual_parser_t::parse(std::istream& in, path = std::string(save_path.prev, 0, pos + 1) + path; } - DEBUG_PRINT("ledger.textual.include", + DEBUG_PRINT("ledger.textual.include", "line " << linenum << ": " << "Including path '" << path << "'"); count += parse_journal_file(path, config, journal, account_stack.front()); @@ -13,10 +13,6 @@ std::auto_ptr<value_calc> total_expr; std::auto_ptr<scope_t> global_scope; std::time_t terminus; -bool keep_price = false; -bool keep_date = false; -bool keep_tag = false; - details_t::details_t(const transaction_t& _xact) : entry(_xact.entry), xact(&_xact), account(xact_account(_xact)) { @@ -351,6 +347,40 @@ void value_expr_t::compute(value_t& result, const details_t& details, result = 0L; break; + case F_PRICE: { + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); + expr->compute(result, details, context); + result = result.price(); + break; + } + + case F_DATE: { + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); + expr->compute(result, details, context); + result = result.date(); + break; + } + + case F_DATECMP: { + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); + expr->compute(result, details, context); + result = result.date(); + if (! result) + break; + + index = 0; + expr = find_leaf(context, 1, index); + amount_t moment; + if (compute_amount(expr, moment, NULL, context)) + result -= moment; + else + throw compute_error("Invalid date passed to datecmp(value,date)"); + break; + } + case F_ARITH_MEAN: { int index = 0; value_expr_t * expr = find_leaf(context, 0, index); @@ -382,6 +412,14 @@ void value_expr_t::compute(value_t& result, const details_t& details, break; } + case F_ROUND: { + int index = 0; + value_expr_t * expr = find_leaf(context, 0, index); + expr->compute(result, details, context); + result.round(); + break; + } + case F_COMMODITY: { int index = 0; value_expr_t * expr = find_leaf(context, 0, index); @@ -647,9 +685,6 @@ void value_expr_t::compute(value_t& result, const details_t& details, assert(0); break; } - - if (! keep_price || ! keep_date || ! keep_tag) - result = result.reduce(keep_price, keep_date, keep_tag); } static inline void unexpected(char c, char wanted = '\0') { @@ -685,7 +720,8 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope) if (std::strchr(buf, '.')) { node.reset(new value_expr_t(value_expr_t::CONSTANT_A)); - node->constant_a = new amount_t(buf); + node->constant_a = new amount_t; + node->constant_a->parse(buf, AMOUNT_PARSE_NO_MIGRATE); } else { node.reset(new value_expr_t(value_expr_t::CONSTANT_I)); node->constant_i = std::atol(buf); @@ -911,7 +947,7 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope) unexpected(c, '}'); node.reset(new value_expr_t(value_expr_t::CONSTANT_A)); - node->constant_a = new amount_t(); + node->constant_a = new amount_t; node->constant_a->parse(buf, AMOUNT_PARSE_NO_MIGRATE); break; } @@ -1171,6 +1207,7 @@ void init_value_expr() node = new value_expr_t(value_expr_t::F_NOW); globals->define("m", node); globals->define("now", node); + globals->define("today", node); node = new value_expr_t(value_expr_t::AMOUNT); globals->define("a", node); @@ -1230,11 +1267,11 @@ void init_value_expr() node = new value_expr_t(value_expr_t::PRICE_TOTAL); globals->define("I", node); - globals->define("price_total", node); + globals->define("total_price", node); node = new value_expr_t(value_expr_t::COST_TOTAL); globals->define("B", node); - globals->define("cost_total", node); + globals->define("total_cost", node); // Relating to format_t globals->define("t", new value_expr_t(value_expr_t::VALUE_EXPR)); @@ -1251,6 +1288,12 @@ void init_value_expr() 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_ROUND)); + globals->define("round", 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_QUANTITY)); globals->define("S", node); globals->define("quant", node); @@ -1284,9 +1327,28 @@ void init_value_expr() node->left->constant_i = 2; node->set_right(new value_expr_t(value_expr_t::F_VALUE)); globals->define("P", node); - globals->define("val", node); - globals->define("value", node); - value_auto_ptr cval(parse_boolean_expr("current_value(x)=P(x,m)", globals)); + value_auto_ptr val(parse_boolean_expr("value=P(t,m)", globals)); + value_auto_ptr tval(parse_boolean_expr("total_value=P(T,m)", globals)); + value_auto_ptr valof(parse_boolean_expr("valueof(x)=P(x,m)", globals)); + value_auto_ptr dvalof(parse_boolean_expr("datedvalueof(x,y)=P(x,y)", globals)); + + 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_PRICE)); + globals->define("priceof", 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_DATE)); + globals->define("dateof", 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 = 2; + node->set_right(new value_expr_t(value_expr_t::F_DATECMP)); + globals->define("datecmp", node); // Macros node = parse_value_expr("P(a,d)"); @@ -1295,7 +1357,7 @@ void init_value_expr() node = parse_value_expr("P(O,d)"); globals->define("V", node); - globals->define("market_total", node); + globals->define("total_market", node); node = parse_value_expr("v-b"); globals->define("g", node); @@ -1303,7 +1365,7 @@ void init_value_expr() node = parse_value_expr("V-B"); globals->define("G", node); - globals->define("gain_total", node); + globals->define("total_gain", node); value_auto_ptr minx(parse_boolean_expr("min(x,y)=x<y?x:y", globals)); value_auto_ptr maxx(parse_boolean_expr("max(x,y)=x>y?x:y", globals)); @@ -1417,6 +1479,8 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node, case value_expr_t::F_COMMODITY_MASK: out << "F_COMMODITY_MASK"; break; 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::O_NOT: out << "O_NOT"; break; case value_expr_t::O_ARG: out << "O_ARG"; break; @@ -33,27 +33,37 @@ struct details_t #endif }; -typedef void (*value_func_t)(value_t& result, const details_t& details, - value_expr_t * context); - class value_calc { public: virtual ~value_calc() {} - virtual void compute(value_t& result, const details_t& details, - value_expr_t * context = NULL) = 0; + virtual void compute(value_t& result, + const details_t& details = details_t(), + value_expr_t * context = NULL) = 0; + virtual value_t compute(const details_t& details = details_t(), + value_expr_t * context = NULL) = 0; }; +typedef void (*value_func_t)(value_t& result, const details_t& details, + value_expr_t * context); + class value_func : public value_calc { value_func_t func; public: value_func(value_func_t _func) : func(_func) {} - virtual void compute(value_t& result, const details_t& details, - value_expr_t * context = NULL) { + virtual void compute(value_t& result, + const details_t& details = details_t(), + value_expr_t * context = NULL) { func(result, details, context); } + virtual value_t compute(const details_t& details = details_t(), + value_expr_t * context = NULL) { + value_t temp; + func(temp, details, context); + return temp; + } }; struct value_expr_t @@ -99,6 +109,10 @@ struct value_expr_t F_SET_COMMODITY, F_VALUE, F_ABS, + F_ROUND, + F_PRICE, + F_DATE, + F_DATECMP, F_CODE_MASK, F_PAYEE_MASK, F_NOTE_MASK, @@ -193,8 +207,15 @@ struct value_expr_t right = expr ? expr->acquire() : NULL; } - void compute(value_t& result, const details_t& details, - value_expr_t * context = NULL) const; + void compute(value_t& result, + const details_t& details = details_t(), + value_expr_t * context = NULL) const; + value_t compute(const details_t& details = details_t(), + value_expr_t * context = NULL) const { + value_t temp; + compute(temp, details, context); + return temp; + } }; struct scope_t @@ -249,10 +270,6 @@ extern std::auto_ptr<scope_t> global_scope; extern std::time_t terminus; extern bool initialized; -extern bool keep_price; -extern bool keep_date; -extern bool keep_tag; - void init_value_expr(); bool compute_amount(value_expr_t * expr, amount_t& amt, @@ -357,25 +374,44 @@ public: parsed->release(); } - virtual void compute(value_t& result, const details_t& details, - value_expr_t * context = NULL) { + virtual void compute(value_t& result, + const details_t& details = details_t(), + value_expr_t * context = NULL) { parsed->compute(result, details, context); } + virtual value_t compute(const details_t& details = details_t(), + value_expr_t * context = NULL) { + value_t temp; + parsed->compute(temp, details, context); + return temp; + } }; extern std::auto_ptr<value_calc> amount_expr; extern std::auto_ptr<value_calc> total_expr; -inline void compute_amount(value_t& result, const details_t& details) { +inline void compute_amount(value_t& result, + const details_t& details = details_t()) { if (amount_expr.get()) amount_expr->compute(result, details); } -inline void compute_total(value_t& result, const details_t& details) { +inline value_t compute_amount(const details_t& details = details_t()) { + if (amount_expr.get()) + return amount_expr->compute(details); +} + +inline void compute_total(value_t& result, + const details_t& details = details_t()) { if (total_expr.get()) total_expr->compute(result, details); } +inline value_t compute_total(const details_t& details = details_t()) { + if (total_expr.get()) + return total_expr->compute(details); +} + ////////////////////////////////////////////////////////////////////// template <typename T> @@ -408,12 +444,7 @@ class item_predicate } bool operator()(const T& item) const { - if (predicate) { - value_t result; - predicate->compute(result, details_t(item)); - return result; - } - return true; + return ! predicate || predicate->compute(details_t(item)); } }; @@ -1,4 +1,5 @@ #include "value.h" +#include "debug.h" namespace ledger { @@ -19,6 +20,34 @@ void value_t::destroy() } } +void value_t::simplify() +{ + if (realzero()) { + DEBUG_PRINT("amounts.values.simplify", "Zeroing type " << type); + *this = 0L; + return; + } + + if (type == BALANCE_PAIR && + (! ((balance_pair_t *) data)->cost || + ((balance_pair_t *) data)->cost->realzero())) { + DEBUG_PRINT("amounts.values.simplify", "Reducing balance pair to balance"); + cast(BALANCE); + } + + if (type == BALANCE && + ((balance_t *) data)->amounts.size() == 1) { + DEBUG_PRINT("amounts.values.simplify", "Reducing balance to amount"); + cast(AMOUNT); + } + + if (type == AMOUNT && + ! ((amount_t *) data)->commodity()) { + DEBUG_PRINT("amounts.values.simplify", "Reducing amount to integer"); + cast(INTEGER); + } +} + value_t& value_t::operator=(const value_t& value) { if (this == &value) @@ -57,253 +86,493 @@ value_t& value_t::operator=(const value_t& value) return *this; } -#define DEF_VALUE_ADDSUB_OP(OP) \ -value_t& value_t::operator OP(const value_t& value) \ -{ \ - switch (type) { \ - case BOOLEAN: \ - case INTEGER: \ - cast(INTEGER); \ - switch (value.type) { \ - case BOOLEAN: \ - *((long *) data) OP (*((bool *) value.data) ? 1L : 0L); \ - break; \ - case INTEGER: \ - *((long *) data) OP *((long *) value.data); \ - break; \ - case AMOUNT: \ - cast(AMOUNT); \ - *((amount_t *) data) OP *((amount_t *) value.data); \ - break; \ - case BALANCE: \ - cast(BALANCE); \ - *((balance_t *) data) OP *((balance_t *) value.data); \ - break; \ - case BALANCE_PAIR: \ - cast(BALANCE_PAIR); \ - *((balance_pair_t *) data) OP *((balance_pair_t *) value.data); \ - break; \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case AMOUNT: \ - switch (value.type) { \ - case BOOLEAN: \ - if (*((bool *) value.data) && \ - ((amount_t *) data)->commodity()) { \ - cast(BALANCE); \ - return *this OP value; \ - } \ - *((amount_t *) data) OP (*((bool *) value.data) ? 1L : 0L); \ - break; \ - \ - case INTEGER: \ - if (*((long *) value.data) && \ - ((amount_t *) data)->commodity()) { \ - cast(BALANCE); \ - return *this OP value; \ - } \ - *((amount_t *) data) OP *((long *) value.data); \ - break; \ - \ - case AMOUNT: \ - if (((amount_t *) data)->commodity() != \ - ((amount_t *) value.data)->commodity()) { \ - cast(BALANCE); \ - return *this OP value; \ - } \ - *((amount_t *) data) OP *((amount_t *) value.data); \ - break; \ - \ - case BALANCE: \ - cast(BALANCE); \ - *((balance_t *) data) OP *((balance_t *) value.data); \ - break; \ - \ - case BALANCE_PAIR: \ - cast(BALANCE_PAIR); \ - *((balance_pair_t *) data) OP *((balance_pair_t *) value.data); \ - break; \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case BALANCE: \ - switch (value.type) { \ - case BOOLEAN: \ - *((balance_t *) data) OP (*((bool *) value.data) ? 1L : 0L); \ - break; \ - case INTEGER: \ - *((balance_t *) data) OP *((long *) value.data); \ - break; \ - case AMOUNT: \ - *((balance_t *) data) OP *((amount_t *) value.data); \ - break; \ - case BALANCE: \ - *((balance_t *) data) OP *((balance_t *) value.data); \ - break; \ - case BALANCE_PAIR: \ - cast(BALANCE_PAIR); \ - *((balance_pair_t *) data) OP *((balance_pair_t *) value.data); \ - break; \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case BALANCE_PAIR: \ - switch (value.type) { \ - case BOOLEAN: \ - *((balance_pair_t *) data) OP (*((bool *) value.data) ? 1L : 0L); \ - break; \ - case INTEGER: \ - *((balance_pair_t *) data) OP *((long *) value.data); \ - break; \ - case AMOUNT: \ - *((balance_pair_t *) data) OP *((amount_t *) value.data); \ - break; \ - case BALANCE: \ - *((balance_pair_t *) data) OP *((balance_t *) value.data); \ - break; \ - case BALANCE_PAIR: \ - *((balance_pair_t *) data) OP *((balance_pair_t *) value.data); \ - break; \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - default: \ - assert(0); \ - break; \ - } \ - return *this; \ +value_t& value_t::operator+=(const value_t& value) +{ + switch (type) { + case 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; + case AMOUNT: + cast(AMOUNT); + *((amount_t *) data) += *((amount_t *) value.data); + break; + case BALANCE: + cast(BALANCE); + *((balance_t *) data) += *((balance_t *) value.data); + break; + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) += *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + 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()) { + cast(BALANCE); + return *this += value; + } + *((amount_t *) data) += *((long *) value.data); + break; + + case AMOUNT: + if (((amount_t *) data)->commodity() != + ((amount_t *) value.data)->commodity()) { + cast(BALANCE); + return *this += value; + } + *((amount_t *) data) += *((amount_t *) value.data); + break; + + case BALANCE: + cast(BALANCE); + *((balance_t *) data) += *((balance_t *) value.data); + break; + + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) += *((balance_pair_t *) value.data); + break; + + default: + assert(0); + break; + } + break; + + 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; + case AMOUNT: + *((balance_t *) data) += *((amount_t *) value.data); + break; + case BALANCE: + *((balance_t *) data) += *((balance_t *) value.data); + break; + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) += *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + 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; + case AMOUNT: + *((balance_pair_t *) data) += *((amount_t *) value.data); + break; + case BALANCE: + *((balance_pair_t *) data) += *((balance_t *) value.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) += *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + default: + assert(0); + break; + } + return *this; } -DEF_VALUE_ADDSUB_OP(+=) -DEF_VALUE_ADDSUB_OP(-=) +value_t& value_t::operator-=(const value_t& value) +{ + switch (type) { + case 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; + case AMOUNT: + cast(AMOUNT); + *((amount_t *) data) -= *((amount_t *) value.data); + break; + case BALANCE: + cast(BALANCE); + *((balance_t *) data) -= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) -= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; -#define DEF_VALUE_MULDIV_OP(OP) \ -value_t& value_t::operator OP(const value_t& value) \ -{ \ - switch (type) { \ - case BOOLEAN: \ - case INTEGER: \ - cast(INTEGER); \ - switch (value.type) { \ - case BOOLEAN: \ - *((long *) data) OP (*((bool *) value.data) ? 1L : 0L); \ - break; \ - case INTEGER: \ - *((long *) data) OP *((long *) value.data); \ - break; \ - case AMOUNT: \ - cast(AMOUNT); \ - *((amount_t *) data) OP *((amount_t *) value.data); \ - break; \ - case BALANCE: \ - cast(BALANCE); \ - *((balance_t *) data) OP *((balance_t *) value.data); \ - break; \ - case BALANCE_PAIR: \ - cast(BALANCE_PAIR); \ - *((balance_pair_t *) data) OP *((balance_pair_t *) value.data); \ - break; \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case AMOUNT: \ - switch (value.type) { \ - case BOOLEAN: \ - *((amount_t *) data) OP (*((bool *) value.data) ? 1L : 0L); \ - break; \ - case INTEGER: \ - *((amount_t *) data) OP *((long *) value.data); \ - break; \ - case AMOUNT: \ - *((amount_t *) data) OP *((amount_t *) value.data); \ - break; \ - case BALANCE: \ - cast(BALANCE); \ - *((balance_t *) data) OP *((balance_t *) value.data); \ - break; \ - case BALANCE_PAIR: \ - cast(BALANCE_PAIR); \ - *((balance_pair_t *) data) OP *((balance_pair_t *) value.data); \ - break; \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case BALANCE: \ - switch (value.type) { \ - case BOOLEAN: \ - *((balance_t *) data) OP (*((bool *) value.data) ? 1L : 0L); \ - break; \ - case INTEGER: \ - *((balance_t *) data) OP *((long *) value.data); \ - break; \ - case AMOUNT: \ - *((balance_t *) data) OP *((amount_t *) value.data); \ - break; \ - case BALANCE: \ - *((balance_t *) data) OP *((balance_t *) value.data); \ - break; \ - case BALANCE_PAIR: \ - cast(BALANCE_PAIR); \ - *((balance_pair_t *) data) OP *((balance_pair_t *) value.data); \ - break; \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case BALANCE_PAIR: \ - switch (value.type) { \ - case BOOLEAN: \ - *((balance_pair_t *) data) OP (*((bool *) value.data) ? 1L : 0L); \ - break; \ - case INTEGER: \ - *((balance_pair_t *) data) OP *((long *) value.data); \ - break; \ - case AMOUNT: \ - *((balance_pair_t *) data) OP *((amount_t *) value.data); \ - break; \ - case BALANCE: \ - *((balance_pair_t *) data) OP *((balance_t *) value.data); \ - break; \ - case BALANCE_PAIR: \ - *((balance_pair_t *) data) OP *((balance_pair_t *) value.data); \ - break; \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - default: \ - assert(0); \ - break; \ - } \ - return *this; \ + 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()) { + cast(BALANCE); + return *this -= value; + } + *((amount_t *) data) -= *((long *) value.data); + break; + + case AMOUNT: + if (((amount_t *) data)->commodity() != + ((amount_t *) value.data)->commodity()) { + cast(BALANCE); + return *this -= value; + } + *((amount_t *) data) -= *((amount_t *) value.data); + break; + + case BALANCE: + cast(BALANCE); + *((balance_t *) data) -= *((balance_t *) value.data); + break; + + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) -= *((balance_pair_t *) value.data); + break; + + default: + assert(0); + break; + } + break; + + 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; + case AMOUNT: + *((balance_t *) data) -= *((amount_t *) value.data); + break; + case BALANCE: + *((balance_t *) data) -= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) -= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + 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; + case AMOUNT: + *((balance_pair_t *) data) -= *((amount_t *) value.data); + break; + case BALANCE: + *((balance_pair_t *) data) -= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) -= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + default: + assert(0); + break; + } + + simplify(); + + return *this; } -DEF_VALUE_MULDIV_OP(*=) -DEF_VALUE_MULDIV_OP(/=) +value_t& value_t::operator*=(const value_t& value) +{ + if (value.realzero()) { + *this = 0L; + return *this; + } + + switch (type) { + case 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; + case AMOUNT: + cast(AMOUNT); + *((amount_t *) data) *= *((amount_t *) value.data); + break; + case BALANCE: + cast(BALANCE); + *((balance_t *) data) *= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) *= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + 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; + case AMOUNT: + *((amount_t *) data) *= *((amount_t *) value.data); + break; + case BALANCE: + cast(BALANCE); + *((balance_t *) data) *= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) *= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + 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; + case AMOUNT: + *((balance_t *) data) *= *((amount_t *) value.data); + break; + case BALANCE: + *((balance_t *) data) *= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) *= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + 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; + case AMOUNT: + *((balance_pair_t *) data) *= *((amount_t *) value.data); + break; + case BALANCE: + *((balance_pair_t *) data) *= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) *= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + default: + assert(0); + break; + } + return *this; +} + +value_t& value_t::operator/=(const value_t& value) +{ + switch (type) { + case 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; + case AMOUNT: + cast(AMOUNT); + *((amount_t *) data) /= *((amount_t *) value.data); + break; + case BALANCE: + cast(BALANCE); + *((balance_t *) data) /= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + 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; + case AMOUNT: + *((amount_t *) data) /= *((amount_t *) value.data); + break; + case BALANCE: + cast(BALANCE); + *((balance_t *) data) /= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + 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; + case AMOUNT: + *((balance_t *) data) /= *((amount_t *) value.data); + break; + case BALANCE: + *((balance_t *) data) /= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + cast(BALANCE_PAIR); + *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + 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; + case AMOUNT: + *((balance_pair_t *) data) /= *((amount_t *) value.data); + break; + case BALANCE: + *((balance_pair_t *) data) /= *((balance_t *) value.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); + break; + default: + assert(0); + break; + } + break; + + default: + assert(0); + break; + } + return *this; +} #define DEF_VALUE_CMP_OP(OP) \ bool value_t::operator OP(const value_t& value) \ @@ -747,6 +1016,30 @@ value_t value_t::price() const return value_t(); } +value_t value_t::date() const +{ + switch (type) { + case BOOLEAN: + case INTEGER: + return *this; + + case AMOUNT: + return ((amount_t *) data)->date(); + + case BALANCE: + return (long)((balance_t *) data)->date(); + + case BALANCE_PAIR: + return (long)((balance_pair_t *) data)->quantity.date(); + + default: + assert(0); + break; + } + assert(0); + return value_t(); +} + value_t value_t::reduce(const bool keep_price, const bool keep_date, const bool keep_tag) const { @@ -89,6 +89,7 @@ class value_t } void destroy(); + void simplify(); value_t& operator=(const value_t& value); value_t& operator=(const bool value) { @@ -120,40 +121,54 @@ class value_t return *this = amount_t(value); } value_t& operator=(const amount_t& value) { - if ((amount_t *) data != &value) { - if (! value) { - return *this = 0L; - } else { - destroy(); - new((amount_t *)data) amount_t(value); - type = AMOUNT; - } + if (type == AMOUNT && + (amount_t *) data == &value) + return *this; + + if (value.realzero()) { + return *this = 0L; + } else { + destroy(); + new((amount_t *)data) amount_t(value); + type = AMOUNT; } return *this; } value_t& operator=(const balance_t& value) { - if ((balance_t *) data != &value) { - if (value.amounts.size() == 1) { - return *this = (*value.amounts.begin()).second; - } else { - destroy(); - new((balance_t *)data) balance_t(value); - type = BALANCE; - } + if (type == BALANCE && + (balance_t *) data == &value) + return *this; + + if (value.realzero()) { + return *this = 0L; + } + else if (value.amounts.size() == 1) { + return *this = (*value.amounts.begin()).second; + } + else { + destroy(); + new((balance_t *)data) balance_t(value); + type = BALANCE; + return *this; } - return *this; } value_t& operator=(const balance_pair_t& value) { - if ((balance_pair_t *) data != &value) { - if (! value.cost) { - return *this = value.quantity; - } else { - destroy(); - new((balance_pair_t *)data) balance_pair_t(value); - type = BALANCE_PAIR; - } + if (type == BALANCE_PAIR && + (balance_pair_t *) data == &value) + return *this; + + if (value.realzero()) { + return *this = 0L; + } + else if (! value.cost) { + return *this = value.quantity; + } + else { + destroy(); + new((balance_pair_t *)data) balance_pair_t(value); + type = BALANCE_PAIR; + return *this; } - return *this; } value_t& operator+=(const value_t& value); @@ -263,10 +278,32 @@ class value_t return negated(); } + bool realzero() const { + switch (type) { + case BOOLEAN: + return ! *((bool *) data); + case INTEGER: + return *((long *) data) == 0; + case AMOUNT: + return ((amount_t *) data)->realzero(); + case BALANCE: + return ((balance_t *) data)->realzero(); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->realzero(); + + default: + assert(0); + break; + } + assert(0); + 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; @@ -291,12 +328,9 @@ class value_t case BOOLEAN: case INTEGER: break; - case AMOUNT: { - amount_t& amount = *((amount_t *) data); - if (amount.commodity()) - amount = amount.round(amount.commodity().precision()); + case AMOUNT: + *((amount_t *) data) = ((amount_t *) data)->round(); break; - } case BALANCE: ((balance_t *) data)->round(); break; |