/* * Copyright (c) 2003-2009, 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 #include "value.h" #include "commodity.h" #include "annotate.h" #include "pool.h" #include "unistring.h" namespace ledger { intrusive_ptr value_t::true_value; intrusive_ptr value_t::false_value; value_t::storage_t& value_t::storage_t::operator=(const value_t::storage_t& rhs) { type = rhs.type; switch (type) { case BALANCE: data = new balance_t(*boost::get(rhs.data)); break; case SEQUENCE: data = new sequence_t(*boost::get(rhs.data)); break; default: data = rhs.data; break; } return *this; } void value_t::initialize() { true_value = new storage_t; true_value->type = BOOLEAN; true_value->data = true; false_value = new storage_t; false_value->type = BOOLEAN; false_value->data = false; } void value_t::shutdown() { true_value = intrusive_ptr(); false_value = intrusive_ptr(); } value_t::operator bool() const { switch (type()) { case VOID: return false; case BOOLEAN: return as_boolean(); case DATETIME: return is_valid(as_datetime()); case DATE: return is_valid(as_date()); case INTEGER: return as_long(); case AMOUNT: return as_amount(); case BALANCE: return as_balance(); case STRING: return ! as_string().empty(); case MASK: { std::ostringstream out; out << *this; throw_(value_error, _("Cannot determine truth of %1 (did you mean 'account =~ %2'?)") << label() << out.str()); } case SEQUENCE: if (! as_sequence().empty()) { foreach (const value_t& value, as_sequence()) { if (value) return true; } } return false; case SCOPE: return as_scope() != NULL; default: break; } throw_(value_error, _("Cannot determine truth of %1") << label()); return false; } void value_t::set_type(type_t new_type) { if (new_type == VOID) { #if BOOST_VERSION >= 103700 storage.reset(); #else storage = intrusive_ptr(); #endif } else { if (! storage || storage->refc > 1) storage = new storage_t; else storage->destroy(); storage->type = new_type; } } 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(); } } datetime_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(); } } date_t value_t::to_date() const { if (is_date()) { return as_date(); } else { value_t temp(*this); temp.in_place_cast(DATE); return temp.as_date(); } } int value_t::to_int() const { if (is_long()) { return static_cast(as_long()); } else { value_t temp(*this); temp.in_place_cast(INTEGER); return static_cast(temp.as_long()); } } 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(); } } 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(); } } 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(); } } mask_t value_t::to_mask() const { if (is_mask()) { return as_mask(); } else { value_t temp(*this); temp.in_place_cast(MASK); return temp.as_mask(); } } 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() { #if defined(DEBUG_ON) LOGGER("value.simplify"); #endif if (is_realzero()) { DEBUG_("Zeroing type " << static_cast(type())); set_long(0L); return; } if (is_balance() && as_balance().single_amount()) { DEBUG_("Reducing balance to amount"); DEBUG_("as a balance it looks like: " << *this); in_place_cast(AMOUNT); DEBUG_("as an amount it looks like: " << *this); } #ifdef REDUCE_TO_INTEGER // this is off by default 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::number() const { switch (type()) { case VOID: return 0L; case BOOLEAN: return as_boolean() ? 1L : 0L; case INTEGER: return as_long(); case AMOUNT: return as_amount().number(); case BALANCE: return as_balance().number(); case SEQUENCE: if (! as_sequence().empty()) { value_t temp; foreach (const value_t& value, as_sequence()) temp += value.number(); return temp; } break; default: break; } throw_(value_error, _("Cannot determine numeric value of %1") << label()); return false; } 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()) { if (size() == val.size()) { sequence_t::iterator i = begin(); sequence_t::const_iterator j = val.begin(); for (; i != end(); i++, j++) *i += *j; } else { throw_(value_error, _("Cannot add sequences of different lengths")); } } else { as_sequence_lval().push_back(val); } return *this; } 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 DATE: switch (val.type()) { case INTEGER: as_date_lval() += date_duration_t(val.as_long()); return *this; case AMOUNT: as_date_lval() += date_duration_t(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: if (val.as_amount().has_commodity()) { in_place_cast(BALANCE); return *this += val; } 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; 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; 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; default: break; } break; default: break; } throw_(value_error, _("Cannot add %1 to %2") << val.label() << 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()) { if (size() == val.size()) { sequence_t::iterator i = begin(); sequence_t::const_iterator j = val.begin(); for (; i != end(); i++, j++) *i -= *j; } else { throw_(value_error, _("Cannot subtract sequences of different lengths")); } } else { sequence_t::iterator i = std::find(seq.begin(), seq.end(), val); if (i != seq.end()) seq.erase(i); } return *this; } 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 DATE: switch (val.type()) { case INTEGER: as_date_lval() -= date_duration_t(val.as_long()); return *this; case AMOUNT: as_date_lval() -= date_duration_t(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; 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; 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; default: break; } break; default: break; } throw_(value_error, _("Cannot subtract %1 from %2") << val.label() << 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; } 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: as_amount_lval() *= val.as_amount(); return *this; case BALANCE: if (val.as_balance().single_amount()) { as_amount_lval() *= val.simplified().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 (as_balance().single_amount()) { in_place_simplify(); as_amount_lval() *= val.as_amount(); return *this; } else if (! val.as_amount().has_commodity()) { as_balance_lval() *= val.as_amount(); return *this; } break; default: break; } break; default: break; } DEBUG("value.multiply.error", "Left: " << *this); DEBUG("value.multiply.error", "Right: " << val); throw_(value_error, _("Cannot multiply %1 with %2") << label() << val.label()); return *this; } value_t& value_t::operator/=(const value_t& val) { 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: as_amount_lval() /= val.as_amount(); return *this; case BALANCE: if (val.as_balance().single_amount()) { value_t simpler(val.simplified()); switch (simpler.type()) { case INTEGER: as_amount_lval() /= simpler.as_long(); break; case AMOUNT: as_amount_lval() /= simpler.as_amount(); break; default: assert(0); break; } return *this; } break; default: break; } break; case BALANCE: switch (val.type()) { case INTEGER: as_balance_lval() /= val.as_long(); return *this; case AMOUNT: if (as_balance().single_amount()) { in_place_simplify(); as_amount_lval() /= val.as_amount(); return *this; } else if (! val.as_amount().has_commodity()) { as_balance_lval() /= val.as_amount(); return *this; } break; default: break; } break; default: break; } throw_(value_error, _("Cannot divide %1 by %2") << label() << val.label()); return *this; } bool value_t::is_equal_to(const value_t& val) const { switch (type()) { case VOID: return val.type() == VOID; 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 DATE: if (val.is_date()) return as_date() == val.as_date(); 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(); 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(); 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(); default: break; } break; case STRING: if (val.is_string()) return as_string() == val.as_string(); break; case MASK: if (val.is_mask()) return as_mask() == val.as_mask(); break; case SEQUENCE: if (val.is_sequence()) return as_sequence() == val.as_sequence(); break; default: break; } throw_(value_error, _("Cannot compare %1 by %2") << label() << val.label()); return *this; } bool value_t::is_less_than(const value_t& val) const { switch (type()) { case DATETIME: if (val.is_datetime()) return as_datetime() < val.as_datetime(); break; case DATE: if (val.is_date()) return as_date() < val.as_date(); 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: try { return as_amount() < val.as_amount(); } catch (const amount_error&) { return compare_amount_commodities()(&as_amount(), &val.as_amount()); } default: break; } break; case BALANCE: switch (val.type()) { case INTEGER: case AMOUNT: { if (val.is_nonzero()) break; bool no_amounts = true; foreach (const balance_t::amounts_map::value_type& pair, as_balance().amounts) { if (pair.second >= 0L) return false; no_amounts = false; } return ! no_amounts; } default: break; } break; case STRING: if (val.is_string()) return as_string() < val.as_string(); break; case SEQUENCE: switch (val.type()) { case INTEGER: case AMOUNT: { if (val.is_nonzero()) break; bool no_amounts = true; foreach (const value_t& value, as_sequence()) { if (value >= 0L) return false; no_amounts = false; } return ! no_amounts; } default: break; } break; default: break; } throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label()); return *this; } bool value_t::is_greater_than(const value_t& val) const { switch (type()) { case DATETIME: if (val.is_datetime()) return as_datetime() > val.as_datetime(); break; case DATE: if (val.is_date()) return as_date() > val.as_date(); 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 BALANCE: switch (val.type()) { case INTEGER: case AMOUNT: { if (val.is_nonzero()) break; bool no_amounts = true; foreach (const balance_t::amounts_map::value_type& pair, as_balance().amounts) { if (pair.second <= 0L) return false; no_amounts = false; } return ! no_amounts; } default: break; } break; case STRING: if (val.is_string()) return as_string() > val.as_string(); break; case SEQUENCE: switch (val.type()) { case INTEGER: case AMOUNT: { if (val.is_nonzero()) break; bool no_amounts = true; foreach (const value_t& value, as_sequence()) { if (value <= 0L) return false; no_amounts = false; } return ! no_amounts; } default: break; } break; default: break; } throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label()); return *this; } void value_t::in_place_cast(type_t cast_type) { if (type() == cast_type) return; _dup(); 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; } switch (type()) { case BOOLEAN: switch (cast_type) { case AMOUNT: set_amount(as_boolean() ? 1L : 0L); return; case STRING: set_string(as_boolean() ? "true" : "false"); return; default: break; } break; case DATE: switch (cast_type) { case DATETIME: set_datetime(datetime_t(as_date(), time_duration(0, 0, 0, 0))); return; case STRING: set_string(format_date(as_date(), FMT_WRITTEN)); return; default: break; } break; case DATETIME: switch (cast_type) { case DATE: set_date(as_datetime().date()); return; case STRING: set_string(format_datetime(as_datetime(), FMT_WRITTEN)); return; default: break; } break; case INTEGER: switch (cast_type) { case AMOUNT: set_amount(as_long()); return; case BALANCE: set_balance(to_amount()); return; case STRING: set_string(lexical_cast(as_long())); return; default: break; } break; case AMOUNT: { const amount_t& amt(as_amount()); switch (cast_type) { case INTEGER: if (amt.is_null()) set_long(0L); else set_long(as_amount().to_long()); return; case BALANCE: if (amt.is_null()) set_balance(balance_t()); else set_balance(as_amount()); return; case STRING: if (amt.is_null()) set_string(""); else 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) { // Because we are changing the current balance value to an amount // value, and because set_amount takes a reference (and that memory is // about to be repurposed), we must pass in a copy. set_amount(amount_t((*temp.amounts.begin()).second)); return; } else if (temp.amounts.size() == 0) { set_amount(0L); return; } else { throw_(value_error, _("Cannot convert %1 with multiple commodities to %2") << label() << label(cast_type)); } break; } default: break; } break; case STRING: switch (cast_type) { case INTEGER: { if (all(as_string(), is_digit())) { set_long(lexical_cast(as_string())); return; } break; } case AMOUNT: set_amount(amount_t(as_string())); return; case MASK: set_mask(as_string()); return; default: break; } break; default: break; } throw_(value_error, _("Cannot convert %1 to %2") << label() << label(cast_type)); } void value_t::in_place_negate() { switch (type()) { case BOOLEAN: set_boolean(! as_boolean()); return; case INTEGER: case DATETIME: set_long(- as_long()); return; case DATE: set_long(- as_long()); return; case AMOUNT: as_amount_lval().in_place_negate(); return; case BALANCE: as_balance_lval().in_place_negate(); return; default: break; } throw_(value_error, _("Cannot negate %1") << label()); } void value_t::in_place_not() { switch (type()) { case BOOLEAN: set_boolean(! as_boolean()); return; case INTEGER: case DATETIME: set_boolean(! as_long()); return; case DATE: set_boolean(! as_long()); return; case AMOUNT: set_boolean(! as_amount()); return; case BALANCE: set_boolean(! as_balance()); return; case STRING: set_boolean(as_string().empty()); return; default: break; } throw_(value_error, _("Cannot 'not' %1") << 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(as_datetime()); case DATE: return ! is_valid(as_date()); case AMOUNT: return as_amount().is_realzero(); case BALANCE: return as_balance().is_realzero(); case STRING: return as_string().empty(); case SEQUENCE: return as_sequence().empty(); case SCOPE: return as_scope() == NULL; default: throw_(value_error, _("Cannot determine if %1 is really zero") << label()); } return false; } bool value_t::is_zero() const { switch (type()) { case BOOLEAN: return ! as_boolean(); case INTEGER: return as_long() == 0; case DATETIME: return ! is_valid(as_datetime()); case DATE: return ! is_valid(as_date()); case AMOUNT: return as_amount().is_zero(); case BALANCE: return as_balance().is_zero(); case STRING: return as_string().empty(); case SEQUENCE: return as_sequence().empty(); case SCOPE: return as_scope() == NULL; default: throw_(value_error, _("Cannot determine if %1 is zero") << label()); } return false; } value_t value_t::value(const bool primary_only, const optional& moment, const optional& in_terms_of) const { switch (type()) { case INTEGER: return NULL_VALUE; case AMOUNT: if (optional val = as_amount().value(primary_only, moment, in_terms_of)) return *val; return NULL_VALUE; case BALANCE: if (optional bal = as_balance().value(primary_only, moment, in_terms_of)) return *bal; return NULL_VALUE; default: break; } throw_(value_error, _("Cannot find the value of %1") << label()); return NULL_VALUE; } value_t value_t::price() const { switch (type()) { case AMOUNT: return as_amount().price(); case BALANCE: return as_balance().price(); default: return *this; } } value_t value_t::exchange_commodities(const std::string& commodities, const bool add_prices, const optional& moment) { scoped_array buf(new char[commodities.length() + 1]); std::strcpy(buf.get(), commodities.c_str()); for (char * p = std::strtok(buf.get(), ","); p; p = std::strtok(NULL, ",")) { if (commodity_t * commodity = amount_t::current_pool->parse_price_expression(p, add_prices, moment)) { value_t result = value(false, moment, *commodity); if (! result.is_null()) return result; } } return *this; } void value_t::in_place_reduce() { switch (type()) { case AMOUNT: as_amount_lval().in_place_reduce(); return; case BALANCE: as_balance_lval().in_place_reduce(); return; default: return; } //throw_(value_error, "Cannot reduce " << label()); } void value_t::in_place_unreduce() { switch (type()) { case AMOUNT: as_amount_lval().in_place_unreduce(); return; case BALANCE: as_balance_lval().in_place_unreduce(); return; default: return; } //throw_(value_error, "Cannot reduce " << label()); } value_t value_t::abs() const { switch (type()) { case INTEGER: { long val = as_long(); if (val < 0) return - val; return val; } case AMOUNT: return as_amount().abs(); case BALANCE: return as_balance().abs(); default: break; } throw_(value_error, _("Cannot abs %1") << label()); return NULL_VALUE; } void value_t::in_place_round() { switch (type()) { case INTEGER: return; case AMOUNT: as_amount_lval().in_place_round(); return; case BALANCE: as_balance_lval().in_place_round(); return; case SEQUENCE: { value_t temp; foreach (const value_t& value, as_sequence()) temp.push_back(value.rounded()); *this = temp; return; } default: break; } throw_(value_error, _("Cannot set rounding for %1") << label()); } void value_t::in_place_truncate() { switch (type()) { case INTEGER: return; case AMOUNT: as_amount_lval().in_place_truncate(); return; case BALANCE: as_balance_lval().in_place_truncate(); return; case SEQUENCE: { value_t temp; foreach (const value_t& value, as_sequence()) temp.push_back(value.truncated()); *this = temp; return; } default: break; } throw_(value_error, _("Cannot truncate %1") << label()); } void value_t::in_place_unround() { switch (type()) { case INTEGER: return; case AMOUNT: as_amount_lval().in_place_unround(); return; case BALANCE: as_balance_lval().in_place_unround(); return; case SEQUENCE: { value_t temp; foreach (const value_t& value, as_sequence()) temp.push_back(value.unrounded()); *this = temp; return; } default: break; } throw_(value_error, _("Cannot unround %1") << label()); } void value_t::annotate(const annotation_t& details) { if (is_amount()) as_amount_lval().annotate(details); else throw_(value_error, _("Cannot annotate %1") << label()); } bool value_t::is_annotated() const { if (is_amount()) return as_amount().is_annotated(); else throw_(value_error, _("Cannot determine whether %1 is annotated") << label()); return false; } annotation_t& value_t::annotation() { if (is_amount()) return as_amount_lval().annotation(); else { throw_(value_error, _("Cannot request annotation of %1") << label()); return as_amount_lval().annotation(); // quiet g++ warning } } value_t value_t::strip_annotations(const keep_details_t& what_to_keep) const { if (what_to_keep.keep_all()) return *this; switch (type()) { case VOID: case BOOLEAN: case INTEGER: case DATETIME: case DATE: case STRING: case MASK: case SCOPE: return *this; case SEQUENCE: { sequence_t temp; foreach (const value_t& value, as_sequence()) temp.push_back(value.strip_annotations(what_to_keep)); return temp; } case AMOUNT: return as_amount().strip_annotations(what_to_keep); case BALANCE: return as_balance().strip_annotations(what_to_keep); default: assert(false); break; } assert(false); return NULL_VALUE; } void value_t::print(std::ostream& out, const int first_width, const int latter_width, const bool right_justify, const bool colorize) const { if (first_width > 0 && (! is_amount() || as_amount().is_zero()) && ! is_balance() && ! is_string()) { out.width(first_width); if (right_justify) out << std::right; else out << std::left; } switch (type()) { case VOID: out << ""; break; case BOOLEAN: out << (as_boolean() ? "1" : "0"); break; case DATETIME: out << format_datetime(as_datetime(), FMT_WRITTEN); break; case DATE: out << format_date(as_date(), FMT_WRITTEN); break; case INTEGER: if (colorize && as_long() < 0) justify(out, to_string(), first_width, right_justify, true); else out << as_long(); break; case AMOUNT: { if (as_amount().is_zero()) { out << 0; } else { std::ostringstream buf; buf << as_amount(); justify(out, buf.str(), first_width, right_justify, colorize && as_amount().sign() < 0); } break; } case BALANCE: as_balance().print(out, first_width, latter_width, right_justify, colorize); break; case STRING: justify(out, as_string(), first_width, right_justify); break; case MASK: out << '/' << as_mask() << '/'; 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, right_justify, colorize); } out << ')'; break; } case SCOPE: out << ""; break; default: throw_(value_error, _("Cannot print %1") << label()); } } void value_t::dump(std::ostream& out, const bool relaxed) const { switch (type()) { case VOID: out << "null"; break; case BOOLEAN: if (as_boolean()) out << "true"; else out << "false"; break; case DATETIME: out << '[' << format_datetime(as_datetime(), FMT_WRITTEN) << ']'; break; case DATE: out << '[' << format_date(as_date(), FMT_WRITTEN) << ']'; break; case INTEGER: out << as_long(); break; case AMOUNT: if (! relaxed) out << '{'; out << as_amount(); if (! relaxed) out << '}'; break; case BALANCE: out << as_balance(); break; case STRING: out << '"'; foreach (const char& ch, as_string()) { switch (ch) { case '"': out << "\\\""; break; case '\\': out << "\\\\"; break; default: out << ch; break; } } out << '"'; break; case MASK: out << '/' << as_mask() << '/'; break; case SCOPE: out << as_scope(); break; case SEQUENCE: { out << '('; bool first = true; foreach (const value_t& value, as_sequence()) { if (first) first = false; else out << ", "; value.dump(out, relaxed); } out << ')'; break; } default: assert(false); break; } } bool value_t::valid() const { switch (type()) { case AMOUNT: return as_amount().valid(); case BALANCE: return as_balance().valid(); default: break; } return true; } bool sort_value_is_less_than(const std::list& left_values, const std::list& right_values) { std::list::const_iterator left_iter = left_values.begin(); std::list::const_iterator right_iter = right_values.begin(); while (left_iter != left_values.end() && right_iter != right_values.end()) { // Don't even try to sort balance values if (! (*left_iter).value.is_balance() && ! (*right_iter).value.is_balance()) { DEBUG("value.sort", " Comparing " << (*left_iter).value << " < " << (*right_iter).value); if ((*left_iter).value < (*right_iter).value) { DEBUG("value.sort", " is less"); return ! (*left_iter).inverted; } else if ((*left_iter).value > (*right_iter).value) { DEBUG("value.sort", " is greater"); return (*left_iter).inverted; } } left_iter++; right_iter++; } assert(left_iter == left_values.end()); assert(right_iter == right_values.end()); return false; } void to_xml(std::ostream& out, const value_t& value) { switch (value.type()) { case value_t::VOID: out << ""; break; case value_t::BOOLEAN: { push_xml y(out, "boolean"); out << (value.as_boolean() ? "true" : "false"); break; } case value_t::INTEGER: { push_xml y(out, "integer"); out << value.as_long(); break; } case value_t::AMOUNT: to_xml(out, value.as_amount()); break; case value_t::BALANCE: to_xml(out, value.as_balance()); break; case value_t::DATETIME: to_xml(out, value.as_datetime()); break; case value_t::DATE: to_xml(out, value.as_date()); break; case value_t::STRING: { push_xml y(out, "string"); out << y.guard(value.as_string()); break; } case value_t::MASK: to_xml(out, value.as_mask()); break; case value_t::SEQUENCE: { push_xml y(out, "sequence"); foreach (const value_t& member, value.as_sequence()) to_xml(out, member); break; } case value_t::SCOPE: default: assert(false); break; } } } // namespace ledger