From c8899addfd2deed3d84be2de234681db64987722 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 30 Apr 2007 06:26:38 +0000 Subject: Rearranged the sources a bit. --- src/.gitignore | 1 + src/COPYRIGHT | 30 + src/amount.cc | 2041 +++++++++++++++++++++++++++++++++++++++++++ src/amount.h | 760 ++++++++++++++++ src/balance.cc | 313 +++++++ src/balance.h | 969 +++++++++++++++++++++ src/binary.cc | 1013 +++++++++++++++++++++ src/binary.h | 252 ++++++ src/context.h | 28 + src/csv.cc | 0 src/csv.h | 0 src/derive.cc | 178 ++++ src/derive.h | 18 + src/emacs.cc | 0 src/emacs.h | 0 src/error.h | 151 ++++ src/fdstream.hpp | 184 ++++ src/format.cc | 235 +++++ src/format.h | 108 +++ src/gd_qnan.h | 12 + src/gnucash.cc | 366 ++++++++ src/gnucash.h | 75 ++ src/journal.cc | 667 ++++++++++++++ src/journal.h | 471 ++++++++++ src/ledger.h | 42 + src/main.cc | 484 +++++++++++ src/mask.cc | 24 + src/mask.h | 26 + src/ofx.cc | 218 +++++ src/ofx.h | 21 + src/option.cc | 219 +++++ src/option.h | 22 + src/parser.h | 86 ++ src/py_amount.cc | 245 ++++++ src/py_balance.cc | 202 +++++ src/py_format.cc | 11 + src/py_journal.cc | 374 ++++++++ src/py_option.cc | 73 ++ src/py_parser.cc | 48 + src/py_report.cc | 13 + src/py_session.cc | 36 + src/py_transform.cc | 8 + src/py_value.cc | 337 +++++++ src/py_xpath.cc | 79 ++ src/pyfstream.h | 138 +++ src/pyinterp.cc | 164 ++++ src/pyinterp.h | 83 ++ src/pyledger.cc | 53 ++ src/pyledger.h | 16 + src/qif.cc | 243 ++++++ src/qif.h | 21 + src/quotes.cc | 80 ++ src/quotes.h | 30 + src/reconcile.cc | 0 src/reconcile.h | 0 src/register.cc | 166 ++++ src/register.h | 38 + src/report.cc | 189 ++++ src/report.h | 141 +++ src/session.cc | 220 +++++ src/session.h | 197 +++++ src/system.hh | 99 +++ src/textual.cc | 966 +++++++++++++++++++++ src/textual.h | 44 + src/times.cc | 53 ++ src/times.h | 102 +++ src/transform.cc | 326 +++++++ src/transform.h | 136 +++ src/utils.cc | 687 +++++++++++++++ src/utils.h | 422 +++++++++ src/value.cc | 2311 ++++++++++++++++++++++++++++++++++++++++++++++++ src/value.h | 580 +++++++++++++ src/xml.cc | 550 ++++++++++++ src/xml.h | 423 +++++++++ src/xmlparse.cc | 467 ++++++++++ src/xpath.cc | 2414 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/xpath.h | 783 +++++++++++++++++ 77 files changed, 22582 insertions(+) create mode 100644 src/.gitignore create mode 100644 src/COPYRIGHT create mode 100644 src/amount.cc create mode 100644 src/amount.h create mode 100644 src/balance.cc create mode 100644 src/balance.h create mode 100644 src/binary.cc create mode 100644 src/binary.h create mode 100644 src/context.h create mode 100644 src/csv.cc create mode 100644 src/csv.h create mode 100644 src/derive.cc create mode 100644 src/derive.h create mode 100644 src/emacs.cc create mode 100644 src/emacs.h create mode 100644 src/error.h create mode 100644 src/fdstream.hpp create mode 100644 src/format.cc create mode 100644 src/format.h create mode 100644 src/gd_qnan.h create mode 100644 src/gnucash.cc create mode 100644 src/gnucash.h create mode 100644 src/journal.cc create mode 100644 src/journal.h create mode 100644 src/ledger.h create mode 100644 src/main.cc create mode 100644 src/mask.cc create mode 100644 src/mask.h create mode 100644 src/ofx.cc create mode 100644 src/ofx.h create mode 100644 src/option.cc create mode 100644 src/option.h create mode 100644 src/parser.h create mode 100644 src/py_amount.cc create mode 100644 src/py_balance.cc create mode 100644 src/py_format.cc create mode 100644 src/py_journal.cc create mode 100644 src/py_option.cc create mode 100644 src/py_parser.cc create mode 100644 src/py_report.cc create mode 100644 src/py_session.cc create mode 100644 src/py_transform.cc create mode 100644 src/py_value.cc create mode 100644 src/py_xpath.cc create mode 100644 src/pyfstream.h create mode 100644 src/pyinterp.cc create mode 100644 src/pyinterp.h create mode 100644 src/pyledger.cc create mode 100644 src/pyledger.h create mode 100644 src/qif.cc create mode 100644 src/qif.h create mode 100644 src/quotes.cc create mode 100644 src/quotes.h create mode 100644 src/reconcile.cc create mode 100644 src/reconcile.h create mode 100644 src/register.cc create mode 100644 src/register.h create mode 100644 src/report.cc create mode 100644 src/report.h create mode 100644 src/session.cc create mode 100644 src/session.h create mode 100644 src/system.hh create mode 100644 src/textual.cc create mode 100644 src/textual.h create mode 100644 src/times.cc create mode 100644 src/times.h create mode 100644 src/transform.cc create mode 100644 src/transform.h create mode 100644 src/utils.cc create mode 100644 src/utils.h create mode 100644 src/value.cc create mode 100644 src/value.h create mode 100644 src/xml.cc create mode 100644 src/xml.h create mode 100644 src/xmlparse.cc create mode 100644 src/xpath.cc create mode 100644 src/xpath.h (limited to 'src') diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..0d20b648 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/src/COPYRIGHT b/src/COPYRIGHT new file mode 100644 index 00000000..c9d4bd18 --- /dev/null +++ b/src/COPYRIGHT @@ -0,0 +1,30 @@ +/* + * 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. + */ diff --git a/src/amount.cc b/src/amount.cc new file mode 100644 index 00000000..ac7bbdb7 --- /dev/null +++ b/src/amount.cc @@ -0,0 +1,2041 @@ +/** + * @file amount.cc + * @author John Wiegley + * @date Thu Apr 26 15:19:46 2007 + * + * @brief Types for handling commoditized math. + * + * This file defines member functions for amount_t and the various + * flavors of commodity_t. + */ + +/* + * 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 "amount.h" +#include "binary.h" + +namespace ledger { + +bool do_cleanup = true; + +bool amount_t::keep_price = false; +bool amount_t::keep_date = false; +bool amount_t::keep_tag = false; +bool amount_t::keep_base = false; +bool amount_t::full_strings = false; + +#define BIGINT_BULK_ALLOC 0x0001 +#define BIGINT_KEEP_PREC 0x0002 + +class amount_t::bigint_t +{ + public: + mpz_t val; + unsigned char prec; + unsigned char flags; + unsigned int ref; + unsigned int index; + + bigint_t() : prec(0), flags(0), ref(1), index(0) { + TRACE_CTOR(bigint_t, ""); + mpz_init(val); + } + bigint_t(mpz_t _val) : prec(0), flags(0), ref(1), index(0) { + TRACE_CTOR(bigint_t, "mpz_t"); + mpz_init_set(val, _val); + } + bigint_t(const bigint_t& other) + : prec(other.prec), flags(other.flags & BIGINT_KEEP_PREC), + ref(1), index(0) { + TRACE_CTOR(bigint_t, "copy"); + mpz_init_set(val, other.val); + } + ~bigint_t(); +}; + +unsigned int sizeof_bigint_t() { + return sizeof(amount_t::bigint_t); +} + +#define MPZ(x) ((x)->val) + +#ifndef THREADSAFE +static mpz_t temp; // these are the global temp variables +static mpz_t divisor; +#endif + +static amount_t::bigint_t * true_value = NULL; + +inline amount_t::bigint_t::~bigint_t() { + TRACE_DTOR(bigint_t); + assert(ref == 0 || (! do_cleanup && this == true_value)); + mpz_clear(val); +} + +#ifndef THREADSAFE +base_commodities_map commodity_base_t::commodities; + +commodity_base_t::updater_t * commodity_base_t::updater = NULL; + +commodities_map commodity_t::commodities; +commodities_array * commodity_t::commodities_by_ident; +bool commodity_t::commodities_sorted = false; +commodity_t * commodity_t::null_commodity; +commodity_t * commodity_t::default_commodity = NULL; +#endif + +void amount_t::initialize() +{ + mpz_init(temp); + mpz_init(divisor); + + true_value = new amount_t::bigint_t; + mpz_set_ui(true_value->val, 1); + + commodity_base_t::updater = NULL; + + commodity_t::commodities_by_ident = new commodities_array; + + commodity_t::default_commodity = NULL; + commodity_t::null_commodity = commodity_t::create(""); + commodity_t::null_commodity->add_flags(COMMODITY_STYLE_NOMARKET | + COMMODITY_STYLE_BUILTIN); + + // Add time commodity conversions, so that timelog's may be parsed + // in terms of seconds, but reported as minutes or hours. + commodity_t * commodity = commodity_t::create("s"); + commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); + + parse_conversion("1.0m", "60s"); + parse_conversion("1.0h", "60m"); +} + +void amount_t::shutdown() +{ + mpz_clear(temp); + mpz_clear(divisor); + + if (commodity_base_t::updater) { + delete commodity_base_t::updater; + commodity_base_t::updater = NULL; + } + + for (base_commodities_map::iterator i = commodity_base_t::commodities.begin(); + i != commodity_base_t::commodities.end(); + i++) + delete (*i).second; + + for (commodities_map::iterator i = commodity_t::commodities.begin(); + i != commodity_t::commodities.end(); + i++) + delete (*i).second; + + commodity_base_t::commodities.clear(); + commodity_t::commodities.clear(); + + delete commodity_t::commodities_by_ident; + commodity_t::commodities_by_ident = NULL; + + commodity_t::null_commodity = NULL; + commodity_t::default_commodity = NULL; + + true_value->ref--; + assert(true_value->ref == 0); + delete true_value; + true_value = NULL; +} + +static void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec) +{ + // Round `value', with an encoding precision of `value_prec', to a + // rounded value with precision `round_prec'. Result is stored in + // `out'. + + assert(value_prec > round_prec); + + mpz_t quotient; + mpz_t remainder; + + mpz_init(quotient); + mpz_init(remainder); + + mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); + mpz_tdiv_qr(quotient, remainder, value, divisor); + mpz_divexact_ui(divisor, divisor, 10); + mpz_mul_ui(divisor, divisor, 5); + + if (mpz_sgn(remainder) < 0) { + mpz_neg(divisor, divisor); + if (mpz_cmp(remainder, divisor) < 0) { + mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); + mpz_add(remainder, divisor, remainder); + mpz_ui_sub(remainder, 0, remainder); + mpz_add(out, value, remainder); + } else { + mpz_sub(out, value, remainder); + } + } else { + if (mpz_cmp(remainder, divisor) >= 0) { + mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); + mpz_sub(remainder, divisor, remainder); + mpz_add(out, value, remainder); + } else { + mpz_sub(out, value, remainder); + } + } + mpz_clear(quotient); + mpz_clear(remainder); + + // chop off the rounded bits + mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); + mpz_tdiv_q(out, out, divisor); +} + +amount_t::amount_t(const long val) +{ + TRACE_CTOR(amount_t, "const long"); + if (val != 0) { + quantity = new bigint_t; + mpz_set_si(MPZ(quantity), val); + } else { + quantity = NULL; + } + commodity_ = NULL; +} + +amount_t::amount_t(const unsigned long val) +{ + TRACE_CTOR(amount_t, "const unsigned long"); + if (val != 0) { + quantity = new bigint_t; + mpz_set_ui(MPZ(quantity), val); + } else { + quantity = NULL; + } + commodity_ = NULL; +} + +namespace { + unsigned char convert_double(mpz_t dest, double val) + { +#ifndef HAVE_GDTOA + // This code is far too imprecise to be worthwhile. + + mpf_t temp; + mpf_init_set_d(temp, val); + + mp_exp_t exp; + char * buf = mpf_get_str(NULL, &exp, 10, 1000, temp); + + int len = std::strlen(buf); + if (len > 0 && buf[0] == '-') + exp++; + + if (exp <= len) { + exp = len - exp; + } else { + // There were trailing zeros, which we have to put back on in + // order to convert this buffer into an integer. + + int zeroes = exp - len; + + char * newbuf = (char *)std::malloc(len + zeroes); + std::strcpy(newbuf, buf); + + int i; + for (i = 0; i < zeroes; i++) + newbuf[len + i] = '0'; + newbuf[len + i] = '\0'; + + free(buf); + buf = newbuf; + + exp = (len - exp) + zeroes; + } + + mpz_set_str(dest, buf, 10); + free(buf); + + return (unsigned char)exp; +#else + int decpt, sign; + char * buf = dtoa(val, 0, 0, &decpt, &sign, NULL); + char * result; + int len = std::strlen(buf); + + if (decpt <= len) { + decpt = len - decpt; + result = NULL; + } else { + // There were trailing zeros, which we have to put back on in + // order to convert this buffer into an integer. + + int zeroes = decpt - len; + result = new char[len + zeroes]; + + std::strcpy(result, buf); + int i; + for (i = 0; i < zeroes; i++) + result[len + i] = '0'; + result[len + i] = '\0'; + + decpt = (len - decpt) + zeroes; + } + + if (sign) { + char * newbuf = new char[std::strlen(result ? result : buf) + 1]; + newbuf[0] = '-'; + std::strcpy(&newbuf[1], result ? result : buf); + mpz_set_str(dest, newbuf, 10); + delete[] newbuf; + } else { + mpz_set_str(dest, result ? result : buf, 10); + } + + if (result) + delete[] result; + freedtoa(buf); + + return decpt; +#endif + } +} + +amount_t::amount_t(const double val) +{ + TRACE_CTOR(amount_t, "const double"); + quantity = new bigint_t; + quantity->prec = convert_double(MPZ(quantity), val); + commodity_ = NULL; +} + +void amount_t::_release() +{ + DEBUG_("amounts.refs", + quantity << " ref--, now " << (quantity->ref - 1)); + if (--quantity->ref == 0) { + if (! (quantity->flags & BIGINT_BULK_ALLOC)) + delete quantity; + else + quantity->~bigint_t(); + } +} + +void amount_t::_init() +{ + if (! quantity) { + quantity = new bigint_t; + } + else if (quantity->ref > 1) { + _release(); + quantity = new bigint_t; + } +} + +void amount_t::_dup() +{ + if (quantity->ref > 1) { + bigint_t * q = new bigint_t(*quantity); + _release(); + quantity = q; + } +} + +void amount_t::_copy(const amount_t& amt) +{ + if (quantity != amt.quantity) { + if (quantity) + _release(); + + // Never maintain a pointer into a bulk allocation pool; such + // pointers are not guaranteed to remain. + if (amt.quantity->flags & BIGINT_BULK_ALLOC) { + quantity = new bigint_t(*amt.quantity); + } else { + quantity = amt.quantity; + DEBUG_("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; + } + } + commodity_ = amt.commodity_; +} + +amount_t& amount_t::operator=(const string& val) +{ + std::istringstream str(val); + parse(str); + return *this; +} + +amount_t& amount_t::operator=(const char * val) +{ + string valstr(val); + std::istringstream str(valstr); + parse(str); + return *this; +} + +// assignment operator +amount_t& amount_t::operator=(const amount_t& amt) +{ + if (this != &amt) { + if (amt.quantity) + _copy(amt); + else if (quantity) + _clear(); + } + return *this; +} + +amount_t& amount_t::operator=(const long val) +{ + if (val == 0) { + if (quantity) + _clear(); + } else { + commodity_ = NULL; + _init(); + mpz_set_si(MPZ(quantity), val); + } + return *this; +} + +amount_t& amount_t::operator=(const unsigned long val) +{ + if (val == 0) { + if (quantity) + _clear(); + } else { + commodity_ = NULL; + _init(); + mpz_set_ui(MPZ(quantity), val); + } + return *this; +} + +amount_t& amount_t::operator=(const double val) +{ + commodity_ = NULL; + _init(); + quantity->prec = convert_double(MPZ(quantity), val); + return *this; +} + + +void amount_t::_resize(unsigned int prec) +{ + assert(prec < 256); + + if (! quantity || prec == quantity->prec) + return; + + _dup(); + + if (prec < quantity->prec) { + mpz_ui_pow_ui(divisor, 10, quantity->prec - prec); + mpz_tdiv_q(MPZ(quantity), MPZ(quantity), divisor); + } else { + mpz_ui_pow_ui(divisor, 10, prec - quantity->prec); + mpz_mul(MPZ(quantity), MPZ(quantity), divisor); + } + + quantity->prec = prec; +} + + +void amount_t::_clear() +{ + if (quantity) { + _release(); + quantity = NULL; + commodity_ = NULL; + } else { + assert(! commodity_); + } +} + + +amount_t& amount_t::operator+=(const amount_t& amt) +{ + if (commodity() != amt.commodity()) { + throw amount_exception + (string("Adding amounts with different commodities: ") + + (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + + (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), + context()); + } + + if (! amt.quantity) + return *this; + + if (! quantity) { + _copy(amt); + return *this; + } + + _dup(); + + if (quantity->prec == amt.quantity->prec) { + mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + } + else if (quantity->prec < amt.quantity->prec) { + _resize(amt.quantity->prec); + mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + } + else { + amount_t t = amt; + t._resize(quantity->prec); + mpz_add(MPZ(quantity), MPZ(quantity), MPZ(t.quantity)); + } + + return *this; +} + +amount_t& amount_t::operator-=(const amount_t& amt) +{ + if (commodity() != amt.commodity()) + throw amount_exception + (string("Subtracting amounts with different commodities: ") + + (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + + (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), + context()); + + if (! amt.quantity) + return *this; + + if (! quantity) { + quantity = new bigint_t(*amt.quantity); + commodity_ = amt.commodity_; + mpz_neg(MPZ(quantity), MPZ(quantity)); + return *this; + } + + _dup(); + + if (quantity->prec == amt.quantity->prec) { + mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + } + else if (quantity->prec < amt.quantity->prec) { + _resize(amt.quantity->prec); + mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + } + else { + amount_t t = amt; + t._resize(quantity->prec); + mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(t.quantity)); + } + + return *this; +} + +amount_t& amount_t::operator*=(const amount_t& amt) +{ + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) { + throw amount_exception + (string("Multiplying amounts with different commodities: ") + + (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + + (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), + context()); + } + + if (! amt.quantity) { + *this = *this - *this; // preserve our commodity + goto finish; + } + else if (! quantity) { + *this = amt; + *this = *this - *this; // preserve the foreign commodity + goto finish; + } + + _dup(); + + mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + quantity->prec += amt.quantity->prec; + + finish: + if (! has_commodity()) + commodity_ = amt.commodity_; + + if (has_commodity() && ! (quantity->flags & BIGINT_KEEP_PREC)) { + unsigned int comm_prec = commodity().precision(); + if (quantity->prec > comm_prec + 6U) { + mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); + quantity->prec = comm_prec + 6U; + } + } + + return *this; +} + +amount_t& amount_t::operator/=(const amount_t& amt) +{ + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) { + throw amount_exception + (string("Dividing amounts with different commodities: ") + + (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + + (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), + context()); + } + + if (! amt.quantity || ! amt) { + throw amount_exception("Divide by zero", context()); + } + else if (! quantity) { + *this = amt; + *this = *this - *this; // preserve the foreign commodity + goto finish; + } + + _dup(); + + // Increase the value's precision, to capture fractional parts after + // the divide. Round up in the last position. + + mpz_ui_pow_ui(divisor, 10, (2 * amt.quantity->prec) + quantity->prec + 7U); + mpz_mul(MPZ(quantity), MPZ(quantity), divisor); + mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + quantity->prec += amt.quantity->prec + quantity->prec + 7U; + + mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, quantity->prec - 1); + quantity->prec -= 1; + + finish: + if (! has_commodity()) + commodity_ = amt.commodity_; + + // If this amount has a commodity, and we're not dealing with plain + // numbers, or internal numbers (which keep full precision at all + // times), then round the number to within the commodity's precision + // plus six places. + + if (has_commodity() && ! (quantity->flags & BIGINT_KEEP_PREC)) { + unsigned int comm_prec = commodity().precision(); + if (quantity->prec > comm_prec + 6U) { + mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); + quantity->prec = comm_prec + 6U; + } + } + + return *this; +} + +// unary negation +void amount_t::in_place_negate() +{ + if (quantity) { + _dup(); + mpz_neg(MPZ(quantity), MPZ(quantity)); + } +} + +int amount_t::sign() const +{ + return quantity ? mpz_sgn(MPZ(quantity)) : 0; +} + +int amount_t::compare(const amount_t& amt) const +{ + if (! quantity) { + if (! amt.quantity) + return 0; + return - amt.sign(); + } + if (! amt.quantity) + return sign(); + + if (has_commodity() && amt.commodity() && commodity() != amt.commodity()) + throw amount_exception + (string("Cannot compare amounts with different commodities: ") + + commodity().symbol() + " and " + amt.commodity().symbol(), + context()); + + if (quantity->prec == amt.quantity->prec) { + return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)); + } + else if (quantity->prec < amt.quantity->prec) { + amount_t t = *this; + t._resize(amt.quantity->prec); + return mpz_cmp(MPZ(t.quantity), MPZ(amt.quantity)); + } + else { + amount_t t = amt; + t._resize(quantity->prec); + return mpz_cmp(MPZ(quantity), MPZ(t.quantity)); + } +} + +bool amount_t::operator==(const amount_t& amt) const +{ + if (commodity() != amt.commodity()) + return false; + return compare(amt) == 0; +} + +bool amount_t::operator!=(const amount_t& amt) const +{ + if (commodity() != amt.commodity()) + return true; + return compare(amt) != 0; +} + +bool amount_t::zero() const +{ + if (! quantity) + return true; + + if (has_commodity()) { + if (quantity->prec <= commodity().precision()) + return realzero(); + else + return round(commodity().precision()).sign() == 0; + } + return realzero(); +} + +amount_t::operator long() const +{ + if (! quantity) + return 0; + + mpz_set(temp, MPZ(quantity)); + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_q(temp, temp, divisor); + return mpz_get_si(temp); +} + +amount_t::operator double() const +{ + if (! quantity) + return 0.0; + + mpz_t remainder; + mpz_init(remainder); + + mpz_set(temp, MPZ(quantity)); + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_qr(temp, remainder, temp, divisor); + + char * quotient_s = mpz_get_str(NULL, 10, temp); + char * remainder_s = mpz_get_str(NULL, 10, remainder); + + std::ostringstream num; + num << quotient_s << '.' << remainder_s; + + std::free(quotient_s); + std::free(remainder_s); + + mpz_clear(remainder); + + return std::atof(num.str().c_str()); +} + +amount_t amount_t::value(const moment_t& moment) const +{ + if (quantity) { + amount_t amt(commodity().value(moment)); + if (! amt.realzero()) + return (amt * number()).round(); + } + return *this; +} + +amount_t amount_t::round(unsigned int prec) const +{ + amount_t t = *this; + + if (! quantity || quantity->prec <= prec) { + if (quantity && quantity->flags & BIGINT_KEEP_PREC) { + t._dup(); + t.quantity->flags &= ~BIGINT_KEEP_PREC; + } + return t; + } + + t._dup(); + + mpz_round(MPZ(t.quantity), MPZ(t.quantity), t.quantity->prec, prec); + + t.quantity->prec = prec; + t.quantity->flags &= ~BIGINT_KEEP_PREC; + + return t; +} + +amount_t amount_t::unround() const +{ + if (! quantity) { + amount_t t(0L); + assert(t.quantity); + t.quantity->flags |= BIGINT_KEEP_PREC; + return t; + } + else if (quantity->flags & BIGINT_KEEP_PREC) { + return *this; + } + + amount_t t = *this; + t._dup(); + t.quantity->flags |= BIGINT_KEEP_PREC; + + return t; +} + +void amount_t::print_quantity(std::ostream& out) const +{ + if (! quantity) { + out << "0"; + return; + } + + mpz_t quotient; + mpz_t rquotient; + mpz_t remainder; + + mpz_init(quotient); + mpz_init(rquotient); + mpz_init(remainder); + + bool negative = false; + + // Ensure the value is rounded to the commodity's precision before + // outputting it. NOTE: `rquotient' is used here as a temp variable! + + commodity_t& comm(commodity()); + unsigned char precision; + + if (! comm || quantity->flags & BIGINT_KEEP_PREC) { + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); + precision = quantity->prec; + } + else if (comm.precision() < quantity->prec) { + mpz_round(rquotient, MPZ(quantity), quantity->prec, comm.precision()); + mpz_ui_pow_ui(divisor, 10, comm.precision()); + mpz_tdiv_qr(quotient, remainder, rquotient, divisor); + precision = comm.precision(); + } + else if (comm.precision() > quantity->prec) { + mpz_ui_pow_ui(divisor, 10, comm.precision() - quantity->prec); + mpz_mul(rquotient, MPZ(quantity), divisor); + mpz_ui_pow_ui(divisor, 10, comm.precision()); + mpz_tdiv_qr(quotient, remainder, rquotient, divisor); + precision = comm.precision(); + } + else if (quantity->prec) { + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); + precision = quantity->prec; + } + else { + mpz_set(quotient, MPZ(quantity)); + mpz_set_ui(remainder, 0); + precision = 0; + } + + if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { + negative = true; + + mpz_abs(quotient, quotient); + mpz_abs(remainder, remainder); + } + mpz_set(rquotient, remainder); + + if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) { + out << "0"; + return; + } + + if (negative) + out << "-"; + + if (mpz_sgn(quotient) == 0) { + out << '0'; + } else { + char * p = mpz_get_str(NULL, 10, quotient); + out << p; + std::free(p); + } + + if (precision) { + out << '.'; + + out.width(precision); + out.fill('0'); + + char * p = mpz_get_str(NULL, 10, rquotient); + out << p; + std::free(p); + } + + mpz_clear(quotient); + mpz_clear(rquotient); + mpz_clear(remainder); +} + +void amount_t::print(std::ostream& _out, bool omit_commodity, + bool full_precision) const +{ + amount_t base(*this); + if (! amount_t::keep_base && commodity().larger()) { + amount_t last(*this); + while (last.commodity().larger()) { + last /= last.commodity().larger()->number(); + last.commodity_ = last.commodity().larger()->commodity_; + if (last.abs() < 1) + break; + base = last.round(); + } + } + + std::ostringstream out; + + mpz_t quotient; + mpz_t rquotient; + mpz_t remainder; + + mpz_init(quotient); + mpz_init(rquotient); + mpz_init(remainder); + + bool negative = false; + + // Ensure the value is rounded to the commodity's precision before + // outputting it. NOTE: `rquotient' is used here as a temp variable! + + commodity_t& comm(base.commodity()); + unsigned char precision = 0; + + if (quantity) { + if (! comm || full_precision || base.quantity->flags & BIGINT_KEEP_PREC) { + mpz_ui_pow_ui(divisor, 10, base.quantity->prec); + mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor); + precision = base.quantity->prec; + } + else if (comm.precision() < base.quantity->prec) { + mpz_round(rquotient, MPZ(base.quantity), base.quantity->prec, + comm.precision()); + mpz_ui_pow_ui(divisor, 10, comm.precision()); + mpz_tdiv_qr(quotient, remainder, rquotient, divisor); + precision = comm.precision(); + } + else if (comm.precision() > base.quantity->prec) { + mpz_ui_pow_ui(divisor, 10, comm.precision() - base.quantity->prec); + mpz_mul(rquotient, MPZ(base.quantity), divisor); + mpz_ui_pow_ui(divisor, 10, comm.precision()); + mpz_tdiv_qr(quotient, remainder, rquotient, divisor); + precision = comm.precision(); + } + else if (base.quantity->prec) { + mpz_ui_pow_ui(divisor, 10, base.quantity->prec); + mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor); + precision = base.quantity->prec; + } + else { + mpz_set(quotient, MPZ(base.quantity)); + mpz_set_ui(remainder, 0); + precision = 0; + } + + if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { + negative = true; + + mpz_abs(quotient, quotient); + mpz_abs(remainder, remainder); + } + mpz_set(rquotient, remainder); + } + + if (! omit_commodity && ! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) { + comm.write(out); + + if (comm.flags() & COMMODITY_STYLE_SEPARATED) + out << " "; + } + + if (negative) + out << "-"; + + if (! quantity || mpz_sgn(quotient) == 0) { + out << '0'; + } + else if (! (comm.flags() & COMMODITY_STYLE_THOUSANDS)) { + char * p = mpz_get_str(NULL, 10, quotient); + out << p; + std::free(p); + } + else { + std::list strs; + char buf[4]; + + for (int powers = 0; true; powers += 3) { + if (powers > 0) { + mpz_ui_pow_ui(divisor, 10, powers); + mpz_tdiv_q(temp, quotient, divisor); + if (mpz_sgn(temp) == 0) + break; + mpz_tdiv_r_ui(temp, temp, 1000); + } else { + mpz_tdiv_r_ui(temp, quotient, 1000); + } + mpz_get_str(buf, 10, temp); + strs.push_back(buf); + } + + bool printed = false; + + for (std::list::reverse_iterator i = strs.rbegin(); + i != strs.rend(); + i++) { + if (printed) { + out << (comm.flags() & COMMODITY_STYLE_EUROPEAN ? '.' : ','); + out.width(3); + out.fill('0'); + } + out << *i; + + printed = true; + } + } + + if (quantity && precision) { + std::ostringstream final; + final.width(precision); + final.fill('0'); + char * p = mpz_get_str(NULL, 10, rquotient); + final << p; + std::free(p); + + const string& str(final.str()); + int i, len = str.length(); + const char * q = str.c_str(); + for (i = len; i > 0; i--) + if (q[i - 1] != '0') + break; + + string ender; + if (i == len) + ender = str; + else if (i < comm.precision()) + ender = string(str, 0, comm.precision()); + else + ender = string(str, 0, i); + + if (! ender.empty()) { + out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); + out << ender; + } + } + + if (! omit_commodity && comm.flags() & COMMODITY_STYLE_SUFFIXED) { + if (comm.flags() & COMMODITY_STYLE_SEPARATED) + out << " "; + + comm.write(out); + } + + mpz_clear(quotient); + mpz_clear(rquotient); + mpz_clear(remainder); + + // If there are any annotations associated with this commodity, + // output them now. + + if (! omit_commodity && comm.annotated) { + annotated_commodity_t& ann(static_cast(comm)); + assert(&ann.price != this); + ann.write_annotations(out); + } + + // Things are output to a string first, so that if anyone has + // specified a width or fill for _out, it will be applied to the + // entire amount string, and not just the first part. + + _out << out.str(); + + return; +} + +static void parse_quantity(std::istream& in, string& value) +{ + char buf[256]; + char c = peek_next_nonws(in); + READ_INTO(in, buf, 255, c, + std::isdigit(c) || c == '-' || c == '.' || c == ','); + + int len = std::strlen(buf); + while (len > 0 && ! std::isdigit(buf[len - 1])) { + buf[--len] = '\0'; + in.unget(); + } + + value = buf; +} + +// Invalid commodity characters: +// SPACE, TAB, NEWLINE, RETURN +// 0-9 . , ; - + * / ^ ? : & | ! = +// < > { } [ ] ( ) @ + +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, +}; + +static void parse_commodity(std::istream& in, string& symbol) +{ + 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_exception("Quoted commodity symbol lacks closing quote", + context()); + } else { + READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]); + } + symbol = buf; +} + +bool parse_annotations(std::istream& in, amount_t& price, + moment_t& date, string& tag) +{ + bool has_date = false; + + do { + char buf[256]; + char c = peek_next_nonws(in); + if (c == '{') { + if (price) + throw amount_exception("Commodity specifies more than one price", + context()); + + in.get(c); + READ_INTO(in, buf, 255, c, c != '}'); + if (c == '}') + in.get(c); + else + throw amount_exception("Commodity price lacks closing brace", context()); + + price.parse(buf, AMOUNT_PARSE_NO_MIGRATE); + price.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 (price.has_commodity() && + price.quantity->prec < price.commodity().precision()) + price = price.round(); // no need to retain individual precision + } + else if (c == '[') { + if (is_valid_moment(date)) + throw amount_exception("Commodity specifies more than one date", + context()); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ']'); + if (c == ']') + in.get(c); + else + throw amount_exception("Commodity date lacks closing bracket", + context()); + + date = parse_datetime(buf); + has_date = true; + } + else if (c == '(') { + if (! tag.empty()) + throw amount_exception("Commodity specifies more than one tag", + context()); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') + in.get(c); + else + throw amount_exception("Commodity tag lacks closing parenthesis", + context()); + + tag = buf; + } + else { + break; + } + } while (true); + + DEBUG_("amounts.commodities", + "Parsed commodity annotations: " + << " price " << price << " " + << " date " << date << " " + << " tag " << tag); + + return has_date; +} + +void amount_t::parse(std::istream& in, unsigned char flags) +{ + // The possible syntax for an amount is: + // + // [-]NUM[ ]SYM [@ AMOUNT] + // SYM[ ][-]NUM [@ AMOUNT] + + string symbol; + string quant; + amount_t tprice; + moment_t tdate; + bool had_date = false; + string tag; + unsigned int comm_flags = COMMODITY_STYLE_DEFAULTS; + bool negative = false; + + char c = peek_next_nonws(in); + if (c == '-') { + negative = true; + in.get(c); + c = peek_next_nonws(in); + } + + char n; + if (std::isdigit(c)) { + parse_quantity(in, quant); + + if (! in.eof() && ((n = in.peek()) != '\n')) { + if (std::isspace(n)) + comm_flags |= COMMODITY_STYLE_SEPARATED; + + parse_commodity(in, symbol); + + if (! symbol.empty()) + comm_flags |= COMMODITY_STYLE_SUFFIXED; + + if (! in.eof() && ((n = in.peek()) != '\n')) + had_date = parse_annotations(in, tprice, tdate, tag); + } + } else { + parse_commodity(in, symbol); + + if (! in.eof() && ((n = in.peek()) != '\n')) { + if (std::isspace(in.peek())) + comm_flags |= COMMODITY_STYLE_SEPARATED; + + parse_quantity(in, quant); + + if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n')) + had_date = parse_annotations(in, tprice, tdate, tag); + } + } + + if (quant.empty()) + throw amount_exception("No quantity specified for amount", + context()); + + _init(); + + // Create the commodity if has not already been seen, and update the + // precision if something greater was used for the quantity. + + bool newly_created = false; + + if (symbol.empty()) { + commodity_ = NULL; + } else { + commodity_ = commodity_t::find(symbol); + if (! commodity_) { + commodity_ = commodity_t::create(symbol); + newly_created = true; + } + assert(commodity_); + + if (! tprice.realzero() || had_date || ! tag.empty()) + commodity_ = + annotated_commodity_t::find_or_create(*commodity_, tprice, tdate, tag); + } + + // Determine the precision of the amount, based on the usage of + // comma or period. + + string::size_type last_comma = quant.rfind(','); + string::size_type last_period = quant.rfind('.'); + + if (last_comma != string::npos && last_period != string::npos) { + comm_flags |= COMMODITY_STYLE_THOUSANDS; + if (last_comma > last_period) { + comm_flags |= COMMODITY_STYLE_EUROPEAN; + quantity->prec = quant.length() - last_comma - 1; + } else { + quantity->prec = quant.length() - last_period - 1; + } + } + else if (last_comma != string::npos && + commodity().flags() & COMMODITY_STYLE_EUROPEAN) { + quantity->prec = quant.length() - last_comma - 1; + } + else if (last_period != string::npos && + ! (commodity().flags() & COMMODITY_STYLE_EUROPEAN)) { + quantity->prec = quant.length() - last_period - 1; + } + else { + quantity->prec = 0; + } + + // Set the commodity's flags and precision accordingly + + if (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) { + commodity().add_flags(comm_flags); + if (quantity->prec > commodity().precision()) + commodity().set_precision(quantity->prec); + } + + if (flags & AMOUNT_PARSE_NO_MIGRATE) + quantity->flags |= BIGINT_KEEP_PREC; + + // Now we have the final number. Remove commas and periods, if + // necessary. + + if (last_comma != string::npos || last_period != string::npos) { + int len = quant.length(); + char * buf = new char[len + 1]; + const char * p = quant.c_str(); + char * t = buf; + + while (*p) { + if (*p == ',' || *p == '.') + p++; + *t++ = *p++; + } + *t = '\0'; + + mpz_set_str(MPZ(quantity), buf, 10); + delete[] buf; + } else { + mpz_set_str(MPZ(quantity), quant.c_str(), 10); + } + + if (negative) + in_place_negate(); + + if (! (flags & AMOUNT_PARSE_NO_REDUCE)) + in_place_reduce(); +} + +void amount_t::in_place_reduce() +{ + while (commodity_ && commodity().smaller()) { + *this *= commodity().smaller()->number(); + commodity_ = commodity().smaller()->commodity_; + } +} + +void parse_conversion(const string& larger_str, + const string& smaller_str) +{ + amount_t larger, smaller; + + larger.parse(larger_str.c_str(), AMOUNT_PARSE_NO_REDUCE); + smaller.parse(smaller_str.c_str(), AMOUNT_PARSE_NO_REDUCE); + + larger *= smaller.number(); + + if (larger.commodity()) { + larger.commodity().set_smaller(smaller); + larger.commodity().add_flags(smaller.commodity().flags() | + COMMODITY_STYLE_NOMARKET); + } + if (smaller.commodity()) + smaller.commodity().set_larger(larger); +} + +void amount_t::read(std::istream& in) +{ + commodity_t::ident_t ident; + read_binary_long(in, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = commodity_t::null_commodity; + else + commodity_ = (*commodity_t::commodities_by_ident)[ident - 1]; + + read_quantity(in); +} + +void amount_t::read(char *& data) +{ + commodity_t::ident_t ident; + read_binary_long(data, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = commodity_t::null_commodity; + else + commodity_ = (*commodity_t::commodities_by_ident)[ident - 1]; + + read_quantity(data); +} + +void amount_t::write(std::ostream& out) const +{ + if (commodity_) + write_binary_long(out, commodity_->ident); + else + write_binary_long(out, 0xffffffff); + + write_quantity(out); +} + + +#ifndef THREADSAFE +static char * bigints; +static char * bigints_next; +static unsigned int bigints_index; +static unsigned int bigints_count; +#endif + +void amount_t::read_quantity(char *& data) +{ + char byte = *data++;; + + if (byte == 0) { + quantity = NULL; + } + else if (byte == 1) { + quantity = new((bigint_t *)bigints_next) bigint_t; + bigints_next += sizeof(bigint_t); + + unsigned short len = *((unsigned short *) data); + data += sizeof(unsigned short); + mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), + 0, 0, data); + data += len; + + char negative = *data++; + if (negative) + mpz_neg(MPZ(quantity), MPZ(quantity)); + + quantity->prec = *((unsigned char *) data); + data += sizeof(unsigned char); + quantity->flags = *((unsigned char *) data); + data += sizeof(unsigned char); + quantity->flags |= BIGINT_BULK_ALLOC; + } else { + unsigned int index = *((unsigned int *) data); + data += sizeof(unsigned int); + + quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); + DEBUG_("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; + } +} + +#ifndef THREADSAFE +static char buf[4096]; +#endif + +void amount_t::read_quantity(std::istream& in) +{ + char byte; + in.read(&byte, sizeof(byte)); + + if (byte == 0) { + quantity = NULL; + } + else if (byte == 1) { + quantity = new bigint_t; + + unsigned short len; + in.read((char *)&len, sizeof(len)); + assert(len < 4096); + in.read(buf, len); + mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), + 0, 0, buf); + + char negative; + in.read(&negative, sizeof(negative)); + if (negative) + mpz_neg(MPZ(quantity), MPZ(quantity)); + + in.read((char *)&quantity->prec, sizeof(quantity->prec)); + in.read((char *)&quantity->flags, sizeof(quantity->flags)); + } + else { + assert(0); + } +} + +void amount_t::write_quantity(std::ostream& out) const +{ + char byte; + + if (! quantity) { + byte = 0; + out.write(&byte, sizeof(byte)); + return; + } + + if (quantity->index == 0) { + quantity->index = ++bigints_index; + bigints_count++; + + byte = 1; + out.write(&byte, sizeof(byte)); + + std::size_t size; + mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity)); + unsigned short len = size * sizeof(short); + out.write((char *)&len, sizeof(len)); + if (len) { + assert(len < 4096); + out.write(buf, len); + } + + byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0; + out.write(&byte, sizeof(byte)); + + out.write((char *)&quantity->prec, sizeof(quantity->prec)); + unsigned char flags = quantity->flags & ~BIGINT_BULK_ALLOC; + assert(sizeof(flags) == sizeof(quantity->flags)); + out.write((char *)&flags, sizeof(flags)); + } else { + assert(quantity->ref > 1); + + // Since this value has already been written, we simply write + // out a reference to which one it was. + byte = 2; + out.write(&byte, sizeof(byte)); + out.write((char *)&quantity->index, sizeof(quantity->index)); + } +} + +bool amount_t::valid() const +{ + if (quantity) { + if (quantity->ref == 0) { + DEBUG_("ledger.validate", "amount_t: quantity->ref == 0"); + return false; + } + } + else if (commodity_) { + DEBUG_("ledger.validate", "amount_t: commodity_ != NULL"); + return false; + } + return true; +} + +void amount_t::annotate_commodity(const amount_t& tprice, + const moment_t& tdate, + const string& tag) +{ + const commodity_t * this_base; + annotated_commodity_t * this_ann = NULL; + + if (commodity().annotated) { + this_ann = &static_cast(commodity()); + this_base = this_ann->ptr; + } else { + this_base = &commodity(); + } + assert(this_base); + + DEBUG_("amounts.commodities", "Annotating commodity for amount " + << *this << std::endl + << " price " << tprice << " " + << " date " << tdate << " " + << " tag " << tag); + + commodity_t * ann_comm = + annotated_commodity_t::find_or_create + (*this_base, ! tprice && this_ann ? this_ann->price : tprice, + ! is_valid_moment(tdate) && this_ann ? this_ann->date : tdate, + tag.empty() && this_ann ? this_ann->tag : tag); + if (ann_comm) + set_commodity(*ann_comm); + + DEBUG_("amounts.commodities", " Annotated amount is " << *this); +} + +amount_t amount_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) const +{ + if (! commodity().annotated || + (_keep_price && _keep_date && _keep_tag)) + return *this; + + DEBUG_("amounts.commodities", "Reducing commodity for amount " + << *this << std::endl + << " keep price " << _keep_price << " " + << " keep date " << _keep_date << " " + << " keep tag " << _keep_tag); + + annotated_commodity_t& + ann_comm(static_cast(commodity())); + assert(ann_comm.base); + + commodity_t * new_comm; + + if ((_keep_price && ann_comm.price) || + (_keep_date && is_valid_moment(ann_comm.date)) || + (_keep_tag && ! ann_comm.tag.empty())) + { + new_comm = annotated_commodity_t::find_or_create + (*ann_comm.ptr, _keep_price ? ann_comm.price : amount_t(), + _keep_date ? ann_comm.date : moment_t(), + _keep_tag ? ann_comm.tag : ""); + } else { + new_comm = commodity_t::find_or_create(ann_comm.base_symbol()); + } + assert(new_comm); + + amount_t t(*this); + t.set_commodity(*new_comm); + DEBUG_("amounts.commodities", " Reduced amount is " << t); + + return t; +} + +amount_t amount_t::price() const +{ + if (commodity_ && commodity_->annotated) { + amount_t t(((annotated_commodity_t *)commodity_)->price); + t *= number(); + DEBUG_("amounts.commodities", + "Returning price of " << *this << " = " << t); + return t; + } + return *this; +} + +moment_t amount_t::date() const +{ + if (commodity_ && commodity_->annotated) { + DEBUG_("amounts.commodities", + "Returning date of " << *this << " = " + << ((annotated_commodity_t *)commodity_)->date); + return ((annotated_commodity_t *)commodity_)->date; + } + return moment_t(); +} + + +void commodity_base_t::add_price(const moment_t& date, + const amount_t& price) +{ + if (! history) + history = new history_t; + + history_map::iterator i = history->prices.find(date); + if (i != history->prices.end()) { + (*i).second = price; + } else { + std::pair result + = history->prices.insert(history_pair(date, price)); + assert(result.second); + } +} + +bool commodity_base_t::remove_price(const moment_t& date) +{ + if (history) { + history_map::size_type n = history->prices.erase(date); + if (n > 0) { + if (history->prices.empty()) + history = NULL; + return true; + } + } + return false; +} + +commodity_base_t * commodity_base_t::create(const string& symbol) +{ + commodity_base_t * commodity = new commodity_base_t(symbol); + + DEBUG_("amounts.commodities", "Creating base commodity " << symbol); + + std::pair result + = commodities.insert(base_commodities_pair(symbol, commodity)); + assert(result.second); + + return commodity; +} + +bool commodity_t::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; +} + +bool commodity_t::valid() const +{ + if (symbol().empty() && this != 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; +} + +commodity_t * commodity_t::create(const string& symbol) +{ + std::auto_ptr commodity(new commodity_t); + + commodity->base = commodity_base_t::create(symbol); + + if (needs_quotes(symbol)) { + commodity->qualified_symbol = "\""; + commodity->qualified_symbol += symbol; + commodity->qualified_symbol += "\""; + } else { + commodity->qualified_symbol = symbol; + } + + DEBUG_("amounts.commodities", + "Creating commodity " << commodity->qualified_symbol); + + std::pair result + = commodities.insert(commodities_pair(symbol, commodity.get())); + if (! result.second) + return NULL; + + commodity->ident = commodities_by_ident->size(); + commodities_by_ident->push_back(commodity.get()); + + // Start out the new commodity with the default commodity's flags + // and precision, if one has been defined. + if (default_commodity) + commodity->drop_flags(COMMODITY_STYLE_THOUSANDS | + COMMODITY_STYLE_NOMARKET); + + return commodity.release(); +} + +commodity_t * commodity_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_t::find(const string& symbol) +{ + DEBUG_("amounts.commodities", "Find commodity " << symbol); + + commodities_map::const_iterator i = commodities.find(symbol); + if (i != commodities.end()) + return (*i).second; + return NULL; +} + +amount_t commodity_base_t::value(const moment_t& moment) +{ + moment_t age; + amount_t price; + + if (history) { + assert(history->prices.size() > 0); + + if (! is_valid_moment(moment)) { + history_map::reverse_iterator r = history->prices.rbegin(); + age = (*r).first; + price = (*r).second; + } else { + history_map::iterator i = history->prices.lower_bound(moment); + if (i == history->prices.end()) { + history_map::reverse_iterator r = history->prices.rbegin(); + age = (*r).first; + price = (*r).second; + } else { + age = (*i).first; + if (moment != age) { + if (i != history->prices.begin()) { + --i; + age = (*i).first; + price = (*i).second; + } else { + age = moment_t(); + } + } else { + price = (*i).second; + } + } + } + } + + if (updater && ! (flags & COMMODITY_STYLE_NOMARKET)) + (*updater)(*this, moment, age, + (history && history->prices.size() > 0 ? + (*history->prices.rbegin()).first : moment_t()), price); + + return price; +} + +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; + + if (price && + (! comm.annotated || + price != static_cast(comm).price)) + return false; + + if (is_valid_moment(date) && + (! comm.annotated || + date != static_cast(comm).date)) + return false; + + if (! tag.empty() && + (! comm.annotated || + tag != static_cast(comm).tag)) + return false; + + return true; +} + +void +annotated_commodity_t::write_annotations(std::ostream& out, + const amount_t& price, + const moment_t& date, + const string& tag) +{ + if (price) + out << " {" << price << '}'; + + if (is_valid_moment(date)) + out << " [" << date << ']'; + + if (! tag.empty()) + out << " (" << tag << ')'; +} + +commodity_t * +annotated_commodity_t::create(const commodity_t& comm, + const amount_t& price, + const moment_t& date, + const string& tag, + const string& mapping_key) +{ + std::auto_ptr commodity(new annotated_commodity_t); + + // Set the annotated bits + commodity->price = price; + commodity->date = date; + commodity->tag = tag; + + commodity->ptr = &comm; + assert(commodity->ptr); + commodity->base = comm.base; + assert(commodity->base); + + commodity->qualified_symbol = comm.symbol(); + + DEBUG_("amounts.commodities", "Creating annotated commodity " + << "symbol " << commodity->symbol() + << " key " << mapping_key << std::endl + << " price " << price << " " + << " date " << date << " " + << " tag " << tag); + + // Add the fully annotated name to the map, so that this symbol may + // quickly be found again. + std::pair result + = commodities.insert(commodities_pair(mapping_key, commodity.get())); + if (! result.second) + return NULL; + + commodity->ident = commodities_by_ident->size(); + commodities_by_ident->push_back(commodity.get()); + + return commodity.release(); +} + +namespace { + string make_qualified_name(const commodity_t& comm, + const amount_t& price, + const moment_t& date, + const string& tag) + { + if (price < 0) + throw amount_exception("A commodity's price may not be negative", + context()); + + std::ostringstream name; + + comm.write(name); + annotated_commodity_t::write_annotations(name, price, date, tag); + + DEBUG_("amounts.commodities", "make_qualified_name for " + << comm.qualified_symbol << std::endl + << " price " << price << " " + << " date " << date << " " + << " tag " << tag); + + DEBUG_("amounts.commodities", "qualified_name is " << name.str()); + + return name.str(); + } +} + +commodity_t * +annotated_commodity_t::find_or_create(const commodity_t& comm, + const amount_t& price, + const moment_t& date, + const string& tag) +{ + string name = make_qualified_name(comm, price, date, tag); + + commodity_t * ann_comm = commodity_t::find(name); + if (ann_comm) { + assert(ann_comm->annotated); + return ann_comm; + } + return create(comm, price, date, tag, name); +} + +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.price && arightcomm.price) + return true; + if (aleftcomm.price && ! arightcomm.price) + return false; + + if (aleftcomm.price && arightcomm.price) { + amount_t leftprice(aleftcomm.price); + leftprice.in_place_reduce(); + amount_t rightprice(arightcomm.price); + rightprice.in_place_reduce(); + + if (leftprice.commodity() == rightprice.commodity()) { + amount_t val = leftprice - rightprice; + if (val) + return val < 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(); + + amount_t val = leftprice - rightprice; + if (val) + return val < 0; + } + } + + if (! is_valid_moment(aleftcomm.date) && + is_valid_moment(arightcomm.date)) + return true; + if (is_valid_moment(aleftcomm.date) && + ! is_valid_moment(arightcomm.date)) + return false; + + if (is_valid_moment(aleftcomm.date) && + is_valid_moment(arightcomm.date)) { + duration_t diff = aleftcomm.date - arightcomm.date; + return diff.is_negative(); + } + + if (aleftcomm.tag.empty() && ! arightcomm.tag.empty()) + return true; + if (! aleftcomm.tag.empty() && arightcomm.tag.empty()) + return false; + + if (! aleftcomm.tag.empty() && ! arightcomm.tag.empty()) + return aleftcomm.tag < arightcomm.tag; + + assert(0); + return true; + } +} + +} // namespace ledger diff --git a/src/amount.h b/src/amount.h new file mode 100644 index 00000000..dcd30b8d --- /dev/null +++ b/src/amount.h @@ -0,0 +1,760 @@ +/** + * @file amount.h + * @author John Wiegley + * @date Wed Apr 18 22:05:53 2007 + * + * @brief Types for handling commoditized math. + * + * This file contains two of the most basic types in Ledger: amount_t + * commodity_t, and annotated_commodity_t. Both the commodity types + * share a common base class, commodity_base_t. These four class + * together allow Ledger to handle mathematical expressions involving + * differing commodities, or in some cases math using no commodities + * at all (such as increasing a dollar amount by a multiplier). + */ + +/* + * 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 _AMOUNT_H +#define _AMOUNT_H + +#include "utils.h" +#include "times.h" + +namespace ledger { + +extern bool do_cleanup; + +class commodity_t; + +/** + * @class amount_t + * + * @brief Encapsulates infinite-precision commoditized amounts. + * + * The amount_t class can be used for commoditized infinite-precision + * math, and also for uncommoditized math. In the commoditized case, + * commodities keep track of how they are used, and will always + * display back to the user after the same fashion. For + * uncommoditized numbers, no display truncation is ever done. + * Internally, precision is always kept to an excessive degree. + */ + +class amount_t +{ + public: + class bigint_t; + + static void initialize(); + static void shutdown(); + + static bool keep_price; + static bool keep_date; + static bool keep_tag; + static bool keep_base; + static bool full_strings; + + protected: + void _init(); + void _copy(const amount_t& amt); + void _release(); + void _dup(); + void _resize(unsigned int prec); + void _clear(); + + bigint_t * quantity; + commodity_t * commodity_; + + public: + // constructors + amount_t() : quantity(NULL), commodity_(NULL) { + TRACE_CTOR(amount_t, ""); + } + amount_t(const amount_t& amt) : quantity(NULL) { + TRACE_CTOR(amount_t, "copy"); + if (amt.quantity) + _copy(amt); + else + commodity_ = NULL; + } + amount_t(const string& val) : quantity(NULL) { + TRACE_CTOR(amount_t, "const string&"); + parse(val); + } + amount_t(const char * val) : quantity(NULL) { + TRACE_CTOR(amount_t, "const char *"); + parse(val); + } + amount_t(const long val); + amount_t(const unsigned long val); + amount_t(const double val); + + // destructor + ~amount_t() { + TRACE_DTOR(amount_t); + if (quantity) + _release(); + } + + amount_t number() const { + amount_t temp(*this); + temp.clear_commodity(); + return temp; + } + + bool has_commodity() const; + commodity_t& commodity() const; + void set_commodity(commodity_t& comm) { + commodity_ = &comm; + } + void annotate_commodity(const amount_t& price, + const moment_t& date = moment_t(), + const string& tag = ""); + amount_t strip_annotations(const bool _keep_price = keep_price, + const bool _keep_date = keep_date, + const bool _keep_tag = keep_tag) const; + void clear_commodity() { + commodity_ = NULL; + } + amount_t price() const; + moment_t date() const; + + bool null() const { + return ! quantity && ! has_commodity(); + } + + // assignment operator + amount_t& operator=(const amount_t& amt); + amount_t& operator=(const string& val); + amount_t& operator=(const char * val); + amount_t& operator=(const long val); + amount_t& operator=(const unsigned long val); + amount_t& operator=(const double val); + + // general methods + amount_t round(unsigned int prec) const; + amount_t round() const; + amount_t unround() const; + + // in-place arithmetic + amount_t& operator+=(const amount_t& amt); + amount_t& operator-=(const amount_t& amt); + amount_t& operator*=(const amount_t& amt); + amount_t& operator/=(const amount_t& amt); + + template + amount_t& operator+=(T val) { + return *this += amount_t(val); + } + template + amount_t& operator-=(T val) { + return *this -= amount_t(val); + } + template + amount_t& operator*=(T val) { + return *this *= amount_t(val); + } + template + amount_t& operator/=(T val) { + return *this /= amount_t(val); + } + + // simple arithmetic + amount_t operator+(const amount_t& amt) const { + amount_t temp = *this; + temp += amt; + return temp; + } + amount_t operator-(const amount_t& amt) const { + amount_t temp = *this; + temp -= amt; + return temp; + } + amount_t operator*(const amount_t& amt) const { + amount_t temp = *this; + temp *= amt; + return temp; + } + amount_t operator/(const amount_t& amt) const { + amount_t temp = *this; + temp /= amt; + return temp; + } + + template + amount_t operator+(T val) const { + amount_t temp = *this; + temp += val; + return temp; + } + template + amount_t operator-(T val) const { + amount_t temp = *this; + temp -= val; + return temp; + } + template + amount_t operator*(T val) const { + amount_t temp = *this; + temp *= val; + return temp; + } + template + amount_t operator/(T val) const { + amount_t temp = *this; + temp /= val; + return temp; + } + + // unary negation + void in_place_negate(); + amount_t negate() const { + amount_t temp = *this; + temp.in_place_negate(); + return temp; + } + amount_t operator-() const { + return negate(); + } + + // test for zero and non-zero + int sign() const; + bool zero() const; + bool realzero() const { + return sign() == 0; + } + operator bool() const { + return ! zero(); + } + operator string() const { + return to_string(); + } + + operator long() const; + operator double() const; + + string to_string() const; + string to_fullstring() const; + string quantity_string() const; + + // comparisons between amounts + int compare(const amount_t& amt) const; + + bool operator<(const amount_t& amt) const { + return compare(amt) < 0; + } + bool operator<=(const amount_t& amt) const { + return compare(amt) <= 0; + } + bool operator>(const amount_t& amt) const { + return compare(amt) > 0; + } + bool operator>=(const amount_t& amt) const { + return compare(amt) >= 0; + } + bool operator==(const amount_t& amt) const; + bool operator!=(const amount_t& amt) const; + + template + void parse_num(T num) { + std::ostringstream temp; + temp << num; + std::istringstream in(temp.str()); + parse(in); + } + + // POD comparisons +#define AMOUNT_CMP_INT(OP) \ + template \ + bool operator OP (T num) const { \ + if (num == 0) { \ + return sign() OP 0; \ + } else { \ + amount_t amt; \ + amt.parse_num(num); \ + return *this OP amt; \ + } \ + } + + AMOUNT_CMP_INT(<) + AMOUNT_CMP_INT(<=) + AMOUNT_CMP_INT(>) + AMOUNT_CMP_INT(>=) + AMOUNT_CMP_INT(==) + + template + bool operator!=(T num) const { + return ! (*this == num); + } + + amount_t value(const moment_t& moment) const; + + amount_t abs() const { + if (*this < 0) + return negate(); + return *this; + } + + void in_place_reduce(); + amount_t reduce() const { + amount_t temp(*this); + temp.in_place_reduce(); + return temp; + } + + bool valid() const; + + static amount_t exact(const string& value); + + // This function is special, and exists only to support a custom + // optimization in binary.cc (which offers a significant enough gain + // to be worth the trouble). + + friend void clean_commodity_history(char * item_pool, + char * item_pool_end); + + friend bool parse_annotations(std::istream& in, amount_t& price, + moment_t& date, string& tag); + + // Streaming interface + + void dump(std::ostream& out) const { + out << "AMOUNT("; + print(out); + out << ")"; + } + +#define AMOUNT_PARSE_NO_MIGRATE 0x01 +#define AMOUNT_PARSE_NO_REDUCE 0x02 + + void print(std::ostream& out, bool omit_commodity = false, + bool full_precision = false) const; + void parse(std::istream& in, unsigned char flags = 0); + void parse(const string& str, unsigned char flags = 0) { + std::istringstream stream(str); + parse(stream, flags); + } + + void print_quantity(std::ostream& out) const; + + void write(std::ostream& out) const; + void read(std::istream& in); + void read(char *& data); + + void write_quantity(std::ostream& out) const; + void read_quantity(std::istream& in); + void read_quantity(char *& data); +}; + +inline amount_t amount_t::exact(const string& value) { + amount_t temp; + temp.parse(value, AMOUNT_PARSE_NO_MIGRATE); + return temp; +} + +inline string amount_t::to_string() const { + std::ostringstream bufstream; + print(bufstream); + return bufstream.str(); +} + +inline string amount_t::to_fullstring() const { + std::ostringstream bufstream; + print(bufstream, false, true); + return bufstream.str(); +} + +inline string amount_t::quantity_string() const { + std::ostringstream bufstream; + print(bufstream, true); + return bufstream.str(); +} + +#define DEFINE_AMOUNT_OPERATORS(T) \ +inline amount_t operator+(const T val, const amount_t& amt) { \ + amount_t temp(val); \ + temp += amt; \ + return temp; \ +} \ +inline amount_t operator-(const T val, const amount_t& amt) { \ + amount_t temp(val); \ + temp -= amt; \ + return temp; \ +} \ +inline amount_t operator*(const T val, const amount_t& amt) { \ + amount_t temp(val); \ + temp *= amt; \ + return temp; \ +} \ +inline amount_t operator/(const T val, const amount_t& amt) { \ + amount_t temp(val); \ + temp /= amt; \ + return temp; \ +} \ + \ +inline bool operator<(const T val, const amount_t& amt) { \ + return amount_t(val) < amt; \ +} \ +inline bool operator<=(const T val, const amount_t& amt) { \ + return amount_t(val) <= amt; \ +} \ +inline bool operator>(const T val, const amount_t& amt) { \ + return amount_t(val) > amt; \ +} \ +inline bool operator>=(const T val, const amount_t& amt) { \ + return amount_t(val) >= amt; \ +} \ +inline bool operator==(const T val, const amount_t& amt) { \ + return amount_t(val) == amt; \ +} \ +inline bool operator!=(const T val, const amount_t& amt) { \ + return amount_t(val) != amt; \ +} + +DEFINE_AMOUNT_OPERATORS(long) +DEFINE_AMOUNT_OPERATORS(unsigned long) +DEFINE_AMOUNT_OPERATORS(double) + +inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) { + amt.print(out, false, amount_t::full_strings); + return out; +} +inline std::istream& operator>>(std::istream& in, amount_t& amt) { + amt.parse(in); + return in; +} + + +#define COMMODITY_STYLE_DEFAULTS 0x0000 +#define COMMODITY_STYLE_SUFFIXED 0x0001 +#define COMMODITY_STYLE_SEPARATED 0x0002 +#define COMMODITY_STYLE_EUROPEAN 0x0004 +#define COMMODITY_STYLE_THOUSANDS 0x0008 +#define COMMODITY_STYLE_NOMARKET 0x0010 +#define COMMODITY_STYLE_BUILTIN 0x0020 + +typedef std::map history_map; +typedef std::pair history_pair; + +class commodity_base_t; + +typedef std::map base_commodities_map; +typedef std::pair base_commodities_pair; + +class commodity_base_t +{ + public: + friend class commodity_t; + friend class annotated_commodity_t; + + typedef unsigned long ident_t; + + ident_t ident; + string name; + string note; + unsigned char precision; + unsigned char flags; + amount_t * smaller; + amount_t * larger; + + commodity_base_t() + : precision(0), flags(COMMODITY_STYLE_DEFAULTS), + smaller(NULL), larger(NULL), history(NULL) { + TRACE_CTOR(commodity_base_t, ""); + } + + commodity_base_t(const commodity_base_t&) { + TRACE_CTOR(commodity_base_t, "copy"); + assert(0); + } + + commodity_base_t(const string& _symbol, + unsigned int _precision = 0, + unsigned int _flags = COMMODITY_STYLE_DEFAULTS) + : precision(_precision), flags(_flags), + smaller(NULL), larger(NULL), symbol(_symbol), history(NULL) { + TRACE_CTOR(commodity_base_t, "const string&, unsigned int, unsigned int"); + } + + ~commodity_base_t() { + TRACE_DTOR(commodity_base_t); + if (history) delete history; + if (smaller) delete smaller; + if (larger) delete larger; + } + + static base_commodities_map commodities; + static commodity_base_t * create(const string& symbol); + + string symbol; + + struct history_t { + history_map prices; + ptime last_lookup; + history_t() : last_lookup() {} + }; + history_t * history; + + void add_price(const moment_t& date, const amount_t& price); + bool remove_price(const moment_t& date); + amount_t value(const moment_t& moment = now); + + class updater_t { + public: + virtual ~updater_t() {} + virtual void operator()(commodity_base_t& commodity, + const moment_t& moment, + const moment_t& date, + const moment_t& last, + amount_t& price) = 0; + }; + friend class updater_t; + + static updater_t * updater; +}; + +typedef std::map commodities_map; +typedef std::pair commodities_pair; + +typedef std::vector commodities_array; + +class commodity_t +{ + friend class annotated_commodity_t; + + public: + // This map remembers all commodities that have been defined. + + static commodities_map commodities; + static commodities_array * commodities_by_ident; + static bool commodities_sorted; + static commodity_t * null_commodity; + static commodity_t * default_commodity; + + static commodity_t * create(const string& symbol); + static commodity_t * find(const string& name); + static commodity_t * find_or_create(const string& symbol); + + static bool needs_quotes(const string& symbol); + + static void make_alias(const string& symbol, + commodity_t * commodity); + + // These are specific to each commodity reference + + typedef unsigned long ident_t; + + ident_t ident; + commodity_base_t * base; + string qualified_symbol; + bool annotated; + + public: + explicit commodity_t() : base(NULL), annotated(false) { + TRACE_CTOR(commodity_t, ""); + } + commodity_t(const commodity_t& o) + : ident(o.ident), base(o.base), + qualified_symbol(o.qualified_symbol), annotated(o.annotated) { + TRACE_CTOR(commodity_t, "copy"); + } + virtual ~commodity_t() { + TRACE_DTOR(commodity_t); + } + + operator bool() const { + return this != null_commodity; + } + virtual bool operator==(const commodity_t& comm) const { + if (comm.annotated) + return comm == *this; + return base == comm.base; + } + bool operator!=(const commodity_t& comm) const { + return ! (*this == comm); + } + + string base_symbol() const { + return base->symbol; + } + string symbol() const { + return qualified_symbol; + } + + void write(std::ostream& out) const { + out << symbol(); + } + + string name() const { + return base->name; + } + void set_name(const string& arg) { + base->name = arg; + } + + string note() const { + return base->note; + } + void set_note(const string& arg) { + base->note = arg; + } + + unsigned char precision() const { + return base->precision; + } + void set_precision(unsigned char arg) { + base->precision = arg; + } + + unsigned char flags() const { + return base->flags; + } + void set_flags(unsigned char arg) { + base->flags = arg; + } + void add_flags(unsigned char arg) { + base->flags |= arg; + } + void drop_flags(unsigned char arg) { + base->flags &= ~arg; + } + + amount_t * smaller() const { + return base->smaller; + } + void set_smaller(const amount_t& arg) { + if (base->smaller) + delete base->smaller; + base->smaller = new amount_t(arg); + } + + amount_t * larger() const { + return base->larger; + } + void set_larger(const amount_t& arg) { + if (base->larger) + delete base->larger; + base->larger = new amount_t(arg); + } + + commodity_base_t::history_t * history() const { + return base->history; + } + + void add_price(const moment_t& date, const amount_t& price) { + return base->add_price(date, price); + } + bool remove_price(const moment_t& date) { + return base->remove_price(date); + } + amount_t value(const moment_t& moment = now) const { + return base->value(moment); + } + + bool valid() const; +}; + +class annotated_commodity_t : public commodity_t +{ + public: + const commodity_t * ptr; + + amount_t price; + moment_t date; + string tag; + + explicit annotated_commodity_t() { + TRACE_CTOR(annotated_commodity_t, ""); + annotated = true; + } + virtual ~annotated_commodity_t() { + TRACE_DTOR(annotated_commodity_t); + } + + virtual bool operator==(const commodity_t& comm) const; + + void write_annotations(std::ostream& out) const { + annotated_commodity_t::write_annotations(out, price, date, tag); + } + + static void write_annotations(std::ostream& out, + const amount_t& price, + const moment_t& date, + const string& tag); + + private: + static commodity_t * create(const commodity_t& comm, + const amount_t& price, + const moment_t& date, + const string& tag, + const string& mapping_key); + + static commodity_t * find_or_create(const commodity_t& comm, + const amount_t& price, + const moment_t& date, + const string& tag); + + friend class amount_t; +}; + +inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { + out << comm.symbol(); + return out; +} + +inline amount_t amount_t::round() const { + return round(commodity().precision()); +} + +inline bool amount_t::has_commodity() const { + return commodity_ && commodity_ != commodity_t::null_commodity; +} + +inline commodity_t& amount_t::commodity() const { + if (! commodity_) + return *commodity_t::null_commodity; + else + return *commodity_; +} + +void parse_conversion(const string& larger_str, + const string& smaller_str); + +DECLARE_EXCEPTION(amount_exception); + +struct compare_amount_commodities { + bool operator()(const amount_t * left, const amount_t * right) const; +}; + +} // namespace ledger + +#endif // _AMOUNT_H diff --git a/src/balance.cc b/src/balance.cc new file mode 100644 index 00000000..487f749f --- /dev/null +++ b/src/balance.cc @@ -0,0 +1,313 @@ +#include "balance.h" + +namespace ledger { + +amount_t balance_t::amount(const commodity_t& commodity) const +{ + 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.amount(commodity); + + throw_(amount_exception, + "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 amount_t(); +} + +balance_t balance_t::value(const moment_t& moment) const +{ + balance_t temp; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + temp += (*i).second.value(moment); + + return temp; +} + +balance_t balance_t::price() const +{ + balance_t temp; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + temp += (*i).second.price(); + + return temp; +} + +moment_t balance_t::date() const +{ + moment_t temp; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) { + moment_t tdate = (*i).second.date(); + if (! is_valid_moment(temp) && is_valid_moment(tdate)) + temp = tdate; + else if (temp != tdate) + return moment_t(); + } + return temp; +} + +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::write(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; + + if (commodity_t::commodities_sorted) { + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) { + int width; + if (! first) { + out << std::endl; + width = lwidth; + } else { + first = false; + width = first_width; + } + + out.width(width); + out.fill(' '); + out << std::right << (*i).second; + } + } else { + typedef std::vector 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"; + } +} + +balance_t& balance_t::operator*=(const balance_t& bal) +{ + if (realzero() || bal.realzero()) { + return *this = 0L; + } + else if (bal.amounts.size() == 1) { + return *this *= (*bal.amounts.begin()).second; + } + else if (amounts.size() == 1) { + return *this = bal * *this; + } + else { + // Since we would fail with an error at this point otherwise, try + // stripping annotations to see if we can come up with a + // reasonable result. The user will not notice any annotations + // missing (since they are viewing a stripped report anyway), only + // that some of their value expression may not see any pricing or + // date data because of this operation. + + balance_t temp(bal.strip_annotations()); + if (temp.amounts.size() == 1) + return *this *= temp; + temp = strip_annotations(); + if (temp.amounts.size() == 1) + return *this = bal * temp; + + std::ostringstream errmsg; + errmsg << "Cannot multiply two balances: " << temp << " * " << bal; + throw amount_exception(errmsg.str(), context()); + } +} + +balance_t& balance_t::operator*=(const amount_t& amt) +{ + if (realzero() || amt.realzero()) { + return *this = 0L; + } + else if (! amt.commodity()) { + // Multiplying by the null commodity causes all amounts to be + // increased by the same factor. + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second *= amt; + } + else if (amounts.size() == 1) { + *this = (*amounts.begin()).second * amt; + } + else { + amounts_map::iterator i = amounts.find(&amt.commodity()); + if (i != amounts.end()) { + (*i).second *= amt; + } else { + // Try stripping annotations before giving an error. + balance_t temp(strip_annotations()); + if (temp.amounts.size() == 1) { + return *this = (*temp.amounts.begin()).second * amt; + } else { + i = temp.amounts.find(&amt.commodity()); + if (i != temp.amounts.end()) + return *this = temp * amt; + } + + std::ostringstream errmsg; + errmsg << "Attempt to multiply balance by a commodity" + << " not found in that balance: " + << temp << " * " << amt; + throw amount_exception(errmsg.str(), context()); + } + } + return *this; +} + +balance_t& balance_t::operator/=(const balance_t& bal) +{ + if (bal.realzero()) { + std::ostringstream errmsg; + errmsg << "Attempt to divide by zero: " << *this << " / " << bal; + throw amount_exception(errmsg.str(), context()); + } + else if (realzero()) { + return *this = 0L; + } + else if (bal.amounts.size() == 1) { + return *this /= (*bal.amounts.begin()).second; + } + else if (*this == bal) { + return *this = 1L; + } + else { + // Try stripping annotations before giving an error. + balance_t temp(bal.strip_annotations()); + if (temp.amounts.size() == 1) + return *this /= temp; + + std::ostringstream errmsg; + errmsg << "Cannot divide between two balances: " << temp << " / " << bal; + throw amount_exception(errmsg.str(), context()); + } +} + +balance_t& balance_t::operator/=(const amount_t& amt) +{ + if (amt.realzero()) { + std::ostringstream errmsg; + errmsg << "Attempt to divide by zero: " << *this << " / " << amt; + throw amount_exception(errmsg.str(), context()); + } + else if (realzero()) { + return *this = 0L; + } + else if (! amt.commodity()) { + // Dividing by the null commodity causes all amounts to be + // decreased by the same factor. + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second /= amt; + } + else if (amounts.size() == 1 && + (*amounts.begin()).first == &amt.commodity()) { + (*amounts.begin()).second /= amt; + } + else { + amounts_map::iterator i = amounts.find(&amt.commodity()); + if (i != amounts.end()) { + (*i).second /= amt; + } else { + // Try stripping annotations before giving an error. + balance_t temp(strip_annotations()); + if (temp.amounts.size() == 1 && + (*temp.amounts.begin()).first == &amt.commodity()) + return *this = temp / amt; + + std::ostringstream errmsg; + errmsg << "Attempt to divide balance by a commodity" + << " not found in that balance: " + << temp << " * " << amt; + throw amount_exception(errmsg.str(), context()); + } + } + return *this; +} + +balance_t::operator amount_t() const +{ + if (amounts.size() == 1) { + return (*amounts.begin()).second; + } + else if (amounts.size() == 0) { + return amount_t(); + } + else { + // Try stripping annotations before giving an error. + balance_t temp(strip_annotations()); + if (temp.amounts.size() == 1) + return (*temp.amounts.begin()).second; + + throw_(amount_exception, + "Cannot convert a balance with " << + "multiple commodities to an amount: " << temp); + } +} + +} // namespace ledger diff --git a/src/balance.h b/src/balance.h new file mode 100644 index 00000000..2a6f3072 --- /dev/null +++ b/src/balance.h @@ -0,0 +1,969 @@ +#ifndef _BALANCE_H +#define _BALANCE_H + +#include "amount.h" + +namespace ledger { + +typedef std::map amounts_map; +typedef std::pair amounts_pair; + +class balance_t +{ + public: + amounts_map amounts; + + bool valid() const { + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (! (*i).second.valid()) + return false; + return true; + } + + // constructors + balance_t() { + TRACE_CTOR(balance_t, ""); + } + balance_t(const balance_t& bal) { + TRACE_CTOR(balance_t, "copy"); + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); + i++) + *this += (*i).second; + } + balance_t(const amount_t& amt) { + TRACE_CTOR(balance_t, "const amount_t&"); + if (! amt.realzero()) + amounts.insert(amounts_pair(&amt.commodity(), amt)); + } + template + balance_t(T val) { + TRACE_CTOR(balance_t, "T"); + amount_t amt(val); + if (! amt.realzero()) + amounts.insert(amounts_pair(&amt.commodity(), amt)); + } + + ~balance_t() { + TRACE_DTOR(balance_t); + } + + // assignment operator + balance_t& operator=(const balance_t& bal) { + if (this != &bal) { + amounts.clear(); + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); + i++) + *this += (*i).second; + } + return *this; + } + balance_t& operator=(const amount_t& amt) { + amounts.clear(); + *this += amt; + return *this; + } + template + balance_t& operator=(T val) { + amounts.clear(); + *this += val; + return *this; + } + + // in-place arithmetic + 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& operator+=(const amount_t& amt) { + amounts_map::iterator i = amounts.find(&amt.commodity()); + if (i != amounts.end()) + (*i).second += amt; + else if (! amt.realzero()) + amounts.insert(amounts_pair(&amt.commodity(), amt)); + return *this; + } + template + balance_t& operator+=(T val) { + return *this += amount_t(val); + } + 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& operator-=(const amount_t& amt) { + amounts_map::iterator i = amounts.find(&amt.commodity()); + if (i != amounts.end()) { + (*i).second -= amt; + if ((*i).second.realzero()) + amounts.erase(i); + } + else if (! amt.realzero()) { + amounts.insert(amounts_pair(&amt.commodity(), - amt)); + } + return *this; + } + template + balance_t& operator-=(T val) { + return *this -= amount_t(val); + } + + // simple arithmetic + balance_t operator+(const balance_t& bal) const { + balance_t temp = *this; + temp += bal; + return temp; + } + balance_t operator+(const amount_t& amt) const { + balance_t temp = *this; + temp += amt; + return temp; + } + template + balance_t operator+(T val) const { + balance_t temp = *this; + temp += val; + return temp; + } + balance_t operator-(const balance_t& bal) const { + balance_t temp = *this; + temp -= bal; + return temp; + } + balance_t operator-(const amount_t& amt) const { + balance_t temp = *this; + temp -= amt; + return temp; + } + template + balance_t operator-(T val) const { + balance_t temp = *this; + temp -= val; + return temp; + } + + // multiplication and divide + balance_t& operator*=(const balance_t& bal); + balance_t& operator*=(const amount_t& amt); + template + balance_t& operator*=(T val) { + return *this *= amount_t(val); + } + + balance_t& operator/=(const balance_t& bal); + balance_t& operator/=(const amount_t& amt); + template + balance_t& operator/=(T val) { + return *this /= amount_t(val); + } + + // multiplication and divide + balance_t operator*(const balance_t& bal) const { + balance_t temp = *this; + temp *= bal; + return temp; + } + balance_t operator*(const amount_t& amt) const { + balance_t temp = *this; + temp *= amt; + return temp; + } + template + balance_t operator*(T val) const { + balance_t temp = *this; + temp *= val; + return temp; + } + balance_t operator/(const balance_t& bal) const { + balance_t temp = *this; + temp /= bal; + return temp; + } + balance_t operator/(const amount_t& amt) const { + balance_t temp = *this; + temp /= amt; + return temp; + } + template + balance_t operator/(T val) const { + balance_t temp = *this; + temp /= val; + return temp; + } + + // comparison + bool operator<(const balance_t& bal) const { + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); + i++) + if (! (amount(*(*i).first) < (*i).second)) + return false; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (! ((*i).second < bal.amount(*(*i).first))) + return false; + + if (bal.amounts.size() == 0 && amounts.size() == 0) + return false; + + return true; + } + bool operator<(const amount_t& amt) const { + if (amt.commodity()) + return amount(amt.commodity()) < amt; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second < amt) + return true; + return false; + } + template + bool operator<(T val) const { + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second < val) + return true; + return false; + } + + bool operator<=(const balance_t& bal) const { + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); + i++) + if (! (amount(*(*i).first) <= (*i).second)) + return false; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (! ((*i).second <= bal.amount(*(*i).first))) + return false; + + return true; + } + bool operator<=(const amount_t& amt) const { + if (amt.commodity()) + return amount(amt.commodity()) <= amt; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second <= amt) + return true; + return false; + } + template + bool operator<=(T val) const { + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second <= val) + return true; + return false; + } + + bool operator>(const balance_t& bal) const { + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); + i++) + if (! (amount(*(*i).first) > (*i).second)) + return false; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (! ((*i).second > bal.amount(*(*i).first))) + return false; + + if (bal.amounts.size() == 0 && amounts.size() == 0) + return false; + + return true; + } + bool operator>(const amount_t& amt) const { + if (amt.commodity()) + return amount(amt.commodity()) > amt; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second > amt) + return true; + return false; + } + template + bool operator>(T val) const { + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second > val) + return true; + return false; + } + + bool operator>=(const balance_t& bal) const { + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); + i++) + if (! (amount(*(*i).first) >= (*i).second)) + return false; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (! ((*i).second >= bal.amount(*(*i).first))) + return false; + + return true; + } + bool operator>=(const amount_t& amt) const { + if (amt.commodity()) + return amount(amt.commodity()) >= amt; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second >= amt) + return true; + return false; + } + template + bool operator>=(T val) const { + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second >= val) + return true; + return false; + } + + bool operator==(const balance_t& bal) const { + amounts_map::const_iterator i, j; + for (i = amounts.begin(), j = bal.amounts.begin(); + i != amounts.end() && j != bal.amounts.end(); + i++, j++) { + if (! ((*i).first == (*j).first && + (*i).second == (*j).second)) + return false; + } + return i == amounts.end() && j == bal.amounts.end(); + } + bool operator==(const amount_t& amt) const { + if (amt.commodity()) + return amounts.size() == 1 && (*amounts.begin()).second == amt; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second == amt) + return true; + return false; + } + template + bool operator==(T val) const { + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second == val) + return true; + return false; + } + + bool operator!=(const balance_t& bal) const { + return ! (*this == bal); + } + bool operator!=(const amount_t& amt) const { + return ! (*this == amt); + } + template + bool operator!=(T val) const { + return ! (*this == val); + } + + // unary negation + void in_place_negate() { + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second = (*i).second.negate(); + } + balance_t negate() const { + balance_t temp = *this; + temp.in_place_negate(); + return temp; + } + balance_t operator-() const { + return negate(); + } + + // conversion operators + operator amount_t() const; + operator bool() const { + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second) + return true; + return false; + } + + bool realzero() const { + if (amounts.size() == 0) + return true; + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (! (*i).second.realzero()) + return false; + return true; + } + + amount_t amount(const commodity_t& commodity = + *commodity_t::null_commodity) const; + balance_t value(const moment_t& moment = now) const; + balance_t price() const; + moment_t date() const; + + balance_t + strip_annotations(const bool keep_price = amount_t::keep_price, + const bool keep_date = amount_t::keep_date, + const bool keep_tag = amount_t::keep_tag) const; + + void write(std::ostream& out, const int first_width, + const int latter_width = -1) const; + + void in_place_abs() { + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second = (*i).second.abs(); + } + balance_t abs() const { + balance_t temp = *this; + temp.in_place_abs(); + return temp; + } + + void in_place_reduce() { + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second.in_place_reduce(); + } + balance_t reduce() const { + balance_t temp(*this); + temp.in_place_reduce(); + return temp; + } + + void in_place_round() { + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second = (*i).second.round(); + } + balance_t round() const { + balance_t temp(*this); + temp.in_place_round(); + return temp; + } + + balance_t unround() const { + balance_t temp; + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if ((*i).second.commodity()) + temp += (*i).second.unround(); + return temp; + } +}; + +inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) { + bal.write(out, 12); + return out; +} + +class balance_pair_t +{ + public: + balance_t quantity; + balance_t * cost; + + // constructors + balance_pair_t() : cost(NULL) { + TRACE_CTOR(balance_pair_t, ""); + } + balance_pair_t(const balance_pair_t& bal_pair) + : quantity(bal_pair.quantity), cost(NULL) { + TRACE_CTOR(balance_pair_t, "copy"); + if (bal_pair.cost) + cost = new balance_t(*bal_pair.cost); + } + balance_pair_t(const balance_t& _quantity) + : quantity(_quantity), cost(NULL) { + TRACE_CTOR(balance_pair_t, "const balance_t&"); + } + balance_pair_t(const amount_t& _quantity) + : quantity(_quantity), cost(NULL) { + TRACE_CTOR(balance_pair_t, "const amount_t&"); + } + template + balance_pair_t(T val) : quantity(val), cost(NULL) { + TRACE_CTOR(balance_pair_t, "T"); + } + + // destructor + ~balance_pair_t() { + TRACE_DTOR(balance_pair_t); + if (cost) delete cost; + } + + // assignment operator + balance_pair_t& operator=(const balance_pair_t& bal_pair) { + if (this != &bal_pair) { + if (cost) { + delete cost; + cost = NULL; + } + quantity = bal_pair.quantity; + if (bal_pair.cost) + cost = new balance_t(*bal_pair.cost); + } + return *this; + } + balance_pair_t& operator=(const balance_t& bal) { + if (cost) { + delete cost; + cost = NULL; + } + quantity = bal; + return *this; + } + balance_pair_t& operator=(const amount_t& amt) { + if (cost) { + delete cost; + cost = NULL; + } + quantity = amt; + return *this; + } + template + balance_pair_t& operator=(T val) { + if (cost) { + delete cost; + cost = NULL; + } + quantity = val; + return *this; + } + + // in-place arithmetic + balance_pair_t& operator+=(const balance_pair_t& bal_pair) { + if (bal_pair.cost && ! cost) + cost = new balance_t(quantity); + quantity += bal_pair.quantity; + if (cost) + *cost += bal_pair.cost ? *bal_pair.cost : bal_pair.quantity; + return *this; + } + balance_pair_t& operator+=(const balance_t& bal) { + quantity += bal; + if (cost) + *cost += bal; + return *this; + } + balance_pair_t& operator+=(const amount_t& amt) { + quantity += amt; + if (cost) + *cost += amt; + return *this; + } + template + balance_pair_t& operator+=(T val) { + return *this += amount_t(val); + } + + balance_pair_t& operator-=(const balance_pair_t& bal_pair) { + if (bal_pair.cost && ! cost) + cost = new balance_t(quantity); + quantity -= bal_pair.quantity; + if (cost) + *cost -= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity; + return *this; + } + balance_pair_t& operator-=(const balance_t& bal) { + quantity -= bal; + if (cost) + *cost -= bal; + return *this; + } + balance_pair_t& operator-=(const amount_t& amt) { + quantity -= amt; + if (cost) + *cost -= amt; + return *this; + } + template + balance_pair_t& operator-=(T val) { + return *this -= amount_t(val); + } + + // simple arithmetic + balance_pair_t operator+(const balance_pair_t& bal_pair) const { + balance_pair_t temp = *this; + temp += bal_pair; + return temp; + } + balance_pair_t operator+(const balance_t& bal) const { + balance_pair_t temp = *this; + temp += bal; + return temp; + } + balance_pair_t operator+(const amount_t& amt) const { + balance_pair_t temp = *this; + temp += amt; + return temp; + } + template + balance_pair_t operator+(T val) const { + balance_pair_t temp = *this; + temp += val; + return temp; + } + + balance_pair_t operator-(const balance_pair_t& bal_pair) const { + balance_pair_t temp = *this; + temp -= bal_pair; + return temp; + } + balance_pair_t operator-(const balance_t& bal) const { + balance_pair_t temp = *this; + temp -= bal; + return temp; + } + balance_pair_t operator-(const amount_t& amt) const { + balance_pair_t temp = *this; + temp -= amt; + return temp; + } + template + balance_pair_t operator-(T val) const { + balance_pair_t temp = *this; + temp -= val; + return temp; + } + + // multiplication and division + balance_pair_t& operator*=(const balance_pair_t& bal_pair) { + if (bal_pair.cost && ! cost) + cost = new balance_t(quantity); + quantity *= bal_pair.quantity; + if (cost) + *cost *= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity; + return *this; + } + balance_pair_t& operator*=(const balance_t& bal) { + quantity *= bal; + if (cost) + *cost *= bal; + return *this; + } + balance_pair_t& operator*=(const amount_t& amt) { + quantity *= amt; + if (cost) + *cost *= amt; + return *this; + } + template + balance_pair_t& operator*=(T val) { + return *this *= amount_t(val); + } + + balance_pair_t& operator/=(const balance_pair_t& bal_pair) { + if (bal_pair.cost && ! cost) + cost = new balance_t(quantity); + quantity /= bal_pair.quantity; + if (cost) + *cost /= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity; + return *this; + } + balance_pair_t& operator/=(const balance_t& bal) { + quantity /= bal; + if (cost) + *cost /= bal; + return *this; + } + balance_pair_t& operator/=(const amount_t& amt) { + quantity /= amt; + if (cost) + *cost /= amt; + return *this; + } + template + balance_pair_t& operator/=(T val) { + return *this /= amount_t(val); + } + + balance_pair_t operator*(const balance_pair_t& bal_pair) const { + balance_pair_t temp = *this; + temp *= bal_pair; + return temp; + } + balance_pair_t operator*(const balance_t& bal) const { + balance_pair_t temp = *this; + temp *= bal; + return temp; + } + balance_pair_t operator*(const amount_t& amt) const { + balance_pair_t temp = *this; + temp *= amt; + return temp; + } + template + balance_pair_t operator*(T val) const { + balance_pair_t temp = *this; + temp *= val; + return temp; + } + + balance_pair_t operator/(const balance_pair_t& bal_pair) const { + balance_pair_t temp = *this; + temp /= bal_pair; + return temp; + } + balance_pair_t operator/(const balance_t& bal) const { + balance_pair_t temp = *this; + temp /= bal; + return temp; + } + balance_pair_t operator/(const amount_t& amt) const { + balance_pair_t temp = *this; + temp /= amt; + return temp; + } + template + balance_pair_t operator/(T val) const { + balance_pair_t temp = *this; + temp /= val; + return temp; + } + + // comparison + bool operator<(const balance_pair_t& bal_pair) const { + return quantity < bal_pair.quantity; + } + bool operator<(const balance_t& bal) const { + return quantity < bal; + } + bool operator<(const amount_t& amt) const { + return quantity < amt; + } + template + bool operator<(T val) const { + return quantity < val; + } + + bool operator<=(const balance_pair_t& bal_pair) const { + return quantity <= bal_pair.quantity; + } + bool operator<=(const balance_t& bal) const { + return quantity <= bal; + } + bool operator<=(const amount_t& amt) const { + return quantity <= amt; + } + template + bool operator<=(T val) const { + return quantity <= val; + } + + bool operator>(const balance_pair_t& bal_pair) const { + return quantity > bal_pair.quantity; + } + bool operator>(const balance_t& bal) const { + return quantity > bal; + } + bool operator>(const amount_t& amt) const { + return quantity > amt; + } + template + bool operator>(T val) const { + return quantity > val; + } + + bool operator>=(const balance_pair_t& bal_pair) const { + return quantity >= bal_pair.quantity; + } + bool operator>=(const balance_t& bal) const { + return quantity >= bal; + } + bool operator>=(const amount_t& amt) const { + return quantity >= amt; + } + template + bool operator>=(T val) const { + return quantity >= val; + } + + bool operator==(const balance_pair_t& bal_pair) const { + return quantity == bal_pair.quantity; + } + bool operator==(const balance_t& bal) const { + return quantity == bal; + } + bool operator==(const amount_t& amt) const { + return quantity == amt; + } + template + bool operator==(T val) const { + return quantity == val; + } + + bool operator!=(const balance_pair_t& bal_pair) const { + return ! (*this == bal_pair); + } + bool operator!=(const balance_t& bal) const { + return ! (*this == bal); + } + bool operator!=(const amount_t& amt) const { + return ! (*this == amt); + } + template + bool operator!=(T val) const { + return ! (*this == val); + } + + // unary negation + void in_place_negate() { + quantity = quantity.negate(); + if (cost) + *cost = cost->negate(); + } + balance_pair_t negate() const { + balance_pair_t temp = *this; + temp.in_place_negate(); + return temp; + } + balance_pair_t operator-() const { + return negate(); + } + + // test for non-zero (use ! for zero) + operator balance_t() const { + return quantity; + } + operator amount_t() const { + return quantity; + } + operator bool() const { + return quantity; + } + + bool realzero() const { + return ((! cost || cost->realzero()) && quantity.realzero()); + } + + void in_place_abs() { + quantity = quantity.abs(); + if (cost) + *cost = cost->abs(); + } + balance_pair_t abs() const { + balance_pair_t temp = *this; + temp.in_place_abs(); + return temp; + } + + amount_t amount(const commodity_t& commodity = + *commodity_t::null_commodity) const { + return quantity.amount(commodity); + } + balance_t value(const moment_t& moment = now) const { + return quantity.value(moment); + } + balance_t price() const { + return quantity.price(); + } + moment_t date() const { + return quantity.date(); + } + + balance_t + strip_annotations(const bool keep_price = amount_t::keep_price, + const bool keep_date = amount_t::keep_date, + const bool keep_tag = amount_t::keep_tag) const { + return quantity.strip_annotations(keep_price, keep_date, keep_tag); + } + + void write(std::ostream& out, const int first_width, + const int latter_width = -1) const { + quantity.write(out, first_width, latter_width); + } + + balance_pair_t& add(const amount_t& amt, + const amount_t * a_cost = NULL) { + if (a_cost && ! cost) + cost = new balance_t(quantity); + quantity += amt; + if (cost) + *cost += a_cost ? *a_cost : amt; + return *this; + } + + bool valid() { + return quantity.valid() && (! cost || cost->valid()); + } + + void in_place_reduce() { + quantity.in_place_reduce(); + if (cost) cost->in_place_reduce(); + } + balance_pair_t reduce() const { + balance_pair_t temp(*this); + temp.in_place_reduce(); + return temp; + } + + void in_place_round() { + quantity = quantity.round(); + if (cost) + *cost = cost->round(); + } + balance_pair_t round() const { + balance_pair_t temp(*this); + temp.in_place_round(); + return temp; + } + + balance_pair_t unround() { + balance_pair_t temp(quantity.unround()); + if (cost) + temp.cost = new balance_t(cost->unround()); + return temp; + } +}; + +inline std::ostream& operator<<(std::ostream& out, + const balance_pair_t& bal_pair) { + bal_pair.quantity.write(out, 12); + return out; +} + +} // namespace ledger + +#endif // _BALANCE_H diff --git a/src/binary.cc b/src/binary.cc new file mode 100644 index 00000000..d3238b5a --- /dev/null +++ b/src/binary.cc @@ -0,0 +1,1013 @@ +#include "binary.h" + +namespace ledger { + +#if 0 +static unsigned long binary_magic_number = 0xFFEED765; +#if defined(DEBUG_ON) +static unsigned long format_version = 0x00030000; +#else +static unsigned long format_version = 0x00030000; +#endif + +static account_t ** accounts; +static account_t ** accounts_next; +static unsigned int account_index; + +static commodity_base_t ** base_commodities; +static commodity_base_t ** base_commodities_next; +static unsigned int base_commodity_index; + +static commodity_t ** commodities; +static commodity_t ** commodities_next; +static unsigned int commodity_index; + +extern char * bigints; +extern char * bigints_next; +extern unsigned int bigints_index; +extern unsigned int bigints_count; +#endif + +void read_binary_bool(std::istream& in, bool& num) +{ + read_binary_guard(in, 0x2005); + unsigned char val; + in.read((char *)&val, sizeof(val)); + num = val == 1; + read_binary_guard(in, 0x2006); +} + +void read_binary_bool(char *& data, bool& num) +{ + read_binary_guard(data, 0x2005); + unsigned char val = *((unsigned char *) data); + data += sizeof(unsigned char); + num = val == 1; + read_binary_guard(data, 0x2006); +} + +void read_binary_string(std::istream& in, string& str) +{ + read_binary_guard(in, 0x3001); + + unsigned char len; + read_binary_number_nocheck(in, len); + if (len == 0xff) { + unsigned short slen; + read_binary_number_nocheck(in, slen); + char * buf = new char[slen + 1]; + in.read(buf, slen); + buf[slen] = '\0'; + str = buf; + delete[] buf; + } + else if (len) { + char buf[256]; + in.read(buf, len); + buf[len] = '\0'; + str = buf; + } else { + str = ""; + } + + read_binary_guard(in, 0x3002); +} + +void read_binary_string(char *& data, string& str) +{ + read_binary_guard(data, 0x3001); + + unsigned char len; + read_binary_number_nocheck(data, len); + if (len == 0xff) { + unsigned short slen; + read_binary_number_nocheck(data, slen); + str = string(data, slen); + data += slen; + } + else if (len) { + str = string(data, len); + data += len; + } + else { + str = ""; + } + + read_binary_guard(data, 0x3002); +} + +void read_binary_string(char *& data, string * str) +{ + read_binary_guard(data, 0x3001); + + unsigned char len; + read_binary_number_nocheck(data, len); + if (len == 0xff) { + unsigned short slen; + read_binary_number_nocheck(data, slen); + new(str) string(data, slen); + data += slen; + } + else if (len) { + new(str) string(data, len); + data += len; + } + else { + new(str) string(""); + } + + read_binary_guard(data, 0x3002); +} + +#if 0 +inline void read_binary_value(char *& data, value_t& val) +{ + val.type = static_cast(read_binary_long(data)); + + switch (val.type) { + case value_t::BOOLEAN: + read_binary_bool(data, *((bool *) val.data)); + break; + case value_t::INTEGER: + read_binary_long(data, *((long *) val.data)); + break; + case value_t::DATETIME: + read_binary_number(data, *((moment_t *) val.data)); + break; + case value_t::AMOUNT: + read_binary_amount(data, *((amount_t *) val.data)); + break; + + case value_t::BALANCE: + case value_t::BALANCE_PAIR: + assert(0); + break; + } +} + +inline void read_binary_mask(char *& data, mask_t *& mask) +{ + bool exclude; + read_binary_number(data, exclude); + string pattern; + read_binary_string(data, pattern); + + mask = new mask_t(pattern); + mask->exclude = exclude; +} + +inline void read_binary_transaction(char *& data, transaction_t * xact) +{ + read_binary_number(data, xact->_date); + read_binary_number(data, xact->_date_eff); + xact->account = accounts[read_binary_long(data) - 1]; + + unsigned char flag = read_binary_number(data); + if (flag == 0) { + read_binary_amount(data, xact->amount); + } + else if (flag == 1) { + string expr; + read_binary_string(data, expr); + xact->amount_expr = expr; + + repitem_t * item = + repitem_t::wrap(xact, static_cast(xact->entry->data)); + xact->data = item; + + xact->amount = valexpr_t(xact->amount_expr).calc(item).to_amount(); + } + + if (read_binary_bool(data)) { + xact->cost = new amount_t; + read_binary_amount(data, *xact->cost); + read_binary_string(data, xact->cost_expr); + } else { + xact->cost = NULL; + } + + read_binary_number(data, xact->state); + read_binary_number(data, xact->flags); + xact->flags |= TRANSACTION_BULK_ALLOC; + read_binary_string(data, &xact->note); + + xact->beg_pos = read_binary_long(data); + read_binary_long(data, xact->beg_line); + xact->end_pos = read_binary_long(data); + read_binary_long(data, xact->end_line); + + xact->data = NULL; +} + +inline void read_binary_entry_base(char *& data, entry_base_t * entry, + transaction_t *& xact_pool, bool& finalize) +{ + read_binary_long(data, entry->src_idx); + entry->beg_pos = read_binary_long(data); + read_binary_long(data, entry->beg_line); + entry->end_pos = read_binary_long(data); + read_binary_long(data, entry->end_line); + + bool ignore_calculated = read_binary_bool(data); + + for (unsigned long i = 0, count = read_binary_long(data); + i < count; + i++) { + new(xact_pool) transaction_t; + xact_pool->entry = static_cast(entry); + read_binary_transaction(data, xact_pool); + if (ignore_calculated && xact_pool->flags & TRANSACTION_CALCULATED) + finalize = true; + entry->add_transaction(xact_pool++); + } +} + +inline void read_binary_entry(char *& data, entry_t * entry, + transaction_t *& xact_pool, bool& finalize) +{ + entry->data = + repitem_t::wrap(entry, static_cast(entry->journal->data)); + + read_binary_entry_base(data, entry, xact_pool, finalize); + read_binary_number(data, entry->_date); + read_binary_number(data, entry->_date_eff); + read_binary_string(data, &entry->code); + read_binary_string(data, &entry->payee); +} + +inline void read_binary_auto_entry(char *& data, auto_entry_t * entry, + transaction_t *& xact_pool) +{ + bool ignore; + read_binary_entry_base(data, entry, xact_pool, ignore); + + string pred_str; + read_binary_string(data, &pred_str); + entry->predicate.parse(pred_str); +} + +inline void read_binary_period_entry(char *& data, period_entry_t * entry, + transaction_t *& xact_pool, bool& finalize) +{ + read_binary_entry_base(data, entry, xact_pool, finalize); + read_binary_string(data, &entry->period_string); + std::istringstream stream(entry->period_string); + entry->period.parse(stream); +} + +inline commodity_base_t * read_binary_commodity_base(char *& data) +{ + commodity_base_t * commodity = new commodity_base_t; + *base_commodities_next++ = commodity; + + read_binary_string(data, commodity->symbol); + read_binary_string(data, commodity->name); + read_binary_string(data, commodity->note); + read_binary_number(data, commodity->precision); + read_binary_number(data, commodity->flags); + + return commodity; +} + +inline void read_binary_commodity_base_extra(char *& data, + commodity_t::ident_t ident) +{ + commodity_base_t * commodity = base_commodities[ident]; + + bool read_history = false; + for (unsigned long i = 0, count = read_binary_long(data); + i < count; + i++) { + moment_t when; + read_binary_number(data, when); + amount_t amt; + read_binary_amount(data, amt); + + // Upon insertion, amt will be copied, which will cause the amount + // to be duplicated (and thus not lost when the journal's + // item_pool is deleted). + if (! commodity->history) + commodity->history = new commodity_base_t::history_t; + commodity->history->prices.insert(history_pair(when, amt)); + + read_history = true; + } + if (read_history) + read_binary_number(data, commodity->history->last_lookup); + + if (read_binary_bool(data)) { + amount_t amt; + read_binary_amount(data, amt); + commodity->smaller = new amount_t(amt); + } + + if (read_binary_bool(data)) { + amount_t amt; + read_binary_amount(data, amt); + commodity->larger = new amount_t(amt); + } +} + +inline commodity_t * read_binary_commodity(char *& data) +{ + commodity_t * commodity = new commodity_t; + *commodities_next++ = commodity; + + commodity->base = + base_commodities[read_binary_long(data) - 1]; + + read_binary_string(data, commodity->qualified_symbol); + commodity->annotated = false; + + return commodity; +} + +inline commodity_t * read_binary_commodity_annotated(char *& data) +{ + annotated_commodity_t * commodity = new annotated_commodity_t; + *commodities_next++ = commodity; + + commodity->base = + base_commodities[read_binary_long(data) - 1]; + + read_binary_string(data, commodity->qualified_symbol); + commodity->annotated = true; + + commodity->ptr = + commodities[read_binary_long(data) - 1]; + + // This read-and-then-assign causes a new amount to be allocated + // which does not live within the bulk allocation pool, since that + // pool will be deleted *before* the commodities are destroyed. + amount_t amt; + read_binary_amount(data, amt); + commodity->price = amt; + + read_binary_number(data, commodity->date); + read_binary_string(data, commodity->tag); + + return commodity; +} + +inline +account_t * read_binary_account(char *& data, journal_t * journal, + account_t * master = NULL) +{ + account_t * acct = new account_t(NULL); + *accounts_next++ = acct; + + acct->journal = journal; + + account_t::ident_t id; + read_binary_long(data, id); // parent id + if (id == 0xffffffff) + acct->parent = NULL; + else + acct->parent = accounts[id - 1]; + + read_binary_string(data, acct->name); + read_binary_string(data, acct->note); + read_binary_number(data, acct->depth); + + // If all of the subaccounts will be added to a different master + // account, throw away what we've learned about the recorded + // journal's own master account. + + if (master && acct != master) { + delete acct; + acct = master; + } + + for (account_t::ident_t i = 0, + count = read_binary_long(data); + i < count; + i++) { + account_t * child = read_binary_account(data, journal); + child->parent = acct; + assert(acct != child); + acct->add_account(child); + } + + return acct; +} + +unsigned int read_binary_journal(std::istream& in, + journal_t * journal, + account_t * master, + const string& original_file) +{ + account_index = + base_commodity_index = + commodity_index = 0; + + // Read in the files that participated in this journal, so that they + // can be checked for changes on reading. + + if (! original_file.empty()) { + for (unsigned short i = 0, + count = read_binary_number(in); + i < count; + i++) { + string path = read_binary_string(in); + std::time_t old_mtime; + read_binary_number(in, old_mtime); + struct stat info; + stat(path.c_str(), &info); + if (std::difftime(info.st_mtime, old_mtime) > 0) + return 0; + + journal->sources.push_back(path); + } + + // Make sure that the cache uses the same price database, + // otherwise it means that LEDGER_PRICE_DB has been changed, and + // we should ignore this cache file. + if (read_binary_string(in) != journal->price_db) + return 0; + } + + // Read all of the data in at once, so that we're just dealing with + // a big data buffer. + + unsigned long data_size = read_binary_number(in); + + char * data_pool = new char[data_size]; + char * data = data_pool; + in.read(data, data_size); + + // Read in the accounts + + account_t::ident_t a_count = read_binary_long(data); + accounts = accounts_next = new account_t *[a_count]; + + assert(journal->master); + delete journal->master; + journal->master = read_binary_account(data, journal, master); + + if (read_binary_bool(data)) + journal->basket = accounts[read_binary_long(data) - 1]; + + // Allocate the memory needed for the entries and transactions in + // one large block, which is then chopped up and custom constructed + // as necessary. + + unsigned long count = read_binary_long(data); + unsigned long auto_count = read_binary_long(data); + unsigned long period_count = read_binary_long(data); + unsigned long xact_count = read_binary_number(data); + unsigned long bigint_count = read_binary_number(data); + + std::size_t pool_size = (sizeof(entry_t) * count + + sizeof(transaction_t) * xact_count + + sizeof_bigint_t() * bigint_count); + + char * item_pool = new char[pool_size]; + + journal->item_pool = item_pool; + journal->item_pool_end = item_pool + pool_size; + + entry_t * entry_pool = (entry_t *) item_pool; + transaction_t * xact_pool = (transaction_t *) (item_pool + + sizeof(entry_t) * count); + bigints_index = 0; + bigints = bigints_next = (item_pool + sizeof(entry_t) * count + + sizeof(transaction_t) * xact_count); + + // Read in the base commodities and then derived commodities + + commodity_base_t::ident_t bc_count = + read_binary_long(data); + base_commodities = base_commodities_next = new commodity_base_t *[bc_count]; + + for (commodity_base_t::ident_t i = 0; i < bc_count; i++) { + commodity_base_t * commodity = read_binary_commodity_base(data); + + std::pair result = + commodity_base_t::commodities.insert + (base_commodities_pair(commodity->symbol, commodity)); + if (! result.second) { + base_commodities_map::iterator c = + commodity_base_t::commodities.find(commodity->symbol); + + // It's possible the user might have used a commodity in a value + // expression passed to an option, we'll just override the + // flags, but keep the commodity pointer intact. + if (c == commodity_base_t::commodities.end()) + throw new error(string("Failed to read base commodity from cache: ") + + commodity->symbol); + + (*c).second->name = commodity->name; + (*c).second->note = commodity->note; + (*c).second->precision = commodity->precision; + (*c).second->flags = commodity->flags; + if ((*c).second->smaller) + delete (*c).second->smaller; + (*c).second->smaller = commodity->smaller; + if ((*c).second->larger) + delete (*c).second->larger; + (*c).second->larger = commodity->larger; + + *(base_commodities_next - 1) = (*c).second; + delete commodity; + } + } + + commodity_t::ident_t c_count = read_binary_long(data); + commodities = commodities_next = new commodity_t *[c_count]; + + for (commodity_t::ident_t i = 0; i < c_count; i++) { + commodity_t * commodity; + string mapping_key; + + if (! read_binary_bool(data)) { + commodity = read_binary_commodity(data); + mapping_key = commodity->base->symbol; + } else { + read_binary_string(data, mapping_key); + commodity = read_binary_commodity_annotated(data); + } + + std::pair result = + commodity_t::commodities.insert(commodities_pair + (mapping_key, commodity)); + if (! result.second) { + commodities_map::iterator c = + commodity_t::commodities.find(mapping_key); + if (c == commodity_t::commodities.end()) + throw new error(string("Failed to read commodity from cache: ") + + commodity->symbol()); + + *(commodities_next - 1) = (*c).second; + delete commodity; + } + } + + for (commodity_base_t::ident_t i = 0; i < bc_count; i++) + read_binary_commodity_base_extra(data, i); + + commodity_t::ident_t ident; + read_binary_long(data, ident); + if (ident == 0xffffffff || ident == 0) + commodity_t::default_commodity = NULL; + else + commodity_t::default_commodity = commodities[ident - 1]; + + // Read in the entries and transactions + + for (unsigned long i = 0; i < count; i++) { + new(entry_pool) entry_t; + bool finalize = false; + entry_pool->journal = journal; + read_binary_entry(data, entry_pool, xact_pool, finalize); + if (finalize && ! entry_pool->finalize()) + continue; + journal->entries.push_back(entry_pool++); + } + + for (unsigned long i = 0; i < auto_count; i++) { + auto_entry_t * auto_entry = new auto_entry_t; + read_binary_auto_entry(data, auto_entry, xact_pool); + auto_entry->journal = journal; + journal->auto_entries.push_back(auto_entry); + } + + for (unsigned long i = 0; i < period_count; i++) { + period_entry_t * period_entry = new period_entry_t; + bool finalize = false; + read_binary_period_entry(data, period_entry, xact_pool, finalize); + period_entry->journal = journal; + if (finalize && ! period_entry->finalize()) + continue; + journal->period_entries.push_back(period_entry); + } + + // Clean up and return the number of entries read + + delete[] accounts; + delete[] commodities; + delete[] data_pool; + + VALIDATE(journal->valid()); + + return count; +} +#endif + +#if 0 +bool binary_parser_t::test(std::istream& in) const +{ + if (read_binary_number_nocheck(in) == binary_magic_number && + read_binary_number_nocheck(in) == format_version) + return true; + + in.clear(); + in.seekg(0, std::ios::beg); + return false; +} + +unsigned int binary_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const string * original_file) +{ +#if 0 + return read_binary_journal(in, journal, master, + original_file ? *original_file : ""); +#endif +} +#endif + + +void write_binary_bool(std::ostream& out, bool num) +{ + write_binary_guard(out, 0x2005); + unsigned char val = num ? 1 : 0; + out.write((char *)&val, sizeof(val)); + write_binary_guard(out, 0x2006); +} + +void write_binary_string(std::ostream& out, const string& str) +{ + write_binary_guard(out, 0x3001); + + unsigned long len = str.length(); + if (len > 255) { + assert(len < 65536); + write_binary_number_nocheck(out, 0xff); + write_binary_number_nocheck(out, len); + } else { + write_binary_number_nocheck(out, len); + } + + if (len) + out.write(str.c_str(), len); + + write_binary_guard(out, 0x3002); +} + +#if 0 +void write_binary_value(std::ostream& out, const value_t& val) +{ + write_binary_long(out, (int)val.type); + + switch (val.type) { + case value_t::BOOLEAN: + write_binary_bool(out, *((bool *) val.data)); + break; + case value_t::INTEGER: + write_binary_long(out, *((long *) val.data)); + break; + case value_t::DATETIME: + write_binary_number(out, *((moment_t *) val.data)); + break; + case value_t::AMOUNT: + write_binary_amount(out, *((amount_t *) val.data)); + break; + + case value_t::BALANCE: + case value_t::BALANCE_PAIR: + throw new error("Cannot write a balance to the binary cache"); + } +} + +void write_binary_mask(std::ostream& out, mask_t * mask) +{ + write_binary_number(out, mask->exclude); + write_binary_string(out, mask->pattern); +} + +void write_binary_transaction(std::ostream& out, transaction_t * xact, + bool ignore_calculated) +{ + write_binary_number(out, xact->_date); + write_binary_number(out, xact->_date_eff); + write_binary_long(out, xact->account->ident); + + if (ignore_calculated && xact->flags & TRANSACTION_CALCULATED) { + write_binary_number(out, 0); + write_binary_amount(out, amount_t()); + } + else if (! xact->amount_expr.empty()) { + write_binary_number(out, 1); + write_binary_string(out, xact->amount_expr); + } + else { + write_binary_number(out, 0); + write_binary_amount(out, xact->amount); + } + + if (xact->cost && + (! (ignore_calculated && xact->flags & TRANSACTION_CALCULATED))) { + write_binary_bool(out, true); + write_binary_amount(out, *xact->cost); + write_binary_string(out, xact->cost_expr); + } else { + write_binary_bool(out, false); + } + + write_binary_number(out, xact->state); + write_binary_number(out, xact->flags); + write_binary_string(out, xact->note); + + write_binary_long(out, xact->beg_pos); + write_binary_long(out, xact->beg_line); + write_binary_long(out, xact->end_pos); + write_binary_long(out, xact->end_line); +} + +void write_binary_entry_base(std::ostream& out, entry_base_t * entry) +{ + write_binary_long(out, entry->src_idx); + write_binary_long(out, entry->beg_pos); + write_binary_long(out, entry->beg_line); + write_binary_long(out, entry->end_pos); + write_binary_long(out, entry->end_line); + + bool ignore_calculated = false; + for (transactions_list::const_iterator i = entry->transactions.begin(); + i != entry->transactions.end(); + i++) + if (! (*i)->amount_expr.empty()) { + ignore_calculated = true; + break; + } + + write_binary_bool(out, ignore_calculated); + + write_binary_long(out, entry->transactions.size()); + for (transactions_list::const_iterator i = entry->transactions.begin(); + i != entry->transactions.end(); + i++) + write_binary_transaction(out, *i, ignore_calculated); +} + +void write_binary_entry(std::ostream& out, entry_t * entry) +{ + write_binary_entry_base(out, entry); + write_binary_number(out, entry->_date); + write_binary_number(out, entry->_date_eff); + write_binary_string(out, entry->code); + write_binary_string(out, entry->payee); +} + +void write_binary_auto_entry(std::ostream& out, auto_entry_t * entry) +{ + write_binary_entry_base(out, entry); + write_binary_string(out, entry->predicate.expr); +} + +void write_binary_period_entry(std::ostream& out, period_entry_t * entry) +{ + write_binary_entry_base(out, entry); + write_binary_string(out, entry->period_string); +} + +void write_binary_commodity_base(std::ostream& out, commodity_base_t * commodity) +{ + commodity->ident = ++base_commodity_index; + + write_binary_string(out, commodity->symbol); + write_binary_string(out, commodity->name); + write_binary_string(out, commodity->note); + write_binary_number(out, commodity->precision); + write_binary_number(out, commodity->flags); +} + +void write_binary_commodity_base_extra(std::ostream& out, + commodity_base_t * commodity) +{ + if (commodity->history && commodity->history->bogus_time) + commodity->remove_price(commodity->history->bogus_time); + + if (! commodity->history) { + write_binary_long(out, 0); + } else { + write_binary_long(out, commodity->history->prices.size()); + for (history_map::const_iterator i = commodity->history->prices.begin(); + i != commodity->history->prices.end(); + i++) { + write_binary_number(out, (*i).first); + write_binary_amount(out, (*i).second); + } + write_binary_number(out, commodity->history->last_lookup); + } + + if (commodity->smaller) { + write_binary_bool(out, true); + write_binary_amount(out, *commodity->smaller); + } else { + write_binary_bool(out, false); + } + + if (commodity->larger) { + write_binary_bool(out, true); + write_binary_amount(out, *commodity->larger); + } else { + write_binary_bool(out, false); + } +} + +void write_binary_commodity(std::ostream& out, commodity_t * commodity) +{ + commodity->ident = ++commodity_index; + + write_binary_long(out, commodity->base->ident); + write_binary_string(out, commodity->qualified_symbol); +} + +void write_binary_commodity_annotated(std::ostream& out, + commodity_t * commodity) +{ + commodity->ident = ++commodity_index; + + write_binary_long(out, commodity->base->ident); + write_binary_string(out, commodity->qualified_symbol); + + annotated_commodity_t * ann_comm = + static_cast(commodity); + + write_binary_long(out, ann_comm->base->ident); + write_binary_amount(out, ann_comm->price); + write_binary_number(out, ann_comm->date); + write_binary_string(out, ann_comm->tag); +} + +static inline account_t::ident_t count_accounts(account_t * account) +{ + account_t::ident_t count = 1; + + for (accounts_map::iterator i = account->accounts.begin(); + i != account->accounts.end(); + i++) + count += count_accounts((*i).second); + + return count; +} + +void write_binary_account(std::ostream& out, account_t * account) +{ + account->ident = ++account_index; + + if (account->parent) + write_binary_long(out, account->parent->ident); + else + write_binary_long(out, 0xffffffff); + + write_binary_string(out, account->name); + write_binary_string(out, account->note); + write_binary_number(out, account->depth); + + write_binary_long(out, account->accounts.size()); + for (accounts_map::iterator i = account->accounts.begin(); + i != account->accounts.end(); + i++) + write_binary_account(out, (*i).second); +} + +void write_binary_journal(std::ostream& out, journal_t * journal) +{ + account_index = + base_commodity_index = + commodity_index = 0; + + write_binary_number_nocheck(out, binary_magic_number); + write_binary_number_nocheck(out, format_version); + + // Write out the files that participated in this journal, so that + // they can be checked for changes on reading. + + if (journal->sources.empty()) { + write_binary_number(out, 0); + } else { + write_binary_number(out, journal->sources.size()); + for (strings_list::const_iterator i = journal->sources.begin(); + i != journal->sources.end(); + i++) { + write_binary_string(out, *i); + struct stat info; + stat((*i).c_str(), &info); + write_binary_number(out, std::time_t(info.st_mtime)); + } + + // Write out the price database that relates to this data file, so + // that if it ever changes the cache can be invalidated. + write_binary_string(out, journal->price_db); + } + + ostream_pos_type data_val = out.tellp(); + write_binary_number(out, 0); + + // Write out the accounts + + write_binary_long(out, count_accounts(journal->master)); + write_binary_account(out, journal->master); + + if (journal->basket) { + write_binary_bool(out, true); + write_binary_long(out, journal->basket->ident); + } else { + write_binary_bool(out, false); + } + + // Write out the number of entries, transactions, and amounts + + write_binary_long(out, journal->entries.size()); + write_binary_long(out, journal->auto_entries.size()); + write_binary_long(out, journal->period_entries.size()); + + ostream_pos_type xacts_val = out.tellp(); + write_binary_number(out, 0); + + ostream_pos_type bigints_val = out.tellp(); + write_binary_number(out, 0); + + bigints_count = 0; + + // Write out the commodities + + write_binary_long + (out, commodity_base_t::commodities.size()); + + for (base_commodities_map::const_iterator i = + commodity_base_t::commodities.begin(); + i != commodity_base_t::commodities.end(); + i++) + write_binary_commodity_base(out, (*i).second); + + write_binary_long + (out, commodity_t::commodities.size()); + + for (commodities_map::const_iterator i = commodity_t::commodities.begin(); + i != commodity_t::commodities.end(); + i++) { + if (! (*i).second->annotated) { + write_binary_bool(out, false); + write_binary_commodity(out, (*i).second); + } + } + + for (commodities_map::const_iterator i = commodity_t::commodities.begin(); + i != commodity_t::commodities.end(); + i++) { + if ((*i).second->annotated) { + write_binary_bool(out, true); + write_binary_string(out, (*i).first); // the mapping key + write_binary_commodity_annotated(out, (*i).second); + } + } + + // Write out the history and smaller/larger convertible links after + // both the base and the main commodities have been written, since + // the amounts in both will refer to the mains. + + for (base_commodities_map::const_iterator i = + commodity_base_t::commodities.begin(); + i != commodity_base_t::commodities.end(); + i++) + write_binary_commodity_base_extra(out, (*i).second); + + if (commodity_t::default_commodity) + write_binary_long(out, commodity_t::default_commodity->ident); + else + write_binary_long(out, 0xffffffff); + + // Write out the entries and transactions + + unsigned long xact_count = 0; + + for (entries_list::const_iterator i = journal->entries.begin(); + i != journal->entries.end(); + i++) { + write_binary_entry(out, *i); + xact_count += (*i)->transactions.size(); + } + + for (auto_entries_list::const_iterator i = journal->auto_entries.begin(); + i != journal->auto_entries.end(); + i++) { + write_binary_auto_entry(out, *i); + xact_count += (*i)->transactions.size(); + } + + for (period_entries_list::const_iterator i = journal->period_entries.begin(); + i != journal->period_entries.end(); + i++) { + write_binary_period_entry(out, *i); + xact_count += (*i)->transactions.size(); + } + + // Back-patch the count for amounts + + unsigned long data_size = (((unsigned long) out.tellp()) - + ((unsigned long) data_val) - + sizeof(unsigned long)); + out.seekp(data_val); + write_binary_number(out, data_size); + out.seekp(xacts_val); + write_binary_number(out, xact_count); + out.seekp(bigints_val); + write_binary_number(out, bigints_count); +} +#endif + +} // namespace ledger diff --git a/src/binary.h b/src/binary.h new file mode 100644 index 00000000..528217fa --- /dev/null +++ b/src/binary.h @@ -0,0 +1,252 @@ +#ifndef _BINARY_H +#define _BINARY_H + +#include "parser.h" + +namespace ledger { + +#if 0 +class binary_parser_t : public parser_t +{ + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); +}; +#endif + +template +inline void read_binary_number_nocheck(std::istream& in, T& num) { + in.read((char *)&num, sizeof(num)); +} + +template +inline void read_binary_number_nocheck(char *& data, T& num) { + num = *((T *) data); + data += sizeof(T); +} + +template +inline T read_binary_number_nocheck(std::istream& in) { + T num; + read_binary_number_nocheck(in, num); + return num; +} + +template +inline T read_binary_number_nocheck(char *& data) { + T num; + read_binary_number_nocheck(data, num); + return num; +} + +#if DEBUG_LEVEL >= ALPHA +#define read_binary_guard(in, id) \ + if (read_binary_number_nocheck(in) != id) \ + assert(0); +#else +#define read_binary_guard(in, id) +#endif + +template +inline void read_binary_number(std::istream& in, T& num) { + read_binary_guard(in, 0x2003); + in.read((char *)&num, sizeof(num)); + read_binary_guard(in, 0x2004); +} + +template +inline void read_binary_number(char *& data, T& num) { + read_binary_guard(data, 0x2003); + num = *((T *) data); + data += sizeof(T); + read_binary_guard(data, 0x2004); +} + +template +inline T read_binary_number(std::istream& in) { + T num; + read_binary_number(in, num); + return num; +} + +template +inline T read_binary_number(char *& data) { + T num; + read_binary_number(data, num); + return num; +} + +void read_binary_bool(std::istream& in, bool& num); +void read_binary_bool(char *& data, bool& num); + +inline bool read_binary_bool(std::istream& in) { + bool num; + read_binary_bool(in, num); + return num; +} + +inline bool read_binary_bool(char *& data) { + bool num; + read_binary_bool(data, num); + return num; +} + +template +void read_binary_long(std::istream& in, T& num) +{ + read_binary_guard(in, 0x2001); + + unsigned char len; + read_binary_number_nocheck(in, len); + + num = 0; + unsigned char temp; + if (len > 3) { + read_binary_number_nocheck(in, temp); + num |= ((unsigned long)temp) << 24; + } + if (len > 2) { + read_binary_number_nocheck(in, temp); + num |= ((unsigned long)temp) << 16; + } + if (len > 1) { + read_binary_number_nocheck(in, temp); + num |= ((unsigned long)temp) << 8; + } + + read_binary_number_nocheck(in, temp); + num |= ((unsigned long)temp); + + read_binary_guard(in, 0x2002); +} + +template +void read_binary_long(char *& data, T& num) +{ + read_binary_guard(data, 0x2001); + + unsigned char len; + read_binary_number_nocheck(data, len); + + num = 0; + unsigned char temp; + if (len > 3) { + read_binary_number_nocheck(data, temp); + num |= ((unsigned long)temp) << 24; + } + if (len > 2) { + read_binary_number_nocheck(data, temp); + num |= ((unsigned long)temp) << 16; + } + if (len > 1) { + read_binary_number_nocheck(data, temp); + num |= ((unsigned long)temp) << 8; + } + + read_binary_number_nocheck(data, temp); + num |= ((unsigned long)temp); + + read_binary_guard(data, 0x2002); +} + +template +inline T read_binary_long(std::istream& in) { + T num; + read_binary_long(in, num); + return num; +} + +template +inline T read_binary_long(char *& data) { + T num; + read_binary_long(data, num); + return num; +} + +void read_binary_string(std::istream& in, string& str); +void read_binary_string(char *& data, string& str); +void read_binary_string(char *& data, string * str); + +inline string read_binary_string(std::istream& in) { + string temp; + read_binary_string(in, temp); + return temp; +} + +inline string read_binary_string(char *& data) { + string temp; + read_binary_string(data, temp); + return temp; +} + + +template +inline void write_binary_number_nocheck(std::ostream& out, T num) { + out.write((char *)&num, sizeof(num)); +} + +#if DEBUG_LEVEL >= ALPHA +#define write_binary_guard(out, id) \ + write_binary_number_nocheck(out, id) +#else +#define write_binary_guard(in, id) +#endif + +template +inline void write_binary_number(std::ostream& out, T num) { + write_binary_guard(out, 0x2003); + out.write((char *)&num, sizeof(num)); + write_binary_guard(out, 0x2004); +} + +void write_binary_bool(std::ostream& out, bool num); + +template +void write_binary_long(std::ostream& out, T num) +{ + write_binary_guard(out, 0x2001); + + unsigned char len = 4; + if (((unsigned long)num) < 0x00000100UL) + len = 1; + else if (((unsigned long)num) < 0x00010000UL) + len = 2; + else if (((unsigned long)num) < 0x01000000UL) + len = 3; + write_binary_number_nocheck(out, len); + + unsigned char temp; + if (len > 3) { + temp = (((unsigned long)num) & 0xFF000000UL) >> 24; + write_binary_number_nocheck(out, temp); + } + if (len > 2) { + temp = (((unsigned long)num) & 0x00FF0000UL) >> 16; + write_binary_number_nocheck(out, temp); + } + if (len > 1) { + temp = (((unsigned long)num) & 0x0000FF00UL) >> 8; + write_binary_number_nocheck(out, temp); + } + + temp = (((unsigned long)num) & 0x000000FFUL); + write_binary_number_nocheck(out, temp); + + write_binary_guard(out, 0x2002); +} + +void write_binary_string(std::ostream& out, const string& str); + + + +#if 0 +void write_binary_journal(std::ostream& out, journal_t * journal); +#endif + +} // namespace ledger + +#endif // _BINARY_H diff --git a/src/context.h b/src/context.h new file mode 100644 index 00000000..3851d073 --- /dev/null +++ b/src/context.h @@ -0,0 +1,28 @@ +#ifndef _CONTEXT_H +#define _CONTEXT_H + +namespace ledger { + +class context +{ +public: + string context; // ex: 'While parsing file "%R" at line %L' + + string resource; // ex: ledger.dat + long linenum_beg; // ex: 1010 + long linenum_end; // ex: 1010 + long colnum_beg; // ex: 8 + long colnum_end; // ex: 8 + long position_beg; + long position_end; + + string text; // ex: (The multi-line text of an entry) + long linenum_beg_off; // ex: 2 / -1 means start at beginning + long linenum_end_off; // ex: 2 / -1 means start at beginning + long colnum_beg_off; // ex: 8 / -1 means start + long colnum_end_off; // ex: 8 / -1 means start +}; + +} // namespace ledger + +#endif // _CONTEXT_H diff --git a/src/csv.cc b/src/csv.cc new file mode 100644 index 00000000..e69de29b diff --git a/src/csv.h b/src/csv.h new file mode 100644 index 00000000..e69de29b diff --git a/src/derive.cc b/src/derive.cc new file mode 100644 index 00000000..6586c1f4 --- /dev/null +++ b/src/derive.cc @@ -0,0 +1,178 @@ +#include "derive.h" +#include "mask.h" + +namespace ledger { + +void derive_command::operator() + (value_t& result, xml::xpath_t::scope_t * locals) +{ +#if 0 + std::ostream& out = *get_ptr(locals, 0); + repitem_t * items = get_ptr(locals, 1); + strings_list& args = *get_ptr(locals, 2); + + std::auto_ptr added(new entry_t); + + entry_t * matching = NULL; + + strings_list::iterator i = args.begin(); + + added->_date = *i++; + if (i == args.end()) + throw new error("Too few arguments to 'entry'"); + + mask_t regexp(*i++); + + entries_list::reverse_iterator j; + for (j = journal.entries.rbegin(); + j != journal.entries.rend(); + j++) + if (regexp.match((*j)->payee)) { + matching = *j; + break; + } + + added->payee = matching ? matching->payee : regexp.pattern; + + if (! matching) { + account_t * acct; + if (i == args.end() || ((*i)[0] == '-' || std::isdigit((*i)[0]))) { + acct = journal.find_account("Expenses"); + } + else if (i != args.end()) { + acct = journal.find_account_re(*i); + if (! acct) + acct = journal.find_account(*i); + assert(acct); + i++; + } + + if (i == args.end()) { + added->add_transaction(new transaction_t(acct)); + } else { + transaction_t * xact = new transaction_t(acct, amount_t(*i++)); + added->add_transaction(xact); + + if (! xact->amount.commodity()) { + // If the amount has no commodity, we can determine it given + // the account by creating a final for the account and then + // checking if it contains only a single commodity. An + // account to which only dollars are applied would imply that + // dollars are wanted now too. + + std::auto_ptr > formatter; + formatter.reset(new set_account_value); + walk_entries(journal.entries, *formatter.get()); + formatter->flush(); + + sum_accounts(*journal.master); + + value_t total = account_xdata(*acct).total; + if (total.type == value_t::AMOUNT) + xact->amount.set_commodity(((amount_t *) total.data)->commodity()); + } + } + + if (journal.basket) + acct = journal.basket; + else + acct = journal.find_account("Equity"); + + added->add_transaction(new transaction_t(acct)); + } + else if (i == args.end()) { + // If no argument were given but the payee, assume the user wants + // to see the same transaction as last time. + added->code = matching->code; + + for (transactions_list::iterator k = matching->transactions.begin(); + k != matching->transactions.end(); + k++) + added->add_transaction(new transaction_t(**k)); + } + else if ((*i)[0] == '-' || std::isdigit((*i)[0])) { + transaction_t * m_xact, * xact, * first; + m_xact = matching->transactions.front(); + + first = xact = new transaction_t(m_xact->account, amount_t(*i++)); + added->add_transaction(xact); + + if (! xact->amount.commodity()) + xact->amount.set_commodity(m_xact->amount.commodity()); + + m_xact = matching->transactions.back(); + + xact = new transaction_t(m_xact->account, - first->amount); + added->add_transaction(xact); + + if (i != args.end()) { + account_t * acct = journal.find_account_re(*i); + if (! acct) + acct = journal.find_account(*i); + assert(acct); + added->transactions.back()->account = acct; + } + } + else { + while (i != args.end()) { + string& re_pat(*i++); + account_t * acct = NULL; + amount_t * amt = NULL; + + mask_t acct_regex(re_pat); + + for (; j != journal.entries.rend(); j++) + if (regexp.match((*j)->payee)) { + entry_t * entry = *j; + for (transactions_list::const_iterator x = + entry->transactions.begin(); + x != entry->transactions.end(); + x++) + if (acct_regex.match((*x)->account->fullname())) { + acct = (*x)->account; + amt = &(*x)->amount; + matching = entry; + goto found; + } + } + + found: + if (! acct) + acct = journal.find_account_re(re_pat); + if (! acct) + acct = journal.find_account(re_pat); + + transaction_t * xact; + if (i == args.end()) { + if (amt) + xact = new transaction_t(acct, *amt); + else + xact = new transaction_t(acct); + } else { + xact = new transaction_t(acct, amount_t(*i++)); + if (! xact->amount.commodity()) { + if (amt) + xact->amount.set_commodity(amt->commodity()); + else if (commodity_t::default_commodity) + xact->amount.set_commodity(*commodity_t::default_commodity); + } + } + added->add_transaction(xact); + } + + assert(matching->transactions.back()->account); + if (account_t * draw_acct = matching->transactions.back()->account) + added->add_transaction(new transaction_t(draw_acct)); + } + + done: + if (! run_hooks(journal.entry_finalize_hooks, *added, false) || + ! added->finalize() || + ! run_hooks(journal.entry_finalize_hooks, *added, true)) + throw new error("Failed to finalize derived entry (check commodities)"); + + return added.release(); +#endif +} + +} // namespace ledger diff --git a/src/derive.h b/src/derive.h new file mode 100644 index 00000000..c0607fc2 --- /dev/null +++ b/src/derive.h @@ -0,0 +1,18 @@ +#ifndef _DERIVE_H +#define _DERIVE_H + +#include "journal.h" + +namespace ledger { + +class derive_command : public xml::xpath_t::functor_t +{ + public: + derive_command() : xml::xpath_t::functor_t("entry", true) {} + + virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals); +}; + +} // namespace ledger + +#endif // _DERIVE_H diff --git a/src/emacs.cc b/src/emacs.cc new file mode 100644 index 00000000..e69de29b diff --git a/src/emacs.h b/src/emacs.h new file mode 100644 index 00000000..e69de29b diff --git a/src/error.h b/src/error.h new file mode 100644 index 00000000..5cbf54fb --- /dev/null +++ b/src/error.h @@ -0,0 +1,151 @@ +#ifndef _ERROR_H +#define _ERROR_H + +#import "context.h" + +namespace ledger { + +class exception : public std::exception +{ +protected: + string reason; + +public: + std::list context_stack; + + exception(const string& _reason, + const context& immediate_ctxt) throw() + : reason(_reason) { + EXCEPTION(reason); + push(immediate_ctxt); + } + + virtual ~exception() throw() {} + + void push(const context& intermediate_ctxt) throw() { + context_stack.push_front(intermediate_ctxt); + } + + void write(std::ostream& out) const throw() { +#if 0 + for (std::list::const_iterator + i = context_stack.begin(); + i != context_stack.end(); + i++) + (*i).write(out); +#endif + } + + const char * what() const throw() { + return reason.c_str(); + } +}; + +#define DECLARE_EXCEPTION(name) \ + class name : public exception { \ + public: \ + name(const string& _reason, \ + const context& immediate_ctxt) throw() \ + : exception(_reason, immediate_ctxt) {} \ + } + +#if 0 + +class error_context +{ + public: + string desc; + + error_context(const string& _desc) throw() : desc(_desc) {} + virtual ~error_context() throw() {} + virtual void describe(std::ostream& out) const throw() { + if (! desc.empty()) + out << desc << std::endl; + } +}; + +class file_context : public error_context +{ + protected: + string file; + unsigned long line; + public: + file_context(const string& _file, unsigned long _line, + const string& _desc = "") throw() + : error_context(_desc), file(_file), line(_line) {} + virtual ~file_context() throw() {} + + virtual void describe(std::ostream& out) const throw() { + if (! desc.empty()) + out << desc << " "; + + out << "\"" << file << "\", line " << line << ": "; + } +}; + +class line_context : public error_context { + public: + string line; + long pos; + + line_context(const string& _line, long _pos, + const string& _desc = "") throw() + : error_context(_desc), line(_line), pos(_pos) {} + virtual ~line_context() throw() {} + + virtual void describe(std::ostream& out) const throw() { + if (! desc.empty()) + out << desc << std::endl; + + out << " " << line << std::endl << " "; + long idx = pos < 0 ? line.length() - 1 : pos; + for (int i = 0; i < idx; i++) + out << " "; + out << "^" << std::endl; + } +}; + +class error : public str_exception { + public: + error(const string& _reason, error_context * _ctxt = NULL) throw() + : str_exception(_reason, _ctxt) {} + virtual ~error() throw() {} +}; + +class fatal : public str_exception { + public: + fatal(const string& _reason, error_context * _ctxt = NULL) throw() + : str_exception(_reason, _ctxt) {} + virtual ~fatal() throw() {} +}; + +class fatal_assert : public fatal { + public: + fatal_assert(const string& _reason, error_context * _ctxt = NULL) throw() + : fatal(string("assertion failed '") + _reason + "'", _ctxt) {} + virtual ~fatal_assert() throw() {} +}; + +#endif // 0 + +inline void unexpected(char c, char wanted) +{ +#if 0 + if ((unsigned char) c == 0xff) { + if (wanted) + throw new error(string("Missing '") + wanted + "'"); + else + throw new error("Unexpected end of input"); + } else { + if (wanted) + throw new error(string("Invalid char '") + c + + "' (wanted '" + wanted + "')"); + else + throw new error(string("Invalid char '") + c + "'"); + } +#endif +} + +} // namespace ledger + +#endif // _ERROR_H diff --git a/src/fdstream.hpp b/src/fdstream.hpp new file mode 100644 index 00000000..a74a5781 --- /dev/null +++ b/src/fdstream.hpp @@ -0,0 +1,184 @@ +/* The following code declares classes to read from and write to + * file descriptore or file handles. + * + * See + * http://www.josuttis.com/cppcode + * for details and the latest version. + * + * - open: + * - integrating BUFSIZ on some systems? + * - optimized reading of multiple characters + * - stream for reading AND writing + * - i18n + * + * (C) Copyright Nicolai M. Josuttis 2001. + * Permission to copy, use, modify, sell and distribute this software + * is granted provided this copyright notice appears in all copies. + * This software is provided "as is" without express or implied + * warranty, and with no claim as to its suitability for any purpose. + * + * Version: Jul 28, 2002 + * History: + * Jul 28, 2002: bugfix memcpy() => memmove() + * fdinbuf::underflow(): cast for return statements + * Aug 05, 2001: first public version + */ +#ifndef BOOST_FDSTREAM_HPP +#define BOOST_FDSTREAM_HPP + +#include +#include +#include +// for EOF: +#include +// for memmove(): +#include + + +// low-level read and write functions +#ifdef _MSC_VER +# include +#else +# include +//extern "C" { +// int write (int fd, const char* buf, int num); +// int read (int fd, char* buf, int num); +//} +#endif + + +// BEGIN namespace BOOST +namespace boost { + + +/************************************************************ + * fdostream + * - a stream that writes on a file descriptor + ************************************************************/ + + +class fdoutbuf : public std::streambuf { + protected: + int fd; // file descriptor + public: + // constructor + fdoutbuf (int _fd) : fd(_fd) { + } + protected: + // write one character + virtual int_type overflow (int_type c) { + if (c != EOF) { + char z = c; + if (write (fd, &z, 1) != 1) { + return EOF; + } + } + return c; + } + // write multiple characters + virtual + std::streamsize xsputn (const char* s, + std::streamsize num) { + return write(fd,s,num); + } +}; + +class fdostream : public std::ostream { + protected: + fdoutbuf buf; + public: + fdostream (int fd) : std::ostream(0), buf(fd) { + rdbuf(&buf); + } +}; + + +/************************************************************ + * fdistream + * - a stream that reads on a file descriptor + ************************************************************/ + +class fdinbuf : public std::streambuf { + protected: + int fd; // file descriptor + protected: + /* data buffer: + * - at most, pbSize characters in putback area plus + * - at most, bufSize characters in ordinary read buffer + */ + static const int pbSize = 4; // size of putback area + static const int bufSize = 1024; // size of the data buffer + char buffer[bufSize+pbSize]; // data buffer + + public: + /* constructor + * - initialize file descriptor + * - initialize empty data buffer + * - no putback area + * => force underflow() + */ + fdinbuf (int _fd) : fd(_fd) { + setg (buffer+pbSize, // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize); // end position + } + + protected: + // insert new characters into the buffer + virtual int_type underflow () { +#ifndef _MSC_VER + using std::memmove; +#endif + + // is read position before end of buffer? + if (gptr() < egptr()) { + return traits_type::to_int_type(*gptr()); + } + + /* process size of putback area + * - use number of characters read + * - but at most size of putback area + */ + int numPutback; + numPutback = gptr() - eback(); + if (numPutback > pbSize) { + numPutback = pbSize; + } + + /* copy up to pbSize characters previously read into + * the putback area + */ + memmove (buffer+(pbSize-numPutback), gptr()-numPutback, + numPutback); + + // read at most bufSize new characters + int num; + num = read (fd, buffer+pbSize, bufSize); + if (num <= 0) { + // ERROR or EOF + return EOF; + } + + // reset buffer pointers + setg (buffer+(pbSize-numPutback), // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize+num); // end of buffer + + // return next character + return traits_type::to_int_type(*gptr()); + } +}; + +class fdistream : public std::istream { + protected: + fdinbuf buf; + public: + fdistream (int fd) : std::istream(0), buf(fd) { + rdbuf(&buf); + } +}; + + +} // END namespace boost + +#endif /*BOOST_FDSTREAM_HPP*/ diff --git a/src/format.cc b/src/format.cc new file mode 100644 index 00000000..774af6ca --- /dev/null +++ b/src/format.cc @@ -0,0 +1,235 @@ +#include "format.h" +#include "pyinterp.h" + +namespace ledger { + +void format_t::parse(const string& fmt) +{ + element_t * current = NULL; + + char buf[1024]; + char * q = buf; + + if (elements.size() > 0) + clear_elements(); + format_string = fmt; + + for (const char * p = fmt.c_str(); *p; p++) { + if (*p != '%' && *p != '\\') { + *q++ = *p; + continue; + } + else if (*p == '\\') { + p++; + switch (*p) { + case 'b': *q++ = '\b'; break; + case 'f': *q++ = '\f'; break; + case 'n': *q++ = '\n'; break; + case 'r': *q++ = '\r'; break; + case 't': *q++ = '\t'; break; + case 'v': *q++ = '\v'; break; + default: + *q++ = *p; + break; + } + continue; + } + else { + assert(*p == '%'); + if (*(p + 1) == '%') { + p++; // %% is the same as \% + *q++ = *p; + continue; + } + } + + current = new element_t; + elements.push_back(current); + + if (q != buf) { + current->kind = element_t::TEXT; + current->chars = new string(buf, q); + q = buf; + + current = new element_t; + elements.push_back(current); + } + + ++p; + if (*p == '-') { + current->align_left = true; + ++p; + } + + if (*p && std::isdigit(*p)) { + int num = *p++ - '0'; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + current->min_width = num; + } + + if (*p == '.') { + ++p; + int num = 0; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + + current->max_width = num; + if (current->min_width == -1) + current->min_width = current->max_width; + } + + if (current->max_width != -1 && current->min_width != -1 && + current->max_width < current->min_width) + throw_(format_exception, "Maximum width is less than the minimum width"); + + switch (*p) { + case '|': + current->kind = element_t::COLUMN; + break; + + case '{': + case '(': { + char open = *p; + char close = *p == '{' ? '}' : ')'; + ++p; + const char * b = p; + int depth = 1; + while (*p) { + if (*p == close && --depth == 0) + break; + else if (*p == open) + ++depth; + p++; + } + if (*p != close) + throw_(format_exception, "Missing '" << close << "'"); + + if (open == '{') { + assert(! current->xpath); + current->kind = element_t::XPATH; + current->xpath = new xml::xpath_t(string(b, p)); + } else { + assert(! current->format); + current->kind = element_t::GROUP; + current->format = new format_t(string(b, p)); + } + break; + } + + default: + assert(! current->xpath); + current->kind = element_t::XPATH; + current->xpath = new xml::xpath_t(string(p, p + 1)); + break; + } + } + + if (q != buf) { + current = new element_t; + elements.push_back(current); + + current->kind = element_t::TEXT; + current->chars = new string(buf, q); + } +} + +void format_t::compile(xml::node_t * context) +{ + for (std::list::iterator i = elements.begin(); + i != elements.end(); + i++) + switch ((*i)->kind) { + case element_t::XPATH: + assert((*i)->xpath); + (*i)->xpath->compile(context); + break; + case element_t::GROUP: + assert((*i)->format); + (*i)->format->compile(context); + break; + default: + break; + } +} + +int format_t::element_formatter_t::operator() + (std::ostream& out_str, element_t * elem, xml::node_t * context, + int column) const +{ + if (elem->kind == element_t::COLUMN) { + if (elem->max_width != -1 && elem->max_width < column) { + out_str << '\n'; + column = 0; + } + + if (elem->min_width != -1 && elem->min_width > column) { + out_str << string(elem->min_width - column, ' '); + column = elem->min_width; + } + return column; + } + + std::ostringstream out; + + if (elem->align_left) + out << std::left; + else + out << std::right; + + if (elem->min_width > 0) + out.width(elem->min_width); + + int start_column = column; + + if (elem->kind == element_t::XPATH) + elem->xpath->calc(context).strip_annotations() + .write(out, elem->min_width, elem->max_width); + else if (elem->kind == element_t::GROUP) + column = elem->format->format(out, context, column); + else if (elem->kind == element_t::TEXT) + out << *elem->chars; + else + assert(0); + + string temp = out.str(); + for (string::const_iterator i = temp.begin(); + i != temp.end(); + i++) + if (*i == '\n' || *i == '\r') + column = 0; + else + column++; + + int virtual_width = column - start_column; + + if (elem->min_width != -1 && virtual_width < elem->min_width) { + out_str << temp << string(' ', elem->min_width - virtual_width); + } + else if (elem->max_width != -1 && virtual_width > elem->max_width) { + temp.erase(temp.length() - (virtual_width - elem->max_width)); + out_str << temp; + } + else { + out_str << temp; + } + + return column; +} + +int format_t::format(std::ostream& out, xml::node_t * context, + int column, const element_formatter_t& formatter) const +{ + for (std::list::const_iterator i = elements.begin(); + i != elements.end(); + i++) + column = formatter(out, *i, context, column); + + return column; +} + +} // namespace ledger diff --git a/src/format.h b/src/format.h new file mode 100644 index 00000000..1ddd8202 --- /dev/null +++ b/src/format.h @@ -0,0 +1,108 @@ +#ifndef _FORMAT_H +#define _FORMAT_H + +#include "xpath.h" + +namespace ledger { + +class format_t +{ + public: + struct element_t + { + bool align_left; + short min_width; + short max_width; + + enum kind_t { UNKNOWN, TEXT, COLUMN, XPATH, GROUP } kind; + union { + string * chars; + xml::xpath_t * xpath; + format_t * format; + }; + + element_t() + : align_left(false), min_width(-1), max_width(-1), + kind(UNKNOWN), chars(NULL) { + TRACE_CTOR(element_t, ""); + } + + ~element_t() { + TRACE_DTOR(element_t); + + switch (kind) { + case TEXT: + delete chars; + break; + case XPATH: + delete xpath; + break; + case GROUP: + delete format; + break; + default: + assert(! chars); + break; + } + } + + private: + element_t(const element_t& other); + }; + + struct element_formatter_t { + virtual ~element_formatter_t() {} + virtual int operator()(std::ostream& out, element_t * element, + xml::node_t * context, int column) const; + }; + + string format_string; + std::list elements; + + private: + format_t(const format_t&); + + public: + format_t() { + TRACE_CTOR(format_t, ""); + } + format_t(const string& fmt) { + TRACE_CTOR(format_t, "const string&"); + parse(fmt); + } + + void clear_elements() { + for (std::list::iterator i = elements.begin(); + i != elements.end(); + i++) + delete *i; + elements.clear(); + } + + virtual ~format_t() { + TRACE_DTOR(format_t); + clear_elements(); + } + + void parse(const string& fmt); + + void compile(const string& fmt, xml::node_t * context = NULL) { + parse(fmt); + compile(context); + } + void compile(xml::node_t * context = NULL); + + int format(std::ostream& out, xml::node_t * context = NULL, + int column = 0, const element_formatter_t& formatter = + element_formatter_t()) const; + + operator bool() const { + return ! format_string.empty(); + } +}; + +DECLARE_EXCEPTION(format_exception); + +} // namespace ledger + +#endif // _FORMAT_H diff --git a/src/gd_qnan.h b/src/gd_qnan.h new file mode 100644 index 00000000..87eba8fb --- /dev/null +++ b/src/gd_qnan.h @@ -0,0 +1,12 @@ +#define f_QNAN 0xffc00000 +#define d_QNAN0 0x0 +#define d_QNAN1 0xfff80000 +#define ld_QNAN0 0x0 +#define ld_QNAN1 0xc0000000 +#define ld_QNAN2 0xffff +#define ld_QNAN3 0x0 +#define ldus_QNAN0 0x0 +#define ldus_QNAN1 0x0 +#define ldus_QNAN2 0x0 +#define ldus_QNAN3 0xc000 +#define ldus_QNAN4 0xffff diff --git a/src/gnucash.cc b/src/gnucash.cc new file mode 100644 index 00000000..abe8c555 --- /dev/null +++ b/src/gnucash.cc @@ -0,0 +1,366 @@ +#include "gnucash.h" + +namespace ledger { + +void startElement(void *userData, const char *name, const char ** /* attrs */) +{ + gnucash_parser_t * parser = static_cast(userData); + + if (std::strcmp(name, "gnc:account") == 0) { + parser->curr_account = new account_t(parser->master_account); + } + else if (std::strcmp(name, "act:name") == 0) + parser->action = gnucash_parser_t::ACCOUNT_NAME; + else if (std::strcmp(name, "act:id") == 0) + parser->action = gnucash_parser_t::ACCOUNT_ID; + else if (std::strcmp(name, "act:parent") == 0) + parser->action = gnucash_parser_t::ACCOUNT_PARENT; + else if (std::strcmp(name, "gnc:commodity") == 0) + parser->curr_comm = NULL; + else if (std::strcmp(name, "cmdty:id") == 0) + parser->action = gnucash_parser_t::COMM_SYM; + else if (std::strcmp(name, "cmdty:name") == 0) + parser->action = gnucash_parser_t::COMM_NAME; + else if (std::strcmp(name, "cmdty:fraction") == 0) + parser->action = gnucash_parser_t::COMM_PREC; + else if (std::strcmp(name, "gnc:transaction") == 0) { + assert(! parser->curr_entry); + parser->curr_entry = new entry_t; + } + else if (std::strcmp(name, "trn:num") == 0) + parser->action = gnucash_parser_t::ENTRY_NUM; + else if (std::strcmp(name, "trn:date-posted") == 0) + parser->action = gnucash_parser_t::ALMOST_ENTRY_DATE; + else if (parser->action == gnucash_parser_t::ALMOST_ENTRY_DATE && + std::strcmp(name, "ts:date") == 0) + parser->action = gnucash_parser_t::ENTRY_DATE; + else if (std::strcmp(name, "trn:description") == 0) + parser->action = gnucash_parser_t::ENTRY_DESC; + else if (std::strcmp(name, "trn:split") == 0) { + assert(parser->curr_entry); + parser->curr_entry->add_transaction(new transaction_t(parser->curr_account)); + } + else if (std::strcmp(name, "split:reconciled-state") == 0) + parser->action = gnucash_parser_t::XACT_STATE; + else if (std::strcmp(name, "split:amount") == 0) + parser->action = gnucash_parser_t::XACT_AMOUNT; + else if (std::strcmp(name, "split:value") == 0) + parser->action = gnucash_parser_t::XACT_VALUE; + else if (std::strcmp(name, "split:quantity") == 0) + parser->action = gnucash_parser_t::XACT_QUANTITY; + else if (std::strcmp(name, "split:account") == 0) + parser->action = gnucash_parser_t::XACT_ACCOUNT; + else if (std::strcmp(name, "split:memo") == 0) + parser->action = gnucash_parser_t::XACT_NOTE; +} + +void endElement(void *userData, const char *name) +{ + gnucash_parser_t * parser = static_cast(userData); + + if (std::strcmp(name, "gnc:account") == 0) { + assert(parser->curr_account); + if (parser->curr_account->parent == parser->master_account) + parser->curr_journal->add_account(parser->curr_account); + parser->accounts_by_id.insert(accounts_pair(parser->curr_account_id, + parser->curr_account)); + parser->curr_account = NULL; + } + else if (std::strcmp(name, "gnc:commodity") == 0) { + parser->curr_comm = NULL; + } + else if (std::strcmp(name, "gnc:transaction") == 0) { + assert(parser->curr_entry); + + // Add the new entry (what gnucash calls a 'transaction') to the + // journal + if (! parser->curr_journal->add_entry(parser->curr_entry)) { + print_entry(std::cerr, *parser->curr_entry); + parser->have_error = "The above entry does not balance"; + delete parser->curr_entry; + } else { + parser->curr_entry->src_idx = parser->src_idx; + parser->curr_entry->beg_pos = parser->beg_pos; + parser->curr_entry->beg_line = parser->beg_line; + parser->curr_entry->end_pos = parser->instreamp->tellg(); + parser->curr_entry->end_line = + XML_GetCurrentLineNumber(parser->expat_parser) - parser->offset; + parser->count++; + } + + // Clear the relevant variables for the next run + parser->curr_entry = NULL; + parser->entry_comm = NULL; + } + else if (std::strcmp(name, "trn:split") == 0) { + transaction_t * xact = parser->curr_entry->transactions.back(); + + // Identify the commodity to use for the value of this + // transaction. The quantity indicates how many times that value + // the transaction is worth. + amount_t value; + commodity_t * default_commodity = NULL; + if (parser->entry_comm) { + default_commodity = parser->entry_comm; + } else { + gnucash_parser_t::account_comm_map::iterator ac = + parser->account_comms.find(xact->account); + if (ac != parser->account_comms.end()) + default_commodity = (*ac).second; + } + + if (default_commodity) { + parser->curr_quant.set_commodity(*default_commodity); + value = parser->curr_quant.round(); + + if (parser->curr_value.commodity() == *default_commodity) + parser->curr_value = value; + } else { + value = parser->curr_quant; + } + + xact->state = parser->curr_state; + xact->amount = value; + if (value != parser->curr_value) + xact->cost = new amount_t(parser->curr_value); + + xact->beg_pos = parser->beg_pos; + xact->beg_line = parser->beg_line; + xact->end_pos = parser->instreamp->tellg(); + xact->end_line = + XML_GetCurrentLineNumber(parser->expat_parser) - parser->offset; + + // Clear the relevant variables for the next run + parser->curr_state = transaction_t::UNCLEARED; + parser->curr_value = amount_t(); + parser->curr_quant = amount_t(); + } + + parser->action = gnucash_parser_t::NO_ACTION; +} + +amount_t gnucash_parser_t::convert_number(const string& number, + int * precision) +{ + const char * num = number.c_str(); + + if (char * p = std::strchr(num, '/')) { + string numer_str(num, p - num); + string denom_str(p + 1); + + amount_t amt(numer_str); + amount_t den(denom_str); + + if (precision) + *precision = denom_str.length() - 1; + + if (! den) { + have_error = "Denominator in entry is zero!"; + return amt; + } else { + return amt / den; + } + } else { + return amount_t(number); + } +} + +void dataHandler(void *userData, const char *s, int len) +{ + gnucash_parser_t * parser = static_cast(userData); + + switch (parser->action) { + case gnucash_parser_t::ACCOUNT_NAME: + parser->curr_account->name = string(s, len); + break; + + case gnucash_parser_t::ACCOUNT_ID: + parser->curr_account_id = string(s, len); + break; + + case gnucash_parser_t::ACCOUNT_PARENT: { + accounts_map::iterator i = parser->accounts_by_id.find(string(s, len)); + assert(i != parser->accounts_by_id.end()); + parser->curr_account->parent = (*i).second; + parser->curr_account->depth = parser->curr_account->parent->depth + 1; + (*i).second->add_account(parser->curr_account); + break; + } + + case gnucash_parser_t::COMM_SYM: { + string symbol(s, len); + if (symbol == "USD") symbol = "$"; + + parser->curr_comm = commodity_t::find_or_create(symbol); + assert(parser->curr_comm); + + if (symbol != "$") + parser->curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); + + if (parser->curr_account) + parser->account_comms.insert + (gnucash_parser_t::account_comm_pair(parser->curr_account, + parser->curr_comm)); + else if (parser->curr_entry) + parser->entry_comm = parser->curr_comm; + break; + } + + case gnucash_parser_t::COMM_NAME: + parser->curr_comm->set_name(string(s, len)); + break; + + case gnucash_parser_t::COMM_PREC: + parser->curr_comm->set_precision(len - 1); + break; + + case gnucash_parser_t::ENTRY_NUM: + parser->curr_entry->code = string(s, len); + break; + + case gnucash_parser_t::ENTRY_DATE: + parser->curr_entry->_date = parse_datetime(string(s, len)); + break; + + case gnucash_parser_t::ENTRY_DESC: + parser->curr_entry->payee = string(s, len); + break; + + case gnucash_parser_t::XACT_STATE: + if (*s == 'y') + parser->curr_state = transaction_t::CLEARED; + else if (*s == 'n') + parser->curr_state = transaction_t::UNCLEARED; + else + parser->curr_state = transaction_t::PENDING; + break; + + case gnucash_parser_t::XACT_VALUE: { + int precision; + assert(parser->entry_comm); + parser->curr_value = parser->convert_number(string(s, len), &precision); + parser->curr_value.set_commodity(*parser->entry_comm); + + if (precision > parser->entry_comm->precision()) + parser->entry_comm->set_precision(precision); + break; + } + + case gnucash_parser_t::XACT_QUANTITY: + parser->curr_quant = parser->convert_number(string(s, len)); + break; + + case gnucash_parser_t::XACT_ACCOUNT: { + transaction_t * xact = parser->curr_entry->transactions.back(); + + accounts_map::iterator i = + parser->accounts_by_id.find(string(s, len)); + if (i != parser->accounts_by_id.end()) { + xact->account = (*i).second; + } else { + xact->account = parser->curr_journal->find_account(""); + + parser->have_error = (string("Could not find account ") + + string(s, len)); + } + break; + } + + case gnucash_parser_t::XACT_NOTE: + parser->curr_entry->transactions.back()->note = string(s, len); + break; + + case gnucash_parser_t::NO_ACTION: + case gnucash_parser_t::ALMOST_ENTRY_DATE: + case gnucash_parser_t::XACT_AMOUNT: + break; + + default: + assert(0); + break; + } +} + +bool gnucash_parser_t::test(std::istream& in) const +{ + char buf[5]; + in.read(buf, 5); + in.clear(); + in.seekg(0, std::ios::beg); + + return std::strncmp(buf, "master; + curr_account = NULL; + curr_entry = NULL; + curr_comm = NULL; + entry_comm = NULL; + curr_state = transaction_t::UNCLEARED; + + instreamp = ∈ + path = original_file ? *original_file : ""; + src_idx = journal->sources.size() - 1; + + // GnuCash uses the USD commodity without defining it, which really + // means $. + commodity_t * usd = commodity_t::find_or_create("$"); + usd->set_precision(2); + usd->add_flags(COMMODITY_STYLE_THOUSANDS); + + offset = 2; + expat_parser = XML_ParserCreate(NULL); + + XML_SetElementHandler(parser, startElement, endElement); + XML_SetCharacterDataHandler(parser, dataHandler); + XML_SetUserData(parser, this); + + while (in.good() && ! in.eof()) { + beg_pos = in.tellg(); + beg_line = (XML_GetCurrentLineNumber(parser) - offset) + 1; + + in.getline(buf, BUFSIZ - 1); + std::strcat(buf, "\n"); + if (! XML_Parse(parser, buf, std::strlen(buf), in.eof())) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + const char * msg = XML_ErrorString(XML_GetErrorCode(parser)); + XML_ParserFree(parser); + throw_(parse_exception, msg); + } + + if (! have_error.empty()) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; +#if 0 + // jww (2007-04-26): What is this doing? + parse_error err(have_error); + std::cerr << "Error: " << err.what() << std::endl; +#endif + have_error = ""; + } + } + + XML_ParserFree(parser); + + accounts_by_id.clear(); + curr_account_id.clear(); + + return count; +} + +} // namespace ledger diff --git a/src/gnucash.h b/src/gnucash.h new file mode 100644 index 00000000..a0d9fb18 --- /dev/null +++ b/src/gnucash.h @@ -0,0 +1,75 @@ +#ifndef _GNUCASH_H +#define _GNUCASH_H + +#include "parser.h" +#include "journal.h" + +namespace ledger { + +struct gnucash_parser_t : public parser_t +{ + typedef std::map accounts_map; + typedef std::pair accounts_pair; + + typedef std::map account_comm_map; + typedef std::pair account_comm_pair; + + journal_t * curr_journal; + account_t * master_account; + account_t * curr_account; + string curr_account_id; + entry_t * curr_entry; + commodity_t * entry_comm; + commodity_t * curr_comm; + amount_t curr_value; + amount_t curr_quant; + XML_Parser expat_parser; + accounts_map accounts_by_id; + account_comm_map account_comms; + unsigned int count; + string have_error; + + std::istream * instreamp; + unsigned int offset; + XML_Parser parser; + string path; + unsigned int src_idx; + unsigned long beg_pos; + unsigned long beg_line; + + transaction_t::state_t curr_state; + + enum action_t { + NO_ACTION, + ACCOUNT_NAME, + ACCOUNT_ID, + ACCOUNT_PARENT, + COMM_SYM, + COMM_NAME, + COMM_PREC, + ENTRY_NUM, + ALMOST_ENTRY_DATE, + ENTRY_DATE, + ENTRY_DESC, + XACT_STATE, + XACT_AMOUNT, + XACT_VALUE, + XACT_QUANTITY, + XACT_ACCOUNT, + XACT_NOTE + } action; + + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); + + amount_t convert_number(const string& number, int * precision = NULL); +}; + +} // namespace ledger + +#endif // _GNUCASH_H diff --git a/src/journal.cc b/src/journal.cc new file mode 100644 index 00000000..1f643364 --- /dev/null +++ b/src/journal.cc @@ -0,0 +1,667 @@ +#include "journal.h" +#include "mask.h" +#if 0 +#ifdef USE_BOOST_PYTHON +#include "pyinterp.h" +#endif +#endif + +namespace ledger { + +const string version = PACKAGE_VERSION; + +bool transaction_t::use_effective_date = false; + +transaction_t::~transaction_t() +{ + TRACE_DTOR(transaction_t); + if (cost) delete cost; +} + +moment_t transaction_t::actual_date() const +{ + if (! is_valid_moment(_date) && entry) + return entry->actual_date(); + return _date; +} + +moment_t transaction_t::effective_date() const +{ + if (! is_valid_moment(_date_eff) && entry) + return entry->effective_date(); + return _date_eff; +} + +bool transaction_t::valid() const +{ + if (! entry) { + DEBUG_("ledger.validate", "transaction_t: ! entry"); + return false; + } + + if (state != UNCLEARED && state != CLEARED && state != PENDING) { + DEBUG_("ledger.validate", "transaction_t: state is bad"); + return false; + } + + bool found = false; + for (transactions_list::const_iterator i = entry->transactions.begin(); + i != entry->transactions.end(); + i++) + if (*i == this) { + found = true; + break; + } + if (! found) { + DEBUG_("ledger.validate", "transaction_t: ! found"); + return false; + } + + if (! account) { + DEBUG_("ledger.validate", "transaction_t: ! account"); + return false; + } + + if (! amount.valid()) { + DEBUG_("ledger.validate", "transaction_t: ! amount.valid()"); + return false; + } + + if (cost && ! cost->valid()) { + DEBUG_("ledger.validate", "transaction_t: cost && ! cost->valid()"); + return false; + } + + if (flags & ~0x003f) { + DEBUG_("ledger.validate", "transaction_t: flags are bad"); + return false; + } + + return true; +} + +void entry_base_t::add_transaction(transaction_t * xact) +{ + transactions.push_back(xact); +} + +bool entry_base_t::remove_transaction(transaction_t * xact) +{ + transactions.remove(xact); + return true; +} + +bool entry_base_t::finalize() +{ + // Scan through and compute the total balance for the entry. This + // is used for auto-calculating the value of entries with no cost, + // and the per-unit price of unpriced commodities. + + value_t balance; + + bool no_amounts = true; + bool saw_null = false; + for (transactions_list::const_iterator x = transactions.begin(); + x != transactions.end(); + x++) + if (! ((*x)->flags & TRANSACTION_VIRTUAL) || + ((*x)->flags & TRANSACTION_BALANCE)) { + amount_t * p = (*x)->cost ? (*x)->cost : &(*x)->amount; + if (*p) { + if (no_amounts) { + balance = *p; + no_amounts = false; + } else { + balance += *p; + } + + if ((*x)->cost && (*x)->amount.commodity().annotated) { + annotated_commodity_t& + ann_comm(static_cast + ((*x)->amount.commodity())); + if (ann_comm.price) + balance += ann_comm.price * (*x)->amount.number() - *((*x)->cost); + } + } else { + saw_null = true; + } + } + + // If it's a null entry, then let the user have their fun + if (no_amounts) + return true; + + // If there is only one transaction, balance against the basket + // account if one has been set. + + if (journal && journal->basket && transactions.size() == 1) { + assert(balance.type < value_t::BALANCE); + transaction_t * nxact = new transaction_t(journal->basket); + // The amount doesn't need to be set because the code below will + // balance this transaction against the other. + add_transaction(nxact); + nxact->flags |= TRANSACTION_CALCULATED; + } + + // If the first transaction of a two-transaction entry is of a + // different commodity than the other, and it has no per-unit price, + // determine its price by dividing the unit count into the value of + // the balance. This is done for the last eligible commodity. + + if (! saw_null && balance && balance.type == value_t::BALANCE && + ((balance_t *) balance.data)->amounts.size() == 2) { + transactions_list::const_iterator x = transactions.begin(); + commodity_t& this_comm = (*x)->amount.commodity(); + + amounts_map::const_iterator this_bal = + ((balance_t *) balance.data)->amounts.find(&this_comm); + amounts_map::const_iterator other_bal = + ((balance_t *) balance.data)->amounts.begin(); + if (this_bal == other_bal) + other_bal++; + + amount_t per_unit_cost = + amount_t((*other_bal).second / (*this_bal).second.number()).unround(); + + for (; x != transactions.end(); x++) { + if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL) || + ! (*x)->amount || (*x)->amount.commodity() != this_comm) + continue; + + assert((*x)->amount); + balance -= (*x)->amount; + + entry_t * entry = dynamic_cast(this); + + if ((*x)->amount.commodity() && + ! (*x)->amount.commodity().annotated) + (*x)->amount.annotate_commodity + (per_unit_cost.abs(), + entry ? entry->actual_date() : moment_t(), + entry ? entry->code : ""); + + (*x)->cost = new amount_t(- (per_unit_cost * (*x)->amount.number())); + balance += *(*x)->cost; + } + } + + // Walk through each of the transactions, fixing up any that we + // can, and performing any on-the-fly calculations. + + bool empty_allowed = true; + + for (transactions_list::const_iterator x = transactions.begin(); + x != transactions.end(); + x++) { + if (! (*x)->amount.null() || + (((*x)->flags & TRANSACTION_VIRTUAL) && + ! ((*x)->flags & TRANSACTION_BALANCE))) + continue; + + if (! empty_allowed) + throw_(exception, "Only one transaction with null amount allowed per entry"); + empty_allowed = false; + + // If one transaction gives no value at all, its value will become + // the inverse of the value of the others. If multiple + // commodities are involved, multiple transactions will be + // generated to balance them all. + + balance_t * bal = NULL; + switch (balance.type) { + case value_t::BALANCE_PAIR: + bal = &((balance_pair_t *) balance.data)->quantity; + // fall through... + + case value_t::BALANCE: + if (! bal) + bal = (balance_t *) balance.data; + + if (bal->amounts.size() < 2) { + balance.cast(value_t::AMOUNT); + } else { + bool first = true; + for (amounts_map::const_iterator i = bal->amounts.begin(); + i != bal->amounts.end(); + i++) { + amount_t amt = (*i).second.negate(); + + if (first) { + (*x)->amount = amt; + first = false; + } else { + transaction_t * nxact = new transaction_t((*x)->account); + add_transaction(nxact); + nxact->flags |= TRANSACTION_CALCULATED; + nxact->amount = amt; + } + + balance += amt; + } + break; + } + // fall through... + + case value_t::AMOUNT: + (*x)->amount = ((amount_t *) balance.data)->negate(); + (*x)->flags |= TRANSACTION_CALCULATED; + + balance += (*x)->amount; + break; + + default: + break; + } + } + + if (balance) { +#if 1 + throw_(balance_exception, "Entry does not balance"); +#else + error * err = + new balance_error("Entry does not balance", + new entry_context(*this, "While balancing entry:")); + err->context.push_front + (new value_context(balance, "Unbalanced remainder is:")); + throw err; +#endif + } + + return true; +} + +entry_t::entry_t(const entry_t& e) + : entry_base_t(e), _date(e._date), _date_eff(e._date_eff), + code(e.code), payee(e.payee), data(NULL) +{ + TRACE_CTOR(entry_t, "copy"); + for (transactions_list::const_iterator i = transactions.begin(); + i != transactions.end(); + i++) + (*i)->entry = this; +} + +bool entry_t::get_state(transaction_t::state_t * state) const +{ + bool first = true; + bool hetero = false; + + for (transactions_list::const_iterator i = transactions.begin(); + i != transactions.end(); + i++) { + if (first) { + *state = (*i)->state; + first = false; + } + else if (*state != (*i)->state) { + hetero = true; + break; + } + } + + return ! hetero; +} + +void entry_t::add_transaction(transaction_t * xact) +{ + xact->entry = this; + entry_base_t::add_transaction(xact); +} + +bool entry_t::valid() const +{ + if (! is_valid_moment(_date) || ! journal) { + DEBUG_("ledger.validate", "entry_t: ! _date || ! journal"); + return false; + } + + for (transactions_list::const_iterator i = transactions.begin(); + i != transactions.end(); + i++) + if ((*i)->entry != this || ! (*i)->valid()) { + DEBUG_("ledger.validate", "entry_t: transaction not valid"); + return false; + } + + return true; +} + +void auto_entry_t::extend_entry(entry_base_t& entry, bool post) +{ + transactions_list initial_xacts(entry.transactions.begin(), + entry.transactions.end()); + + for (transactions_list::iterator i = initial_xacts.begin(); + i != initial_xacts.end(); + i++) { + // jww (2006-09-10): Create a scope here based on entry + if (predicate.calc((xml::node_t *) NULL)) { + for (transactions_list::iterator t = transactions.begin(); + t != transactions.end(); + t++) { + amount_t amt; + if (! (*t)->amount.commodity()) { + if (! post) + continue; + amt = (*i)->amount * (*t)->amount; + } else { + if (post) + continue; + amt = (*t)->amount; + } + + account_t * account = (*t)->account; + string fullname = account->fullname(); + assert(! fullname.empty()); + if (fullname == "$account" || fullname == "@account") + account = (*i)->account; + + transaction_t * xact + = new transaction_t(account, amt, (*t)->flags | TRANSACTION_AUTO); + entry.add_transaction(xact); + } + } + } +} + +account_t::~account_t() +{ + TRACE_DTOR(account_t); + + for (accounts_map::iterator i = accounts.begin(); + i != accounts.end(); + i++) + delete (*i).second; +} + +account_t * account_t::find_account(const string& name, + const bool auto_create) +{ + accounts_map::const_iterator i = accounts.find(name); + if (i != accounts.end()) + return (*i).second; + + char buf[256]; + + string::size_type sep = name.find(':'); + assert(sep < 256|| sep == string::npos); + + const char * first, * rest; + if (sep == string::npos) { + first = name.c_str(); + rest = NULL; + } else { + std::strncpy(buf, name.c_str(), sep); + buf[sep] = '\0'; + + first = buf; + rest = name.c_str() + sep + 1; + } + + account_t * account; + + i = accounts.find(first); + if (i == accounts.end()) { + if (! auto_create) + return NULL; + + account = new account_t(this, first); + account->journal = journal; + + std::pair result + = accounts.insert(accounts_pair(first, account)); + assert(result.second); + } else { + account = (*i).second; + } + + if (rest) + account = account->find_account(rest, auto_create); + + return account; +} + +static inline +account_t * find_account_re_(account_t * account, const mask_t& regexp) +{ + if (regexp.match(account->fullname())) + return account; + + for (accounts_map::iterator i = account->accounts.begin(); + i != account->accounts.end(); + i++) + if (account_t * a = find_account_re_((*i).second, regexp)) + return a; + + return NULL; +} + +account_t * journal_t::find_account_re(const string& regexp) +{ + return find_account_re_(master, mask_t(regexp)); +} + +string account_t::fullname() const +{ + if (! _fullname.empty()) { + return _fullname; + } else { + const account_t * first = this; + string fullname = name; + + while (first->parent) { + first = first->parent; + if (! first->name.empty()) + fullname = first->name + ":" + fullname; + } + + _fullname = fullname; + + return fullname; + } +} + +std::ostream& operator<<(std::ostream& out, const account_t& account) +{ + out << account.fullname(); + return out; +} + +bool account_t::valid() const +{ + if (depth > 256 || ! journal) { + DEBUG_("ledger.validate", "account_t: depth > 256 || ! journal"); + return false; + } + + for (accounts_map::const_iterator i = accounts.begin(); + i != accounts.end(); + i++) { + if (this == (*i).second) { + DEBUG_("ledger.validate", "account_t: parent refers to itself!"); + return false; + } + + if (! (*i).second->valid()) { + DEBUG_("ledger.validate", "account_t: child not valid"); + return false; + } + } + + return true; +} + +journal_t::~journal_t() +{ + TRACE_DTOR(journal_t); + + assert(master); + delete master; + + if (document) + delete document; + + // Don't bother unhooking each entry's transactions from the + // accounts they refer to, because all accounts are about to + // be deleted. + for (entries_list::iterator i = entries.begin(); + i != entries.end(); + i++) + if (! item_pool || + ((char *) *i) < item_pool || ((char *) *i) >= item_pool_end) + delete *i; + else + (*i)->~entry_t(); + + for (auto_entries_list::iterator i = auto_entries.begin(); + i != auto_entries.end(); + i++) + if (! item_pool || + ((char *) *i) < item_pool || ((char *) *i) >= item_pool_end) + delete *i; + else + (*i)->~auto_entry_t(); + + for (period_entries_list::iterator i = period_entries.begin(); + i != period_entries.end(); + i++) + if (! item_pool || + ((char *) *i) < item_pool || ((char *) *i) >= item_pool_end) + delete *i; + else + (*i)->~period_entry_t(); + + if (item_pool) + delete[] item_pool; +} + +bool journal_t::add_entry(entry_t * entry) +{ + entry->journal = this; + + if (! run_hooks(entry_finalize_hooks, *entry, false) || + ! entry->finalize() || + ! run_hooks(entry_finalize_hooks, *entry, true)) { + entry->journal = NULL; + return false; + } + + entries.push_back(entry); + + for (transactions_list::const_iterator i = entry->transactions.begin(); + i != entry->transactions.end(); + i++) + if ((*i)->cost && (*i)->amount) + (*i)->amount.commodity().add_price(entry->date(), + *(*i)->cost / (*i)->amount.number()); + + return true; +} + +bool journal_t::remove_entry(entry_t * entry) +{ + bool found = false; + entries_list::iterator i; + for (i = entries.begin(); i != entries.end(); i++) + if (*i == entry) { + found = true; + break; + } + if (! found) + return false; + + entries.erase(i); + entry->journal = NULL; + + return true; +} + +bool journal_t::valid() const +{ + if (! master->valid()) { + DEBUG_("ledger.validate", "journal_t: master not valid"); + return false; + } + + for (entries_list::const_iterator i = entries.begin(); + i != entries.end(); + i++) + if (! (*i)->valid()) { + DEBUG_("ledger.validate", "journal_t: entry not valid"); + return false; + } + + for (commodities_map::const_iterator i = commodity_t::commodities.begin(); + i != commodity_t::commodities.end(); + i++) + if (! (*i).second->valid()) { + DEBUG_("ledger.validate", "journal_t: commodity not valid"); + return false; + } + + return true; +} + +void print_entry(std::ostream& out, const entry_base_t& entry_base, + const string& prefix) +{ + string print_format; + + if (dynamic_cast(&entry_base)) { + print_format = (prefix + "%D %X%C%P\n" + + prefix + " %-34A %12o\n%/" + + prefix + " %-34A %12o\n"); + } + else if (const auto_entry_t * entry = + dynamic_cast(&entry_base)) { + out << "= " << entry->predicate.expr << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else if (const period_entry_t * entry = + dynamic_cast(&entry_base)) { + out << "~ " << entry->period_string << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else { + assert(0); + } + +#if 0 + format_entries formatter(out, print_format); + walk_transactions(const_cast(entry_base.transactions), + formatter); + formatter.flush(); + + clear_transaction_xdata cleaner; + walk_transactions(const_cast(entry_base.transactions), + cleaner); +#endif +} + +#if 0 +void entry_context::describe(std::ostream& out) const throw() +{ + if (! desc.empty()) + out << desc << std::endl; + + print_entry(out, entry, " "); +} + +xact_context::xact_context(const ledger::transaction_t& _xact, + const string& desc) throw() + : file_context("", 0, desc), xact(_xact) +{ + const ledger::strings_list& sources(xact.entry->journal->sources); + unsigned int x = 0; + for (ledger::strings_list::const_iterator i = sources.begin(); + i != sources.end(); + i++, x++) + if (x == xact.entry->src_idx) { + file = *i; + break; + } + line = xact.beg_line; +} +#endif + +} // namespace ledger diff --git a/src/journal.h b/src/journal.h new file mode 100644 index 00000000..1995e0f3 --- /dev/null +++ b/src/journal.h @@ -0,0 +1,471 @@ +#ifndef _JOURNAL_H +#define _JOURNAL_H + +#include "xpath.h" + +namespace ledger { + +// These flags persist with the object +#define TRANSACTION_NORMAL 0x0000 +#define TRANSACTION_VIRTUAL 0x0001 +#define TRANSACTION_BALANCE 0x0002 +#define TRANSACTION_AUTO 0x0004 +#define TRANSACTION_BULK_ALLOC 0x0008 +#define TRANSACTION_CALCULATED 0x0010 + +class entry_t; +class account_t; + +class transaction_t +{ + public: + enum state_t { UNCLEARED, CLEARED, PENDING }; + + entry_t * entry; + moment_t _date; + moment_t _date_eff; + account_t * account; + amount_t amount; + string amount_expr; + amount_t * cost; + string cost_expr; + state_t state; + unsigned short flags; + string note; + unsigned long beg_pos; + unsigned long beg_line; + unsigned long end_pos; + unsigned long end_line; + + mutable void * data; + + static bool use_effective_date; + + transaction_t(account_t * _account = NULL) + : entry(NULL), account(_account), cost(NULL), + state(UNCLEARED), flags(TRANSACTION_NORMAL), + beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { + TRACE_CTOR(transaction_t, "account_t *"); + } + transaction_t(account_t * _account, + const amount_t& _amount, + unsigned int _flags = TRANSACTION_NORMAL, + const string& _note = "") + : entry(NULL), account(_account), amount(_amount), cost(NULL), + state(UNCLEARED), flags(_flags), + note(_note), beg_pos(0), beg_line(0), end_pos(0), end_line(0), + data(NULL) { + TRACE_CTOR(transaction_t, "account_t *, const amount_t&, unsigned int, const string&"); + } + transaction_t(const transaction_t& xact) + : entry(xact.entry), account(xact.account), amount(xact.amount), + cost(xact.cost ? new amount_t(*xact.cost) : NULL), + state(xact.state), flags(xact.flags), note(xact.note), + beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { + TRACE_CTOR(transaction_t, "copy"); + } + ~transaction_t(); + + moment_t actual_date() const; + moment_t effective_date() const; + moment_t date() const { + if (use_effective_date) + return effective_date(); + else + return actual_date(); + } + + bool operator==(const transaction_t& xact) { + return this == &xact; + } + bool operator!=(const transaction_t& xact) { + return ! (*this == xact); + } + + bool valid() const; +}; + +#if 0 +class xact_context : public file_context { + public: + const transaction_t& xact; + + xact_context(const transaction_t& _xact, + const string& desc = "") throw(); + virtual ~xact_context() throw() {} +}; +#endif + +class journal_t; + +typedef std::list transactions_list; + +class entry_base_t +{ + public: + journal_t * journal; + unsigned long src_idx; + unsigned long beg_pos; + unsigned long beg_line; + unsigned long end_pos; + unsigned long end_line; + transactions_list transactions; + + entry_base_t() : journal(NULL), + beg_pos(0), beg_line(0), end_pos(0), end_line(0) { + TRACE_CTOR(entry_base_t, ""); + } + entry_base_t(const entry_base_t& e) : journal(NULL), + beg_pos(0), beg_line(0), end_pos(0), end_line(0) + { + TRACE_CTOR(entry_base_t, "copy"); + for (transactions_list::const_iterator i = e.transactions.begin(); + i != e.transactions.end(); + i++) + transactions.push_back(new transaction_t(**i)); + } + virtual ~entry_base_t() { + TRACE_DTOR(entry_base_t); + for (transactions_list::iterator i = transactions.begin(); + i != transactions.end(); + i++) + if (! ((*i)->flags & TRANSACTION_BULK_ALLOC)) + delete *i; + else + (*i)->~transaction_t(); + } + + bool operator==(const entry_base_t& entry) { + return this == &entry; + } + bool operator!=(const entry_base_t& entry) { + return ! (*this == entry); + } + + virtual void add_transaction(transaction_t * xact); + virtual bool remove_transaction(transaction_t * xact); + + virtual bool finalize(); + virtual bool valid() const = 0; +}; + +class entry_t : public entry_base_t +{ + public: + moment_t _date; + moment_t _date_eff; + string code; + string payee; + + mutable void * data; + + entry_t() : data(NULL) { + TRACE_CTOR(entry_t, ""); + } + entry_t(const entry_t& e); + + virtual ~entry_t() { + TRACE_DTOR(entry_t); + } + + moment_t actual_date() const { + return _date; + } + moment_t effective_date() const { + if (! is_valid_moment(_date_eff)) + return _date; + return _date_eff; + } + moment_t date() const { + if (transaction_t::use_effective_date) + return effective_date(); + else + return actual_date(); + } + + virtual void add_transaction(transaction_t * xact); + + virtual bool valid() const; + + bool get_state(transaction_t::state_t * state) const; +}; + +struct entry_finalizer_t { + virtual ~entry_finalizer_t() {} + virtual bool operator()(entry_t& entry, bool post) = 0; +}; + +void print_entry(std::ostream& out, const entry_base_t& entry, + const string& prefix = ""); + +#if 0 +class entry_context : public error_context { + public: + const entry_base_t& entry; + + entry_context(const entry_base_t& _entry, + const string& _desc = "") throw() + : error_context(_desc), entry(_entry) {} + virtual ~entry_context() throw() {} + + virtual void describe(std::ostream& out) const throw(); +}; +#endif + +DECLARE_EXCEPTION(balance_exception); + + +class auto_entry_t : public entry_base_t +{ +public: + xml::xpath_t predicate; + + auto_entry_t() { + TRACE_CTOR(auto_entry_t, ""); + } + auto_entry_t(const string& _predicate) + : predicate(_predicate) { + TRACE_CTOR(auto_entry_t, "const string&"); + } + + virtual ~auto_entry_t() { + TRACE_DTOR(auto_entry_t); + } + + virtual void extend_entry(entry_base_t& entry, bool post); + virtual bool valid() const { + return true; + } +}; + +struct auto_entry_finalizer_t : public entry_finalizer_t { + journal_t * journal; + auto_entry_finalizer_t(journal_t * _journal) : journal(_journal) {} + virtual bool operator()(entry_t& entry, bool post); +}; + + +class period_entry_t : public entry_base_t +{ + public: + interval_t period; + string period_string; + + period_entry_t() { + TRACE_CTOR(period_entry_t, ""); + } + period_entry_t(const string& _period) + : period(_period), period_string(_period) { + TRACE_CTOR(period_entry_t, "const string&"); + } + period_entry_t(const period_entry_t& e) + : entry_base_t(e), period(e.period), period_string(e.period_string) { + TRACE_CTOR(period_entry_t, "copy"); + } + + virtual ~period_entry_t() { + TRACE_DTOR(period_entry_t); + } + + virtual bool valid() const { + return period; + } +}; + + +typedef std::map accounts_map; +typedef std::pair accounts_pair; + +class account_t +{ + public: + typedef unsigned long ident_t; + + journal_t * journal; + account_t * parent; + string name; + string note; + unsigned short depth; + accounts_map accounts; + + mutable void * data; + mutable ident_t ident; + mutable string _fullname; + + account_t(account_t * _parent = NULL, + const string& _name = "", + const string& _note = "") + : parent(_parent), name(_name), note(_note), + depth(parent ? parent->depth + 1 : 0), data(NULL), ident(0) { + TRACE_CTOR(account_t, "account_t *, const string&, const string&"); + } + ~account_t(); + + bool operator==(const account_t& account) { + return this == &account; + } + bool operator!=(const account_t& account) { + return ! (*this == account); + } + + string fullname() const; + + void add_account(account_t * acct) { + accounts.insert(accounts_pair(acct->name, acct)); + acct->journal = journal; + } + bool remove_account(account_t * acct) { + accounts_map::size_type n = accounts.erase(acct->name); + acct->journal = NULL; + return n > 0; + } + + account_t * find_account(const string& name, bool auto_create = true); + + operator string() const { + return fullname(); + } + + bool valid() const; + + friend class journal_t; +}; + +std::ostream& operator<<(std::ostream& out, const account_t& account); + + +struct func_finalizer_t : public entry_finalizer_t { + typedef bool (*func_t)(entry_t& entry, bool post); + func_t func; + func_finalizer_t(func_t _func) : func(_func) {} + func_finalizer_t(const func_finalizer_t& other) : + entry_finalizer_t(), func(other.func) {} + virtual bool operator()(entry_t& entry, bool post) { + return func(entry, post); + } +}; + +template +void add_hook(std::list& list, T obj, const bool prepend = false) { + if (prepend) + list.push_front(obj); + else + list.push_back(obj); +} + +template +void remove_hook(std::list& list, T obj) { + list.remove(obj); +} + +template +bool run_hooks(std::list& list, Data& item, bool post) { + for (typename std::list::const_iterator i = list.begin(); + i != list.end(); + i++) + if (! (*(*i))(item, post)) + return false; + return true; +} + + +typedef std::list entries_list; +typedef std::list auto_entries_list; +typedef std::list period_entries_list; +typedef std::list strings_list; + +class session_t; + +class journal_t +{ + public: + session_t * session; + account_t * master; + account_t * basket; + entries_list entries; + strings_list sources; + string price_db; + char * item_pool; + char * item_pool_end; + + // This is used for dynamically representing the journal data as an + // XML tree, to facilitate transformations without modifying any of + // the underlying structures (the transformers modify the XML tree + // -- perhaps even adding, changing or deleting nodes -- but they do + // not affect the basic data parsed from the journal file). + xml::document_t * document; + + auto_entries_list auto_entries; + period_entries_list period_entries; + mutable accounts_map accounts_cache; + + std::list entry_finalize_hooks; + + journal_t(session_t * _session) + : session(_session), basket(NULL), + item_pool(NULL), item_pool_end(NULL), document(NULL) { + TRACE_CTOR(journal_t, ""); + master = new account_t(NULL, ""); + master->journal = this; + } + ~journal_t(); + + bool operator==(const journal_t& journal) { + return this == &journal; + } + bool operator!=(const journal_t& journal) { + return ! (*this == journal); + } + + void add_account(account_t * acct) { + master->add_account(acct); + acct->journal = this; + } + bool remove_account(account_t * acct) { + return master->remove_account(acct); + acct->journal = NULL; + } + + account_t * find_account(const string& name, bool auto_create = true) { + accounts_map::iterator c = accounts_cache.find(name); + if (c != accounts_cache.end()) + return (*c).second; + + account_t * account = master->find_account(name, auto_create); + accounts_cache.insert(accounts_pair(name, account)); + account->journal = this; + return account; + } + account_t * find_account_re(const string& regexp); + + bool add_entry(entry_t * entry); + bool remove_entry(entry_t * entry); + + void add_entry_finalizer(entry_finalizer_t * finalizer) { + add_hook(entry_finalize_hooks, finalizer); + } + void remove_entry_finalizer(entry_finalizer_t * finalizer) { + remove_hook(entry_finalize_hooks, finalizer); + } + + bool valid() const; +}; + +inline void extend_entry_base(journal_t * journal, entry_base_t& entry, + bool post) { + for (auto_entries_list::iterator i = journal->auto_entries.begin(); + i != journal->auto_entries.end(); + i++) + (*i)->extend_entry(entry, post); +} + +inline bool auto_entry_finalizer_t::operator()(entry_t& entry, bool post) { + extend_entry_base(journal, entry, post); + return true; +} + +extern const string version; + +} // namespace ledger + +#endif // _JOURNAL_H diff --git a/src/ledger.h b/src/ledger.h new file mode 100644 index 00000000..2122ece3 --- /dev/null +++ b/src/ledger.h @@ -0,0 +1,42 @@ +#ifndef _LEDGER_H +#define _LEDGER_H + +////////////////////////////////////////////////////////////////////// +// +// Ledger Accounting Tool +// +// A command-line tool for general double-entry accounting. +// +// Copyright (c) 2003,2004 John Wiegley +// + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#if 0 +#include +#include +#include +#include +#endif + +#endif // _LEDGER_H diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 00000000..0ca984c3 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,484 @@ +#if defined(USE_BOOST_PYTHON) +#include +#else +#include +#endif +#include + +#include "acconf.h" + +#ifdef HAVE_UNIX_PIPES +#include +#include +#include "fdstream.hpp" +#endif + +using namespace ledger; + +#if 0 +class print_addr : public repitem_t::select_callback_t { + virtual void operator()(repitem_t * item) { + std::cout << item << std::endl; + } +}; +#endif + +static int read_and_report(report_t * report, int argc, char * argv[], + char * envp[]) +{ + session_t& session(*report->session); + + // Handle the command-line arguments + + std::list args; + process_arguments(argc - 1, argv + 1, false, report, args); + + if (args.empty()) { +#if 0 + help(std::cerr); +#endif + return 1; + } + strings_list::iterator arg = args.begin(); + + if (session.cache_file == "") + session.use_cache = false; + else + session.use_cache = session.data_file.empty() && session.price_db.empty(); + + DEBUG_("ledger.session.cache", "1. use_cache = " << session.use_cache); + + // Process the environment settings + + TRACE_START(environment, 1, "Processed environment variables"); + process_environment(const_cast(envp), "LEDGER_", report); + TRACE_FINISH(environment, 1); + + const char * p = std::getenv("HOME"); + string home = p ? p : ""; + + if (session.init_file.empty()) + session.init_file = home + "/.ledgerrc"; + if (session.price_db.empty()) + session.price_db = home + "/.pricedb"; + + if (session.cache_file.empty()) + session.cache_file = home + "/.ledger-cache"; + + if (session.data_file == session.cache_file) + session.use_cache = false; + + DEBUG_("ledger.session.cache", "2. use_cache = " << session.use_cache); + + INFO("Initialization file is " << session.init_file); + INFO("Price database is " << session.price_db); + INFO("Binary cache is " << session.cache_file); + INFO("Journal file is " << session.data_file); + + if (! session.use_cache) + INFO("Binary cache mechanism will not be used"); + + // Read the command word and create a command object based on it + + string verb = *arg++; + + std::auto_ptr command; + + if (verb == "register" || verb == "reg" || verb == "r") { +#if 1 + command.reset(new register_command); +#else + command = new format_command + ("register", either_or(report->format_string, + report->session->register_format)); +#endif + } +#if 0 + else if (verb == "balance" || verb == "bal" || verb == "b") { + if (! report->raw_mode) { + report->transforms.push_back(new accounts_transform); + report->transforms.push_back(new clean_transform); + report->transforms.push_back(new compact_transform); + } + command = new format_command + ("balance", either_or(report->format_string, + report->session->balance_format)); + } + else if (verb == "print" || verb == "p") { + if (! report->raw_mode) + report->transforms.push_back(new optimize_transform); + command = new format_command + ("print", either_or(report->format_string, + report->session->print_format)); + } + else if (verb == "equity") { + if (! report->raw_mode) + report->transforms.push_back(new accounts_transform); + command = new format_command + ("equity", either_or(report->format_string, + report->session->equity_format)); + } + else if (verb == "entry") + command = new entry_command; + else if (verb == "dump") + command = new dump_command; + else if (verb == "output") + command = new output_command; + else if (verb == "prices") + command = new prices_command; + else if (verb == "pricesdb") + command = new pricesdb_command; + else if (verb == "csv") + command = new csv_command; + else if (verb == "emacs" || verb == "lisp") + command = new emacs_command; +#endif + else if (verb == "xml") + command.reset(new xml_command); + else if (verb == "expr") + ; + else if (verb == "xpath") + ; + else if (verb == "parse") { + xml::xpath_t expr(*arg); + + IF_INFO() { + std::cout << "Value expression tree:" << std::endl; + expr.dump(std::cout); + std::cout << std::endl; + std::cout << "Value expression parsed was:" << std::endl; + expr.write(std::cout); + std::cout << std::endl << std::endl; + std::cout << "Result of calculation: "; + } + + std::cout << expr.calc((xml::document_t *)NULL, report). + strip_annotations() << std::endl; + + return 0; + } + else { + char buf[128]; + std::strcpy(buf, "command_"); + std::strcat(buf, verb.c_str()); + + // jww (2007-04-19): This is an error, since command is an + // auto_ptr! + if (xml::xpath_t::op_t * def = report->lookup(buf)) + command.reset(def->functor_obj()); + + if (! command.get()) + throw_(exception, string("Unrecognized command '") + verb + "'"); + } + + // Parse the initialization file, which can only be textual; then + // parse the journal data. + + session.read_init(); + + INFO_START(journal, "Read journal file"); + journal_t * journal = session.read_data(report->account); + INFO_FINISH(journal); + + TRACE_FINISH(entry_text, 1); + TRACE_FINISH(entry_date, 1); + TRACE_FINISH(entry_details, 1); + TRACE_FINISH(entry_xacts, 1); + TRACE_FINISH(entries, 1); + TRACE_FINISH(parsing_total, 1); + + // Configure the output stream + +#ifdef HAVE_UNIX_PIPES + int status, pfd[2]; // Pipe file descriptors +#endif + std::ostream * out = &std::cout; + + if (! report->output_file.empty()) { + out = new std::ofstream(report->output_file.c_str()); + } +#ifdef HAVE_UNIX_PIPES + else if (! report->pager.empty()) { + status = pipe(pfd); + if (status == -1) + throw_(exception, "Failed to create pipe"); + + status = fork(); + if (status < 0) { + throw_(exception, "Failed to fork child process"); + } + else if (status == 0) { // child + const char *arg0; + + // Duplicate pipe's reading end into stdin + status = dup2(pfd[0], STDIN_FILENO); + if (status == -1) + perror("dup2"); + + // Close unuseful file descriptors: the pipe's writing and + // reading ends (the latter is not needed anymore, after the + // duplication). + close(pfd[1]); + close(pfd[0]); + + // Find command name: its the substring starting right of the + // rightmost '/' character in the pager pathname. See manpage + // for strrchr. + arg0 = std::strrchr(report->pager.c_str(), '/'); + if (arg0) + arg0++; + else + arg0 = report->pager.c_str(); // No slashes in pager. + + execlp(report->pager.c_str(), arg0, (char *)0); + perror("execl"); + exit(1); + } + else { // parent + close(pfd[0]); + out = new boost::fdostream(pfd[1]); + } + } +#endif + + // Are we handling the expr commands? Do so now. + + if (verb == "expr") { + xml::xpath_t expr(*arg); + + IF_INFO() { + *out << "Value expression tree:" << std::endl; + expr.dump(*out); + *out << std::endl; + *out << "Value expression parsed was:" << std::endl; + expr.write(*out); + *out << std::endl << std::endl; + *out << "Result of calculation: "; + } + + *out << expr.calc((xml::document_t *)NULL, report). + strip_annotations() << std::endl; + + return 0; + } + else if (verb == "xpath") { + std::cout << "XPath parsed:" << std::endl; + xml::xpath_t xpath(*arg); + xpath.write(*out); + *out << std::endl; + +#if 0 + std::auto_ptr items(repitem_t::wrap(&session, report, true)); + print_addr cb; + items->select(path.get(), cb); +#endif + return 0; + } + + // Create the an argument scope containing the report command's + // arguments, and then invoke the command. + + std::auto_ptr locals + (new xml::xpath_t::scope_t(report, xml::xpath_t::scope_t::ARGUMENT)); + + locals->args = new value_t::sequence_t; + locals->args.push_back(out); + locals->args.push_back(journal->document); + + if (command->wants_args) { + for (strings_list::iterator i = args.begin(); + i != args.end(); + i++) + locals->args.push_back(*i); + } else { + string regexps[4]; + + // Treat the remaining command-line arguments as regular + // expressions, used for refining report results. + + int base = 0; + for (strings_list::iterator i = arg; i != args.end(); i++) + if ((*i)[0] == '-') { + if ((*i)[1] == '-') { + if (base == 0) + base += 2; + continue; + } + if (! regexps[base + 1].empty()) + regexps[base + 1] += "|"; + regexps[base + 1] += (*i).substr(1); + } else { + if (! regexps[base].empty()) + regexps[base] += "|"; + regexps[base] += *i; + } + +#if 0 + // jww (2006-09-21): Escape the \ in these strings! + + if (! regexps[3].empty()) + report->transforms.push_front + (new remove_transform + (string("//entry[payee =~ /(") + regexps[3] + ")/]")); + + if (! regexps[2].empty()) + report->transforms.push_front + (new select_transform + (string("//entry[payee =~ /(") + regexps[2] + ")/]")); + + if (! regexps[1].empty()) + report->transforms.push_front + (new remove_transform + (string("//xact[account =~ /(") + regexps[1] + ")/]")); + + if (! regexps[0].empty()) + report->transforms.push_front + (new select_transform + (string("//xact[account =~ /(") + regexps[0] + ")/]")); +#endif + } + + INFO_START(transforms, "Applied transforms"); + report->apply_transforms(journal->document); + INFO_FINISH(transforms); + + INFO_START(command, "Did user command '" << verb << "'"); + value_t temp; + (*command)(temp, locals.get()); + INFO_FINISH(command); + + // Write out the binary cache, if need be + + if (session.use_cache && session.cache_dirty && + ! session.cache_file.empty()) { + TRACE_START(binary_cache, 1, "Wrote binary journal file"); + + std::ofstream stream(session.cache_file.c_str()); +#if 0 + write_binary_journal(stream, journal); +#endif + + TRACE_FINISH(binary_cache, 1); + } + +#if defined(FREE_MEMORY) + // Cleanup memory -- if this is a beta or development build. + + if (! report->output_file.empty()) + delete out; +#endif + + // If the user specified a pager, wait for it to exit now + +#ifdef HAVE_UNIX_PIPES + if (report->output_file.empty() && ! report->pager.empty()) { + delete out; + close(pfd[1]); + + // Wait for child to finish + wait(&status); + if (status & 0xffff != 0) + throw_(exception, "Something went wrong in the pager"); + } +#endif + + return 0; +} + +int main(int argc, char * argv[], char * envp[]) +{ + int status = 1; + + for (int i = 1; i < argc; i++) + if (argv[i][0] == '-') { +#if defined(VERIFY_ON) + if (std::strcmp(argv[i], "--verify") == 0) + ledger::verify_enabled = true; +#endif +#if defined(DEBUG_ON) + if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { + ledger::_log_level = LOG_DEBUG; + ledger::_log_category = argv[i + 1]; + i++; + } +#endif +#if defined(TRACING_ON) + if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) { + ledger::_log_level = LOG_TRACE; + ledger::_trace_level = std::atoi(argv[i + 1]); + i++; + } +#endif + } + + try { + std::ios::sync_with_stdio(false); + + ledger::initialize(); + +#if ! defined(FULL_DEBUG) + ledger::do_cleanup = false; +#endif + INFO("Ledger starting"); + + std::auto_ptr session(new ledger::session_t); + +#if 0 + session->register_parser(new binary_parser_t); +#endif +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + session->register_parser(new xml::xml_parser_t); + session->register_parser(new gnucash_parser_t); +#endif +#ifdef HAVE_LIBOFX + session->register_parser(new ofx_parser_t); +#endif + session->register_parser(new qif_parser_t); + session->register_parser(new textual_parser_t); + + std::auto_ptr report(new ledger::report_t(session.get())); + + status = read_and_report(report.get(), argc, argv, envp); + + if (! ledger::do_cleanup) { + report.release(); + session.release(); + } + } +#if 0 + catch (error * err) { + std::cout.flush(); + // Push a null here since there's no file context + if (err->context.empty() || + ! dynamic_cast(err->context.front())) + err->context.push_front(new error_context("")); + err->reveal_context(std::cerr, "Error"); + std::cerr << err->what() << std::endl; + delete err; + } + catch (fatal * err) { + std::cout.flush(); + // Push a null here since there's no file context + if (err->context.empty() || + ! dynamic_cast(err->context.front())) + err->context.push_front(new error_context("")); + err->reveal_context(std::cerr, "Fatal"); + std::cerr << err->what() << std::endl; + delete err; + } +#endif + catch (const std::exception& err) { + std::cout.flush(); + std::cerr << "Error: " << err.what() << std::endl; + } + catch (int _status) { + status = _status; + } + + if (ledger::do_cleanup) + ledger::shutdown(); + + return status; +} + +// main.cc ends here. diff --git a/src/mask.cc b/src/mask.cc new file mode 100644 index 00000000..02cac880 --- /dev/null +++ b/src/mask.cc @@ -0,0 +1,24 @@ +#include "mask.h" + +namespace ledger { + +mask_t::mask_t(const string& pat) : exclude(false) +{ + const char * p = pat.c_str(); + + if (*p == '-') { + exclude = true; + p++; + while (std::isspace(*p)) + p++; + } + else if (*p == '+') { + p++; + while (std::isspace(*p)) + p++; + } + + expr.assign(p); +} + +} // namespace ledger diff --git a/src/mask.h b/src/mask.h new file mode 100644 index 00000000..82634c19 --- /dev/null +++ b/src/mask.h @@ -0,0 +1,26 @@ +#ifndef _MASK_H +#define _MASK_H + +#include "utils.h" + +#include + +namespace ledger { + +class mask_t +{ + public: + bool exclude; + boost::regex expr; + + explicit mask_t(const string& pattern); + mask_t(const mask_t& m) : exclude(m.exclude), expr(m.expr) {} + + bool match(const string& str) const { + return boost::regex_match(str, expr) && ! exclude; + } +}; + +} // namespace ledger + +#endif // _MASK_H diff --git a/src/ofx.cc b/src/ofx.cc new file mode 100644 index 00000000..f0bce1f2 --- /dev/null +++ b/src/ofx.cc @@ -0,0 +1,218 @@ +#include "ofx.h" + +namespace ledger { + +typedef std::map accounts_map; +typedef std::pair accounts_pair; + +typedef std::map commodities_map; +typedef std::pair commodities_pair; + +journal_t * curr_journal; +accounts_map ofx_accounts; +commodities_map ofx_account_currencies; +commodities_map ofx_securities; +account_t * master_account; + +int ofx_proc_statement_cb(struct OfxStatementData data, void * statement_data) +{ +} + +int ofx_proc_account_cb(struct OfxAccountData data, void * account_data) +{ + if (! data.account_id_valid) + return -1; + + DEBUG_("ledger.ofx.parse", "account " << data.account_name); + account_t * account = new account_t(master_account, data.account_name); + curr_journal->add_account(account); + ofx_accounts.insert(accounts_pair(data.account_id, account)); + + if (data.currency_valid) { + commodity_t * commodity = commodity_t::find_or_create(data.currency); + commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED); + + commodities_map::iterator i = ofx_account_currencies.find(data.account_id); + if (i == ofx_account_currencies.end()) + ofx_account_currencies.insert(commodities_pair(data.account_id, + commodity)); + } + + return 0; +} + +int ofx_proc_transaction_cb(struct OfxTransactionData data, + void * transaction_data) +{ + if (! data.account_id_valid || ! data.units_valid) + return -1; + + accounts_map::iterator i = ofx_accounts.find(data.account_id); + assert(i != ofx_accounts.end()); + account_t * account = (*i).second; + + entry_t * entry = new entry_t; + + entry->add_transaction(new transaction_t(account)); + transaction_t * xact = entry->transactions.back(); + + // get the account's default currency + commodities_map::iterator ac = ofx_account_currencies.find(data.account_id); + assert(ac != ofx_account_currencies.end()); + commodity_t * default_commodity = (*ac).second; + + std::ostringstream stream; + stream << - data.units; + + // jww (2005-02-09): what if the amount contains fees? + + if (data.unique_id_valid) { + commodities_map::iterator s = ofx_securities.find(data.unique_id); + assert(s != ofx_securities.end()); + xact->amount = stream.str() + " " + (*s).second->base_symbol(); + } else { + xact->amount = stream.str() + " " + default_commodity->base_symbol(); + } + + if (data.unitprice_valid && data.unitprice != 1.0) { + std::ostringstream cstream; + stream << - data.unitprice << " " << default_commodity->base_symbol(); + xact->cost = new amount_t(stream.str()); + } + + DEBUG_("ofx.parse", "xact " << xact->amount << " from " << *xact->account); + + if (data.date_initiated_valid) + entry->_date = data.date_initiated; + else if (data.date_posted_valid) + entry->_date = data.date_posted; + + if (data.check_number_valid) + entry->code = data.check_number; + else if (data.reference_number_valid) + entry->code = data.reference_number; + + if (data.name_valid) + entry->payee = data.name; + + if (data.memo_valid) + xact->note = data.memo; + + // jww (2005-02-09): check for fi_id_corrected? or is this handled + // by the library? + + // Balance all entries into , since it is not specified. + account = curr_journal->find_account(""); + entry->add_transaction(new transaction_t(account)); + + if (! curr_journal->add_entry(entry)) { + print_entry(std::cerr, *entry); +#if 0 + // jww (2005-02-09): uncomment + have_error = "The above entry does not balance"; +#endif + delete entry; + return -1; + } + return 0; +} + +int ofx_proc_security_cb(struct OfxSecurityData data, void * security_data) +{ + if (! data.unique_id_valid) + return -1; + + string symbol; + if (data.ticker_valid) + symbol = data.ticker; + else if (data.currency_valid) + symbol = data.currency; + else + return -1; + + commodity_t * commodity = commodity_t::find_or_create(symbol); + commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED); + + if (data.secname_valid) + commodity->set_name(data.secname); + + if (data.memo_valid) + commodity->set_note(data.memo); + + commodities_map::iterator i = ofx_securities.find(data.unique_id); + if (i == ofx_securities.end()) { + DEBUG_("ledger.ofx.parse", "security " << symbol); + ofx_securities.insert(commodities_pair(data.unique_id, commodity)); + } + + // jww (2005-02-09): What is the commodity for data.unitprice? + if (data.date_unitprice_valid && data.unitprice_valid) { + DEBUG_("ledger.ofx.parse", " price " << data.unitprice); + commodity->add_price(data.date_unitprice, amount_t(data.unitprice)); + } + + return 0; +} + +int ofx_proc_status_cb(struct OfxStatusData data, void * status_data) +{ +} + +bool ofx_parser_t::test(std::istream& in) const +{ + char buf[80]; + + in.getline(buf, 79); + if (std::strncmp(buf, "OFXHEADER", 9) == 0) { + in.clear(); + in.seekg(0, std::ios::beg); + return true; + } + else if (std::strncmp(buf, "master; + + LibofxContextPtr libofx_context = libofx_get_new_context(); + + ofx_set_statement_cb (libofx_context, ofx_proc_statement_cb, 0); + ofx_set_account_cb (libofx_context, ofx_proc_account_cb, 0); + ofx_set_transaction_cb(libofx_context, ofx_proc_transaction_cb, 0); + ofx_set_security_cb (libofx_context, ofx_proc_security_cb, 0); + ofx_set_status_cb (libofx_context, ofx_proc_status_cb, 0); + + // The processing is done by way of callbacks, which are all defined + // above. + libofx_proc_file(libofx_context, original_file->c_str(), AUTODETECT); + + libofx_free_context(libofx_context); + + return 1; // jww (2005-02-09): count; +} + +} // namespace ledger diff --git a/src/ofx.h b/src/ofx.h new file mode 100644 index 00000000..54713c17 --- /dev/null +++ b/src/ofx.h @@ -0,0 +1,21 @@ +#ifndef _OFX_H +#define _OFX_H + +#include "parser.h" + +namespace ledger { + +class ofx_parser_t : public parser_t +{ + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); +}; + +} // namespace ledger + +#endif // _OFX_H diff --git a/src/option.cc b/src/option.cc new file mode 100644 index 00000000..222baf92 --- /dev/null +++ b/src/option.cc @@ -0,0 +1,219 @@ +#include "option.h" + +#if 0 +#ifdef USE_BOOST_PYTHON +static ledger::option_t * find_option(const string& name); +#endif +#endif + +namespace ledger { + +namespace { + xml::xpath_t::op_t * find_option(xml::xpath_t::scope_t * scope, + const string& name) + { + char buf[128]; + std::strcpy(buf, "option_"); + char * p = &buf[7]; + for (const char * q = name.c_str(); *q; q++) { + if (*q == '-') + *p++ = '_'; + else + *p++ = *q; + } + *p = '\0'; + + return scope->lookup(buf); + } + + xml::xpath_t::op_t * find_option(xml::xpath_t::scope_t * scope, + const char letter) + { + char buf[9]; + std::strcpy(buf, "option_"); + buf[7] = letter; + buf[8] = '\0'; + + return scope->lookup(buf); + } + + void process_option(xml::xpath_t::functor_t * opt, xml::xpath_t::scope_t * scope, + const char * arg) + { +#if 0 + try { +#endif + std::auto_ptr args; + if (arg) { + args.reset(new xml::xpath_t::scope_t(scope, xml::xpath_t::scope_t::ARGUMENT)); + args->args.set_string(arg); + } + + value_t temp; + (*opt)(temp, args.get()); +#if 0 + } + catch (error * err) { + err->context.push_back + (new error_context + (string("While parsing option '--") + opt->long_opt + + "'" + (opt->short_opt != '\0' ? + (string(" (-") + opt->short_opt + "):") : ":"))); + throw err; + } +#endif + } +} + +bool process_option(const string& name, xml::xpath_t::scope_t * scope, + const char * arg) +{ + std::auto_ptr opt(find_option(scope, name)); + if (opt.get()) { + xml::xpath_t::functor_t * def = opt->functor_obj(); + if (def) { + process_option(def, scope, arg); + return true; + } + } + return false; +} + +void process_environment(const char ** envp, const string& tag, + xml::xpath_t::scope_t * scope) +{ + const char * tag_p = tag.c_str(); + unsigned int tag_len = tag.length(); + + for (const char ** p = envp; *p; p++) + if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) { + char buf[128]; + char * r = buf; + const char * q; + for (q = *p + tag_len; + *q && *q != '=' && r - buf < 128; + q++) + if (*q == '_') + *r++ = '-'; + else + *r++ = std::tolower(*q); + *r = '\0'; + + if (*q == '=') { +#if 0 + try { +#endif + if (! process_option(buf, scope, q + 1)) +#if 0 + throw new option_error("unknown option") +#endif + ; +#if 0 + } + catch (error * err) { + err->context.push_back + (new error_context + (string("While parsing environment variable option '") + + *p + "':")); + throw err; + } +#endif + } + } +} + +void process_arguments(int argc, char ** argv, const bool anywhere, + xml::xpath_t::scope_t * scope, + std::list& args) +{ + for (char ** i = argv; *i; i++) { + if ((*i)[0] != '-') { + if (anywhere) { + args.push_back(*i); + continue; + } else { + for (; *i; i++) + args.push_back(*i); + break; + } + } + + // --long-option or -s + again: + if ((*i)[1] == '-') { + if ((*i)[2] == '\0') + break; + + char * name = *i + 2; + char * value = NULL; + if (char * p = std::strchr(name, '=')) { + *p++ = '\0'; + value = p; + } + + std::auto_ptr opt(find_option(scope, name)); + if (! opt.get()) + throw_(option_exception, "illegal option --" << name); + + xml::xpath_t::functor_t * def = opt->functor_obj(); + if (! def) + throw_(option_exception, "illegal option --" << name); + + if (def->wants_args && value == NULL) { + value = *++i; + if (value == NULL) + throw_(option_exception, "missing option argument for --" << name); + } + process_option(def, scope, value); + } + else if ((*i)[1] == '\0') { + throw_(option_exception, "illegal option -"); + } + else { + std::list option_queue; + + int x = 1; + for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) { + xml::xpath_t::op_t * opt = find_option(scope, c); + if (! opt) + throw_(option_exception, "illegal option -" << c); + + xml::xpath_t::functor_t * def = opt->functor_obj(); + if (! def) + throw_(option_exception, "illegal option -" << c); + + option_queue.push_back(opt); + } + + for (std::list::iterator + o = option_queue.begin(); + o != option_queue.end(); + o++) { + char * value = NULL; + + xml::xpath_t::functor_t * def = (*o)->functor_obj(); + assert(def); + + if (def->wants_args) { + value = *++i; + if (value == NULL) + throw_(option_exception, "missing option argument for -" << +#if 0 + def->short_opt +#else + '?' +#endif + ); + } + process_option(def, scope, value); + + delete *o; + } + } + + next: + ; + } +} + +} // namespace ledger diff --git a/src/option.h b/src/option.h new file mode 100644 index 00000000..c9664ae3 --- /dev/null +++ b/src/option.h @@ -0,0 +1,22 @@ +#ifndef _OPTION_H +#define _OPTION_H + +#include "xpath.h" + +namespace ledger { + +bool process_option(const string& name, xml::xpath_t::scope_t * scope, + const char * arg = NULL); + +void process_environment(const char ** envp, const string& tag, + xml::xpath_t::scope_t * scope); + +void process_arguments(int argc, char ** argv, const bool anywhere, + xml::xpath_t::scope_t * scope, + std::list& args); + +DECLARE_EXCEPTION(option_exception); + +} // namespace ledger + +#endif // _OPTION_H diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 00000000..2bdc4622 --- /dev/null +++ b/src/parser.h @@ -0,0 +1,86 @@ +#ifndef _PARSER_H +#define _PARSER_H + +#include "utils.h" + +namespace ledger { + +class account_t; +class journal_t; + +class parser_t +{ + public: + virtual ~parser_t() {} + + virtual bool test(std::istream& in) const = 0; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL) = 0; +}; + +DECLARE_EXCEPTION(parse_exception); + +/************************************************************************ + * + * General utility parsing functions + */ + +inline char * skip_ws(char * ptr) { + while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') + ptr++; + return ptr; +} + +inline char peek_next_nonws(std::istream& in) { + char c = in.peek(); + while (! in.eof() && std::isspace(c)) { + in.get(c); + c = in.peek(); + } + return c; +} + +#define READ_INTO(str, targ, size, var, cond) { \ + char * _p = targ; \ + var = str.peek(); \ + while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \ + str.get(var); \ + if (str.eof()) \ + break; \ + if (var == '\\') { \ + str.get(var); \ + if (in.eof()) \ + break; \ + } \ + *_p++ = var; \ + var = str.peek(); \ + } \ + *_p = '\0'; \ +} + +#define READ_INTO_(str, targ, size, var, idx, cond) { \ + char * _p = targ; \ + var = str.peek(); \ + while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \ + str.get(var); \ + if (str.eof()) \ + break; \ + idx++; \ + if (var == '\\') { \ + str.get(var); \ + if (in.eof()) \ + break; \ + idx++; \ + } \ + *_p++ = var; \ + var = str.peek(); \ + } \ + *_p = '\0'; \ +} + +} // namespace ledger + +#endif // _PARSER_H diff --git a/src/py_amount.cc b/src/py_amount.cc new file mode 100644 index 00000000..1d9f9255 --- /dev/null +++ b/src/py_amount.cc @@ -0,0 +1,245 @@ +#include "pyinterp.h" +#include "amount.h" + +using namespace boost::python; + +namespace ledger { + +int py_amount_quantity(amount_t& amount) +{ + std::ostringstream quant; + amount.print_quantity(quant); + return std::atol(quant.str().c_str()); +} + +void py_parse_1(amount_t& amount, const string& str, + unsigned char flags) { + amount.parse(str, flags); +} +void py_parse_2(amount_t& amount, const string& str) { + amount.parse(str); +} + +amount_t py_round_1(amount_t& amount, unsigned int prec) { + return amount.round(prec); +} +amount_t py_round_2(amount_t& amount) { + return amount.round(); +} + +struct commodity_updater_wrap : public commodity_base_t::updater_t +{ + PyObject * self; + commodity_updater_wrap(PyObject * self_) : self(self_) {} + + virtual void operator()(commodity_base_t& commodity, + const moment_t& moment, + const moment_t& date, + const moment_t& last, + amount_t& price) { + call_method(self, "__call__", commodity, moment, date, last, price); + } +}; + +commodity_t * py_find_commodity(const string& symbol) +{ + return commodity_t::find(symbol); +} + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_ArithmeticError, err.what()); \ + } + +EXC_TRANSLATOR(amount_exception) + +void export_amount() +{ + scope().attr("AMOUNT_PARSE_NO_MIGRATE") = AMOUNT_PARSE_NO_MIGRATE; + scope().attr("AMOUNT_PARSE_NO_REDUCE") = AMOUNT_PARSE_NO_REDUCE; + + class_< amount_t > ("amount") + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + + .def(self += self) + .def(self += long()) + .def(self += double()) + + .def(self + self) + .def(self + long()) + .def(long() + self) + .def(self + double()) + .def(double() + self) + + .def(self -= self) + .def(self -= long()) + .def(self -= double()) + + .def(self - self) + .def(self - long()) + .def(long() - self) + .def(self - double()) + .def(double() - self) + + .def(self *= self) + .def(self *= long()) + .def(self *= double()) + + .def(self * self) + .def(self * long()) + .def(long() * self) + .def(self * double()) + .def(double() * self) + + .def(self /= self) + .def(self /= long()) + .def(self /= double()) + + .def(self / self) + .def(self / long()) + .def(long() / self) + .def(self / double()) + .def(double() / self) + + .def(- self) + + .def(self < self) + .def(self < long()) + .def(long() < self) + + .def(self <= self) + .def(self <= long()) + .def(long() <= self) + + .def(self > self) + .def(self > long()) + .def(long() > self) + + .def(self >= self) + .def(self >= long()) + .def(long() >= self) + + .def(self == self) + .def(self == long()) + .def(long() == self) + + .def(self != self) + .def(self != long()) + .def(long() != self) + + .def(! self) + + .def(self_ns::int_(self)) + .def(self_ns::float_(self)) + + .def("__str__", &amount_t::to_string) + .def("__repr__", &amount_t::to_fullstring) + + .def("has_commodity", &amount_t::has_commodity) + + .add_property("commodity", + make_function(&amount_t::commodity, + return_value_policy()), + make_function(&amount_t::set_commodity, + with_custodian_and_ward<1, 2>())) + + .def("annotate_commodity", &amount_t::annotate_commodity) + .def("strip_annotations", &amount_t::strip_annotations) + .def("clear_commodity", &amount_t::clear_commodity) + + //.add_static_property("full_strings", &amount_t::full_strings) + + .def("to_string", &amount_t::to_string) + .def("to_fullstring", &amount_t::to_fullstring) + .def("quantity_string", &amount_t::quantity_string) + + .def("exact", &amount_t::exact) + .staticmethod("exact") + + .def("__abs__", &amount_t::abs) + .def("compare", &amount_t::compare) + .def("date", &amount_t::date) + .def("negate", &amount_t::negate) + .def("null", &amount_t::null) + .def("parse", py_parse_1) + .def("parse", py_parse_2) + .def("price", &amount_t::price) + .def("realzero", &amount_t::realzero) + .def("reduce", &amount_t::reduce) + .def("round", py_round_1) + .def("round", py_round_2) + .def("sign", &amount_t::sign) + .def("unround", &amount_t::unround) + .def("value", &amount_t::value) + .def("zero", &amount_t::zero) + + .def("valid", &amount_t::valid) + + .def("initialize", &amount_t::initialize) + .staticmethod("initialize") + .def("shutdown", &amount_t::shutdown) + .staticmethod("shutdown") + ; + + class_< commodity_base_t::updater_t, commodity_updater_wrap, + boost::noncopyable > + ("updater") + ; + + scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; + scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; + scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; + scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN; + scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; + scope().attr("COMMODITY_STYLE_NOMARKET") = COMMODITY_STYLE_NOMARKET; + scope().attr("COMMODITY_STYLE_BUILTIN") = COMMODITY_STYLE_BUILTIN; + + class_< commodity_t > ("commodity") +#if 0 + .add_property("symbol", &commodity_t::symbol) + + .add_property("name", &commodity_t::name, &commodity_t::set_name) + .add_property("note", &commodity_t::note, &commodity_t::set_note) + .add_property("precision", &commodity_t::precision, + &commodity_t::set_precision) + .add_property("flags", &commodity_t::flags, &commodity_t::set_flags) + .add_property("add_flags", &commodity_t::add_flags) + .add_property("drop_flags", &commodity_t::drop_flags) + //.add_property("updater", &commodity_t::updater) + + .add_property("smaller", + make_getter(&commodity_t::smaller, + return_value_policy()), + make_setter(&commodity_t::smaller, + return_value_policy())) + .add_property("larger", + make_getter(&commodity_t::larger, + return_value_policy()), + make_setter(&commodity_t::larger, + return_value_policy())) + + .def(self_ns::str(self)) + + .def("find", py_find_commodity, + return_value_policy()) + .staticmethod("find") + + .def("add_price", &commodity_t::add_price) + .def("remove_price", &commodity_t::remove_price) + .def("value", &commodity_t::value) + + .def("valid", &commodity_t::valid) +#endif + ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator(&exc_translate_ ## type); + + EXC_TRANSLATE(amount_exception); +} + +} // namespace ledger diff --git a/src/py_balance.cc b/src/py_balance.cc new file mode 100644 index 00000000..372bf1e9 --- /dev/null +++ b/src/py_balance.cc @@ -0,0 +1,202 @@ +using namespace boost::python; +using namespace ledger; + +unsigned int balance_len(balance_t& bal) +{ + return bal.amounts.size(); +} + +amount_t balance_getitem(balance_t& bal, int i) +{ + std::size_t len = bal.amounts.size(); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + int x = i < 0 ? len + i : i; + amounts_map::iterator elem = bal.amounts.begin(); + while (--x >= 0) + elem++; + + return (*elem).second; +} + +unsigned int balance_pair_len(balance_pair_t& bal_pair) +{ + return balance_len(bal_pair.quantity); +} + +amount_t balance_pair_getitem(balance_pair_t& bal_pair, int i) +{ + return balance_getitem(bal_pair.quantity, i); +} + +void export_balance() +{ + class_< balance_t > ("Balance") + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + + .def(self += self) + .def(self += other()) + .def(self += long()) + .def(self + self) + .def(self + other()) + .def(self + long()) + .def(self -= self) + .def(self -= other()) + .def(self -= long()) + .def(self - self) + .def(self - other()) + .def(self - long()) + .def(self *= self) + .def(self *= other()) + .def(self *= long()) + .def(self * self) + .def(self * other()) + .def(self * long()) + .def(self /= self) + .def(self /= other()) + .def(self /= long()) + .def(self / self) + .def(self / other()) + .def(self / long()) + .def(- self) + + .def(self < self) + .def(self < other()) + .def(self < long()) + .def(self <= self) + .def(self <= other()) + .def(self <= long()) + .def(self > self) + .def(self > other()) + .def(self > long()) + .def(self >= self) + .def(self >= other()) + .def(self >= long()) + .def(self == self) + .def(self == other()) + .def(self == long()) + .def(self != self) + .def(self != other()) + .def(self != long()) + .def(! self) + + .def(self_ns::str(self)) + + .def("__abs__", &balance_t::abs) + .def("__len__", balance_len) + .def("__getitem__", balance_getitem) + + .def("valid", &balance_t::valid) + + .def("realzero", &balance_t::realzero) + .def("amount", &balance_t::amount) + .def("value", &balance_t::value) + .def("price", &balance_t::price) + .def("date", &balance_t::date) + .def("strip_annotations", &balance_t::strip_annotations) + .def("write", &balance_t::write) + .def("round", &balance_t::round) + .def("negate", &balance_t::negate) + .def("negated", &balance_t::negated) + ; + + class_< balance_pair_t > ("BalancePair") + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + + .def(self += self) + .def(self += other()) + .def(self += other()) + .def(self += long()) + .def(self + self) + .def(self + other()) + .def(self + other()) + .def(self + long()) + .def(self -= self) + .def(self -= other()) + .def(self -= other()) + .def(self -= long()) + .def(self - self) + .def(self - other()) + .def(self - other()) + .def(self - long()) + .def(self *= self) + .def(self *= other()) + .def(self *= other()) + .def(self *= long()) + .def(self * self) + .def(self * other()) + .def(self * other()) + .def(self * long()) + .def(self /= self) + .def(self /= other()) + .def(self /= other()) + .def(self /= long()) + .def(self / self) + .def(self / other()) + .def(self / other()) + .def(self / long()) + .def(- self) + + .def(self < self) + .def(self < other()) + .def(self < other()) + .def(self < long()) + .def(self <= self) + .def(self <= other()) + .def(self <= other()) + .def(self <= long()) + .def(self > self) + .def(self > other()) + .def(self > other()) + .def(self > long()) + .def(self >= self) + .def(self >= other()) + .def(self >= other()) + .def(self >= long()) + .def(self == self) + .def(self == other()) + .def(self == other()) + .def(self == long()) + .def(self != self) + .def(self != other()) + .def(self != other()) + .def(self != long()) + .def(! self) + + .def(self_ns::str(self)) + + .def("__abs__", &balance_pair_t::abs) + .def("__len__", balance_pair_len) + .def("__getitem__", balance_pair_getitem) + + .def("valid", &balance_pair_t::valid) + + .def("realzero", &balance_pair_t::realzero) + .def("amount", &balance_pair_t::amount) + .def("value", &balance_pair_t::value) + .def("price", &balance_pair_t::price) + .def("date", &balance_pair_t::date) + .def("strip_annotations", &balance_pair_t::strip_annotations) + .def("write", &balance_pair_t::write) + .def("round", &balance_pair_t::round) + .def("negate", &balance_pair_t::negate) + .def("negated", &balance_pair_t::negated) + + .add_property("cost", + make_getter(&balance_pair_t::cost, + return_value_policy())) + ; +} diff --git a/src/py_format.cc b/src/py_format.cc new file mode 100644 index 00000000..e2faf5dc --- /dev/null +++ b/src/py_format.cc @@ -0,0 +1,11 @@ +using namespace boost::python; +using namespace ledger; + +void export_format() +{ + class_< format_t > ("Format") + .def(init()) + .def("parse", &format_t::parse) + .def("format", &format_t::format) + ; +} diff --git a/src/py_journal.cc b/src/py_journal.cc new file mode 100644 index 00000000..d309cf95 --- /dev/null +++ b/src/py_journal.cc @@ -0,0 +1,374 @@ +using namespace boost::python; +using namespace ledger; + +entry_t& transaction_entry(const transaction_t& xact) +{ + return *xact.entry; +} + +unsigned int transactions_len(entry_base_t& entry) +{ + return entry.transactions.size(); +} + +transaction_t& transactions_getitem(entry_base_t& entry, int i) +{ + static int last_index = 0; + static entry_base_t * last_entry = NULL; + static transactions_list::iterator elem; + + std::size_t len = entry.transactions.size(); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + if (&entry == last_entry && i == last_index + 1) { + last_index = i; + return **++elem; + } + + int x = i < 0 ? len + i : i; + elem = entry.transactions.begin(); + while (--x >= 0) + elem++; + + last_entry = &entry; + last_index = i; + + return **elem; +} + +unsigned int entries_len(journal_t& journal) +{ + return journal.entries.size(); +} + +entry_t& entries_getitem(journal_t& journal, int i) +{ + static int last_index = 0; + static journal_t * last_journal = NULL; + static entries_list::iterator elem; + + std::size_t len = journal.entries.size(); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + if (&journal == last_journal && i == last_index + 1) { + last_index = i; + return **++elem; + } + + int x = i < 0 ? len + i : i; + elem = journal.entries.begin(); + while (--x >= 0) + elem++; + + last_journal = &journal; + last_index = i; + + return **elem; +} + +unsigned int accounts_len(account_t& account) +{ + return account.accounts.size(); +} + +account_t& accounts_getitem(account_t& account, int i) +{ + static int last_index = 0; + static account_t * last_account = NULL; + static accounts_map::iterator elem; + + std::size_t len = account.accounts.size(); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + if (&account == last_account && i == last_index + 1) { + last_index = i; + return *(*++elem).second; + } + + int x = i < 0 ? len + i : i; + elem = account.accounts.begin(); + while (--x >= 0) + elem++; + + last_account = &account; + last_index = i; + + return *(*elem).second; +} + +PyObject * py_account_get_data(account_t& account) +{ + return (PyObject *) account.data; +} + +void py_account_set_data(account_t& account, PyObject * obj) +{ + account.data = obj; +} + +account_t * py_find_account_1(journal_t& journal, const string& name) +{ + return journal.find_account(name); +} + +account_t * py_find_account_2(journal_t& journal, const string& name, + const bool auto_create) +{ + return journal.find_account(name, auto_create); +} + +bool py_add_entry(journal_t& journal, entry_t * entry) { + return journal.add_entry(new entry_t(*entry)); +} + +void py_add_transaction(entry_base_t& entry, transaction_t * xact) { + return entry.add_transaction(new transaction_t(*xact)); +} + +struct entry_base_wrap : public entry_base_t +{ + PyObject * self; + entry_base_wrap(PyObject * self_) : self(self_) {} + + virtual bool valid() const { + return call_method(self, "valid"); + } +}; + +struct py_entry_finalizer_t : public entry_finalizer_t { + object pyobj; + py_entry_finalizer_t() {} + py_entry_finalizer_t(object obj) : pyobj(obj) {} + py_entry_finalizer_t(const py_entry_finalizer_t& other) + : pyobj(other.pyobj) {} + virtual bool operator()(entry_t& entry, bool post) { + return call(pyobj.ptr(), entry, post); + } +}; + +std::list py_finalizers; + +void py_add_entry_finalizer(journal_t& journal, object x) +{ + py_finalizers.push_back(py_entry_finalizer_t(x)); + journal.add_entry_finalizer(&py_finalizers.back()); +} + +void py_remove_entry_finalizer(journal_t& journal, object x) +{ + for (std::list::iterator i = py_finalizers.begin(); + i != py_finalizers.end(); + i++) + if ((*i).pyobj == x) { + journal.remove_entry_finalizer(&(*i)); + py_finalizers.erase(i); + return; + } +} + +void py_run_entry_finalizers(journal_t& journal, entry_t& entry, bool post) +{ + run_hooks(journal.entry_finalize_hooks, entry, post); +} + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_RuntimeError, err.what()); \ + } + +EXC_TRANSLATOR(balance_error) +EXC_TRANSLATOR(interval_expr_error) +EXC_TRANSLATOR(format_error) +EXC_TRANSLATOR(parse_error) + +value_t py_transaction_amount(transaction_t * xact) { + return value_t(xact->amount); +} + +transaction_t::state_t py_entry_state(entry_t * entry) { + transaction_t::state_t state; + if (entry->get_state(&state)) + return state; + else + return transaction_t::UNCLEARED; +} + +void export_journal() +{ + scope().attr("TRANSACTION_NORMAL") = TRANSACTION_NORMAL; + scope().attr("TRANSACTION_VIRTUAL") = TRANSACTION_VIRTUAL; + scope().attr("TRANSACTION_BALANCE") = TRANSACTION_BALANCE; + scope().attr("TRANSACTION_AUTO") = TRANSACTION_AUTO; + scope().attr("TRANSACTION_BULK_ALLOC") = TRANSACTION_BULK_ALLOC; + scope().attr("TRANSACTION_CALCULATED") = TRANSACTION_CALCULATED; + + enum_< transaction_t::state_t > ("State") + .value("Uncleared", transaction_t::UNCLEARED) + .value("Cleared", transaction_t::CLEARED) + .value("Pending", transaction_t::PENDING) + ; + + class_< transaction_t > ("Transaction") + .def(init >()) + .def(init >()) + + .def(self == self) + .def(self != self) + + .add_property("entry", + make_getter(&transaction_t::entry, + return_value_policy())) + .add_property("account", + make_getter(&transaction_t::account, + return_value_policy())) + + .add_property("amount", &py_transaction_amount) + .def_readonly("amount_expr", &transaction_t::amount_expr) + .add_property("cost", + make_getter(&transaction_t::cost, + return_internal_reference<1>())) + .def_readonly("cost_expr", &transaction_t::cost_expr) + + .def_readwrite("state", &transaction_t::state) + .def_readwrite("flags", &transaction_t::flags) + .def_readwrite("note", &transaction_t::note) + + .def_readonly("beg_pos", &transaction_t::beg_pos) + .def_readonly("beg_line", &transaction_t::beg_line) + .def_readonly("end_pos", &transaction_t::end_pos) + .def_readonly("end_line", &transaction_t::end_line) + + .def("actual_date", &transaction_t::actual_date) + .def("effective_date", &transaction_t::effective_date) + .def("date", &transaction_t::date) + + .def("use_effective_date", &transaction_t::use_effective_date) + + .def("valid", &transaction_t::valid) + ; + + class_< account_t > + ("Account", + init >() + [with_custodian_and_ward<1, 2>()]) + .def(self == self) + .def(self != self) + + .def(self_ns::str(self)) + + .def("__len__", accounts_len) + .def("__getitem__", accounts_getitem, return_internal_reference<1>()) + + .add_property("journal", + make_getter(&account_t::journal, + return_value_policy())) + .add_property("parent", + make_getter(&account_t::parent, + return_value_policy())) + .def_readwrite("name", &account_t::name) + .def_readwrite("note", &account_t::note) + .def_readonly("depth", &account_t::depth) + .add_property("data", py_account_get_data, py_account_set_data) + .def_readonly("ident", &account_t::ident) + + .def("fullname", &account_t::fullname) + + .def("add_account", &account_t::add_account) + .def("remove_account", &account_t::remove_account) + + .def("find_account", &account_t::find_account, + return_value_policy()) + + .def("valid", &account_t::valid) + ; + + class_< journal_t > ("Journal") + .def(self == self) + .def(self != self) + + .def("__len__", entries_len) + .def("__getitem__", entries_getitem, return_internal_reference<1>()) + + .add_property("master", make_getter(&journal_t::master, + return_internal_reference<1>())) + .add_property("basket", make_getter(&journal_t::basket, + return_internal_reference<1>())) + + .def_readonly("sources", &journal_t::sources) + + .def_readwrite("price_db", &journal_t::price_db) + + .def("add_account", &journal_t::add_account) + .def("remove_account", &journal_t::remove_account) + + .def("find_account", py_find_account_1, return_internal_reference<1>()) + .def("find_account", py_find_account_2, return_internal_reference<1>()) + .def("find_account_re", &journal_t::find_account_re, + return_internal_reference<1>()) + + .def("add_entry", py_add_entry) + .def("remove_entry", &journal_t::remove_entry) + + .def("add_entry_finalizer", py_add_entry_finalizer) + .def("remove_entry_finalizer", py_remove_entry_finalizer) + .def("run_entry_finalizers", py_run_entry_finalizers) + + .def("valid", &journal_t::valid) + ; + + class_< entry_base_t, entry_base_wrap, boost::noncopyable > ("EntryBase") + .def("__len__", transactions_len) + .def("__getitem__", transactions_getitem, + return_internal_reference<1>()) + + .def_readonly("journal", &entry_base_t::journal) + + .def_readonly("src_idx", &entry_base_t::src_idx) + .def_readonly("beg_pos", &entry_base_t::beg_pos) + .def_readonly("beg_line", &entry_base_t::beg_line) + .def_readonly("end_pos", &entry_base_t::end_pos) + .def_readonly("end_line", &entry_base_t::end_line) + + .def("add_transaction", py_add_transaction) + .def("remove_transaction", &entry_base_t::remove_transaction) + + .def(self == self) + .def(self != self) + + .def("finalize", &entry_base_t::finalize) + .def("valid", &entry_base_t::valid) + ; + + class_< entry_t, bases > ("Entry") + .add_property("date", &entry_t::date) + .add_property("effective_date", &entry_t::effective_date) + .add_property("actual_date", &entry_t::actual_date) + + .def_readwrite("code", &entry_t::code) + .def_readwrite("payee", &entry_t::payee) + + .add_property("state", &py_entry_state) + + .def("valid", &entry_t::valid) + ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator(&exc_translate_ ## type); + + EXC_TRANSLATE(balance_error); + EXC_TRANSLATE(interval_expr_error); + EXC_TRANSLATE(format_error); + EXC_TRANSLATE(parse_error); +} diff --git a/src/py_option.cc b/src/py_option.cc new file mode 100644 index 00000000..877d92a7 --- /dev/null +++ b/src/py_option.cc @@ -0,0 +1,73 @@ +using namespace boost::python; +using namespace ledger; + +struct py_option_t : public option_t +{ + PyObject * self; + + py_option_t(PyObject * self_, + const string& long_opt, + const bool wants_arg) + : self(self_), option_t(long_opt, wants_arg) {} + + virtual ~py_option_t() {} + + virtual bool check(option_source_t source) { + return call_method(self, "check", source); + } + + virtual void select(report_t * report, const char * optarg = NULL) { + if (optarg) + return call_method(self, "select", report, optarg); + else + return call_method(self, "select", report); + } +}; + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(option_select_overloads, + py_option_t::select, 1, 2) + +typedef std::map options_map; +typedef std::pair options_pair; + +options_map options; + +static option_t * find_option(const string& name) +{ + options_map::const_iterator i = options.find(name); + if (i != options.end()) + return extract((*i).second.ptr()); + + return NULL; +} + +void shutdown_option() +{ + options.clear(); +} + +void export_option() +{ + class_< option_t, py_option_t, boost::noncopyable > + ("Option", init()) + .def_readonly("long_opt", &py_option_t::long_opt) + .def_readonly("short_opt", &py_option_t::short_opt) + .def_readonly("wants_arg", &py_option_t::wants_arg) + .def_readwrite("handled", &py_option_t::handled) + .def("check", &py_option_t::check) + .def("select", &py_option_t::select, option_select_overloads()) + ; + + enum_< option_t::option_source_t > ("OptionSource") + .value("InitFile", option_t::INIT_FILE) + .value("Environment", option_t::ENVIRONMENT) + .value("DataFile", option_t::DATA_FILE) + .value("CommandLine", option_t::COMMAND_LINE) + ; + + class_< options_map > ("OptionsMap") + .def(map_indexing_suite()) + ; + + scope().attr("options") = ptr(&options); +} diff --git a/src/py_parser.cc b/src/py_parser.cc new file mode 100644 index 00000000..f119a0ef --- /dev/null +++ b/src/py_parser.cc @@ -0,0 +1,48 @@ +#include "parser.h" + +#if 0 +#ifdef USE_BOOST_PYTHON + +using namespace boost::python; +using namespace ledger; + +struct py_parser_t : public parser_t +{ + PyObject * self; + py_parser_t(PyObject * self_) : self(self_) {} + + virtual bool test(std::istream& in) const { + return call_method(self, "test", in); + } + + virtual repitem_t * parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL) { + return call_method(self, "parse", in, journal, master, + original_file); + } +}; + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(parser_parse_overloads, + py_parser_t::parse, 2, 4) + +BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_overloads, parse_journal, 2, 4) +BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_file_overloads, + parse_journal_file, 2, 4) + +void export_parser() { + class_< parser_t, py_parser_t, boost::noncopyable > ("Parser") + .def("test", &py_parser_t::test) + .def("parse", &py_parser_t::parse, parser_parse_overloads()) + ; + + def("register_parser", register_parser); + def("unregister_parser", unregister_parser); + + def("parse_journal", parse_journal, parse_journal_overloads()); + def("parse_journal_file", parse_journal_file, parse_journal_file_overloads()); +} + +#endif // USE_BOOST_PYTHON +#endif diff --git a/src/py_report.cc b/src/py_report.cc new file mode 100644 index 00000000..0bc36857 --- /dev/null +++ b/src/py_report.cc @@ -0,0 +1,13 @@ +using namespace boost::python; +using namespace ledger; + +void export_report() +{ + class_< report_t > ("Report") + .add_property("session", + make_getter(&report_t::session, + return_value_policy())) + + .def("apply_transforms", &report_t::apply_transforms) + ; +} diff --git a/src/py_session.cc b/src/py_session.cc new file mode 100644 index 00000000..f249c54e --- /dev/null +++ b/src/py_session.cc @@ -0,0 +1,36 @@ +using namespace boost::python; +using namespace ledger; + +void export_session() +{ + class_< session_t > ("Session") + .def_readwrite("init_file", &session_t::init_file) + .def_readwrite("data_file", &session_t::data_file) + .def_readwrite("cache_file", &session_t::cache_file) + .def_readwrite("price_db", &session_t::price_db) + + .def_readwrite("balance_format", &session_t::balance_format) + .def_readwrite("register_format", &session_t::register_format) + .def_readwrite("wide_register_format", &session_t::wide_register_format) + .def_readwrite("plot_amount_format", &session_t::plot_amount_format) + .def_readwrite("plot_total_format", &session_t::plot_total_format) + .def_readwrite("print_format", &session_t::print_format) + .def_readwrite("write_hdr_format", &session_t::write_hdr_format) + .def_readwrite("write_xact_format", &session_t::write_xact_format) + .def_readwrite("equity_format", &session_t::equity_format) + .def_readwrite("prices_format", &session_t::prices_format) + .def_readwrite("pricesdb_format", &session_t::pricesdb_format) + + .def_readwrite("pricing_leeway", &session_t::pricing_leeway) + + .def_readwrite("download_quotes", &session_t::download_quotes) + .def_readwrite("use_cache", &session_t::use_cache) + .def_readwrite("cache_dirty", &session_t::cache_dirty) + .def_readwrite("debug_mode", &session_t::debug_mode) + .def_readwrite("verbose_mode", &session_t::verbose_mode) + .def_readwrite("trace_alloc_mode", &session_t::trace_alloc_mode) + .def_readwrite("trace_class_mode", &session_t::trace_class_mode) + + .def_readwrite("journals", &session_t::journals) + ; +} diff --git a/src/py_transform.cc b/src/py_transform.cc new file mode 100644 index 00000000..a0ba31d4 --- /dev/null +++ b/src/py_transform.cc @@ -0,0 +1,8 @@ +using namespace boost::python; +using namespace ledger; + +void export_transform() +{ + class_< repitem_t > ("Transform") + ; +} diff --git a/src/py_value.cc b/src/py_value.cc new file mode 100644 index 00000000..1a43ebc1 --- /dev/null +++ b/src/py_value.cc @@ -0,0 +1,337 @@ +using namespace boost::python; +using namespace ledger; + +long balance_len(balance_t& bal); +amount_t balance_getitem(balance_t& bal, int i); +long balance_pair_len(balance_pair_t& bal_pair); +amount_t balance_pair_getitem(balance_pair_t& bal_pair, int i); + +long value_len(value_t& val) +{ + switch (val.type) { + case value_t::BOOLEAN: + case value_t::INTEGER: + case value_t::DATETIME: + case value_t::AMOUNT: + return 1; + + case value_t::BALANCE: + return balance_len(*((balance_t *) val.data)); + + case value_t::BALANCE_PAIR: + return balance_pair_len(*((balance_pair_t *) val.data)); + + case value_t::STRING: + case value_t::XML_NODE: + case value_t::POINTER: + return 1; + + case value_t::SEQUENCE: + return (*(value_t::sequence_t **) val.data)->size(); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +amount_t value_getitem(value_t& val, int i) +{ + std::size_t len = value_len(val); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + switch (val.type) { + case value_t::BOOLEAN: + throw_(value_exception, "Cannot cast a boolean to an amount"); + + case value_t::INTEGER: + return long(val); + + case value_t::DATETIME: + throw_(value_exception, "Cannot cast a date/time to an amount"); + + case value_t::AMOUNT: + return *((amount_t *) val.data); + + case value_t::BALANCE: + return balance_getitem(*((balance_t *) val.data), i); + + case value_t::BALANCE_PAIR: + return balance_pair_getitem(*((balance_pair_t *) val.data), i); + + case value_t::STRING: + throw_(value_exception, "Cannot cast a string to an amount"); + + case value_t::XML_NODE: + return (*(xml::node_t **) data)->to_value(); + + case value_t::POINTER: + throw_(value_exception, "Cannot cast a pointer to an amount"); + + case value_t::SEQUENCE: + return (*(value_t::sequence_t **) val.data)[i]; + + default: + assert(0); + break; + } + assert(0); + return 0L; +} + +double py_to_float(value_t& val) +{ + return double(val); +} + +void export_value() +{ + class_< value_t > ("value") + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(initmoment_t()) + + .def(self + self) + .def(self + other()) + .def(self + other()) + .def(self + other()) + .def(self + other()) + .def(self + long()) + .def(self + double()) + + .def(other() + self) + .def(other() + self) + .def(other() + self) + .def(other() + self) + .def(long() + self) + .def(double() + self) + + .def(self - self) + .def(self - other()) + .def(self - other()) + .def(self - other()) + .def(self - other()) + .def(self - long()) + .def(self - double()) + + .def(other() - self) + .def(other() - self) + .def(other() - self) + .def(other() - self) + .def(long() - self) + .def(double() - self) + + .def(self * self) + .def(self * other()) + .def(self * other()) + .def(self * other()) + .def(self * other()) + .def(self * long()) + .def(self * double()) + + .def(other() * self) + .def(other() * self) + .def(other() * self) + .def(other() * self) + .def(long() * self) + .def(double() * self) + + .def(self / self) + .def(self / other()) + .def(self / other()) + .def(self / other()) + .def(self / other()) + .def(self / long()) + .def(self / double()) + + .def(other() / self) + .def(other() / self) + .def(other() / self) + .def(other() / self) + .def(long() / self) + .def(double() / self) + + .def(- self) + + .def(self += self) + .def(self += other()) + .def(self += other()) + .def(self += other()) + .def(self += other()) + .def(self += long()) + .def(self += double()) + + .def(self -= self) + .def(self -= other()) + .def(self -= other()) + .def(self -= other()) + .def(self -= other()) + .def(self -= long()) + .def(self -= double()) + + .def(self *= self) + .def(self *= other()) + .def(self *= other()) + .def(self *= other()) + .def(self *= other()) + .def(self *= long()) + .def(self *= double()) + + .def(self /= self) + .def(self /= other()) + .def(self /= other()) + .def(self /= other()) + .def(self /= other()) + .def(self /= long()) + .def(self /= double()) + + .def(self < self) + .def(self < other()) + .def(self < other()) + .def(self < other()) + .def(self < other()) + .def(self < long()) + .def(self < othermoment_t()) + .def(self < double()) + + .def(other() < self) + .def(other() < self) + .def(other() < self) + .def(other() < self) + .def(long() < self) + .def(othermoment_t() < self) + .def(double() < self) + + .def(self <= self) + .def(self <= other()) + .def(self <= other()) + .def(self <= other()) + .def(self <= other()) + .def(self <= long()) + .def(self <= othermoment_t()) + .def(self <= double()) + + .def(other() <= self) + .def(other() <= self) + .def(other() <= self) + .def(other() <= self) + .def(long() <= self) + .def(othermoment_t() <= self) + .def(double() <= self) + + .def(self > self) + .def(self > other()) + .def(self > other()) + .def(self > other()) + .def(self > other()) + .def(self > long()) + .def(self > othermoment_t()) + .def(self > double()) + + .def(other() > self) + .def(other() > self) + .def(other() > self) + .def(other() > self) + .def(long() > self) + .def(othermoment_t() > self) + .def(double() > self) + + .def(self >= self) + .def(self >= other()) + .def(self >= other()) + .def(self >= other()) + .def(self >= other()) + .def(self >= long()) + .def(self >= othermoment_t()) + .def(self >= double()) + + .def(other() >= self) + .def(other() >= self) + .def(other() >= self) + .def(other() >= self) + .def(long() >= self) + .def(othermoment_t() >= self) + .def(double() >= self) + + .def(self == self) + .def(self == other()) + .def(self == other()) + .def(self == other()) + .def(self == other()) + .def(self == long()) + .def(self == othermoment_t()) + .def(self == double()) + + .def(other() == self) + .def(other() == self) + .def(other() == self) + .def(other() == self) + .def(long() == self) + .def(othermoment_t() == self) + .def(double() == self) + + .def(self != self) + .def(self != other()) + .def(self != other()) + .def(self != other()) + .def(self != other()) + .def(self != long()) + .def(self != othermoment_t()) + .def(self != double()) + + .def(other() != self) + .def(other() != self) + .def(other() != self) + .def(other() != self) + .def(long() != self) + .def(othermoment_t() != self) + .def(double() != self) + + .def(! self) + + .def(self_ns::int_(self)) + .def(self_ns::float_(self)) + .def(self_ns::str(self)) + + .def_readonly("type", &value_t::type) + + .def("__abs__", &value_t::abs) + .def("__len__", value_len) + .def("__getitem__", value_getitem) + + .def("cast", &value_t::cast) + .def("cost", &value_t::cost) + .def("price", &value_t::price) + .def("date", &value_t::date) + .def("strip_annotations", &value_t::strip_annotations) + .def("add", &value_t::add, return_internal_reference<>()) + .def("value", &value_t::value) + .def("round", &value_t::round) + .def("negate", &value_t::negate) + .def("write", &value_t::write) + ; + + enum_< value_t::type_t > ("ValueType") + .value("Boolean", value_t::BOOLEAN) + .value("Integer", value_t::INTEGER) + .value("DateTime", value_t::DATETIME) + .value("Amount", value_t::AMOUNT) + .value("Balance", value_t::BALANCE) + .value("BalancePair", value_t::BALANCE_PAIR) + .value("String", value_t::STRING) + .value("XmlNode", value_t::XML_NODE) + .value("Pointer", value_t::POINTER) + .value("Sequence", value_t::SEQUENCE) + ; +} diff --git a/src/py_xpath.cc b/src/py_xpath.cc new file mode 100644 index 00000000..6569ce7b --- /dev/null +++ b/src/py_xpath.cc @@ -0,0 +1,79 @@ +using namespace boost::python; +using namespace ledger; + +value_t py_calc_1(xpath_t::op_t& xpath_t, const details_t& item) +{ + value_t result; + xpath_t.calc(result, item); + return result; +} + +template +value_t py_calc(xpath_t::op_t& xpath_t, const T& item) +{ + value_t result; + xpath_t.calc(result, details_t(item)); + return result; +} + +xpath_t::op_t * py_parse_xpath_t_1(const string& str) +{ + return parse_xpath_t(str); +} + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_RuntimeError, err.what()); \ + } + +EXC_TRANSLATOR(xpath_t_error) +EXC_TRANSLATOR(calc_error) +#if 0 +EXC_TRANSLATOR(mask_error) +#endif + +void export_xpath() +{ + class_< details_t > ("Details", init()) + .def(init()) + .def(init()) + .add_property("entry", + make_getter(&details_t::entry, + return_value_policy())) + .add_property("xact", + make_getter(&details_t::xact, + return_value_policy())) + .add_property("account", + make_getter(&details_t::account, + return_value_policy())) + ; + + class_< xpath_t::op_t > ("ValueExpr", init()) + .def("calc", py_calc_1) + .def("calc", py_calc) + .def("calc", py_calc) + .def("calc", py_calc) + ; + + def("parse_xpath_t", py_parse_xpath_t_1, + return_value_policy()); + + class_< item_predicate > + ("TransactionPredicate", init()) + .def("__call__", &item_predicate::operator()) + ; + + class_< item_predicate > + ("AccountPredicate", init()) + .def("__call__", &item_predicate::operator()) + ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator(&exc_translate_ ## type); + + EXC_TRANSLATE(xpath_t_error); + EXC_TRANSLATE(calc_error); +#if 0 + EXC_TRANSLATE(mask_error); +#endif +} diff --git a/src/pyfstream.h b/src/pyfstream.h new file mode 100644 index 00000000..27e5166b --- /dev/null +++ b/src/pyfstream.h @@ -0,0 +1,138 @@ +#ifndef _PYFSTREAM_H +#define _PYFSTREAM_H + +// pyofstream +// - a stream that writes on a Python file object + +class pyoutbuf : public std::streambuf { + protected: + PyFileObject * fo; // Python file object + public: + // constructor + pyoutbuf (PyFileObject * _fo) : fo(_fo) {} + + protected: + // write one character + virtual int_type overflow (int_type c) { + if (c != EOF) { + char z[2]; + z[0] = c; + z[1] = '\0'; + if (PyFile_WriteString(z, (PyObject *)fo) < 0) { + return EOF; + } + } + return c; + } + + // write multiple characters + virtual std::streamsize xsputn (const char* s, std::streamsize num) { + char * buf = new char[num + 1]; + std::strncpy(buf, s, num); + buf[num] = '\0'; + if (PyFile_WriteString(buf, (PyObject *)fo) < 0) + num = 0; + delete[] buf; + return num; + } +}; + +class pyofstream : public std::ostream { + protected: + pyoutbuf buf; + public: + pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) { + rdbuf(&buf); + } +}; + +// pyifstream +// - a stream that reads on a file descriptor + +class pyinbuf : public std::streambuf { + protected: + PyFileObject * fo; // Python file object + protected: + /* data buffer: + * - at most, pbSize characters in putback area plus + * - at most, bufSize characters in ordinary read buffer + */ + static const int pbSize = 4; // size of putback area + static const int bufSize = 1024; // size of the data buffer + char buffer[bufSize + pbSize]; // data buffer + + public: + /* constructor + * - initialize file descriptor + * - initialize empty data buffer + * - no putback area + * => force underflow() + */ + pyinbuf (PyFileObject * _fo) : fo(_fo) { + setg (buffer+pbSize, // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize); // end position + } + + protected: + // insert new characters into the buffer + virtual int_type underflow () { +#ifndef _MSC_VER + using std::memmove; +#endif + + // is read position before end of buffer? + if (gptr() < egptr()) { + return traits_type::to_int_type(*gptr()); + } + + /* process size of putback area + * - use number of characters read + * - but at most size of putback area + */ + int numPutback; + numPutback = gptr() - eback(); + if (numPutback > pbSize) { + numPutback = pbSize; + } + + /* copy up to pbSize characters previously read into + * the putback area + */ + memmove (buffer+(pbSize-numPutback), gptr()-numPutback, + numPutback); + + // read at most bufSize new characters + int num; + PyObject *line = PyFile_GetLine((PyObject *)fo, bufSize); + if (! line || ! PyString_Check(line)) { + // ERROR or EOF + return EOF; + } + + num = PyString_Size(line); + if (num == 0) + return EOF; + + memmove (buffer+pbSize, PyString_AsString(line), num); + + // reset buffer pointers + setg (buffer+(pbSize-numPutback), // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize+num); // end of buffer + + // return next character + return traits_type::to_int_type(*gptr()); + } +}; + +class pyifstream : public std::istream { + protected: + pyinbuf buf; + public: + pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) { + rdbuf(&buf); + } +}; + +#endif // _PYFSTREAM_H diff --git a/src/pyinterp.cc b/src/pyinterp.cc new file mode 100644 index 00000000..b0cb166c --- /dev/null +++ b/src/pyinterp.cc @@ -0,0 +1,164 @@ +#include "pyinterp.h" + +namespace ledger { + +struct python_run +{ + object result; + python_run(python_interpreter_t * intepreter, + const string& str, int input_mode) + : result(handle<>(borrowed(PyRun_String(str.c_str(), input_mode, + intepreter->nspace.ptr(), + intepreter->nspace.ptr())))) {} + operator object() { + return result; + } +}; + +extern void initialize_for_python(); + +python_interpreter_t::python_interpreter_t(xml::xpath_t::scope_t * parent) + : xml::xpath_t::scope_t(parent), + mmodule(borrowed(PyImport_AddModule("__main__"))), + nspace(handle<>(borrowed(PyModule_GetDict(mmodule.get())))) +{ + Py_Initialize(); + detail::init_module("ledger", &initialize_for_python); +} + +object python_interpreter_t::import(const string& str) +{ + assert(Py_IsInitialized()); + + try { + PyObject * mod = PyImport_Import(PyString_FromString(str.c_str())); + if (! mod) + throw_(exception, "Failed to import Python module " << str); + + object newmod(handle<>(borrowed(mod))); + +#if 1 + // Import all top-level entries directly into the main namespace + dict m_nspace(handle<>(borrowed(PyModule_GetDict(mod)))); + nspace.update(m_nspace); +#else + nspace[string(PyModule_GetName(mod))] = newmod; +#endif + return newmod; + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(exception, "Importing Python module " << str); + } +} + +object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode) +{ + bool first = true; + string buffer; + buffer.reserve(4096); + + while (! in.eof()) { + char buf[256]; + in.getline(buf, 255); + if (buf[0] == '!') + break; + if (first) + first = false; + else + buffer += "\n"; + buffer += buf; + } + + try { + int input_mode; + switch (mode) { + case PY_EVAL_EXPR: input_mode = Py_eval_input; break; + case PY_EVAL_STMT: input_mode = Py_single_input; break; + case PY_EVAL_MULTI: input_mode = Py_file_input; break; + } + assert(Py_IsInitialized()); + return python_run(this, buffer, input_mode); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(exception, "Evaluating Python code"); + } +} + +object python_interpreter_t::eval(const string& str, py_eval_mode_t mode) +{ + try { + int input_mode; + switch (mode) { + case PY_EVAL_EXPR: input_mode = Py_eval_input; break; + case PY_EVAL_STMT: input_mode = Py_single_input; break; + case PY_EVAL_MULTI: input_mode = Py_file_input; break; + } + assert(Py_IsInitialized()); + return python_run(this, str, input_mode); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(exception, "Evaluating Python code"); + } +} + +void python_interpreter_t::functor_t::operator()(value_t& result, + xml::xpath_t::scope_t * locals) +{ + try { + if (! PyCallable_Check(func.ptr())) { + result = static_cast(extract(func.ptr())); + } else { + assert(locals->args.type == value_t::SEQUENCE); + if (locals->args.to_sequence()->size() > 0) { + list arglist; + for (value_t::sequence_t::iterator + i = locals->args.to_sequence()->begin(); + i != locals->args.to_sequence()->end(); + i++) + arglist.append(*i); + + if (PyObject * val = + PyObject_CallObject(func.ptr(), tuple(arglist).ptr())) { + result = extract(val)(); + Py_DECREF(val); + } + else if (PyObject * err = PyErr_Occurred()) { + PyErr_Print(); + throw_(xml::xpath_t::calc_exception, + "While calling Python function '" << name() << "'"); + } else { + assert(0); + } + } else { + result = call(func.ptr()); + } + } + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(xml::xpath_t::calc_exception, + "While calling Python function '" << name() << "'"); + } +} + +void python_interpreter_t::lambda_t::operator()(value_t& result, + xml::xpath_t::scope_t * locals) +{ + try { + assert(locals->args.type == value_t::SEQUENCE); + assert(locals->args.to_sequence()->size() == 1); + value_t item = locals->args[0]; + assert(item.type == value_t::POINTER); + result = call(func.ptr(), (xml::node_t *)*(void **)item.data); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(xml::xpath_t::calc_exception, + "While evaluating Python lambda expression"); + } +} + +} // namespace ledger diff --git a/src/pyinterp.h b/src/pyinterp.h new file mode 100644 index 00000000..a75ef78b --- /dev/null +++ b/src/pyinterp.h @@ -0,0 +1,83 @@ +#ifndef _PY_EVAL_H +#define _PY_EVAL_H + +#include "xpath.h" + +#if defined(USE_BOOST_PYTHON) + +#include +#include +#include +#include + +#include + +#include "pyfstream.h" + +using namespace boost::python; + +namespace ledger { + +class python_interpreter_t : public xml::xpath_t::scope_t +{ + handle<> mmodule; + + public: + dict nspace; + + python_interpreter_t(xml::xpath_t::scope_t * parent); + + virtual ~python_interpreter_t() { + Py_Finalize(); + } + + object import(const string& name); + + enum py_eval_mode_t { + PY_EVAL_EXPR, + PY_EVAL_STMT, + PY_EVAL_MULTI + }; + + object eval(std::istream& in, py_eval_mode_t mode = PY_EVAL_EXPR); + object eval(const string& str, py_eval_mode_t mode = PY_EVAL_EXPR); + object eval(const char * c_str, py_eval_mode_t mode = PY_EVAL_EXPR) { + string str(c_str); + return eval(str, mode); + } + + class functor_t : public xml::xpath_t::functor_t { + protected: + object func; + public: + functor_t(const string& name, object _func) + : xml::xpath_t::functor_t(name), func(_func) {} + + virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals); + }; + + virtual void define(const string& name, xml::xpath_t::op_t * def) { + // Pass any definitions up to our parent + parent->define(name, def); + } + + virtual xml::xpath_t::op_t * lookup(const string& name) { + object func = eval(name); + if (! func) + return parent ? parent->lookup(name) : NULL; + return xml::xpath_t::wrap_functor(new functor_t(name, func)); + } + + class lambda_t : public functor_t { + public: + lambda_t(object code) : functor_t("", code) {} + + virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals); + }; +}; + +} // namespace ledger + +#endif // USE_BOOST_PYTHON + +#endif // _PY_EVAL_H diff --git a/src/pyledger.cc b/src/pyledger.cc new file mode 100644 index 00000000..b7654e5d --- /dev/null +++ b/src/pyledger.cc @@ -0,0 +1,53 @@ +#include + +using namespace boost::python; + +namespace ledger { + +void export_amount(); +#if 0 +void export_balance(); +void export_value(); + +void export_journal(); +void export_parser(); +void export_option(); +void export_walk(); +void export_report(); +void export_format(); +void export_valexpr(); + +void shutdown_option(); +#endif + +void initialize_for_python() +{ + export_amount(); +#if 0 + export_balance(); + export_value(); + + export_journal(); + export_parser(); + export_option(); + export_walk(); + export_format(); + export_report(); + export_valexpr(); +#endif +} + +void shutdown_for_python() +{ +#if 0 + shutdown_option(); +#endif +} + +} + +BOOST_PYTHON_MODULE(ledger) +{ + ledger::initialize(); + ledger::initialize_for_python(); +} diff --git a/src/pyledger.h b/src/pyledger.h new file mode 100644 index 00000000..4056fec0 --- /dev/null +++ b/src/pyledger.h @@ -0,0 +1,16 @@ +#ifndef _PYLEDGER_H +#define _PYLEDGER_H + +////////////////////////////////////////////////////////////////////// +// +// Ledger Accounting Tool (with Python support via Boost.Python) +// +// A command-line tool for general double-entry accounting. +// +// Copyright (c) 2003,2004 John Wiegley +// + +#include +#include + +#endif // _PYLEDGER_H diff --git a/src/qif.cc b/src/qif.cc new file mode 100644 index 00000000..5e567ea0 --- /dev/null +++ b/src/qif.cc @@ -0,0 +1,243 @@ +#include "qif.h" +#include "journal.h" + +namespace ledger { + +#define MAX_LINE 1024 + +static char line[MAX_LINE + 1]; +static string path; +static unsigned int src_idx; +static unsigned int linenum; + +static inline char * get_line(std::istream& in) { + in.getline(line, MAX_LINE); + int len = std::strlen(line); + if (line[len - 1] == '\r') + line[len - 1] = '\0'; + linenum++; + return line; +} + +bool qif_parser_t::test(std::istream& in) const +{ + char magic[sizeof(unsigned int) + 1]; + in.read(magic, sizeof(unsigned int)); + magic[sizeof(unsigned int)] = '\0'; + in.clear(); + in.seekg(0, std::ios::beg); + + return (std::strcmp(magic, "!Typ") == 0 || + std::strcmp(magic, "\n!Ty") == 0 || + std::strcmp(magic, "\r\n!T") == 0); +} + +unsigned int qif_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const string *) +{ + std::auto_ptr entry; + std::auto_ptr amount; + + transaction_t * xact; + unsigned int count = 0; + account_t * misc = NULL; + commodity_t * def_commodity = NULL; + bool saw_splits = false; + bool saw_category = false; + transaction_t * total = NULL; + + entry.reset(new entry_t); + xact = new transaction_t(master); + entry->add_transaction(xact); + + path = journal->sources.back(); + src_idx = journal->sources.size() - 1; + linenum = 1; + + unsigned long beg_pos = 0; + unsigned long beg_line = 0; + +#define SET_BEG_POS_AND_LINE() \ + if (! beg_line) { \ + beg_pos = in.tellg(); \ + beg_line = linenum; \ + } + + while (in.good() && ! in.eof()) { + char c; + in.get(c); + switch (c) { + case ' ': + case '\t': + if (peek_next_nonws(in) != '\n') { + get_line(in); + throw_(parse_exception, "Line begins with whitespace"); + } + // fall through... + + case '\n': + linenum++; + case '\r': // skip blank lines + break; + + case '!': + get_line(in); + + if (std::strcmp(line, "Type:Invst") == 0 || + std::strcmp(line, "Account") == 0 || + std::strcmp(line, "Type:Cat") == 0 || + std::strcmp(line, "Type:Class") == 0 || + std::strcmp(line, "Type:Memorized") == 0) + throw_(parse_exception, + "QIF files of type " << line << " are not supported."); + break; + + case 'D': + SET_BEG_POS_AND_LINE(); + get_line(in); + entry->_date = parse_datetime(line); + break; + + case 'T': + case '$': { + SET_BEG_POS_AND_LINE(); + get_line(in); + xact->amount.parse(line); + + unsigned char flags = xact->amount.commodity().flags(); + unsigned char prec = xact->amount.commodity().precision(); + + if (! def_commodity) { + def_commodity = commodity_t::find_or_create("$"); + assert(def_commodity); + } + xact->amount.set_commodity(*def_commodity); + + def_commodity->add_flags(flags); + if (prec > def_commodity->precision()) + def_commodity->set_precision(prec); + + if (c == '$') { + saw_splits = true; + xact->amount.in_place_negate(); + } else { + total = xact; + } + break; + } + + case 'C': + SET_BEG_POS_AND_LINE(); + c = in.peek(); + if (c == '*' || c == 'X') { + in.get(c); + xact->state = transaction_t::CLEARED; + } + break; + + case 'N': + SET_BEG_POS_AND_LINE(); + get_line(in); + entry->code = line; + break; + + case 'P': + case 'M': + case 'L': + case 'S': + case 'E': { + SET_BEG_POS_AND_LINE(); + get_line(in); + + switch (c) { + case 'P': + entry->payee = line; + break; + + case 'S': + xact = new transaction_t(NULL); + entry->add_transaction(xact); + // fall through... + case 'L': { + int len = std::strlen(line); + if (line[len - 1] == ']') + line[len - 1] = '\0'; + xact->account = journal->find_account(line[0] == '[' ? + line + 1 : line); + if (c == 'L') + saw_category = true; + break; + } + + case 'M': + case 'E': + xact->note = line; + break; + } + break; + } + + case 'A': + SET_BEG_POS_AND_LINE(); + // jww (2004-08-19): these are ignored right now + get_line(in); + break; + + case '^': { + account_t * other; + if (xact->account == master) { + if (! misc) + misc = journal->find_account("Miscellaneous"); + other = misc; + } else { + other = master; + } + + if (total && saw_category) { + if (! saw_splits) + total->amount.in_place_negate(); // negate, to show correct flow + else + total->account = other; + } + + if (! saw_splits) { + transaction_t * nxact = new transaction_t(other); + // The amount doesn't need to be set because the code below + // will balance this transaction against the other. + entry->add_transaction(nxact); + } + + if (journal->add_entry(entry.get())) { + entry->src_idx = src_idx; + entry->beg_pos = beg_pos; + entry->beg_line = beg_line; + entry->end_pos = in.tellg(); + entry->end_line = linenum; + entry.release(); + count++; + } + + // reset things for the next entry + entry.reset(new entry_t); + xact = new transaction_t(master); + entry->add_transaction(xact); + + saw_splits = false; + saw_category = false; + total = NULL; + beg_line = 0; + break; + } + + default: + get_line(in); + break; + } + } + + return count; +} + +} // namespace ledger diff --git a/src/qif.h b/src/qif.h new file mode 100644 index 00000000..6c68a99b --- /dev/null +++ b/src/qif.h @@ -0,0 +1,21 @@ +#ifndef _QIF_H +#define _QIF_H + +#include "parser.h" + +namespace ledger { + +class qif_parser_t : public parser_t +{ + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); +}; + +} // namespace ledger + +#endif // _QIF_H diff --git a/src/quotes.cc b/src/quotes.cc new file mode 100644 index 00000000..1463c9bc --- /dev/null +++ b/src/quotes.cc @@ -0,0 +1,80 @@ +#include "quotes.h" + +namespace ledger { + +void quotes_by_script::operator()(commodity_base_t& commodity, + const ptime& moment, + const ptime& date, + const ptime& last, + amount_t& price) +{ + LOGGER("quotes.download"); + + DEBUG("commodity: " << commodity.symbol); + DEBUG(" now: " << now); + DEBUG(" moment: " << moment); + DEBUG(" date: " << date); + DEBUG(" last: " << last); + + if (SHOW_DEBUG() && commodity.history) + DEBUG("last_lookup: " << commodity.history->last_lookup); + DEBUG("pricing_leeway is " << pricing_leeway); + + if ((commodity.history && + (time_now - commodity.history->last_lookup) < pricing_leeway) || + (time_now - last) < pricing_leeway || + (price && moment > date && (moment - date) <= pricing_leeway)) + return; + + DEBUG("downloading quote for symbol " << commodity.symbol); + + char buf[256]; + buf[0] = '\0'; + + bool success = true; + + if (FILE * fp = popen((string("getquote \"") + + commodity.symbol + "\"").c_str(), "r")) { + if (feof(fp) || ! fgets(buf, 255, fp)) + success = false; + if (pclose(fp) != 0) + success = false; + } else { + success = false; + } + + if (success && buf[0]) { + char * p = strchr(buf, '\n'); + if (p) *p = '\0'; + + DEBUG("downloaded quote: " << buf); + + price.parse(buf); + commodity.add_price(now, price); + + commodity.history->last_lookup = time_now; + cache_dirty = true; + + if (price && ! price_db.empty()) { +#if defined(__GNUG__) && __GNUG__ < 3 + std::ofstream database(price_db.c_str(), ios::out | ios::app); +#else + std::ofstream database(price_db.c_str(), + std::ios_base::out | std::ios_base::app); +#endif +#if 0 + // jww (2007-04-18): Need to convert to local time and print + // here, print with UTC timezone specifier + database << "P " << now.to_string("%Y/%m/%d %H:%M:%S") + << " " << commodity.symbol << " " << price << endl; +#endif + } + } else { + throw exception(string("Failed to download price for '") + + commodity.symbol + "' (command: \"getquote " + + commodity.symbol + "\")", + context()); + } +} + +} // namespace ledger diff --git a/src/quotes.h b/src/quotes.h new file mode 100644 index 00000000..a1fabd93 --- /dev/null +++ b/src/quotes.h @@ -0,0 +1,30 @@ +#ifndef _QUOTES_H +#define _QUOTES_H + +#include "amount.h" + +namespace ledger { + +class quotes_by_script : public commodity_base_t::updater_t +{ + string price_db; + time_duration pricing_leeway; + bool& cache_dirty; + + public: + quotes_by_script(string _price_db, + time_duration _pricing_leeway, + bool& _cache_dirty) + : price_db(_price_db), pricing_leeway(_pricing_leeway), + cache_dirty(_cache_dirty) {} + + virtual void operator()(commodity_base_t& commodity, + const ptime& moment, + const ptime& date, + const ptime& last, + amount_t& price); +}; + +} // namespace ledger + +#endif // _QUOTES_H diff --git a/src/reconcile.cc b/src/reconcile.cc new file mode 100644 index 00000000..e69de29b diff --git a/src/reconcile.h b/src/reconcile.h new file mode 100644 index 00000000..e69de29b diff --git a/src/register.cc b/src/register.cc new file mode 100644 index 00000000..9f33bd0c --- /dev/null +++ b/src/register.cc @@ -0,0 +1,166 @@ +#include "register.h" +#include "journal.h" + +namespace ledger { + +string abbreviate(const string& str, + unsigned int width, + elision_style_t elision_style, + const bool is_account, + int abbrev_length) +{ + const unsigned int len = str.length(); + if (len <= width) + return str; + + assert(width < 4095); + + static char buf[4096]; + + switch (elision_style) { + case TRUNCATE_LEADING: + // This method truncates at the beginning. + std::strncpy(buf, str.c_str() + (len - width), width); + buf[0] = '.'; + buf[1] = '.'; + break; + + case TRUNCATE_MIDDLE: + // This method truncates in the middle. + std::strncpy(buf, str.c_str(), width / 2); + std::strncpy(buf + width / 2, + str.c_str() + (len - (width / 2 + width % 2)), + width / 2 + width % 2); + buf[width / 2 - 1] = '.'; + buf[width / 2] = '.'; + break; + + case ABBREVIATE: + if (is_account) { + std::list parts; + string::size_type beg = 0; + for (string::size_type pos = str.find(':'); + pos != string::npos; + beg = pos + 1, pos = str.find(':', beg)) + parts.push_back(string(str, beg, pos - beg)); + parts.push_back(string(str, beg)); + + string result; + unsigned int newlen = len; + for (std::list::iterator i = parts.begin(); + i != parts.end(); + i++) { + // Don't contract the last element + std::list::iterator x = i; + if (++x == parts.end()) { + result += *i; + break; + } + + if (newlen > width) { + result += string(*i, 0, abbrev_length); + result += ":"; + newlen -= (*i).length() - abbrev_length; + } else { + result += *i; + result += ":"; + } + } + + if (newlen > width) { + // Even abbreviated its too big to show the last account, so + // abbreviate all but the last and truncate at the beginning. + std::strncpy(buf, result.c_str() + (result.length() - width), width); + buf[0] = '.'; + buf[1] = '.'; + } else { + std::strcpy(buf, result.c_str()); + } + break; + } + // fall through... + + case TRUNCATE_TRAILING: + // This method truncates at the end (the default). + std::strncpy(buf, str.c_str(), width - 2); + buf[width - 2] = '.'; + buf[width - 1] = '.'; + break; + } + buf[width] = '\0'; + + return buf; +} + +static void scan_for_transactions(std::ostream& out, const xml::node_t * node) +{ + if (! (node->flags & XML_NODE_IS_PARENT)) + return; + + const xml::parent_node_t * parent = + static_cast(node); + + for (const xml::node_t * child = parent->children(); + child; + child = child->next) + if (child->name_id == xml::document_t::TRANSACTION) { + const xml::transaction_node_t * xact_node = + dynamic_cast(child); + assert(xact_node); + + const transaction_t * xact = xact_node->transaction; + assert(xact); + + out << xact->entry->date() << ' ' + << std::setw(21) << std::left + << abbreviate(xact->entry->payee, 21) << ' ' + << std::setw(21) << std::left + << abbreviate(xact->account->fullname(), 21, + ABBREVIATE, true) << ' ' + << std::setw(12) << std::right + << xact->amount << '\n'; + } else { + scan_for_transactions(out, child); + } +} + +void register_command::print_document(std::ostream& out, + xml::document_t * doc) +{ +#if 1 + scan_for_transactions(out, doc->top); + out.flush(); +#else + value_t nodelist; + xml::xpath_t::eval(nodelist, "//transaction", doc); + + const value_t::sequence_t * xact_list = nodelist.to_sequence(); + assert(xact_list); + + for (value_t::sequence_t::const_iterator i = xact_list->begin(); + i != xact_list->end(); + i++) { + const xml::node_t * node = (*i).to_xml_node(); + assert(node); + + const xml::transaction_node_t * xact_node = + dynamic_cast(node); + assert(xact_node); + + const transaction_t * xact = xact_node->transaction; + assert(xact); + + std::cout << xact->entry->date() << ' ' + << std::setw(21) << std::left + << abbreviate(xact->entry->payee, 21) << ' ' + << std::setw(21) << std::left + << abbreviate(xact->account->fullname(), 21, + ABBREVIATE, true) << ' ' + << std::setw(12) << std::right + << xact->amount + << std::endl; + } +#endif +} + +} // namespace ledger diff --git a/src/register.h b/src/register.h new file mode 100644 index 00000000..ba2020ec --- /dev/null +++ b/src/register.h @@ -0,0 +1,38 @@ +#ifndef _REGISTER_H +#define _REGISTER_H + +#include "xpath.h" + +namespace ledger { + +class register_command : public xml::xpath_t::functor_t +{ + public: + register_command() : xml::xpath_t::functor_t("register") {} + + virtual void operator()(value_t&, xml::xpath_t::scope_t * locals) { + std::ostream * out = get_ptr(locals, 0); + xml::document_t * doc = get_ptr(locals, 1); + + print_document(*out, doc); + } + + virtual void print_document(std::ostream& out, xml::document_t * doc); +}; + +enum elision_style_t { + TRUNCATE_TRAILING, + TRUNCATE_MIDDLE, + TRUNCATE_LEADING, + ABBREVIATE +}; + +string abbreviate(const string& str, + unsigned int width, + elision_style_t elision_style = TRUNCATE_TRAILING, + const bool is_account = false, + int abbrev_length = 2); + +} // namespace ledger + +#endif // _REGISTER_H diff --git a/src/report.cc b/src/report.cc new file mode 100644 index 00000000..116747ef --- /dev/null +++ b/src/report.cc @@ -0,0 +1,189 @@ +#include "report.h" + +namespace ledger { + +report_t::~report_t() +{ + TRACE_DTOR(report_t); + for (std::list::const_iterator i = transforms.begin(); + i != transforms.end(); + i++) + delete *i; +} + +void report_t::apply_transforms(xml::document_t * document) +{ + for (std::list::const_iterator i = transforms.begin(); + i != transforms.end(); + i++) + (*i)->execute(document); +} + +void report_t::abbrev(value_t& result, xml::xpath_t::scope_t * locals) +{ + if (locals->args.size() < 2) + throw_(exception, "usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])"); + + string str = locals->args[0].to_string(); + long wid = locals->args[1]; + + elision_style_t style = session->elision_style; + if (locals->args.size() == 3) + style = (elision_style_t)locals->args[2].to_integer(); + + long abbrev_len = session->abbrev_length; + if (locals->args.size() == 4) + abbrev_len = locals->args[3].to_integer(); + + result.set_string(abbreviate(str, wid, style, true, (int)abbrev_len)); +} + +void report_t::ftime(value_t&, xml::xpath_t::scope_t * locals) +{ + if (locals->args.size() < 1) + throw_(exception, "usage: ftime(DATE [, DATE_FORMAT])"); + + moment_t date = locals->args[0].to_datetime(); + + string date_format; + if (locals->args.size() == 2) + date_format = locals->args[1].to_string(); +#if 0 + // jww (2007-04-18): Need to setup an output facet here + else + date_format = moment_t::output_format; + + result.set_string(date.to_string(date_format)); +#endif +} + +bool report_t::resolve(const string& name, value_t& result, + xml::xpath_t::scope_t * locals) +{ + const char * p = name.c_str(); + switch (*p) { + case 'a': + if (name == "abbrev") { + abbrev(result, locals); + return true; + } + break; + + case 'f': + if (name == "ftime") { + ftime(result, locals); + return true; + } + break; + } + + return xml::xpath_t::scope_t::resolve(name, result, locals); +} + +xml::xpath_t::op_t * report_t::lookup(const string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'o': + if (std::strncmp(p, "option_", 7) == 0) { + p = p + 7; + switch (*p) { + case 'a': +#if 0 + if (std::strcmp(p, "accounts") == 0) + return MAKE_FUNCTOR(report_t, option_accounts); + else +#endif + if (std::strcmp(p, "amount") == 0) + return MAKE_FUNCTOR(report_t, option_amount); + break; + + case 'b': + if (std::strcmp(p, "bar") == 0) + return MAKE_FUNCTOR(report_t, option_bar); + break; + +#if 0 + case 'c': + if (std::strcmp(p, "clean") == 0) + return MAKE_FUNCTOR(report_t, option_clean); + else if (std::strcmp(p, "compact") == 0) + return MAKE_FUNCTOR(report_t, option_compact); + break; +#endif + + case 'e': +#if 0 + if (std::strcmp(p, "entries") == 0) + return MAKE_FUNCTOR(report_t, option_entries); + else if (std::strcmp(p, "eval") == 0) + return MAKE_FUNCTOR(report_t, option_eval); + else if (std::strcmp(p, "exclude") == 0) + return MAKE_FUNCTOR(report_t, option_remove); +#endif + break; + + case 'f': + if (std::strcmp(p, "foo") == 0) + return MAKE_FUNCTOR(report_t, option_foo); + else if (std::strcmp(p, "format") == 0) + return MAKE_FUNCTOR(report_t, option_format); + break; + + case 'i': +#if 0 + if (std::strcmp(p, "include") == 0) + return MAKE_FUNCTOR(report_t, option_select); +#endif + break; + + case 'l': +#if 0 + if (! *(p + 1) || std::strcmp(p, "limit") == 0) + return MAKE_FUNCTOR(report_t, option_limit); +#endif + break; + +#if 0 + case 'm': + if (std::strcmp(p, "merge") == 0) + return MAKE_FUNCTOR(report_t, option_merge); + break; +#endif + + case 'r': +#if 0 + if (std::strcmp(p, "remove") == 0) + return MAKE_FUNCTOR(report_t, option_remove); +#endif + break; + +#if 0 + case 's': + if (std::strcmp(p, "select") == 0) + return MAKE_FUNCTOR(report_t, option_select); + else if (std::strcmp(p, "split") == 0) + return MAKE_FUNCTOR(report_t, option_split); + break; +#endif + + case 't': + if (! *(p + 1)) + return MAKE_FUNCTOR(report_t, option_amount); + else if (std::strcmp(p, "total") == 0) + return MAKE_FUNCTOR(report_t, option_total); + break; + + case 'T': + if (! *(p + 1)) + return MAKE_FUNCTOR(report_t, option_total); + break; + } + } + break; + } + + return xml::xpath_t::scope_t::lookup(name); +} + +} // namespace ledger diff --git a/src/report.h b/src/report.h new file mode 100644 index 00000000..11a0b759 --- /dev/null +++ b/src/report.h @@ -0,0 +1,141 @@ +#ifndef _REPORT_H +#define _REPORT_H + +#include "session.h" +#include "transform.h" + +namespace ledger { + +typedef std::list strings_list; + +class report_t : public xml::xpath_t::scope_t +{ + public: + string output_file; + string format_string; + string amount_expr; + string total_expr; + string date_output_format; + + unsigned long budget_flags; + + string account; + string pager; + + bool show_totals; + bool raw_mode; + + session_t * session; + transform_t * last_transform; + + std::list transforms; + + report_t(session_t * _session) + : xml::xpath_t::scope_t(_session), + show_totals(false), + raw_mode(false), + session(_session), + last_transform(NULL) + { + TRACE_CTOR(report_t, "session_t *"); + eval("t=total,TOT=0,T()=(TOT=TOT+t,TOT)"); + } + + virtual ~report_t(); + + void apply_transforms(xml::document_t * document); + + // + // Utility functions for value expressions + // + + void ftime(value_t& result, xml::xpath_t::scope_t * locals); + void abbrev(value_t& result, xml::xpath_t::scope_t * locals); + + // + // Config options + // + + void eval(const string& expr) { + xml::xpath_t(expr).compile((xml::document_t *)NULL, this); + } + void option_eval(value_t&, xml::xpath_t::scope_t * locals) { + eval(locals->args[0].to_string()); + } + + void option_amount(value_t&, xml::xpath_t::scope_t * locals) { + eval(string("t=") + locals->args[0].to_string()); + } + void option_total(value_t&, xml::xpath_t::scope_t * locals) { + eval(string("T()=") + locals->args[0].to_string()); + } + + void option_format(value_t&, xml::xpath_t::scope_t * locals) { + format_string = locals->args[0].to_string(); + } + + void option_raw(value_t&) { + raw_mode = true; + } + + void option_foo(value_t&) { + std::cout << "This is foo" << std::endl; + } + void option_bar(value_t&, xml::xpath_t::scope_t * locals) { + std::cout << "This is bar: " << locals->args[0] << std::endl; + } + + // + // Transform options + // + +#if 0 + void option_select(value_t&, xml::xpath_t::scope_t * locals) { + transforms.push_back(new select_transform(locals->args[0].to_string())); + } + void option_limit(value_t&, xml::xpath_t::scope_t * locals) { + string expr = (string("//xact[") + + locals->args[0].to_string() + "]"); + transforms.push_back(new select_transform(expr)); + } + + void option_remove(value_t&, xml::xpath_t::scope_t * locals) { + transforms.push_back(new remove_transform(locals->args[0].to_string())); + } + + void option_accounts(value_t&) { + transforms.push_back(new accounts_transform); + } + void option_compact(value_t&) { + transforms.push_back(new compact_transform); + } + void option_clean(value_t&) { + transforms.push_back(new clean_transform); + } + void option_entries(value_t&) { + transforms.push_back(new entries_transform); + } + + void option_split(value_t&) { + transforms.push_back(new split_transform); + } + void option_merge(value_t&) { + transforms.push_back(new merge_transform); + } +#endif + + // + // Scope members + // + + virtual bool resolve(const string& name, value_t& result, + xml::xpath_t::scope_t * locals); + virtual xml::xpath_t::op_t * lookup(const string& name); +}; + +string abbrev(const string& str, unsigned int width, + const bool is_account); + +} // namespace ledger + +#endif // _REPORT_H diff --git a/src/session.cc b/src/session.cc new file mode 100644 index 00000000..78fc2596 --- /dev/null +++ b/src/session.cc @@ -0,0 +1,220 @@ +#include "session.h" + +namespace ledger { + +unsigned int session_t::read_journal(std::istream& in, + journal_t * journal, + account_t * master, + const string * original_file) +{ + if (! master) + master = journal->master; + + for (std::list::iterator i = parsers.begin(); + i != parsers.end(); + i++) + if ((*i)->test(in)) + return (*i)->parse(in, journal, master, original_file); + + return 0; +} + +unsigned int session_t::read_journal(const string& path, + journal_t * journal, + account_t * master, + const string * original_file) +{ + journal->sources.push_back(path); + + if (access(path.c_str(), R_OK) == -1) + throw_(exception, "Cannot read file '" << path << "'"); + + if (! original_file) + original_file = &path; + + std::ifstream stream(path.c_str()); + return read_journal(stream, journal, master, original_file); +} + +void session_t::read_init() +{ + if (init_file.empty()) + return; + + if (access(init_file.c_str(), R_OK) == -1) + throw_(exception, "Cannot read init file '" << init_file << "'"); + + std::ifstream init(init_file.c_str()); + + // jww (2006-09-15): Read initialization options here! +} + +journal_t * session_t::read_data(const string& master_account) +{ + TRACE_START(parser, 1, "Parsing journal file"); + + journal_t * journal = new_journal(); + journal->document = new xml::document_t; + journal->document->set_top(xml::wrap_node(journal->document, journal)); + + unsigned int entry_count = 0; + + DEBUG_("ledger.cache", "3. use_cache = " << use_cache); + + if (use_cache && ! cache_file.empty() && + ! data_file.empty()) { + DEBUG_("ledger.cache", "using_cache " << cache_file); + cache_dirty = true; + if (access(cache_file.c_str(), R_OK) != -1) { + std::ifstream stream(cache_file.c_str()); + + string price_db_orig = journal->price_db; + journal->price_db = price_db; + entry_count += read_journal(stream, journal, NULL, + &data_file); + if (entry_count > 0) + cache_dirty = false; + else + journal->price_db = price_db_orig; + } + } + + if (entry_count == 0 && ! data_file.empty()) { + account_t * acct = NULL; + if (! master_account.empty()) + acct = journal->find_account(master_account); + + journal->price_db = price_db; + if (! journal->price_db.empty() && + access(journal->price_db.c_str(), R_OK) != -1) { + if (read_journal(journal->price_db, journal)) { + throw_(exception, "Entries not allowed in price history file"); + } else { + DEBUG_("ledger.cache", "read price database " << journal->price_db); + journal->sources.pop_back(); + } + } + + DEBUG_("ledger.cache", "rejected cache, parsing " << data_file); + if (data_file == "-") { + use_cache = false; + journal->sources.push_back(""); + entry_count += read_journal(std::cin, journal, acct); + } + else if (access(data_file.c_str(), R_OK) != -1) { + entry_count += read_journal(data_file, journal, acct); + if (! journal->price_db.empty()) + journal->sources.push_back(journal->price_db); + } + } + + VERIFY(journal->valid()); + + if (entry_count == 0) + throw_(exception, "Failed to locate any journal entries; " + "did you specify a valid file with -f?"); + + TRACE_STOP(parser, 1); + + return journal; +} + +bool session_t::resolve(const string& name, value_t& result, + xml::xpath_t::scope_t * locals) +{ + const char * p = name.c_str(); + switch (*p) { + case 'd': + if (name == "date_format") { + // jww (2007-04-18): What to do here? +#if 0 + result.set_string(moment_t::output_format); +#endif + return true; + } + break; + + case 'n': + switch (*++p) { + case 'o': + if (name == "now") { + result = now; + return true; + } + break; + } + break; + + case 'r': + if (name == "register_format") { + result = register_format; + return true; + } + break; + } + + return xml::xpath_t::scope_t::resolve(name, result, locals); +} + +xml::xpath_t::op_t * session_t::lookup(const string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'o': + if (std::strncmp(p, "option_", 7) == 0) { + p = p + 7; + switch (*p) { + case 'd': + if (std::strcmp(p, "debug") == 0) + return MAKE_FUNCTOR(session_t, option_debug); + break; + + case 'f': + if (! *(p + 1) || std::strcmp(p, "file") == 0) + return MAKE_FUNCTOR(session_t, option_file); + break; + + case 't': + if (std::strcmp(p, "trace") == 0) + return MAKE_FUNCTOR(session_t, option_trace); + break; + + case 'v': + if (! *(p + 1) || std::strcmp(p, "verbose") == 0) + return MAKE_FUNCTOR(session_t, option_verbose); + else if (std::strcmp(p, "verify") == 0) + return MAKE_FUNCTOR(session_t, option_verify); + break; + } + } + break; + } + + return xml::xpath_t::scope_t::lookup(name); +} + +// jww (2007-04-26): All of Ledger should be accessed through a +// session_t object +void initialize() +{ + IF_VERIFY() + initialize_memory_tracing(); + + amount_t::initialize(); + xml::xpath_t::initialize(); +} + +void shutdown() +{ + xml::xpath_t::shutdown(); + amount_t::shutdown(); + + IF_VERIFY() { + INFO("Ledger shutdown (Boost/libstdc++ may still hold memory)"); + shutdown_memory_tracing(); + } else { + INFO("Ledger shutdown"); + } +} + +} // namespace ledger diff --git a/src/session.h b/src/session.h new file mode 100644 index 00000000..a1fd304b --- /dev/null +++ b/src/session.h @@ -0,0 +1,197 @@ +#ifndef _SESSION_H +#define _SESSION_H + +#include "journal.h" +#include "parser.h" +#include "register.h" + +namespace ledger { + +class session_t : public xml::xpath_t::scope_t +{ + public: + string init_file; + string data_file; + string cache_file; + string price_db; + + string register_format; + string wide_register_format; + string print_format; + string balance_format; + string equity_format; + string plot_amount_format; + string plot_total_format; + string write_hdr_format; + string write_xact_format; + string prices_format; + string pricesdb_format; + + unsigned long pricing_leeway; + + bool download_quotes; + bool use_cache; + bool cache_dirty; + + moment_t now; + + elision_style_t elision_style; + + int abbrev_length; + + bool ansi_codes; + bool ansi_invert; + + std::list journals; + std::list parsers; + + session_t(xml::xpath_t::scope_t * _parent = NULL) : + xml::xpath_t::scope_t(_parent), + + register_format + ("%((//entry)%{date} %-.20{payee}" + "%((./xact)%32|%-22{abbrev(account, 22)} %12.67t %12.80T\n))"), + wide_register_format + ("%D %-.35P %-.38A %22.108t %!22.132T\n%/" + "%48|%-.38A %22.108t %!22.132T\n"), + print_format +#if 1 + ("%(/%(/%{date} %-.20{payee}\n%(: %-34{account} %12t\n)\n))"), +#else + ("\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"), +#endif + balance_format + ("%(/%(//%20t %{\" \" * rdepth}%{rname}\n))--------------------\n%20t\n"), + equity_format + + ("%((/)%{ftime(now, date_format)} %-.20{\"Opening Balance\"}\n%((.//account[value != 0]) %-34{fullname} %12{value}\n)\n)"), + plot_amount_format + ("%D %(@S(@t))\n"), + plot_total_format + ("%D %(@S(@T))\n"), + write_hdr_format + ("%d %Y%C%P\n"), + write_xact_format + (" %-34W %12o%n\n"), + prices_format + ("%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"), + pricesdb_format + ("P %[%Y/%m/%d %H:%M:%S] %A %t\n"), + + pricing_leeway(24 * 3600), + + download_quotes(false), + use_cache(false), + cache_dirty(false), + + now(now), + + elision_style(ABBREVIATE), + abbrev_length(2), + + ansi_codes(false), + ansi_invert(false) { + TRACE_CTOR(session_t, "xml::xpath_t::scope_t *"); + } + + virtual ~session_t() { + TRACE_DTOR(session_t); + + for (std::list::iterator i = journals.begin(); + i != journals.end(); + i++) + delete *i; + + for (std::list::iterator i = parsers.begin(); + i != parsers.end(); + i++) + delete *i; + } + + journal_t * new_journal() { + journal_t * journal = new journal_t(this); + journals.push_back(journal); + return journal; + } + void close_journal(journal_t * journal) { + journals.remove(journal); + delete journal; + } + + unsigned int read_journal(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); + + unsigned int read_journal(const string& path, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); + + void read_init(); + + journal_t * read_data(const string& master_account = ""); + + void register_parser(parser_t * parser) { + parsers.push_back(parser); + } + bool unregister_parser(parser_t * parser) { + std::list::iterator i; + for (i = parsers.begin(); i != parsers.end(); i++) + if (*i == parser) + break; + if (i == parsers.end()) + return false; + + parsers.erase(i); + + return true; + } + + // + // Scope members + // + + virtual bool resolve(const string& name, value_t& result, + xml::xpath_t::scope_t * locals = NULL); + virtual xml::xpath_t::op_t * lookup(const string& name); + + // + // Debug options + // + + void option_verify(value_t&) {} + void option_trace(value_t&, xml::xpath_t::scope_t * locals) {} + void option_debug(value_t&, xml::xpath_t::scope_t * locals) {} + + void option_verbose(value_t&) { + if (_log_level < LOG_INFO) + _log_level = LOG_INFO; + } + + // + // Option handlers + // + + void option_file(value_t&, xml::xpath_t::scope_t * locals) { + data_file = locals->args.to_string(); + } + +#if 0 +#if defined(USE_BOOST_PYTHON) + void option_import(value_t&) { + python_import(optarg); + } + void option_import_stdin(value_t&) { + python_eval(std::cin, PY_EVAL_MULTI); + } +#endif +#endif +}; + +void initialize(); +void shutdown(); + +} // namespace ledger + +#endif // _SESSION_H diff --git a/src/system.hh b/src/system.hh new file mode 100644 index 00000000..bf376deb --- /dev/null +++ b/src/system.hh @@ -0,0 +1,99 @@ +#ifndef _SYSTEM_HH +#define _SYSTEM_HH + +/** + * @file system.hh + * @author John Wiegley + * @date Mon Apr 23 03:43:05 2007 + * + * @brief All system headers needed by Ledger. + * + * These are collected here so that a pre-compiled header can be made. + * None of these header files (with the exception of acconf.h, when + * configure is re-run) are expected to change. + */ + +#include "acconf.h" + +#if defined(__GNUG__) && __GNUG__ < 3 +#define _XOPEN_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__GNUG__) && __GNUG__ < 3 +namespace std { + inline ostream & right (ostream & i) { + i.setf(i.right, i.adjustfield); + return i; + } + inline ostream & left (ostream & i) { + i.setf(i.left, i.adjustfield); + return i; + } +} +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if defined __FreeBSD__ && __FreeBSD__ <= 4 +// FreeBSD has a broken isspace macro, so don't use it +#undef isspace(c) +#endif + +#include + +#ifdef WIN32 +#include +#else +#include +#endif + +#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM) +#include +#endif + +#if defined(HAVE_NL_LANGINFO) +#include +#endif + +#include + +#define HAVE_GDTOA 1 +#ifdef HAVE_GDTOA +#include +#endif + +extern "C" { +#if defined(HAVE_EXPAT) +#include // expat XML parser +#elif defined(HAVE_XMLPARSE) +#include // expat XML parser +#endif +} + +#if defined(HAVE_LIBOFX) +#include +#endif + +#endif // _SYSTEM_HH diff --git a/src/textual.cc b/src/textual.cc new file mode 100644 index 00000000..ab657898 --- /dev/null +++ b/src/textual.cc @@ -0,0 +1,966 @@ +#include "textual.h" +#include "session.h" + +#define TIMELOG_SUPPORT 1 + +namespace ledger { + +#define MAX_LINE 1024 + +static string path; +static unsigned int linenum; +static unsigned int src_idx; +static accounts_map account_aliases; + +static std::list > include_stack; + +#ifdef TIMELOG_SUPPORT +struct time_entry_t { + moment_t checkin; + account_t * account; + string desc; +}; +std::list time_entries; +#endif + +inline char * next_element(char * buf, bool variable = false) +{ + for (char * p = buf; *p; p++) { + if (! (*p == ' ' || *p == '\t')) + continue; + + if (! variable) { + *p = '\0'; + return skip_ws(p + 1); + } + else if (*p == '\t') { + *p = '\0'; + return skip_ws(p + 1); + } + else if (*(p + 1) == ' ') { + *p = '\0'; + return skip_ws(p + 2); + } + } + return NULL; +} + +static inline void +parse_amount_expr(std::istream& in, journal_t *, + transaction_t& xact, amount_t& amount, + unsigned short flags = 0) +{ + xml::xpath_t xpath(in, flags | XPATH_PARSE_RELAXED | XPATH_PARSE_PARTIAL); + + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed an amount expression"); + +#if 0 + IF_DEBUG_("ledger.textual.parse") { + if (_debug_stream) { + xpath.dump(*_debug_stream); + *_debug_stream << std::endl; + } + } +#endif + + amount = xpath.calc(static_cast(xact.data)).to_amount(); + + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "The transaction amount is " << amount); +} + +transaction_t * parse_transaction(char * line, + journal_t * journal, + account_t * account, + entry_t * entry = NULL) +{ + // The account will be determined later... + std::auto_ptr xact(new transaction_t(NULL)); + + // First cut up the input line into its various parts. + + char * state = NULL; + char * account_path = NULL; + char * amount = NULL; + char * note = NULL; + + char * p = line; + + if (*p == '*' || *p == '!') + state = p++; + + account_path = skip_ws(p); + + amount = next_element(account_path, true); + if (amount) { + char * p = amount; + while (*p && *p != ';') + p++; + + if (*p == ';') { + *p++ = '\0'; + note = skip_ws(p); + } + + p = amount + (std::strlen(amount) - 1); + while (p > amount && std::isspace(*p)) + p--; + + if (std::isspace(*(p + 1))) + *++p = '\0'; + } + + string err_desc; +#if 0 + try { +#endif + + xact->entry = entry; // this might be NULL + + // Parse the state flag + + if (state) + switch (*state) { + case '*': + xact->state = transaction_t::CLEARED; + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed the CLEARED flag"); + break; + case '!': + xact->state = transaction_t::PENDING; + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed the PENDING flag"); + break; + } + + // Parse the account name + + char * b = &account_path[0]; + char * e = &account_path[std::strlen(account_path) - 1]; + if ((*b == '[' && *e == ']') || + (*b == '(' && *e == ')')) { + xact->flags |= TRANSACTION_VIRTUAL; + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a virtual account name"); + if (*b == '[') { + xact->flags |= TRANSACTION_BALANCE; + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a balanced virtual account name"); + } + *account_path++ = '\0'; + *e = '\0'; + } + + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed account name " << account_path); + if (account_aliases.size() > 0) { + accounts_map::const_iterator i = account_aliases.find(account_path); + if (i != account_aliases.end()) + xact->account = (*i).second; + } + if (! xact->account) + xact->account = account->find_account(account_path); + + // Parse the optional amount + + if (amount && *amount) { + std::istringstream in(amount); + + try { + // jww (2006-09-15): Make sure it doesn't gobble up the upcoming @ symbol + + unsigned long beg = (long)in.tellg(); + + xact->amount.parse(in, AMOUNT_PARSE_NO_REDUCE); + + char c; + if (! in.eof() && (c = peek_next_nonws(in)) != '@' && + c != ';' && ! in.eof()) { + in.seekg(beg, std::ios::beg); + + if (xact->entry) { + // Create a report item for this entry, so the transaction + // below may refer to it + + if (! xact->entry->data) + xact->entry->data = xml::wrap_node(journal->document, xact->entry, + journal->document->top); + + xact->data = xml::wrap_node(journal->document, xact.get(), + xact->entry->data); + } + + parse_amount_expr(in, journal, *xact, xact->amount, + XPATH_PARSE_NO_REDUCE); + + if (xact->entry) { + delete static_cast(xact->data); + xact->data = NULL; + } + + unsigned long end = (long)in.tellg(); + + xact->amount_expr = string(line, beg, end - beg); + } + } + catch (exception& err) { + err_desc = "While parsing transaction amount:"; + throw; + } + + // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST) + + if (in.good() && ! in.eof()) { + char c = peek_next_nonws(in); + if (c == '@') { + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Found a price indicator"); + bool per_unit = true; + in.get(c); + if (in.peek() == '@') { + in.get(c); + per_unit = false; + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "And it's for a total price"); + } + + if (in.good() && ! in.eof()) { + xact->cost = new amount_t; + + try { + unsigned long beg = (long)in.tellg(); + + xact->cost->parse(in); + + unsigned long end = (long)in.tellg(); + + if (per_unit) + xact->cost_expr = (string("@") + + string(amount, beg, end - beg)); + else + xact->cost_expr = (string("@@") + + string(amount, beg, end - beg)); + } + catch (exception& err) { + err_desc = "While parsing transaction cost:"; + throw; + } + + if (*xact->cost < 0) + throw_(parse_exception, "A transaction's cost may not be negative"); + + amount_t per_unit_cost(*xact->cost); + if (per_unit) + *xact->cost *= xact->amount.number(); + else + per_unit_cost /= xact->amount.number(); + + if (xact->amount.commodity() && + ! xact->amount.commodity().annotated) + xact->amount.annotate_commodity(per_unit_cost, + xact->entry->actual_date(), + xact->entry->code); + + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Total cost is " << *xact->cost); + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Per-unit cost is " << per_unit_cost); + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Annotated amount is " << xact->amount); + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Bare amount is " << xact->amount.number()); + } + } + } + + xact->amount.in_place_reduce(); + + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Reduced amount is " << xact->amount); + } + + // Parse the optional note + + if (note) { + xact->note = note; + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a note '" << xact->note << "'"); + + if (char * b = std::strchr(xact->note.c_str(), '[')) + if (char * e = std::strchr(xact->note.c_str(), ']')) { + char buf[256]; + std::strncpy(buf, b + 1, e - b - 1); + buf[e - b - 1] = '\0'; + + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a transaction date " << buf); + + if (char * p = std::strchr(buf, '=')) { + *p++ = '\0'; + xact->_date_eff = parse_datetime(p); + } + if (buf[0]) + xact->_date = parse_datetime(buf); + } + } + + return xact.release(); + +#if 0 + } + catch (error * err) { + err->context.push_back + (new line_context(line, -1, ! err_desc.empty() ? + err_desc : "While parsing transaction:")); + throw err; + } +#endif +} + +bool parse_transactions(std::istream& in, + journal_t * journal, + account_t * account, + entry_base_t& entry, + const string& /* kind */, + unsigned long beg_pos) +{ + static char line[MAX_LINE + 1]; + bool added = false; + + while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { + in.getline(line, MAX_LINE); + if (in.eof()) + break; + + beg_pos += std::strlen(line) + 1; + linenum++; + + char * p = skip_ws(line); + if (! *p || *p == '\r' || *p == '\n') + break; + + if (transaction_t * xact = parse_transaction(p, journal, account)) { + entry.add_transaction(xact); + added = true; + } + } + + return added; +} + +entry_t * parse_entry(std::istream& in, char * line, journal_t * journal, + account_t * master, textual_parser_t& /* parser */, + unsigned long beg_pos) +{ + TRACE_START(entry_text, 1, "Time spent preparing entry text:"); + + std::auto_ptr curr(new entry_t); + + // First cut up the input line into its various parts. + + char * date = NULL; + char * date_eff = NULL; + char * statep = NULL; + char * code = NULL; + char * payee = NULL; + + date = line; + + char * p = line; + + while (*p && (std::isdigit(*p) || *p == '/' || *p == '.' || *p == '-')) + p++; + assert(*p); + + if (*p == '=') { + *p++ = '\0'; + date_eff = p; + + while (*p && (std::isdigit(*p) || *p == '/' || *p == '.' || *p == '-')) + p++; + assert(*p); + } else { + *p++ = '\0'; + } + + p = skip_ws(p); + + if (*p == '*' || *p == '!') { + statep = p; + p++; *p++ = '\0'; + + p = skip_ws(p); + } + + if (*p == '(') { + code = ++p; + while (*p && *p != ')') + p++; + assert(*p); + *p++ = '\0'; + + p = skip_ws(p); + } + + payee = p; + + p = payee + (std::strlen(payee) - 1); + while (p > payee && std::isspace(*p)) + p--; + + if (std::isspace(*(p + 1))) + *++p = '\0'; + + TRACE_STOP(entry_text, 1); + + // Parse the date + + TRACE_START(entry_date, 1, "Time spent parsing entry dates:"); + + curr->_date = parse_datetime(date); + + if (date_eff) + curr->_date_eff = parse_datetime(date_eff); + + TRACE_STOP(entry_date, 1); + + // Parse the optional cleared flag: * + + TRACE_START(entry_details, 1, "Time spent parsing entry details:"); + + transaction_t::state_t state = transaction_t::UNCLEARED; + if (statep) { + switch (*statep) { + case '*': + state = transaction_t::CLEARED; + break; + case '!': + state = transaction_t::PENDING; + break; + } + } + + // Parse the optional code: (TEXT) + + if (code) + curr->code = code; + + // Parse the payee/description text + + assert(payee); + curr->payee = *payee != '\0' ? payee : ""; + + TRACE_STOP(entry_details, 1); + + // Parse all of the transactions associated with this entry + + TRACE_START(entry_xacts, 1, "Time spent parsing transactions:"); + + unsigned long end_pos; + unsigned long beg_line = linenum; + + while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { + line[0] = '\0'; + in.getline(line, MAX_LINE); + if (in.eof() || line[0] == '\0') + break; + end_pos = beg_pos + std::strlen(line) + 1; + linenum++; + + char * p = skip_ws(line); + if (! *p || *p == '\r' || *p == '\n') + break; + + if (transaction_t * xact = parse_transaction(p, journal, master, + curr.get())) { + if (state != transaction_t::UNCLEARED && + xact->state == transaction_t::UNCLEARED) + xact->state = state; + + xact->beg_pos = beg_pos; + xact->beg_line = beg_line; + xact->end_pos = end_pos; + xact->end_line = linenum; + beg_pos = end_pos; + + curr->add_transaction(xact); + } + + if (in.eof()) + break; + } + + if (curr->data) { + delete static_cast(curr->data); + curr->data = NULL; + } + + TRACE_STOP(entry_xacts, 1); + + return curr.release(); +} + +template +struct push_var { + T& var; + T prev; + push_var(T& _var) : var(_var), prev(var) {} + ~push_var() { var = prev; } +}; + +static inline void parse_symbol(char *& p, string& symbol) +{ + if (*p == '"') { + char * q = std::strchr(p + 1, '"'); + if (! q) + throw_(parse_exception, "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_exception, "Failed to parse commodity"); +} + +bool textual_parser_t::test(std::istream& in) const +{ + char buf[5]; + + in.read(buf, 5); + if (std::strncmp(buf, "::iterator i = time_entries.begin(); + i != time_entries.end(); + i++) + if (account == (*i).account) { + event = *i; + found = true; + time_entries.erase(i); + break; + } + + if (! found) + throw_(parse_exception, + "Timelog check-out event does not match any current check-ins"); + } + + if (desc && event.desc.empty()) { + event.desc = desc; + desc = NULL; + } + + std::auto_ptr curr(new entry_t); + curr->_date = when; + curr->code = desc ? desc : ""; + curr->payee = event.desc; + + if (curr->_date < event.checkin) + throw_(parse_exception, + "Timelog check-out date less than corresponding check-in"); + + char buf[32]; + std::sprintf(buf, "%lds", (long)(curr->_date - event.checkin).total_seconds()); + amount_t amt; + amt.parse(buf); + + transaction_t * xact + = new transaction_t(event.account, amt, TRANSACTION_VIRTUAL); + xact->state = transaction_t::CLEARED; + curr->add_transaction(xact); + + if (! journal->add_entry(curr.get())) + throw_(parse_exception, "Failed to record 'out' timelog entry"); + else + curr.release(); +} + +unsigned int textual_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const string * original_file) +{ + static bool added_auto_entry_hook = false; + static char line[MAX_LINE + 1]; + unsigned int count = 0; + unsigned int errors = 0; + + TRACE_START(parsing_total, 1, "Total time spent parsing text:"); + + std::list account_stack; + + auto_entry_finalizer_t auto_entry_finalizer(journal); + + if (! master && journal) + master = journal->master; + + account_stack.push_front(master); + + path = journal ? journal->sources.back() : *original_file; + src_idx = journal ? journal->sources.size() - 1 : 0; + linenum = 1; + + INFO("Parsing file '" << path << "'"); + + unsigned long beg_pos = in.tellg(); + unsigned long end_pos; + unsigned long beg_line = linenum; + + while (in.good() && ! in.eof()) { +#if 0 + try { +#endif + in.getline(line, MAX_LINE); + if (in.eof()) + break; + end_pos = beg_pos + std::strlen(line) + 1; + linenum++; + + switch (line[0]) { + case '\0': + case '\r': + break; + + case ' ': + case '\t': { + char * p = skip_ws(line); + if (*p && *p != '\r') + throw_(parse_exception, "Line begins with whitespace"); + break; + } + +#ifdef TIMELOG_SUPPORT + case 'i': + case 'I': { + string date(line, 2, 19); + + char * p = skip_ws(line + 22); + char * n = next_element(p, true); + + time_entry_t event; + event.desc = n ? n : ""; + event.checkin = parse_datetime(date); + event.account = account_stack.front()->find_account(p); + + if (! time_entries.empty()) + for (std::list::iterator i = time_entries.begin(); + i != time_entries.end(); + i++) + if (event.account == (*i).account) + throw_(parse_exception, + "Cannot double check-in to the same account"); + + time_entries.push_back(event); + break; + } + + case 'o': + case 'O': + if (time_entries.empty()) { + throw_(parse_exception, "Timelog check-out event without a check-in"); + } else { + string date(line, 2, 19); + + char * p = skip_ws(line + 22); + char * n = next_element(p, true); + + clock_out_from_timelog + (parse_datetime(date), + p ? account_stack.front()->find_account(p) : NULL, n, journal); + count++; + } + break; +#endif // TIMELOG_SUPPORT + + case 'D': { // a default commodity for "entry" + amount_t amt(skip_ws(line + 1)); + commodity_t::default_commodity = &amt.commodity(); + break; + } + + case 'A': // a default account for unbalanced xacts + journal->basket = + account_stack.front()->find_account(skip_ws(line + 1)); + break; + + case 'C': // a set of conversions + if (char * p = std::strchr(line + 1, '=')) { + *p++ = '\0'; + parse_conversion(line + 1, p); + } + break; + + case 'P': { // a pricing entry + char * date_field_ptr = skip_ws(line + 1); + char * time_field_ptr = next_element(date_field_ptr); + if (! time_field_ptr) break; + string date_field = date_field_ptr; + + char * symbol_and_price; + moment_t datetime; + + if (std::isdigit(time_field_ptr[0])) { + symbol_and_price = next_element(time_field_ptr); + if (! symbol_and_price) break; + datetime = parse_datetime(date_field + " " + time_field_ptr); + } else { + symbol_and_price = time_field_ptr; + datetime = parse_datetime(date_field); + } + + string symbol; + parse_symbol(symbol_and_price, symbol); + amount_t price(symbol_and_price); + + if (commodity_t * commodity = commodity_t::find_or_create(symbol)) + commodity->add_price(datetime, price); + break; + } + + case 'N': { // don't download prices + char * p = skip_ws(line + 1); + string symbol; + parse_symbol(p, symbol); + + if (commodity_t * commodity = commodity_t::find_or_create(symbol)) + commodity->add_flags(COMMODITY_STYLE_NOMARKET); + break; + } + + case 'Y': // set current year +#if 0 + // jww (2007-04-18): Need to set this up again + date_t::current_year = std::atoi(skip_ws(line + 1)); +#endif + break; + +#ifdef TIMELOG_SUPPORT + case 'h': + case 'b': +#endif + case ';': // comment + break; + + case '-': // option setting + throw_(parse_exception, "Option settings are not allowed in journal files"); + + case '=': { // automated entry + if (! added_auto_entry_hook) { + journal->add_entry_finalizer(&auto_entry_finalizer); + added_auto_entry_hook = true; + } + + auto_entry_t * ae = new auto_entry_t(skip_ws(line + 1)); + if (parse_transactions(in, journal, account_stack.front(), *ae, + "automated", end_pos)) { + journal->auto_entries.push_back(ae); + ae->src_idx = src_idx; + ae->beg_pos = beg_pos; + ae->beg_line = beg_line; + ae->end_pos = end_pos; + ae->end_line = linenum; + } + break; + } + + case '~': { // period entry + period_entry_t * pe = new period_entry_t(skip_ws(line + 1)); + if (! pe->period) + throw_(parse_exception, string("Parsing time period '") + skip_ws(line + 1) + "'"); + + if (parse_transactions(in, journal, account_stack.front(), *pe, + "period", end_pos)) { + if (pe->finalize()) { + extend_entry_base(journal, *pe, true); + journal->period_entries.push_back(pe); + pe->src_idx = src_idx; + pe->beg_pos = beg_pos; + pe->beg_line = beg_line; + pe->end_pos = end_pos; + pe->end_line = linenum; + } else { + throw_(parse_exception, "Period entry failed to balance"); + } + } + break; + } + + case '@': + case '!': { // directive + char * p = next_element(line); + string word(line + 1); + if (word == "include") { + push_var save_path(path); + push_var save_src_idx(src_idx); + push_var save_beg_pos(beg_pos); + push_var save_end_pos(end_pos); + push_var save_linenum(linenum); + + path = p; + if (path[0] != '/' && path[0] != '\\' && path[0] != '~') { + string::size_type pos = save_path.prev.rfind('/'); + if (pos == string::npos) + pos = save_path.prev.rfind('\\'); + if (pos != string::npos) + path = string(save_path.prev, 0, pos + 1) + path; + } + path = resolve_path(path); + + DEBUG_("ledger.textual.include", "line " << linenum << ": " << + "Including path '" << path << "'"); + + include_stack.push_back(std::pair + (journal->sources.back(), linenum - 1)); + count += journal->session->read_journal(path, journal, + account_stack.front()); + include_stack.pop_back(); + } + else if (word == "account") { + account_t * acct; + acct = account_stack.front()->find_account(p); + account_stack.push_front(acct); + } + else if (word == "end") { + account_stack.pop_front(); + } + else if (word == "alias") { + char * b = p; + if (char * e = std::strchr(b, '=')) { + char * z = e - 1; + while (std::isspace(*z)) + *z-- = '\0'; + *e++ = '\0'; + e = skip_ws(e); + + // Once we have an alias name (b) and the target account + // name (e), add a reference to the account in the + // `account_aliases' map, which is used by the transaction + // parser to resolve alias references. + account_t * acct = account_stack.front()->find_account(e); + std::pair result + = account_aliases.insert(accounts_pair(b, acct)); + assert(result.second); + } + } + else if (word == "def" || word == "eval") { + // jww (2006-09-13): Read the string after and evaluate it. + // But also keep a list of these value expressions, and a + // way to know where they fall in the transaction sequence. + // This will be necessary so that binary file reading can + // re-evaluate them at the appopriate time. + + // compile(&journal->defs); + } + break; + } + + default: { + //unsigned int first_line = linenum; + unsigned long pos = end_pos; + + TRACE_START(entries, 1, "Time spent handling entries:"); + if (entry_t * entry = parse_entry(in, line, journal, + account_stack.front(), + *this, pos)) { + if (journal->add_entry(entry)) { + entry->src_idx = src_idx; + entry->beg_pos = beg_pos; + entry->beg_line = beg_line; + entry->end_pos = end_pos; + entry->end_line = linenum; + count++; + } else { + delete entry; + throw_(parse_exception, "Entry does not balance"); + } + } else { + throw_(parse_exception, "Failed to parse entry"); + } + TRACE_STOP(entries, 1); + + end_pos = pos; + break; + } + } +#if 0 + } + catch (error * err) { + for (std::list >::reverse_iterator i = + include_stack.rbegin(); + i != include_stack.rend(); + i++) + err->context.push_back(new include_context((*i).first, (*i).second, + "In file included from")); + err->context.push_front(new file_context(path, linenum - 1)); + + std::cout.flush(); + if (errors > 0 && err->context.size() > 1) + std::cerr << std::endl; + err->reveal_context(std::cerr, "Error"); + std::cerr << err->what() << std::endl; + delete err; + errors++; + } +#endif + beg_pos = end_pos; + } + + if (! time_entries.empty()) { + for (std::list::iterator i = time_entries.begin(); + i != time_entries.end(); + i++) + clock_out_from_timelog(now, (*i).account, NULL, journal); + time_entries.clear(); + } + + if (added_auto_entry_hook) + journal->remove_entry_finalizer(&auto_entry_finalizer); + + if (errors > 0) + throw (int)errors; + + TRACE_STOP(parsing_total, 1); + + return count; +} + +} // namespace ledger diff --git a/src/textual.h b/src/textual.h new file mode 100644 index 00000000..bf05b1fc --- /dev/null +++ b/src/textual.h @@ -0,0 +1,44 @@ +#ifndef _TEXTUAL_H +#define _TEXTUAL_H + +#include "parser.h" + +namespace ledger { + +class textual_parser_t : public parser_t +{ + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); +}; + +#if 0 +void write_textual_journal(journal_t& journal, string path, + item_handler& formatter, + const string& write_hdr_format, + std::ostream& out); +#endif + +#if 0 +class include_context : public file_context { + public: + include_context(const string& file, unsigned long line, + const string& desc = "") throw() + : file_context(file, line, desc) {} + virtual ~include_context() throw() {} + + virtual void describe(std::ostream& out) const throw() { + if (! desc.empty()) + out << desc << ": "; + out << "\"" << file << "\", line " << line << ":" << std::endl; + } +}; +#endif + +} // namespace ledger + +#endif // _TEXTUAL_H diff --git a/src/times.cc b/src/times.cc new file mode 100644 index 00000000..8966d598 --- /dev/null +++ b/src/times.cc @@ -0,0 +1,53 @@ +#include "times.h" + +namespace ledger { + +#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK +const ptime time_now = boost::posix_time::microsec_clock::universal_time(); +#else +const ptime time_now = boost::posix_time::second_clock::universal_time(); +#endif +const date date_now = boost::gregorian::day_clock::universal_day(); + +#ifdef SUPPORT_DATE_AND_TIME +const moment_t& now(time_now); +#else +const moment_t& now(date_now); +#endif + +bool day_before_month = false; +static bool day_before_month_initialized = false; + +moment_t parse_datetime(const char * str) +{ + if (! day_before_month_initialized) { +#ifdef HAVE_NL_LANGINFO + const char * d_fmt = nl_langinfo(D_FMT); + if (d_fmt && std::strlen(d_fmt) > 1 && d_fmt[1] == 'd') + day_before_month = true; + day_before_month_initialized = true; +#endif + } +#if 0 + return parse_abs_datetime(in); +#else + int year = ((str[0] - '0') * 1000 + + (str[1] - '0') * 100 + + (str[2] - '0') * 10 + + (str[3] - '0')); + + int mon = ((str[5] - '0') * 10 + + (str[6] - '0')); + + int day = ((str[8] - '0') * 10 + + (str[9] - '0')); + + return moment_t(boost::gregorian::date(year, mon, day)); +#endif +} + +moment_t datetime_range_from_stream(std::istream& in) +{ +} + +} // namespace ledger diff --git a/src/times.h b/src/times.h new file mode 100644 index 00000000..2cc0d7e4 --- /dev/null +++ b/src/times.h @@ -0,0 +1,102 @@ +#ifndef _TIMES_H +#define _TIMES_H + +#include "utils.h" + +#include + +namespace ledger { + +typedef boost::posix_time::ptime ptime; +typedef ptime::time_duration_type time_duration; +typedef boost::gregorian::date date; +typedef boost::gregorian::date_duration date_duration; +typedef boost::posix_time::seconds seconds; + +#define SUPPORT_DATE_AND_TIME 1 +#ifdef SUPPORT_DATE_AND_TIME + +typedef boost::posix_time::ptime moment_t; +typedef moment_t::time_duration_type duration_t; + +inline bool is_valid_moment(const moment_t& moment) { + return ! moment.is_not_a_date_time(); +} + +#else // SUPPORT_DATE_AND_TIME + +typedef boost::gregorian::date moment_t; +typedef boost::gregorian::date_duration duration_t; + +inline bool is_valid_moment(const moment_t& moment) { + return ! moment.is_not_a_date(); +} + +#endif // SUPPORT_DATE_AND_TIME + +extern const moment_t& now; + +DECLARE_EXCEPTION(datetime_exception); + +class interval_t +{ +public: + interval_t() {} + interval_t(const string&) {} + + operator bool() const { + return false; + } + + void start(const moment_t&) {} + moment_t next() const { return moment_t(); } + + void parse(std::istream&) {} +}; + +#if 0 +inline moment_t ptime_local_to_utc(const moment_t& when) { + struct std::tm tm_gmt = to_tm(when); + return boost::posix_time::from_time_t(std::mktime(&tm_gmt)); +} + +// jww (2007-04-18): I need to make a general parsing function +// instead, and then make these into private methods. +inline moment_t ptime_from_local_date_string(const string& date_string) { + return ptime_local_to_utc(moment_t(boost::gregorian::from_string(date_string), + time_duration())); +} + +inline moment_t ptime_from_local_time_string(const string& time_string) { + return ptime_local_to_utc(boost::posix_time::time_from_string(time_string)); +} +#endif + +moment_t parse_datetime(const char * str); + +inline moment_t parse_datetime(const string& str) { + return parse_datetime(str.c_str()); +} + +extern const ptime time_now; +extern const date date_now; +extern bool day_before_month; + +#if 0 +struct intorchar +{ + int ival; + string sval; + + intorchar() : ival(-1) {} + intorchar(int val) : ival(val) {} + intorchar(const string& val) : ival(-1), sval(val) {} + intorchar(const intorchar& o) : ival(o.ival), sval(o.sval) {} +}; + +ledger::moment_t parse_abs_datetime(std::istream& input); +#endif + +} // namespace ledger + +#endif // _TIMES_H diff --git a/src/transform.cc b/src/transform.cc new file mode 100644 index 00000000..b6a25cee --- /dev/null +++ b/src/transform.cc @@ -0,0 +1,326 @@ +#include "transform.h" + +namespace ledger { + +#if 0 +void populate_account(account_t& acct, xml::document_t * document) +{ + if (! acct.parent) + return; + + account_repitem_t * acct_item; + if (acct.data == NULL) { + acct.data = acct_item = + static_cast(repitem_t::wrap(&acct)); + if (acct.parent) { + if (acct.parent->data == NULL) + populate_account(*acct.parent, acct_item); + else + static_cast(acct.parent->data)-> + add_child(acct_item); + } + } else { + acct_item = static_cast(acct.data); + } + + if (item->kind == repitem_t::ACCOUNT) + acct_item->add_child(item); + else + acct_item->add_content(item); +} + +class populate_accounts : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->kind == repitem_t::TRANSACTION) { + item->extract(); + populate_account(*static_cast(item)->account(), item); + } + } +}; + +class clear_account_data : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->kind == repitem_t::ACCOUNT) + static_cast(item)->account->data = NULL; + } +}; + +void accounts_transform::execute(xml::document_t * document) +{ + populate_accounts cb1; + items->select_all(cb1); + + for (repitem_t * j = items->children; j; j = j->next) { + assert(j->kind == repitem_t::JOURNAL); + + j->clear(); + + for (accounts_map::iterator i = j->journal->master->accounts.begin(); + i != j->journal->master->accounts.end(); + i++) { + assert((*i).second->data); + j->add_child(static_cast((*i).second->data)); + (*i).second->data = NULL; + } + } + + clear_account_data cb2; + items->select_all(cb2); +} + +void compact_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->kind == repitem_t::ACCOUNT) { + while (! i->contents && + i->children && ! i->children->next) { + account_repitem_t * p = static_cast(i); + i = p->children; + p->children = NULL; + p->last_child = NULL; + + i->set_parent(p->parent); + p->set_parent(NULL); + i->prev = p->prev; + if (p->prev) + p->prev->next = i; + p->prev = NULL; + i->next = p->next; + if (p->next) + p->next->prev = i; + p->next = NULL; + + if (i->parent->children == p) + i->parent->children = i; + if (i->parent->last_child == p) + i->parent->last_child = i; + + account_repitem_t * acct = static_cast(i); + acct->parents_elided = p->parents_elided + 1; + + delete p; + } + } + + if (i->children) + execute(i->children); + } +} + +void clean_transform::execute(xml::document_t * document) +{ + repitem_t * i = items; + while (i) { + if (i->kind == repitem_t::ACCOUNT) { + value_t temp; + i->add_total(temp); + if (! temp) { + repitem_t * next = i->next; + delete i; + i = next; + continue; + } + } +#if 0 + else if (i->kind == repitem_t::ENTRY && ! i->contents) { + assert(! i->children); + repitem_t * next = i->next; + delete i; + i = next; + continue; + } +#endif + + if (i->children) + execute(i->children); + + i = i->next; + } +} + +void entries_transform::execute(xml::document_t * document) +{ +} + +void optimize_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->kind == repitem_t::ENTRY) { + if (i->contents && + i->contents->next && + ! i->contents->next->next) { // exactly two transactions + xact_repitem_t * first = + static_cast(i->contents); + xact_repitem_t * second = + static_cast(i->contents->next); + if (first->xact->amount == - second->xact->amount) + ; + } + } + + if (i->children) + execute(i->children); + } +} + +void split_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->contents && i->contents->next) { + repitem_t * j; + + switch (i->kind) { + case repitem_t::TRANSACTION: + assert(0); + j = new xact_repitem_t(static_cast(i)->xact); + break; + case repitem_t::ENTRY: + j = new entry_repitem_t(static_cast(i)->entry); + break; + case repitem_t::ACCOUNT: + j = new account_repitem_t(static_cast(i)->account); + break; + default: + j = new repitem_t(i->kind); + break; + } + + j->set_parent(i->parent); + j->prev = i; + j->next = i->next; + i->next = j; + + j->contents = i->contents->next; + j->contents->prev = NULL; + j->contents->set_parent(j); + i->contents->next = NULL; + + j->last_content = i->last_content; + if (j->contents == i->last_content) + i->last_content = i->contents; + } + + if (i->children) + execute(i->children); + } +} + +void merge_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->next) { + assert(i->kind == i->next->kind); + bool merge = false; + switch (i->kind) { + case repitem_t::TRANSACTION: + assert(0); + break; + case repitem_t::ENTRY: + if (static_cast(i)->entry == + static_cast(i->next)->entry) + merge = true; + break; + case repitem_t::ACCOUNT: +#if 0 + if (static_cast(i)->account == + static_cast(i->next)->account) + merge = true; +#endif + break; + default: + break; + } + + if (merge) { + repitem_t * j = i->next; + + i->next = i->next->next; + if (i->next) + i->next->prev = i; + + for (repitem_t * k = j->contents; k; k = k->next) + k->set_parent(i); + + i->last_content->next = j->contents; + i->last_content = j->last_content; + + j->contents = NULL; + assert(! j->children); + delete j; + } + } + + if (i->children) + execute(i->children); + } +} + +namespace { +#define REPITEM_FLAGGED 0x1 + + class mark_selected : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + item->flags |= REPITEM_FLAGGED; + } + }; + + class mark_selected_and_ancestors : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + while (item->parent) { + item->flags |= REPITEM_FLAGGED; + item = item->parent; + } + } + }; + + class delete_unmarked : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->parent && ! (item->flags & REPITEM_FLAGGED)) + delete item; + } + }; + + class delete_marked : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->flags & REPITEM_FLAGGED) + delete item; + } + }; + + class clear_flags : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + item->flags = 0; + } + }; +} + +void select_transform::execute(xml::document_t * document) +{ + if (! path) { + items->clear(); + return; + } + mark_selected_and_ancestors cb1; + items->select(path, cb1); + + delete_unmarked cb2; + items->select_all(cb2); + clear_flags cb3; + items->select_all(cb3); +} + +void remove_transform::execute(xml::document_t * document) +{ + if (! path) + return; + mark_selected cb1; + items->select(path, cb1); + + delete_marked cb2; + items->select_all(cb2); + clear_flags cb3; + items->select_all(cb3); +} +#endif + +} // namespace ledger diff --git a/src/transform.h b/src/transform.h new file mode 100644 index 00000000..1a5286a1 --- /dev/null +++ b/src/transform.h @@ -0,0 +1,136 @@ +#ifndef _TRANSFORM_H +#define _TRANSFORM_H + +#include "xpath.h" + +namespace ledger { + +class transform_t { + public: + virtual ~transform_t() {} + virtual void execute(xml::document_t * document) = 0; +}; + +class check_transform : public transform_t { + // --check checks the validity of the item list. + public: + virtual void execute(xml::document_t * document); +}; + +class accounts_transform : public transform_t { + // --accounts transforms the report tree into an account-wise view. + public: + virtual void execute(xml::document_t * document); +}; + +class compact_transform : public transform_t { + // --compact compacts an account tree to remove accounts with only + // one child account. + public: + virtual void execute(xml::document_t * document); +}; + +class clean_transform : public transform_t { + // --clean clears out entries and accounts that have no contents. + public: + virtual void execute(xml::document_t * document); +}; + +class entries_transform : public transform_t { + // --entries transforms the report tree into an entries-wise view. + public: + virtual void execute(xml::document_t * document); +}; + +class optimize_transform : public transform_t { + // --optimize optimizes entries for display by the print command. + // What this means is that if an entry has two transactions of the + // commodity (one the negative of the other), the amount of the + // second transaction will be nulled out. + public: + virtual void execute(xml::document_t * document); +}; + +class split_transform : public transform_t { + // --split breaks entry with two or more transactions into what + // seems like two entries each with one transaction -- even though + // it is the same entry being reported in both cases. This is + // useful before sorting, for exampel, in order to sort by + // transaction instead of by entry. + public: + virtual void execute(xml::document_t * document); +}; + +class merge_transform : public transform_t { + // --merge is the opposite of --split: any adjacent transactions + // which share the same entry will be merged into a group of + // transactions under one reported entry. + public: + virtual void execute(xml::document_t * document); +}; + +class combine_transform : public transform_t { + // --combine EXPR combines all transactions matching EXPR so that + // they appear within the same virtual entry (whose date will span + // the earliest to the latest of those entries, and whose payee name + // will show the terminating date or a label that is characteristic + // of the set). + public: + virtual void execute(xml::document_t * document); +}; + +class group_transform : public transform_t { + // --group groups all transactions that affect the same account + // within an entry, so that they appear as a single transaction. + public: + virtual void execute(xml::document_t * document); +}; + +class collapse_transform : public transform_t { + // --collapse makes all transactions within an entry appear as a + // single transaction, even if they affect different accounts. The + // fictitous account "" is used to represent the final sum, + // if multiple accounts are involved. + public: + virtual void execute(xml::document_t * document); +}; + +class subtotal_transform : public transform_t { + // --subtotal will combine the transactions from all entries into + // one giant entry. When used in conjunction with --group, the + // affect is very similar to a regular balance report. + public: + virtual void execute(xml::document_t * document); +}; + +#if 0 +class select_transform : public transform_t +{ + protected: + xml::xpath_t xpath; + + public: + select_transform(const string& selection_path) { + xpath.parse(selection_path); + } + virtual ~select_transform() { + if (path) + delete path; + } + + virtual void execute(xml::document_t * document); +}; + +class remove_transform : public select_transform +{ + public: + remove_transform(const string& selection_path) + : select_transform(selection_path) {} + + virtual void execute(xml::document_t * document); +}; +#endif + +} // namespace ledger + +#endif // _TRANSFORM_H diff --git a/src/utils.cc b/src/utils.cc new file mode 100644 index 00000000..63c28c5c --- /dev/null +++ b/src/utils.cc @@ -0,0 +1,687 @@ +#include "utils.h" +#include "times.h" + +/********************************************************************** + * + * Assertions + */ + +#if defined(ASSERTS_ON) + +namespace ledger { + +void debug_assert(const string& reason, + const string& func, + const string& file, + unsigned long line) +{ + std::ostringstream buf; + buf << "Assertion failed in \"" << file << "\", line " << line + << ": " << reason; + throw exception(buf.str(), context()); +} + +} // namespace ledger + +#endif + +/********************************************************************** + * + * Verification (basically, very slow asserts) + */ + +#if defined(VERIFY_ON) + +namespace ledger { + +bool verify_enabled = false; + +typedef std::pair allocation_pair; +typedef std::map live_memory_map; +typedef std::pair live_memory_pair; +typedef std::multimap live_objects_map; +typedef std::pair live_objects_pair; +typedef std::pair count_size_pair; +typedef std::map object_count_map; +typedef std::pair object_count_pair; + +static live_memory_map * live_memory = NULL; +static object_count_map * live_memory_count = NULL; +static object_count_map * total_memory_count = NULL; + +static bool memory_tracing_active = false; + +static live_objects_map * live_objects = NULL; +static object_count_map * live_object_count = NULL; +static object_count_map * total_object_count = NULL; +static object_count_map * total_ctor_count = NULL; + +void initialize_memory_tracing() +{ + memory_tracing_active = false; + + live_memory = new live_memory_map; + live_memory_count = new object_count_map; + total_memory_count = new object_count_map; + + live_objects = new live_objects_map; + live_object_count = new object_count_map; + total_object_count = new object_count_map; + total_ctor_count = new object_count_map; + + memory_tracing_active = true; +} + +void shutdown_memory_tracing() +{ + memory_tracing_active = false; + + if (live_objects) { + IF_DEBUG_("memory.counts") + report_memory(std::cerr, true); + else + IF_DEBUG_("memory.counts.live") + report_memory(std::cerr); + else if (live_objects->size() > 0) + report_memory(std::cerr); + } + + delete live_memory; live_memory = NULL; + delete live_memory_count; live_memory_count = NULL; + delete total_memory_count; total_memory_count = NULL; + + delete live_objects; live_objects = NULL; + delete live_object_count; live_object_count = NULL; + delete total_object_count; total_object_count = NULL; + delete total_ctor_count; total_ctor_count = NULL; +} + +inline void add_to_count_map(object_count_map& the_map, + const char * name, std::size_t size) +{ + object_count_map::iterator k = the_map.find(name); + if (k != the_map.end()) { + (*k).second.first++; + (*k).second.second += size; + } else { + std::pair result = + the_map.insert(object_count_pair(name, count_size_pair(1, size))); + VERIFY(result.second); + } +} + +std::size_t current_memory_size() +{ + std::size_t memory_size = 0; + + for (object_count_map::const_iterator i = live_memory_count->begin(); + i != live_memory_count->end(); + i++) + memory_size += (*i).second.second; + + return memory_size; +} + +static void trace_new_func(void * ptr, const char * which, std::size_t size) +{ + memory_tracing_active = false; + + if (! live_memory) return; + + live_memory->insert(live_memory_pair(ptr, allocation_pair(which, size))); + + add_to_count_map(*live_memory_count, which, size); + add_to_count_map(*total_memory_count, which, size); + add_to_count_map(*total_memory_count, "__ALL__", size); + + memory_tracing_active = true; +} + +static void trace_delete_func(void * ptr, const char * which) +{ + memory_tracing_active = false; + + if (! live_memory) return; + + // Ignore deletions of memory not tracked, since it's possible that + // a user (like boost) allocated a block of memory before memory + // tracking began, and then deleted it before memory tracking ended. + // If it really is a double-delete, the malloc library on OS/X will + // notify me. + + live_memory_map::iterator i = live_memory->find(ptr); + if (i == live_memory->end()) + return; + + std::size_t size = (*i).second.second; + VERIFY((*i).second.first == which); + + live_memory->erase(i); + + object_count_map::iterator j = live_memory_count->find(which); + VERIFY(j != live_memory_count->end()); + + (*j).second.second -= size; + if (--(*j).second.first == 0) + live_memory_count->erase(j); + + memory_tracing_active = true; +} + +} // namespace ledger + +void * operator new(std::size_t size) throw (std::bad_alloc) { + void * ptr = std::malloc(size); + if (DO_VERIFY() && ledger::memory_tracing_active) + ledger::trace_new_func(ptr, "new", size); + return ptr; +} +void * operator new(std::size_t size, const std::nothrow_t&) throw() { + void * ptr = std::malloc(size); + if (DO_VERIFY() && ledger::memory_tracing_active) + ledger::trace_new_func(ptr, "new", size); + return ptr; +} +void * operator new[](std::size_t size) throw (std::bad_alloc) { + void * ptr = std::malloc(size); + if (DO_VERIFY() && ledger::memory_tracing_active) + ledger::trace_new_func(ptr, "new[]", size); + return ptr; +} +void * operator new[](std::size_t size, const std::nothrow_t&) throw() { + void * ptr = std::malloc(size); + if (DO_VERIFY() && ledger::memory_tracing_active) + ledger::trace_new_func(ptr, "new[]", size); + return ptr; +} +void operator delete(void * ptr) throw() { + if (DO_VERIFY() && ledger::memory_tracing_active) + ledger::trace_delete_func(ptr, "new"); + std::free(ptr); +} +void operator delete(void * ptr, const std::nothrow_t&) throw() { + if (DO_VERIFY() && ledger::memory_tracing_active) + ledger::trace_delete_func(ptr, "new"); + std::free(ptr); +} +void operator delete[](void * ptr) throw() { + if (DO_VERIFY() && ledger::memory_tracing_active) + ledger::trace_delete_func(ptr, "new[]"); + std::free(ptr); +} +void operator delete[](void * ptr, const std::nothrow_t&) throw() { + if (DO_VERIFY() && ledger::memory_tracing_active) + ledger::trace_delete_func(ptr, "new[]"); + std::free(ptr); +} + +namespace ledger { + +inline void report_count_map(std::ostream& out, object_count_map& the_map) +{ + for (object_count_map::iterator i = the_map.begin(); + i != the_map.end(); + i++) + out << " " << std::right << std::setw(12) << (*i).second.first + << " " << std::right << std::setw(12) << (*i).second.second + << " " << std::left << (*i).first + << std::endl; +} + +std::size_t current_objects_size() +{ + std::size_t objects_size = 0; + + for (object_count_map::const_iterator i = live_object_count->begin(); + i != live_object_count->end(); + i++) + objects_size += (*i).second.second; + + return objects_size; +} + +void trace_ctor_func(void * ptr, const char * cls_name, const char * args, + std::size_t cls_size) +{ + memory_tracing_active = false; + + if (! live_objects) return; + + static char name[1024]; + std::strcpy(name, cls_name); + std::strcat(name, "("); + std::strcat(name, args); + std::strcat(name, ")"); + + DEBUG_("verify.memory", "TRACE_CTOR " << ptr << " " << name); + + live_objects->insert(live_objects_pair(ptr, allocation_pair(cls_name, cls_size))); + + add_to_count_map(*live_object_count, cls_name, cls_size); + add_to_count_map(*total_object_count, cls_name, cls_size); + add_to_count_map(*total_object_count, "__ALL__", cls_size); + add_to_count_map(*total_ctor_count, name, cls_size); + + memory_tracing_active = true; +} + +void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size) +{ + memory_tracing_active = false; + + if (! live_objects) return; + + DEBUG_("ledger.trace.debug", "TRACE_DTOR " << ptr << " " << cls_name); + + live_objects_map::iterator i = live_objects->find(ptr); + VERIFY(i != live_objects->end()); + + int ptr_count = live_objects->count(ptr); + for (int x = 0; x < ptr_count; x++, i++) { + if ((*i).second.first == cls_name) { + live_objects->erase(i); + break; + } + } + + object_count_map::iterator k = live_object_count->find(cls_name); + VERIFY(k != live_object_count->end()); + + (*k).second.second -= cls_size; + if (--(*k).second.first == 0) + live_object_count->erase(k); + + memory_tracing_active = true; +} + +void report_memory(std::ostream& out, bool report_all) +{ + if (! live_memory) return; + + if (live_memory_count->size() > 0) { + out << "NOTE: There may be memory held by Boost " + << "and libstdc++ after ledger::shutdown()" << std::endl; + out << "Live memory count:" << std::endl; + report_count_map(out, *live_memory_count); + } + + if (live_memory->size() > 0) { + out << "Live memory:" << std::endl; + + for (live_memory_map::const_iterator i = live_memory->begin(); + i != live_memory->end(); + i++) + out << " " << std::right << std::setw(7) << (*i).first + << " " << std::right << std::setw(7) << (*i).second.second + << " " << std::left << (*i).second.first + << std::endl; + } + + if (report_all && total_memory_count->size() > 0) { + out << "Total memory counts:" << std::endl; + report_count_map(out, *total_memory_count); + } + + if (live_object_count->size() > 0) { + out << "Live object count:" << std::endl; + report_count_map(out, *live_object_count); + } + + if (live_objects->size() > 0) { + out << "Live objects:" << std::endl; + + for (live_objects_map::const_iterator i = live_objects->begin(); + i != live_objects->end(); + i++) + out << " " << std::right << std::setw(7) << (*i).first + << " " << std::right << std::setw(7) << (*i).second.second + << " " << std::left << (*i).second.first + << std::endl; + } + + if (report_all) { + if (total_object_count->size() > 0) { + out << "Total object counts:" << std::endl; + report_count_map(out, *total_object_count); + } + + if (total_ctor_count->size() > 0) { + out << "Total constructor counts:" << std::endl; + report_count_map(out, *total_ctor_count); + } + } +} + +#if ! defined(USE_BOOST_PYTHON) + +string::string() : std::string() { + TRACE_CTOR(string, ""); +} +string::string(const string& str) : std::string(str) { + TRACE_CTOR(string, "const string&"); +} +string::string(const std::string& str) : std::string(str) { + TRACE_CTOR(string, "const std::string&"); +} +string::string(const int len, char x) : std::string(len, x) { + TRACE_CTOR(string, "const int, char"); +} +string::string(const char * str) : std::string(str) { + TRACE_CTOR(string, "const char *"); +} +string::string(const char * str, const char * end) : std::string(str, end) { + TRACE_CTOR(string, "const char *, const char *"); +} +string::string(const string& str, int x) : std::string(str, x) { + TRACE_CTOR(string, "const string&, int"); +} +string::string(const string& str, int x, int y) : std::string(str, x, y) { + TRACE_CTOR(string, "const string&, int, int"); +} +string::string(const char * str, int x) : std::string(str, x) { + TRACE_CTOR(string, "const char *, int"); +} +string::string(const char * str, int x, int y) : std::string(str, x, y) { + TRACE_CTOR(string, "const char *, int, int"); +} +string::~string() { + TRACE_DTOR(string); +} + +#endif + +} // namespace ledger + +#endif // VERIFY_ON + +/********************************************************************** + * + * Logging + */ + +#if defined(LOGGING_ON) + +namespace ledger { + +log_level_t _log_level; +std::ostream * _log_stream = &std::cerr; +std::ostringstream _log_buffer; + +#if defined(TRACING_ON) +unsigned int _trace_level; +#endif + +#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK +#define CURRENT_TIME() boost::posix_time::microsec_clock::universal_time() +#else +#define CURRENT_TIME() boost::posix_time::second_clock::universal_time() +#endif + +static inline void stream_memory_size(std::ostream& out, std::size_t size) +{ + if (size < 1024) + out << size << 'b'; + else if (size < (1024 * 1024)) + out << (double(size) / 1024.0) << 'K'; + else if (size < (1024 * 1024 * 1024)) + out << (double(size) / (1024.0 * 1024.0)) << 'M'; + else if (size < (1024 * 1024 * 1024 * 1024)) + out << (double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G'; + else + assert(false); +} + +static bool logger_has_run = false; +static ptime logger_start; + +bool logger_func(log_level_t level) +{ + unsigned long appender = 0; + + if (! logger_has_run) { + logger_has_run = true; + logger_start = CURRENT_TIME(); + + IF_VERIFY() + *_log_stream << " TIME OBJSZ MEMSZ" << std::endl; + + appender = (logger_start - now).total_milliseconds(); + } + + *_log_stream << std::right << std::setw(5) + << (CURRENT_TIME() - logger_start).total_milliseconds(); + + IF_VERIFY() { + *_log_stream << std::right << std::setw(6) << std::setprecision(3); + stream_memory_size(*_log_stream, current_objects_size()); + *_log_stream << std::right << std::setw(6) << std::setprecision(3); + stream_memory_size(*_log_stream, current_memory_size()); + } + + *_log_stream << " " << std::left << std::setw(7); + + switch (level) { + case LOG_CRIT: *_log_stream << "[CRIT]"; break; + case LOG_FATAL: *_log_stream << "[FATAL]"; break; + case LOG_ASSERT: *_log_stream << "[ASSRT]"; break; + case LOG_ERROR: *_log_stream << "[ERROR]"; break; + case LOG_VERIFY: *_log_stream << "[VERFY]"; break; + case LOG_WARN: *_log_stream << "[WARN]"; break; + case LOG_INFO: *_log_stream << "[INFO]"; break; + case LOG_EXCEPT: *_log_stream << "[EXCPT]"; break; + case LOG_DEBUG: *_log_stream << "[DEBUG]"; break; + case LOG_TRACE: *_log_stream << "[TRACE]"; break; + + case LOG_OFF: + case LOG_ALL: + assert(false); + break; + } + + *_log_stream << ' ' << _log_buffer.str(); + + if (appender) + *_log_stream << " (" << appender << "ms startup)"; + + *_log_stream << std::endl; + + _log_buffer.str(""); + + return true; +} + +} // namespace ledger + +#if defined(DEBUG_ON) + +#include + +namespace ledger { + +std::string _log_category; + +} // namespace ledger + +#endif // DEBUG_ON +#endif // LOGGING_ON + +/********************************************************************** + * + * Timers (allows log entries to specify cumulative time spent) + */ + +#if defined(LOGGING_ON) && defined(TIMERS_ON) + +namespace ledger { + +struct timer_t { + log_level_t level; + ptime begin; + time_duration spent; + std::string description; + bool active; + + timer_t(log_level_t _level, std::string _description) + : level(_level), begin(CURRENT_TIME()), + spent(time_duration(0, 0, 0, 0)), + description(_description), active(true) {} +}; + +typedef std::map timer_map; +typedef std::pair timer_pair; + +static timer_map timers; + +void start_timer(const char * name, log_level_t lvl) +{ +#if defined(VERIFY_ON) + memory_tracing_active = false; +#endif + + timer_map::iterator i = timers.find(name); + if (i == timers.end()) { + timers.insert(timer_pair(name, timer_t(lvl, _log_buffer.str()))); + } else { + assert((*i).second.description == _log_buffer.str()); + (*i).second.begin = CURRENT_TIME(); + (*i).second.active = true; + } + _log_buffer.str(""); + +#if defined(VERIFY_ON) + memory_tracing_active = true; +#endif +} + +void stop_timer(const char * name) +{ +#if defined(VERIFY_ON) + memory_tracing_active = false; +#endif + + timer_map::iterator i = timers.find(name); + assert(i != timers.end()); + + (*i).second.spent += CURRENT_TIME() - (*i).second.begin; + (*i).second.active = false; + +#if defined(VERIFY_ON) + memory_tracing_active = true; +#endif +} + +void finish_timer(const char * name) +{ +#if defined(VERIFY_ON) + memory_tracing_active = false; +#endif + + timer_map::iterator i = timers.find(name); + if (i == timers.end()) + return; + + time_duration spent = (*i).second.spent; + if ((*i).second.active) { + spent = CURRENT_TIME() - (*i).second.begin; + (*i).second.active = false; + } + + _log_buffer << (*i).second.description << ' '; + + bool need_paren = + (*i).second.description[(*i).second.description.size() - 1] != ':'; + + if (need_paren) + _log_buffer << '('; + + _log_buffer << spent.total_milliseconds() << "ms"; + + if (need_paren) + _log_buffer << ')'; + + logger_func((*i).second.level); + + timers.erase(i); + +#if defined(VERIFY_ON) + memory_tracing_active = true; +#endif +} + +} // namespace ledger + +#endif // LOGGING_ON && TIMERS_ON + +/********************************************************************** + * + * Exception handling + */ + +namespace ledger { + +std::ostringstream _exc_buffer; + +} // namespace ledger + +/********************************************************************** + * + * General utility functions + */ + +namespace ledger { + +string expand_path(const string& path) +{ + if (path.length() == 0 || path[0] != '~') + return path; + + const char * pfx = NULL; + string::size_type pos = path.find_first_of('/'); + + if (path.length() == 1 || pos == 1) { + pfx = std::getenv("HOME"); +#ifdef HAVE_GETPWUID + if (! pfx) { + // Punt. We're trying to expand ~/, but HOME isn't set + struct passwd * pw = getpwuid(getuid()); + if (pw) + pfx = pw->pw_dir; + } +#endif + } +#ifdef HAVE_GETPWNAM + else { + string user(path, 1, pos == string::npos ? + string::npos : pos - 1); + struct passwd * pw = getpwnam(user.c_str()); + if (pw) + pfx = pw->pw_dir; + } +#endif + + // if we failed to find an expansion, return the path unchanged. + + if (! pfx) + return path; + + string result(pfx); + + if (pos == string::npos) + return result; + + if (result.length() == 0 || result[result.length() - 1] != '/') + result += '/'; + + result += path.substr(pos + 1); + + return result; +} + +string resolve_path(const string& path) +{ + if (path[0] == '~') + return expand_path(path); + return path; +} + +} // namespace ledger diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 00000000..79595c71 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,422 @@ +#ifndef _UTILS_H +#define _UTILS_H + +#include + +/********************************************************************** + * + * Forward declarations + */ + +namespace ledger { +#if ! defined(USE_BOOST_PYTHON) + class string; +#else + typedef std::string string; +#endif +} + +/********************************************************************** + * + * Default values + */ + +#if defined(FULL_DEBUG) +#define VERIFY_ON 1 +#define TRACING_ON 1 +#define DEBUG_ON 1 +#define TIMERS_ON 1 +#define FREE_MEMORY 1 +#elif defined(NO_DEBUG) +#define NO_ASSERTS 1 +#define NO_LOGGING 1 +#else +#define VERIFY_ON 1 // compiled in, use --verify to enable +#define TRACING_ON 1 // use --trace X to enable +#define TIMERS_ON 1 // jww (2007-04-25): is this correct? +#endif + +/********************************************************************** + * + * Assertions + */ + +#ifdef assert +#undef assert +#endif + +#if ! defined(NO_ASSERTS) +#define ASSERTS_ON 1 +#endif +#if defined(ASSERTS_ON) + +#include + +namespace ledger { + void debug_assert(const string& reason, const string& func, + const string& file, unsigned long line); +} + +#define assert(x) \ + ((x) ? ((void)0) : debug_assert(#x, BOOST_CURRENT_FUNCTION, \ + __FILE__, __LINE__)) + +#endif // ASSERTS_ON + +/********************************************************************** + * + * Verification (basically, very slow asserts) + */ + +#if defined(VERIFY_ON) + +namespace ledger { + +extern bool verify_enabled; + +#define VERIFY(x) (ledger::verify_enabled ? assert(x) : ((void)0)) +#define DO_VERIFY() ledger::verify_enabled +#define IF_VERIFY() if (DO_VERIFY()) + +void initialize_memory_tracing(); +void shutdown_memory_tracing(); + +std::size_t current_memory_size(); +std::size_t current_objects_size(); + +void trace_ctor_func(void * ptr, const char * cls_name, const char * args, + std::size_t cls_size); +void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size); + +#define TRACE_CTOR(cls, args) \ + (DO_VERIFY() ? trace_ctor_func(this, #cls, args, sizeof(cls)) : ((void)0)) +#define TRACE_DTOR(cls) \ + (DO_VERIFY() ? trace_dtor_func(this, #cls, sizeof(cls)) : ((void)0)) + +void report_memory(std::ostream& out, bool report_all = false); + +#if ! defined(USE_BOOST_PYTHON) + +class string : public std::string +{ +public: + string(); + string(const string& str); + string(const std::string& str); + string(const int len, char x); + string(const char * str); + string(const char * str, const char * end); + string(const string& str, int x); + string(const string& str, int x, int y); + string(const char * str, int x); + string(const char * str, int x, int y); + ~string(); +}; + +inline string operator+(const string& __lhs, const string& __rhs) +{ + string __str(__lhs); + __str.append(__rhs); + return __str; +} + +string operator+(const char* __lhs, const string& __rhs); +string operator+(char __lhs, const string& __rhs); + +inline string operator+(const string& __lhs, const char* __rhs) +{ + string __str(__lhs); + __str.append(__rhs); + return __str; +} + +inline string operator+(const string& __lhs, char __rhs) +{ + typedef string __string_type; + typedef string::size_type __size_type; + __string_type __str(__lhs); + __str.append(__size_type(1), __rhs); + return __str; +} + +inline bool operator==(const string& __lhs, const string& __rhs) +{ return __lhs.compare(__rhs) == 0; } + +inline bool operator==(const char* __lhs, const string& __rhs) +{ return __rhs.compare(__lhs) == 0; } + +inline bool operator==(const string& __lhs, const char* __rhs) +{ return __lhs.compare(__rhs) == 0; } + +inline bool operator!=(const string& __lhs, const string& __rhs) +{ return __rhs.compare(__lhs) != 0; } + +inline bool operator!=(const char* __lhs, const string& __rhs) +{ return __rhs.compare(__lhs) != 0; } + +inline bool operator!=(const string& __lhs, const char* __rhs) +{ return __lhs.compare(__rhs) != 0; } + +#endif + +} // namespace ledger + +#else // ! VERIFY_ON + +#define VERIFY(x) +#define TRACE_CTOR(cls, args) +#define TRACE_DTOR(cls) + +#endif // VERIFY_ON + +/********************************************************************** + * + * Logging + */ + +#if ! defined(NO_LOGGING) +#define LOGGING_ON 1 +#endif +#if defined(LOGGING_ON) + +namespace ledger { + +enum log_level_t { + LOG_OFF = 0, + LOG_CRIT, + LOG_FATAL, + LOG_ASSERT, + LOG_ERROR, + LOG_VERIFY, + LOG_WARN, + LOG_INFO, + LOG_EXCEPT, + LOG_DEBUG, + LOG_TRACE, + LOG_ALL +}; + +extern log_level_t _log_level; +extern std::ostream * _log_stream; +extern std::ostringstream _log_buffer; + +bool logger_func(log_level_t level); + +#define LOGGER(cat) \ + static const char * const _this_category = cat + +#if defined(TRACING_ON) + +extern unsigned int _trace_level; + +#define SHOW_TRACE(lvl) \ + (_log_level >= LOG_TRACE && lvl <= _trace_level) +#define TRACE(lvl, msg) \ + (SHOW_TRACE(lvl) ? ((_log_buffer << msg), logger_func(LOG_TRACE)) : false) + +#else // TRACING_ON + +#define SHOW_TRACE(lvl) false +#define TRACE(lvl, msg) + +#endif // TRACING_ON + +#if defined(DEBUG_ON) + +extern std::string _log_category; + +inline bool category_matches(const char * cat) { + return (_log_category == cat || + (std::strlen(cat) > _log_category.size() + 1 && + std::strncmp(cat, _log_category.c_str(), + _log_category.size()) == 0 && + cat[_log_category.size()] == '.')); +} + +#define SHOW_DEBUG_(cat) \ + (_log_level >= LOG_DEBUG && category_matches(cat)) +#define SHOW_DEBUG() SHOW_DEBUG_(_this_category) + +#define DEBUG_(cat, msg) \ + (SHOW_DEBUG_(cat) ? ((_log_buffer << msg), logger_func(LOG_DEBUG)) : false) +#define DEBUG(msg) DEBUG_(_this_category, msg) + +#else // DEBUG_ON + +#define SHOW_DEBUG_(cat) false +#define SHOW_DEBUG() false +#define DEBUG_(cat, msg) +#define DEBUG(msg) + +#endif // DEBUG_ON + +#define LOG_MACRO(level, msg) \ + (_log_level >= level ? \ + ((_log_buffer << msg), logger_func(level)) : false) + +#define SHOW_INFO() (_log_level >= LOG_INFO) +#define SHOW_WARN() (_log_level >= LOG_WARN) +#define SHOW_ERROR() (_log_level >= LOG_ERROR) +#define SHOW_FATAL() (_log_level >= LOG_FATAL) +#define SHOW_CRITICAL() (_log_level >= LOG_CRIT) + +#define INFO(msg) LOG_MACRO(LOG_INFO, msg) +#define WARN(msg) LOG_MACRO(LOG_WARN, msg) +#define ERROR(msg) LOG_MACRO(LOG_ERROR, msg) +#define FATAL(msg) LOG_MACRO(LOG_FATAL, msg) +#define CRITICAL(msg) LOG_MACRO(LOG_CRIT, msg) +#define EXCEPTION(msg) LOG_MACRO(LOG_EXCEPT, msg) + +} // namespace ledger + +#else // ! LOGGING_ON + +#define LOGGER(cat) + +#define SHOW_TRACE(lvl) false +#define SHOW_DEBUG_(cat) false +#define SHOW_DEBUG() false +#define SHOW_INFO() false +#define SHOW_WARN() false +#define SHOW_ERROR() false +#define SHOW_FATAL() false +#define SHOW_CRITICAL() false + +#define TRACE(lvl, msg) +#define DEBUG(msg) +#define DEBUG_(cat, msg) +#define INFO(msg) +#define WARN(msg) +#define ERROR(msg) +#define FATAL(msg) +#define CRITICAL(msg) + +#endif // LOGGING_ON + +#define IF_TRACE(lvl) if (SHOW_TRACE(lvl)) +#define IF_DEBUG_(cat) if (SHOW_DEBUG_(cat)) +#define IF_DEBUG() if (SHOW_DEBUG()) +#define IF_INFO() if (SHOW_INFO()) +#define IF_WARN() if (SHOW_WARN()) +#define IF_ERROR() if (SHOW_ERROR()) +#define IF_FATAL() if (SHOW_FATAL()) +#define IF_CRITICAL() if (SHOW_CRITICAL()) + +/********************************************************************** + * + * Timers (allows log entries to specify cumulative time spent) + */ + +#if defined(LOGGING_ON) && defined(TIMERS_ON) + +namespace ledger { + +void start_timer(const char * name, log_level_t lvl); +void stop_timer(const char * name); +void finish_timer(const char * name); + +#if defined(TRACING_ON) +#define TRACE_START(name, lvl, msg) \ + (SHOW_TRACE(lvl) ? \ + ((_log_buffer << msg), start_timer(#name, LOG_TRACE)) : ((void)0)) +#define TRACE_STOP(name, lvl) \ + (SHOW_TRACE(lvl) ? stop_timer(#name) : ((void)0)) +#define TRACE_FINISH(name, lvl) \ + (SHOW_TRACE(lvl) ? finish_timer(#name) : ((void)0)) +#else +#define TRACE_START(name, lvl, msg) +#define TRACE_STOP(name) +#define TRACE_FINISH(name) +#endif + +#if defined(DEBUG_ON) +#define DEBUG_START_(name, cat, msg) \ + (SHOW_DEBUG_(cat) ? \ + ((_log_buffer << msg), start_timer(#name, LOG_DEBUG)) : ((void)0)) +#define DEBUG_START(name, msg) \ + DEBUG_START_(name, _this_category, msg) +#define DEBUG_STOP_(name, cat) \ + (SHOW_DEBUG_(cat) ? stop_timer(#name) : ((void)0)) +#define DEBUG_STOP(name) \ + DEBUG_STOP_(name, _this_category) +#define DEBUG_FINISH_(name, cat) \ + (SHOW_DEBUG_(cat) ? finish_timer(#name) : ((void)0)) +#define DEBUG_FINISH(name) \ + DEBUG_FINISH_(name, _this_category) +#else +#define DEBUG_START(name, msg) +#define DEBUG_START_(name, cat, msg) +#define DEBUG_STOP(name) +#define DEBUG_FINISH(name) +#endif + +#define INFO_START(name, msg) \ + (SHOW_INFO() ? \ + ((_log_buffer << msg), start_timer(#name, LOG_INFO)) : ((void)0)) +#define INFO_STOP(name) \ + (SHOW_INFO() ? stop_timer(#name) : ((void)0)) +#define INFO_FINISH(name) \ + (SHOW_INFO() ? finish_timer(#name) : ((void)0)) + +} // namespace ledger + +#else // ! (LOGGING_ON && TIMERS_ON) + +#define TRACE_START(lvl, msg, name) +#define TRACE_STOP(name) +#define TRACE_FINISH(name) + +#define DEBUG_START(name, msg) +#define DEBUG_START_(name, cat, msg) +#define DEBUG_STOP(name) +#define DEBUG_FINISH(name) + +#define INFO_START(name, msg) +#define INFO_STOP(name) +#define INFO_FINISH(name) + +#endif // TIMERS_ON + +/********************************************************************** + * + * Exception handling + */ + +#include "error.h" + +namespace ledger { + +extern std::ostringstream _exc_buffer; + +template +inline void throw_func(const std::string& message) { + _exc_buffer.str(""); + throw T(message, context()); +} + +#define throw_(cls, msg) \ + ((_exc_buffer << msg), throw_func(_exc_buffer.str())) + +} // namespace ledger + +/********************************************************************** + * + * General utility functions + */ + +namespace ledger { + +string resolve_path(const string& path); + +#ifdef HAVE_REALPATH +extern "C" char * realpath(const char *, char resolved_path[]); +#endif + +inline const string& either_or(const string& first, + const string& second) { + return first.empty() ? second : first; +} + +} // namespace ledger + +#endif // _UTILS_H diff --git a/src/value.cc b/src/value.cc new file mode 100644 index 00000000..914a49e2 --- /dev/null +++ b/src/value.cc @@ -0,0 +1,2311 @@ +#include "value.h" +#include "xml.h" + +namespace ledger { + +bool value_t::to_boolean() const +{ + if (type == BOOLEAN) { + return *(bool *) data; + } else { + value_t temp(*this); + temp.in_place_cast(BOOLEAN); + return *(bool *) temp.data; + } +} + +long value_t::to_integer() const +{ + if (type == INTEGER) { + return *(long *) data; + } else { + value_t temp(*this); + temp.in_place_cast(INTEGER); + return *(long *) temp.data; + } +} + +moment_t value_t::to_datetime() const +{ + if (type == DATETIME) { + return *(moment_t *) data; + } else { + value_t temp(*this); + temp.in_place_cast(DATETIME); + return *(moment_t *) temp.data; + } +} + +amount_t value_t::to_amount() const +{ + if (type == AMOUNT) { + return *(amount_t *) data; + } else { + value_t temp(*this); + temp.in_place_cast(AMOUNT); + return *(amount_t *) temp.data; + } +} + +balance_t value_t::to_balance() const +{ + if (type == BALANCE) { + return *(balance_t *) data; + } else { + value_t temp(*this); + temp.in_place_cast(BALANCE); + return *(balance_t *) temp.data; + } +} + +balance_pair_t value_t::to_balance_pair() const +{ + if (type == BALANCE_PAIR) { + return *(balance_pair_t *) data; + } else { + value_t temp(*this); + temp.in_place_cast(BALANCE_PAIR); + return *(balance_pair_t *) temp.data; + } +} + +string value_t::to_string() const +{ + if (type == STRING) { + return **(string **) data; + } else { + std::ostringstream out; + out << *this; + return out.str(); + } +} + +xml::node_t * value_t::to_xml_node() const +{ + if (type == XML_NODE) + return *(xml::node_t **) data; + else + throw_(value_exception, "Value is not an XML node"); +} + +void * value_t::to_pointer() const +{ + if (type == POINTER) + return *(void **) data; + else + throw_(value_exception, "Value is not a pointer"); +} + +value_t::sequence_t * value_t::to_sequence() const +{ + if (type == SEQUENCE) + return *(sequence_t **) data; + else + throw_(value_exception, "Value is not a sequence"); +} + +void value_t::destroy() +{ + switch (type) { + case AMOUNT: + ((amount_t *)data)->~amount_t(); + break; + case BALANCE: + ((balance_t *)data)->~balance_t(); + break; + case BALANCE_PAIR: + ((balance_pair_t *)data)->~balance_pair_t(); + break; + case STRING: + delete *(string **) data; + break; + case SEQUENCE: + delete *(sequence_t **) data; + break; + default: + break; + } +} + +void value_t::simplify() +{ + if (realzero()) { + DEBUG_("amounts.values.simplify", "Zeroing type " << type); + *this = 0L; + return; + } + + if (type == BALANCE_PAIR && + (! ((balance_pair_t *) data)->cost || + ((balance_pair_t *) data)->cost->realzero())) { + DEBUG_("amounts.values.simplify", "Reducing balance pair to balance"); + in_place_cast(BALANCE); + } + + if (type == BALANCE && + ((balance_t *) data)->amounts.size() == 1) { + DEBUG_("amounts.values.simplify", "Reducing balance to amount"); + in_place_cast(AMOUNT); + } + + if (type == AMOUNT && + ! ((amount_t *) data)->commodity()) { + DEBUG_("amounts.values.simplify", "Reducing amount to integer"); + in_place_cast(INTEGER); + } +} + +value_t& value_t::operator=(const value_t& val) +{ + if (this == &val) + return *this; + + if (type == BOOLEAN && val.type == BOOLEAN) { + *((bool *) data) = *((bool *) val.data); + return *this; + } + else if (type == INTEGER && val.type == INTEGER) { + *((long *) data) = *((long *) val.data); + return *this; + } + else if (type == DATETIME && val.type == DATETIME) { + *((moment_t *) data) = *((moment_t *) val.data); + return *this; + } + else if (type == AMOUNT && val.type == AMOUNT) { + *(amount_t *) data = *(amount_t *) val.data; + return *this; + } + else if (type == BALANCE && val.type == BALANCE) { + *(balance_t *) data = *(balance_t *) val.data; + return *this; + } + else if (type == BALANCE_PAIR && val.type == BALANCE_PAIR) { + *(balance_pair_t *) data = *(balance_pair_t *) val.data; + return *this; + } + else if (type == STRING && val.type == STRING) { + **(string **) data = **(string **) val.data; + return *this; + } + else if (type == SEQUENCE && val.type == SEQUENCE) { + **(sequence_t **) data = **(sequence_t **) val.data; + return *this; + } + + destroy(); + + switch (val.type) { + case BOOLEAN: + *((bool *) data) = *((bool *) val.data); + break; + + case INTEGER: + *((long *) data) = *((long *) val.data); + break; + + case DATETIME: + *((moment_t *) data) = *((moment_t *) val.data); + break; + + case AMOUNT: + new((amount_t *)data) amount_t(*((amount_t *) val.data)); + break; + + case BALANCE: + new((balance_t *)data) balance_t(*((balance_t *) val.data)); + break; + + case BALANCE_PAIR: + new((balance_pair_t *)data) balance_pair_t(*((balance_pair_t *) val.data)); + break; + + case STRING: + *(string **) data = new string(**(string **) val.data); + break; + + case XML_NODE: + *(xml::node_t **) data = *(xml::node_t **) val.data; + break; + + case POINTER: + *(void **) data = *(void **) val.data; + break; + + case SEQUENCE: + *(sequence_t **) data = new sequence_t(**(sequence_t **) val.data); + break; + + default: + assert(0); + break; + } + + type = val.type; + + return *this; +} + +value_t& value_t::operator+=(const value_t& val) +{ + if (val.type == BOOLEAN) + throw_(value_exception, "Cannot add a boolean to a value"); + else if (val.type == DATETIME) + throw_(value_exception, "Cannot add a date/time to a value"); + else if (val.type == POINTER) + throw_(value_exception, "Cannot add a pointer to a value"); + else if (val.type == SEQUENCE) + throw_(value_exception, "Cannot add a sequence to a value"); + else if (val.type == XML_NODE) // recurse + return *this += (*(xml::node_t **) val.data)->to_value(); + + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot add a value to a boolean"); + + case INTEGER: + switch (val.type) { + case INTEGER: + *((long *) data) += *((long *) val.data); + break; + case AMOUNT: + in_place_cast(AMOUNT); + *((amount_t *) data) += *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) += *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) += *((balance_pair_t *) val.data); + break; + case STRING: + throw_(value_exception, "Cannot add a string to an integer"); + default: + assert(0); + break; + } + break; + + case DATETIME: + switch (val.type) { + case INTEGER: + *((moment_t *) data) += date_duration(*((long *) val.data)); + break; + case AMOUNT: + *((moment_t *) data) += date_duration(long(*((amount_t *) val.data))); + break; + case BALANCE: + *((moment_t *) data) += date_duration(long(*((balance_t *) val.data))); + break; + case BALANCE_PAIR: + *((moment_t *) data) += date_duration(long(*((balance_pair_t *) val.data))); + break; + case STRING: + throw_(value_exception, "Cannot add a string to an date/time"); + default: + assert(0); + break; + } + break; + + case AMOUNT: + switch (val.type) { + case INTEGER: + if (*((long *) val.data) && + ((amount_t *) data)->commodity()) { + in_place_cast(BALANCE); + return *this += val; + } + *((amount_t *) data) += *((long *) val.data); + break; + + case AMOUNT: + if (((amount_t *) data)->commodity() != + ((amount_t *) val.data)->commodity()) { + in_place_cast(BALANCE); + return *this += val; + } + *((amount_t *) data) += *((amount_t *) val.data); + break; + + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) += *((balance_t *) val.data); + break; + + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) += *((balance_pair_t *) val.data); + break; + + case STRING: + throw_(value_exception, "Cannot add a string to an amount"); + + default: + assert(0); + break; + } + break; + + case BALANCE: + switch (val.type) { + case INTEGER: + *((balance_t *) data) += *((long *) val.data); + break; + case AMOUNT: + *((balance_t *) data) += *((amount_t *) val.data); + break; + case BALANCE: + *((balance_t *) data) += *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) += *((balance_pair_t *) val.data); + break; + case STRING: + throw_(value_exception, "Cannot add a string to an balance"); + default: + assert(0); + break; + } + break; + + case BALANCE_PAIR: + switch (val.type) { + case INTEGER: + *((balance_pair_t *) data) += *((long *) val.data); + break; + case AMOUNT: + *((balance_pair_t *) data) += *((amount_t *) val.data); + break; + case BALANCE: + *((balance_pair_t *) data) += *((balance_t *) val.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) += *((balance_pair_t *) val.data); + break; + case STRING: + throw_(value_exception, "Cannot add a string to an balance pair"); + default: + assert(0); + break; + } + break; + + case STRING: + switch (val.type) { + case INTEGER: + throw_(value_exception, "Cannot add an integer to a string"); + case AMOUNT: + throw_(value_exception, "Cannot add an amount to a string"); + case BALANCE: + throw_(value_exception, "Cannot add a balance to a string"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot add a balance pair to a string"); + case STRING: + **(string **) data += **(string **) val.data; + break; + default: + assert(0); + break; + } + break; + + case XML_NODE: + throw_(value_exception, "Cannot add a value to an XML node"); + + case POINTER: + throw_(value_exception, "Cannot add a value to a pointer"); + + case SEQUENCE: + throw_(value_exception, "Cannot add a value to a sequence"); + + default: + assert(0); + break; + } + return *this; +} + +value_t& value_t::operator-=(const value_t& val) +{ + if (val.type == BOOLEAN) + throw_(value_exception, "Cannot subtract a boolean from a value"); + else if (val.type == DATETIME && type != DATETIME) + throw_(value_exception, "Cannot subtract a date/time from a value"); + else if (val.type == STRING) + throw_(value_exception, "Cannot subtract a string from a value"); + else if (val.type == POINTER) + throw_(value_exception, "Cannot subtract a pointer from a value"); + else if (val.type == SEQUENCE) + throw_(value_exception, "Cannot subtract a sequence from a value"); + else if (val.type == XML_NODE) // recurse + return *this -= (*(xml::node_t **) val.data)->to_value(); + + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot subtract a value from a boolean"); + + case INTEGER: + switch (val.type) { + case INTEGER: + *((long *) data) -= *((long *) val.data); + break; + case AMOUNT: + in_place_cast(AMOUNT); + *((amount_t *) data) -= *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) -= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case DATETIME: + switch (val.type) { + case INTEGER: + *((moment_t *) data) -= date_duration(*((long *) val.data)); + break; + case DATETIME: { + duration_t tval = ((moment_t *) data)->operator-(*((moment_t *) val.data)); + in_place_cast(INTEGER); + *((long *) data) = tval.total_seconds() / 86400L; + break; + } + case AMOUNT: + *((moment_t *) data) -= date_duration(long(*((amount_t *) val.data))); + break; + case BALANCE: + *((moment_t *) data) -= date_duration(long(*((balance_t *) val.data))); + break; + case BALANCE_PAIR: + *((moment_t *) data) -= date_duration(long(*((balance_pair_t *) val.data))); + break; + default: + assert(0); + break; + } + break; + + case AMOUNT: + switch (val.type) { + case INTEGER: + if (*((long *) val.data) && + ((amount_t *) data)->commodity()) { + in_place_cast(BALANCE); + return *this -= val; + } + *((amount_t *) data) -= *((long *) val.data); + break; + + case AMOUNT: + if (((amount_t *) data)->commodity() != + ((amount_t *) val.data)->commodity()) { + in_place_cast(BALANCE); + return *this -= val; + } + *((amount_t *) data) -= *((amount_t *) val.data); + break; + + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) -= *((balance_t *) val.data); + break; + + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); + break; + + default: + assert(0); + break; + } + break; + + case BALANCE: + switch (val.type) { + case INTEGER: + *((balance_t *) data) -= *((long *) val.data); + break; + case AMOUNT: + *((balance_t *) data) -= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_t *) data) -= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case BALANCE_PAIR: + switch (val.type) { + case INTEGER: + *((balance_pair_t *) data) -= *((long *) val.data); + break; + case AMOUNT: + *((balance_pair_t *) data) -= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_pair_t *) data) -= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case STRING: + throw_(value_exception, "Cannot subtract a value from a string"); + case XML_NODE: + throw_(value_exception, "Cannot subtract a value from an XML node"); + case POINTER: + throw_(value_exception, "Cannot subtract a value from a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot subtract a value from a sequence"); + + default: + assert(0); + break; + } + + simplify(); + + return *this; +} + +value_t& value_t::operator*=(const value_t& val) +{ + if (val.type == BOOLEAN) + throw_(value_exception, "Cannot multiply a value by a boolean"); + else if (val.type == DATETIME) + throw_(value_exception, "Cannot multiply a value by a date/time"); + else if (val.type == STRING) + throw_(value_exception, "Cannot multiply a value by a string"); + else if (val.type == POINTER) + throw_(value_exception, "Cannot multiply a value by a pointer"); + else if (val.type == SEQUENCE) + throw_(value_exception, "Cannot multiply a value by a sequence"); + else if (val.type == XML_NODE) // recurse + return *this *= (*(xml::node_t **) val.data)->to_value(); + + if (val.realzero() && type != STRING) { + *this = 0L; + return *this; + } + + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot multiply a value by a boolean"); + + case INTEGER: + switch (val.type) { + case INTEGER: + *((long *) data) *= *((long *) val.data); + break; + case AMOUNT: + in_place_cast(AMOUNT); + *((amount_t *) data) *= *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) *= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case AMOUNT: + switch (val.type) { + case INTEGER: + *((amount_t *) data) *= *((long *) val.data); + break; + case AMOUNT: + *((amount_t *) data) *= *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) *= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case BALANCE: + switch (val.type) { + case INTEGER: + *((balance_t *) data) *= *((long *) val.data); + break; + case AMOUNT: + *((balance_t *) data) *= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_t *) data) *= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case BALANCE_PAIR: + switch (val.type) { + case INTEGER: + *((balance_pair_t *) data) *= *((long *) val.data); + break; + case AMOUNT: + *((balance_pair_t *) data) *= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_pair_t *) data) *= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case STRING: + switch (val.type) { + case INTEGER: { + string temp; + for (long i = 0; i < *(long *) val.data; i++) + temp += **(string **) data; + **(string **) data = temp; + break; + } + case AMOUNT: { + string temp; + value_t num(val); + num.in_place_cast(INTEGER); + for (long i = 0; i < *(long *) num.data; i++) + temp += **(string **) data; + **(string **) data = temp; + break; + } + case BALANCE: + throw_(value_exception, "Cannot multiply a string by a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot multiply a string by a balance pair"); + default: + assert(0); + break; + } + break; + + case XML_NODE: + throw_(value_exception, "Cannot multiply an XML node by a value"); + case POINTER: + throw_(value_exception, "Cannot multiply a pointer by a value"); + case SEQUENCE: + throw_(value_exception, "Cannot multiply a sequence by a value"); + + default: + assert(0); + break; + } + return *this; +} + +value_t& value_t::operator/=(const value_t& val) +{ + if (val.type == BOOLEAN) + throw_(value_exception, "Cannot divide a boolean by a value"); + else if (val.type == DATETIME) + throw_(value_exception, "Cannot divide a date/time by a value"); + else if (val.type == STRING) + throw_(value_exception, "Cannot divide a string by a value"); + else if (val.type == POINTER) + throw_(value_exception, "Cannot divide a pointer by a value"); + else if (val.type == SEQUENCE) + throw_(value_exception, "Cannot divide a value by a sequence"); + else if (val.type == XML_NODE) // recurse + return *this /= (*(xml::node_t **) val.data)->to_value(); + + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot divide a value by a boolean"); + + case INTEGER: + switch (val.type) { + case INTEGER: + *((long *) data) /= *((long *) val.data); + break; + case AMOUNT: + in_place_cast(AMOUNT); + *((amount_t *) data) /= *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) /= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case AMOUNT: + switch (val.type) { + case INTEGER: + *((amount_t *) data) /= *((long *) val.data); + break; + case AMOUNT: + *((amount_t *) data) /= *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) /= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case BALANCE: + switch (val.type) { + case INTEGER: + *((balance_t *) data) /= *((long *) val.data); + break; + case AMOUNT: + *((balance_t *) data) /= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_t *) data) /= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case BALANCE_PAIR: + switch (val.type) { + case INTEGER: + *((balance_pair_t *) data) /= *((long *) val.data); + break; + case AMOUNT: + *((balance_pair_t *) data) /= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_pair_t *) data) /= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case STRING: + throw_(value_exception, "Cannot divide a value from a string"); + case XML_NODE: + throw_(value_exception, "Cannot divide a value from an XML node"); + case POINTER: + throw_(value_exception, "Cannot divide a value from a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot divide a value from a sequence"); + + default: + assert(0); + break; + } + return *this; +} + +template <> +value_t::operator bool() const +{ + switch (type) { + case BOOLEAN: + return *(bool *) data; + case INTEGER: + return *(long *) data; + case DATETIME: + return is_valid_moment(*((moment_t *) data)); + case AMOUNT: + return *(amount_t *) data; + case BALANCE: + return *(balance_t *) data; + case BALANCE_PAIR: + return *(balance_pair_t *) data; + case STRING: + return ! (**((string **) data)).empty(); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().to_boolean(); + case POINTER: + return *(void **) data != NULL; + case SEQUENCE: + return (*(sequence_t **) data != NULL && + ! (*(sequence_t **) data)->empty()); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator long() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot convert a boolean to an integer"); + case INTEGER: + return *((long *) data); + case DATETIME: + throw_(value_exception, "Cannot convert a date/time to an integer"); + case AMOUNT: + return *((amount_t *) data); + case BALANCE: + throw_(value_exception, "Cannot convert a balance to an integer"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a balance pair to an integer"); + case STRING: + throw_(value_exception, "Cannot convert a string to an integer"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().to_integer(); + case POINTER: + throw_(value_exception, "Cannot convert a pointer to an integer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a sequence to an integer"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator moment_t() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot convert a boolean to a date/time"); + case INTEGER: + throw_(value_exception, "Cannot convert an integer to a date/time"); + case DATETIME: + return *((moment_t *) data); + case AMOUNT: + throw_(value_exception, "Cannot convert an amount to a date/time"); + case BALANCE: + throw_(value_exception, "Cannot convert a balance to a date/time"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a balance pair to a date/time"); + case STRING: + throw_(value_exception, "Cannot convert a string to a date/time"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().to_datetime(); + case POINTER: + throw_(value_exception, "Cannot convert a pointer to a date/time"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a sequence to a date/time"); + + default: + assert(0); + break; + } + assert(0); + return moment_t(); +} + +template <> +value_t::operator double() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot convert a boolean to a double"); + case INTEGER: + return *((long *) data); + case DATETIME: + throw_(value_exception, "Cannot convert a date/time to a double"); + case AMOUNT: + return *((amount_t *) data); + case BALANCE: + throw_(value_exception, "Cannot convert a balance to a double"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a balance pair to a double"); + case STRING: + throw_(value_exception, "Cannot convert a string to a double"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().to_amount().number(); + case POINTER: + throw_(value_exception, "Cannot convert a pointer to a double"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a sequence to a double"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator string() const +{ + switch (type) { + case BOOLEAN: + case INTEGER: + case DATETIME: + case AMOUNT: + case BALANCE: + case BALANCE_PAIR: { + value_t temp(*this); + temp.in_place_cast(STRING); + return temp; + } + case STRING: + return **(string **) data; + case XML_NODE: + return (*(xml::node_t **) data)->to_value().to_string(); + + case POINTER: + throw_(value_exception, "Cannot convert a pointer to a string"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a sequence to a string"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +#define DEF_VALUE_CMP_OP(OP) \ +bool value_t::operator OP(const value_t& val) \ +{ \ + switch (type) { \ + case BOOLEAN: \ + switch (val.type) { \ + case BOOLEAN: \ + return *((bool *) data) OP *((bool *) val.data); \ + \ + case INTEGER: \ + return *((bool *) data) OP bool(*((long *) val.data)); \ + \ + case DATETIME: \ + throw_(value_exception, "Cannot compare a boolean to a date/time"); \ + \ + case AMOUNT: \ + return *((bool *) data) OP bool(*((amount_t *) val.data)); \ + \ + case BALANCE: \ + return *((bool *) data) OP bool(*((balance_t *) val.data)); \ + \ + case BALANCE_PAIR: \ + return *((bool *) data) OP bool(*((balance_pair_t *) val.data)); \ + \ + case STRING: \ + throw_(value_exception, "Cannot compare a boolean to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare a boolean to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a boolean to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case INTEGER: \ + switch (val.type) { \ + case BOOLEAN: \ + return (*((long *) data) OP \ + ((long) *((bool *) val.data))); \ + \ + case INTEGER: \ + return (*((long *) data) OP *((long *) val.data)); \ + \ + case DATETIME: \ + throw_(value_exception, "Cannot compare an integer to a date/time"); \ + \ + case AMOUNT: \ + return (amount_t(*((long *) data)) OP \ + *((amount_t *) val.data)); \ + \ + case BALANCE: \ + return (balance_t(*((long *) data)) OP \ + *((balance_t *) val.data)); \ + \ + case BALANCE_PAIR: \ + return (balance_pair_t(*((long *) data)) OP \ + *((balance_pair_t *) val.data)); \ + \ + case STRING: \ + throw_(value_exception, "Cannot compare an integer to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare an integer to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare an integer to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case DATETIME: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare a date/time to a boolean"); \ + \ + case INTEGER: \ + throw_(value_exception, "Cannot compare a date/time to an integer"); \ + \ + case DATETIME: \ + return *((moment_t *) data) OP *((moment_t *) val.data); \ + \ + case AMOUNT: \ + throw_(value_exception, "Cannot compare a date/time to an amount"); \ + case BALANCE: \ + throw_(value_exception, "Cannot compare a date/time to a balance"); \ + case BALANCE_PAIR: \ + throw_(value_exception, "Cannot compare a date/time to a balance pair"); \ + case STRING: \ + throw_(value_exception, "Cannot compare a date/time to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare a date/time to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a date/time to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case AMOUNT: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare an amount to a boolean"); \ + \ + case INTEGER: \ + return (*((amount_t *) data) OP \ + amount_t(*((long *) val.data))); \ + \ + case DATETIME: \ + throw_(value_exception, "Cannot compare an amount to a date/time"); \ + \ + case AMOUNT: \ + return *((amount_t *) data) OP *((amount_t *) val.data); \ + \ + case BALANCE: \ + return (balance_t(*((amount_t *) data)) OP \ + *((balance_t *) val.data)); \ + \ + case BALANCE_PAIR: \ + return (balance_t(*((amount_t *) data)) OP \ + *((balance_pair_t *) val.data)); \ + \ + case STRING: \ + throw_(value_exception, "Cannot compare an amount to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare an amount to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare an amount to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case BALANCE: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare a balance to a boolean"); \ + \ + case INTEGER: \ + return *((balance_t *) data) OP *((long *) val.data); \ + \ + case DATETIME: \ + throw_(value_exception, "Cannot compare a balance to a date/time"); \ + \ + case AMOUNT: \ + return *((balance_t *) data) OP *((amount_t *) val.data); \ + \ + case BALANCE: \ + return *((balance_t *) data) OP *((balance_t *) val.data); \ + \ + case BALANCE_PAIR: \ + return (*((balance_t *) data) OP \ + ((balance_pair_t *) val.data)->quantity); \ + \ + case STRING: \ + throw_(value_exception, "Cannot compare a balance to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare a balance to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a balance to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case BALANCE_PAIR: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare a balance pair to a boolean"); \ + \ + case INTEGER: \ + return (((balance_pair_t *) data)->quantity OP \ + *((long *) val.data)); \ + \ + case DATETIME: \ + throw_(value_exception, "Cannot compare a balance pair to a date/time"); \ + \ + case AMOUNT: \ + return (((balance_pair_t *) data)->quantity OP \ + *((amount_t *) val.data)); \ + \ + case BALANCE: \ + return (((balance_pair_t *) data)->quantity OP \ + *((balance_t *) val.data)); \ + \ + case BALANCE_PAIR: \ + return (*((balance_pair_t *) data) OP \ + *((balance_pair_t *) val.data)); \ + \ + case STRING: \ + throw_(value_exception, "Cannot compare a balance pair to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare a balance pair to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a balance pair to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case STRING: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare a string to a boolean"); \ + case INTEGER: \ + throw_(value_exception, "Cannot compare a string to an integer"); \ + case DATETIME: \ + throw_(value_exception, "Cannot compare a string to a date/time"); \ + case AMOUNT: \ + throw_(value_exception, "Cannot compare a string to an amount"); \ + case BALANCE: \ + throw_(value_exception, "Cannot compare a string to a balance"); \ + case BALANCE_PAIR: \ + throw_(value_exception, "Cannot compare a string to a balance pair"); \ + \ + case STRING: \ + return (**((string **) data) OP \ + **((string **) val.data)); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare a string to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a string to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case XML_NODE: \ + switch (val.type) { \ + case BOOLEAN: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case INTEGER: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case DATETIME: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case AMOUNT: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case BALANCE: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case BALANCE_PAIR: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case STRING: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + \ + case XML_NODE: \ + return ((*(xml::node_t **) data)->to_value() OP \ + (*(xml::node_t **) val.data)->to_value()); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare an XML node to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare an XML node to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case POINTER: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare a pointer to a boolean"); \ + case INTEGER: \ + throw_(value_exception, "Cannot compare a pointer to an integer"); \ + case DATETIME: \ + throw_(value_exception, "Cannot compare a pointer to a date/time"); \ + case AMOUNT: \ + throw_(value_exception, "Cannot compare a pointer to an amount"); \ + case BALANCE: \ + throw_(value_exception, "Cannot compare a pointer to a balance"); \ + case BALANCE_PAIR: \ + throw_(value_exception, "Cannot compare a pointer to a balance pair"); \ + case STRING: \ + throw_(value_exception, "Cannot compare a pointer to a string node"); \ + case XML_NODE: \ + throw_(value_exception, "Cannot compare a pointer to an XML node"); \ + case POINTER: \ + return (*((void **) data) OP *((void **) val.data)); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a pointer to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a value to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + return *this; \ +} + +DEF_VALUE_CMP_OP(==) +DEF_VALUE_CMP_OP(<) +DEF_VALUE_CMP_OP(<=) +DEF_VALUE_CMP_OP(>) +DEF_VALUE_CMP_OP(>=) + +void value_t::in_place_cast(type_t cast_type) +{ + switch (type) { + case BOOLEAN: + switch (cast_type) { + case BOOLEAN: + break; + case INTEGER: + throw_(value_exception, "Cannot convert a boolean to an integer"); + case DATETIME: + throw_(value_exception, "Cannot convert a boolean to a date/time"); + case AMOUNT: + throw_(value_exception, "Cannot convert a boolean to an amount"); + case BALANCE: + throw_(value_exception, "Cannot convert a boolean to a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a boolean to a balance pair"); + case STRING: + *(string **) data = new string(*((bool *) data) ? "true" : "false"); + break; + case XML_NODE: + throw_(value_exception, "Cannot convert a boolean to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a boolean to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a boolean to a sequence"); + + default: + assert(0); + break; + } + break; + + case INTEGER: + switch (cast_type) { + case BOOLEAN: + *((bool *) data) = *((long *) data); + break; + case INTEGER: + break; + case DATETIME: + throw_(value_exception, "Cannot convert an integer to a date/time"); + + case AMOUNT: + new((amount_t *)data) amount_t(*((long *) data)); + break; + case BALANCE: + new((balance_t *)data) balance_t(*((long *) data)); + break; + case BALANCE_PAIR: + new((balance_pair_t *)data) balance_pair_t(*((long *) data)); + break; + case STRING: { + char buf[32]; + std::sprintf(buf, "%ld", *(long *) data); + *(string **) data = new string(buf); + break; + } + case XML_NODE: + throw_(value_exception, "Cannot convert an integer to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert an integer to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert an integer to a sequence"); + + default: + assert(0); + break; + } + break; + + case DATETIME: + switch (cast_type) { + case BOOLEAN: + *((bool *) data) = is_valid_moment(*((moment_t *) data)); + break; + case INTEGER: + throw_(value_exception, "Cannot convert a date/time to an integer"); + case DATETIME: + break; + case AMOUNT: + throw_(value_exception, "Cannot convert a date/time to an amount"); + case BALANCE: + throw_(value_exception, "Cannot convert a date/time to a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a date/time to a balance pair"); + case STRING: + throw_(value_exception, "Cannot convert a date/time to a string"); + case XML_NODE: + throw_(value_exception, "Cannot convert a date/time to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a date/time to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a date/time to a sequence"); + + default: + assert(0); + break; + } + break; + + case AMOUNT: + switch (cast_type) { + case BOOLEAN: { + bool temp = *((amount_t *) data); + destroy(); + *((bool *)data) = temp; + break; + } + case INTEGER: { + long temp = *((amount_t *) data); + destroy(); + *((long *)data) = temp; + break; + } + case DATETIME: + throw_(value_exception, "Cannot convert an amount to a date/time"); + case AMOUNT: + break; + case BALANCE: { + amount_t temp = *((amount_t *) data); + destroy(); + new((balance_t *)data) balance_t(temp); + break; + } + case BALANCE_PAIR: { + amount_t temp = *((amount_t *) data); + destroy(); + new((balance_pair_t *)data) balance_pair_t(temp); + break; + } + case STRING: { + std::ostringstream out; + out << *(amount_t *) data; + destroy(); + *(string **) data = new string(out.str()); + break; + } + case XML_NODE: + throw_(value_exception, "Cannot convert an amount to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert an amount to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert an amount to a sequence"); + + default: + assert(0); + break; + } + break; + + case BALANCE: + switch (cast_type) { + case BOOLEAN: { + bool temp = *((balance_t *) data); + destroy(); + *((bool *)data) = temp; + break; + } + case INTEGER: + throw_(value_exception, "Cannot convert a balance to an integer"); + case DATETIME: + throw_(value_exception, "Cannot convert a balance to a date/time"); + + case AMOUNT: { + balance_t * temp = (balance_t *) data; + if (temp->amounts.size() == 1) { + amount_t amt = (*temp->amounts.begin()).second; + destroy(); + new((amount_t *)data) amount_t(amt); + } + else if (temp->amounts.size() == 0) { + new((amount_t *)data) amount_t(); + } + else { + throw_(value_exception, "Cannot convert a balance with " + "multiple commodities to an amount"); + } + break; + } + case BALANCE: + break; + case BALANCE_PAIR: { + balance_t temp = *((balance_t *) data); + destroy(); + new((balance_pair_t *)data) balance_pair_t(temp); + break; + } + case STRING: + throw_(value_exception, "Cannot convert a balance to a string"); + case XML_NODE: + throw_(value_exception, "Cannot convert a balance to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a balance to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a balance to a sequence"); + + default: + assert(0); + break; + } + break; + + case BALANCE_PAIR: + switch (cast_type) { + case BOOLEAN: { + bool temp = *((balance_pair_t *) data); + destroy(); + *((bool *)data) = temp; + break; + } + case INTEGER: + throw_(value_exception, "Cannot convert a balance pair to an integer"); + case DATETIME: + throw_(value_exception, "Cannot convert a balance pair to a date/time"); + + case AMOUNT: { + balance_t * temp = &((balance_pair_t *) data)->quantity; + if (temp->amounts.size() == 1) { + amount_t amt = (*temp->amounts.begin()).second; + destroy(); + new((amount_t *)data) amount_t(amt); + } + else if (temp->amounts.size() == 0) { + new((amount_t *)data) amount_t(); + } + else { + throw_(value_exception, "Cannot convert a balance pair with " + "multiple commodities to an amount"); + } + break; + } + case BALANCE: { + balance_t temp = ((balance_pair_t *) data)->quantity; + destroy(); + new((balance_t *)data) balance_t(temp); + break; + } + case BALANCE_PAIR: + break; + case STRING: + throw_(value_exception, "Cannot convert a balance pair to a string"); + case XML_NODE: + throw_(value_exception, "Cannot convert a balance pair to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a balance pair to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a balance pair to a sequence"); + + default: + assert(0); + break; + } + break; + + case STRING: + switch (cast_type) { + case BOOLEAN: { + if (**(string **) data == "true") { + destroy(); + *(bool *) data = true; + } + else if (**(string **) data == "false") { + destroy(); + *(bool *) data = false; + } + else { + throw_(value_exception, "Cannot convert string to an boolean"); + } + break; + } + case INTEGER: { + int l = (*(string **) data)->length(); + const char * p = (*(string **) data)->c_str(); + bool alldigits = true; + for (int i = 0; i < l; i++) + if (! std::isdigit(p[i])) { + alldigits = false; + break; + } + if (alldigits) { + long temp = std::atol((*(string **) data)->c_str()); + destroy(); + *(long *) data = temp; + } else { + throw_(value_exception, "Cannot convert string to an integer"); + } + break; + } + + case DATETIME: + throw_(value_exception, "Cannot convert a string to a date/time"); + + case AMOUNT: { + amount_t temp = **(string **) data; + destroy(); + new((amount_t *)data) amount_t(temp); + break; + } + case BALANCE: + throw_(value_exception, "Cannot convert a string to a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a string to a balance pair"); + case STRING: + break; + case XML_NODE: + throw_(value_exception, "Cannot convert a string to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a string to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a string to a sequence"); + + default: + assert(0); + break; + } + break; + + case XML_NODE: + switch (cast_type) { + case BOOLEAN: + case INTEGER: + case DATETIME: + case AMOUNT: + case BALANCE: + case BALANCE_PAIR: + case STRING: + *this = (*(xml::node_t **) data)->to_value(); + break; + case XML_NODE: + break; + case POINTER: + throw_(value_exception, "Cannot convert an XML node to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert an XML node to a sequence"); + + default: + assert(0); + break; + } + break; + + case POINTER: + switch (cast_type) { + case BOOLEAN: + throw_(value_exception, "Cannot convert a pointer to a boolean"); + case INTEGER: + throw_(value_exception, "Cannot convert a pointer to an integer"); + case DATETIME: + throw_(value_exception, "Cannot convert a pointer to a date/time"); + case AMOUNT: + throw_(value_exception, "Cannot convert a pointer to an amount"); + case BALANCE: + throw_(value_exception, "Cannot convert a pointer to a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a pointer to a balance pair"); + case STRING: + throw_(value_exception, "Cannot convert a pointer to a string"); + case XML_NODE: + throw_(value_exception, "Cannot convert a pointer to an XML node"); + case POINTER: + break; + case SEQUENCE: + throw_(value_exception, "Cannot convert a pointer to a sequence"); + + default: + assert(0); + break; + } + break; + + case SEQUENCE: + switch (cast_type) { + case BOOLEAN: + throw_(value_exception, "Cannot convert a sequence to a boolean"); + case INTEGER: + throw_(value_exception, "Cannot convert a sequence to an integer"); + case DATETIME: + throw_(value_exception, "Cannot convert a sequence to a date/time"); + case AMOUNT: + throw_(value_exception, "Cannot convert a sequence to an amount"); + case BALANCE: + throw_(value_exception, "Cannot convert a sequence to a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a sequence to a balance pair"); + case STRING: + throw_(value_exception, "Cannot convert a sequence to a string"); + case XML_NODE: + throw_(value_exception, "Cannot compare a sequence to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a sequence to a pointer"); + case SEQUENCE: + break; + + default: + assert(0); + break; + } + break; + + default: + assert(0); + break; + } + type = cast_type; +} + +void value_t::in_place_negate() +{ + switch (type) { + case BOOLEAN: + *((bool *) data) = ! *((bool *) data); + break; + case INTEGER: + *((long *) data) = - *((long *) data); + break; + case DATETIME: + throw_(value_exception, "Cannot negate a date/time"); + case AMOUNT: + ((amount_t *) data)->in_place_negate(); + break; + case BALANCE: + ((balance_t *) data)->in_place_negate(); + break; + case BALANCE_PAIR: + ((balance_pair_t *) data)->in_place_negate(); + break; + case STRING: + throw_(value_exception, "Cannot negate a string"); + case XML_NODE: + *this = (*(xml::node_t **) data)->to_value(); + in_place_negate(); + break; + case POINTER: + throw_(value_exception, "Cannot negate a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot negate a sequence"); + + default: + assert(0); + break; + } +} + +void value_t::in_place_abs() +{ + switch (type) { + case BOOLEAN: + break; + case INTEGER: + if (*((long *) data) < 0) + *((long *) data) = - *((long *) data); + break; + case DATETIME: + break; + case AMOUNT: + ((amount_t *) data)->abs(); + break; + case BALANCE: + ((balance_t *) data)->abs(); + break; + case BALANCE_PAIR: + ((balance_pair_t *) data)->abs(); + break; + case STRING: + throw_(value_exception, "Cannot take the absolute value of a string"); + case XML_NODE: + *this = (*(xml::node_t **) data)->to_value(); + in_place_abs(); + break; + case POINTER: + throw_(value_exception, "Cannot take the absolute value of a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot take the absolute value of a sequence"); + + default: + assert(0); + break; + } +} + +value_t value_t::value(const moment_t& moment) const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot find the value of a boolean"); + case DATETIME: + throw_(value_exception, "Cannot find the value of a date/time"); + case INTEGER: + return *this; + case AMOUNT: + return ((amount_t *) data)->value(moment); + case BALANCE: + return ((balance_t *) data)->value(moment); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->quantity.value(moment); + case STRING: + throw_(value_exception, "Cannot find the value of a string"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().value(moment); + case POINTER: + throw_(value_exception, "Cannot find the value of a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot find the value of a sequence"); + default: + assert(0); + return value_t(); + } +} + +void value_t::in_place_reduce() +{ + switch (type) { + case BOOLEAN: + case DATETIME: + case INTEGER: + break; + case AMOUNT: + ((amount_t *) data)->in_place_reduce(); + break; + case BALANCE: + ((balance_t *) data)->in_place_reduce(); + break; + case BALANCE_PAIR: + ((balance_pair_t *) data)->in_place_reduce(); + break; + case STRING: + throw_(value_exception, "Cannot reduce a string"); + case XML_NODE: + *this = (*(xml::node_t **) data)->to_value(); + in_place_reduce(); // recurse + break; + case POINTER: + throw_(value_exception, "Cannot reduce a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot reduce a sequence"); + } +} + +value_t value_t::round() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot round a boolean"); + case DATETIME: + throw_(value_exception, "Cannot round a date/time"); + case INTEGER: + return *this; + case AMOUNT: + return ((amount_t *) data)->round(); + case BALANCE: + return ((balance_t *) data)->round(); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->round(); + case STRING: + throw_(value_exception, "Cannot round a string"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().round(); + case POINTER: + throw_(value_exception, "Cannot round a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot round a sequence"); + } + assert(0); + return value_t(); +} + +value_t value_t::unround() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot un-round a boolean"); + case DATETIME: + throw_(value_exception, "Cannot un-round a date/time"); + case INTEGER: + return *this; + case AMOUNT: + return ((amount_t *) data)->unround(); + case BALANCE: + return ((balance_t *) data)->unround(); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->unround(); + case STRING: + throw_(value_exception, "Cannot un-round a string"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().unround(); + case POINTER: + throw_(value_exception, "Cannot un-round a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot un-round a sequence"); + } + assert(0); + return value_t(); +} + +value_t value_t::price() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot find the price of a boolean"); + case INTEGER: + return *this; + case DATETIME: + throw_(value_exception, "Cannot find the price of a date/time"); + + case AMOUNT: + return ((amount_t *) data)->price(); + case BALANCE: + return ((balance_t *) data)->price(); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->quantity.price(); + + case STRING: + throw_(value_exception, "Cannot find the price of a string"); + + case XML_NODE: + return (*(xml::node_t **) data)->to_value().price(); + + case POINTER: + throw_(value_exception, "Cannot find the price of a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot find the price of a sequence"); + + default: + assert(0); + break; + } + assert(0); + return value_t(); +} + +value_t value_t::date() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot find the date of a boolean"); + case INTEGER: + throw_(value_exception, "Cannot find the date of an integer"); + + case DATETIME: + return *this; + + case AMOUNT: + return ((amount_t *) data)->date(); + case BALANCE: + return ((balance_t *) data)->date(); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->quantity.date(); + + case STRING: + throw_(value_exception, "Cannot find the date of a string"); + + case XML_NODE: + return (*(xml::node_t **) data)->to_value().date(); + + case POINTER: + throw_(value_exception, "Cannot find the date of a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot find the date of a sequence"); + + default: + assert(0); + break; + } + assert(0); + 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 BOOLEAN: + case INTEGER: + case DATETIME: + case STRING: + case XML_NODE: + case POINTER: + return *this; + + case SEQUENCE: + assert(0); // jww (2006-09-28): strip them all! + break; + + case AMOUNT: + return ((amount_t *) data)->strip_annotations + (keep_price, keep_date, keep_tag); + case BALANCE: + return ((balance_t *) data)->strip_annotations + (keep_price, keep_date, keep_tag); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->quantity.strip_annotations + (keep_price, keep_date, keep_tag); + + default: + assert(0); + break; + } + assert(0); + return value_t(); +} + +value_t value_t::cost() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot find the cost of a boolean"); + case INTEGER: + case AMOUNT: + case BALANCE: + return *this; + case DATETIME: + throw_(value_exception, "Cannot find the cost of a date/time"); + + case BALANCE_PAIR: + assert(((balance_pair_t *) data)->cost); + if (((balance_pair_t *) data)->cost) + return *(((balance_pair_t *) data)->cost); + else + return ((balance_pair_t *) data)->quantity; + + case STRING: + throw_(value_exception, "Cannot find the cost of a string"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().cost(); + case POINTER: + throw_(value_exception, "Cannot find the cost of a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot find the cost of a sequence"); + + default: + assert(0); + break; + } + assert(0); + return value_t(); +} + +value_t& value_t::add(const amount_t& amount, const amount_t * tcost) +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot add an amount to a boolean"); + case DATETIME: + throw_(value_exception, "Cannot add an amount to a date/time"); + case INTEGER: + case AMOUNT: + if (tcost) { + in_place_cast(BALANCE_PAIR); + return add(amount, tcost); + } + else if ((type == AMOUNT && + ((amount_t *) data)->commodity() != amount.commodity()) || + (type != AMOUNT && amount.commodity())) { + in_place_cast(BALANCE); + return add(amount, tcost); + } + else if (type != AMOUNT) { + in_place_cast(AMOUNT); + } + *((amount_t *) data) += amount; + break; + + case BALANCE: + if (tcost) { + in_place_cast(BALANCE_PAIR); + return add(amount, tcost); + } + *((balance_t *) data) += amount; + break; + + case BALANCE_PAIR: + ((balance_pair_t *) data)->add(amount, tcost); + break; + + case STRING: + throw_(value_exception, "Cannot add an amount to a string"); + case XML_NODE: + throw_(value_exception, "Cannot add an amount to an XML node"); + case POINTER: + throw_(value_exception, "Cannot add an amount to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot add an amount to a sequence"); + + default: + assert(0); + break; + } + + return *this; +} + +void value_t::write(std::ostream& out, const int first_width, + const int latter_width) const +{ + switch (type) { + case BOOLEAN: + case DATETIME: + case INTEGER: + case AMOUNT: + case STRING: + case POINTER: + out << *this; + break; + + case XML_NODE: + (*(xml::node_t **) data)->write(out); + break; + + case SEQUENCE: + assert(0); // jww (2006-09-28): write them all out! + throw_(value_exception, "Cannot write out a sequence"); + + case BALANCE: + ((balance_t *) data)->write(out, first_width, latter_width); + break; + case BALANCE_PAIR: + ((balance_pair_t *) data)->write(out, first_width, latter_width); + break; + } +} + +std::ostream& operator<<(std::ostream& out, const value_t& val) +{ + switch (val.type) { + case value_t::BOOLEAN: + out << (*((bool *) val.data) ? "true" : "false"); + break; + case value_t::INTEGER: + out << *(long *) val.data; + break; + case value_t::DATETIME: + out << *(moment_t *) val.data; + break; + case value_t::AMOUNT: + out << *(amount_t *) val.data; + break; + case value_t::BALANCE: + out << *(balance_t *) val.data; + break; + case value_t::BALANCE_PAIR: + out << *(balance_pair_t *) val.data; + break; + case value_t::STRING: + out << **(string **) val.data; + break; + case value_t::XML_NODE: + if ((*(xml::node_t **) val.data)->flags & XML_NODE_IS_PARENT) + out << '<' << (*(xml::node_t **) val.data)->name() << '>'; + else + out << (*(xml::node_t **) val.data)->text(); + break; + + case value_t::POINTER: + throw_(value_exception, "Cannot output a pointer value"); + + case value_t::SEQUENCE: { + out << '('; + bool first = true; + for (value_t::sequence_t::iterator + i = (*(value_t::sequence_t **) val.data)->begin(); + i != (*(value_t::sequence_t **) val.data)->end(); + i++) { + if (first) + first = false; + else + out << ", "; + out << *i; + } + out << ')'; + break; + } + + default: + assert(0); + 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() +{ + 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->write(out, 20); + break; + default: + assert(0); + break; + } + out << std::endl; +} +#endif + +} // namespace ledger diff --git a/src/value.h b/src/value.h new file mode 100644 index 00000000..2b0adf2b --- /dev/null +++ b/src/value.h @@ -0,0 +1,580 @@ +#ifndef _VALUE_H +#define _VALUE_H + +#include "amount.h" +#include "balance.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: + typedef std::vector sequence_t; + + char data[sizeof(balance_pair_t)]; + + enum type_t { + BOOLEAN, + INTEGER, + DATETIME, + AMOUNT, + BALANCE, + BALANCE_PAIR, + STRING, + XML_NODE, + POINTER, + SEQUENCE + } type; + + value_t() { + TRACE_CTOR(value_t, ""); + *((long *) data) = 0; + type = INTEGER; + } + + value_t(const value_t& val) : type(INTEGER) { + TRACE_CTOR(value_t, "copy"); + *this = val; + } + value_t(const bool val) { + TRACE_CTOR(value_t, "const bool"); + *((bool *) data) = val; + type = BOOLEAN; + } + value_t(const long val) { + TRACE_CTOR(value_t, "const long"); + *((long *) data) = val; + type = INTEGER; + } + value_t(const moment_t val) { + TRACE_CTOR(value_t, "const moment_t"); + *((moment_t *) data) = val; + type = DATETIME; + } + value_t(const unsigned long val) { + TRACE_CTOR(value_t, "const unsigned long"); + new((amount_t *) data) amount_t(val); + type = AMOUNT; + } + value_t(const double val) { + TRACE_CTOR(value_t, "const double"); + new((amount_t *) data) amount_t(val); + type = AMOUNT; + } + value_t(const string& val, bool literal = false) { + TRACE_CTOR(value_t, "const string&, bool"); + if (literal) { + type = INTEGER; + set_string(val); + } else { + new((amount_t *) data) amount_t(val); + type = AMOUNT; + } + } + value_t(const char * val) { + TRACE_CTOR(value_t, "const char *"); + new((amount_t *) data) amount_t(val); + type = AMOUNT; + } + value_t(const amount_t& val) { + TRACE_CTOR(value_t, "const amount_t&"); + new((amount_t *)data) amount_t(val); + type = AMOUNT; + } + value_t(const balance_t& val) : type(INTEGER) { + TRACE_CTOR(value_t, "const balance_t&"); + *this = val; + } + value_t(const balance_pair_t& val) : type(INTEGER) { + TRACE_CTOR(value_t, "const balance_pair_t&"); + *this = val; + } + value_t(xml::node_t * xml_node) : type(INTEGER) { // gets set in = + TRACE_CTOR(value_t, "xml::node_t *"); + *this = xml_node; + } + value_t(void * item) : type(INTEGER) { // gets set in = + TRACE_CTOR(value_t, "void *"); + *this = item; + } + value_t(sequence_t * seq) : type(INTEGER) { // gets set in = + TRACE_CTOR(value_t, "sequence_t *"); + *this = seq; + } + + ~value_t() { + TRACE_DTOR(value_t); + destroy(); + } + + void destroy(); + void simplify(); + + value_t& operator=(const value_t& val); + value_t& operator=(const bool val) { + if ((bool *) data != &val) { + destroy(); + *((bool *) data) = val; + type = BOOLEAN; + } + return *this; + } + value_t& operator=(const long val) { + if ((long *) data != &val) { + destroy(); + *((long *) data) = val; + type = INTEGER; + } + return *this; + } + value_t& operator=(const moment_t val) { + if ((moment_t *) data != &val) { + destroy(); + *((moment_t *) data) = val; + type = DATETIME; + } + return *this; + } + value_t& operator=(const unsigned long val) { + return *this = amount_t(val); + } + value_t& operator=(const double val) { + return *this = amount_t(val); + } + value_t& operator=(const string& val) { + return *this = amount_t(val); + } + value_t& operator=(const char * val) { + return *this = amount_t(val); + } + value_t& operator=(const amount_t& val) { + if (type == AMOUNT && + (amount_t *) data == &val) + return *this; + + if (val.realzero()) { + return *this = 0L; + } else { + destroy(); + new((amount_t *)data) amount_t(val); + type = AMOUNT; + } + return *this; + } + value_t& operator=(const balance_t& val) { + if (type == BALANCE && + (balance_t *) data == &val) + return *this; + + if (val.realzero()) { + return *this = 0L; + } + else if (val.amounts.size() == 1) { + return *this = (*val.amounts.begin()).second; + } + else { + destroy(); + new((balance_t *)data) balance_t(val); + type = BALANCE; + return *this; + } + } + value_t& operator=(const balance_pair_t& val) { + if (type == BALANCE_PAIR && + (balance_pair_t *) data == &val) + return *this; + + if (val.realzero()) { + return *this = 0L; + } + else if (! val.cost) { + return *this = val.quantity; + } + else { + destroy(); + new((balance_pair_t *)data) balance_pair_t(val); + type = BALANCE_PAIR; + return *this; + } + } + value_t& operator=(xml::node_t * xml_node) { + assert(xml_node); + if (type == XML_NODE && *(xml::node_t **) data == xml_node) + return *this; + + if (! xml_node) { + type = XML_NODE; + return *this = 0L; + } + else { + destroy(); + *(xml::node_t **)data = xml_node; + type = XML_NODE; + return *this; + } + } + value_t& operator=(void * item) { + assert(item); + if (type == POINTER && *(void **) data == item) + return *this; + + if (! item) { + type = POINTER; + return *this = 0L; + } + else { + destroy(); + *(void **)data = item; + type = POINTER; + return *this; + } + } + value_t& operator=(sequence_t * seq) { + assert(seq); + if (type == SEQUENCE && *(sequence_t **) data == seq) + return *this; + + if (! seq) { + type = SEQUENCE; + return *this = 0L; + } + else { + destroy(); + *(sequence_t **)data = seq; + type = SEQUENCE; + return *this; + } + } + + value_t& set_string(const string& str = "") { + if (type != STRING) { + destroy(); + *(string **) data = new string(str); + type = STRING; + } else { + **(string **) data = str; + } + return *this; + } + + bool to_boolean() const; + long to_integer() 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; + xml::node_t * to_xml_node() const; + void * to_pointer() const; + sequence_t * to_sequence() const; + + value_t& operator[](const int index) { + sequence_t * seq = to_sequence(); + assert(seq); + return (*seq)[index]; + } + + void push_back(const value_t& val) { + sequence_t * seq = to_sequence(); + assert(seq); + return seq->push_back(val); + } + + std::size_t size() const { + sequence_t * seq = to_sequence(); + assert(seq); + return seq->size(); + } + + 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); + + template + value_t& operator+=(const T& val) { + return *this += value_t(val); + } + template + value_t& operator-=(const T& val) { + return *this -= value_t(val); + } + template + value_t& operator*=(const T& val) { + return *this *= value_t(val); + } + template + value_t& operator/=(const T& val) { + return *this /= value_t(val); + } + + value_t operator+(const value_t& val) { + value_t temp(*this); + temp += val; + return temp; + } + value_t operator-(const value_t& val) { + value_t temp(*this); + temp -= val; + return temp; + } + value_t operator*(const value_t& val) { + value_t temp(*this); + temp *= val; + return temp; + } + value_t operator/(const value_t& val) { + value_t temp(*this); + temp /= val; + return temp; + } + + template + value_t operator+(const T& val) { + return *this + value_t(val); + } + template + value_t operator-(const T& val) { + return *this - value_t(val); + } + template + value_t operator*(const T& val) { + return *this * value_t(val); + } + template + value_t operator/(const T& val) { + return *this / value_t(val); + } + + bool operator<(const value_t& val); + bool operator<=(const value_t& val); + bool operator>(const value_t& val); + bool operator>=(const value_t& val); + bool operator==(const value_t& val); + bool operator!=(const value_t& val) { + return ! (*this == val); + } + + template + bool operator<(const T& val) { + return *this < value_t(val); + } + template + bool operator<=(const T& val) { + return *this <= value_t(val); + } + template + bool operator>(const T& val) { + return *this > value_t(val); + } + template + bool operator>=(const T& val) { + return *this >= value_t(val); + } + template + bool operator==(const T& val) { + return *this == value_t(val); + } + template + bool operator!=(const T& val) { + return ! (*this == val); + } + + template + operator T() const; + + void in_place_negate(); + value_t negate() const { + value_t temp = *this; + temp.in_place_negate(); + return temp; + } + value_t operator-() const { + return negate(); + } + + bool realzero() const { + switch (type) { + case BOOLEAN: + return ! *((bool *) data); + case INTEGER: + return *((long *) data) == 0; + case DATETIME: + return ! is_valid_moment(*((moment_t *) data)); + case AMOUNT: + return ((amount_t *) data)->realzero(); + case BALANCE: + return ((balance_t *) data)->realzero(); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->realzero(); + case STRING: + return ((string *) data)->empty(); + case XML_NODE: + case POINTER: + case SEQUENCE: + return *(void **) data == NULL; + + default: + assert(0); + break; + } + assert(0); + return 0; + } + + void in_place_abs(); + value_t abs() const; + void in_place_cast(type_t cast_type); + value_t cost() const; + value_t price() const; + value_t date() 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 amount_t * cost = NULL); + value_t value(const moment_t& moment) 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 write(std::ostream& out, const int first_width, + const int latter_width = -1) const; +}; + +#define DEFINE_VALUE_OPERATORS(T, OP) \ +inline value_t operator OP(const T& val, const value_t& obj) { \ + return value_t(val) OP obj; \ +} + +DEFINE_VALUE_OPERATORS(bool, ==) +DEFINE_VALUE_OPERATORS(bool, !=) + +DEFINE_VALUE_OPERATORS(long, +) +DEFINE_VALUE_OPERATORS(long, -) +DEFINE_VALUE_OPERATORS(long, *) +DEFINE_VALUE_OPERATORS(long, /) +DEFINE_VALUE_OPERATORS(long, <) +DEFINE_VALUE_OPERATORS(long, <=) +DEFINE_VALUE_OPERATORS(long, >) +DEFINE_VALUE_OPERATORS(long, >=) +DEFINE_VALUE_OPERATORS(long, ==) +DEFINE_VALUE_OPERATORS(long, !=) + +DEFINE_VALUE_OPERATORS(amount_t, +) +DEFINE_VALUE_OPERATORS(amount_t, -) +DEFINE_VALUE_OPERATORS(amount_t, *) +DEFINE_VALUE_OPERATORS(amount_t, /) +DEFINE_VALUE_OPERATORS(amount_t, <) +DEFINE_VALUE_OPERATORS(amount_t, <=) +DEFINE_VALUE_OPERATORS(amount_t, >) +DEFINE_VALUE_OPERATORS(amount_t, >=) +DEFINE_VALUE_OPERATORS(amount_t, ==) +DEFINE_VALUE_OPERATORS(amount_t, !=) + +DEFINE_VALUE_OPERATORS(balance_t, +) +DEFINE_VALUE_OPERATORS(balance_t, -) +DEFINE_VALUE_OPERATORS(balance_t, *) +DEFINE_VALUE_OPERATORS(balance_t, /) +DEFINE_VALUE_OPERATORS(balance_t, <) +DEFINE_VALUE_OPERATORS(balance_t, <=) +DEFINE_VALUE_OPERATORS(balance_t, >) +DEFINE_VALUE_OPERATORS(balance_t, >=) +DEFINE_VALUE_OPERATORS(balance_t, ==) +DEFINE_VALUE_OPERATORS(balance_t, !=) + +DEFINE_VALUE_OPERATORS(balance_pair_t, +) +DEFINE_VALUE_OPERATORS(balance_pair_t, -) +DEFINE_VALUE_OPERATORS(balance_pair_t, *) +DEFINE_VALUE_OPERATORS(balance_pair_t, /) +DEFINE_VALUE_OPERATORS(balance_pair_t, <) +DEFINE_VALUE_OPERATORS(balance_pair_t, <=) +DEFINE_VALUE_OPERATORS(balance_pair_t, >) +DEFINE_VALUE_OPERATORS(balance_pair_t, >=) +DEFINE_VALUE_OPERATORS(balance_pair_t, ==) +DEFINE_VALUE_OPERATORS(balance_pair_t, !=) + +template +value_t::operator T() const +{ + switch (type) { + case BOOLEAN: + return *(bool *) data; + case INTEGER: + return *(long *) data; + case DATETIME: + return *(moment_t *) data; + case AMOUNT: + return *(amount_t *) data; + case BALANCE: + return *(balance_t *) data; + case STRING: + return **(string **) data; + case XML_NODE: + return *(xml::node_t **) data; + case POINTER: + return *(void **) data; + case SEQUENCE: + return *(sequence_t **) data; + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> value_t::operator bool() const; +template <> value_t::operator long() const; +template <> value_t::operator moment_t() const; +template <> value_t::operator double() const; +template <> value_t::operator string() const; + +std::ostream& operator<<(std::ostream& out, const value_t& val); + +#if 0 +class value_context : public error_context +{ + value_t * bal; + public: + value_context(const value_t& _bal, + const string& desc = "") throw(); + virtual ~value_context() throw(); + + virtual void describe(std::ostream& out) const throw(); +}; +#endif + +DECLARE_EXCEPTION(value_exception); + +} // namespace ledger + +#endif // _VALUE_H diff --git a/src/xml.cc b/src/xml.cc new file mode 100644 index 00000000..448b62b7 --- /dev/null +++ b/src/xml.cc @@ -0,0 +1,550 @@ +#include "xml.h" +#include "journal.h" + +namespace ledger { +namespace xml { + +const std::size_t document_t::ledger_builtins_size = 12; +const char * document_t::ledger_builtins[] = { + "account", + "account-path", + "amount", + "code", + "commodity", + "entries", + "entry", + "journal", + "name", + "note", + "payee", + "transaction" +}; + +document_t::document_t(node_t * _top) + : stub(this), top(_top ? _top : &stub) { + TRACE_CTOR(xml::document_t, "node_t *, const char **, const int"); +} + +document_t::~document_t() +{ + TRACE_DTOR(xml::document_t); + if (top && top != &stub) + delete top; +} + +void document_t::set_top(node_t * _top) +{ + if (top && top != &stub) + delete top; + top = _top; +} + +int document_t::register_name(const string& name) +{ + int index = lookup_name_id(name); + if (index != -1) + return index; + + names.push_back(name); + index = names.size() - 1; + + DEBUG_("xml.lookup", this << " Inserting name: " << names.back()); + + std::pair result = + names_index.insert(names_pair(names.back(), index)); + assert(result.second); + + return index + 1000; +} + +int document_t::lookup_name_id(const string& name) const +{ + int id; + if ((id = lookup_builtin_id(name)) != -1) + return id; + + DEBUG_("xml.lookup", this << " Finding name: " << name); + + names_map::const_iterator i = names_index.find(name); + if (i != names_index.end()) + return (*i).second + 1000; + + return -1; +} + +int document_t::lookup_builtin_id(const string& name) +{ + int first = 0; + int last = (int)ledger_builtins_size; + + while (first <= last) { + int mid = (first + last) / 2; // compute mid point. + + int result; + if ((result = (int)name[0] - (int)ledger_builtins[mid][0]) == 0) + result = std::strcmp(name.c_str(), ledger_builtins[mid]); + + if (result > 0) + first = mid + 1; // repeat search in top half. + else if (result < 0) + last = mid - 1; // repeat search in bottom half. + else + return mid + 10; + } + + return -1; +} + +const char * document_t::lookup_name(int id) const +{ + if (id < 1000) { + switch (id) { + case CURRENT: + return "CURRENT"; + case PARENT: + return "PARENT"; + case ROOT: + return "ROOT"; + case ALL: + return "ALL"; + default: + assert(id >= 10); + return ledger_builtins[id - 10]; + } + } else { + return names[id - 1000].c_str(); + } +} + +void document_t::write(std::ostream& out) const +{ + if (top) { + out << "\n"; + top->write(out); + } +} + +#ifndef THREADSAFE +document_t * node_t::document; +#endif + +node_t::node_t(document_t * _document, parent_node_t * _parent, + unsigned int _flags) + : name_id(0), parent(_parent), next(NULL), prev(NULL), + flags(_flags), attrs(NULL) +{ + TRACE_CTOR(node_t, "document_t *, node_t *"); + document = _document; + if (document && ! document->top) + document->set_top(this); + if (parent) + parent->add_child(this); +} + +void node_t::extract() +{ + if (prev) + prev->next = next; + + if (parent) { + if (parent->_children == this) + parent->_children = next; + + if (parent->_last_child == this) + parent->_last_child = prev; + + parent = NULL; + } + + if (next) + next->prev = prev; + + next = NULL; + prev = NULL; +} + +const char * node_t::name() const +{ + return document->lookup_name(name_id); +} + +int node_t::set_name(const char * _name) +{ + name_id = document->register_name(_name); + return name_id; +} + +node_t * node_t::lookup_child(const char * _name) const +{ + int id = document->lookup_name_id(_name); + return lookup_child(id); +} + +node_t * node_t::lookup_child(const string& _name) const +{ + int id = document->lookup_name_id(_name); + return lookup_child(id); +} + +void parent_node_t::clear() +{ + node_t * child = _children; + while (child) { + node_t * tnext = child->next; + delete child; + child = tnext; + } +} + +void parent_node_t::add_child(node_t * node) +{ + // It is important that this node is not called before children(), + // otherwise, this node will not get auto-populated. + if (_children == NULL) { + assert(_last_child == NULL); + _children = node; + node->prev = NULL; + } else { + assert(_last_child != NULL); + _last_child->next = node; + node->prev = _last_child; + } + + node->parent = this; + + while (node->next) { + node_t * next_node = node->next; + assert(next_node->prev == node); + next_node->parent = this; + node = next_node; + } + + _last_child = node; +} + +void parent_node_t::write(std::ostream& out, int depth) const +{ + for (int i = 0; i < depth; i++) out << " "; + out << '<' << name() << ">\n"; + + for (node_t * child = children(); child; child = child->next) + child->write(out, depth + 1); + + for (int i = 0; i < depth; i++) out << " "; + out << "\n"; +} + +void terminal_node_t::write(std::ostream& out, int depth) const +{ + for (int i = 0; i < depth; i++) out << " "; + + if (data.empty()) { + out << '<' << name() << " />\n"; + } else { + out << '<' << name() << ">" + << text() + << "\n"; + } +} + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +template +inline T * create_node(document_t::parser_t * parser) +{ + T * node = new T(parser->document, parser->node_stack.empty() ? + NULL : parser->node_stack.front()); + + node->set_name(parser->pending); + node->attrs = parser->pending_attrs; + + parser->pending = NULL; + parser->pending_attrs = NULL; + + return node; +} + +static void startElement(void *userData, const char *name, const char **attrs) +{ + document_t::parser_t * parser = static_cast(userData); + + DEBUG_("xml.parse", "startElement(" << name << ")"); + + if (parser->pending) { + parent_node_t * node = create_node(parser); + if (parser->node_stack.empty()) + parser->document->top = node; + parser->node_stack.push_front(node); + } + + parser->pending = name; + + if (attrs) { + for (const char ** p = attrs; *p; p += 2) { + if (! parser->pending_attrs) + parser->pending_attrs = new node_t::attrs_map; + + std::pair result + = parser->pending_attrs->insert(node_t::attrs_pair(*p, *(p + 1))); + assert(result.second); + } + } +} + +static void endElement(void *userData, const char *name) +{ + document_t::parser_t * parser = static_cast(userData); + + DEBUG_("xml.parse", "endElement(" << name << ")"); + + if (parser->pending) { + terminal_node_t * node = create_node(parser); + if (parser->node_stack.empty()) { + parser->document->top = node; + return; + } + } + else if (! parser->handled_data) { + assert(! parser->node_stack.empty()); + parser->node_stack.pop_front(); + } + else { + parser->handled_data = false; + } +} + +static void dataHandler(void *userData, const char *s, int len) +{ + document_t::parser_t * parser = static_cast(userData); + + DEBUG_("xml.parse", "dataHandler(" << string(s, len) << ")"); + + bool all_whitespace = true; + for (int i = 0; i < len; i++) { + if (! std::isspace(s[i])) { + all_whitespace = false; + break; + } + } + + // jww (2006-09-28): I currently do not support text nodes within a + // node that has children. + + if (! all_whitespace) { + terminal_node_t * node = create_node(parser); + + node->set_text(string(s, len)); + parser->handled_data = true; + + if (parser->node_stack.empty()) { + parser->document->top = node; + return; + } + } +} + +bool document_t::parser_t::test(std::istream& in) const +{ + char buf[80]; + + in.getline(buf, 79); + if (std::strncmp(buf, " doc(new document_t); + + document = doc.get(); + + parser = XML_ParserCreate(NULL); + + XML_SetElementHandler(parser, startElement, endElement); + XML_SetCharacterDataHandler(parser, dataHandler); + XML_SetUserData(parser, this); + + char buf[BUFSIZ]; + while (! in.eof()) { + in.getline(buf, BUFSIZ - 1); + std::strcat(buf, "\n"); + bool result; + try { + result = XML_Parse(parser, buf, std::strlen(buf), in.eof()); + } + catch (const std::exception& err) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + XML_ParserFree(parser); + throw_(parse_exception, err.what()); + } + + if (! have_error.empty()) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; +#if 0 + // jww (2007-04-26): What is this doing?? + parse_exception err(have_error); + std::cerr << "Error: " << err.what() << std::endl; +#endif + have_error = ""; + } + + if (! result) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + const char * err = XML_ErrorString(XML_GetErrorCode(parser)); + XML_ParserFree(parser); + throw_(parse_exception, err); + } + } + + XML_ParserFree(parser); + + document = NULL; + return doc.release(); +} + +node_t * commodity_node_t::children() const +{ + // jww (2007-04-19): Need to report the commodity and its details + return NULL; +} + +node_t * amount_node_t::children() const +{ + // jww (2007-04-19): Need to report the quantity and commodity + return NULL; +} + +node_t * transaction_node_t::children() const +{ + return parent_node_t::children(); +} + +node_t * transaction_node_t::lookup_child(int _name_id) const +{ + switch (_name_id) { + case document_t::PAYEE: + payee_virtual_node = new terminal_node_t(document); + payee_virtual_node->set_text(transaction->entry->payee); + return payee_virtual_node; + + case document_t::ACCOUNT: + return new account_node_t(document, transaction->account, + const_cast(this)); + } + return NULL; +} + +value_t transaction_node_t::to_value() const +{ + return transaction->amount; +} + +node_t * entry_node_t::children() const +{ + if (! _children) + for (transactions_list::iterator i = entry->transactions.begin(); + i != entry->transactions.end(); + i++) + new transaction_node_t(document, *i, const_cast(this)); + + return parent_node_t::children(); +} + +node_t * entry_node_t::lookup_child(int _name_id) const +{ + switch (_name_id) { + case document_t::CODE: + // jww (2007-04-20): I have to save this and then delete it later + terminal_node_t * code_node = + new terminal_node_t(document, const_cast(this)); + code_node->set_name(document_t::CODE); + code_node->set_text(entry->code); + return code_node; + + case document_t::PAYEE: + // jww (2007-04-20): I have to save this and then delete it later + terminal_node_t * payee_node = + new terminal_node_t(document, const_cast(this)); + payee_node->set_name(document_t::PAYEE); + payee_node->set_text(entry->payee); + return payee_node; + } + return NULL; +} + +node_t * account_node_t::children() const +{ + if (! _children) { + if (! account->name.empty()) { + terminal_node_t * name_node = + new terminal_node_t(document, const_cast(this)); + name_node->set_name(document_t::NAME); + name_node->set_text(account->name); + } + + if (! account->note.empty()) { + terminal_node_t * note_node = + new terminal_node_t(document, const_cast(this)); + note_node->set_name(document_t::NOTE); + note_node->set_text(account->note); + } + + for (accounts_map::iterator i = account->accounts.begin(); + i != account->accounts.end(); + i++) + new account_node_t(document, (*i).second, const_cast(this)); + } + return parent_node_t::children(); +} + +node_t * journal_node_t::children() const +{ + if (! _children) { +#if 0 + account_node_t * master_account = + new account_node_t(document, journal->master, const_cast(this)); +#endif + + parent_node_t * entries = + new parent_node_t(document, const_cast(this)); + entries->set_name(document_t::ENTRIES); + + for (entries_list::iterator i = journal->entries.begin(); + i != journal->entries.end(); + i++) + new entry_node_t(document, *i, const_cast(this)); + } + return parent_node_t::children(); +} + +#endif // defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +void output_xml_string(std::ostream& out, const string& str) +{ + for (const char * s = str.c_str(); *s; s++) { + switch (*s) { + case '<': + out << "<"; + break; + case '>': + out << "&rt;"; + break; + case '&': + out << "&"; + break; + default: + out << *s; + break; + } + } +} + +} // namespace xml +} // namespace ledger diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 00000000..6a600786 --- /dev/null +++ b/src/xml.h @@ -0,0 +1,423 @@ +#ifndef _XML_H +#define _XML_H + +#include "value.h" +#include "parser.h" + +namespace ledger { + +class transaction_t; +class entry_t; +class account_t; +class journal_t; + +namespace xml { + +#define XML_NODE_IS_PARENT 0x1 + +DECLARE_EXCEPTION(conversion_exception); + +class parent_node_t; +class document_t; + +class node_t +{ +public: + unsigned int name_id; +#ifdef THREADSAFE + document_t * document; +#else + static document_t * document; +#endif + parent_node_t * parent; + node_t * next; + node_t * prev; + unsigned int flags; + + typedef std::map attrs_map; + typedef std::pair attrs_pair; + + attrs_map * attrs; + + node_t(document_t * _document, parent_node_t * _parent = NULL, + unsigned int _flags = 0); + + virtual ~node_t() { + TRACE_DTOR(node_t); + if (parent) extract(); + if (attrs) delete attrs; + } + + void extract(); // extract this node from its parent's child list + + virtual const char * text() const { + assert(0); + return NULL; + } + + const char * name() const; + int set_name(const char * _name); + int set_name(int _name_id) { + name_id = _name_id; + return name_id; + } + + void set_attr(const char * n, const char * v) { + if (! attrs) + attrs = new attrs_map; + std::pair result = + attrs->insert(attrs_pair(n, v)); + assert(result.second); + } + const char * get_attr(const char * n) { + if (attrs) { + attrs_map::iterator i = attrs->find(n); + if (i != attrs->end()) + return (*i).second.c_str(); + } + return NULL; + } + + node_t * lookup_child(const char * _name) const; + node_t * lookup_child(const string& _name) const; + virtual node_t * lookup_child(int /* _name_id */) const { + return NULL; + } + + virtual value_t to_value() const { + throw_(conversion_exception, "Cannot convert node to a value"); + } + + virtual void write(std::ostream& out, int depth = 0) const = 0; + +private: + node_t(const node_t&); + node_t& operator=(const node_t&); +}; + +class parent_node_t : public node_t +{ +public: + mutable node_t * _children; + mutable node_t * _last_child; + + parent_node_t(document_t * _document, parent_node_t * _parent = NULL) + : node_t(_document, _parent, XML_NODE_IS_PARENT), + _children(NULL), _last_child(NULL) + { + TRACE_CTOR(parent_node_t, "document_t *, parent_node_t *"); + } + virtual ~parent_node_t() { + TRACE_DTOR(parent_node_t); + if (_children) clear(); + } + + virtual void clear(); // clear out all child nodes + virtual node_t * children() const { + return _children; + } + virtual node_t * last_child() { + if (! _children) + children(); + return _last_child; + } + virtual void add_child(node_t * node); + + void write(std::ostream& out, int depth = 0) const; + +private: + parent_node_t(const parent_node_t&); + parent_node_t& operator=(const parent_node_t&); +}; + +class terminal_node_t : public node_t +{ + string data; + +public: + terminal_node_t(document_t * _document, parent_node_t * _parent = NULL) + : node_t(_document, _parent) + { + TRACE_CTOR(terminal_node_t, "document_t *, parent_node_t *"); + } + virtual ~terminal_node_t() { + TRACE_DTOR(terminal_node_t); + } + + virtual const char * text() const { + return data.c_str(); + } + virtual void set_text(const char * _data) { + data = _data; + } + virtual void set_text(const string& _data) { + data = _data; + } + + virtual value_t to_value() const { + return text(); + } + + void write(std::ostream& out, int depth = 0) const; + +private: + terminal_node_t(const node_t&); + terminal_node_t& operator=(const node_t&); +}; + +class document_t +{ + static const char * ledger_builtins[]; + static const std::size_t ledger_builtins_size; + +public: + enum ledger_builtins_t { + ACCOUNT = 10, + ACCOUNT_PATH, + AMOUNT, + CODE, + COMMODITY, + ENTRIES, + ENTRY, + JOURNAL, + NAME, + NOTE, + PAYEE, + TRANSACTION + }; + +private: + typedef std::vector names_array; + + names_array names; + + typedef std::map names_map; + typedef std::pair names_pair; + + names_map names_index; + + terminal_node_t stub; + + public: + node_t * top; + + // Ids 0-9 are reserved. 10-999 are for "builtin" names. 1000+ are + // for dynamically registered names. + enum special_names_t { + CURRENT, PARENT, ROOT, ALL + }; + + document_t(node_t * _top = NULL); + ~document_t(); + + void set_top(node_t * _top); + + int register_name(const string& name); + int lookup_name_id(const string& name) const; + static int lookup_builtin_id(const string& name); + const char * lookup_name(int id) const; + + void write(std::ostream& out) const; + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + class parser_t + { + public: + document_t * document; + XML_Parser parser; + string have_error; + const char * pending; + node_t::attrs_map * pending_attrs; + bool handled_data; + + std::list node_stack; + + parser_t() : document(NULL), pending(NULL), pending_attrs(NULL), + handled_data(false) {} + virtual ~parser_t() {} + + virtual bool test(std::istream& in) const; + virtual document_t * parse(std::istream& in); + }; +#endif +}; + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +class xml_parser_t : public parser_t +{ + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); +}; + +DECLARE_EXCEPTION(parse_exception); + +#endif + +class commodity_node_t : public parent_node_t +{ +public: + commodity_t * commodity; + + commodity_node_t(document_t * _document, + commodity_t * _commodity, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), commodity(_commodity) { + TRACE_CTOR(commodity_node_t, "document_t *, commodity_t *, parent_node_t *"); + set_name(document_t::COMMODITY); + } + virtual ~commodity_node_t() { + TRACE_DTOR(commodity_node_t); + } + + virtual node_t * children() const; +}; + +class amount_node_t : public parent_node_t +{ +public: + amount_t * amount; + + amount_node_t(document_t * _document, + amount_t * _amount, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), amount(_amount) { + TRACE_CTOR(amount_node_t, "document_t *, amount_t *, parent_node_t *"); + set_name(document_t::AMOUNT); + } + virtual ~amount_node_t() { + TRACE_DTOR(amount_node_t); + } + + virtual node_t * children() const; + + virtual value_t to_value() const { + return *amount; + } +}; + +class transaction_node_t : public parent_node_t +{ + mutable terminal_node_t * payee_virtual_node; + +public: + transaction_t * transaction; + + transaction_node_t(document_t * _document, + transaction_t * _transaction, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), payee_virtual_node(NULL), + transaction(_transaction) { + TRACE_CTOR(transaction_node_t, "document_t *, transaction_t *, parent_node_t *"); + set_name(document_t::TRANSACTION); + } + virtual ~transaction_node_t() { + TRACE_DTOR(transaction_node_t); + if (payee_virtual_node) + delete payee_virtual_node; + } + + virtual node_t * children() const; + virtual node_t * lookup_child(int _name_id) const; + virtual value_t to_value() const; +}; + +class entry_node_t : public parent_node_t +{ + entry_t * entry; + +public: + entry_node_t(document_t * _document, entry_t * _entry, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), entry(_entry) { + TRACE_CTOR(entry_node_t, "document_t *, entry_t *, parent_node_t *"); + set_name(document_t::ENTRY); + } + virtual ~entry_node_t() { + TRACE_DTOR(entry_node_t); + } + + virtual node_t * children() const; + virtual node_t * lookup_child(int _name_id) const; + + friend class transaction_node_t; +}; + +class account_node_t : public parent_node_t +{ + account_t * account; + +public: + account_node_t(document_t * _document, account_t * _account, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), account(_account) { + TRACE_CTOR(account_node_t, "document_t *, account_t *, parent_node_t *"); + set_name(document_t::ACCOUNT); + } + virtual ~account_node_t() { + TRACE_DTOR(account_node_t); + } + + virtual node_t * children() const; +}; + +class journal_node_t : public parent_node_t +{ + journal_t * journal; + +public: + journal_node_t(document_t * _document, journal_t * _journal, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), journal(_journal) { + TRACE_CTOR(journal_node_t, "document_t *, journal_t *, parent_node_t *"); + set_name(document_t::JOURNAL); + } + virtual ~journal_node_t() { + TRACE_DTOR(journal_node_t); + } + + virtual node_t * children() const; + + friend class transaction_node_t; +}; + +template +inline parent_node_t * wrap_node(document_t * doc, T * item, + void * parent_node = NULL) { + assert(0); + return NULL; +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, transaction_t * xact, + void * parent_node) { + return new transaction_node_t(doc, xact, (parent_node_t *)parent_node); +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, entry_t * entry, + void * parent_node) { + return new entry_node_t(doc, entry, (parent_node_t *)parent_node); +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, account_t * account, + void * parent_node) { + return new account_node_t(doc, account, (parent_node_t *)parent_node); +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, journal_t * journal, + void * parent_node) { + return new journal_node_t(doc, journal, (parent_node_t *)parent_node); +} + +} // namespace xml +} // namespace ledger + +#endif // _XML_H diff --git a/src/xmlparse.cc b/src/xmlparse.cc new file mode 100644 index 00000000..5dfcdbaa --- /dev/null +++ b/src/xmlparse.cc @@ -0,0 +1,467 @@ +#include "xml.h" +#include "journal.h" + +namespace ledger { +namespace xml { + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +static XML_Parser current_parser; +static unsigned int count; + +static journal_t * curr_journal; +static entry_t * curr_entry; +static commodity_t * curr_comm; +static string comm_flags; + +static transaction_t::state_t curr_state; + +static string data; +static bool ignore; +static string have_error; + +static void startElement(void *userData, const char *name, const char **attrs) +{ + if (ignore) + return; + + if (std::strcmp(name, "entry") == 0) { + assert(! curr_entry); + curr_entry = new entry_t; + curr_state = transaction_t::UNCLEARED; + } + else if (std::strcmp(name, "transaction") == 0) { + assert(curr_entry); + curr_entry->add_transaction(new transaction_t); + if (curr_state != transaction_t::UNCLEARED) + curr_entry->transactions.back()->state = curr_state; + } + else if (std::strcmp(name, "commodity") == 0) { + if (string(attrs[0]) == "flags") + comm_flags = attrs[1]; + } + else if (std::strcmp(name, "total") == 0) { + ignore = true; + } +} + +static void endElement(void *userData, const char *name) +{ + if (ignore) { + if (std::strcmp(name, "total") == 0) + ignore = false; + return; + } + + if (std::strcmp(name, "entry") == 0) { + assert(curr_entry); + if (curr_journal->add_entry(curr_entry)) { + count++; + } else { + account_t * acct = curr_journal->find_account(""); + curr_entry->add_transaction(new transaction_t(acct)); + if (curr_journal->add_entry(curr_entry)) { + count++; + } else { + delete curr_entry; + have_error = "Entry cannot be balanced"; + } + } + curr_entry = NULL; + } + else if (std::strcmp(name, "en:date") == 0) { + curr_entry->_date = parse_datetime(data); + } + else if (std::strcmp(name, "en:date_eff") == 0) { + curr_entry->_date_eff = parse_datetime(data); + } + else if (std::strcmp(name, "en:code") == 0) { + curr_entry->code = data; + } + else if (std::strcmp(name, "en:cleared") == 0) { + curr_state = transaction_t::CLEARED; + } + else if (std::strcmp(name, "en:pending") == 0) { + curr_state = transaction_t::PENDING; + } + else if (std::strcmp(name, "en:payee") == 0) { + curr_entry->payee = data; + } + else if (std::strcmp(name, "tr:account") == 0) { + curr_entry->transactions.back()->account = curr_journal->find_account(data); + } + else if (std::strcmp(name, "tr:cleared") == 0) { + curr_entry->transactions.back()->state = transaction_t::CLEARED; + } + else if (std::strcmp(name, "tr:pending") == 0) { + curr_entry->transactions.back()->state = transaction_t::PENDING; + } + else if (std::strcmp(name, "tr:virtual") == 0) { + curr_entry->transactions.back()->flags |= TRANSACTION_VIRTUAL; + } + else if (std::strcmp(name, "tr:generated") == 0) { + curr_entry->transactions.back()->flags |= TRANSACTION_AUTO; + } + else if (std::strcmp(name, "symbol") == 0) { + assert(! curr_comm); + curr_comm = commodity_t::find_or_create(data); + assert(curr_comm); + curr_comm->add_flags(COMMODITY_STYLE_SUFFIXED); + if (! comm_flags.empty()) { + for (string::size_type i = 0, l = comm_flags.length(); i < l; i++) { + switch (comm_flags[i]) { + case 'P': curr_comm->drop_flags(COMMODITY_STYLE_SUFFIXED); break; + case 'S': curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); break; + case 'T': curr_comm->add_flags(COMMODITY_STYLE_THOUSANDS); break; + case 'E': curr_comm->add_flags(COMMODITY_STYLE_EUROPEAN); break; + } + } + } + } +#if 0 + // jww (2006-03-02): !!! + else if (std::strcmp(name, "price") == 0) { + assert(curr_comm); + amount_t * price = new amount_t(data); + std::ostringstream symstr; + symstr << curr_comm->symbol << " {" << *price << "}"; + commodity_t * priced_comm = + commodity_t::find_commodity(symstr.str(), true); + priced_comm->price = price; + priced_comm->base = curr_comm; + curr_comm = priced_comm; + } +#endif + else if (std::strcmp(name, "quantity") == 0) { + curr_entry->transactions.back()->amount.parse(data); + if (curr_comm) { + string::size_type i = data.find('.'); + if (i != string::npos) { + int precision = data.length() - i - 1; + if (precision > curr_comm->precision()) + curr_comm->set_precision(precision); + } + curr_entry->transactions.back()->amount.set_commodity(*curr_comm); + curr_comm = NULL; + } + } + else if (std::strcmp(name, "tr:amount") == 0) { + curr_comm = NULL; + } +} + +static void dataHandler(void *userData, const char *s, int len) +{ + if (! ignore) + data = string(s, len); +} + +bool xml_parser_t::test(std::istream& in) const +{ + char buf[80]; + + in.getline(buf, 79); + if (std::strncmp(buf, "\n"; + + commodity_t& c = amount.commodity(); + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; +#if 0 + // jww (2006-03-02): !!! + if (c.price) { + out << "" << c.base->symbol << "\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "\n"; + xml_write_amount(out, *c.price, depth + 6); + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "\n"; + } else { + out << "" << c.symbol << "\n"; + } +#endif + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "\n"; + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << ""; + out << amount.quantity_string() << "\n"; + + for (int i = 0; i < depth; i++) out << ' '; + out << "\n"; +} + +void xml_write_value(std::ostream& out, const value_t& value, + const int depth = 0) +{ + balance_t * bal = NULL; + + for (int i = 0; i < depth; i++) out << ' '; + out << "\n"; + + switch (value.type) { + case value_t::BOOLEAN: + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "" << *((bool *) value.data) << "\n"; + break; + + case value_t::INTEGER: + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "" << *((long *) value.data) << "\n"; + break; + + case value_t::AMOUNT: + xml_write_amount(out, *((amount_t *) value.data), depth + 2); + break; + + case value_t::BALANCE: + bal = (balance_t *) value.data; + // fall through... + + case value_t::BALANCE_PAIR: + if (! bal) + bal = &((balance_pair_t *) value.data)->quantity; + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "\n"; + + for (amounts_map::const_iterator i = bal->amounts.begin(); + i != bal->amounts.end(); + i++) + xml_write_amount(out, (*i).second, depth + 4); + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "\n"; + break; + + default: + assert(0); + break; + } + + for (int i = 0; i < depth; i++) out << ' '; + out << "\n"; +} + +void output_xml_string(std::ostream& out, const string& str) +{ + for (const char * s = str.c_str(); *s; s++) { + switch (*s) { + case '<': + out << "<"; + break; + case '>': + out << "&rt;"; + break; + case '&': + out << "&"; + break; + default: + out << *s; + break; + } + } +} + +void format_xml_entries::format_last_entry() +{ + output_stream << " \n" + << " " << last_entry->_date.to_string("%Y/%m/%d") + << "\n"; + + if (last_entry->_date_eff) + output_stream << " " + << last_entry->_date_eff.to_string("%Y/%m/%d") + << "\n"; + + if (! last_entry->code.empty()) { + output_stream << " "; + output_xml_string(output_stream, last_entry->code); + output_stream << "\n"; + } + + if (! last_entry->payee.empty()) { + output_stream << " "; + output_xml_string(output_stream, last_entry->payee); + output_stream << "\n"; + } + + bool first = true; + for (transactions_list::const_iterator i = last_entry->transactions.begin(); + i != last_entry->transactions.end(); + i++) { + if (transaction_has_xdata(**i) && + transaction_xdata_(**i).dflags & TRANSACTION_TO_DISPLAY) { + if (first) { + output_stream << " \n"; + first = false; + } + + output_stream << " \n"; + + if ((*i)->_date) + output_stream << " " + << (*i)->_date.to_string("%Y/%m/%d") + << "\n"; + + if ((*i)->_date_eff) + output_stream << " " + << (*i)->_date_eff.to_string("%Y/%m/%d") + << "\n"; + + if ((*i)->state == transaction_t::CLEARED) + output_stream << " \n"; + else if ((*i)->state == transaction_t::PENDING) + output_stream << " \n"; + + if ((*i)->flags & TRANSACTION_VIRTUAL) + output_stream << " \n"; + if ((*i)->flags & TRANSACTION_AUTO) + output_stream << " \n"; + + if ((*i)->account) { + string name = (*i)->account->fullname(); + if (name == "") + name = "[TOTAL]"; + else if (name == "") + name = "[UNKNOWN]"; + + output_stream << " "; + output_xml_string(output_stream, name); + output_stream << "\n"; + } + + output_stream << " \n"; + if (transaction_xdata_(**i).dflags & TRANSACTION_COMPOUND) + xml_write_value(output_stream, + transaction_xdata_(**i).value, 10); + else + xml_write_value(output_stream, value_t((*i)->amount), 10); + output_stream << " \n"; + + if ((*i)->cost) { + output_stream << " \n"; + xml_write_value(output_stream, value_t(*(*i)->cost), 10); + output_stream << " \n"; + } + + if (! (*i)->note.empty()) { + output_stream << " "; + output_xml_string(output_stream, (*i)->note); + output_stream << "\n"; + } + + if (show_totals) { + output_stream << " \n"; + xml_write_value(output_stream, transaction_xdata_(**i).total, 10); + output_stream << " \n"; + } + + output_stream << " \n"; + + transaction_xdata_(**i).dflags |= TRANSACTION_DISPLAYED; + } + } + + if (! first) + output_stream << " \n"; + + output_stream << " \n"; +} +#endif + +} // namespace xml +} // namespace ledger diff --git a/src/xpath.cc b/src/xpath.cc new file mode 100644 index 00000000..6eb30d48 --- /dev/null +++ b/src/xpath.cc @@ -0,0 +1,2414 @@ +#include "xpath.h" +#include "parser.h" + +namespace ledger { +namespace xml { + +#ifndef THREADSAFE +xpath_t::token_t * xpath_t::lookahead = NULL; +#endif + +void xpath_t::initialize() +{ + lookahead = new xpath_t::token_t; +} + +void xpath_t::shutdown() +{ + delete lookahead; + lookahead = NULL; +} + +void xpath_t::token_t::parse_ident(std::istream& in) +{ + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + char c = peek_next_nonws(in); + + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + kind = IDENT; + length = 0; + + char buf[256]; + READ_INTO_(in, buf, 255, c, length, + std::isalnum(c) || c == '_' || c == '.'); + + switch (buf[0]) { + case 'a': + if (std::strcmp(buf, "and") == 0) + kind = KW_AND; + break; + case 'd': + if (std::strcmp(buf, "div") == 0) + kind = KW_DIV; + break; + case 'e': + if (std::strcmp(buf, "eq") == 0) + kind = EQUAL; + break; + case 'f': + if (std::strcmp(buf, "false") == 0) { + kind = VALUE; + value = false; + } + break; + case 'g': + if (std::strcmp(buf, "gt") == 0) + kind = GREATER; + else if (std::strcmp(buf, "ge") == 0) + kind = GREATEREQ; + break; + case 'i': + if (std::strcmp(buf, "is") == 0) + kind = EQUAL; + break; + case 'l': + if (std::strcmp(buf, "lt") == 0) + kind = LESS; + else if (std::strcmp(buf, "le") == 0) + kind = LESSEQ; + break; + case 'm': + if (std::strcmp(buf, "mod") == 0) + kind = KW_MOD; + break; + case 'n': + if (std::strcmp(buf, "ne") == 0) + kind = NEQUAL; + break; + case 'o': + if (std::strcmp(buf, "or") == 0) + kind = KW_OR; + break; + case 't': + if (std::strcmp(buf, "true") == 0) { + kind = VALUE; + value = true; + } + break; + case 'u': + if (std::strcmp(buf, "union") == 0) + kind = KW_UNION; + break; + } + + if (kind == IDENT) + value.set_string(buf); +} + +void xpath_t::token_t::next(std::istream& in, unsigned short flags) +{ + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + char c = peek_next_nonws(in); + + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + symbol[0] = c; + symbol[1] = '\0'; + + length = 1; + + if (! (flags & XPATH_PARSE_RELAXED) && + (std::isalpha(c) || c == '_')) { + parse_ident(in); + return; + } + + switch (c) { + case '@': + in.get(c); + kind = AT_SYM; + break; +#if 0 + case '$': + in.get(c); + kind = DOLLAR; + break; +#endif + + case '(': + in.get(c); + kind = LPAREN; + break; + case ')': + in.get(c); + kind = RPAREN; + break; + + case '[': { + in.get(c); + if (flags & XPATH_PARSE_ALLOW_DATE) { + char buf[256]; + READ_INTO_(in, buf, 255, c, length, c != ']'); + if (c != ']') + unexpected(c, ']'); + in.get(c); + length++; + interval_t timespan(buf); + kind = VALUE; + value = timespan.next(); + } else { + kind = LBRACKET; + } + break; + } + + case ']': { + in.get(c); + kind = RBRACKET; + break; + } + + case '"': { + in.get(c); + char buf[4096]; + READ_INTO_(in, buf, 4095, c, length, c != '"'); + if (c != '"') + unexpected(c, '"'); + in.get(c); + length++; + kind = VALUE; + value.set_string(buf); + break; + } + + case '{': { + in.get(c); + amount_t temp; + temp.parse(in, AMOUNT_PARSE_NO_MIGRATE); + in.get(c); + if (c != '}') + unexpected(c, '}'); + length++; + kind = VALUE; + value = temp; + break; + } + + case '!': + in.get(c); + c = in.peek(); + if (c == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = NEQUAL; + length = 2; + break; + } +#if 0 + else if (c == '~') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = NMATCH; + length = 2; + break; + } +#endif + kind = EXCLAM; + break; + + case '-': + in.get(c); + kind = MINUS; + break; + case '+': + in.get(c); + kind = PLUS; + break; + + case '*': + in.get(c); + if (in.peek() == '*') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = POWER; + length = 2; + break; + } + kind = STAR; + break; + + case '/': + in.get(c); +#if 0 + if (flags & XPATH_PARSE_REGEXP) { + char buf[1024]; + READ_INTO_(in, buf, 1023, c, length, c != '/'); + in.get(c); + if (c != '/') + unexpected(c, '/'); + kind = REGEXP; + value.set_string(buf); + break; + } +#endif + kind = SLASH; + break; + + case '=': + in.get(c); +#if 0 + if (in.peek() == '~') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = MATCH; + length = 2; + break; + } +#endif + kind = EQUAL; + break; + + case '<': + in.get(c); + if (in.peek() == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = LESSEQ; + length = 2; + break; + } + kind = LESS; + break; + + case '>': + in.get(c); + if (in.peek() == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = GREATEREQ; + length = 2; + break; + } + kind = GREATER; + break; + + case '&': + in.get(c); + kind = AMPER; + break; + case '|': + in.get(c); + kind = PIPE; + break; + case '?': + in.get(c); + kind = QUESTION; + break; + case ':': + in.get(c); + if (in.peek() == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = ASSIGN; + length = 2; + break; + } + kind = COLON; + break; + case ',': + in.get(c); + kind = COMMA; + break; +#if 0 + case '%': + in.get(c); + kind = PERCENT; + break; +#endif + + case '.': + in.get(c); + c = in.peek(); + if (c == '.') { + in.get(c); + length++; + kind = DOTDOT; + break; + } + else if (! std::isdigit(c)) { + kind = DOT; + break; + } + in.unget(); // put the first '.' back + // fall through... + + default: + if (! (flags & XPATH_PARSE_RELAXED)) { + kind = UNKNOWN; + } else { + amount_t temp; + unsigned long pos = 0; + + // When in relaxed parsing mode, we want to migrate commodity + // flags so that any precision specified by the user updates the + // current maximum displayed precision. + try { + pos = (long)in.tellg(); + + unsigned char parse_flags = 0; + if (flags & XPATH_PARSE_NO_MIGRATE) + parse_flags |= AMOUNT_PARSE_NO_MIGRATE; + if (flags & XPATH_PARSE_NO_REDUCE) + parse_flags |= AMOUNT_PARSE_NO_REDUCE; + + temp.parse(in, parse_flags); + + kind = VALUE; + value = temp; + } + catch (amount_exception& err) { + // If the amount had no commodity, it must be an unambiguous + // variable reference + + // jww (2007-04-19): There must be a more efficient way to do this! + if (std::strcmp(err.what(), "No quantity specified for amount") == 0) { + in.clear(); + in.seekg(pos, std::ios::beg); + + c = in.peek(); + assert(! (std::isdigit(c) || c == '.')); + parse_ident(in); + } else { + throw; + } + } + } + break; + } +} + +void xpath_t::token_t::rewind(std::istream& in) +{ + for (unsigned int i = 0; i < length; i++) + in.unget(); +} + + +void xpath_t::token_t::unexpected() +{ + switch (kind) { + case TOK_EOF: + throw_(parse_exception, "Unexpected end of expression"); + case IDENT: + throw_(parse_exception, "Unexpected symbol '" << value << "'"); + case VALUE: + throw_(parse_exception, "Unexpected value '" << value << "'"); + default: + throw_(parse_exception, "Unexpected operator '" << symbol << "'"); + } +} + +void xpath_t::token_t::unexpected(char c, char wanted) +{ + if ((unsigned char) c == 0xff) { + if (wanted) + throw_(parse_exception, "Missing '" << wanted << "'"); + else + throw_(parse_exception, "Unexpected end"); + } else { + if (wanted) + throw_(parse_exception, "Invalid char '" << c << + "' (wanted '" << wanted << "')"); + else + throw_(parse_exception, "Invalid char '" << c << "'"); + } +} + +xpath_t::op_t * xpath_t::wrap_value(const value_t& val) +{ + xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::VALUE); + temp->valuep = new value_t(val); + return temp; +} + +xpath_t::op_t * xpath_t::wrap_sequence(value_t::sequence_t * val) +{ + if (val->size() == 0) + return wrap_value(false); + else if (val->size() == 1) + return wrap_value(val->front()); + else + return wrap_value(val); +} + +xpath_t::op_t * xpath_t::wrap_functor(functor_t * fobj) +{ + xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::FUNCTOR); + temp->functor = fobj; + return temp; +} + +#if 0 +xpath_t::op_t * xpath_t::wrap_mask(const string& pattern) +{ + xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::MASK); + temp->mask = new mask_t(pattern); + return temp; +} +#endif + +void xpath_t::scope_t::define(const string& name, op_t * def) +{ + DEBUG_("ledger.xpath.syms", "Defining '" << name << "' = " << def); + + std::pair result + = symbols.insert(symbol_pair(name, def)); + if (! result.second) { + symbol_map::iterator i = symbols.find(name); + assert(i != symbols.end()); + (*i).second->release(); + symbols.erase(i); + + std::pair result2 + = symbols.insert(symbol_pair(name, def)); + if (! result2.second) + throw_(compile_exception, + "Redefinition of '" << name << "' in same scope"); + } + def->acquire(); +} + +xpath_t::op_t * +xpath_t::scope_t::lookup(const string& name) +{ + symbol_map::const_iterator i = symbols.find(name); + if (i != symbols.end()) + return (*i).second; + else if (parent) + return parent->lookup(name); + return NULL; +} + +void xpath_t::scope_t::define(const string& name, functor_t * def) { + define(name, wrap_functor(def)); +} + +bool xpath_t::function_scope_t::resolve(const string& name, + value_t& result, + scope_t * locals) +{ + switch (name[0]) { + case 'l': + if (name == "last") { + if (sequence) + result = (long)sequence->size(); + else + result = 1L; + return true; + } + break; + + case 'p': + if (name == "position") { + result = (long)index + 1; + return true; + } + break; + + case 't': + if (name == "text") { + if (value->type == value_t::XML_NODE) + result.set_string(value->to_xml_node()->text()); + else + throw_(calc_exception, "Attempt to call text() on a non-node value"); + return true; + } + break; + } + return scope_t::resolve(name, result, locals); +} + +xpath_t::op_t::~op_t() +{ + TRACE_DTOR(xpath_t::op_t); + + DEBUG_("ledger.xpath.memory", "Destroying " << this); + assert(refc == 0); + + switch (kind) { + case VALUE: + assert(! left); + assert(valuep); + delete valuep; + break; + + case NODE_NAME: + case FUNC_NAME: + case ATTR_NAME: + case VAR_NAME: + assert(! left); + assert(name); + delete name; + break; + + case ARG_INDEX: + break; + + case FUNCTOR: + assert(! left); + assert(functor); + delete functor; + break; + +#if 0 + case MASK: + assert(! left); + assert(mask); + delete mask; + break; +#endif + + default: + assert(kind < LAST); + if (left) + left->release(); + if (kind > TERMINALS && right) + right->release(); + break; + } +} + +void xpath_t::op_t::get_value(value_t& result) const +{ + switch (kind) { + case VALUE: + result = *valuep; + break; + case ARG_INDEX: + result = (long)arg_index; + break; + default: + throw_(calc_exception, + "Cannot determine value of expression symbol '" << *this << "'"); + } +} + +xpath_t::op_t * +xpath_t::parse_value_term(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node; + + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::VALUE: + node.reset(new op_t(op_t::VALUE)); + node->valuep = new value_t(tok.value); + break; + + case token_t::IDENT: { +#if 0 +#ifdef USE_BOOST_PYTHON + if (tok.value->to_string() == "lambda") // special + try { + char c, buf[4096]; + + std::strcpy(buf, "lambda "); + READ_INTO(in, &buf[7], 4000, c, true); + + op_t * eval = new op_t(op_t::O_EVAL); + op_t * lambda = new op_t(op_t::FUNCTOR); + lambda->functor = new python_functor_t(python_eval(buf)); + eval->set_left(lambda); + op_t * sym = new op_t(op_t::SYMBOL); + sym->name = new string("__ptr"); + eval->set_right(sym); + + node.reset(eval); + + goto done; + } + catch(const boost::python::error_already_set&) { + throw_(parse_exception, "Error parsing lambda expression"); + } +#endif /* USE_BOOST_PYTHON */ +#endif + + string ident = tok.value.to_string(); + int id = -1; + if (std::isdigit(ident[0])) { + node.reset(new op_t(op_t::ARG_INDEX)); + node->arg_index = std::atol(ident.c_str()); + } + else if ((id = document_t::lookup_builtin_id(ident)) != -1) { + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = id; + } + else { + node.reset(new op_t(op_t::NODE_NAME)); + node->name = new string(ident); + } + + // An identifier followed by ( represents a function call + tok = next_token(in, tflags); + if (tok.kind == token_t::LPAREN) { + node->kind = op_t::FUNC_NAME; + + std::auto_ptr call_node; + call_node.reset(new op_t(op_t::O_EVAL)); + call_node->set_left(node.release()); + call_node->set_right(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); + + tok = next_token(in, tflags); + if (tok.kind != token_t::RPAREN) + tok.unexpected(); // jww (2006-09-09): wanted ) + + node.reset(call_node.release()); + } else { + push_token(tok); + } + break; + } + + case token_t::AT_SYM: + tok = next_token(in, tflags); + if (tok.kind != token_t::IDENT) + throw_(parse_exception, "@ symbol must be followed by attribute name"); + + node.reset(new op_t(op_t::ATTR_NAME)); + node->name = new string(tok.value.to_string()); + break; + +#if 0 + case token_t::DOLLAR: + tok = next_token(in, tflags); + if (tok.kind != token_t::IDENT) + throw parse_error("$ symbol must be followed by variable name"); + + node.reset(new op_t(op_t::VAR_NAME)); + node->name = new string(tok.value.to_string()); + break; +#endif + + case token_t::DOT: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::CURRENT; + break; + case token_t::DOTDOT: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::PARENT; + break; + case token_t::SLASH: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::ROOT; + push_token(); + break; + case token_t::STAR: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::ALL; + break; + + case token_t::LPAREN: + node.reset(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); + if (! node.get()) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + tok = next_token(in, tflags); + if (tok.kind != token_t::RPAREN) + tok.unexpected(); // jww (2006-09-09): wanted ) + break; + +#if 0 + case token_t::REGEXP: + node.reset(wrap_mask(tok.value.to_string())); + break; +#endif + + default: + push_token(tok); + break; + } + +#if 0 +#ifdef USE_BOOST_PYTHON + done: +#endif +#endif + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_predicate_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_value_term(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + while (tok.kind == token_t::LBRACKET) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_PRED)); + node->set_left(prev.release()); + node->set_right(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); + if (! node->right) + throw_(parse_exception, "[ operator not followed by valid expression"); + + tok = next_token(in, tflags); + if (tok.kind != token_t::RBRACKET) + tok.unexpected(); // jww (2006-09-09): wanted ] + + tok = next_token(in, tflags); + } + + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_path_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_predicate_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + while (tok.kind == token_t::SLASH) { + std::auto_ptr prev(node.release()); + + tok = next_token(in, tflags); + node.reset(new op_t(tok.kind == token_t::SLASH ? + op_t::O_RFIND : op_t::O_FIND)); + if (tok.kind != token_t::SLASH) + push_token(tok); + + node->set_left(prev.release()); + node->set_right(parse_predicate_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, "/ operator not followed by a valid term"); + + tok = next_token(in, tflags); + } + + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_unary_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node; + + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::EXCLAM: { + std::auto_ptr texpr(parse_path_expr(in, tflags)); + if (! texpr.get()) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + // A very quick optimization + if (texpr->kind == op_t::VALUE) { + *texpr->valuep = ! *texpr->valuep; + node.reset(texpr.release()); + } else { + node.reset(new op_t(op_t::O_NOT)); + node->set_left(texpr.release()); + } + break; + } + + case token_t::MINUS: { + std::auto_ptr texpr(parse_path_expr(in, tflags)); + if (! texpr.get()) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + // A very quick optimization + if (texpr->kind == op_t::VALUE) { + texpr->valuep->in_place_negate(); + node.reset(texpr.release()); + } else { + node.reset(new op_t(op_t::O_NEG)); + node->set_left(texpr.release()); + } + break; + } + +#if 0 + case token_t::PERCENT: { + std::auto_ptr texpr(parse_path_expr(in, tflags)); + if (! texpr.get()) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + // A very quick optimization + if (texpr->kind == op_t::VALUE) { + static value_t perc("100.0%"); + *texpr->valuep = perc * *texpr->valuep; + node.reset(texpr.release()); + } else { + node.reset(new op_t(op_t::O_PERC)); + node->set_left(texpr.release()); + } + break; + } +#endif + + default: + push_token(tok); + node.reset(parse_path_expr(in, tflags)); + break; + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_union_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_unary_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::PIPE || tok.kind == token_t::KW_UNION) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_UNION)); + node->set_left(prev.release()); + node->set_right(parse_union_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_mul_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_union_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::STAR || tok.kind == token_t::KW_DIV) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(tok.kind == token_t::STAR ? + op_t::O_MUL : op_t::O_DIV)); + node->set_left(prev.release()); + node->set_right(parse_mul_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + + tok = next_token(in, tflags); + } + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_add_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_mul_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::PLUS || + tok.kind == token_t::MINUS) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(tok.kind == token_t::PLUS ? + op_t::O_ADD : op_t::O_SUB)); + node->set_left(prev.release()); + node->set_right(parse_add_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + + tok = next_token(in, tflags); + } + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_logic_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_add_expr(in, tflags)); + + if (node.get()) { + op_t::kind_t kind = op_t::LAST; + + unsigned short _flags = tflags; + + token_t& tok = next_token(in, tflags); + switch (tok.kind) { + case token_t::ASSIGN: + kind = op_t::O_DEFINE; + break; + case token_t::EQUAL: + kind = op_t::O_EQ; + break; + case token_t::NEQUAL: + kind = op_t::O_NEQ; + break; +#if 0 + case token_t::MATCH: + kind = op_t::O_MATCH; + _flags |= XPATH_PARSE_REGEXP; + break; + case token_t::NMATCH: + kind = op_t::O_NMATCH; + _flags |= XPATH_PARSE_REGEXP; + break; +#endif + case token_t::LESS: + kind = op_t::O_LT; + break; + case token_t::LESSEQ: + kind = op_t::O_LTE; + break; + case token_t::GREATER: + kind = op_t::O_GT; + break; + case token_t::GREATEREQ: + kind = op_t::O_GTE; + break; + default: + push_token(tok); + break; + } + + if (kind != op_t::LAST) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(kind)); + node->set_left(prev.release()); + if (kind == op_t::O_DEFINE) + node->set_right(parse_querycolon_expr(in, tflags)); + else + node->set_right(parse_add_expr(in, _flags)); + + if (! node->right) { + if (tok.kind == token_t::PLUS) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + else + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + } + } + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_and_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_logic_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::KW_AND) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_AND)); + node->set_left(prev.release()); + node->set_right(parse_and_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_or_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_and_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::KW_OR) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_OR)); + node->set_left(prev.release()); + node->set_right(parse_or_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_querycolon_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_or_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::QUESTION) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_QUES)); + node->set_left(prev.release()); + node->set_right(new op_t(op_t::O_COLON)); + node->right->set_left(parse_querycolon_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + tok = next_token(in, tflags); + if (tok.kind != token_t::COLON) + tok.unexpected(); // jww (2006-09-09): wanted : + node->right->set_right(parse_querycolon_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_value_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_querycolon_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::COMMA) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_COMMA)); + node->set_left(prev.release()); + node->set_right(parse_value_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + tok = next_token(in, tflags); + } + + if (tok.kind != token_t::TOK_EOF) { + if (tflags & XPATH_PARSE_PARTIAL) + push_token(tok); + else + tok.unexpected(); + } + } + else if (! (tflags & XPATH_PARSE_PARTIAL)) { + throw_(parse_exception, "Failed to parse value expression"); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_value_expr(in, tflags)); + + if (use_lookahead) { + use_lookahead = false; +#ifdef THREADSAFE + lookahead.rewind(in); +#else + lookahead->rewind(in); +#endif + } +#ifdef THREADSAFE + lookahead.clear(); +#else + lookahead->clear(); +#endif + + return node.release(); +} + +xpath_t::op_t * +xpath_t::op_t::new_node(kind_t kind, op_t * left, op_t * right) +{ + std::auto_ptr node(new op_t(kind)); + if (left) + node->set_left(left); + if (right) + node->set_right(right); + return node.release(); +} + +xpath_t::op_t * +xpath_t::op_t::copy(op_t * tleft, op_t * tright) const +{ + std::auto_ptr node(new op_t(kind)); + if (tleft) + node->set_left(tleft); + if (tright) + node->set_right(tright); + return node.release(); +} + +void xpath_t::op_t::find_values(value_t * context, scope_t * scope, + value_t::sequence_t& result_seq, + bool recursive) +{ + xpath_t expr(compile(context, scope, true)); + + if (expr->kind == VALUE) + append_value(*expr->valuep, result_seq); + + if (recursive) { + if (context->type == value_t::XML_NODE) { + node_t * ptr = context->to_xml_node(); + if (ptr->flags & XML_NODE_IS_PARENT) { + parent_node_t * parent = static_cast(ptr); + for (node_t * node = parent->children(); + node; + node = node->next) { + value_t temp(node); + find_values(&temp, scope, result_seq, recursive); + } + } + } else { + throw_(calc_exception, "Recursive path selection on a non-node value"); + } + } +} + +bool xpath_t::op_t::test_value(value_t * context, scope_t * scope, + int index) +{ + xpath_t expr(compile(context, scope, true)); + + if (expr->kind != VALUE) + throw_(calc_exception, "Predicate expression does not yield a constant value"); + + switch (expr->valuep->type) { + case value_t::INTEGER: + case value_t::AMOUNT: + return *expr->valuep == (long)index + 1; + + default: + return expr->valuep->to_boolean(); + } +} + +xpath_t::op_t * xpath_t::op_t::defer_sequence(value_t::sequence_t& result_seq) +{ + // If not all of the elements were constants, transform the result + // into an expression sequence using O_COMMA. + + assert(! result_seq.empty()); + + if (result_seq.size() == 1) + return wrap_value(result_seq.front())->acquire(); + + value_t::sequence_t::iterator i = result_seq.begin(); + + std::auto_ptr lit_seq(new op_t(O_COMMA)); + + lit_seq->set_left(wrap_value(*i++)); + op_t ** opp = &lit_seq->right; + + for (; i != result_seq.end(); i++) { + if (*opp) { + op_t * val = *opp; + *opp = new op_t(O_COMMA); + (*opp)->set_left(val); + opp = &(*opp)->right; + } + + if ((*i).type != value_t::POINTER) + *opp = wrap_value(*i)->acquire(); + else + *opp = static_cast((*i).to_pointer()); + } + + return lit_seq.release(); +} + +void xpath_t::op_t::append_value(value_t& val, + value_t::sequence_t& result_seq) +{ + if (val.type == value_t::SEQUENCE) { + value_t::sequence_t * subseq = val.to_sequence(); + for (value_t::sequence_t::iterator i = subseq->begin(); + i != subseq->end(); + i++) + result_seq.push_back(*i); + } else { + result_seq.push_back(val); + } +} + +xpath_t::op_t * xpath_t::op_t::compile(value_t * context, scope_t * scope, + bool resolve) +{ +#if 0 + try { +#endif + switch (kind) { + case VALUE: + return acquire(); + + case NODE_ID: + switch (name_id) { + case document_t::CURRENT: + return wrap_value(context)->acquire(); + + case document_t::PARENT: + if (context->type != value_t::XML_NODE) + throw_(compile_exception, "Referencing parent node from a non-node value"); + else if (context->to_xml_node()->parent) + return wrap_value(context->to_xml_node()->parent)->acquire(); + else + throw_(compile_exception, "Referencing parent node from the root node"); + + case document_t::ROOT: + if (context->type != value_t::XML_NODE) + throw_(compile_exception, "Referencing root node from a non-node value"); + else + return wrap_value(context->to_xml_node()->document->top)->acquire(); + + case document_t::ALL: { + if (context->type != value_t::XML_NODE) + throw_(compile_exception, "Referencing child nodes from a non-node value"); + + node_t * ptr = context->to_xml_node(); + if (! (ptr->flags & XML_NODE_IS_PARENT)) + throw_(compile_exception, "Request for child nodes of a leaf node"); + + parent_node_t * parent = static_cast(ptr); + + value_t::sequence_t * nodes = new value_t::sequence_t; + for (node_t * node = parent->children(); node; node = node->next) + nodes->push_back(node); + + return wrap_value(nodes)->acquire(); + } + + default: + break; // pass down to the NODE_NAME case + } + // fall through... + + case NODE_NAME: + if (context->type == value_t::XML_NODE) { + node_t * ptr = context->to_xml_node(); + if (resolve) { + // First, look up the symbol as a node name within the current + // context. If any exist, then return the set of names. + + std::auto_ptr nodes(new value_t::sequence_t); + + if (ptr->flags & XML_NODE_IS_PARENT) { + parent_node_t * parent = static_cast(ptr); + for (node_t * node = parent->children(); + node; + node = node->next) { + if ((kind == NODE_NAME && + std::strcmp(name->c_str(), node->name()) == 0) || + (kind == NODE_ID && name_id == node->name_id)) + nodes->push_back(node); + } + } + return wrap_value(nodes.release())->acquire(); + } else { + assert(ptr); + int id = ptr->document->lookup_name_id(*name); + if (id != -1) { + op_t * node = new_node(NODE_ID); + node->name_id = id; + return node->acquire(); + } + } + } + return acquire(); + + case ATTR_NAME: { + // jww (2006-09-29): Attrs should map strings to values, not strings + const char * value = context->to_xml_node()->get_attr(name->c_str()); + return wrap_value(value)->acquire(); + } + + case VAR_NAME: + case FUNC_NAME: + if (scope) { + if (resolve) { + value_t temp; + if (scope->resolve(*name, temp)) + return wrap_value(temp)->acquire(); + } + if (op_t * def = scope->lookup(*name)) + return def->compile(context, scope, resolve); + } + return acquire(); + + case ARG_INDEX: + if (scope && scope->kind == scope_t::ARGUMENT) { + assert(scope->args.type == value_t::SEQUENCE); + if (arg_index < scope->args.to_sequence()->size()) + return wrap_value((*scope->args.to_sequence())[arg_index])->acquire(); + else + throw_(compile_exception, "Reference to non-existing argument"); + } else { + return acquire(); + } + + case FUNCTOR: + if (resolve) { + value_t temp; + (*functor)(temp, scope); + return wrap_value(temp)->acquire(); + } else { + return acquire(); + } + break; + +#if 0 + case MASK: + return acquire(); +#endif + + case O_NOT: { + assert(left); + xpath_t expr(left->compile(context, scope, resolve)); + if (! expr->constant()) { + if (left == expr) + return acquire(); + else + return copy(expr)->acquire(); + } + + if (left == expr) { + if (expr->valuep->strip_annotations()) + return wrap_value(false)->acquire(); + else + return wrap_value(true)->acquire(); + } else { + if (expr->valuep->strip_annotations()) + *expr->valuep = false; + else + *expr->valuep = true; + + return expr->acquire(); + } + } + + case O_NEG: { + assert(left); + xpath_t expr(left->compile(context, scope, resolve)); + if (! expr->constant()) { + if (left == expr) + return acquire(); + else + return copy(expr)->acquire(); + } + + if (left == expr) { + return wrap_value(expr->valuep->negate())->acquire(); + } else { + expr->valuep->in_place_negate(); + return expr->acquire(); + } + } + + case O_UNION: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + std::auto_ptr result_seq(new value_t::sequence_t); + + append_value(*lexpr->valuep, *result_seq); + append_value(*rexpr->valuep, *result_seq); + + if (result_seq->size() == 1) + return wrap_value(result_seq->front())->acquire(); + else + return wrap_sequence(result_seq.release())->acquire(); + break; + } + + case O_ADD: + case O_SUB: + case O_MUL: + case O_DIV: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (left == lexpr) { + value_t temp(*lexpr->valuep); + switch (kind) { + case O_ADD: temp += *rexpr->valuep; break; + case O_SUB: temp -= *rexpr->valuep; break; + case O_MUL: temp *= *rexpr->valuep; break; + case O_DIV: temp /= *rexpr->valuep; break; + default: assert(0); break; + } + return wrap_value(temp)->acquire(); + } else { + switch (kind) { + case O_ADD: *lexpr->valuep += *rexpr->valuep; break; + case O_SUB: *lexpr->valuep -= *rexpr->valuep; break; + case O_MUL: *lexpr->valuep *= *rexpr->valuep; break; + case O_DIV: *lexpr->valuep /= *rexpr->valuep; break; + default: assert(0); break; + } + return lexpr->acquire(); + } + } + + case O_NEQ: + case O_EQ: + case O_LT: + case O_LTE: + case O_GT: + case O_GTE: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (left == lexpr) { + switch (kind) { + case O_NEQ: + return wrap_value(*lexpr->valuep != *rexpr->valuep)->acquire(); + break; + case O_EQ: + return wrap_value(*lexpr->valuep == *rexpr->valuep)->acquire(); + break; + case O_LT: + return wrap_value(*lexpr->valuep < *rexpr->valuep)->acquire(); + break; + case O_LTE: + return wrap_value(*lexpr->valuep <= *rexpr->valuep)->acquire(); + break; + case O_GT: + return wrap_value(*lexpr->valuep > *rexpr->valuep)->acquire(); + break; + case O_GTE: + return wrap_value(*lexpr->valuep >= *rexpr->valuep)->acquire(); + break; + default: assert(0); break; + } + } else { + switch (kind) { + case O_NEQ: *lexpr->valuep = *lexpr->valuep != *rexpr->valuep; break; + case O_EQ: *lexpr->valuep = *lexpr->valuep == *rexpr->valuep; break; + case O_LT: *lexpr->valuep = *lexpr->valuep < *rexpr->valuep; break; + case O_LTE: *lexpr->valuep = *lexpr->valuep <= *rexpr->valuep; break; + case O_GT: *lexpr->valuep = *lexpr->valuep > *rexpr->valuep; break; + case O_GTE: *lexpr->valuep = *lexpr->valuep >= *rexpr->valuep; break; + default: assert(0); break; + } + return lexpr->acquire(); + } + } + + case O_AND: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (lexpr->constant() && ! lexpr->valuep->strip_annotations()) { + *lexpr->valuep = false; + return lexpr->acquire(); + } + + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (! rexpr->valuep->strip_annotations()) { + if (left == lexpr) { + return wrap_value(false)->acquire(); + } else { + *lexpr->valuep = false; + return lexpr->acquire(); + } + } else { + return rexpr->acquire(); + } + } + + case O_OR: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (lexpr->constant() && lexpr->valuep->strip_annotations()) + return lexpr->acquire(); + + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (rexpr->valuep->strip_annotations()) { + return rexpr->acquire(); + } else { + if (left == lexpr) { + return wrap_value(false)->acquire(); + } else { + *lexpr->valuep = false; + return lexpr->acquire(); + } + } + } + + case O_QUES: { + assert(left); + assert(right); + assert(right->kind == O_COLON); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (! lexpr->constant()) { + xpath_t rexpr(right->compile(context, scope, resolve)); + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (lexpr->valuep->strip_annotations()) + return right->left->compile(context, scope, resolve); + else + return right->right->compile(context, scope, resolve); + } + + case O_COLON: { + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + case O_COMMA: { + assert(left); + assert(right); + // jww (2006-09-29): This should act just like union + xpath_t lexpr(left->compile(context, scope, resolve)); // for side-effects + return right->compile(context, scope, resolve); + } + +#if 0 + case O_MATCH: + case O_NMATCH: { + assert(left); + assert(right); + xpath_t rexpr(right->compile(context, scope, resolve)); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (! lexpr->constant() || rexpr->kind != MASK) { + if (left == lexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (lexpr->valuep->type != value_t::STRING) + throw_(compile_exception, "Left operand of mask operator is not a string"); + + assert(rexpr->mask); + + bool result = rexpr->mask->match(lexpr->valuep->to_string()); + if (kind == O_NMATCH) + result = ! result; + + if (left == lexpr) { + return wrap_value(result)->acquire(); + } else { + *lexpr->valuep = result; + return lexpr->acquire(); + } + } +#endif + + case O_DEFINE: + assert(left); + assert(right); + if (left->kind == VAR_NAME || left->kind == FUNC_NAME) { + xpath_t rexpr(right->compile(context, scope, resolve)); + if (scope) + scope->define(*left->name, rexpr); + return rexpr->acquire(); + } else { + assert(left->kind == O_EVAL); + assert(left->left->kind == FUNC_NAME); + + std::auto_ptr arg_scope(new scope_t(scope)); + + int index = 0; + op_t * args = left->right; + while (args) { + op_t * arg = args; + if (args->kind == O_COMMA) { + arg = args->left; + args = args->right; + } else { + args = NULL; + } + + // Define the parameter so that on lookup the parser will find + // an ARG_INDEX value. + std::auto_ptr ref(new op_t(ARG_INDEX)); + ref->arg_index = index++; + + assert(arg->kind == NODE_NAME); + arg_scope->define(*arg->name, ref.release()); + } + + // jww (2006-09-16): If I compile the definition of a function, + // I eliminate the possibility of future lookups + //xpath_t rexpr(right->compile(arg_scope.get(), resolve)); + + if (scope) + scope->define(*left->left->name, right); + + return right->acquire(); + } + + case O_EVAL: { + assert(left); + + std::auto_ptr call_args(new scope_t(scope)); + call_args->kind = scope_t::ARGUMENT; + + std::auto_ptr call_seq; + + op_t * args = right; + while (args) { + op_t * arg = args; + if (args->kind == O_COMMA) { + arg = args->left; + args = args->right; + } else { + args = NULL; + } + + if (! call_seq.get()) + call_seq.reset(new value_t::sequence_t); + + // jww (2006-09-15): Need to return a reference to these, if + // there are undetermined arguments! + call_seq->push_back(arg->compile(context, scope, resolve)->value()); + } + + if (call_seq.get()) + call_args->args = call_seq.release(); + + if (left->kind == FUNC_NAME) { + if (resolve) { + value_t temp; + if (scope && scope->resolve(*left->name, temp, call_args.get())) + return wrap_value(temp)->acquire(); + } + + // Don't compile to the left, otherwise the function name may + // get resolved before we have a chance to call it + xpath_t func(left->compile(context, scope, false)); + if (func->kind == FUNCTOR) { + value_t temp; + (*func->functor)(temp, call_args.get()); + return wrap_value(temp)->acquire(); + } + else if (! resolve) { + return func->compile(context, call_args.get(), resolve); + } + else { + throw_(calc_exception, "Unknown function name '" << *left->name << "'"); + } + } + else if (left->kind == FUNCTOR) { + value_t temp; + (*left->functor)(temp, call_args.get()); + return wrap_value(temp)->acquire(); + } + else { + assert(0); + } + break; + } + + case O_FIND: + case O_RFIND: + case O_PRED: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(resolve ? right->acquire() : + right->compile(context, scope, false)); + + if (! lexpr->constant() || ! resolve) { + if (left == lexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + std::auto_ptr result_seq(new value_t::sequence_t); + + // jww (2006-09-24): What about when nothing is found? + switch (lexpr->valuep->type) { + case value_t::XML_NODE: { + function_scope_t xpath_fscope(NULL, lexpr->valuep, 0, scope); + if (kind == O_PRED) { + if (rexpr->test_value(lexpr->valuep, &xpath_fscope)) + result_seq->push_back(*lexpr->valuep); + } else { + rexpr->find_values(lexpr->valuep, &xpath_fscope, *result_seq.get(), + kind == O_RFIND); + } + break; + } + + case value_t::SEQUENCE: { + value_t::sequence_t * seq = lexpr->valuep->to_sequence(); + + int index = 0; + for (value_t::sequence_t::iterator i = seq->begin(); + i != seq->end(); + i++, index++) { + assert((*i).type != value_t::SEQUENCE); + if ((*i).type != value_t::XML_NODE) + throw_(compile_exception, "Attempting to apply path selection " + "to non-node(s)"); + + function_scope_t xpath_fscope(seq, &(*i), index, scope); + if (kind == O_PRED) { + if (rexpr->test_value(&(*i), &xpath_fscope, index)) + result_seq->push_back(*i); + } else { + rexpr->find_values(&(*i), &xpath_fscope, *result_seq.get(), + kind == O_RFIND); + } + } + break; + } + + default: + throw_(compile_exception, "Attempting to apply path selection " + "to non-node(s)"); + } + + if (result_seq->size() == 1) + return wrap_value(result_seq->front())->acquire(); + else + return wrap_sequence(result_seq.release())->acquire(); + } + +#if 0 + case O_PERC: { + assert(left); + xpath_t expr(left->compile(context, scope, resolve)); + if (! expr->constant()) { + if (left == expr) + return acquire(); + else + return copy(expr)->acquire(); + } + + static value_t perc("100.0%"); + *expr->valuep = perc * *expr->valuep; + return expr->acquire(); + } +#endif + + case LAST: + default: + assert(0); + break; + } +#if 0 + } + catch (error * err) { +#if 0 + // jww (2006-09-09): I need a reference to the parent xpath_t + if (err->context.empty() || + ! dynamic_cast(err->context.back())) + err->context.push_back(new context(this)); +#endif + throw err; + } +#endif + + assert(0); + return NULL; +} + +void xpath_t::calc(value_t& result, node_t * node, scope_t * scope) const +{ +#if 0 + try { +#endif + if (node) { + value_t context_node(node); + xpath_t final(ptr->compile(&context_node, scope, true)); + // jww (2006-09-09): Give a better error here if this is not + // actually a value + final->get_value(result); + } else { + std::auto_ptr fake_node(new terminal_node_t(NULL)); + value_t context_node(fake_node.get()); + xpath_t final(ptr->compile(&context_node, scope, true)); + final->get_value(result); + } +#if 0 + } + catch (error * err) { + if (err->context.empty() || + ! dynamic_cast(err->context.back())) + err->context.push_back + (new context(*this, ptr, "While calculating value expression:")); +#if 0 + error_context * last = err->context.back(); + if (context * ctxt = dynamic_cast(last)) { + ctxt->xpath = *this; + ctxt->desc = "While calculating value expression:"; + } +#endif + throw err; + } +#endif +} + +#if 0 +xpath_t::context::context(const xpath_t& _xpath, + const op_t * _err_node, + const string& desc) throw() + : error_context(desc), xpath(_xpath), err_node(_err_node) +{ + _err_node->acquire(); +} + +xpath_t::context::~context() throw() +{ + if (err_node) err_node->release(); +} + +void xpath_t::context::describe(std::ostream& out) const throw() +{ + if (! xpath) { + out << "xpath_t::context expr not set!" << std::endl; + return; + } + + if (! desc.empty()) + out << desc << std::endl; + + out << " "; + unsigned long start = (long)out.tellp() - 1; + unsigned long begin; + unsigned long end; + bool found = false; + if (xpath) + xpath.write(out, true, err_node, &begin, &end); + out << std::endl; + if (found) { + out << " "; + for (unsigned int i = 0; i < end - start; i++) { + if (i >= begin - start) + out << "^"; + else + out << " "; + } + out << std::endl; + } +} +#endif + +bool xpath_t::op_t::write(std::ostream& out, + const bool relaxed, + const op_t * op_to_find, + unsigned long * start_pos, + unsigned long * end_pos) const +{ + int arg_index = 0; + bool found = false; + + if (start_pos && this == op_to_find) { + *start_pos = (long)out.tellp() - 1; + found = true; + } + + string symbol; + + switch (kind) { + case VALUE: + switch (valuep->type) { + case value_t::BOOLEAN: + if (*(valuep)) + out << "1"; + else + out << "0"; + break; + case value_t::INTEGER: + case value_t::AMOUNT: + if (! relaxed) + out << '{'; + out << *(valuep); + if (! relaxed) + out << '}'; + break; + case value_t::BALANCE: + case value_t::BALANCE_PAIR: + assert(0); + break; + case value_t::DATETIME: + out << '[' << *valuep << ']'; + break; + case value_t::STRING: + out << '"' << *valuep << '"'; + break; + + case value_t::XML_NODE: + out << '<' << valuep << '>'; + break; + case value_t::POINTER: + out << '&' << valuep; + break; + case value_t::SEQUENCE: + out << '~' << valuep << '~'; + break; + } + break; + + case NODE_ID: +#ifdef THREADSAFE + out << '%' << name_id; +#else + out << node_t::document->lookup_name(name_id); +#endif + break; + + case NODE_NAME: + case FUNC_NAME: + out << *name; + break; + + case ATTR_NAME: + out << '@' << *name; + break; + + case VAR_NAME: + out << '$' << *name; + break; + + case FUNCTOR: + out << functor->name(); + break; + +#if 0 + case MASK: + out << '/' << mask->pattern << '/'; + break; +#endif + + case ARG_INDEX: + out << '@' << arg_index; + break; + + case O_NOT: + out << "!"; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_NEG: + out << "-"; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + + case O_UNION: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " | "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + + case O_ADD: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " + "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_SUB: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " - "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_MUL: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " * "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_DIV: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " / "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_NEQ: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " != "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_EQ: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " == "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_LT: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " < "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_LTE: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " <= "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_GT: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " > "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_GTE: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " >= "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_AND: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " & "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_OR: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " | "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_QUES: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " ? "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_COLON: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " : "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + + case O_COMMA: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ", "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + +#if 0 + case O_MATCH: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " =~ "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_NMATCH: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " !~ "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; +#endif + + case O_DEFINE: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << '='; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_EVAL: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "("; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_FIND: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "/"; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_RFIND: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "//"; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_PRED: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "["; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "]"; + break; + +#if 0 + case O_PERC: + out << "%"; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; +#endif + + case LAST: + default: + assert(0); + break; + } + + if (! symbol.empty()) { + if (commodity_t::find(symbol)) + out << '@'; + out << symbol; + } + + if (end_pos && this == op_to_find) + *end_pos = (long)out.tellp() - 1; + + return found; +} + +void xpath_t::op_t::dump(std::ostream& out, const int depth) const +{ + out.setf(std::ios::left); + out.width(10); + out << this << " "; + + for (int i = 0; i < depth; i++) + out << " "; + + switch (kind) { + case VALUE: + out << "VALUE - " << *valuep; + break; + + case NODE_NAME: + out << "NODE_NAME - " << *name; + break; + + case NODE_ID: +#ifdef THREADSAFE + out << "NODE_ID - " << name_id; +#else + out << "NODE_ID - " << node_t::document->lookup_name(name_id); +#endif + break; + + case ATTR_NAME: + out << "ATTR_NAME - " << *name; + break; + + case FUNC_NAME: + out << "FUNC_NAME - " << *name; + break; + + case VAR_NAME: + out << "VAR_NAME - " << *name; + break; + + case ARG_INDEX: + out << "ARG_INDEX - " << arg_index; + break; + + case FUNCTOR: + out << "FUNCTOR - " << functor->name(); + break; +#if 0 + case MASK: + out << "MASK - " << mask->pattern; + break; +#endif + + case O_NOT: out << "O_NOT"; break; + case O_NEG: out << "O_NEG"; break; + + case O_UNION: out << "O_UNION"; break; + + case O_ADD: out << "O_ADD"; break; + case O_SUB: out << "O_SUB"; break; + case O_MUL: out << "O_MUL"; break; + case O_DIV: out << "O_DIV"; break; + + case O_NEQ: out << "O_NEQ"; break; + case O_EQ: out << "O_EQ"; break; + case O_LT: out << "O_LT"; break; + case O_LTE: out << "O_LTE"; break; + case O_GT: out << "O_GT"; break; + case O_GTE: out << "O_GTE"; break; + + case O_AND: out << "O_AND"; break; + case O_OR: out << "O_OR"; break; + + case O_QUES: out << "O_QUES"; break; + case O_COLON: out << "O_COLON"; break; + + case O_COMMA: out << "O_COMMA"; break; + +#if 0 + case O_MATCH: out << "O_MATCH"; break; + case O_NMATCH: out << "O_NMATCH"; break; +#endif + + case O_DEFINE: out << "O_DEFINE"; break; + case O_EVAL: out << "O_EVAL"; break; + + case O_FIND: out << "O_FIND"; break; + case O_RFIND: out << "O_RFIND"; break; + case O_PRED: out << "O_PRED"; break; + +#if 0 + case O_PERC: out << "O_PERC"; break; +#endif + + case LAST: + default: + assert(0); + break; + } + + out << " (" << refc << ')' << std::endl; + + if (kind > TERMINALS) { + if (left) { + left->dump(out, depth + 1); + if (right) + right->dump(out, depth + 1); + } else { + assert(! right); + } + } else { + assert(! left); + } +} + +} // namespace xml +} // namespace ledger diff --git a/src/xpath.h b/src/xpath.h new file mode 100644 index 00000000..13966ffc --- /dev/null +++ b/src/xpath.h @@ -0,0 +1,783 @@ +#ifndef _XPATH_H +#define _XPATH_H + +#include "xml.h" + +namespace ledger { +namespace xml { + +class xpath_t +{ +public: + struct op_t; + + static void initialize(); + static void shutdown(); + + DECLARE_EXCEPTION(parse_exception); + DECLARE_EXCEPTION(compile_exception); + DECLARE_EXCEPTION(calc_exception); + +#if 0 + class context : public error_context { + public: + const xpath_t& xpath; + const op_t * err_node; + + context(const xpath_t& _xpath, + const op_t * _err_node, + const string& desc = "") throw(); + virtual ~context() throw(); + + virtual void describe(std::ostream& out) const throw(); + }; +#endif + +public: + class scope_t; + + class functor_t { + protected: + string fname; + public: + bool wants_args; + + functor_t(const string& _fname, bool _wants_args = false) + : fname(_fname), wants_args(_wants_args) {} + virtual ~functor_t() {} + + virtual void operator()(value_t& result, scope_t * locals) = 0; + virtual string name() const { return fname; } + }; + + template + class member_functor_t : public functor_t { + public: + T * ptr; + U T::*dptr; + + member_functor_t(const string& _name, T * _ptr, U T::*_dptr) + : functor_t(_name, false), ptr(_ptr), dptr(_dptr) {} + + virtual void operator()(value_t& result, scope_t * locals) { + assert(ptr); + assert(dptr); + result = ptr->*dptr; + } + }; + + template + class member_functor_t : public functor_t { + public: + T * ptr; + string T::*dptr; + + member_functor_t(const string& _name, T * _ptr, string T::*_dptr) + : functor_t(_name, false), ptr(_ptr), dptr(_dptr) {} + + virtual void operator()(value_t& result, scope_t * locals) { + assert(ptr); + assert(dptr); + result.set_string(ptr->*dptr); + } + }; + + template + class memfun_functor_t : public functor_t { + public: + T * ptr; + void (T::*mptr)(value_t& result); + + memfun_functor_t(const string& _name, T * _ptr, + void (T::*_mptr)(value_t& result)) + : functor_t(_name, false), ptr(_ptr), mptr(_mptr) {} + + virtual void operator()(value_t& result, + scope_t * locals = NULL) { + assert(ptr); + assert(mptr); + assert(locals || locals == NULL); + (ptr->*mptr)(result); + } + }; + + template + class memfun_args_functor_t : public functor_t { + public: + T * ptr; + void (T::*mptr)(value_t& result, scope_t * locals); + + memfun_args_functor_t(const string& _name, T * _ptr, + void (T::*_mptr)(value_t& result, scope_t * locals)) + : functor_t(_name, true), ptr(_ptr), mptr(_mptr) {} + + virtual void operator()(value_t& result, scope_t * locals) { + assert(ptr); + assert(mptr); + (ptr->*mptr)(result, locals); + } + }; + + static op_t * wrap_value(const value_t& val); + static op_t * wrap_sequence(value_t::sequence_t * val); + static op_t * wrap_functor(functor_t * fobj); +#if 0 + static op_t * wrap_mask(const string& pattern); +#endif + + template + static op_t * + make_functor(const string& name, T * ptr, U T::*mptr) { + return wrap_functor(new member_functor_t(name, ptr, mptr)); + } + + template + static op_t * + make_functor(const string& fname, T * ptr, + void (T::*mptr)(value_t& result)) { + return wrap_functor(new memfun_functor_t(fname, ptr, mptr)); + } + + template + static op_t * + make_functor(const string& fname, T * ptr, + void (T::*mptr)(value_t& result, scope_t * locals)) { + return wrap_functor(new memfun_args_functor_t(fname, ptr, mptr)); + } + +#define MAKE_FUNCTOR(cls, name) \ + xml::xpath_t::make_functor(#name, this, &cls::name) + +public: + class scope_t + { + typedef std::map symbol_map; + typedef std::pair symbol_pair; + + symbol_map symbols; + + scope_t(const scope_t&); + scope_t& operator=(const scope_t&); + + public: + scope_t * parent; + value_t args; + + enum kind_t { NORMAL, STATIC, ARGUMENT } kind; + + scope_t(scope_t * _parent = NULL, kind_t _kind = NORMAL) + : parent(_parent), kind(_kind) { + TRACE_CTOR(xpath_t::scope_t, "scope *, kind_t"); + } + + virtual ~scope_t() { + TRACE_DTOR(xpath_t::scope_t); + for (symbol_map::iterator i = symbols.begin(); + i != symbols.end(); + i++) + (*i).second->release(); + } + + public: + virtual void define(const string& name, op_t * def); + virtual bool resolve(const string& name, value_t& result, + scope_t * locals = NULL) { + if (parent) + return parent->resolve(name, result, locals); + return false; + } + virtual op_t * lookup(const string& name); + + void define(const string& name, functor_t * def); + + friend struct op_t; + }; + + class function_scope_t : public scope_t + { + value_t::sequence_t * sequence; + value_t * value; + int index; + + public: + function_scope_t(value_t::sequence_t * _sequence, value_t * _value, + int _index, scope_t * _parent = NULL) + : scope_t(_parent, STATIC), + sequence(_sequence), value(_value), index(_index) {} + + virtual bool resolve(const string& name, value_t& result, + scope_t * locals = NULL); + }; + +#define XPATH_PARSE_NORMAL 0x00 +#define XPATH_PARSE_PARTIAL 0x01 +#define XPATH_PARSE_RELAXED 0x02 +#define XPATH_PARSE_NO_MIGRATE 0x04 +#define XPATH_PARSE_NO_REDUCE 0x08 +#if 0 +#define XPATH_PARSE_REGEXP 0x10 +#endif +#define XPATH_PARSE_ALLOW_DATE 0x20 + +private: + struct token_t + { + enum kind_t { + IDENT, // [A-Za-z_][-A-Za-z0-9_:]* + VALUE, // any kind of literal value +#if 0 + REGEXP, // /regexp/ jww (2006-09-24): deprecate + // in favor of a "match" function +#endif + AT_SYM, // @ + DOLLAR, // $ + DOT, // . + DOTDOT, // .. + LPAREN, // ( + RPAREN, // ) + LBRACKET, // ( + RBRACKET, // ) + EXCLAM, // ! + NEQUAL, // != + MINUS, // - + PLUS, // + + STAR, // * + POWER, // ** + SLASH, // / + EQUAL, // = + ASSIGN, // := + LESS, // < + LESSEQ, // <= + GREATER, // > + GREATEREQ, // >= + AMPER, // & + PIPE, // | + QUESTION, // ? + COLON, // : + COMMA, // , +#if 0 + MATCH, // =~ + NMATCH, // !~ + PERCENT, // % +#endif + KW_AND, + KW_OR, + KW_DIV, + KW_MOD, + KW_UNION, + TOK_EOF, + UNKNOWN + } kind; + + char symbol[3]; + value_t value; + unsigned int length; + + token_t() : kind(UNKNOWN), length(0) { + TRACE_CTOR(xpath_t::token_t, ""); + } + + token_t(const token_t& other) { + assert(0); + TRACE_CTOR(xpath_t::token_t, "copy"); + *this = other; + } + + ~token_t() { + TRACE_DTOR(xpath_t::token_t); + } + + token_t& operator=(const token_t& other) { + if (&other == this) + return *this; + assert(0); + return *this; + } + + void clear() { + kind = UNKNOWN; + length = 0; + value = 0L; + + symbol[0] = '\0'; + symbol[1] = '\0'; + symbol[2] = '\0'; + } + + void parse_ident(std::istream& in); + void next(std::istream& in, unsigned short flags); + void rewind(std::istream& in); + void unexpected(); + + static void unexpected(char c, char wanted = '\0'); + }; + +public: + struct op_t + { + enum kind_t { + VOID, + VALUE, + + NODE_NAME, + NODE_ID, + FUNC_NAME, + ATTR_NAME, + VAR_NAME, + + ARG_INDEX, + + CONSTANTS, // constants end here + + FUNCTOR, +#if 0 + MASK, +#endif + + TERMINALS, // terminals end here + + O_NOT, + O_NEG, + + O_UNION, + + O_ADD, + O_SUB, + O_MUL, + O_DIV, + + O_NEQ, + O_EQ, + O_LT, + O_LTE, + O_GT, + O_GTE, + + O_AND, + O_OR, + + O_QUES, + O_COLON, + + O_COMMA, + +#if 0 + O_MATCH, + O_NMATCH, +#endif + + O_DEFINE, + O_EVAL, + O_ARG, + +#if 0 + O_PERC, +#endif + + O_FIND, + O_RFIND, + O_PRED, + + LAST // operators end here + }; + + kind_t kind; + mutable short refc; + op_t * left; + + union { + value_t * valuep; // used by constant VALUE + string * name; // used by constant SYMBOL + unsigned int arg_index; // used by ARG_INDEX and O_ARG + functor_t * functor; // used by terminal FUNCTOR + unsigned int name_id; // used by NODE_NAME and ATTR_NAME +#if 0 + mask_t * mask; // used by terminal MASK +#endif + op_t * right; // used by all operators + }; + + op_t(const kind_t _kind) + : kind(_kind), refc(0), left(NULL), right(NULL) { + TRACE_CTOR(xpath_t::op_t, "const kind_t"); + } + op_t(const op_t&); + ~op_t(); + + op_t& operator=(const op_t&); + + bool constant() const { + return kind == VALUE; + } + void get_value(value_t& result) const; + value_t value() const { + value_t temp; + get_value(temp); + return temp; + } + + functor_t * functor_obj() const { + if (kind == FUNCTOR) + return functor; + else + return NULL; + } + + void release() const { + DEBUG_("ledger.xpath.memory", + "Releasing " << this << ", refc now " << refc - 1); + assert(refc > 0); + if (--refc == 0) + delete this; + } + op_t * acquire() { + DEBUG_("ledger.xpath.memory", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + return this; + } + const op_t * acquire() const { + DEBUG_("ledger.xpath.memory", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + return this; + } + + void set_left(op_t * expr) { + assert(kind > TERMINALS); + if (left) + left->release(); + left = expr ? expr->acquire() : NULL; + } + + void set_right(op_t * expr) { + assert(kind > TERMINALS); + if (right) + right->release(); + right = expr ? expr->acquire() : NULL; + } + + static op_t * new_node(kind_t kind, op_t * left = NULL, + op_t * right = NULL); + + op_t * copy(op_t * left = NULL, + op_t * right = NULL) const; + op_t * compile(value_t * context, scope_t * scope, + bool resolve = false); + + void find_values(value_t * context, scope_t * scope, + value_t::sequence_t& result_seq, bool recursive); + bool test_value(value_t * context, scope_t * scope, int index = 0); + + void append_value(value_t& value, value_t::sequence_t& result_seq); + + static op_t * defer_sequence(value_t::sequence_t& result_seq); + + bool write(std::ostream& out, + const bool relaxed = true, + const op_t * op_to_find = NULL, + unsigned long * start_pos = NULL, + unsigned long * end_pos = NULL) const; + + void dump(std::ostream& out, const int depth) const; + }; + +public: + op_t * ptr; + + xpath_t& operator=(op_t * _expr) { + expr = ""; + reset(_expr); + return *this; + } + + op_t& operator*() throw() { + return *ptr; + } + const op_t& operator*() const throw() { + return *ptr; + } + op_t * operator->() throw() { + return ptr; + } + const op_t * operator->() const throw() { + return ptr; + } + + op_t * get() throw() { return ptr; } + const op_t * get() const throw() { return ptr; } + + op_t * release() throw() { + op_t * tmp = ptr; + ptr = 0; + return tmp; + } + + void reset(op_t * p = 0) throw() { + if (p != ptr) { + if (ptr) + ptr->release(); + ptr = p; + } + } + +#ifdef THREADSAFE + mutable token_t lookahead; +#else + static token_t * lookahead; +#endif + mutable bool use_lookahead; + + token_t& next_token(std::istream& in, unsigned short tflags) const { + if (use_lookahead) + use_lookahead = false; + else +#ifdef THREADSAFE + lookahead.next(in, tflags); +#else + lookahead->next(in, tflags); +#endif +#ifdef THREADSAFE + return lookahead; +#else + return *lookahead; +#endif + } + void push_token(const token_t& tok) const { +#ifdef THREADSAFE + assert(&tok == &lookahead); +#else + assert(&tok == lookahead); +#endif + use_lookahead = true; + } + void push_token() const { + use_lookahead = true; + } + + op_t * parse_value_term(std::istream& in, unsigned short flags) const; + op_t * parse_predicate_expr(std::istream& in, unsigned short flags) const; + op_t * parse_path_expr(std::istream& in, unsigned short flags) const; + op_t * parse_unary_expr(std::istream& in, unsigned short flags) const; + op_t * parse_union_expr(std::istream& in, unsigned short flags) const; + op_t * parse_mul_expr(std::istream& in, unsigned short flags) const; + op_t * parse_add_expr(std::istream& in, unsigned short flags) const; + op_t * parse_logic_expr(std::istream& in, unsigned short flags) const; + op_t * parse_and_expr(std::istream& in, unsigned short flags) const; + op_t * parse_or_expr(std::istream& in, unsigned short flags) const; + op_t * parse_querycolon_expr(std::istream& in, unsigned short flags) const; + op_t * parse_value_expr(std::istream& in, unsigned short flags) const; + + op_t * parse_expr(std::istream& in, + unsigned short flags = XPATH_PARSE_RELAXED) const; + + op_t * parse_expr(const string& str, + unsigned short tflags = XPATH_PARSE_RELAXED) const + { + std::istringstream stream(str); +#if 0 + try { +#endif + return parse_expr(stream, tflags); +#if 0 + } + catch (error * err) { + err->context.push_back + (new line_context(str, (long)stream.tellg() - 1, + "While parsing value expression:")); + throw err; + } +#endif + } + + op_t * parse_expr(const char * p, + unsigned short tflags = XPATH_PARSE_RELAXED) const { + return parse_expr(string(p), tflags); + } + + bool write(std::ostream& out, + const bool relaxed, + const op_t * op_to_find, + unsigned long * start_pos, + unsigned long * end_pos) const { + if (ptr) + ptr->write(out, relaxed, op_to_find, start_pos, end_pos); + return true; + } + +public: + string expr; + unsigned short flags; // flags used to parse `expr' + + xpath_t() : ptr(NULL), use_lookahead(false), flags(0) { + TRACE_CTOR(xpath_t, ""); + } + xpath_t(op_t * _ptr) : ptr(_ptr), use_lookahead(false) { + TRACE_CTOR(xpath_t, "op_t *"); + } + + xpath_t(const string& _expr, + unsigned short _flags = XPATH_PARSE_RELAXED) + : ptr(NULL), use_lookahead(false), flags(0) { + TRACE_CTOR(xpath_t, "const string&, unsigned short"); + if (! _expr.empty()) + parse(_expr, _flags); + } + xpath_t(std::istream& in, unsigned short _flags = XPATH_PARSE_RELAXED) + : ptr(NULL), use_lookahead(false), flags(0) { + TRACE_CTOR(xpath_t, "std::istream&, unsigned short"); + parse(in, _flags); + } + xpath_t(const xpath_t& other) + : ptr(other.ptr ? other.ptr->acquire() : NULL), + use_lookahead(false), expr(other.expr), flags(other.flags) { + TRACE_CTOR(xpath_t, "copy"); + } + virtual ~xpath_t() { + TRACE_DTOR(xpath_t); + reset(NULL); + } + + xpath_t& operator=(const string& _expr) { + parse(_expr); + return *this; + } + xpath_t& operator=(const xpath_t& _expr); + xpath_t& operator=(xpath_t& _xpath) { + ptr = _xpath.ptr->acquire(); + expr = _xpath.expr; + flags = _xpath.flags; + use_lookahead = false; + return *this; + } + + operator op_t *() throw() { + return ptr; + } + + operator bool() const throw() { + return ptr != NULL; + } + operator string() const throw() { + return expr; + } + + void parse(const string& _expr, unsigned short _flags = XPATH_PARSE_RELAXED) { + expr = _expr; + flags = _flags; + op_t * tmp = parse_expr(_expr, _flags); + assert(tmp); + reset(tmp ? tmp->acquire() : NULL); + } + void parse(std::istream& in, unsigned short _flags = XPATH_PARSE_RELAXED) { + expr = ""; + flags = _flags; + op_t * tmp = parse_expr(in, _flags); + assert(tmp); + reset(tmp ? tmp->acquire() : NULL); + } + + void compile(const string& _expr, scope_t * scope = NULL, + unsigned short _flags = XPATH_PARSE_RELAXED) { + parse(_expr, _flags); + // jww (2006-09-24): fix + compile((node_t *)NULL, scope); + } + void compile(std::istream& in, scope_t * scope = NULL, + unsigned short _flags = XPATH_PARSE_RELAXED) { + parse(in, _flags); + // jww (2006-09-24): fix + compile((node_t *)NULL, scope); + } + + void compile(document_t * document, scope_t * scope = NULL) { + if (! document) { + document_t tdoc; + compile(tdoc.top, scope); + } else { + compile(document->top, scope); + } + } + void compile(node_t * top_node, scope_t * scope = NULL) { + if (ptr) { + value_t noderef(top_node); + op_t * compiled = ptr->compile(&noderef, scope); + if (compiled == ptr) + compiled->release(); + else + reset(compiled); + } + } + + virtual void calc(value_t& result, node_t * node, scope_t * scope = NULL) const; + + virtual value_t calc(document_t * document, scope_t * scope = NULL) const { + if (! ptr) + return 0L; + value_t temp; + calc(temp, document ? document->top : NULL, scope); + return temp; + } + virtual value_t calc(node_t * tcontext, scope_t * scope = NULL) const { + if (! ptr) + return 0L; + value_t temp; + calc(temp, tcontext, scope); + return temp; + } + + static void eval(value_t& result, const string& _expr, + document_t * document, scope_t * scope = NULL) { + xpath_t temp(_expr); + temp.calc(result, document->top, scope); + } + static value_t eval(const string& _expr, document_t * document, + scope_t * scope = NULL) { + xpath_t temp(_expr); + return temp.calc(document, scope); + } + + void write(std::ostream& out) const { + write(out, true, NULL, NULL, NULL); + } + void dump(std::ostream& out) const { + if (ptr) + ptr->dump(out, 0); + } + + friend class scope_t; +}; + +inline std::ostream& operator<<(std::ostream& out, const xpath_t::op_t& op) { + op.write(out); + return out; +}; + +} // namespace xml + +template +inline T * get_ptr(xml::xpath_t::scope_t * locals, unsigned int idx) { + assert(locals->args.size() > idx); + T * ptr = static_cast(locals->args[idx].to_pointer()); + assert(ptr); + return ptr; +} + +class xml_command : public xml::xpath_t::functor_t +{ + public: + xml_command() : xml::xpath_t::functor_t("xml") {} + + virtual void operator()(value_t&, xml::xpath_t::scope_t * locals) { + std::ostream * out = get_ptr(locals, 0); + xml::document_t * doc = get_ptr(locals, 1); + + doc->write(*out); + } +}; + +} // namespace ledger + +#endif // _XPATH_H -- cgit v1.2.3