diff options
author | John Wiegley <johnw@newartisans.com> | 2007-05-21 20:42:05 +0000 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2008-04-13 03:39:06 -0400 |
commit | 7380da43ab403dacb41d2010093d11942bb7cec1 (patch) | |
tree | 1b9db99b018695254584fe9f8b9ca34a4aa073cb /src/numerics | |
parent | f12d41f233d460bd6d2eb8efb90bf6e36e994a30 (diff) | |
download | fork-ledger-7380da43ab403dacb41d2010093d11942bb7cec1.tar.gz fork-ledger-7380da43ab403dacb41d2010093d11942bb7cec1.tar.bz2 fork-ledger-7380da43ab403dacb41d2010093d11942bb7cec1.zip |
Many changes.
Diffstat (limited to 'src/numerics')
-rw-r--r-- | src/numerics/balance.cc | 272 | ||||
-rw-r--r-- | src/numerics/commodity.cc | 611 | ||||
-rw-r--r-- | src/numerics/commodity.h | 390 | ||||
-rw-r--r-- | src/numerics/value.cc | 1596 | ||||
-rw-r--r-- | src/numerics/value.h | 689 |
5 files changed, 3558 insertions, 0 deletions
diff --git a/src/numerics/balance.cc b/src/numerics/balance.cc new file mode 100644 index 00000000..80637221 --- /dev/null +++ b/src/numerics/balance.cc @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "balance.h" + +namespace ledger { + +balance_t& balance_t::operator+=(const balance_t& bal) +{ + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); + i++) + *this += i->second; + return *this; +} + +balance_t& balance_t::operator+=(const amount_t& amt) +{ + if (amt.is_null()) + throw_(balance_error, + "Cannot add an uninitialized amount to a balance"); + + if (amt.is_realzero()) + return *this; + + amounts_map::iterator i = amounts.find(&amt.commodity()); + if (i != amounts.end()) + i->second += amt; + else + amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); + + return *this; +} + +balance_t& balance_t::operator-=(const balance_t& bal) +{ + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); + i++) + *this -= i->second; + return *this; +} + +balance_t& balance_t::operator-=(const amount_t& amt) +{ + if (amt.is_null()) + throw_(balance_error, + "Cannot subtract an uninitialized amount from a balance"); + + if (amt.is_realzero()) + return *this; + + amounts_map::iterator i = amounts.find(&amt.commodity()); + if (i != amounts.end()) { + i->second -= amt; + if (i->second.is_realzero()) + amounts.erase(i); + } else { + amounts.insert(amounts_map::value_type(&amt.commodity(), amt.negate())); + } + return *this; +} + +balance_t& balance_t::operator*=(const amount_t& amt) +{ + if (amt.is_null()) + throw_(balance_error, + "Cannot multiply a balance by an uninitialized amount"); + + if (is_realzero()) { + ; + } + else if (amt.is_realzero()) { + *this = amt; + } + else if (! amt.commodity()) { + // Multiplying by an amount with no commodity causes all the + // component 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) { + // Multiplying by a commoditized amount is only valid if the sole + // commodity in the balance is of the same kind as the amount's + // commodity. + if (*amounts.begin()->first == amt.commodity()) + amounts.begin()->second *= amt; + else + throw_(balance_error, + "Cannot multiply a balance with annotated commodities by a commoditized amount"); + } + else { + assert(amounts.size() > 1); + throw_(balance_error, + "Cannot multiply a multi-commodity balance by a commoditized amount"); + } + return *this; +} + +balance_t& balance_t::operator/=(const amount_t& amt) +{ + if (amt.is_null()) + throw_(balance_error, + "Cannot divide a balance by an uninitialized amount"); + + if (is_realzero()) { + ; + } + else if (amt.is_realzero()) { + throw_(balance_error, "Divide by zero"); + } + else if (! amt.commodity()) { + // Dividing by an amount with no commodity causes all the + // component amounts to be divided by the same factor. + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + i->second /= amt; + } + else if (amounts.size() == 1) { + // Dividing by a commoditized amount is only valid if the sole + // commodity in the balance is of the same kind as the amount's + // commodity. + if (*amounts.begin()->first == amt.commodity()) + amounts.begin()->second /= amt; + else + throw_(balance_error, + "Cannot divide a balance with annotated commodities by a commoditized amount"); + } + else { + assert(amounts.size() > 1); + throw_(balance_error, + "Cannot divide a multi-commodity balance by a commoditized amount"); + } + return *this; +} + +optional<balance_t> +balance_t::value(const optional<moment_t>& moment) const +{ + optional<balance_t> temp; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (optional<amount_t> val = i->second.value(moment)) { + if (! temp) + temp = balance_t(); + *temp += *val; + } + + return temp; +} + +optional<amount_t> +balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const +{ + // jww (2007-05-20): Needs work + if (! commodity) { + if (amounts.size() == 1) { + 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.commodity_amount(commodity); + + throw_(amount_error, + "Requested amount of a balance with multiple commodities: " << temp); + } + } + else if (amounts.size() > 0) { + amounts_map::const_iterator i = amounts.find(&*commodity); + if (i != amounts.end()) + return i->second; + } + return none; +} + +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.strip_annotations(keep_price, keep_date, keep_tag); + + return temp; +} + +void balance_t::print(std::ostream& out, + const int first_width, + const int latter_width) const +{ + bool first = true; + int lwidth = latter_width; + + if (lwidth == -1) + lwidth = first_width; + + typedef std::vector<const amount_t *> amounts_array; + amounts_array 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_array::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; + } + + if (first) { + out.width(first_width); + out.fill(' '); + out << std::right << "0"; + } +} + +} // namespace ledger diff --git a/src/numerics/commodity.cc b/src/numerics/commodity.cc new file mode 100644 index 00000000..8ab518ee --- /dev/null +++ b/src/numerics/commodity.cc @@ -0,0 +1,611 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file commodity.cc + * @author John Wiegley + * @date Thu Apr 26 15:19:46 2007 + * + * @brief Types for dealing with commodities. + * + * This file defines member functions for flavors of commodity_t. + */ + +#include "amount.h" +#include "parser.h" // for parsing utility functions + +namespace ledger { + +void commodity_t::add_price(const moment_t& date, + const amount_t& price) +{ + if (! base->history) + base->history = history_t(); + + history_map::iterator i = base->history->prices.find(date); + if (i != base->history->prices.end()) { + (*i).second = price; + } else { + std::pair<history_map::iterator, bool> result + = base->history->prices.insert(history_map::value_type(date, price)); + assert(result.second); + } +} + +bool commodity_t::remove_price(const moment_t& date) +{ + if (base->history) { + history_map::size_type n = base->history->prices.erase(date); + if (n > 0) { + if (base->history->prices.empty()) + base->history.reset(); + return true; + } + } + return false; +} + +optional<amount_t> commodity_t::value(const optional<moment_t>& moment) +{ + optional<moment_t> age; + optional<amount_t> price; + + if (base->history) { + assert(base->history->prices.size() > 0); + + if (! moment) { + history_map::reverse_iterator r = base->history->prices.rbegin(); + age = (*r).first; + price = (*r).second; + } else { + history_map::iterator i = base->history->prices.lower_bound(*moment); + if (i == base->history->prices.end()) { + history_map::reverse_iterator r = base->history->prices.rbegin(); + age = (*r).first; + price = (*r).second; + } else { + age = (*i).first; + if (*moment != *age) { + if (i != base->history->prices.begin()) { + --i; + age = (*i).first; + price = (*i).second; + } else { + age = none; + } + } else { + price = (*i).second; + } + } + } + } + + if (! has_flags(COMMODITY_STYLE_NOMARKET) && parent().get_quote) { + if (optional<amount_t> quote = parent().get_quote + (*this, age, moment, + (base->history && base->history->prices.size() > 0 ? + (*base->history->prices.rbegin()).first : optional<moment_t>()))) + return *quote; + } + return price; +} + +commodity_t::operator bool() const +{ + return this != parent().null_commodity; +} + +annotated_commodity_t& commodity_t::as_annotated() +{ + assert(annotated); + return downcast<annotated_commodity_t>(*this); +} + +const annotated_commodity_t& commodity_t::as_annotated() const +{ + assert(annotated); + return downcast<const annotated_commodity_t>(*this); +} + +bool commodity_t::symbol_needs_quotes(const string& symbol) +{ + for (const char * p = symbol.c_str(); *p; p++) + if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.') + return true; + + return false; +} + +void commodity_t::parse_symbol(std::istream& in, string& symbol) +{ + // Invalid commodity characters: + // SPACE, TAB, NEWLINE, RETURN + // 0-9 . , ; - + * / ^ ? : & | ! = + // < > { } [ ] ( ) @ + + static int invalid_chars[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + /* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, + /* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, + /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + char buf[256]; + char c = peek_next_nonws(in); + if (c == '"') { + in.get(c); + READ_INTO(in, buf, 255, c, c != '"'); + if (c == '"') + in.get(c); + else + throw_(amount_error, "Quoted commodity symbol lacks closing quote"); + } else { + READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]); + } + symbol = buf; +} + +void commodity_t::parse_symbol(char *& p, string& symbol) +{ + if (*p == '"') { + char * q = std::strchr(p + 1, '"'); + if (! q) + throw_(parse_error, "Quoted commodity symbol lacks closing quote"); + symbol = string(p + 1, 0, q - p - 1); + p = q + 2; + } else { + char * q = next_element(p); + symbol = p; + if (q) + p = q; + else + p += symbol.length(); + } + if (symbol.empty()) + throw_(parse_error, "Failed to parse commodity"); +} + +bool commodity_t::valid() const +{ + if (symbol().empty() && this != parent().null_commodity) { + DEBUG("ledger.validate", + "commodity_t: symbol().empty() && this != null_commodity"); + return false; + } + + if (annotated && ! base) { + DEBUG("ledger.validate", "commodity_t: annotated && ! base"); + return false; + } + + if (precision() > 16) { + DEBUG("ledger.validate", "commodity_t: precision() > 16"); + return false; + } + + return true; +} + +void annotation_t::parse(std::istream& in) +{ + do { + char buf[256]; + char c = peek_next_nonws(in); + if (c == '{') { + if (price) + throw_(amount_error, "Commodity specifies more than one price"); + + in.get(c); + READ_INTO(in, buf, 255, c, c != '}'); + if (c == '}') + in.get(c); + else + throw_(amount_error, "Commodity price lacks closing brace"); + + amount_t temp; + temp.parse(buf, AMOUNT_PARSE_NO_MIGRATE); + temp.in_place_reduce(); + + // Since this price will maintain its own precision, make sure + // it is at least as large as the base commodity, since the user + // may have only specified {$1} or something similar. + + if (temp.has_commodity() && + temp.precision() < temp.commodity().precision()) + temp = temp.round(); // no need to retain individual precision + + price = temp; + } + else if (c == '[') { + if (date) + throw_(amount_error, "Commodity specifies more than one date"); + + 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 = parse_datetime(buf); + } + else if (c == '(') { + if (tag) + throw_(amount_error, "Commodity specifies more than one tag"); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') + in.get(c); + else + throw_(amount_error, "Commodity tag lacks closing parenthesis"); + + tag = buf; + } + else { + break; + } + } while (true); + + DEBUG("amounts.commodities", + "Parsed commodity annotations: " << std::endl << *this); +} + +bool annotated_commodity_t::operator==(const commodity_t& comm) const +{ + // If the base commodities don't match, the game's up. + if (base != comm.base) + return false; + + assert(annotated); + if (! comm.annotated) + return false; + + if (details != comm.as_annotated().details) + return false; + + return true; +} + +commodity_t& +annotated_commodity_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) +{ + DEBUG("commodity.annotated.strip", + "Reducing commodity " << *this << std::endl + << " keep price " << _keep_price << " " + << " keep date " << _keep_date << " " + << " keep tag " << _keep_tag); + + commodity_t * new_comm; + + if ((_keep_price && details.price) || + (_keep_date && details.date) || + (_keep_tag && details.tag)) + { + new_comm = parent().find_or_create + (referent(), + annotation_t(_keep_price ? details.price : none, + _keep_date ? details.date : none, + _keep_tag ? details.tag : none)); + } else { + new_comm = parent().find_or_create(base_symbol()); + } + + assert(new_comm); + return *new_comm; +} + +void annotated_commodity_t::write_annotations(std::ostream& out, + const annotation_t& info) +{ + if (info.price) + out << " {" << *info.price << '}'; + + if (info.date) + out << " [" << *info.date << ']'; + + if (info.tag) + out << " (" << *info.tag << ')'; +} + +bool compare_amount_commodities::operator()(const amount_t * left, + const amount_t * right) const +{ + commodity_t& leftcomm(left->commodity()); + commodity_t& rightcomm(right->commodity()); + + int cmp = leftcomm.base_symbol().compare(rightcomm.base_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)); + + if (! aleftcomm.details.price && arightcomm.details.price) + return true; + if (aleftcomm.details.price && ! arightcomm.details.price) + return false; + + if (aleftcomm.details.price && arightcomm.details.price) { + amount_t leftprice(*aleftcomm.details.price); + leftprice.in_place_reduce(); + amount_t rightprice(*arightcomm.details.price); + rightprice.in_place_reduce(); + + if (leftprice.commodity() == rightprice.commodity()) { + return (leftprice - rightprice).sign() < 0; + } else { + // Since we have two different amounts, there's really no way + // to establish a true sorting order; we'll just do it based + // on the numerical values. + leftprice.clear_commodity(); + rightprice.clear_commodity(); + return (leftprice - rightprice).sign() < 0; + } + } + + if (! aleftcomm.details.date && arightcomm.details.date) + return true; + if (aleftcomm.details.date && ! arightcomm.details.date) + return false; + + if (aleftcomm.details.date && arightcomm.details.date) { + duration_t diff = *aleftcomm.details.date - *arightcomm.details.date; + return diff.is_negative(); + } + + if (! aleftcomm.details.tag && arightcomm.details.tag) + return true; + if (aleftcomm.details.tag && ! arightcomm.details.tag) + return false; + + if (aleftcomm.details.tag && arightcomm.details.tag) + return *aleftcomm.details.tag < *arightcomm.details.tag; + + assert(false); + return true; + } +} + +commodity_pool_t::commodity_pool_t() : default_commodity(NULL) +{ + null_commodity = create(""); + null_commodity->add_flags(COMMODITY_STYLE_NOMARKET | + COMMODITY_STYLE_BUILTIN); +} + +commodity_t * commodity_pool_t::create(const string& symbol) +{ + shared_ptr<commodity_t::base_t> + base_commodity(new commodity_t::base_t(symbol)); + std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity)); + + DEBUG("amounts.commodities", "Creating base commodity " << symbol); + + // Create the "qualified symbol" version of this commodity's symbol + if (commodity_t::symbol_needs_quotes(symbol)) { + commodity->qualified_symbol = "\""; + *commodity->qualified_symbol += symbol; + *commodity->qualified_symbol += "\""; + } + + DEBUG("amounts.commodities", + "Creating commodity '" << commodity->symbol() << "'"); + + // Start out the new commodity with the default commodity's flags + // and precision, if one has been defined. +#if 0 + // jww (2007-05-02): This doesn't do anything currently! + if (default_commodity) + commodity->drop_flags(COMMODITY_STYLE_THOUSANDS | + COMMODITY_STYLE_NOMARKET); +#endif + + commodity->ident = commodities.size(); + + commodities.push_back(commodity.get()); + return commodity.release(); +} + +commodity_t * commodity_pool_t::find_or_create(const string& symbol) +{ + DEBUG("amounts.commodities", "Find-or-create commodity " << symbol); + + commodity_t * commodity = find(symbol); + if (commodity) + return commodity; + return create(symbol); +} + +commodity_t * commodity_pool_t::find(const string& symbol) +{ + DEBUG("amounts.commodities", "Find commodity " << symbol); + + typedef commodity_pool_t::commodities_t::nth_index<1>::type + commodities_by_name; + + commodities_by_name& name_index = commodities.get<1>(); + commodities_by_name::const_iterator i = name_index.find(symbol); + if (i != name_index.end()) + return *i; + else + return NULL; +} + +commodity_t * commodity_pool_t::find(const commodity_t::ident_t ident) +{ + DEBUG("amounts.commodities", "Find commodity by ident " << ident); + + typedef commodity_pool_t::commodities_t::nth_index<0>::type + commodities_by_ident; + + commodities_by_ident& ident_index = commodities.get<0>(); + return ident_index[ident]; +} + +commodity_t * +commodity_pool_t::create(const string& symbol, const annotation_t& details) +{ + commodity_t * new_comm = create(symbol); + if (! new_comm) + return NULL; + + if (details) + return find_or_create(*new_comm, details); + else + return new_comm; +} + +namespace { + string make_qualified_name(const commodity_t& comm, + const annotation_t& details) + { + assert(details); + + if (details.price && details.price->sign() < 0) + throw_(amount_error, "A commodity's price may not be negative"); + + std::ostringstream name; + comm.print(name); + annotated_commodity_t::write_annotations(name, details); + + DEBUG("amounts.commodities", "make_qualified_name for " + << comm.qualified_symbol << std::endl << details); + DEBUG("amounts.commodities", "qualified_name is " << name.str()); + + return name.str(); + } +} + +commodity_t * +commodity_pool_t::find(const string& symbol, const annotation_t& details) +{ + commodity_t * comm = find(symbol); + if (! comm) + return NULL; + + if (details) { + string name = make_qualified_name(*comm, details); + + if (commodity_t * ann_comm = find(name)) { + assert(ann_comm->annotated && + ann_comm->as_annotated().details); + return ann_comm; + } + return NULL; + } else { + return comm; + } +} + +commodity_t * +commodity_pool_t::find_or_create(const string& symbol, + const annotation_t& details) +{ + commodity_t * comm = find(symbol); + if (! comm) + return NULL; + + if (details) + return find_or_create(*comm, details); + else + return comm; +} + +commodity_t * +commodity_pool_t::create(commodity_t& comm, + const annotation_t& details, + const string& mapping_key) +{ + assert(comm); + assert(details); + assert(! mapping_key.empty()); + + std::auto_ptr<commodity_t> commodity + (new annotated_commodity_t(&comm, details)); + + commodity->qualified_symbol = comm.symbol(); + assert(! commodity->qualified_symbol->empty()); + + DEBUG("amounts.commodities", "Creating annotated commodity " + << "symbol " << commodity->symbol() + << " key " << mapping_key << std::endl << details); + + // Add the fully annotated name to the map, so that this symbol may + // quickly be found again. + commodity->ident = commodities.size(); + commodity->mapping_key_ = mapping_key; + + commodities.push_back(commodity.get()); + return commodity.release(); +} + +commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, + const annotation_t& details) +{ + assert(comm); + assert(details); + + string name = make_qualified_name(comm, details); + assert(! name.empty()); + + if (commodity_t * ann_comm = find(name)) { + assert(ann_comm->annotated && ann_comm->as_annotated().details); + return ann_comm; + } + return create(comm, details, name); +} + +} // namespace ledger diff --git a/src/numerics/commodity.h b/src/numerics/commodity.h new file mode 100644 index 00000000..6212e743 --- /dev/null +++ b/src/numerics/commodity.h @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file commodity.h + * @author John Wiegley + * @date Wed Apr 18 22:05:53 2007 + * + * @brief Types for handling commodities. + * + * This file contains one of the most basic types in Ledger: + * commodity_t, and its annotated cousin, annotated_commodity_t. + */ + +#ifndef _COMMODITY_H +#define _COMMODITY_H + +namespace ledger { + +class annotated_commodity_t; + +class commodity_t + : public delegates_flags<>, + public equality_comparable1<commodity_t, noncopyable> +{ + friend class commodity_pool_t; + + class base_t : public noncopyable, public supports_flags<> + { + public: + typedef std::map<const moment_t, amount_t> history_map; + + struct history_t { + history_map prices; + ptime last_lookup; + }; + +#define COMMODITY_STYLE_DEFAULTS 0x00 +#define COMMODITY_STYLE_SUFFIXED 0x01 +#define COMMODITY_STYLE_SEPARATED 0x02 +#define COMMODITY_STYLE_EUROPEAN 0x04 +#define COMMODITY_STYLE_THOUSANDS 0x08 +#define COMMODITY_STYLE_NOMARKET 0x10 +#define COMMODITY_STYLE_BUILTIN 0x20 + + string symbol; + amount_t::precision_t precision; + optional<string> name; + optional<string> note; + optional<history_t> history; + optional<amount_t> smaller; + optional<amount_t> larger; + + public: + explicit base_t(const string& _symbol) + : supports_flags<>(COMMODITY_STYLE_DEFAULTS), + symbol(_symbol), precision(0) { + TRACE_CTOR(base_t, "const string&"); + } + ~base_t() { + TRACE_DTOR(base_t); + } + }; + +public: + static bool symbol_needs_quotes(const string& symbol); + + typedef base_t::history_t history_t; + typedef base_t::history_map history_map; + typedef uint_least32_t ident_t; + + shared_ptr<base_t> base; + + commodity_pool_t * parent_; + ident_t ident; + optional<string> qualified_symbol; + optional<string> mapping_key_; + bool annotated; + +public: + explicit commodity_t(commodity_pool_t * _parent, + const shared_ptr<base_t>& _base) + : delegates_flags<>(*_base.get()), base(_base), + parent_(_parent), annotated(false) { + TRACE_CTOR(commodity_t, ""); + } + virtual ~commodity_t() { + TRACE_DTOR(commodity_t); + } + + operator bool() const; + + virtual bool operator==(const commodity_t& comm) const { + if (comm.annotated) + return comm == *this; + return base.get() == comm.base.get(); + } + + commodity_pool_t& parent() const { + return *parent_; + } + + annotated_commodity_t& as_annotated(); + const annotated_commodity_t& as_annotated() const; + + string base_symbol() const { + return base->symbol; + } + string symbol() const { + return qualified_symbol ? *qualified_symbol : base_symbol(); + } + + string mapping_key() const { + if (mapping_key_) + return *mapping_key_; + else + return base_symbol(); + } + + optional<string> name() const { + return base->name; + } + void set_name(const optional<string>& arg = none) { + base->name = arg; + } + + optional<string> note() const { + return base->note; + } + void set_note(const optional<string>& arg = none) { + base->note = arg; + } + + amount_t::precision_t precision() const { + return base->precision; + } + void set_precision(amount_t::precision_t arg) { + base->precision = arg; + } + + optional<amount_t> smaller() const { + return base->smaller; + } + void set_smaller(const optional<amount_t>& arg = none) { + base->smaller = arg; + } + + optional<amount_t> larger() const { + return base->larger; + } + void set_larger(const optional<amount_t>& arg = none) { + base->larger = arg; + } + + optional<history_t> history() const { + return base->history; + } + + void add_price(const moment_t& date, const amount_t& price); + bool remove_price(const moment_t& date); + + optional<amount_t> value(const optional<moment_t>& moment = none); + + static void parse_symbol(std::istream& in, string& symbol); + static void parse_symbol(char *& p, string& symbol); + static string parse_symbol(std::istream& in) { + string temp; + parse_symbol(in, temp); + return temp; + } + + void print(std::ostream& out) const { + out << symbol(); + } + + void read(std::istream& in); + void read(char *& data); + void write(std::ostream& out) const; + + bool valid() const; +}; + +inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { + comm.print(out); + return out; +} + +struct annotation_t : public equality_comparable<annotation_t> +{ + optional<amount_t> price; + optional<moment_t> date; + optional<string> tag; + + explicit annotation_t + (const optional<amount_t>& _price = none, + const optional<moment_t>& _date = none, + const optional<string>& _tag = none) + : price(_price), date(_date), tag(_tag) {} + + operator bool() const { + return price || date || tag; + } + + bool operator==(const annotation_t& rhs) const { + return (price == rhs.price && + date == rhs.date && + tag == rhs.tag); + } + + void parse(std::istream& in); + void print(std::ostream& out) const { + out << "price " << (price ? price->to_string() : "NONE") << " " + << "date " << (date ? *date : moment_t()) << " " + << "tag " << (tag ? *tag : "NONE"); + } + + bool valid() const { + assert(*this); + return true; + } +}; + +inline std::ostream& operator<<(std::ostream& out, const annotation_t& details) { + details.print(out); + return out; +} + +class annotated_commodity_t + : public commodity_t, + public equality_comparable<annotated_commodity_t, + equality_comparable2<annotated_commodity_t, commodity_t, + noncopyable> > +{ +public: + commodity_t * ptr; + annotation_t details; + + explicit annotated_commodity_t(commodity_t * _ptr, + const annotation_t& _details) + : commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) { + TRACE_CTOR(annotated_commodity_t, ""); + annotated = true; + } + virtual ~annotated_commodity_t() { + TRACE_DTOR(annotated_commodity_t); + } + + virtual bool operator==(const commodity_t& comm) const; + virtual bool operator==(const annotated_commodity_t& comm) const { + return *this == static_cast<const commodity_t&>(comm); + } + + commodity_t& referent() { + return *ptr; + } + const commodity_t& referent() const { + return *ptr; + } + + commodity_t& strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag); + + void write_annotations(std::ostream& out) const { + annotated_commodity_t::write_annotations(out, details); + } + + static void write_annotations(std::ostream& out, + const annotation_t& info); +}; + +struct compare_amount_commodities { + bool operator()(const amount_t * left, const amount_t * right) const; +}; + +class commodity_pool_t : public noncopyable +{ + /** + * The commodities collection in commodity_pool_t maintains pointers + * to all the commodities which have ever been created by the user, + * whether explicitly by calling the create methods of + * commodity_pool_t, or implicitly by parsing a commoditized amount. + * + * The `commodities' member variable represents a collection which + * is indexed by two vertices: first, and ordered sequence of unique + * integer which identify commodities by a numerical identifier; and + * second, by a hashed set of symbolic names which reflect how the + * commodity was referred to by the user. + */ + typedef multi_index_container< + commodity_t *, + multi_index::indexed_by< + multi_index::random_access<>, + multi_index::hashed_unique< + multi_index::const_mem_fun<commodity_t, + string, &commodity_t::mapping_key> > + > + > commodities_t; + + commodities_t commodities; + +public: + commodity_t * null_commodity; + commodity_t * default_commodity; + +private: + template<typename T> + struct first_initialized + { + typedef T result_type; + + template<typename InputIterator> + T operator()(InputIterator first, InputIterator last) const + { + for (; first != last; first++) + if (*first) + return *first; + return T(); + } + }; + +public: + boost::function<optional<amount_t> + (commodity_t& commodity, + const optional<moment_t>& date, + const optional<moment_t>& moment, + const optional<moment_t>& last)> get_quote; + + explicit commodity_pool_t(); + + ~commodity_pool_t() { + typedef commodity_pool_t::commodities_t::nth_index<0>::type + commodities_by_ident; + + commodities_by_ident& ident_index = commodities.get<0>(); + for (commodities_by_ident::iterator i = ident_index.begin(); + i != ident_index.end(); + i++) + checked_delete(*i); + } + + commodity_t * create(const string& symbol); + commodity_t * find(const string& name); + commodity_t * find(const commodity_t::ident_t ident); + commodity_t * find_or_create(const string& symbol); + + commodity_t * create(const string& symbol, const annotation_t& details); + commodity_t * find(const string& symbol, const annotation_t& details); + commodity_t * find_or_create(const string& symbol, + const annotation_t& details); + + commodity_t * create(commodity_t& comm, + const annotation_t& details, + const string& mapping_key); + + commodity_t * find_or_create(commodity_t& comm, + const annotation_t& details); +}; + +} // namespace ledger + +#endif // _COMMODITY_H diff --git a/src/numerics/value.cc b/src/numerics/value.cc new file mode 100644 index 00000000..a938b1bc --- /dev/null +++ b/src/numerics/value.cc @@ -0,0 +1,1596 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "value.h" +#include "node.h" + +namespace ledger { + +intrusive_ptr<value_t::storage_t> value_t::true_value; +intrusive_ptr<value_t::storage_t> value_t::false_value; + +void value_t::storage_t::destroy() +{ + switch (type) { + case AMOUNT: + ((amount_t *)data)->~amount_t(); + break; + case BALANCE: + checked_delete(*(balance_t **)data); + break; + case BALANCE_PAIR: + checked_delete(*(balance_pair_t **)data); + break; + case STRING: + ((string *)data)->~string(); + break; + case SEQUENCE: + checked_delete(*(sequence_t **)data); + break; + case POINTER: + ((boost::any *)data)->~any(); + break; + + default: + break; + } + type = VOID; +} + +void value_t::initialize() +{ +#if 0 + LOGGER("value.initialize"); +#endif + + true_value = new storage_t; + true_value->type = BOOLEAN; + *(bool *) true_value->data = true; + + false_value = new storage_t; + false_value->type = BOOLEAN; + *(bool *) false_value->data = false; + + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(bool)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(moment_t)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(long)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(amount_t)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_t *)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_pair_t *)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(string)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(sequence_t *)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(xml::node_t *)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(boost::any)); + +#if 0 + DEBUG_(std::setw(3) << std::right << sizeof(bool) + << " sizeof(bool)"); + DEBUG_(std::setw(3) << std::right << sizeof(moment_t) + << " sizeof(moment_t)"); + DEBUG_(std::setw(3) << std::right << sizeof(long) + << " sizeof(long)"); + DEBUG_(std::setw(3) << std::right << sizeof(amount_t) + << " sizeof(amount_t)"); + DEBUG_(std::setw(3) << std::right << sizeof(balance_t *) + << " sizeof(balance_t *)"); + DEBUG_(std::setw(3) << std::right << sizeof(balance_pair_t *) + << " sizeof(balance_pair_t *)"); + DEBUG_(std::setw(3) << std::right << sizeof(string) + << " sizeof(string)"); + DEBUG_(std::setw(3) << std::right << sizeof(sequence_t *) + << " sizeof(sequence_t *)"); + DEBUG_(std::setw(3) << std::right << sizeof(boost::any) + << " sizeof(boost::any)"); +#endif +} + +void value_t::shutdown() +{ + true_value = intrusive_ptr<storage_t>(); + false_value = intrusive_ptr<storage_t>(); +} + +value_t::operator bool() const +{ + switch (type()) { + case BOOLEAN: + return as_boolean(); + case INTEGER: + return as_long(); + case DATETIME: + return is_valid_moment(as_datetime()); + case AMOUNT: + return as_amount(); + case BALANCE: + return as_balance(); + case BALANCE_PAIR: + return as_balance_pair(); + case STRING: + return ! as_string().empty(); + case SEQUENCE: + return ! as_sequence().empty(); + case XML_NODE: + return as_xml_node()->to_value(); + case POINTER: + return ! as_any_pointer().empty(); + default: + assert(false); + break; + } + assert(false); + return 0; +} + +bool value_t::to_boolean() const +{ + if (is_boolean()) { + return as_boolean(); + } else { + value_t temp(*this); + temp.in_place_cast(BOOLEAN); + return temp.as_boolean(); + } +} + +long value_t::to_long() const +{ + if (is_long()) { + return as_long(); + } else { + value_t temp(*this); + temp.in_place_cast(INTEGER); + return temp.as_long(); + } +} + +moment_t value_t::to_datetime() const +{ + if (is_datetime()) { + return as_datetime(); + } else { + value_t temp(*this); + temp.in_place_cast(DATETIME); + return temp.as_datetime(); + } +} + +amount_t value_t::to_amount() const +{ + if (is_amount()) { + return as_amount(); + } else { + value_t temp(*this); + temp.in_place_cast(AMOUNT); + return temp.as_amount(); + } +} + +balance_t value_t::to_balance() const +{ + if (is_balance()) { + return as_balance(); + } else { + value_t temp(*this); + temp.in_place_cast(BALANCE); + return temp.as_balance(); + } +} + +balance_pair_t value_t::to_balance_pair() const +{ + if (is_balance_pair()) { + return as_balance_pair(); + } else { + value_t temp(*this); + temp.in_place_cast(BALANCE_PAIR); + return temp.as_balance_pair(); + } +} + +string value_t::to_string() const +{ + if (is_string()) { + return as_string(); + } else { + value_t temp(*this); + temp.in_place_cast(STRING); + return temp.as_string(); + } +} + +value_t::sequence_t value_t::to_sequence() const +{ + if (is_sequence()) { + return as_sequence(); + } else { + value_t temp(*this); + temp.in_place_cast(SEQUENCE); + return temp.as_sequence(); + } +} + + +void value_t::in_place_simplify() +{ + LOGGER("amounts.values.simplify"); + + if (is_realzero()) { + DEBUG_("Zeroing type " << type()); + set_long(0L); + return; + } + + if (is_balance_pair() && + (! as_balance_pair().cost || as_balance_pair().cost->is_realzero())) { + DEBUG_("Reducing balance pair to balance"); + in_place_cast(BALANCE); + } + + if (is_balance() && as_balance().amounts.size() == 1) { + DEBUG_("Reducing balance to amount"); + in_place_cast(AMOUNT); + } + +#if 0 + if (is_amount() && ! as_amount().has_commodity() && + as_amount().fits_in_long()) { + DEBUG_("Reducing amount to integer"); + in_place_cast(INTEGER); + } +#endif +} + +value_t& value_t::operator+=(const value_t& val) +{ + if (is_string()) { + if (val.is_string()) + as_string_lval() += val.as_string(); + else + as_string_lval() += val.to_string(); + return *this; + } + else if (is_sequence()) { + if (val.is_sequence()) { + sequence_t& seq(as_sequence_lval()); + seq.insert(seq.end(), val.as_sequence().begin(), + val.as_sequence().end()); + } else { + as_sequence_lval().push_back(val); + } + return *this; + } + + if (val.is_xml_node()) // recurse + return *this += val.as_xml_node()->to_value(); + + switch (type()) { + case DATETIME: + switch (val.type()) { + case INTEGER: + as_datetime_lval() += date_duration(val.as_long()); + return *this; + case AMOUNT: + as_datetime_lval() += date_duration(val.as_amount().to_long()); + return *this; + default: + break; + } + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() += val.as_long(); + return *this; + case AMOUNT: + in_place_cast(AMOUNT); + as_amount_lval() += val.as_amount(); + return *this; + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() += val.as_balance(); + return *this; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() += val.as_balance_pair(); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + if (as_amount().has_commodity()) { + in_place_cast(BALANCE); + return *this += val; + } else { + as_amount_lval() += val.as_long(); + return *this; + } + break; + + case AMOUNT: + if (as_amount().commodity() != val.as_amount().commodity()) { + in_place_cast(BALANCE); + return *this += val; + } else { + as_amount_lval() += val.as_amount(); + return *this; + } + break; + + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() += val.as_balance(); + return *this; + + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() += val.as_balance_pair(); + return *this; + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() += val.to_amount(); + return *this; + case AMOUNT: + as_balance_lval() += val.as_amount(); + return *this; + case BALANCE: + as_balance_lval() += val.as_balance(); + return *this; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() += val.as_balance_pair(); + return *this; + default: + break; + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + as_balance_pair_lval() += val.to_amount(); + return *this; + case AMOUNT: + as_balance_pair_lval() += val.as_amount(); + return *this; + case BALANCE: + as_balance_pair_lval() += val.as_balance(); + return *this; + case BALANCE_PAIR: + as_balance_pair_lval() += val.as_balance_pair(); + return *this; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, "Cannot add " << label() << " to " << val.label()); + + return *this; +} + +value_t& value_t::operator-=(const value_t& val) +{ + if (is_sequence()) { + sequence_t& seq(as_sequence_lval()); + + if (val.is_sequence()) { + for (sequence_t::const_iterator i = val.as_sequence().begin(); + i != val.as_sequence().end(); + i++) { + sequence_t::iterator j = std::find(seq.begin(), seq.end(), *i); + if (j != seq.end()) + seq.erase(j); + } + } else { + sequence_t::iterator i = std::find(seq.begin(), seq.end(), val); + if (i != seq.end()) + seq.erase(i); + } + return *this; + } + + if (val.is_xml_node()) // recurse + return *this -= val.as_xml_node()->to_value(); + + switch (type()) { + case DATETIME: + switch (val.type()) { + case INTEGER: + as_datetime_lval() -= date_duration(val.as_long()); + return *this; + case AMOUNT: + as_datetime_lval() -= date_duration(val.as_amount().to_long()); + return *this; + default: + break; + } + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() -= val.as_long(); + return *this; + case AMOUNT: + in_place_cast(AMOUNT); + as_amount_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + if (as_amount().has_commodity()) { + in_place_cast(BALANCE); + *this -= val; + in_place_simplify(); + return *this; + } else { + as_amount_lval() -= val.as_long(); + in_place_simplify(); + return *this; + } + break; + + case AMOUNT: + if (as_amount().commodity() != val.as_amount().commodity()) { + in_place_cast(BALANCE); + *this -= val; + in_place_simplify(); + return *this; + } else { + as_amount_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + } + break; + + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() -= val.to_amount(); + in_place_simplify(); + return *this; + case AMOUNT: + as_balance_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + case BALANCE: + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; + default: + break; + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + as_balance_pair_lval() -= val.to_amount(); + in_place_simplify(); + return *this; + case AMOUNT: + as_balance_pair_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + case BALANCE: + as_balance_pair_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + case BALANCE_PAIR: + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, "Cannot subtract " << label() << " from " << val.label()); + + return *this; +} + +value_t& value_t::operator*=(const value_t& val) +{ + if (is_string()) { + string temp; + long count = val.to_long(); + for (long i = 0; i < count; i++) + temp += as_string(); + set_string(temp); + return *this; + } + else if (is_sequence()) { + value_t temp; + long count = val.to_long(); + for (long i = 0; i < count; i++) + temp += as_sequence(); + return *this = temp; + } + + if (val.is_xml_node()) // recurse + return *this *= val.as_xml_node()->to_value(); + + switch (type()) { + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() *= val.as_long(); + return *this; + case AMOUNT: + set_amount(val.as_amount() * as_long()); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + as_amount_lval() *= val.as_long(); + return *this; + case AMOUNT: + if (as_amount().commodity() == val.as_amount().commodity() || + ! val.as_amount().has_commodity()) { + as_amount_lval() *= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() *= val.as_long(); + return *this; + case AMOUNT: + if (! val.as_amount().has_commodity()) { + as_balance_lval() *= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + as_balance_pair_lval() *= val.as_long(); + return *this; + case AMOUNT: + if (! val.as_amount().has_commodity()) { + as_balance_pair_lval() *= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, "Cannot multiply " << label() << " with " << val.label()); + + return *this; +} + +value_t& value_t::operator/=(const value_t& val) +{ + if (val.is_xml_node()) // recurse + return *this /= val.as_xml_node()->to_value(); + + switch (type()) { + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() /= val.as_long(); + return *this; + case AMOUNT: + set_amount(val.as_amount() / as_long()); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + as_amount_lval() /= val.as_long(); + return *this; + + case AMOUNT: + if (as_amount().commodity() == val.as_amount().commodity() || + ! val.as_amount().has_commodity()) { + as_amount_lval() /= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() /= val.as_long(); + return *this; + case AMOUNT: + if (! val.as_amount().has_commodity()) { + as_balance_lval() /= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + as_balance_pair_lval() /= val.as_long(); + return *this; + case AMOUNT: + if (! val.as_amount().has_commodity()) { + as_balance_pair_lval() /= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, "Cannot divide " << label() << " by " << val.label()); + + return *this; +} + + +bool value_t::operator==(const value_t& val) const +{ + if (is_xml_node() && val.is_xml_node()) + return as_xml_node() == val.as_xml_node(); + else if (is_xml_node()) + return as_xml_node()->to_value() == val; + else if (val.is_xml_node()) + return *this == val.as_xml_node()->to_value(); + + switch (type()) { + case BOOLEAN: + if (val.is_boolean()) + return as_boolean() == val.as_boolean(); + break; + + case DATETIME: + if (val.is_datetime()) + return as_datetime() == val.as_datetime(); + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() == val.as_long(); + case AMOUNT: + return val.as_amount() == to_amount(); + case BALANCE: + return val.as_balance() == to_amount(); + case BALANCE_PAIR: + return val.as_balance_pair() == to_amount(); + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + return as_amount() == val.as_long(); + case AMOUNT: + return as_amount() == val.as_amount(); + case BALANCE: + return val.as_balance() == as_amount(); + case BALANCE_PAIR: + return val.as_balance_pair() == as_amount(); + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + return as_balance() == val.to_amount(); + case AMOUNT: + return as_balance() == val.as_amount(); + case BALANCE: + return as_balance() == val.as_balance(); + case BALANCE_PAIR: + return val.as_balance_pair() == as_balance(); + default: + break; + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + return as_balance_pair() == val.to_amount(); + case AMOUNT: + return as_balance_pair() == val.as_amount(); + case BALANCE: + return as_balance_pair() == val.as_balance(); + case BALANCE_PAIR: + return as_balance_pair() == val.as_balance_pair(); + default: + break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() == val.as_string(); + break; + + case SEQUENCE: + if (val.is_sequence()) + return as_sequence() == val.as_sequence(); + break; + + default: + break; + } + + throw_(value_error, "Cannot compare " << label() << " to " << val.label()); + + return *this; +} + +bool value_t::operator<(const value_t& val) const +{ + if (is_xml_node() && val.is_xml_node()) + return as_xml_node() < val.as_xml_node(); + else if (is_xml_node()) + return as_xml_node()->to_value() < val; + else if (val.is_xml_node()) + return *this < val.as_xml_node()->to_value(); + + switch (type()) { + case DATETIME: + if (val.is_datetime()) + return as_datetime() < val.as_datetime(); + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() < val.as_long(); + case AMOUNT: + return val.as_amount() < as_long(); + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + return as_amount() < val.as_long(); + case AMOUNT: + return as_amount() < val.as_amount(); + default: + break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() < val.as_string(); + break; + + default: + break; + } + + throw_(value_error, "Cannot compare " << label() << " to " << val.label()); + + return *this; +} + +#if 0 +bool value_t::operator>(const value_t& val) const +{ + if (is_xml_node() && val.is_xml_node()) + return as_xml_node() > val.as_xml_node(); + else if (is_xml_node()) + return as_xml_node()->to_value() > val; + else if (val.is_xml_node()) + return *this > val.as_xml_node()->to_value(); + + switch (type()) { + case DATETIME: + if (val.is_datetime()) + return as_datetime() > val.as_datetime(); + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() > val.as_long(); + case AMOUNT: + return val.as_amount() > as_long(); + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + return as_amount() > val.as_long(); + case AMOUNT: + return as_amount() > val.as_amount(); + default: + break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() > val.as_string(); + break; + + default: + break; + } + + throw_(value_error, + "Cannot compare " << label() << " to " << val.label()); + + return *this; +} +#endif + +void value_t::in_place_cast(type_t cast_type) +{ + if (type() == cast_type) + return; + + if (cast_type == BOOLEAN) { + set_boolean(bool(*this)); + return; + } + else if (cast_type == SEQUENCE) { + sequence_t temp; + if (! is_null()) + temp.push_back(*this); + set_sequence(temp); + return; + } + + // This must came after the if's above, otherwise it would be + // impossible to turn an XML node into a sequence containing that + // same XML node. + if (is_xml_node()) { + *this = as_xml_node()->to_value().cast(cast_type); + return; + } + + switch (type()) { + case BOOLEAN: + switch (cast_type) { + case STRING: + set_string(as_boolean() ? "true" : "false"); + return; + default: + break; + } + break; + + case INTEGER: + switch (cast_type) { + case AMOUNT: + set_amount(as_long()); + return; + case BALANCE: + set_balance(to_amount()); + return; + case BALANCE_PAIR: + set_balance_pair(to_amount()); + return; + case STRING: + set_string(lexical_cast<string>(as_long())); + return; + default: + break; + } + break; + + case AMOUNT: + switch (cast_type) { + case INTEGER: + set_long(as_amount().to_long()); + return; + case BALANCE: + set_balance(as_amount()); + return; + case BALANCE_PAIR: + set_balance_pair(as_amount()); + return; + case STRING: + set_string(as_amount().to_string()); + return; + default: + break; + } + break; + + case BALANCE: + switch (cast_type) { + case AMOUNT: { + const balance_t& temp(as_balance()); + if (temp.amounts.size() == 1) { + set_amount((*temp.amounts.begin()).second); + return; + } + else if (temp.amounts.size() == 0) { + set_amount(0L); + return; + } + else { + throw_(value_error, "Cannot convert " << label() << + " with multiple commodities to " << label(cast_type)); + } + break; + } + case BALANCE_PAIR: + set_balance_pair(as_balance()); + return; + default: + break; + } + break; + + case BALANCE_PAIR: + switch (cast_type) { + case AMOUNT: { + const balance_t& temp(as_balance_pair().quantity); + if (temp.amounts.size() == 1) { + set_amount((*temp.amounts.begin()).second); + return; + } + else if (temp.amounts.size() == 0) { + set_amount(0L); + return; + } + else { + throw_(value_error, "Cannot convert " << label() << + " with multiple commodities to " << label(cast_type)); + } + break; + } + case BALANCE: + set_balance(as_balance_pair().quantity); + return; + default: + break; + } + break; + + case STRING: + switch (cast_type) { + case INTEGER: { + if (all(as_string(), is_digit())) { + set_long(lexical_cast<long>(as_string())); + return; + } else { + throw_(value_error, + "Cannot convert string '" << *this << "' to an integer"); + } + break; + } + case AMOUNT: + set_amount(amount_t(as_string())); + return; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, + "Cannot convert " << label() << " to " << label(cast_type)); +} + +void value_t::in_place_negate() +{ + switch (type()) { + case BOOLEAN: + set_boolean(! as_boolean()); + return; + case INTEGER: + set_long(- as_long()); + return; + case AMOUNT: + as_amount_lval().in_place_negate(); + return; + case BALANCE: + as_balance_lval().in_place_negate(); + return; + case BALANCE_PAIR: + as_balance_pair_lval().in_place_negate(); + return; + case XML_NODE: + *this = as_xml_node()->to_value(); + in_place_negate(); + return; + default: + break; + } + + throw_(value_error, "Cannot negate " << label()); +} + +bool value_t::is_realzero() const +{ + switch (type()) { + case BOOLEAN: + return ! as_boolean(); + case INTEGER: + return as_long() == 0; + case DATETIME: + return ! is_valid_moment(as_datetime()); + case AMOUNT: + return as_amount().is_realzero(); + case BALANCE: + return as_balance().is_realzero(); + case BALANCE_PAIR: + return as_balance_pair().is_realzero(); + case STRING: + return as_string().empty(); + case SEQUENCE: + return as_sequence().empty(); + + case XML_NODE: + return as_xml_node() == NULL; + case POINTER: + return as_any_pointer().empty(); + + default: + assert(false); + break; + } + assert(false); + return true; +} + +value_t value_t::value(const optional<moment_t>& moment) const +{ + switch (type()) { + case INTEGER: + return *this; + + case AMOUNT: { + if (optional<amount_t> val = as_amount().value(moment)) + return *val; + return false; + } + case BALANCE: { + if (optional<balance_t> bal = as_balance().value(moment)) + return *bal; + return false; + } + case BALANCE_PAIR: { + if (optional<balance_t> bal_pair = + as_balance_pair().quantity.value(moment)) + return *bal_pair; + return false; + } + case XML_NODE: + return as_xml_node()->to_value().value(moment); + + default: + break; + } + + throw_(value_error, "Cannot find the value of " << label()); + return value_t(); +} + +void value_t::in_place_reduce() +{ + switch (type()) { + case INTEGER: + return; + case AMOUNT: + as_amount_lval().in_place_reduce(); + return; + case BALANCE: + as_balance_lval().in_place_reduce(); + return; + case BALANCE_PAIR: + as_balance_pair_lval().in_place_reduce(); + return; + case XML_NODE: + *this = as_xml_node()->to_value(); + in_place_reduce(); // recurse + return; + default: + break; + } + + throw_(value_error, "Cannot reduce " << label()); +} + +value_t value_t::round() const +{ + switch (type()) { + case INTEGER: + return *this; + case AMOUNT: + return as_amount().round(); + case XML_NODE: + return as_xml_node()->to_value().round(); + default: + break; + } + + throw_(value_error, "Cannot round " << label()); + return value_t(); +} + +value_t value_t::unround() const +{ + switch (type()) { + case INTEGER: + return *this; + case AMOUNT: + return as_amount().unround(); + case XML_NODE: + return as_xml_node()->to_value().unround(); + default: + break; + } + + throw_(value_error, "Cannot unround " << label()); + return value_t(); +} + +value_t value_t::annotated_price() const +{ + switch (type()) { + case AMOUNT: { + optional<amount_t> temp = as_amount().annotation_details().price; + if (! temp) + return false; + return *temp; + } + + case XML_NODE: + return as_xml_node()->to_value().annotated_price(); + + default: + break; + } + + throw_(value_error, "Cannot find the annotated price of " << label()); + return value_t(); +} + +value_t value_t::annotated_date() const +{ + switch (type()) { + case DATETIME: + return *this; + + case AMOUNT: { + optional<moment_t> temp = as_amount().annotation_details().date; + if (! temp) + return false; + return *temp; + } + + case XML_NODE: + return as_xml_node()->to_value().annotated_date(); + + default: + break; + } + + throw_(value_error, "Cannot find the annotated date of " << label()); + return value_t(); +} + +value_t value_t::annotated_tag() const +{ + switch (type()) { + case DATETIME: + return *this; + + case AMOUNT: { + optional<string> temp = as_amount().annotation_details().tag; + if (! temp) + return false; + return value_t(*temp, true); + } + + case XML_NODE: + return as_xml_node()->to_value().annotated_tag(); + + default: + break; + } + + throw_(value_error, "Cannot find the annotated tag of " << label()); + return value_t(); +} + +value_t value_t::strip_annotations(const bool keep_price, + const bool keep_date, + const bool keep_tag) const +{ + switch (type()) { + case VOID: + case BOOLEAN: + case INTEGER: + case DATETIME: + case STRING: + case XML_NODE: + case POINTER: + return *this; + + case SEQUENCE: { + sequence_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(value.strip_annotations(keep_price, keep_date, keep_tag)); + return temp; + } + + case AMOUNT: + return as_amount().strip_annotations(keep_price, keep_date, keep_tag); + case BALANCE: + return as_balance().strip_annotations(keep_price, keep_date, keep_tag); + case BALANCE_PAIR: + return as_balance_pair().quantity.strip_annotations(keep_price, + keep_date, keep_tag); + + default: + assert(false); + break; + } + assert(false); + return value_t(); +} + +value_t value_t::cost() const +{ + switch (type()) { + case INTEGER: + case AMOUNT: + case BALANCE: + return *this; + + case BALANCE_PAIR: + assert(as_balance_pair().cost); + if (as_balance_pair().cost) + return *(as_balance_pair().cost); + else + return as_balance_pair().quantity; + + case XML_NODE: + return as_xml_node()->to_value().cost(); + + default: + break; + } + + throw_(value_error, "Cannot find the cost of " << label()); + return value_t(); +} + +value_t& value_t::add(const amount_t& amount, const optional<amount_t>& tcost) +{ + switch (type()) { + case INTEGER: + case AMOUNT: + if (tcost) { + in_place_cast(BALANCE_PAIR); + return add(amount, tcost); + } + else if ((is_amount() && + as_amount().commodity() != amount.commodity()) || + (! is_amount() && amount.commodity())) { + in_place_cast(BALANCE); + return add(amount, tcost); + } + else if (! is_amount()) { + in_place_cast(AMOUNT); + } + *this += amount; + break; + + case BALANCE: + if (tcost) { + in_place_cast(BALANCE_PAIR); + return add(amount, tcost); + } + *this += amount; + break; + + case BALANCE_PAIR: + as_balance_pair_lval().add(amount, tcost); + break; + + default: + break; + } + + throw_(value_error, "Cannot add an amount to " << label()); + return *this; +} + +void value_t::print(std::ostream& out, const int first_width, + const int latter_width) const +{ + switch (type()) { + case VOID: + out << "NULL"; + break; + + case BOOLEAN: + case DATETIME: + case INTEGER: + case AMOUNT: + case STRING: + case POINTER: + // jww (2007-05-14): I need a version of this print just for XPath + // expression, since amounts and strings need to be output with + // special syntax. + out << *this; + break; + + case XML_NODE: + as_xml_node()->print(out); + break; + + case SEQUENCE: { + out << '('; + bool first = true; + foreach (const value_t& value, as_sequence()) { + if (first) + first = false; + else + out << ", "; + + value.print(out, first_width, latter_width); + } + out << ')'; + break; + } + + case BALANCE: + as_balance().print(out, first_width, latter_width); + break; + case BALANCE_PAIR: + as_balance_pair().print(out, first_width, latter_width); + break; + default: + assert(false); + break; + } +} + +std::ostream& operator<<(std::ostream& out, const value_t& val) +{ + switch (val.type()) { + case value_t::VOID: + out << "VOID"; + break; + case value_t::BOOLEAN: + out << (val.as_boolean() ? "true" : "false"); + break; + case value_t::INTEGER: + out << val.as_long(); + break; + case value_t::DATETIME: + out << val.as_datetime(); + break; + case value_t::AMOUNT: + out << val.as_amount(); + break; + case value_t::BALANCE: + out << val.as_balance(); + break; + case value_t::BALANCE_PAIR: + out << val.as_balance_pair(); + break; + case value_t::STRING: + out << val.as_string(); + break; + case value_t::XML_NODE: + if (val.as_xml_node()->has_flags(XML_NODE_IS_PARENT)) + out << '<' << val.as_xml_node()->name() << '>'; + else + out << val.as_xml_node()->to_value(); + break; + + case value_t::POINTER: + throw_(value_error, "Cannot output a pointer value"); + + case value_t::SEQUENCE: { + out << '('; + bool first = true; + for (value_t::sequence_t::const_iterator i = val.as_sequence().begin(); + i != val.as_sequence().end(); + i++) { + if (first) + first = false; + else + out << ", "; + out << *i; + } + out << ')'; + break; + } + + default: + assert(false); + break; + } + return out; +} + +#if 0 +value_context::value_context(const value_t& _bal, + const string& _desc) throw() + : error_context(_desc), bal(new value_t(_bal)) {} + +value_context::~value_context() throw() +{ + checked_delete(bal); +} + +void value_context::describe(std::ostream& out) const throw() +{ + if (! desc.empty()) + out << desc << std::endl; + + balance_t * ptr = NULL; + + out << std::right; + out.width(20); + + switch (bal->type()) { + case value_t::BOOLEAN: + out << (*((bool *) bal->data) ? "true" : "false"); + break; + case value_t::INTEGER: + out << *((long *) bal->data); + break; + case value_t::DATETIME: + out << *((moment_t *) bal->data); + break; + case value_t::AMOUNT: + out << *((amount_t *) bal->data); + break; + case value_t::BALANCE: + ptr = (balance_t *) bal->data; + // fall through... + + case value_t::BALANCE_PAIR: + if (! ptr) + ptr = &((balance_pair_t *) bal->data)->quantity; + + ptr->print(out, 20); + break; + default: + assert(false); + break; + } + out << std::endl; +} +#endif + +} // namespace ledger diff --git a/src/numerics/value.h b/src/numerics/value.h new file mode 100644 index 00000000..28024767 --- /dev/null +++ b/src/numerics/value.h @@ -0,0 +1,689 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _VALUE_H +#define _VALUE_H + +#include "balpair.h" // pulls in balance.h and amount.h + +namespace ledger { + +namespace xml { + class node_t; +} + +// The following type is a polymorphous value type used solely for +// performance reasons. The alternative is to compute value +// expressions (valexpr.cc) in terms of the largest data type, +// balance_t. This was found to be prohibitively expensive, especially +// when large logic chains were involved, since many temporary +// allocations would occur for every operator. With value_t, and the +// fact that logic chains only need boolean values to continue, no +// memory allocations need to take place at all. + +class value_t + : public ordered_field_operators<value_t, + ordered_field_operators<value_t, balance_pair_t, + ordered_field_operators<value_t, balance_t, + ordered_field_operators<value_t, amount_t, + ordered_field_operators<value_t, double, + ordered_field_operators<value_t, unsigned long, + ordered_field_operators<value_t, long> > > > > > > +{ +public: + typedef std::vector<value_t> sequence_t; + + typedef sequence_t::iterator iterator; + typedef sequence_t::const_iterator const_iterator; + typedef sequence_t::difference_type difference_type; + + enum type_t { + VOID, + BOOLEAN, + DATETIME, + INTEGER, + AMOUNT, + BALANCE, + BALANCE_PAIR, + STRING, + SEQUENCE, + XML_NODE, + POINTER + }; + +private: + class storage_t + { + char data[sizeof(amount_t)]; + type_t type; + + explicit storage_t() : type(VOID), refc(0) { + TRACE_CTOR(value_t::storage_t, ""); + } + explicit storage_t(const storage_t& rhs) + : type(rhs.type), refc(0) { + TRACE_CTOR(value_t::storage_t, ""); + std::memcpy(data, rhs.data, sizeof(data)); + } + + public: // so `checked_delete' can access it + ~storage_t() { + TRACE_DTOR(value_t::storage_t); + DEBUG("value.storage.refcount", "Destroying " << this); + assert(refc == 0); + destroy(); + } + + private: + storage_t& operator=(const storage_t& rhs) { + type = rhs.type; + std::memcpy(data, rhs.data, sizeof(data)); + return *this; + } + + mutable int refc; + + void acquire() const { + DEBUG("value.storage.refcount", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + } + void release() const { + DEBUG("value.storage.refcount", + "Releasing " << this << ", refc now " << refc - 1); + assert(refc > 0); + if (--refc == 0) + checked_delete(this); + } + + void destroy(); + + friend class value_t; + + friend inline void intrusive_ptr_add_ref(value_t::storage_t * storage) { + storage->acquire(); + } + friend inline void intrusive_ptr_release(value_t::storage_t * storage) { + storage->release(); + } + }; + + intrusive_ptr<storage_t> storage; + + static intrusive_ptr<storage_t> true_value; + static intrusive_ptr<storage_t> false_value; + + // jww (2007-05-03): Make this private, and then make + // ledger::initialize into a member function of session_t. +public: + static void initialize(); + static void shutdown(); + +public: + value_t() { + TRACE_CTOR(value_t, ""); + } + + value_t(const value_t& val) { + TRACE_CTOR(value_t, "copy"); + *this = val; + } + value_t(const bool val) { + TRACE_CTOR(value_t, "const bool"); + set_boolean(val); + } + value_t(const long val) { + TRACE_CTOR(value_t, "const long"); + set_long(val); + } + value_t(const moment_t val) { + TRACE_CTOR(value_t, "const moment_t"); + set_datetime(val); + } + value_t(const double val) { + TRACE_CTOR(value_t, "const double"); + set_amount(val); + } + value_t(const unsigned long val) { + TRACE_CTOR(value_t, "const unsigned long"); + set_amount(val); + } + explicit value_t(const string& val, bool literal = false) { + TRACE_CTOR(value_t, "const string&, bool"); + if (literal) + set_string(val); + else + set_amount(amount_t(val)); + } + explicit value_t(const char * val, bool literal = false) { + TRACE_CTOR(value_t, "const char *"); + if (literal) + set_string(val); + else + set_amount(amount_t(val)); + } + value_t(const amount_t& val) { + TRACE_CTOR(value_t, "const amount_t&"); + set_amount(val); + } + value_t(const balance_t& val) { + TRACE_CTOR(value_t, "const balance_t&"); + set_balance(val); + } + value_t(const balance_pair_t& val) { + TRACE_CTOR(value_t, "const balance_pair_t&"); + set_balance_pair(val); + } + value_t(const sequence_t& val) { + TRACE_CTOR(value_t, "const sequence_t&"); + set_sequence(val); + } + value_t(xml::node_t * item) { + TRACE_CTOR(value_t, "xml::node_t *"); + set_xml_node(item); + } + template <typename T> + explicit value_t(T * item) { + TRACE_CTOR(value_t, "T *"); + set_pointer(item); + } + ~value_t() { + TRACE_DTOR(value_t); + } + + value_t& operator=(const value_t& val) { + if (! (this == &val || storage == val.storage)) + storage = val.storage; + return *this; + } + + /** + * _dup() makes a private copy of the current value so that it can + * subsequently be modified. + * + * _clear() removes our pointer to the current value and initializes + * a new value for things to be stored in. + */ + void _dup() { + assert(storage); + if (storage->refc > 1) { + storage = new storage_t(*storage.get()); + + // If the data referenced by storage is an allocated pointer, we + // need to create a new object in order to achieve duplication. + switch (storage->type) { + case BALANCE: + *(balance_t **) storage->data = + new balance_t(**(balance_t **) storage->data); + break; + case BALANCE_PAIR: + *(balance_pair_t **) storage->data = + new balance_pair_t(**(balance_pair_t **) storage->data); + break; + case SEQUENCE: + *(sequence_t **) storage->data = + new sequence_t(**(sequence_t **) storage->data); + break; + default: + break; // everything else has been duplicated + } + } + } + void _clear() { + if (! storage || storage->refc > 1) + storage = new storage_t; + else + storage->destroy(); + } + void _reset() { + if (storage) + storage = intrusive_ptr<storage_t>(); + } + + operator bool() const; + + bool is_null() const { + if (! storage) { + return true; + } else { + assert(! is_type(VOID)); + return false; + } + } + type_t type() const { + type_t result = storage ? storage->type : VOID; + assert(result >= VOID && result <= POINTER); + return result; + } + +private: + bool is_type(type_t _type) const { + return type() == _type; + } + void set_type(type_t new_type) { + assert(new_type >= VOID && new_type <= POINTER); + if (new_type == VOID) { + _reset(); + assert(is_null()); + } else { + _clear(); + storage->type = new_type; + assert(is_type(new_type)); + } + } + +public: + bool is_boolean() const { + return is_type(BOOLEAN); + } + bool& as_boolean_lval() { + assert(is_boolean()); + _dup(); + return *(bool *) storage->data; + } + const bool& as_boolean() const { + assert(is_boolean()); + return *(bool *) storage->data; + } + void set_boolean(const bool val) { + set_type(BOOLEAN); + storage = val ? true_value : false_value; + } + + bool is_long() const { + return is_type(INTEGER); + } + long& as_long_lval() { + assert(is_long()); + _dup(); + return *(long *) storage->data; + } + const long& as_long() const { + assert(is_long()); + return *(long *) storage->data; + } + void set_long(const long val) { + set_type(INTEGER); + *(long *) storage->data = val; + } + + bool is_datetime() const { + return is_type(DATETIME); + } + moment_t& as_datetime_lval() { + assert(is_datetime()); + _dup(); + return *(moment_t *) storage->data; + } + const moment_t& as_datetime() const { + assert(is_datetime()); + return *(moment_t *) storage->data; + } + void set_datetime(const moment_t& val) { + set_type(DATETIME); + new((moment_t *) storage->data) moment_t(val); + } + + bool is_amount() const { + return is_type(AMOUNT); + } + amount_t& as_amount_lval() { + assert(is_amount()); + _dup(); + return *(amount_t *) storage->data; + } + const amount_t& as_amount() const { + assert(is_amount()); + return *(amount_t *) storage->data; + } + void set_amount(const amount_t& val) { + set_type(AMOUNT); + new((amount_t *) storage->data) amount_t(val); + } + + bool is_balance() const { + return is_type(BALANCE); + } + balance_t& as_balance_lval() { + assert(is_balance()); + _dup(); + return **(balance_t **) storage->data; + } + const balance_t& as_balance() const { + assert(is_balance()); + return **(balance_t **) storage->data; + } + void set_balance(const balance_t& val) { + set_type(BALANCE); + *(balance_t **) storage->data = new balance_t(val); + } + + bool is_balance_pair() const { + return is_type(BALANCE_PAIR); + } + balance_pair_t& as_balance_pair_lval() { + assert(is_balance_pair()); + _dup(); + return **(balance_pair_t **) storage->data; + } + const balance_pair_t& as_balance_pair() const { + assert(is_balance_pair()); + return **(balance_pair_t **) storage->data; + } + void set_balance_pair(const balance_pair_t& val) { + set_type(BALANCE_PAIR); + *(balance_pair_t **) storage->data = new balance_pair_t(val); + } + + bool is_string() const { + return is_type(STRING); + } + string& as_string_lval() { + assert(is_string()); + _dup(); + return *(string *) storage->data; + } + const string& as_string() const { + assert(is_string()); + return *(string *) storage->data; + } + void set_string(const string& val = "") { + set_type(STRING); + new((string *) storage->data) string(val); + } + + bool is_sequence() const { + return is_type(SEQUENCE); + } + sequence_t& as_sequence_lval() { + assert(is_sequence()); + _dup(); + return **(sequence_t **) storage->data; + } + const sequence_t& as_sequence() const { + assert(is_sequence()); + return **(sequence_t **) storage->data; + } + void set_sequence(const sequence_t& val) { + set_type(SEQUENCE); + *(sequence_t **) storage->data = new sequence_t(val); + } + + bool is_xml_node() const { + return is_type(XML_NODE); + } + xml::node_t *& as_xml_node_lval() { + assert(is_xml_node()); + _dup(); + return *(xml::node_t **) storage->data; + } + xml::node_t * as_xml_node() const { + assert(is_xml_node()); + return *(xml::node_t **) storage->data; + } + void set_xml_node(xml::node_t * val) { + set_type(XML_NODE); + *(xml::node_t **) storage->data = val; + } + + bool is_pointer() const { + return is_type(POINTER); + } + boost::any& as_any_pointer_lval() { + assert(is_pointer()); + _dup(); + return *(boost::any *) storage->data; + } + template <typename T> + T *& as_pointer_lval() { + assert(is_pointer()); + _dup(); + return any_cast<T *>(*(boost::any *) storage->data); + } + template <typename T> + T& as_ref_lval() { + assert(is_pointer()); + _dup(); + return *any_cast<T *>(*(boost::any *) storage->data); + } + boost::any as_any_pointer() const { + assert(is_pointer()); + return *(boost::any *) storage->data; + } + template <typename T> + T * as_pointer() const { + assert(is_pointer()); + return any_cast<T *>(*(boost::any *) storage->data); + } + template <typename T> + T& as_ref() const { + assert(is_pointer()); + return *any_cast<T *>(*(boost::any *) storage->data); + } + void set_any_pointer(const boost::any& val) { + set_type(POINTER); + new((boost::any *) storage->data) boost::any(val); + } + template <typename T> + void set_pointer(T * val) { + set_type(POINTER); + new((boost::any *) storage->data) boost::any(val); + } + + bool to_boolean() const; + long to_long() const; + moment_t to_datetime() const; + amount_t to_amount() const; + balance_t to_balance() const; + balance_pair_t to_balance_pair() const; + string to_string() const; + sequence_t to_sequence() const; + + value_t simplify() const { + value_t temp = *this; + temp.in_place_simplify(); + return temp; + } + void in_place_simplify(); + + value_t& operator[](const int index) { + assert(! is_null()); + if (is_sequence()) + return as_sequence_lval()[index]; + else if (index == 0) + return *this; + + assert(false); + static value_t null; + return null; + } + const value_t& operator[](const int index) const { + assert(! is_null()); + if (is_sequence()) + return as_sequence()[index]; + else if (index == 0) + return *this; + + assert(false); + static value_t null; + return null; + } + + void push_back(const value_t& val) { + if (! val.is_null()) { + if (is_null()) { + *this = val; + } else { + if (! is_sequence()) + in_place_cast(SEQUENCE); + + value_t::sequence_t& seq(as_sequence_lval()); + if (! val.is_sequence()) { + if (! val.is_null()) + seq.push_back(val); + } else { + const value_t::sequence_t& val_seq(val.as_sequence()); + std::copy(val_seq.begin(), val_seq.end(), back_inserter(seq)); + } + } + } + } + + void pop_back() { + assert(! is_null()); + + if (! is_sequence()) { + _reset(); + } else { + as_sequence_lval().pop_back(); + + std::size_t new_size = as_sequence().size(); + if (new_size == 0) + _reset(); + else if (new_size == 1) + *this = as_sequence().front(); + } + } + + const std::size_t size() const { + if (is_null()) + return 0; + else if (is_sequence()) + return as_sequence().size(); + else + return 1; + } + + value_t& operator+=(const value_t& val); + value_t& operator-=(const value_t& val); + value_t& operator*=(const value_t& val); + value_t& operator/=(const value_t& val); + + bool operator==(const value_t& val) const; + bool operator<(const value_t& val) const; +#if 0 + bool operator>(const value_t& val) const; +#endif + + string label(optional<type_t> the_type = none) const { + switch (the_type ? *the_type : type()) { + case VOID: + return "an uninitialized value"; + case BOOLEAN: + return "a boolean"; + case INTEGER: + return "an integer"; + case DATETIME: + return "a date/time"; + case AMOUNT: + return "an amount"; + case BALANCE: + return "a balance"; + case BALANCE_PAIR: + return "a balance pair"; + case STRING: + return "a string"; + case SEQUENCE: + return "a sequence"; + case XML_NODE: + return "an xml node"; + case POINTER: + return "a pointer"; + default: + assert(false); + break; + } + assert(false); + return "<invalid>"; + } + + value_t operator-() const { + return negate(); + } + value_t negate() const { + value_t temp = *this; + temp.in_place_negate(); + return temp; + } + void in_place_negate(); + + bool is_realzero() const; + value_t abs() const; + void in_place_cast(type_t cast_type); + value_t cost() const; + value_t annotated_price() const; + value_t annotated_date() const; + value_t annotated_tag() const; + + value_t cast(type_t cast_type) const { + value_t temp(*this); + temp.in_place_cast(cast_type); + return temp; + } + + 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; + + value_t& add(const amount_t& amount, + const optional<amount_t>& cost = none); + value_t value(const optional<moment_t>& moment = none) const; + + void in_place_reduce(); + value_t reduce() const { + value_t temp(*this); + temp.in_place_reduce(); + return temp; + } + + value_t round() const; + value_t unround() const; + + void print(std::ostream& out, const int first_width, + const int latter_width = -1) const; + + friend std::ostream& operator<<(std::ostream& out, const value_t& val); +}; + +#define NULL_VALUE (value_t()) + +inline value_t string_value(const string& str) { + return value_t(str, true); +} + +std::ostream& operator<<(std::ostream& out, const value_t& val); + +DECLARE_EXCEPTION(value_error); + +} // namespace ledger + +#endif // _VALUE_H |