/* * Copyright (c) 2003-2008, 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" namespace ledger { void commodity_t::add_price(const datetime_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 result = base->history->prices.insert(history_map::value_type(date, price)); assert(result.second); } } bool commodity_t::remove_price(const datetime_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 commodity_t::value(const optional& moment) { optional age; optional 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 quote = parent().get_quote (*this, age, moment, (base->history && base->history->prices.size() > 0 ? (*base->history->prices.rbegin()).first : optional()))) return *quote; } return price; } amount_t commodity_t::exchange(const amount_t& amount, amount_t& final_cost, // out amount_t& basis_cost, // out const optional& total_cost_, const optional& per_unit_cost_, const optional& moment, const optional& tag) { // (assert (or (and total-cost (not per-unit-cost)) // (and per-unit-cost (not total-cost)))) assert((total_cost_ && ! per_unit_cost_) || (per_unit_cost_ && ! total_cost_)); // (let* ((commodity (amount-commodity amount)) // (current-annotation // (and (annotated-commodity-p commodity) // (commodity-annotation commodity))) // (base-commodity (if (annotated-commodity-p commodity) // (get-referent commodity) // commodity)) // (per-unit-cost (or per-unit-cost // (divide total-cost amount))) // (total-cost (or total-cost // (multiply per-unit-cost amount)))) commodity_t& commodity(amount.commodity()); annotation_t * current_annotation = NULL; if (commodity.annotated) current_annotation = &as_annotated_commodity(commodity).details; commodity_t& base_commodity (current_annotation ? as_annotated_commodity(commodity).referent() : commodity); amount_t per_unit_cost(per_unit_cost_ ? *per_unit_cost_ : *total_cost_ / amount); final_cost = total_cost_ ? *total_cost_ : *per_unit_cost_ * amount; // Add a price history entry for this conversion if we know when it took // place // (if (and moment (not (commodity-no-market-price-p base-commodity))) // (add-price base-commodity per-unit-cost moment)) if (moment && ! commodity.has_flags(COMMODITY_STYLE_NOMARKET)) base_commodity.add_price(*moment, per_unit_cost); // ;; returns: ANNOTATED-AMOUNT TOTAL-COST BASIS-COST // (values (annotate-commodity // amount // (make-commodity-annotation :price per-unit-cost // :date moment // :tag tag)) // total-cost // (if current-annotation // (multiply (annotation-price current-annotation) amount) // total-cost)))) if (current_annotation && current_annotation->price) basis_cost = *current_annotation->price * amount; else basis_cost = final_cost; amount_t ann_amount(amount); ann_amount.annotate(annotation_t(per_unit_cost, moment->date(), tag)); return ann_amount; } commodity_t::operator bool() const { return this != parent().null_commodity; } bool commodity_t::symbol_needs_quotes(const string& symbol) { foreach (char ch, symbol) if (std::isspace(ch) || std::isdigit(ch) || ch == '-' || ch == '.') 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[static_cast(c)]); } symbol = buf; } void commodity_t::parse_symbol(char *& p, string& symbol) { if (*p == '"') { char * q = std::strchr(p + 1, '"'); if (! q) throw_(amount_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_(amount_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_date(buf); } else if (c == '(') { if (tag) throw_(amount_error, "Commodity specifies more than one tag"); in.get(c); READ_INTO(in, buf, 255, c, c != ')'); if (c == ')') in.get(c); else throw_(amount_error, "Commodity tag lacks closing parenthesis"); 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 != as_annotated_commodity(comm).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(leftcomm)); annotated_commodity_t& arightcomm(static_cast(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) { 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) { TRACE_CTOR(commodity_pool_t, ""); null_commodity = create(""); null_commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); } commodity_t * commodity_pool_t::create(const string& symbol) { shared_ptr base_commodity(new commodity_t::base_t(symbol)); std::auto_ptr 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 && as_annotated_commodity(*ann_comm).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 (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 && as_annotated_commodity(*ann_comm).details); return ann_comm; } return create(comm, details, name); } } // namespace ledger