diff options
author | John Wiegley <johnw@newartisans.com> | 2008-08-05 13:17:04 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2008-08-05 18:05:49 -0400 |
commit | f6f4a46cf5b14f9a2170cd6475958efbf320caec (patch) | |
tree | 05bc1defcdebc201de3dd10477483d906a842821 /src | |
parent | b7970b29855563e4c67f85af8b31233eda80c22a (diff) | |
download | fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.tar.gz fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.tar.bz2 fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.zip |
Moved around most of the files so that source code is in src/, documentation
is in doc/, etc.
Diffstat (limited to 'src')
-rw-r--r-- | src/account.cc | 198 | ||||
-rw-r--r-- | src/account.h | 155 | ||||
-rw-r--r-- | src/amount.cc | 1433 | ||||
-rw-r--r-- | src/amount.h | 748 | ||||
-rw-r--r-- | src/balance.cc | 255 | ||||
-rw-r--r-- | src/balance.h | 526 | ||||
-rw-r--r-- | src/balpair.h | 357 | ||||
-rw-r--r-- | src/binary.cc | 188 | ||||
-rw-r--r-- | src/binary.h | 270 | ||||
-rw-r--r-- | src/cache.cc | 871 | ||||
-rw-r--r-- | src/cache.h | 142 | ||||
-rw-r--r-- | src/commodity.cc | 667 | ||||
-rw-r--r-- | src/commodity.h | 420 | ||||
-rw-r--r-- | src/compare.cc | 85 | ||||
-rw-r--r-- | src/compare.h | 77 | ||||
-rw-r--r-- | src/csv.cc | 148 | ||||
-rw-r--r-- | src/csv.h | 63 | ||||
-rw-r--r-- | src/derive.cc | 228 | ||||
-rw-r--r-- | src/derive.h | 45 | ||||
-rw-r--r-- | src/emacs.cc | 110 | ||||
-rw-r--r-- | src/emacs.h | 68 | ||||
-rw-r--r-- | src/entry.cc | 482 | ||||
-rw-r--r-- | src/entry.h | 239 | ||||
-rw-r--r-- | src/error.h | 85 | ||||
-rw-r--r-- | src/expr.cc | 203 | ||||
-rw-r--r-- | src/expr.h | 125 | ||||
-rw-r--r-- | src/fdstream.h | 215 | ||||
-rw-r--r-- | src/filters.cc | 755 | ||||
-rw-r--r-- | src/filters.h | 702 | ||||
-rw-r--r-- | src/flags.h | 117 | ||||
-rw-r--r-- | src/format.cc | 391 | ||||
-rw-r--r-- | src/format.h | 144 | ||||
-rw-r--r-- | src/gnucash.cc | 432 | ||||
-rw-r--r-- | src/gnucash.h | 53 | ||||
-rw-r--r-- | src/handler.h | 72 | ||||
-rw-r--r-- | src/help.cc | 205 | ||||
-rw-r--r-- | src/help.h | 49 | ||||
-rw-r--r-- | src/hooks.h | 71 | ||||
-rw-r--r-- | src/iterators.cc | 200 | ||||
-rw-r--r-- | src/iterators.h | 226 | ||||
-rw-r--r-- | src/journal.cc | 150 | ||||
-rw-r--r-- | src/journal.h | 128 | ||||
-rw-r--r-- | src/ledger.h | 81 | ||||
-rw-r--r-- | src/main.cc | 593 | ||||
-rw-r--r-- | src/mask.cc | 59 | ||||
-rw-r--r-- | src/mask.h | 67 | ||||
-rw-r--r-- | src/ofx.cc | 257 | ||||
-rw-r--r-- | src/ofx.h | 53 | ||||
-rw-r--r-- | src/op.cc | 1130 | ||||
-rw-r--r-- | src/op.h | 330 | ||||
-rw-r--r-- | src/option.cc | 212 | ||||
-rw-r--r-- | src/option.h | 52 | ||||
-rw-r--r-- | src/output.cc | 310 | ||||
-rw-r--r-- | src/output.h | 140 | ||||
-rw-r--r-- | src/parser.cc | 441 | ||||
-rw-r--r-- | src/parser.h | 103 | ||||
-rw-r--r-- | src/predicate.h | 69 | ||||
-rw-r--r-- | src/pushvar.h | 80 | ||||
-rw-r--r-- | src/qif.cc | 276 | ||||
-rw-r--r-- | src/qif.h | 53 | ||||
-rw-r--r-- | src/quotes.cc | 109 | ||||
-rw-r--r-- | src/quotes.h | 70 | ||||
-rw-r--r-- | src/reconcile.cc | 116 | ||||
-rw-r--r-- | src/reconcile.h | 72 | ||||
-rw-r--r-- | src/report.cc | 432 | ||||
-rw-r--r-- | src/report.h | 757 | ||||
-rw-r--r-- | src/scope.cc | 123 | ||||
-rw-r--r-- | src/scope.h | 280 | ||||
-rw-r--r-- | src/session.cc | 391 | ||||
-rw-r--r-- | src/session.h | 255 | ||||
-rw-r--r-- | src/system.hh | 167 | ||||
-rw-r--r-- | src/textual.cc | 1131 | ||||
-rw-r--r-- | src/textual.h | 81 | ||||
-rw-r--r-- | src/times.cc | 350 | ||||
-rw-r--r-- | src/times.h | 154 | ||||
-rw-r--r-- | src/token.cc | 378 | ||||
-rw-r--r-- | src/token.h | 116 | ||||
-rw-r--r-- | src/utils.cc | 716 | ||||
-rw-r--r-- | src/utils.h | 578 | ||||
-rw-r--r-- | src/value.cc | 1773 | ||||
-rw-r--r-- | src/value.h | 922 | ||||
-rw-r--r-- | src/xact.cc | 278 | ||||
-rw-r--r-- | src/xact.h | 228 | ||||
-rw-r--r-- | src/xml.cc | 499 | ||||
-rw-r--r-- | src/xml.h | 85 |
85 files changed, 26465 insertions, 0 deletions
diff --git a/src/account.cc b/src/account.cc new file mode 100644 index 00000000..d3b19ad0 --- /dev/null +++ b/src/account.cc @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "account.h" + +namespace ledger { + +account_t::~account_t() +{ + TRACE_DTOR(account_t); + + foreach (accounts_map::value_type& pair, accounts) + checked_delete(pair.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); + std::pair<accounts_map::iterator, bool> result + = accounts.insert(accounts_map::value_type(first, account)); + assert(result.second); + } else { + account = (*i).second; + } + + if (rest) + account = account->find_account(rest, auto_create); + + return account; +} + +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; +} + +namespace { + value_t get_total(account_t& account) { + assert(account.xdata_); + return account.xdata_->total; + } + + template <value_t (*Func)(account_t&)> + value_t get_wrapper(call_scope_t& scope) { + return (*Func)(find_scope<account_t>(scope)); + } +} + +expr_t::ptr_op_t account_t::lookup(const string& name) +{ + switch (name[0]) { + case 'f': + if (name.find("fmt_") == 0) { + switch (name[4]) { + case 'T': + return WRAP_FUNCTOR(get_wrapper<&get_total>); + } + } + break; + + case 't': + if (name == "total") + return WRAP_FUNCTOR(get_wrapper<&get_total>); + break; + } + return expr_t::ptr_op_t(); +} + +bool account_t::valid() const +{ + if (depth > 256) { + DEBUG("ledger.validate", "account_t: depth > 256"); + return false; + } + + foreach (const accounts_map::value_type& pair, accounts) { + if (this == pair.second) { + DEBUG("ledger.validate", "account_t: parent refers to itself!"); + return false; + } + + if (! pair.second->valid()) { + DEBUG("ledger.validate", "account_t: child not valid"); + return false; + } + } + + return true; +} + +void account_t::calculate_sums() +{ + xdata_t& xd(xdata()); + + foreach (accounts_map::value_type& pair, accounts) { + (*pair.second).calculate_sums(); + + if (xd.total.is_null()) + xd.total = (*pair.second).xdata().total; + else + xd.total += (*pair.second).xdata().total; + + xd.total_count += ((*pair.second).xdata().total_count + + (*pair.second).xdata().count); + } + + value_t result; +#if 0 + compute_amount(result, details_t(account)); +#endif + + if (xd.total.is_null()) + xd.total = result; + else + xd.total += result; + + xd.total_count += xd.count; +} + +} // namespace ledger diff --git a/src/account.h b/src/account.h new file mode 100644 index 00000000..7d35593c --- /dev/null +++ b/src/account.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _ACCOUNT_H +#define _ACCOUNT_H + +#include "utils.h" +#include "scope.h" + +namespace ledger { + +class account_t; + +typedef std::map<const string, account_t *> accounts_map; + +class account_t : public scope_t +{ + public: + typedef unsigned long ident_t; + + account_t * parent; + string name; + optional<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 optional<string>& _note = none) + : scope_t(), 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(const account_t& other) + : scope_t(), + parent(other.parent), + name(other.name), + note(other.note), + depth(other.depth), + accounts(other.accounts), + data(NULL), + ident(0) { + TRACE_CTOR(account_t, "copy"); + assert(other.data == NULL); + assert(other.ident == 0); + } + ~account_t(); + + operator string() const { + return fullname(); + } + string fullname() const; + + void add_account(account_t * acct) { + accounts.insert(accounts_map::value_type(acct->name, acct)); + } + bool remove_account(account_t * acct) { + accounts_map::size_type n = accounts.erase(acct->name); + return n > 0; + } + + account_t * find_account(const string& name, bool auto_create = true); + + virtual expr_t::ptr_op_t lookup(const string& name); + + bool valid() const; + + friend class journal_t; + + struct xdata_t : public supports_flags<> + { +#define ACCOUNT_EXT_TO_DISPLAY 0x01 +#define ACCOUNT_EXT_DISPLAYED 0x02 +#define ACCOUNT_EXT_SORT_CALC 0x04 +#define ACCOUNT_EXT_HAS_NON_VIRTUALS 0x08 +#define ACCOUNT_EXT_HAS_UNB_VIRTUALS 0x10 + + value_t value; + value_t total; + value_t sort_value; + unsigned int count; // xacts counted toward amount + unsigned int total_count; // xacts counted toward total + unsigned int virtuals; + unsigned short dflags; + + xdata_t() + : supports_flags<>(), count(0), total_count(0), + virtuals(0), dflags(0) + { + TRACE_CTOR(xdata_t, ""); + } + + ~xdata_t() throw() { + TRACE_DTOR(xdata_t); + } + }; + + // This variable holds optional "extended data" which is usually produced + // only during reporting, and only for the transaction set being reported. + // It's a memory-saving measure to delay allocation until the last possible + // moment. + mutable optional<xdata_t> xdata_; + + bool has_xdata() const { + return xdata_; + } + void clear_xdata() { + xdata_ = none; + } + xdata_t& xdata() { + if (! xdata_) + xdata_ = xdata_t(); + return *xdata_; + } + + void calculate_sums(); +}; + +std::ostream& operator<<(std::ostream& out, const account_t& account); + +} // namespace ledger + +#endif // _ACCOUNT_H diff --git a/src/amount.cc b/src/amount.cc new file mode 100644 index 00000000..7f849bff --- /dev/null +++ b/src/amount.cc @@ -0,0 +1,1433 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file 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 also defines a + * helper class, bigint_t, which is used as a refcounted wrapper + * around libgmp's mpz_t type. + */ + +#include "amount.h" +#include "binary.h" + +namespace ledger { + +commodity_pool_t * amount_t::current_pool = NULL; + +bool amount_t::keep_base = false; + +bool amount_t::keep_price = false; +bool amount_t::keep_date = false; +bool amount_t::keep_tag = false; + +bool amount_t::stream_fullstrings = false; + +#ifndef THREADSAFE +/** + * These global temporaries are pre-initialized for the sake of + * efficiency, and reused over and over again. + */ +static mpz_t temp; +static mpz_t divisor; +#endif + +struct amount_t::bigint_t : public supports_flags<> +{ +#define BIGINT_BULK_ALLOC 0x01 +#define BIGINT_KEEP_PREC 0x02 + + mpz_t val; + precision_t prec; + uint_least16_t ref; + uint_fast32_t index; + +#define MPZ(bigint) ((bigint)->val) + + bigint_t() : prec(0), ref(1), index(0) { + TRACE_CTOR(bigint_t, ""); + mpz_init(val); + } + bigint_t(mpz_t _val) : prec(0), ref(1), index(0) { + TRACE_CTOR(bigint_t, "mpz_t"); + mpz_init_set(val, _val); + } + bigint_t(const bigint_t& other) + : supports_flags<>(other.flags() & ~BIGINT_BULK_ALLOC), + prec(other.prec), ref(1), index(0) { + TRACE_CTOR(bigint_t, "copy"); + mpz_init_set(val, other.val); + } + ~bigint_t() { + TRACE_DTOR(bigint_t); + assert(ref == 0); + mpz_clear(val); + } + + bool valid() const { + if (prec > 128) { + DEBUG("ledger.validate", "amount_t::bigint_t: prec > 128"); + return false; + } + if (ref > 16535) { + DEBUG("ledger.validate", "amount_t::bigint_t: ref > 16535"); + return false; + } +#if 0 + // jww (2008-07-24): How does one check the validity of an mpz_t? + if (val[0]._mp_size < 0 || val[0]._mp_size > 100) { + DEBUG("ledger.validate", "amount_t::bigint_t: val._mp_size is bad"); + return false; + } +#endif + return true; + } +}; + +uint_fast32_t amount_t::sizeof_bigint_t() +{ + return sizeof(bigint_t); +} + +void amount_t::initialize() +{ + mpz_init(temp); + mpz_init(divisor); + + // jww (2007-05-02): Be very careful here! + if (! current_pool) + current_pool = new commodity_pool_t; + + // Add time commodity conversions, so that timelog's may be parsed + // in terms of seconds, but reported as minutes or hours. + if (commodity_t * commodity = current_pool->create("s")) { + commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); + + parse_conversion("1.0m", "60s"); + parse_conversion("1.0h", "60m"); + } else { + assert(false); + } +} + +void amount_t::shutdown() +{ + mpz_clear(temp); + mpz_clear(divisor); + + // jww (2007-05-02): Be very careful here! + if (current_pool) { + checked_delete(current_pool); + current_pool = NULL; + } +} + +void amount_t::_copy(const amount_t& amt) +{ + assert(amt.valid()); + + 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->has_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_; + + assert(valid()); +} + +void amount_t::_dup() +{ + assert(valid()); + + if (quantity->ref > 1) { + bigint_t * q = new bigint_t(*quantity); + _release(); + quantity = q; + } + + assert(valid()); +} + +void amount_t::_resize(precision_t prec) +{ + assert(prec < 256); + + if (! quantity || prec == quantity->prec) + return; + + _dup(); + + assert(prec > quantity->prec); + mpz_ui_pow_ui(divisor, 10, prec - quantity->prec); + mpz_mul(MPZ(quantity), MPZ(quantity), divisor); + + quantity->prec = prec; + + assert(valid()); +} + +void amount_t::_clear() +{ + if (quantity) { + _release(); + quantity = NULL; + commodity_ = NULL; + } else { + assert(! commodity_); + } +} + +void amount_t::_release() +{ + assert(valid()); + + DEBUG("amounts.refs", quantity << " ref--, now " << (quantity->ref - 1)); + + if (--quantity->ref == 0) { + if (quantity->has_flags(BIGINT_BULK_ALLOC)) + quantity->~bigint_t(); + else + checked_delete(quantity); + quantity = NULL; + commodity_ = NULL; + } + + assert(valid()); +} + + +#ifdef HAVE_GDTOA +namespace { + amount_t::precision_t convert_double(mpz_t dest, double val) + { + 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 + 1]; + + 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) + 2]; + newbuf[0] = '-'; + std::strcpy(&newbuf[1], result ? result : buf); + mpz_set_str(dest, newbuf, 10); + checked_array_delete(newbuf); + } else { + mpz_set_str(dest, result ? result : buf, 10); + } + + if (result) + checked_array_delete(result); + freedtoa(buf); + + return decpt; + } +} + +amount_t::amount_t(const double val) : commodity_(NULL) +{ + TRACE_CTOR(amount_t, "const double"); + quantity = new bigint_t; + quantity->prec = convert_double(MPZ(quantity), val); +} +#endif + +amount_t::amount_t(const unsigned long val) : commodity_(NULL) +{ + TRACE_CTOR(amount_t, "const unsigned long"); + quantity = new bigint_t; + mpz_set_ui(MPZ(quantity), val); +} + +amount_t::amount_t(const long val) : commodity_(NULL) +{ + TRACE_CTOR(amount_t, "const long"); + quantity = new bigint_t; + mpz_set_si(MPZ(quantity), val); +} + + +amount_t& amount_t::operator=(const amount_t& amt) +{ + if (this != &amt) { + if (amt.quantity) + _copy(amt); + else if (quantity) + _clear(); + } + return *this; +} + + +int amount_t::compare(const amount_t& amt) const +{ + assert(amt.valid()); + + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot compare an amount to an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot compare an uninitialized amount to an amount"); + else + throw_(amount_error, "Cannot compare two uninitialized amounts"); + } + + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + "Cannot compare amounts with different commodities: " << + commodity().symbol() << " and " << amt.commodity().symbol()); + + 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)); + } +} + + +amount_t& amount_t::operator+=(const amount_t& amt) +{ + assert(amt.valid()); + + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot add an amount to an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot add an uninitialized amount to an amount"); + else + throw_(amount_error, "Cannot add two uninitialized amounts"); + } + + if (commodity() != amt.commodity()) + throw_(amount_error, + "Adding amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); + + _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) +{ + assert(amt.valid()); + + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot subtract an amount from an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot subtract an uninitialized amount from an amount"); + else + throw_(amount_error, "Cannot subtract two uninitialized amounts"); + } + + if (commodity() != amt.commodity()) + throw_(amount_error, + "Subtracting amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); + + _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; +} + +namespace { + 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::operator*=(const amount_t& amt) +{ + assert(amt.valid()); + + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot multiply an amount by an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot multiply an uninitialized amount by an amount"); + else + throw_(amount_error, "Cannot multiply two uninitialized amounts"); + } + +#if 0 + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + "Multiplying amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); +#endif + + _dup(); + + mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + quantity->prec += amt.quantity->prec; + + if (! has_commodity()) + commodity_ = amt.commodity_; + + if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) { + precision_t 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) +{ + assert(amt.valid()); + + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot divide an amount by an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot divide an uninitialized amount by an amount"); + else + throw_(amount_error, "Cannot divide two uninitialized amounts"); + } + +#if 0 + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + "Dividing amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); +#endif + + if (! amt) + throw_(amount_error, "Divide by zero"); + + _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; + + 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->has_flags(BIGINT_KEEP_PREC))) { + precision_t 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::precision_t amount_t::precision() const +{ + if (! quantity) + throw_(amount_error, "Cannot determine precision of an uninitialized amount"); + + return quantity->prec; +} + +amount_t& amount_t::in_place_negate() +{ + if (quantity) { + _dup(); + mpz_neg(MPZ(quantity), MPZ(quantity)); + } else { + throw_(amount_error, "Cannot negate an uninitialized amount"); + } + return *this; +} + +amount_t amount_t::round() const +{ + if (! quantity) + throw_(amount_error, "Cannot round an uninitialized amount"); + + if (! has_commodity()) + return *this; + + return round(commodity().precision()); +} + +amount_t amount_t::round(precision_t prec) const +{ + if (! quantity) + throw_(amount_error, "Cannot round an uninitialized amount"); + + amount_t t(*this); + + if (quantity->prec <= prec) { + if (quantity && quantity->has_flags(BIGINT_KEEP_PREC)) { + t._dup(); + t.quantity->drop_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->drop_flags(BIGINT_KEEP_PREC); + + return t; +} + +amount_t amount_t::unround() const +{ + if (! quantity) + throw_(amount_error, "Cannot unround an uninitialized amount"); + else if (quantity->has_flags(BIGINT_KEEP_PREC)) + return *this; + + amount_t t(*this); + t._dup(); + t.quantity->add_flags(BIGINT_KEEP_PREC); + + return t; +} + +amount_t& amount_t::in_place_reduce() +{ + if (! quantity) + throw_(amount_error, "Cannot reduce an uninitialized amount"); + + while (commodity_ && commodity().smaller()) { + *this *= commodity().smaller()->number(); + commodity_ = commodity().smaller()->commodity_; + } + return *this; +} + +amount_t& amount_t::in_place_unreduce() +{ + if (! quantity) + throw_(amount_error, "Cannot unreduce an uninitialized amount"); + + while (commodity_ && commodity().larger()) { + *this /= commodity().larger()->number(); + commodity_ = commodity().larger()->commodity_; + if (abs() < amount_t(1L)) + break; + } + return *this; +} + +optional<amount_t> amount_t::value(const optional<datetime_t>& moment) const +{ + if (quantity) { + optional<amount_t> amt(commodity().value(moment)); + if (amt) + return (*amt * number()).round(); + } else { + throw_(amount_error, "Cannot determine value of an uninitialized amount"); + } + return none; +} + + +int amount_t::sign() const +{ + if (! quantity) + throw_(amount_error, "Cannot determine sign of an uninitialized amount"); + + return mpz_sgn(MPZ(quantity)); +} + +bool amount_t::is_zero() const +{ + if (! quantity) + throw_(amount_error, "Cannot determine if an uninitialized amount is zero"); + + if (has_commodity()) { + if (quantity->prec <= commodity().precision() || + quantity->has_flags(BIGINT_KEEP_PREC)) + return is_realzero(); + else + return round(commodity().precision()).sign() == 0; + } + return is_realzero(); +} + + +#ifdef HAVE_GDTOA +double amount_t::to_double(bool no_check) const +{ + if (! quantity) + throw_(amount_error, "Cannot convert an uninitialized amount to a double"); + + 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); + + double value = lexical_cast<double>(num.str()); + + if (! no_check && *this != value) + throw_(amount_error, "Conversion of amount to_double loses precision"); + + return value; +} +#endif + +long amount_t::to_long(bool no_check) const +{ + if (! quantity) + throw_(amount_error, "Cannot convert an uninitialized amount to a long"); + + mpz_set(temp, MPZ(quantity)); + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_q(temp, temp, divisor); + + long value = mpz_get_si(temp); + + if (! no_check && *this != value) + throw_(amount_error, "Conversion of amount to_long loses precision"); + + return value; +} + +#ifdef HAVE_GDTOA +bool amount_t::fits_in_double() const +{ + double value = to_double(true); + return *this == amount_t(value); +} +#endif + +bool amount_t::fits_in_long() const +{ + long value = to_long(true); + return *this == amount_t(value); +} + + +void amount_t::annotate(const annotation_t& details) +{ + commodity_t * this_base; + annotated_commodity_t * this_ann = NULL; + + if (! quantity) + throw_(amount_error, "Cannot annotate the commodity of an uninitialized amount"); + else if (! has_commodity()) + throw_(amount_error, "Cannot annotate an amount with no commodity"); + + if (commodity().annotated) { + this_ann = &as_annotated_commodity(commodity()); + this_base = &this_ann->referent(); + } else { + this_base = &commodity(); + } + assert(this_base); + + DEBUG("amounts.commodities", "Annotating commodity for amount " + << *this << std::endl << details); + + if (commodity_t * ann_comm = + this_base->parent().find_or_create(*this_base, details)) + set_commodity(*ann_comm); +#ifdef ASSERTS_ON + else + assert(false); +#endif + + DEBUG("amounts.commodities", " Annotated amount is " << *this); +} + +bool amount_t::annotated() const +{ + if (! quantity) + throw_(amount_error, + "Cannot determine if an uninitialized amount's commodity is annotated"); + + assert(! commodity().annotated || as_annotated_commodity(commodity()).details); + return commodity().annotated; +} + +annotation_t& amount_t::annotation() +{ + if (! quantity) + throw_(amount_error, + "Cannot return commodity annotation details of an uninitialized amount"); + + if (! commodity().is_annotated()) + throw_(amount_error, + "Request for annotation details from an unannotated amount"); + + annotated_commodity_t& ann_comm(as_annotated_commodity(commodity())); + return ann_comm.details; +} + +amount_t amount_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) const +{ + if (! quantity) + throw_(amount_error, + "Cannot strip commodity annotations from an uninitialized amount"); + + if (! commodity().annotated || + (_keep_price && _keep_date && _keep_tag)) + return *this; + + amount_t t(*this); + t.set_commodity(as_annotated_commodity(commodity()). + strip_annotations(_keep_price, _keep_date, _keep_tag)); + return t; +} + + +namespace { + 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; + } +} + +bool amount_t::parse(std::istream& in, flags_t flags) +{ + // The possible syntax for an amount is: + // + // [-]NUM[ ]SYM [@ AMOUNT] + // SYM[ ][-]NUM [@ AMOUNT] + + string symbol; + string quant; + annotation_t details; + bool negative = false; + + commodity_t::flags_t comm_flags = COMMODITY_STYLE_DEFAULTS; + + 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; + + commodity_t::parse_symbol(in, symbol); + + if (! symbol.empty()) + comm_flags |= COMMODITY_STYLE_SUFFIXED; + + if (! in.eof() && ((n = in.peek()) != '\n')) + details.parse(in); + } + } else { + commodity_t::parse_symbol(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')) + details.parse(in); + } + } + + if (quant.empty()) { + if (flags & AMOUNT_PARSE_SOFT_FAIL) + return false; + else + throw_(amount_error, "No quantity specified for amount"); + } + + // Allocate memory for the amount's quantity value. We have to + // monitor the allocation in an auto_ptr because this function gets + // called sometimes from amount_t's constructor; and if there is an + // exeception thrown by any of the function calls after this point, + // the destructor will never be called and the memory never freed. + + std::auto_ptr<bigint_t> safe_holder; + + if (! quantity) { + quantity = new bigint_t; + safe_holder.reset(quantity); + } + else if (quantity->ref > 1) { + _release(); + quantity = new bigint_t; + safe_holder.reset(quantity); + } + + // 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_ = current_pool->find(symbol); + if (! commodity_) { + commodity_ = current_pool->create(symbol); + newly_created = true; + } + assert(commodity_); + + if (details) + commodity_ = current_pool->find_or_create(*commodity_, details); + } + + // 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().has_flags(COMMODITY_STYLE_EUROPEAN)) { + quantity->prec = quant.length() - last_comma - 1; + } + else if (last_period != string::npos && + ! (commodity().has_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_ && ! (flags & AMOUNT_PARSE_NO_MIGRATE)) { + commodity().add_flags(comm_flags); + + if (quantity->prec > commodity().precision()) + commodity().set_precision(quantity->prec); + } + + // Setup the amount's own flags + + if (flags & AMOUNT_PARSE_NO_MIGRATE) + quantity->add_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); + checked_array_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(); + + safe_holder.release(); // `this->quantity' owns the pointer + + assert(valid()); + + return true; +} + +void amount_t::parse_conversion(const string& larger_str, + const string& smaller_str) +{ + amount_t larger, smaller; + + larger.parse(larger_str, AMOUNT_PARSE_NO_REDUCE); + smaller.parse(smaller_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::print(std::ostream& _out, bool omit_commodity, + bool full_precision) const +{ + assert(valid()); + + if (! quantity) { + _out << "<null>"; + return; + } + + amount_t base(*this); + if (! amount_t::keep_base) + base.in_place_unreduce(); + + 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()); + precision_t precision = 0; + + if (quantity) { + if (! comm || full_precision || base.quantity->has_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.has_flags(COMMODITY_STYLE_SUFFIXED)) { + comm.print(out); + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; + } + + if (negative) + out << "-"; + + if (! quantity || mpz_sgn(quotient) == 0) { + out << '0'; + } + else if (omit_commodity || ! comm.has_flags(COMMODITY_STYLE_THOUSANDS)) { + char * p = mpz_get_str(NULL, 10, quotient); + out << p; + std::free(p); + } + else { + std::list<string> 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<string>::reverse_iterator i = strs.rbegin(); + i != strs.rend(); + i++) { + if (printed) { + out << (comm.has_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()) { + if (omit_commodity) + out << '.'; + else + out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); + out << ender; + } + } + + if (! omit_commodity && comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; + comm.print(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<annotated_commodity_t&>(comm)); + assert(&*ann.details.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(); +} + +void amount_t::read(std::istream& in) +{ + using namespace ledger::binary; + + // Read in the commodity for this amount + + commodity_t::ident_t ident; + read_long(in, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = current_pool->null_commodity; + else { + commodity_ = current_pool->find(ident); + assert(commodity_); + } + + // Read in the quantity + + char byte; + in.read(&byte, sizeof(byte)); + + if (byte < 3) { + quantity = new bigint_t; + + unsigned short len; + in.read(reinterpret_cast<char *>(&len), sizeof(len)); + assert(len < 4096); + static char buf[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(reinterpret_cast<char *>(&quantity->prec), sizeof(quantity->prec)); + + bigint_t::flags_t tflags; + in.read(reinterpret_cast<char *>(&tflags), sizeof(tflags)); + quantity->set_flags(tflags); + } + else { + assert(false); + } +} + +void amount_t::read(const char *& data) +{ + using namespace ledger::binary; + + // Read in the commodity for this amount + + commodity_t::ident_t ident; + read_long(data, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = current_pool->null_commodity; + else { + commodity_ = current_pool->find(ident); + assert(commodity_); + } + + // Read in the quantity + + char byte = *data++;; + + if (byte < 3) { + if (byte == 2) { +#if 0 + quantity = new(reinterpret_cast<bigint_t *>(bigints_next)) bigint_t; + bigints_next += sizeof(bigint_t); +#endif + } else { + quantity = new bigint_t; + } + + unsigned short len = + *reinterpret_cast<unsigned short *>(const_cast<char *>(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 = *reinterpret_cast<precision_t *>(const_cast<char *>(data)); + data += sizeof(precision_t); + quantity->set_flags(*reinterpret_cast<flags_t *>(const_cast<char *>(data))); + data += sizeof(flags_t); + + if (byte == 2) + quantity->add_flags(BIGINT_BULK_ALLOC); + } else { +#if 0 + uint_fast32_t index = *reinterpret_cast<uint_fast32_t *>(const_cast<char *>(data)); + data += sizeof(uint_fast32_t); + + quantity = reinterpret_cast<bigint_t *>(bigints + (index - 1) * sizeof(bigint_t)); +#endif + DEBUG("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; + } +} + +void amount_t::write(std::ostream& out, bool optimized) const +{ + using namespace ledger::binary; + + // Write out the commodity for this amount + + if (! quantity) + throw_(amount_error, "Cannot serialize an uninitialized amount"); + + if (commodity_) + write_long(out, commodity_->ident); + else + write_long<commodity_t::ident_t>(out, 0xffffffff); + + // Write out the quantity + + char byte; + + if (! optimized || quantity->index == 0) { + if (optimized) { +#if 0 + quantity->index = ++bigints_index; // if !optimized, this is garbage + bigints_count++; + byte = 2; +#endif + } else { + byte = 1; + } + out.write(&byte, sizeof(byte)); + + std::size_t size; + static char buf[4096]; + mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity)); + unsigned short len = size * sizeof(short); + out.write(reinterpret_cast<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(reinterpret_cast<char *>(&quantity->prec), sizeof(quantity->prec)); + bigint_t::flags_t tflags = quantity->flags() & ~BIGINT_BULK_ALLOC; + assert(sizeof(tflags) == sizeof(bigint_t::flags_t)); + out.write(reinterpret_cast<char *>(&tflags), sizeof(tflags)); + } else { + assert(quantity->ref > 1); + + // Since this value has already been written, we simply write + // out a reference to which one it was. + byte = 3; + out.write(&byte, sizeof(byte)); + out.write(reinterpret_cast<char *>(&quantity->index), sizeof(quantity->index)); + } +} + +bool amount_t::valid() const +{ + if (quantity) { + if (! quantity->valid()) + return false; + + 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; +} + +} // namespace ledger diff --git a/src/amount.h b/src/amount.h new file mode 100644 index 00000000..8fb5aa14 --- /dev/null +++ b/src/amount.h @@ -0,0 +1,748 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file amount.h + * @author John Wiegley + * @date Wed Apr 18 22:05:53 2007 + * + * @brief Basic type for handling commoditized math: amount_t. + * + * This file contains the most basic numerical type in Ledger: + * amount_t, which relies upon commodity.h (commodity_t) for handling + * commoditized amounts. This class allows Ledger to handle + * mathematical expressions involving differing commodities, as well + * as math using no commodities at all (such as increasing a dollar + * amount by a multiplier). + */ +#ifndef _AMOUNT_H +#define _AMOUNT_H + +#include "utils.h" + +namespace ledger { + +class commodity_t; +class annotation_t; +class commodity_pool_t; + +DECLARE_EXCEPTION(amount_error, std::runtime_error); + +/** + * @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. In + * both cases, internal precision is always kept to an excessive + * degree. + */ +class amount_t + : public ordered_field_operators<amount_t, +#ifdef HAVE_GDTOA + ordered_field_operators<amount_t, double, +#endif + ordered_field_operators<amount_t, unsigned long, + ordered_field_operators<amount_t, long> > > +#ifdef HAVE_GDTOA + > +#endif +{ + // jww (2007-05-03): Make this private, and then make + // ledger::initialize into a member function of session_t. +public: + /** + * The initialize and shutdown methods ready the amount subsystem + * for use. Normally they are called by `ledger::initialize' and + * `ledger::shutdown'. + */ + static void initialize(); + static void shutdown(); + +public: + typedef uint_least16_t precision_t; + + /** + * The current_pool is a static variable indicating which commodity + * pool should be used. + */ + static commodity_pool_t * current_pool; + + /** + * The `keep_base' member determines whether scalable commodities + * are automatically converted to their most reduced form when + * printing. The default is true. + * + * For example, Ledger supports time values specified in seconds + * (10s), hours (5.2h) or minutes. Internally, such amounts are + * always kept as quantities of seconds. However, when streaming + * the amount Ledger will convert it to its "least representation", + * which is "5.2h" in the second case. If `keep_base' is true, this + * amount is displayed as "18720s". + */ + static bool keep_base; + + /** + * The following three members determine whether lot details are + * maintained when working with commoditized values. The default is + * false for all three. + * + * Let's say a user adds two values of the following form: + * 10 AAPL + 10 AAPL {$20} + * + * This expression adds ten shares of Apple stock with another ten + * shares that were purchased for $20 a share. If `keep_price' is + * false, the result of this expression will be an amount equal to + * 20 AAPL. If `keep_price' is true, the expression yields an + * exception for adding amounts with different commodities. In that + * case, a balance_t object must be used to store the combined sum. + */ + static bool keep_price; + static bool keep_date; + static bool keep_tag; + + /** + * The `stream_fullstrings' static member is currently only used by + * the unit testing code. It causes amounts written to streams to + * use the `to_fullstring' method rather than the `to_string' + * method, so that complete precision is always displayed, no matter + * what the precision of an individual commodity might be. + * @see to_string + * @see to_fullstring + */ + static bool stream_fullstrings; + + static uint_fast32_t sizeof_bigint_t(); + +protected: + void _copy(const amount_t& amt); + void _dup(); + void _resize(precision_t prec); + void _clear(); + void _release(); + + struct bigint_t; + +public: // needed by binary.cc + bigint_t * quantity; + commodity_t * commodity_; + +public: + /** + * Constructors. amount_t supports several forms of construction: + * + * amount_t() creates a value for which `is_null' is true, and which + * has no value or commodity. If used in value situations it will + * be zero, and its commodity equals `commodity_t::null_commodity'. + * + * amount_t(double), amount_t(unsigned long), amount_t(long) all + * convert from the respective numerics type to an amount. No + * precision or sign is lost in any of these conversions. The + * resulting commodity is always `commodity_t::null_commodity'. + * + * amount_t(string), amount_t(const char *) both convert from a + * string representation of an amount, which may or may not include + * a commodity. This is the proper way to initialize an amount like + * '$100.00'. + */ + amount_t() : quantity(NULL), commodity_(NULL) { + TRACE_CTOR(amount_t, ""); + } +#ifdef HAVE_GDTOA + amount_t(const double val); +#endif + amount_t(const unsigned long val); + amount_t(const long val); + + explicit amount_t(const string& val) : quantity(NULL) { + TRACE_CTOR(amount_t, "const string&"); + parse(val); + } + explicit amount_t(const char * val) : quantity(NULL) { + TRACE_CTOR(amount_t, "const char *"); + assert(val); + parse(val); + } + + /** + * Static creator function. Calling amount_t::exact(string) will + * create an amount whose display precision is never truncated, even + * if the amount uses a commodity (which normally causes "round on + * streaming" to occur). This function is mostly used by the + * debugging code. It is the proper way to initialize '$100.005', + * where display of the extra precision is required. If a regular + * constructor is used, this amount will stream as '$100.01', even + * though its internal value always equals $100.005. + */ + static amount_t exact(const string& value); + + /** + * Destructor. Releases the reference count held for the underlying + * bigint_t object pointed to be `quantity'. + */ + ~amount_t() { + TRACE_DTOR(amount_t); + if (quantity) + _release(); + } + + /** + * Assignment and copy operators. An amount may be assigned or + * copied. If a double, long or unsigned long is assigned to an + * amount, a temporary is constructed, and then the temporary is + * assigned to `this'. Both the value and the commodity are copied, + * causing the result to compare equal to the reference amount. + * + * Note: `quantity' must be initialized to NULL first, otherwise the + * `_copy' function will attempt to release the uninitialized pointer. + */ + amount_t(const amount_t& amt) : quantity(NULL) { + TRACE_CTOR(amount_t, "copy"); + if (amt.quantity) + _copy(amt); + else + commodity_ = NULL; + } + amount_t& operator=(const amount_t& amt); + +#ifdef HAVE_GDTOA + amount_t& operator=(const double val) { + return *this = amount_t(val); + } +#endif + amount_t& operator=(const unsigned long val) { + return *this = amount_t(val); + } + amount_t& operator=(const long val) { + return *this = amount_t(val); + } + + amount_t& operator=(const string& str) { + return *this = amount_t(str); + } + amount_t& operator=(const char * str) { + assert(str); + return *this = amount_t(str); + } + + /** + * Comparison operators. The fundamental comparison operation for + * amounts is `compare', which returns a value less than, greater + * than or equal to zero. All the other comparison operators are + * defined in terms of this method. The only special detail is that + * `operator==' will fail immediately if amounts with different + * commodities are being compared. Otherwise, if the commodities + * are equivalent (@see keep_price, et al), then the amount + * quantities are compared numerically. + * + * Comparison between an amount and a double, long or unsigned long + * is allowed. In such cases the non-amount value is constructed as + * an amount temporary, which is then compared to `this'. + */ + int compare(const amount_t& amt) const; + + bool operator==(const amount_t& amt) const; + + template <typename T> + bool operator==(const T& val) const { + return compare(val) == 0; + } + template <typename T> + bool operator<(const T& amt) const { + return compare(amt) < 0; + } + template <typename T> + bool operator>(const T& amt) const { + return compare(amt) > 0; + } + + /** + * Binary arithmetic operators. Amounts support addition, + * subtraction, multiplication and division -- but not modulus, + * bitwise operations, or shifting. Arithmetic is also supported + * between amounts, double, long and unsigned long, in which case + * temporary amount are constructed for the life of the expression. + * + * Although only in-place operators are defined here, the remainder + * are provided by `boost::ordered_field_operators<>'. + */ + 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); + + /** + * Unary arithmetic operators. There are several unary methods + * support on amounts: + * + * precision() return an amount's current, internal precision. To + * find the precision it will be displayed at -- assuming it was not + * created using the static method `amount_t::exact' -- refer to + * commodity().precision. + * + * negate(), also unary minus (- x), returns the negated value of an + * amount. + * + * abs() returns the absolute value of an amount. It is equivalent + * to: `(x < 0) ? - x : x'. + * + * round(precision_t) and round() round an amount's internal value + * to the given precision, or to the commodity's current display + * precision if no precision value is given. This method changes + * the internal value of the amount, if it's internal precision was + * greater than the rounding precision. + * + * unround() yields an amount whose display precision is never + * truncated, even though its commodity normally displays only + * rounded values. + * + * reduce() reduces a value to its most basic commodity form, for + * amounts that utilize "scaling commodities". For example, an + * amount of 1h after reduction will be 3600s. + * + * unreduce(), if used with a "scaling commodity", yields the most + * compact form greater than 1.0. That is, 3599s will unreduce to + * 59.98m, while 3601 unreduces to 1h. + * + * value(optional<datetime_t>) returns the historical value for an + * amount -- the default moment returns the most recently known + * price -- based on the price history of its commodity. For + * example, if the amount were 10 AAPL, and on Apr 10, 2000 each + * share of AAPL was worth $10, then call value() for that moment in + * time would yield the amount $100.00. + * + * Further, for the sake of efficiency and avoiding temporary + * objects, the following methods support "in-place" variants that + * act on the amount itself and return a reference to the result + * (`*this'): + * + * in_place_negate() + * in_place_reduce() + * in_place_unreduce() + */ + precision_t precision() const; + + amount_t negate() const { + amount_t temp(*this); + temp.in_place_negate(); + return temp; + } + amount_t& in_place_negate(); + + amount_t operator-() const { + return negate(); + } + + amount_t abs() const { + if (sign() < 0) + return negate(); + return *this; + } + + amount_t round() const; + amount_t round(precision_t prec) const; + amount_t unround() const; + + amount_t reduce() const { + amount_t temp(*this); + temp.in_place_reduce(); + return temp; + } + amount_t& in_place_reduce(); + + amount_t unreduce() const { + amount_t temp(*this); + temp.in_place_unreduce(); + return temp; + } + amount_t& in_place_unreduce(); + + optional<amount_t> value(const optional<datetime_t>& moment = none) const; + + /** + * Truth tests. An amount may be truth test in several ways: + * + * sign() returns an integer less than, greater than, or equal to + * zero depending on whether the amount is negative, zero, or + * greater than zero. Note that this function tests the actual + * value of the amount -- using its internal precision -- and not + * the display value. To test its display value, use: + * `round().sign()'. + * + * is_nonzero(), or operator bool, returns true if an amount's + * display value is not zero. + * + * is_zero() returns true if an amount's display value is zero. + * Thus, $0.0001 is considered zero if the current display precision + * for dollars is two decimal places. + * + * is_realzero() returns true if an amount's actual value is zero. + * Thus, $0.0001 is never considered realzero. + * + * is_null() returns true if an amount has no value and no + * commodity. This only occurs if an uninitialized amount has never + * been assigned a value. + */ + int sign() const; + + operator bool() const { + return is_nonzero(); + } + bool is_nonzero() const { + return ! is_zero(); + } + + bool is_zero() const; + bool is_realzero() const { + return sign() == 0; + } + + bool is_null() const { + if (! quantity) { + assert(! commodity_); + return true; + } + return false; + } + + /** + * Conversion methods. An amount may be converted to the same types + * it can be constructed from -- with the exception of unsigned + * long. Implicit conversions are not allowed in C++ (though they + * are in Python), rather the following conversion methods must be + * called explicitly: + * + * to_double([bool]) returns an amount as a double. If the optional + * boolean argument is true (the default), an exception is thrown if + * the conversion would lose information. + * + * to_long([bool]) returns an amount as a long integer. If the + * optional boolean argument is true (the default), an exception is + * thrown if the conversion would lose information. + * + * fits_in_double() returns true if to_double() would not lose + * precision. + * + * fits_in_long() returns true if to_long() would not lose + * precision. + * + * to_string() returns an amount'ss "display value" as a string -- + * after rounding the value according to the commodity's default + * precision. It is equivalent to: `round().to_fullstring()'. + * + * to_fullstring() returns an amount's "internal value" as a string, + * without any rounding. + * + * quantity_string() returns an amount's "display value", but + * without any commodity. Note that this is different from + * `number().to_string()', because in that case the commodity has + * been stripped and the full, internal precision of the amount + * would be displayed. + */ +#ifdef HAVE_GDTOA + double to_double(bool no_check = false) const; +#endif + long to_long(bool no_check = false) const; + string to_string() const; + string to_fullstring() const; + string quantity_string() const; + +#ifdef HAVE_GDTOA + bool fits_in_double() const; +#endif + bool fits_in_long() const; + + /** + * Commodity-related methods. The following methods relate to an + * amount's commodity: + * + * commodity() returns an amount's commodity. If the amount has no + * commodity, the value returned is `current_pool->null_commodity'. + * + * has_commodity() returns true if the amount has a commodity. + * + * set_commodity(commodity_t) sets an amount's commodity to the + * given value. Note that this merely sets the current amount to + * that commodity, it does not "observe" the amount for possible + * changes in the maximum display precision of the commodity, the + * way that `parse' does. + * + * clear_commodity() sets an amount's commodity to null, such that + * has_commodity() afterwards returns false. + * + * number() returns a commodity-less version of an amount. This is + * useful for accessing just the numeric portion of an amount. + */ + commodity_t& commodity() const; + + bool has_commodity() const; + void set_commodity(commodity_t& comm) { + if (! quantity) + *this = 0L; + commodity_ = &comm; + } + void clear_commodity() { + commodity_ = NULL; + } + + amount_t number() const { + if (! has_commodity()) + return *this; + + amount_t temp(*this); + temp.clear_commodity(); + return temp; + } + + /** + * Annotated commodity methods. An amount's commodity may be + * annotated with special details, such as the price it was + * purchased for, when it was acquired, or an arbitrary note, + * identifying perhaps the lot number of an item. + * + * annotate_commodity(amount_t price, [datetime_t date, string tag]) + * sets the annotations for the current amount's commodity. Only + * the price argument is required, although it can be passed as + * `none' if no price is desired. + * + * commodity_annotated() returns true if an amount's commodity has + * any annotation details associated with it. + * + * annotation_details() returns all of the details of an annotated + * commodity's annotations. The structure returns will evaluate as + * boolean false if there are no details. + * + * strip_annotations([keep_price, keep_date, keep_tag]) returns an + * amount whose commodity's annotations have been stripped. The + * three `keep_' arguments determine which annotation detailed are + * kept, meaning that the default is to follow whatever + * amount_t::keep_price, amount_t::keep_date and amount_t::keep_tag + * have been set to (which all default to false). + */ + void annotate(const annotation_t& details); + bool annotated() const; + + annotation_t& annotation(); + const annotation_t& annotation() const { + return const_cast<amount_t&>(*this).annotation(); + } + + amount_t strip_annotations(const bool _keep_price = keep_price, + const bool _keep_date = keep_date, + const bool _keep_tag = keep_tag) const; + + /** + * Parsing methods. The method `parse' is used to parse an amount + * from an input stream or a string. A global operator>> is also + * defined which simply calls parse on the input stream. The + * `parse' method has two forms: + * + * parse(istream, flags_t) parses an amount from the given input + * stream. + * + * parse(string, flags_t) parses an amount from the given string. + * + * parse(string, flags_t) also parses an amount from a string. + * + * The `flags' argument of both parsing may be one or more of the + * following: + * + * AMOUNT_PARSE_NO_MIGRATE means to not pay attention to the way an + * amount is used. Ordinarily, if an amount were $100.001, for + * example, it would cause the default display precision for $ to be + * "widened" to three decimal places. If AMOUNT_PARSE_NO_MIGRATE is + * used, the commodity's default display precision is not changed. + * + * AMOUNT_PARSE_NO_REDUCE means not to call in_place_reduce() on the + * resulting amount after it is parsed. + * + * These parsing methods observe the amounts they parse (unless + * AMOUNT_PARSE_NO_MIGRATE is true), and set the display details of + * the corresponding commodity accordingly. This way, amounts do + * not require commodities to be pre-defined in any way, but merely + * displays them back to the user in the same fashion as it saw them + * used. + * + * There is also a static convenience method called + * `parse_conversion' which can be used to define a relationship + * between scaling commodity values. For example, Ledger uses it to + * define the relationships among various time values: + * + * amount_t::parse_conversion("1.0m", "60s"); // a minute is 60 seconds + * amount_t::parse_conversion("1.0h", "60m"); // an hour is 60 minutes + */ +#define AMOUNT_PARSE_NO_MIGRATE 0x01 +#define AMOUNT_PARSE_NO_REDUCE 0x02 +#define AMOUNT_PARSE_SOFT_FAIL 0x04 + + typedef uint_least8_t flags_t; + + bool parse(std::istream& in, flags_t flags = 0); + bool parse(const string& str, flags_t flags = 0) { + std::istringstream stream(str); + bool result = parse(stream, flags); + assert(stream.eof()); + return result; + } + + static void parse_conversion(const string& larger_str, + const string& smaller_str); + + /** + * Printing methods. An amount may be output to a stream using the + * `print' method. There is also a global operator<< defined which + * simply calls print for an amount on the given stream. There is + * one form of the print method, which takes one required argument + * and two arguments with default values: + * + * print(ostream, bool omit_commodity = false, bool full_precision = + * false) prints an amounts to the given output stream, using its + * commodity's default display characteristics. If `omit_commodity' + * is true, the commodity will not be displayed, only the amount + * (although the commodity's display precision is still used). If + * `full_precision' is true, the full internal precision of the + * amount is displayed, regardless of its commodity's display + * precision. + */ + void print(std::ostream& out, bool omit_commodity = false, + bool full_precision = false) const; + + /** + * Serialization methods. An amount may be deserialized from an + * input stream or a character pointer, and it may be serialized to + * an output stream. The methods used are: + * + * read(istream) reads an amount from the given input stream. It + * must have been put there using `write(ostream)'. The required + * flow of logic is: + * amount_t::current_pool->write(out) + * amount.write(out) // write out all amounts + * amount_t::current_pool->read(in) + * amount.read(in) + * + * read(char *&) reads an amount from data which has been read from + * an input stream into a buffer. It advances the pointer passed in + * to the end of the deserialized amount. + * + * write(ostream, [bool]) writes an amount to an output stream in a + * compact binary format. If the second parameter is true, + * quantities with multiple reference counts will be written in an + * optimized fashion. NOTE: This form of usage is valid only for + * the binary journal writer, it should not be used otherwise, as it + * has strict requirements for reading that only the binary reader + * knows about. + */ + void read(std::istream& in); + void read(const char *& data); + void write(std::ostream& out, bool optimize = false) const; + + /** + * Debugging methods. There are two methods defined to help with + * debugging: + * + * dump(ostream) dumps an amount to an output stream. There is + * little different from print(), it simply surrounds the display + * value with a marker, for example "AMOUNT($1.00)". This code is + * used by other dumping code elsewhere in Ledger. + * + * valid() returns true if an amount is valid. This ensures that if + * an amount has a commodity, it has a valid value pointer, for + * example, even if that pointer simply points to a zero value. + */ + void dump(std::ostream& out) const { + out << "AMOUNT("; + print(out); + out << ")"; + } + + bool valid() const; +}; + +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(); +} + +inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) { + amt.print(out, false, amount_t::stream_fullstrings); + return out; +} +inline std::istream& operator>>(std::istream& in, amount_t& amt) { + amt.parse(in); + return in; +} + +} // namespace ledger + +#include "commodity.h" + +namespace ledger { + +inline bool amount_t::operator==(const amount_t& amt) const { + if (commodity() != amt.commodity()) + return false; + return compare(amt) == 0; +} + +inline commodity_t& amount_t::commodity() const { + return has_commodity() ? *commodity_ : *current_pool->null_commodity; +} + +inline bool amount_t::has_commodity() const { + return commodity_ && commodity_ != commodity_->parent().null_commodity; +} + +} // namespace ledger + +#endif // _AMOUNT_H diff --git a/src/balance.cc b/src/balance.cc new file mode 100644 index 00000000..bac8d40c --- /dev/null +++ b/src/balance.cc @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "balance.h" + +namespace ledger { + +balance_t& balance_t::operator+=(const balance_t& bal) +{ + foreach (const amounts_map::value_type& pair, bal.amounts) + *this += pair.second; + return *this; +} + +balance_t& balance_t::operator+=(const amount_t& amt) +{ + if (amt.is_null()) + throw_(balance_error, + "Cannot add an uninitialized amount to a balance"); + + if (amt.is_realzero()) + return *this; + + amounts_map::iterator i = amounts.find(&amt.commodity()); + if (i != amounts.end()) + i->second += amt; + else + amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); + + return *this; +} + +balance_t& balance_t::operator-=(const balance_t& bal) +{ + foreach (const amounts_map::value_type& pair, bal.amounts) + *this -= pair.second; + return *this; +} + +balance_t& balance_t::operator-=(const amount_t& amt) +{ + if (amt.is_null()) + throw_(balance_error, + "Cannot subtract an uninitialized amount from a balance"); + + if (amt.is_realzero()) + return *this; + + amounts_map::iterator i = amounts.find(&amt.commodity()); + if (i != amounts.end()) { + i->second -= amt; + if (i->second.is_realzero()) + amounts.erase(i); + } else { + amounts.insert(amounts_map::value_type(&amt.commodity(), amt.negate())); + } + return *this; +} + +balance_t& balance_t::operator*=(const amount_t& amt) +{ + if (amt.is_null()) + throw_(balance_error, + "Cannot multiply a balance by an uninitialized amount"); + + if (is_realzero()) { + ; + } + else if (amt.is_realzero()) { + *this = amt; + } + else if (! amt.commodity()) { + // Multiplying by an amount with no commodity causes all the + // component amounts to be increased by the same factor. + foreach (amounts_map::value_type& pair, amounts) + pair.second *= amt; + } + else if (amounts.size() == 1) { + // Multiplying by a commoditized amount is only valid if the sole + // commodity in the balance is of the same kind as the amount's + // commodity. + if (*amounts.begin()->first == amt.commodity()) + amounts.begin()->second *= amt; + else + throw_(balance_error, + "Cannot multiply a balance with annotated commodities by a commoditized amount"); + } + else { + assert(amounts.size() > 1); + throw_(balance_error, + "Cannot multiply a multi-commodity balance by a commoditized amount"); + } + return *this; +} + +balance_t& balance_t::operator/=(const amount_t& amt) +{ + if (amt.is_null()) + throw_(balance_error, + "Cannot divide a balance by an uninitialized amount"); + + if (is_realzero()) { + ; + } + else if (amt.is_realzero()) { + throw_(balance_error, "Divide by zero"); + } + else if (! amt.commodity()) { + // Dividing by an amount with no commodity causes all the + // component amounts to be divided by the same factor. + foreach (amounts_map::value_type& pair, amounts) + pair.second /= amt; + } + else if (amounts.size() == 1) { + // Dividing by a commoditized amount is only valid if the sole + // commodity in the balance is of the same kind as the amount's + // commodity. + if (*amounts.begin()->first == amt.commodity()) + amounts.begin()->second /= amt; + else + throw_(balance_error, + "Cannot divide a balance with annotated commodities by a commoditized amount"); + } + else { + assert(amounts.size() > 1); + throw_(balance_error, + "Cannot divide a multi-commodity balance by a commoditized amount"); + } + return *this; +} + +optional<balance_t> +balance_t::value(const optional<datetime_t>& moment) const +{ + optional<balance_t> temp; + + foreach (const amounts_map::value_type& pair, amounts) + if (optional<amount_t> val = pair.second.value(moment)) { + if (! temp) + temp = balance_t(); + *temp += *val; + } + + return temp; +} + +optional<amount_t> +balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const +{ + // jww (2007-05-20): Needs work + if (! commodity) { + if (amounts.size() == 1) { + amounts_map::const_iterator i = amounts.begin(); + return i->second; + } + else if (amounts.size() > 1) { + // Try stripping annotations before giving an error. + balance_t temp(strip_annotations()); + if (temp.amounts.size() == 1) + return temp.commodity_amount(commodity); + + throw_(amount_error, + "Requested amount of a balance with multiple commodities: " << temp); + } + } + else if (amounts.size() > 0) { + amounts_map::const_iterator i = amounts.find(&*commodity); + if (i != amounts.end()) + return i->second; + } + return none; +} + +balance_t balance_t::strip_annotations(const bool keep_price, + const bool keep_date, + const bool keep_tag) const +{ + balance_t temp; + + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.strip_annotations(keep_price, keep_date, keep_tag); + + return temp; +} + +void balance_t::print(std::ostream& out, + const int first_width, + const int latter_width) const +{ + bool first = true; + int lwidth = latter_width; + + if (lwidth == -1) + lwidth = first_width; + + typedef std::vector<const amount_t *> amounts_array; + amounts_array sorted; + + foreach (const amounts_map::value_type& pair, amounts) + if (pair.second) + sorted.push_back(&pair.second); + + std::stable_sort(sorted.begin(), sorted.end(), compare_amount_commodities()); + + foreach (const amount_t * amount, sorted) { + int width; + if (! first) { + out << std::endl; + width = lwidth; + } else { + first = false; + width = first_width; + } + + out.width(width); + out.fill(' '); + out << std::right << *amount; + } + + if (first) { + out.width(first_width); + out.fill(' '); + out << std::right << "0"; + } +} + +} // namespace ledger diff --git a/src/balance.h b/src/balance.h new file mode 100644 index 00000000..5146937c --- /dev/null +++ b/src/balance.h @@ -0,0 +1,526 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file balance.h + * @author John Wiegley + * @date Sun May 20 15:28:44 2007 + * + * @brief Basic type for adding multiple commodities together. + * + * Unlike the amount_t class, which throws an exception if amounts of + * differing commodities are added or subtracted, the balance_t class + * is designed to allow this, tracking the amounts of each component + * commodity separately. + */ +#ifndef _BALANCE_H +#define _BALANCE_H + +#include "amount.h" + +namespace ledger { + +DECLARE_EXCEPTION(balance_error, std::runtime_error); + +/** + * @class balance_t + * + * @brief A wrapper around amount_t allowing addition of multiple commodities. + * + * The balance_t class is appopriate for keeping a running balance + * where amounts of multiple commodities may be involved. + */ +class balance_t + : public equality_comparable<balance_t, + equality_comparable<balance_t, amount_t, + equality_comparable<balance_t, double, + equality_comparable<balance_t, unsigned long, + equality_comparable<balance_t, long, + additive<balance_t, + additive<balance_t, amount_t, + additive<balance_t, double, + additive<balance_t, unsigned long, + additive<balance_t, long, + multiplicative<balance_t, amount_t, + multiplicative<balance_t, double, + multiplicative<balance_t, unsigned long, + multiplicative<balance_t, long> > > > > > > > > > > > > > +{ +public: + typedef std::map<const commodity_t *, amount_t> amounts_map; + + amounts_map amounts; + + // jww (2007-05-20): Remove these two by adding access methods + friend class value_t; + friend class entry_base_t; + + /** + * Constructors. balance_t supports similar forms of construction + * to amount_t. + * + * balance_t() creates an empty balance to which amounts or other + * balances may be added or subtracted. + * + * balance_t(amount_t) constructs a balance whose starting value is + * equal to the given amount. + * + * balance_t(double), balance_t(unsigned long) and balance_t(long) + * will construct an amount from their arguments and then construct + * a balance whose starting value is equal to that amount. This + * initial balance will have no commodity. + * + * balance_t(string) and balance_t(const char *) both convert from a + * string representation of an amount to a balance whose initial + * value is that amount. This is the proper way to initialize a + * balance like '$100.00'. + */ + balance_t() { + TRACE_CTOR(balance_t, ""); + } + balance_t(const amount_t& amt) { + TRACE_CTOR(balance_t, "const amount_t&"); + if (amt.is_null()) + throw_(balance_error, + "Cannot initialize a balance from an uninitialized amount"); + if (! amt.is_realzero()) + amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); + } +#ifdef HAVE_GDTOA + balance_t(const double val) { + TRACE_CTOR(balance_t, "const double"); + amounts.insert + (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + } +#endif + balance_t(const unsigned long val) { + TRACE_CTOR(balance_t, "const unsigned long"); + amounts.insert + (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + } + balance_t(const long val) { + TRACE_CTOR(balance_t, "const long"); + amounts.insert + (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + } + + explicit balance_t(const string& val) { + TRACE_CTOR(balance_t, "const string&"); + amount_t temp(val); + amounts.insert(amounts_map::value_type(&temp.commodity(), temp)); + } + explicit balance_t(const char * val) { + TRACE_CTOR(balance_t, "const char *"); + amount_t temp(val); + amounts.insert(amounts_map::value_type(&temp.commodity(), temp)); + } + + /** + * Destructor. Destroys all of the accumulated amounts in the + * balance. + */ + virtual ~balance_t() { + TRACE_DTOR(balance_t); + } + + /** + * Assignment and copy operators. An balance may be assigned or copied. + */ + balance_t(const balance_t& bal) : amounts(bal.amounts) { + TRACE_CTOR(balance_t, "copy"); + } + + balance_t& operator=(const balance_t& bal) { + if (this != &bal) + amounts = bal.amounts; + return *this; + } + balance_t& operator=(const amount_t& amt) { + if (amt.is_null()) + throw_(balance_error, + "Cannot assign an uninitialized amount to a balance"); + + amounts.clear(); + if (! amt.is_realzero()) + amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); + + return *this; + } + + balance_t& operator=(const string& str) { + return *this = balance_t(str); + } + balance_t& operator=(const char * str) { + return *this = balance_t(str); + } + + /** + * Comparison operators. Balances are fairly restrictive in terms + * of how they may be compared. They may be compared for equality + * or inequality, but this is all, since the concept of "less than" + * or "greater than" makes no sense when amounts of multiple + * commodities are involved. + * + * Balances may also be compared to amounts, in which case the sum + * of the balance must equal the amount exactly. + * + * If a comparison between balances is desired, the balances must + * first be rendered to value equivalent amounts using the `value' + * method, to determine a market valuation at some specific moment + * in time. + */ + 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.is_null()) + throw_(balance_error, + "Cannot compare a balance to an uninitialized amount"); + + if (amt.is_realzero()) + return amounts.empty(); + else + return amounts.size() == 1 && amounts.begin()->second == amt; + } + + template <typename T> + bool operator==(const T& val) const { + return *this == balance_t(val); + } + + /** + * Binary arithmetic operators. Balances support addition and + * subtraction of other balances or amounts, but multiplication and + * division are restricted to uncommoditized amounts only. + */ + balance_t& operator+=(const balance_t& bal); + balance_t& operator+=(const amount_t& amt); + balance_t& operator-=(const balance_t& bal); + balance_t& operator-=(const amount_t& amt); + + virtual balance_t& operator*=(const amount_t& amt); + +#ifdef HAVE_GDTOA + balance_t& operator*=(const double val) { + return *this *= amount_t(val); + } +#endif + balance_t& operator*=(const unsigned long val) { + return *this *= amount_t(val); + } + balance_t& operator*=(const long val) { + return *this *= amount_t(val); + } + + virtual balance_t& operator/=(const amount_t& amt); + +#ifdef HAVE_GDTOA + balance_t& operator/=(const double val) { + return *this /= amount_t(val); + } +#endif + balance_t& operator/=(const unsigned long val) { + return *this /= amount_t(val); + } + balance_t& operator/=(const long val) { + return *this /= amount_t(val); + } + + /** + * Unary arithmetic operators. There are only a few unary methods + * support on balance: + * + * negate(), also unary minus (- x), returns a balance all of whose + * component amounts have been negated. In order words, it inverts + * the sign of all member amounts. + * + * abs() returns a balance where no component amount is negative. + * + * reduce() reduces the values in a balance to their most basic + * commodity forms, for amounts that utilize "scaling commodities". + * For example, a balance of 1h and 1m after reduction will be + * 3660s. + * + * unreduce(), if used with amounts that use "scaling commodities", + * yields the most compact form greater than 1.0 for each component + * amount. That is, a balance of 10m and 1799s will unreduce to + * 39.98m. + * + * value(optional<datetime_t>) returns the total historical value for + * a balance -- the default moment returns a value based on the most + * recently known price -- based on the price history of its + * component commodities. See amount_t::value for an example. + * + * Further, for the sake of efficiency and avoiding temporary + * objects, the following methods support "in-place" variants act on + * the balance itself and return a reference to the result + * (`*this'): + * + * in_place_negate() + * in_place_reduce() + * in_place_unreduce() + */ + balance_t negate() const { + balance_t temp(*this); + temp.in_place_negate(); + return temp; + } + virtual balance_t& in_place_negate() { + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_negate(); + return *this; + } + balance_t operator-() const { + return negate(); + } + + balance_t abs() const { + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.abs(); + return temp; + } + + balance_t round() const { + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.round(); + return temp; + } + balance_t round(amount_t::precision_t prec) const { + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.round(prec); + return temp; + } + balance_t unround() const { + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.unround(); + return temp; + } + + balance_t reduce() const { + balance_t temp(*this); + temp.in_place_reduce(); + return temp; + } + virtual balance_t& in_place_reduce() { + // A temporary must be used here because reduction may cause + // multiple component amounts to collapse to the same commodity. + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.reduce(); + return *this = temp; + } + + balance_t unreduce() const { + balance_t temp(*this); + temp.in_place_unreduce(); + return temp; + } + virtual balance_t& in_place_unreduce() { + // A temporary must be used here because unreduction may cause + // multiple component amounts to collapse to the same commodity. + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.unreduce(); + return *this = temp; + } + + optional<balance_t> value(const optional<datetime_t>& moment = none) const; + + /** + * Truth tests. An balance may be truth test in two ways: + * + * is_nonzero(), or operator bool, returns true if a balance's + * display value is not zero. + * + * is_zero() returns true if an balance's display value is zero. + * Thus, a balance containing $0.0001 is considered zero if the + * current display precision for dollars is two decimal places. + * + * is_realzero() returns true if an balance's actual value is zero. + * Thus, a balance containing $0.0001 is never considered realzero. + * + * is_empty() returns true if a balance has no amounts within it. + * This can occur after a balance has been default initialized, or + * if the exact amount it contains is subsequently subtracted from + * it. + */ + operator bool() const { + foreach (const amounts_map::value_type& pair, amounts) + if (pair.second.is_nonzero()) + return true; + return false; + } + + bool is_zero() const { + if (is_empty()) + return true; + + foreach (const amounts_map::value_type& pair, amounts) + if (! pair.second.is_zero()) + return false; + return true; + } + + bool is_realzero() const { + if (is_empty()) + return true; + + foreach (const amounts_map::value_type& pair, amounts) + if (! pair.second.is_realzero()) + return false; + return true; + } + + bool is_empty() const { + return amounts.size() == 0; + } + + /** + * Conversion methods. A balance can be converted to an amount, but + * only if contains a single component amount. + */ + amount_t to_amount() const { + if (is_empty()) + throw_(balance_error, "Cannot convert an empty balance to an amount"); + else if (amounts.size() == 1) + return amounts.begin()->second; + else + throw_(balance_error, + "Cannot convert a balance with multiple commodities to an amount"); + } + + /** + * Commodity-related methods. Balances support two + * commodity-related methods: + * + * commodity_count() returns the number of different commodities + * stored in the balance. + * + * commodity_amount(optional<commodity_t>) returns an (optional) + * amount for the given commodity within the balance; if no + * commodity is specified, it returns the (optional) uncommoditized + * component of the balance. If no matching element can be found, + * boost::none is returned. + */ + std::size_t commodity_count() const { + return amounts.size(); + } + + optional<amount_t> + commodity_amount(const optional<const commodity_t&>& commodity = none) const; + + /** + * Annotated commodity methods. The amounts contained by a balance + * may use annotated commodities. The `strip_annotations' method + * will return a balance all of whose component amount have had + * their commodity annotations likewise stripped. See + * amount_t::strip_annotations for more details. + */ + 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; + + /** + * Printing methods. A balance may be output to a stream using the + * `print' method. There is also a global operator<< defined which + * simply calls print for a balance on the given stream. There is + * one form of the print method, which takes two required arguments + * and one arguments with a default value: + * + * print(ostream, int first_width, int latter_width) prints a + * balance to the given output stream, using each commodity's + * default display characteristics. The first_width parameter + * specifies the width that should be used for printing amounts + * (since they are likely to vary in width). The latter_width, if + * specified, gives the width to be used for each line after the + * first. This is useful when printing in a column which falls at + * the right-hand side of the screen. + * + * In addition to the width constraints, balances will also print + * with commodities in alphabetized order, regardless of the + * relative amounts of those commodities. There is no option to + * change this behavior. + */ + void print(std::ostream& out, const int first_width, + const int latter_width = -1) const; + + /** + * Debugging methods. There are two methods defined to help with + * debugging: + * + * dump(ostream) dumps a balance to an output stream. There is + * little different from print(), it simply surrounds the display + * value with a marker, for example "BALANCE($1.00, DM 12.00)". + * This code is used by other dumping code elsewhere in Ledger. + * + * valid() returns true if the amounts within the balance are valid. + */ + void dump(std::ostream& out) const { + out << "BALANCE("; + bool first = true; + foreach (const amounts_map::value_type& pair, amounts) { + if (first) + first = false; + else + out << ", "; + pair.second.print(out); + } + out << ")"; + } + + virtual bool valid() const { + foreach (const amounts_map::value_type& pair, amounts) + if (! pair.second.valid()) + return false; + return true; + } +}; + +inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) { + bal.print(out, 12); + return out; +} + +} // namespace ledger + +#endif // _BALANCE_H diff --git a/src/balpair.h b/src/balpair.h new file mode 100644 index 00000000..62da0810 --- /dev/null +++ b/src/balpair.h @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file balpair.h + * @author John Wiegley + * @date Sun May 20 19:11:58 2007 + * + * @brief Provides an abstraction around balance_t for tracking costs. + * + * When a transaction's amount is added to a balance, only the "value" + * of the amount is added -- not the associated cost of the + * transaction. To provide for this, the balance_pair_t type allows + * for adding amounts and costs simultaneously to a single balance. + * Both are tracked, and any time either the total amount balance or + * the total cost balance may be extracted. + * + * Note: By default, all balance-like operations operate on the amount + * balance, and not the cost. Also, the cost is entirely optional, in + * which case a balance_pair_t may be used as if it were a balance_t, + * from which is it derived. + */ +#ifndef _BALPAIR_H +#define _BARPAIR_H + +#include "balance.h" + +namespace ledger { + +class balance_pair_t + : public balance_t, + public equality_comparable<balance_pair_t, + equality_comparable<balance_pair_t, balance_t, + equality_comparable<balance_pair_t, amount_t, + equality_comparable<balance_pair_t, double, + equality_comparable<balance_pair_t, unsigned long, + equality_comparable<balance_pair_t, long, + additive<balance_pair_t, + additive<balance_pair_t, balance_t, + additive<balance_pair_t, amount_t, + additive<balance_pair_t, double, + additive<balance_pair_t, unsigned long, + additive<balance_pair_t, long, + multiplicative<balance_pair_t, amount_t, + multiplicative<balance_pair_t, balance_t, + multiplicative<balance_pair_t, double, + multiplicative<balance_pair_t, unsigned long, + multiplicative<balance_pair_t, long> > > > > > > > > > > > > > > > > +{ + /** + * The `cost' member of a balance pair tracks the cost associated + * with each transaction amount that is added. This member is + * optional, and if not cost-bearing transactions are added, it will + * remain uninitialized. + */ + optional<balance_t> cost; + + friend class value_t; + friend class entry_base_t; + +public: + /** + * Constructors. balance_pair_t supports identical forms of construction + * to balance_t. See balance_t for more information. + */ + balance_pair_t() { + TRACE_CTOR(balance_pair_t, ""); + } + balance_pair_t(const balance_t& bal) : balance_t(bal) { + TRACE_CTOR(balance_pair_t, "const balance_t&"); + } + balance_pair_t(const balance_t& bal, + const balance_t& cost_bal) + : balance_t(bal), cost(cost_bal) { + TRACE_CTOR(balance_pair_t, "const balance_t&, const balance_t&"); + } + balance_pair_t(const amount_t& amt) : balance_t(amt) { + TRACE_CTOR(balance_pair_t, "const amount_t&"); + } + balance_pair_t(const amount_t& amt, const amount_t& cost_amt) + : balance_t(amt), cost(cost_amt) { + TRACE_CTOR(balance_pair_t, "const amount_t&, const amount_t&"); + } +#ifdef HAVE_GDTOA + balance_pair_t(const double val) : balance_t(val) { + TRACE_CTOR(balance_pair_t, "const double"); + } +#endif + balance_pair_t(const unsigned long val) : balance_t(val) { + TRACE_CTOR(balance_pair_t, "const unsigned long"); + } + balance_pair_t(const long val) : balance_t(val) { + TRACE_CTOR(balance_pair_t, "const long"); + } + + explicit balance_pair_t(const string& val) : balance_t(val) { + TRACE_CTOR(balance_pair_t, "const string&"); + } + explicit balance_pair_t(const char * val) : balance_t(val) { + TRACE_CTOR(balance_pair_t, "const char *"); + } + + /** + * Destructor. + */ + virtual ~balance_pair_t() { + TRACE_DTOR(balance_pair_t); + } + + /** + * Assignment and copy operators. A balance pair may be assigned or + * copied, and assigned or copied from a balance. + */ + balance_pair_t(const balance_pair_t& bal_pair) + : balance_t(bal_pair), cost(bal_pair.cost) { + TRACE_CTOR(balance_pair_t, "copy"); + } + + balance_pair_t& operator=(const balance_pair_t& bal_pair) { + if (this != &bal_pair) { + balance_t::operator=(bal_pair.quantity()); + cost = bal_pair.cost; + } + return *this; + } + balance_pair_t& operator=(const balance_t& bal) { + balance_t::operator=(bal); + return *this; + } + balance_pair_t& operator=(const amount_t& amt) { + balance_t::operator=(amt); + return *this; + } + + balance_t& operator=(const string& str) { + return *this = balance_t(str); + } + balance_t& operator=(const char * str) { + return *this = balance_t(str); + } + + /** + * Binary arithmetic operators. Balances support addition and + * subtraction of other balance pairs, balances or amounts, but + * multiplication and division are restricted to uncommoditized + * amounts only. + * + * There is also an additional additive method called `add' which + * allows for adding an amount and an associated cost + * simultaneously. The signature is: + * add(amount_t amount, optional<amount_t> cost) + */ + balance_pair_t& operator+=(const balance_pair_t& bal_pair) { + balance_t::operator+=(bal_pair); + if (bal_pair.cost) { + if (! cost) + cost = quantity(); + *cost += *bal_pair.cost; + } + return *this; + } + balance_pair_t& operator-=(const balance_pair_t& bal_pair) { + balance_t::operator+=(bal_pair); + if (bal_pair.cost) { + if (! cost) + cost = quantity(); + *cost += *bal_pair.cost; + } + return *this; + } + + virtual balance_pair_t& operator*=(const amount_t& amt) { + balance_t::operator*=(amt); + if (cost) + *cost *= amt; + return *this; + } + + virtual balance_pair_t& operator/=(const amount_t& amt) { + balance_t::operator/=(amt); + if (cost) + *cost /= amt; + return *this; + } + + balance_pair_t& add(const amount_t& amt, + const optional<amount_t>& a_cost = none) { + if (a_cost && ! cost) + cost = quantity(); + + *this += amt; + + if (cost) + *cost += a_cost ? *a_cost : amt; + + return *this; + } + + /** + * Unary arithmetic operators. There are only a few unary methods + * supported for balance pairs (otherwise, the operators inherited + * from balance_t are used): + * + * abs() returns the absolute value of both the quantity and the + * cost of a balance pair. + * + * in_place_negate() negates all the amounts in both the quantity + * and the cost. + * + * in_place_reduce() reduces all the amounts in both the quantity + * and the cost. + * + * in_place_unreduce() unreduces all the amounts in both the + * quantity and the cost. + * + * quantity() returns the balance part of a balance. It is the same + * as doing a downcast<balance_t>(balance_pair). + */ + balance_pair_t abs() const { + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.abs(); + + if (cost) { + balance_t cost_temp; + foreach (const amounts_map::value_type& pair, amounts) + cost_temp += pair.second.abs(); + return balance_pair_t(temp, cost_temp); + } + return temp; + } + + virtual balance_t& in_place_negate() { + balance_t::in_place_negate(); + if (cost) + cost->in_place_negate(); + return *this; + } + + virtual balance_t& in_place_reduce() { + // A temporary must be used here because reduction may cause + // multiple component amounts to collapse to the same commodity. + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.reduce(); + + if (cost) { + balance_t cost_temp; + foreach (const amounts_map::value_type& pair, amounts) + cost_temp += pair.second.reduce(); + return *this = balance_pair_t(temp, cost_temp); + } + return *this = temp; + } + + virtual balance_t& in_place_unreduce() { + // A temporary must be used here because unreduction may cause + // multiple component amounts to collapse to the same commodity. + balance_t temp; + foreach (const amounts_map::value_type& pair, amounts) + temp += pair.second.unreduce(); + + if (cost) { + balance_t cost_temp; + foreach (const amounts_map::value_type& pair, amounts) + cost_temp += pair.second.unreduce(); + return *this = balance_pair_t(temp, cost_temp); + } + return *this = temp; + } + + balance_t& quantity() { + return *this; + } + const balance_t& quantity() const { + return *this; + } + + /** + * Truth tests. An balance pair may be truth tested by comparison + * to another balance pair, or by using one of the inherited + * operators from balance_t. + */ + bool operator==(const balance_pair_t& bal_pair) const { + if (quantity() != bal_pair.quantity()) + return false; + + if ((cost && ! bal_pair.cost) || + (! cost && bal_pair.cost)) + return false; + + if (*cost != *bal_pair.cost) + return false; + + return true; + } + + bool operator==(const balance_t& bal) const { + return balance_t::operator==(bal); + } + bool operator==(const amount_t& amt) const { + return balance_t::operator==(amt); + } + template <typename T> + bool operator==(const T& val) const { + return balance_t::operator==(val); + } + + /** + * Debugging methods. There is only one method specifically for + * balance pairs to help with debugging: + * + * valid() returns true if the balances within the balance pair are + * valid. + */ + bool valid() const { + if (! balance_t::valid()) + return false; + + if (cost && ! cost->valid()) + return false; + + return true; + } +}; + +} // namespace ledger + +#endif // _BALPAIR_H diff --git a/src/binary.cc b/src/binary.cc new file mode 100644 index 00000000..1744bb5d --- /dev/null +++ b/src/binary.cc @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "binary.h" + +namespace ledger { +namespace binary { + +void read_bool(std::istream& in, bool& num) +{ + read_guard(in, 0x2005); + unsigned char val; + in.read(reinterpret_cast<char *>(&val), sizeof(val)); + num = val == 1; + read_guard(in, 0x2006); +} + +void read_bool(const char *& data, bool& num) +{ + read_guard(data, 0x2005); + const unsigned char val = *reinterpret_cast<const unsigned char *>(data); + data += sizeof(unsigned char); + num = val == 1; + read_guard(data, 0x2006); +} + +void read_string(std::istream& in, string& str) +{ + read_guard(in, 0x3001); + + unsigned char len; + read_number_nocheck(in, len); + if (len == 0xff) { + unsigned short slen; + read_number_nocheck(in, slen); + char * buf = new char[slen + 1]; + in.read(buf, slen); + buf[slen] = '\0'; + str = buf; + checked_array_delete(buf); + } + else if (len) { + char buf[256]; + in.read(buf, len); + buf[len] = '\0'; + str = buf; + } else { + str = ""; + } + + read_guard(in, 0x3002); +} + +void read_string(const char *& data, string& str) +{ + read_guard(data, 0x3001); + + unsigned char len; + read_number_nocheck(data, len); + if (len == 0xff) { + unsigned short slen; + read_number_nocheck(data, slen); + str = string(data, slen); + data += slen; + } + else if (len) { + str = string(data, len); + data += len; + } + else { + str = ""; + } + + read_guard(data, 0x3002); +} + +void read_string(const char *& data, string * str) +{ + read_guard(data, 0x3001); + + unsigned char len; + read_number_nocheck(data, len); + if (len == 0xff) { + unsigned short slen; + read_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_guard(data, 0x3002); +} + +void read_string(std::istream& in, optional<string>& str) +{ + if (read_bool(in)) { + string temp; + read_string(in, temp); + str = temp; + } else { + str = none; + } +} + +void read_string(const char *& data, optional<string>& str) +{ + if (read_bool(data)) { + string temp; + read_string(data, temp); + str = temp; + } else { + str = none; + } +} + +void write_bool(std::ostream& out, bool num) +{ + write_guard(out, 0x2005); + unsigned char val = num ? 1 : 0; + out.write(reinterpret_cast<char *>(&val), sizeof(val)); + write_guard(out, 0x2006); +} + +void write_string(std::ostream& out, const string& str) +{ + write_guard(out, 0x3001); + + unsigned long len = str.length(); + if (len > 255) { + assert(len < 65536); + write_number_nocheck<unsigned char>(out, 0xff); + write_number_nocheck<unsigned short>(out, len); + } else { + write_number_nocheck<unsigned char>(out, len); + } + + if (len) + out.write(str.c_str(), len); + + write_guard(out, 0x3002); +} + +void write_string(std::ostream& out, const optional<string>& str) +{ + if (str) { + write_bool(out, true); + write_string(out, *str); + } else { + write_bool(out, false); + } +} + +} // namespace binary +} // namespace ledger diff --git a/src/binary.h b/src/binary.h new file mode 100644 index 00000000..8a2ef681 --- /dev/null +++ b/src/binary.h @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BINARY_H +#define BINARY_H + +#include "utils.h" + +namespace ledger { +namespace binary { + +template <typename T> +inline void read_number_nocheck(std::istream& in, T& num) { + in.read(reinterpret_cast<char *>(&num), sizeof(num)); +} + +template <typename T> +inline void read_number_nocheck(const char *& data, T& num) { + num = *reinterpret_cast<const T *>(data); + data += sizeof(T); +} + +template <typename T> +inline T read_number_nocheck(std::istream& in) { + T num; + read_number_nocheck(in, num); + return num; +} + +template <typename T> +inline T read_number_nocheck(const char *& data) { + T num; + read_number_nocheck(data, num); + return num; +} + +#if DEBUG_LEVEL >= ALPHA +#define read_guard(in, id) \ + if (read_number_nocheck<unsigned short>(in) != id) \ + assert(false); +#else +#define read_guard(in, id) +#endif + +template <typename T> +inline void read_number(std::istream& in, T& num) { + read_guard(in, 0x2003); + in.read(reinterpret_cast<char *>(&num), sizeof(num)); + read_guard(in, 0x2004); +} + +template <typename T> +inline void read_number(const char *& data, T& num) { + read_guard(data, 0x2003); + num = *reinterpret_cast<const T *>(data); + data += sizeof(T); + read_guard(data, 0x2004); +} + +template <typename T> +inline T read_number(std::istream& in) { + T num; + read_number(in, num); + return num; +} + +template <typename T> +inline T read_number(const char *& data) { + T num; + read_number(data, num); + return num; +} + +void read_bool(std::istream& in, bool& num); +void read_bool(const char *& data, bool& num); + +inline bool read_bool(std::istream& in) { + bool num; + read_bool(in, num); + return num; +} + +inline bool read_bool(const char *& data) { + bool num; + read_bool(data, num); + return num; +} + +template <typename T> +void read_long(std::istream& in, T& num) +{ + read_guard(in, 0x2001); + + unsigned char len; + read_number_nocheck(in, len); + + num = 0; + unsigned char temp; + if (len > 3) { + read_number_nocheck(in, temp); + num |= static_cast<unsigned long>(temp) << 24; + } + if (len > 2) { + read_number_nocheck(in, temp); + num |= static_cast<unsigned long>(temp) << 16; + } + if (len > 1) { + read_number_nocheck(in, temp); + num |= static_cast<unsigned long>(temp) << 8; + } + + read_number_nocheck(in, temp); + num |= static_cast<unsigned long>(temp); + + read_guard(in, 0x2002); +} + +template <typename T> +void read_long(const char *& data, T& num) +{ + read_guard(data, 0x2001); + + unsigned char len; + read_number_nocheck(data, len); + + num = 0; + unsigned char temp; + if (len > 3) { + read_number_nocheck(data, temp); + num |= static_cast<unsigned long>(temp) << 24; + } + if (len > 2) { + read_number_nocheck(data, temp); + num |= static_cast<unsigned long>(temp) << 16; + } + if (len > 1) { + read_number_nocheck(data, temp); + num |= static_cast<unsigned long>(temp) << 8; + } + + read_number_nocheck(data, temp); + num |= static_cast<unsigned long>(temp); + + read_guard(data, 0x2002); +} + +template <typename T> +inline T read_long(std::istream& in) { + T num; + read_long(in, num); + return num; +} + +template <typename T> +inline T read_long(const char *& data) { + T num; + read_long(data, num); + return num; +} + +void read_string(std::istream& in, string& str); +void read_string(const char *& data, string& str); +void read_string(const char *& data, string * str); + +inline string read_string(std::istream& in) { + string temp; + read_string(in, temp); + return temp; +} + +inline string read_string(const char *& data) { + string temp; + read_string(data, temp); + return temp; +} + +void read_string(std::istream& in, optional<string>& str); +void read_string(const char *& data, optional<string>& str); + + +template <typename T> +inline void write_number_nocheck(std::ostream& out, T num) { + out.write(reinterpret_cast<char *>(&num), sizeof(num)); +} + +#if DEBUG_LEVEL >= ALPHA +#define write_guard(out, id) \ + write_number_nocheck<unsigned short>(out, id) +#else +#define write_guard(in, id) +#endif + +template <typename T> +inline void write_number(std::ostream& out, T num) { + write_guard(out, 0x2003); + out.write(reinterpret_cast<char *>(&num), sizeof(num)); + write_guard(out, 0x2004); +} + +void write_bool(std::ostream& out, bool num); + +template <typename T> +void write_long(std::ostream& out, T num) +{ + write_guard(out, 0x2001); + + unsigned char len = 4; + if (static_cast<unsigned long>(num) < 0x00000100UL) + len = 1; + else if (static_cast<unsigned long>(num) < 0x00010000UL) + len = 2; + else if (static_cast<unsigned long>(num) < 0x01000000UL) + len = 3; + write_number_nocheck<unsigned char>(out, len); + + unsigned char temp; + if (len > 3) { + temp = (static_cast<unsigned long>(num) & 0xFF000000UL) >> 24; + write_number_nocheck(out, temp); + } + if (len > 2) { + temp = (static_cast<unsigned long>(num) & 0x00FF0000UL) >> 16; + write_number_nocheck(out, temp); + } + if (len > 1) { + temp = (static_cast<unsigned long>(num) & 0x0000FF00UL) >> 8; + write_number_nocheck(out, temp); + } + + temp = (static_cast<unsigned long>(num) & 0x000000FFUL); + write_number_nocheck(out, temp); + + write_guard(out, 0x2002); +} + +void write_string(std::ostream& out, const string& str); +void write_string(std::ostream& out, const optional<string>& str); + +} // namespace binary +} // namespace ledger + +#endif // BINARY_H diff --git a/src/cache.cc b/src/cache.cc new file mode 100644 index 00000000..d9ee7549 --- /dev/null +++ b/src/cache.cc @@ -0,0 +1,871 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cache.h" +#include "binary.h" + +namespace ledger { + +using namespace binary; + +#if 0 +void read_xact(const char *& data, xact_t * xact) +{ + read_number(data, xact->_date); + read_number(data, xact->_date_eff); + xact->account = accounts[read_long<account_t::ident_t>(data) - 1]; + + unsigned char flag = read_number<unsigned char>(data); + if (flag == 0) { + xact->amount.read(data); + } + else if (flag == 1) { + xact->amount.read(data); + xact->amount_expr = expr_t(); + xact->amount_expr->set_text(read_string(data)); + } + else { + xact->amount_expr = expr_t(); + xact->amount_expr->read(data); + } + + if (read_bool(data)) { + xact->cost = amount_t(); + xact->cost->read(data); + + xact->cost_expr = expr_t(); + xact->cost_expr->read(data); + } else { + xact->cost = none; + } + + read_number(data, xact->state); + xact->set_flags(read_number<xact_t::flags_t>(data)); + xact->add_flags(XACT_BULK_ALLOC); + read_string(data, xact->note); + + xact->beg_pos = read_long<unsigned long>(data); + read_long(data, xact->beg_line); + xact->end_pos = read_long<unsigned long>(data); + read_long(data, xact->end_line); + + xact->data = NULL; + + if (xact->amount_expr) + expr_t::compute_amount(xact->amount_expr.get(), xact->amount, xact); +} + +void write_xact(std::ostream& out, xact_t * xact, + bool ignore_calculated) +{ + write_number(out, xact->_date); + write_number(out, xact->_date_eff); + write_long(out, xact->account->ident); + + if (ignore_calculated && xact->has_flags(XACT_CALCULATED)) { + write_number<unsigned char>(out, 0); + amount_t().write(out); + } + else if (xact->amount_expr) { + write_number<unsigned char>(out, 2); + // jww (2008-07-30): Um, is this right? + xact->amount_expr->write(out); + } + else if (! xact->amount_expr->text().empty()) { + write_number<unsigned char>(out, 1); + xact->amount.write(out); + write_string(out, xact->amount_expr->text()); + } + else { + write_number<unsigned char>(out, 0); + xact->amount.write(out); + } + + if (xact->cost && + (! (ignore_calculated && xact->has_flags(XACT_CALCULATED)))) { + write_bool(out, true); + xact->cost->write(out); + // jww (2008-07-30): What if there is no cost expression? + xact->cost_expr->write(out); + } else { + write_bool(out, false); + } + + write_number(out, xact->state); + write_number(out, xact->flags()); + write_string(out, xact->note); + + write_long(out, xact->beg_pos); + write_long(out, xact->beg_line); + write_long(out, xact->end_pos); + write_long(out, xact->end_line); +} + +void read_entry_base(const char *& data, entry_base_t * entry, + xact_t *& xact_pool, bool& finalize) +{ + read_long(data, entry->src_idx); + // jww (2008-07-31): Use istream_pos_type + entry->beg_pos = read_long<unsigned long>(data); + read_long(data, entry->beg_line); + entry->end_pos = read_long<unsigned long>(data); + read_long(data, entry->end_line); + + bool ignore_calculated = read_bool(data); + + for (std::size_t i = 0, count = read_long<std::size_t>(data); + i < count; + i++) { + new(xact_pool) xact_t; + read_xact(data, xact_pool); + if (ignore_calculated && xact_pool->has_flags(XACT_CALCULATED)) + finalize = true; + entry->add_xact(xact_pool++); + } +} + +void write_entry_base(std::ostream& out, entry_base_t * entry) +{ + write_long(out, entry->src_idx); + write_long(out, entry->beg_pos); + write_long(out, entry->beg_line); + write_long(out, entry->end_pos); + write_long(out, entry->end_line); + + bool ignore_calculated = false; + foreach (transaction_t * xact, entry->xacts) + if (xact->amount_expr) { + ignore_calculated = true; + break; + } + + write_bool(out, ignore_calculated); + + write_long(out, entry->xacts.size()); + foreach (transaction_t * xact, entry->xacts) + write_xact(out, xact, ignore_calculated); +} + +void read_entry(const char *& data, entry_t * entry, + xact_t *& xact_pool, bool& finalize) +{ + read_entry_base(data, entry, xact_pool, finalize); + read_number(data, entry->_date); + read_number(data, entry->_date_eff); + read_string(data, entry->code); + read_string(data, entry->payee); +} + +void write_entry(std::ostream& out, entry_t * entry) +{ + write_entry_base(out, entry); + write_number(out, entry->_date); + write_number(out, entry->_date_eff); + write_string(out, entry->code); + write_string(out, entry->payee); +} + +void read_auto_entry(const char *& data, auto_entry_t * entry, + xact_t *& xact_pool) +{ + bool ignore; + read_entry_base(data, entry, xact_pool, ignore); + + expr_t expr; + expr.read(data); + entry->predicate = item_predicate<xact_t>(expr); +} + +void write_auto_entry(std::ostream& out, auto_entry_t * entry) +{ + write_entry_base(out, entry); + entry->predicate.predicate.write(out); +} + +void read_period_entry(const char *& data, period_entry_t * entry, + xact_t *& xact_pool, bool& finalize) +{ + read_entry_base(data, entry, xact_pool, finalize); + read_string(data, &entry->period_string); + std::istringstream stream(entry->period_string); + entry->period.parse(stream); +} + +void write_period_entry(std::ostream& out, period_entry_t * entry) +{ + write_entry_base(out, entry); + write_string(out, entry->period_string); +} + +commodity_t::base_t * read_commodity_base(const char *& data) +{ + string str; + + read_string(data, str); + + std::auto_ptr<commodity_t::base_t> commodity(new commodity_t::base_t(str)); + + read_string(data, str); + if (! str.empty()) + commodity->name = str; + + read_string(data, str); + if (! str.empty()) + commodity->note = str; + + read_number(data, commodity->precision); + unsigned long flags; + read_number(data, flags); + commodity->set_flags(flags); + + return commodity.release(); +} + +void write_commodity_base(std::ostream& out, commodity_t::base_t * commodity) +{ + // jww (2008-04-22): Not using this anymore? + //commodity->ident = ++base_commodity_index; + + write_string(out, commodity->symbol); + // jww (2008-04-22): What to do with optional members? + write_string(out, *commodity->name); + write_string(out, *commodity->note); + write_number(out, commodity->precision); + write_number(out, commodity->flags()); +} + +void read_commodity_base_extra(const char *& data, + commodity_t::ident_t ident) +{ + commodity_t::base_t * commodity = base_commodities[ident]; + + bool read_history = false; + // jww (2008-07-31): create a function read_size which does + // read_long<std::size_t>. Don't use read_number<std::size_t>, but it + // wastes too much space. + for (std::size_t i = 0, count = read_long<std::size_t>(data); + i < count; + i++) { + datetime_t when; + read_number(data, when); + amount_t amt; + amt.read(data); + + // 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 = commodity_t::history_t(); + commodity->history->prices.insert(commodity_t::base_t::history_pair(when, amt)); + + read_history = true; + } + if (read_history) + read_number(data, commodity->history->last_lookup); + + if (read_bool(data)) { + amount_t amt; + amt.read(data); + commodity->smaller = amount_t(amt); + } + + if (read_bool(data)) { + amount_t amt; + amt.read(data); + commodity->larger = amount_t(amt); + } +} + +void write_commodity_base_extra(std::ostream& out, + commodity_t::base_t * commodity) +{ +#if 0 + // jww (2008-04-22): What did bogus_time used to do? + if (commodity->history && commodity->history->bogus_time) + commodity->remove_price(commodity->history->bogus_time); +#endif + + if (! commodity->history) { + write_long<unsigned long>(out, 0); + } else { + write_long<unsigned long>(out, commodity->history->prices.size()); + foreach (commodity_t::history_map::value_type& pair, + commodity->history->prices) { + write_number(out, pair.first); + pair.second.write(out); + } + write_number(out, commodity->history->last_lookup); + } + + if (commodity->smaller) { + write_bool(out, true); + commodity->smaller->write(out); + } else { + write_bool(out, false); + } + + if (commodity->larger) { + write_bool(out, true); + commodity->larger->write(out); + } else { + write_bool(out, false); + } +} + +commodity_t * read_commodity(const char *& data) +{ + commodity_t::base_t * base = + base_commodities[read_long<commodity_t::ident_t>(data) - 1]; + + commodity_t * commodity = + new commodity_t(amount_t::current_pool, + shared_ptr<commodity_t::base_t>(base)); + + *commodities_next++ = commodity; + + string str; + read_string(data, str); + if (! str.empty()) + commodity->qualified_symbol = str; + commodity->annotated = false; + + return commodity; +} + +void write_commodity(std::ostream& out, commodity_t * commodity) +{ + commodity->ident = ++commodity_index; + + // jww (2008-04-22): Is this used anymore? + //write_long(out, commodity->base->ident); + // jww (2008-04-22): Optional! + write_string(out, *commodity->qualified_symbol); +} + +commodity_t * read_commodity_annotated(const char *& data) +{ + commodity_t * commodity = + commodities[read_long<commodity_t::ident_t>(data) - 1]; + + annotation_t details; + + string str; + read_string(data, str); + + // 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; + amt.read(data); + details.price = amt; + +#if 0 + // jww (2008-04-22): These are optional members! + read_number(data, details.date); + read_string(data, details.tag); +#endif + + annotated_commodity_t * ann_comm = + new annotated_commodity_t(commodity, details); + *commodities_next++ = ann_comm; + + if (! str.empty()) + ann_comm->qualified_symbol = str; + + return ann_comm; +} + +void write_commodity_annotated(std::ostream& out, + commodity_t * commodity) +{ + commodity->ident = ++commodity_index; + + // jww (2008-04-22): No longer needed? + //write_long(out, commodity->base->ident); + // jww (2008-04-22): Optional! + write_string(out, *commodity->qualified_symbol); + + annotated_commodity_t * ann_comm = + static_cast<annotated_commodity_t *>(commodity); + + // jww (2008-04-22): No longer needed? + //write_long(out, ann_comm->base->ident); + // jww (2008-04-22): Make a write_annotation_details function; and optional! + ann_comm->details.price->write(out); + ann_comm->details.date->write(out); + ann_comm->details.tag->write(out); +} + +inline +account_t * read_account(const char *& data, account_t * master = NULL) +{ + account_t * acct = new account_t(NULL); + + accounts[account_ident++] = acct; + + account_t::ident_t id; + read_long(data, id); // parent id + if (id == 0xffffffff) + acct->parent = NULL; + else + acct->parent = accounts[id - 1]; + + read_string(data, acct->name); + read_string(data, acct->note); + read_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) { + checked_delete(acct); + acct = master; + } + + for (std::size_t i = 0, count = read_long<std::size_t>(data); + i < count; + i++) { + account_t * child = read_account(data); + child->parent = acct; + assert(acct != child); + acct->add_account(child); + } + + return acct; +} + +namespace { + inline account_t::ident_t count_accounts(account_t * account) + { + account_t::ident_t count = 1; + + foreach (accounts_map::value_type& pair, account->accounts) + count += count_accounts(pair.second); + + return count; + } +} + +void write_account(std::ostream& out, account_t * account) +{ + account->ident = ++account_ident; + + if (account->parent) + write_long(out, account->parent->ident); + else + write_long<account_t::ident_t>(out, 0xffffffff); + + write_string(out, account->name); + write_string(out, account->note); + write_number(out, account->depth); + + write_number<std::size_t>(out, account->accounts.size()); + + foreach (accounts_map::value_type& pair, account->accounts) + write_account(out, pair.second); +} + +unsigned int read_journal(std::istream& in, + const path& file, + journal_t& journal, + account_t * master) +{ + using namespace binary; + + // Read in the files that participated in this journal, so that they + // can be checked for changes on reading. + + if (! file.empty()) { + for (unsigned short i = 0, count = read_number<unsigned short>(in); + i < count; + i++) { + path pathname = read_string(in); + std::time_t old_mtime; + read_number(in, old_mtime); + struct stat info; + // jww (2008-04-22): can this be done differently now? + stat(pathname.string().c_str(), &info); + if (std::difftime(info.st_mtime, old_mtime) > 0) + return 0; + + sources.push_back(pathname); + } + + // 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_bool(in)) { + string pathname; + read_string(in, pathname); + if (! price_db || + price_db->string() != std::string(pathname)) + return 0; + } + } + + // jww (2008-07-31): bind master to session.master + + if (read_bool(data)) + basket = accounts[read_long<account_t::ident_t>(data) - 1]; + + // Read in the entries and xacts + + for (std::size_t i = 0; i < count; i++) { + new(entry_pool) entry_t; + bool finalize = false; + read_entry(data, entry_pool, xact_pool, finalize); + entry_pool->journal = &journal; + if (finalize && ! entry_pool->finalize()) + continue; + entries.push_back(entry_pool++); + } + + for (std::size_t i = 0; i < auto_count; i++) { + auto_entry_t * auto_entry = new auto_entry_t; + read_auto_entry(data, auto_entry, xact_pool); + auto_entry->journal = &journal; + auto_entries.push_back(auto_entry); + } + + for (std::size_t i = 0; i < period_count; i++) { + period_entry_t * period_entry = new period_entry_t; + bool finalize = false; + read_period_entry(data, period_entry, xact_pool, finalize); + period_entry->journal = &journal; + if (finalize && ! period_entry->finalize()) + continue; + period_entries.push_back(period_entry); + } + + VERIFY(journal.valid()); + + return count; +} + +std::pair<std::size_t, std::size_t> +write_journal(std::ostream& out, const journal_t& journal) +{ + using namespace binary; + + // Write out the files that participated in this journal, so that + // they can be checked for changes on reading. + + if (sources.empty()) { + write_number<unsigned short>(out, 0); + } else { + write_number<unsigned short>(out, sources.size()); + foreach (const path& path, sources) { + write_string(out, path.string()); + struct stat info; + stat(path.string().c_str(), &info); + write_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. + if (price_db) { + write_bool(out, true); + write_string(out, price_db->string()); + } else { + write_bool(out, false); + } + } + + // Write out the basket accounts + + if (basket) { + write_bool(out, true); + write_long(out, basket->ident); + } else { + write_bool(out, false); + } + + // Write out the entries and xacts + + std::size_t this_entry_count = 0; + std::size_t this_xact_count = 0; + + foreach (entry_t * entry, entries) { + write_entry(out, entry); + + this_entry_count++; + this_xact_count += entry->xacts.size(); + } + + foreach (auto_entry_t * entry, auto_entries) { + write_auto_entry(out, entry); + + this_entry_count++; + this_xact_count += entry->xacts.size(); + } + + foreach (period_entry_t * entry, period_entries) { + write_period_entry(out, entry); + + this_entry_count++; + this_xact_count += entry->xacts.size(); + } + + return std::pair<std::size_t, std::size_t>(this_entry_count, + this_xact_count); +} + +std::size_t read_session(std::istream& in, + const path& file, + session_t& session) +{ + using namespace binary; + + // Read all of the data in at once, so that we're just dealing with + // a big data buffer. + + std::size_t data_size = read_number<std::size_t>(in); + + scoped_array<char> data_pool(new char[data_size]); + + in.read(data_pool, data_size); + + const char * data = data_pool.get(); + + // Read in the accounts + + accounts.resize(read_number<std::size_t>(data)); + account_ident = 0; + + if (session.master) + checked_delete(session.master); + session.master = read_account(data); + + // Allocate the memory needed for the entries, xacts and bigints in one + // large block, which is then chopped up and custom constructed as + // necessary. + + entry_count = read_number<std::size_t>(data); + auto_entry_count = read_number<std::size_t>(data); + period_entry_count = read_number<std::size_t>(data); + xact_count = read_number<std::size_t>(data); + bigints_count = read_number<std::size_t>(data); + +#define ENTRIES_SIZE (sizeof(entry_t) * entry_count) +#define XACTS_SIZE (sizeof(xact_t) * xact_count) +#define BIGINTS_SIZE (amount_t::sizeof_bigint_t() * bigints_count) + +#define ENTRIES_OFFSET 0 +#define XACTS_OFFSET ENTRIES_SIZE +#define BIGINTS_OFFSET (ENTRIES_SIZE + XACTS_SIZE) + + item_pool.reset(new char[ENTRIES_SIZE + XACTS_SIZE + BIGINTS_SIZE]); + + entry_pool = reinterpret_cast<entry_t *>(item_pool.get() + ENTRIES_OFFSET); + xact_pool = reinterpret_cast<xact_t *>(item_pool.get() + XACTS_OFFSET); + bigints = item_pool.get() + BIGINTS_OFFSET; + bigints_next = bigints; + bigint_ident = 0; + +#if 0 + // Read in the base commodities and the derived commodities + + base_commodity_count = read_number<std::size_t>(data); + base_commodities.resize(base_commodity_count); + + for (std::size_t i = 0; i < base_commodity_count; i++) { + commodity_t::base_t * base = read_commodity_base(data); + session.commodity_pool->commodities.push_back(base); + + std::pair<base_commodities_map::iterator, bool> result = + commodity_base_t::commodities.insert + (base_commodities_pair(commodity->symbol, commodity)); + if (! result.second) { + base_commodities_map::iterator c = + commodity_t::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_t::base_t::commodities.end()) + throw_(cache_error, "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) + checked_delete((*c).second->smaller); + (*c).second->smaller = commodity->smaller; + if ((*c).second->larger) + checked_delete((*c).second->larger); + (*c).second->larger = commodity->larger; + + *(base_commodities_next - 1) = (*c).second; + + checked_delete(commodity); + } + } + + commodity_count = read_number<std::size_t>(data); + commodities.resize(commodity_count); + + for (std::size_t i = 0; i < commodity_count; i++) { + commodity_t * commodity; + string mapping_key; + + if (! read_bool(data)) { + commodity = read_commodity(data); + mapping_key = commodity->base->symbol; + } else { + read_string(data, mapping_key); + commodity = read_commodity_annotated(data); + } + + session.commodity_pool->commodities.push_back(commodity); + + if (! result.second) { + commodities_map::iterator c = + commodity_t::commodities.find(mapping_key); + if (c == commodity_t::commodities.end()) + throw_(cache_error, "Failed to read commodity from cache: " + << commodity->symbol()); + + *(commodities_next - 1) = (*c).second; + checked_delete(commodity); + } + } + + for (std::size_t i = 0; i < base_commodity_count; i++) + read_commodity_base_extra(data, i); + + commodity_t::ident_t ident = read_number<commodity_t::ident_t>(data); + if (ident == 0xffffffff || ident == 0) + session.commodity_pool->default_commodity = NULL; + else + session.commodity_pool->default_commodity = commodities[ident - 1]; +#endif + + // Clean up and return the number of entries read + + accounts.clear(); + commodities.clear(); + + VERIFY(session.valid()); + + return count; +} + +void write_session(std::ostream& out, session_t& session) +{ + using namespace binary; + + write_number_nocheck(out, binary_magic_number); + write_number_nocheck(out, format_version); + + // This number gets patched at the end of the function + ostream_pos_type data_val = out.tellp(); + write_number<std::size_t>(out, 0); + + // Write out the accounts + + write_number<std::size_t>(out, count_accounts(session.master)); + write_account(out, session.master); + + // Write out the number of entries, xacts, and amounts + + write_number<std::size_t>(out, entries.size()); + write_number<std::size_t>(out, auto_entries.size()); + write_number<std::size_t>(out, period_entries.size()); + + // These two numbers get patched at the end of the function + ostream_pos_type xacts_val = out.tellp(); + write_number<std::size_t>(out, 0); + ostream_pos_type bigints_val = out.tellp(); + write_number<std::size_t>(out, 0); + + bigint_ident = 0; + +#if 0 + // Write out the commodities + // jww (2008-04-22): This whole section needs to be reworked + + write_number<std::size_t>(out, session.commodity_pool->commodities.size()); + write_number<std::size_t>(out, session.commodity_pool->commodities.size()); + + for (base_commodities_map::value_type pair, commodity_t::base_t::commodities) + write_commodity_base(out, pair.second); + + write_number<commodity_t::ident_t> + (out, commodity_t::commodities.size()); + + for (commodities_map::value_type pair, commodity_t::commodities) { + if (! pair.second->annotated) { + write_bool(out, false); + write_commodity(out, pair.second); + } + } + + for (commodities_map::value_type pair, commodity_t::commodities) { + if (pair.second->annotated) { + write_bool(out, true); + write_string(out, pair.first); // the mapping key + write_commodity_annotated(out, pair.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_t::base_t::commodities.begin(); + i != commodity_t::base_t::commodities.end(); + i++) + write_commodity_base_extra(out, (*i).second); + + if (commodity_t::default_commodity) + write_number(out, commodity_t::default_commodity->ident); + else + write_number<commodity_t::ident_t>(out, 0xffffffff); +#endif + + // Back-patch several counts which were not known beforehand + + out.seekp(data_val); + write_number<std::size_t>(out, (static_cast<std::size_t>(out.tellp()) - + static_cast<std::size_t>(data_val) - + sizeof(std::size_t))); + out.seekp(xacts_val); + write_number<std::size_t>(out, xact_count); + out.seekp(bigints_val); + write_number<std::size_t>(out, bigints_count); +} +#endif + +} // namespace ledger diff --git a/src/cache.h b/src/cache.h new file mode 100644 index 00000000..43123f9f --- /dev/null +++ b/src/cache.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CACHE_H +#define CACHE_H + +#include "utils.h" +#include "session.h" +#include "journal.h" +#include "account.h" + +namespace ledger { + +DECLARE_EXCEPTION(cache_error, std::runtime_error); + +class binary_cache_t +{ + static const unsigned long binary_magic_number = 0xFFEED765; +#if defined(DEBUG_ON) + static const unsigned long format_version = 0x0002060d; +#else + static const unsigned long format_version = 0x0002060c; +#endif + + scoped_array<char> item_pool; + + std::vector<account_t *> accounts; + account_t::ident_t account_ident; + + entry_t * entry_pool; // points into item_pool + std::size_t entry_count; + std::size_t auto_entry_count; + std::size_t period_entry_count; + + xact_t * xact_pool; // points into item_pool + std::size_t xact_count; + +#if 0 + commodity_base_t ** base_commodities; // allocated + commodity_base_t ** base_commodities_next; + uint_fast32_t base_commodity_index; + std::size_t base_commodity_count; +#endif + + commodity_t ** commodities; // allocated + commodity_t ** commodities_next; + uint_fast32_t commodity_ident; + std::size_t commodity_count; + + char * bigints; // points into item_pool + char * bigints_next; + uint_fast32_t bigints_index; + std::size_t bigints_count; + + void read_xact(const char *& data, xact_t * xact); + void write_xact(std::ostream& out, xact_t * xact, + bool ignore_calculated); + + void read_entry_base(const char *& data, entry_base_t * entry, + xact_t *& xact_pool, bool& finalize); + void write_entry_base(std::ostream& out, entry_base_t * entry); + void read_entry(const char *& data, entry_t * entry, + xact_t *& xact_pool, bool& finalize); + void write_entry(std::ostream& out, entry_t * entry); + void read_auto_entry(const char *& data, auto_entry_t * entry, + xact_t *& xact_pool); + void write_auto_entry(std::ostream& out, auto_entry_t * entry); + void read_period_entry(const char *& data, period_entry_t * entry, + xact_t *& xact_pool, bool& finalize); + void write_period_entry(std::ostream& out, period_entry_t * entry); + +#if 0 + commodity_t::base_t * read_commodity_base(const char *& data); + void write_commodity_base(std::ostream& out, commodity_t::base_t * commodity); + void read_commodity_base_extra(const char *& data, + commodity_t::ident_t ident); + void write_commodity_base_extra(std::ostream& out, + commodity_t::base_t * commodity); +#endif + + commodity_t * read_commodity(const char *& data); + void write_commodity(std::ostream& out, commodity_t * commodity); + commodity_t * read_commodity_annotated(const char *& data); + void write_commodity_annotated(std::ostream& out, + commodity_t * commodity); + + account_t * read_account(const char *& data, account_t * master = NULL); + void write_account(std::ostream& out); + + std::size_t read_journal(std::istream& in, + const path& file, + journal_t& journal, + account_t * master); + void write_journal(std::ostream& out, + const journal_t& journal); + +public: + binary_cache_t() + : account_ident(0), +#if 0 + base_commodity_ident(0), +#endif + commodity_ident(0) + { + } + + std::size_t read_session(std::istream& in, const path& file); + void write_session(std::ostream& out, session_t& session); + +}; + +} // namespace ledger + +#endif // CACHE_H diff --git a/src/commodity.cc b/src/commodity.cc new file mode 100644 index 00000000..a1c1e2c9 --- /dev/null +++ b/src/commodity.cc @@ -0,0 +1,667 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file commodity.cc + * @author John Wiegley + * @date Thu Apr 26 15:19:46 2007 + * + * @brief Types for dealing with commodities. + * + * This file defines member functions for flavors of commodity_t. + */ + +#include "amount.h" + +namespace ledger { + +void commodity_t::add_price(const datetime_t& date, + const amount_t& price) +{ + if (! base->history) + base->history = history_t(); + + history_map::iterator i = base->history->prices.find(date); + if (i != base->history->prices.end()) { + (*i).second = price; + } else { + std::pair<history_map::iterator, bool> result + = base->history->prices.insert(history_map::value_type(date, price)); + assert(result.second); + } +} + +bool commodity_t::remove_price(const datetime_t& date) +{ + if (base->history) { + history_map::size_type n = base->history->prices.erase(date); + if (n > 0) { + if (base->history->prices.empty()) + base->history.reset(); + return true; + } + } + return false; +} + +optional<amount_t> commodity_t::value(const optional<datetime_t>& moment) +{ + optional<datetime_t> age; + optional<amount_t> price; + + if (base->history) { + assert(base->history->prices.size() > 0); + + if (! moment) { + history_map::reverse_iterator r = base->history->prices.rbegin(); + age = (*r).first; + price = (*r).second; + } else { + history_map::iterator i = base->history->prices.lower_bound(*moment); + if (i == base->history->prices.end()) { + history_map::reverse_iterator r = base->history->prices.rbegin(); + age = (*r).first; + price = (*r).second; + } else { + age = (*i).first; + if (*moment != *age) { + if (i != base->history->prices.begin()) { + --i; + age = (*i).first; + price = (*i).second; + } else { + age = none; + } + } else { + price = (*i).second; + } + } + } + } + + if (! has_flags(COMMODITY_STYLE_NOMARKET) && parent().get_quote) { + if (optional<amount_t> quote = parent().get_quote + (*this, age, moment, + (base->history && base->history->prices.size() > 0 ? + (*base->history->prices.rbegin()).first : optional<datetime_t>()))) + return *quote; + } + return price; +} + +amount_t commodity_t::exchange(const amount_t& amount, + amount_t& final_cost, // out + amount_t& basis_cost, // out + const optional<amount_t>& total_cost_, + const optional<amount_t>& per_unit_cost_, + const optional<datetime_t>& moment, + const optional<string>& tag) +{ + // (assert (or (and total-cost (not per-unit-cost)) + // (and per-unit-cost (not total-cost)))) + + assert((total_cost_ && ! per_unit_cost_) || (per_unit_cost_ && ! total_cost_)); + + // (let* ((commodity (amount-commodity amount)) + // (current-annotation + // (and (annotated-commodity-p commodity) + // (commodity-annotation commodity))) + // (base-commodity (if (annotated-commodity-p commodity) + // (get-referent commodity) + // commodity)) + // (per-unit-cost (or per-unit-cost + // (divide total-cost amount))) + // (total-cost (or total-cost + // (multiply per-unit-cost amount)))) + + commodity_t& commodity(amount.commodity()); + + annotation_t * current_annotation = NULL; + if (commodity.annotated) + current_annotation = &as_annotated_commodity(commodity).details; + + commodity_t& base_commodity + (current_annotation ? + as_annotated_commodity(commodity).referent() : commodity); + + amount_t per_unit_cost(per_unit_cost_ ? + *per_unit_cost_ : *total_cost_ / amount); + final_cost = total_cost_ ? *total_cost_ : *per_unit_cost_ * amount; + + // Add a price history entry for this conversion if we know when it took + // place + + // (if (and moment (not (commodity-no-market-price-p base-commodity))) + // (add-price base-commodity per-unit-cost moment)) + + if (moment && ! commodity.has_flags(COMMODITY_STYLE_NOMARKET)) + base_commodity.add_price(*moment, per_unit_cost); + + // ;; returns: ANNOTATED-AMOUNT TOTAL-COST BASIS-COST + // (values (annotate-commodity + // amount + // (make-commodity-annotation :price per-unit-cost + // :date moment + // :tag tag)) + // total-cost + // (if current-annotation + // (multiply (annotation-price current-annotation) amount) + // total-cost)))) + + if (current_annotation && current_annotation->price) + basis_cost = *current_annotation->price * amount; + else + basis_cost = final_cost; + + amount_t ann_amount(amount); + ann_amount.annotate(annotation_t(per_unit_cost, moment->date(), tag)); + return ann_amount; +} + +commodity_t::operator bool() const +{ + return this != parent().null_commodity; +} + +bool commodity_t::symbol_needs_quotes(const string& symbol) +{ + foreach (char ch, symbol) + if (std::isspace(ch) || std::isdigit(ch) || ch == '-' || ch == '.') + return true; + + return false; +} + +void commodity_t::parse_symbol(std::istream& in, string& symbol) +{ + // Invalid commodity characters: + // SPACE, TAB, NEWLINE, RETURN + // 0-9 . , ; - + * / ^ ? : & | ! = + // < > { } [ ] ( ) @ + + static int invalid_chars[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + /* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, + /* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, + /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + char buf[256]; + char c = peek_next_nonws(in); + if (c == '"') { + in.get(c); + READ_INTO(in, buf, 255, c, c != '"'); + if (c == '"') + in.get(c); + else + throw_(amount_error, "Quoted commodity symbol lacks closing quote"); + } else { + READ_INTO(in, buf, 255, c, ! invalid_chars[static_cast<unsigned char>(c)]); + } + symbol = buf; +} + +void commodity_t::parse_symbol(char *& p, string& symbol) +{ + if (*p == '"') { + char * q = std::strchr(p + 1, '"'); + if (! q) + throw_(amount_error, "Quoted commodity symbol lacks closing quote"); + symbol = string(p + 1, 0, q - p - 1); + p = q + 2; + } else { + char * q = next_element(p); + symbol = p; + if (q) + p = q; + else + p += symbol.length(); + } + if (symbol.empty()) + throw_(amount_error, "Failed to parse commodity"); +} + +bool commodity_t::valid() const +{ + if (symbol().empty() && this != parent().null_commodity) { + DEBUG("ledger.validate", + "commodity_t: symbol().empty() && this != null_commodity"); + return false; + } + + if (annotated && ! base) { + DEBUG("ledger.validate", "commodity_t: annotated && ! base"); + return false; + } + + if (precision() > 16) { + DEBUG("ledger.validate", "commodity_t: precision() > 16"); + return false; + } + + return true; +} + +void annotation_t::parse(std::istream& in) +{ + do { + char buf[256]; + char c = peek_next_nonws(in); + if (c == '{') { + if (price) + throw_(amount_error, "Commodity specifies more than one price"); + + in.get(c); + READ_INTO(in, buf, 255, c, c != '}'); + if (c == '}') + in.get(c); + else + throw_(amount_error, "Commodity price lacks closing brace"); + + amount_t temp; + temp.parse(buf, AMOUNT_PARSE_NO_MIGRATE); + temp.in_place_reduce(); + + // Since this price will maintain its own precision, make sure + // it is at least as large as the base commodity, since the user + // may have only specified {$1} or something similar. + + if (temp.has_commodity() && + temp.precision() < temp.commodity().precision()) + temp = temp.round(); // no need to retain individual precision + + price = temp; + } + else if (c == '[') { + if (date) + throw_(amount_error, "Commodity specifies more than one date"); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ']'); + if (c == ']') + in.get(c); + else + throw_(amount_error, "Commodity date lacks closing bracket"); + + date = parse_date(buf); + } + else if (c == '(') { + if (tag) + throw_(amount_error, "Commodity specifies more than one tag"); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') + in.get(c); + else + throw_(amount_error, "Commodity tag lacks closing parenthesis"); + + tag = buf; + } + else { + break; + } + } while (true); + + DEBUG("amounts.commodities", + "Parsed commodity annotations: " << std::endl << *this); +} + +bool annotated_commodity_t::operator==(const commodity_t& comm) const +{ + // If the base commodities don't match, the game's up. + if (base != comm.base) + return false; + + assert(annotated); + if (! comm.annotated) + return false; + + if (details != as_annotated_commodity(comm).details) + return false; + + return true; +} + +commodity_t& +annotated_commodity_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) +{ + DEBUG("commodity.annotated.strip", + "Reducing commodity " << *this << std::endl + << " keep price " << _keep_price << " " + << " keep date " << _keep_date << " " + << " keep tag " << _keep_tag); + + commodity_t * new_comm; + + if ((_keep_price && details.price) || + (_keep_date && details.date) || + (_keep_tag && details.tag)) + { + new_comm = parent().find_or_create + (referent(), + annotation_t(_keep_price ? details.price : none, + _keep_date ? details.date : none, + _keep_tag ? details.tag : none)); + } else { + new_comm = parent().find_or_create(base_symbol()); + } + + assert(new_comm); + return *new_comm; +} + +void annotated_commodity_t::write_annotations(std::ostream& out, + const annotation_t& info) +{ + if (info.price) + out << " {" << *info.price << '}'; + + if (info.date) + out << " [" << *info.date << ']'; + + if (info.tag) + out << " (" << *info.tag << ')'; +} + +bool compare_amount_commodities::operator()(const amount_t * left, + const amount_t * right) const +{ + commodity_t& leftcomm(left->commodity()); + commodity_t& rightcomm(right->commodity()); + + int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol()); + if (cmp != 0) + return cmp < 0; + + if (! leftcomm.annotated) { + assert(rightcomm.annotated); + return true; + } + else if (! rightcomm.annotated) { + assert(leftcomm.annotated); + return false; + } + else { + annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm)); + annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm)); + + if (! aleftcomm.details.price && arightcomm.details.price) + return true; + if (aleftcomm.details.price && ! arightcomm.details.price) + return false; + + if (aleftcomm.details.price && arightcomm.details.price) { + amount_t leftprice(*aleftcomm.details.price); + leftprice.in_place_reduce(); + amount_t rightprice(*arightcomm.details.price); + rightprice.in_place_reduce(); + + if (leftprice.commodity() == rightprice.commodity()) { + return (leftprice - rightprice).sign() < 0; + } else { + // Since we have two different amounts, there's really no way + // to establish a true sorting order; we'll just do it based + // on the numerical values. + leftprice.clear_commodity(); + rightprice.clear_commodity(); + return (leftprice - rightprice).sign() < 0; + } + } + + if (! aleftcomm.details.date && arightcomm.details.date) + return true; + if (aleftcomm.details.date && ! arightcomm.details.date) + return false; + + if (aleftcomm.details.date && arightcomm.details.date) { + date_duration_t diff = *aleftcomm.details.date - *arightcomm.details.date; + return diff.is_negative(); + } + + if (! aleftcomm.details.tag && arightcomm.details.tag) + return true; + if (aleftcomm.details.tag && ! arightcomm.details.tag) + return false; + + if (aleftcomm.details.tag && arightcomm.details.tag) + return *aleftcomm.details.tag < *arightcomm.details.tag; + + assert(false); + return true; + } +} + +commodity_pool_t::commodity_pool_t() : default_commodity(NULL) +{ + TRACE_CTOR(commodity_pool_t, ""); + null_commodity = create(""); + null_commodity->add_flags(COMMODITY_STYLE_NOMARKET | + COMMODITY_STYLE_BUILTIN); +} + +commodity_t * commodity_pool_t::create(const string& symbol) +{ + shared_ptr<commodity_t::base_t> + base_commodity(new commodity_t::base_t(symbol)); + std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity)); + + DEBUG("amounts.commodities", "Creating base commodity " << symbol); + + // Create the "qualified symbol" version of this commodity's symbol + if (commodity_t::symbol_needs_quotes(symbol)) { + commodity->qualified_symbol = "\""; + *commodity->qualified_symbol += symbol; + *commodity->qualified_symbol += "\""; + } + + DEBUG("amounts.commodities", + "Creating commodity '" << commodity->symbol() << "'"); + + // Start out the new commodity with the default commodity's flags + // and precision, if one has been defined. +#if 0 + // jww (2007-05-02): This doesn't do anything currently! + if (default_commodity) + commodity->drop_flags(COMMODITY_STYLE_THOUSANDS | + COMMODITY_STYLE_NOMARKET); +#endif + + commodity->ident = commodities.size(); + + commodities.push_back(commodity.get()); + return commodity.release(); +} + +commodity_t * commodity_pool_t::find_or_create(const string& symbol) +{ + DEBUG("amounts.commodities", "Find-or-create commodity " << symbol); + + commodity_t * commodity = find(symbol); + if (commodity) + return commodity; + return create(symbol); +} + +commodity_t * commodity_pool_t::find(const string& symbol) +{ + DEBUG("amounts.commodities", "Find commodity " << symbol); + + typedef commodity_pool_t::commodities_t::nth_index<1>::type + commodities_by_name; + + commodities_by_name& name_index = commodities.get<1>(); + commodities_by_name::const_iterator i = name_index.find(symbol); + if (i != name_index.end()) + return *i; + else + return NULL; +} + +commodity_t * commodity_pool_t::find(const commodity_t::ident_t ident) +{ + DEBUG("amounts.commodities", "Find commodity by ident " << ident); + + typedef commodity_pool_t::commodities_t::nth_index<0>::type + commodities_by_ident; + + commodities_by_ident& ident_index = commodities.get<0>(); + return ident_index[ident]; +} + +commodity_t * +commodity_pool_t::create(const string& symbol, const annotation_t& details) +{ + commodity_t * new_comm = create(symbol); + if (! new_comm) + return NULL; + + if (details) + return find_or_create(*new_comm, details); + else + return new_comm; +} + +namespace { + string make_qualified_name(const commodity_t& comm, + const annotation_t& details) + { + assert(details); + + if (details.price && details.price->sign() < 0) + throw_(amount_error, "A commodity's price may not be negative"); + + std::ostringstream name; + comm.print(name); + annotated_commodity_t::write_annotations(name, details); + + DEBUG("amounts.commodities", "make_qualified_name for " + << comm.qualified_symbol << std::endl << details); + DEBUG("amounts.commodities", "qualified_name is " << name.str()); + + return name.str(); + } +} + +commodity_t * +commodity_pool_t::find(const string& symbol, const annotation_t& details) +{ + commodity_t * comm = find(symbol); + if (! comm) + return NULL; + + if (details) { + string name = make_qualified_name(*comm, details); + + if (commodity_t * ann_comm = find(name)) { + assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); + return ann_comm; + } + return NULL; + } else { + return comm; + } +} + +commodity_t * +commodity_pool_t::find_or_create(const string& symbol, + const annotation_t& details) +{ + commodity_t * comm = find(symbol); + if (! comm) + return NULL; + + if (details) + return find_or_create(*comm, details); + else + return comm; +} + +commodity_t * +commodity_pool_t::create(commodity_t& comm, + const annotation_t& details, + const string& mapping_key) +{ + assert(comm); + assert(details); + assert(! mapping_key.empty()); + + std::auto_ptr<commodity_t> commodity + (new annotated_commodity_t(&comm, details)); + + commodity->qualified_symbol = comm.symbol(); + assert(! commodity->qualified_symbol->empty()); + + DEBUG("amounts.commodities", "Creating annotated commodity " + << "symbol " << commodity->symbol() + << " key " << mapping_key << std::endl << details); + + // Add the fully annotated name to the map, so that this symbol may + // quickly be found again. + commodity->ident = commodities.size(); + commodity->mapping_key_ = mapping_key; + + commodities.push_back(commodity.get()); + return commodity.release(); +} + +commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, + const annotation_t& details) +{ + assert(comm); + assert(details); + + string name = make_qualified_name(comm, details); + assert(! name.empty()); + + if (commodity_t * ann_comm = find(name)) { + assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); + return ann_comm; + } + return create(comm, details, name); +} + +} // namespace ledger diff --git a/src/commodity.h b/src/commodity.h new file mode 100644 index 00000000..2f5ce258 --- /dev/null +++ b/src/commodity.h @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file commodity.h + * @author John Wiegley + * @date Wed Apr 18 22:05:53 2007 + * + * @brief Types for handling commodities. + * + * This file contains one of the most basic types in Ledger: + * commodity_t, and its annotated cousin, annotated_commodity_t. + */ +#ifndef _COMMODITY_H +#define _COMMODITY_H + +namespace ledger { + +class commodity_t + : public delegates_flags<>, + public equality_comparable1<commodity_t, noncopyable> +{ + friend class commodity_pool_t; + +public: + class base_t : public noncopyable, public supports_flags<> + { + base_t(); + + public: + typedef std::map<const datetime_t, amount_t> history_map; + typedef std::pair<const datetime_t, amount_t> history_pair; + + struct history_t { + history_map prices; + ptime last_lookup; + }; + +#define COMMODITY_STYLE_DEFAULTS 0x00 +#define COMMODITY_STYLE_SUFFIXED 0x01 +#define COMMODITY_STYLE_SEPARATED 0x02 +#define COMMODITY_STYLE_EUROPEAN 0x04 +#define COMMODITY_STYLE_THOUSANDS 0x08 +#define COMMODITY_STYLE_NOMARKET 0x10 +#define COMMODITY_STYLE_BUILTIN 0x20 + + string symbol; + amount_t::precision_t precision; + optional<string> name; + optional<string> note; + optional<history_t> history; + optional<amount_t> smaller; + optional<amount_t> larger; + + public: + explicit base_t(const string& _symbol) + : supports_flags<>(COMMODITY_STYLE_DEFAULTS), + symbol(_symbol), precision(0) { + TRACE_CTOR(base_t, "const string&"); + } + ~base_t() { + TRACE_DTOR(base_t); + } + }; + +public: + static bool symbol_needs_quotes(const string& symbol); + + typedef base_t::history_t history_t; + typedef base_t::history_map history_map; + typedef uint_least32_t ident_t; + + shared_ptr<base_t> base; + + commodity_pool_t * parent_; + ident_t ident; + optional<string> qualified_symbol; + optional<string> mapping_key_; + bool annotated; + +public: + explicit commodity_t(commodity_pool_t * _parent, + const shared_ptr<base_t>& _base) + : delegates_flags<>(*_base.get()), base(_base), + parent_(_parent), annotated(false) { + TRACE_CTOR(commodity_t, ""); + } + virtual ~commodity_t() { + TRACE_DTOR(commodity_t); + } + + operator bool() const; + + bool is_annotated() const { + return annotated; + } + + virtual bool operator==(const commodity_t& comm) const { + if (comm.annotated) + return comm == *this; + return base.get() == comm.base.get(); + } + + commodity_pool_t& parent() const { + return *parent_; + } + + string base_symbol() const { + return base->symbol; + } + string symbol() const { + return qualified_symbol ? *qualified_symbol : base_symbol(); + } + + string mapping_key() const { + if (mapping_key_) + return *mapping_key_; + else + return base_symbol(); + } + + optional<string> name() const { + return base->name; + } + void set_name(const optional<string>& arg = none) { + base->name = arg; + } + + optional<string> note() const { + return base->note; + } + void set_note(const optional<string>& arg = none) { + base->note = arg; + } + + amount_t::precision_t precision() const { + return base->precision; + } + void set_precision(amount_t::precision_t arg) { + base->precision = arg; + } + + optional<amount_t> smaller() const { + return base->smaller; + } + void set_smaller(const optional<amount_t>& arg = none) { + base->smaller = arg; + } + + optional<amount_t> larger() const { + return base->larger; + } + void set_larger(const optional<amount_t>& arg = none) { + base->larger = arg; + } + + optional<history_t> history() const { + return base->history; + } + + void add_price(const datetime_t& date, const amount_t& price); + bool remove_price(const datetime_t& date); + + optional<amount_t> value(const optional<datetime_t>& moment = none); + + static amount_t exchange(const amount_t& amount, + amount_t& final_cost, // out + amount_t& basis_cost, // out + const optional<amount_t>& total_cost, + const optional<amount_t>& per_unit_cost = none, + const optional<datetime_t>& moment = none, + const optional<string>& tag = none); + + static void parse_symbol(std::istream& in, string& symbol); + static void parse_symbol(char *& p, string& symbol); + static string parse_symbol(std::istream& in) { + string temp; + parse_symbol(in, temp); + return temp; + } + + void print(std::ostream& out) const { + out << symbol(); + } + + void read(std::istream& in); + void read(char *& data); + void write(std::ostream& out) const; + + bool valid() const; +}; + +inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { + comm.print(out); + return out; +} + +struct annotation_t : public equality_comparable<annotation_t> +{ + optional<amount_t> price; + optional<date_t> date; + optional<string> tag; + + explicit annotation_t(const optional<amount_t>& _price = none, + const optional<date_t>& _date = none, + const optional<string>& _tag = none) + : price(_price), date(_date), tag(_tag) { + TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string"); + } + annotation_t(const annotation_t& other) + : price(other.price), date(other.date), tag(other.tag) { + TRACE_CTOR(annotation_t, "copy"); + } + + ~annotation_t() { + TRACE_DTOR(annotation_t); + } + + operator bool() const { + return price || date || tag; + } + + bool operator==(const annotation_t& rhs) const { + return (price == rhs.price && + date == rhs.date && + tag == rhs.tag); + } + + void parse(std::istream& in); + void print(std::ostream& out) const { + out << "price " << (price ? price->to_string() : "NONE") << " " + << "date " << (date ? *date : date_t()) << " " + << "tag " << (tag ? *tag : "NONE"); + } + + bool valid() const { + assert(*this); + return true; + } +}; + +inline std::ostream& operator<<(std::ostream& out, const annotation_t& details) { + details.print(out); + return out; +} + +class annotated_commodity_t + : public commodity_t, + public equality_comparable<annotated_commodity_t, + equality_comparable2<annotated_commodity_t, commodity_t, + noncopyable> > +{ +public: + commodity_t * ptr; + annotation_t details; + + explicit annotated_commodity_t(commodity_t * _ptr, + const annotation_t& _details) + : commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) { + TRACE_CTOR(annotated_commodity_t, ""); + annotated = true; + } + virtual ~annotated_commodity_t() { + TRACE_DTOR(annotated_commodity_t); + } + + virtual bool operator==(const commodity_t& comm) const; + virtual bool operator==(const annotated_commodity_t& comm) const { + return *this == static_cast<const commodity_t&>(comm); + } + + commodity_t& referent() { + return *ptr; + } + const commodity_t& referent() const { + return *ptr; + } + + commodity_t& strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag); + + void write_annotations(std::ostream& out) const { + annotated_commodity_t::write_annotations(out, details); + } + + static void write_annotations(std::ostream& out, + const annotation_t& info); +}; + +inline annotated_commodity_t& +as_annotated_commodity(commodity_t& commodity) { + return downcast<annotated_commodity_t>(commodity); +} +inline const annotated_commodity_t& +as_annotated_commodity(const commodity_t& commodity) { + return downcast<const annotated_commodity_t>(commodity); +} + + +struct compare_amount_commodities { + bool operator()(const amount_t * left, const amount_t * right) const; +}; + +class commodity_pool_t : public noncopyable +{ + /** + * The commodities collection in commodity_pool_t maintains pointers + * to all the commodities which have ever been created by the user, + * whether explicitly by calling the create methods of + * commodity_pool_t, or implicitly by parsing a commoditized amount. + * + * The `commodities' member variable represents a collection which + * is indexed by two vertices: first, and ordered sequence of unique + * integer which identify commodities by a numerical identifier; and + * second, by a hashed set of symbolic names which reflect how the + * commodity was referred to by the user. + */ + typedef multi_index_container< + commodity_t *, + multi_index::indexed_by< + multi_index::random_access<>, + multi_index::hashed_unique< + multi_index::const_mem_fun<commodity_t, + string, &commodity_t::mapping_key> > + > + > commodities_t; + +public: + typedef commodity_pool_t::commodities_t::nth_index<0>::type + commodities_by_ident; + + commodities_t commodities; + + commodity_t * null_commodity; + commodity_t * default_commodity; + +private: + template<typename T> + struct first_initialized + { + typedef T result_type; + + template<typename InputIterator> + T operator()(InputIterator first, InputIterator last) const + { + for (; first != last; first++) + if (*first) + return *first; + return T(); + } + }; + +public: + boost::function<optional<amount_t> + (commodity_t& commodity, + const optional<datetime_t>& date, + const optional<datetime_t>& moment, + const optional<datetime_t>& last)> get_quote; + + explicit commodity_pool_t(); + + ~commodity_pool_t() { + TRACE_DTOR(commodity_pool_t); + commodities_by_ident& ident_index = commodities.get<0>(); + for (commodities_by_ident::iterator i = ident_index.begin(); + i != ident_index.end(); + i++) + checked_delete(*i); + } + + commodity_t * create(const string& symbol); + commodity_t * find(const string& name); + commodity_t * find(const commodity_t::ident_t ident); + commodity_t * find_or_create(const string& symbol); + + commodity_t * create(const string& symbol, const annotation_t& details); + commodity_t * find(const string& symbol, const annotation_t& details); + commodity_t * find_or_create(const string& symbol, + const annotation_t& details); + + commodity_t * create(commodity_t& comm, + const annotation_t& details, + const string& mapping_key); + + commodity_t * find_or_create(commodity_t& comm, + const annotation_t& details); +}; + +} // namespace ledger + +#endif // _COMMODITY_H diff --git a/src/compare.cc b/src/compare.cc new file mode 100644 index 00000000..1cbe7082 --- /dev/null +++ b/src/compare.cc @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "compare.h" + +namespace ledger { + +template <> +bool compare_items<xact_t>::operator()(xact_t * left, xact_t * right) +{ + assert(left); + assert(right); + + xact_t::xdata_t& lxdata(left->xdata()); + if (! lxdata.has_flags(XACT_EXT_SORT_CALC)) { + lxdata.sort_value = sort_order.calc(*left); + lxdata.sort_value.reduce(); + lxdata.add_flags(XACT_EXT_SORT_CALC); + } + + xact_t::xdata_t& rxdata(right->xdata()); + if (! rxdata.has_flags(XACT_EXT_SORT_CALC)) { + rxdata.sort_value = sort_order.calc(*right); + rxdata.sort_value.reduce(); + rxdata.add_flags(XACT_EXT_SORT_CALC); + } + + DEBUG("ledger.walk.compare_items_xact", + "lxdata.sort_value = " << lxdata.sort_value); + DEBUG("ledger.walk.compare_items_xact", + "rxdata.sort_value = " << rxdata.sort_value); + + return lxdata.sort_value < rxdata.sort_value; +} + +template <> +bool compare_items<account_t>::operator()(account_t * left, account_t * right) +{ + assert(left); + assert(right); + + account_t::xdata_t& lxdata(left->xdata()); + if (! lxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) { + lxdata.sort_value = sort_order.calc(*left); + lxdata.add_flags(ACCOUNT_EXT_SORT_CALC); + } + + account_t::xdata_t& rxdata(right->xdata()); + if (! rxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) { + rxdata.sort_value = sort_order.calc(*right); + rxdata.add_flags(ACCOUNT_EXT_SORT_CALC); + } + + return lxdata.sort_value < rxdata.sort_value; +} + +} // namespace ledger diff --git a/src/compare.h b/src/compare.h new file mode 100644 index 00000000..d86771ef --- /dev/null +++ b/src/compare.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _COMPARE_H +#define _COMPARE_H + +#include "expr.h" +#include "xact.h" +#include "account.h" + +namespace ledger { + +template <typename T> +class compare_items +{ + expr_t sort_order; + + compare_items(); + +public: + compare_items(const compare_items& other) : sort_order(other.sort_order) { + TRACE_CTOR(compare_items, "copy"); + } + compare_items(const expr_t& _sort_order) : sort_order(_sort_order) { + TRACE_CTOR(compare_items, "const value_expr&"); + } + ~compare_items() throw() { + TRACE_DTOR(compare_items); + } + bool operator()(T * left, T * right); +}; + +template <typename T> +bool compare_items<T>::operator()(T * left, T * right) +{ + assert(left); + assert(right); + return sort_order.calc(*left) < sort_order.calc(*right); +} + +template <> +bool compare_items<xact_t>::operator()(xact_t * left, xact_t * right); +template <> +bool compare_items<account_t>::operator()(account_t * left, + account_t * right); + +} // namespace ledger + +#endif // _COMPARE_H diff --git a/src/csv.cc b/src/csv.cc new file mode 100644 index 00000000..c075622e --- /dev/null +++ b/src/csv.cc @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "csv.h" + +namespace ledger { + +namespace { + inline void write_escaped_string(std::ostream& out, const string& xact) + { + out << "\""; + foreach (char ch, xact) + if (ch == '"') { + out << "\\"; + out << "\""; + } else { + out << ch; + } + out << "\""; + } +} + +void format_csv_xacts::operator()(xact_t& xact) +{ + if (! xact.has_xdata() || + ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) { + { + format_t fmt("%D"); + std::ostringstream str; +#if 0 + fmt.format(str, details_t(xact)); +#endif + write_escaped_string(out, str.str()); + } + out << ','; + + { + format_t fmt("%P"); + std::ostringstream str; +#if 0 + fmt.format(str, details_t(xact)); +#endif + write_escaped_string(out, str.str()); + } + out << ','; + + { + format_t fmt("%A"); + std::ostringstream str; +#if 0 + fmt.format(str, details_t(xact)); +#endif + write_escaped_string(out, str.str()); + } + out << ','; + + { + format_t fmt("%t"); + std::ostringstream str; +#if 0 + fmt.format(str, details_t(xact)); +#endif + write_escaped_string(out, str.str()); + } + out << ','; + + { + format_t fmt("%T"); + std::ostringstream str; +#if 0 + fmt.format(str, details_t(xact)); +#endif + write_escaped_string(out, str.str()); + } + out << ','; + + switch (xact.state) { + case xact_t::CLEARED: + write_escaped_string(out, "*"); + break; + case xact_t::PENDING: + write_escaped_string(out, "!"); + break; + default: { + xact_t::state_t state; + if (xact.entry->get_state(&state)) + switch (state) { + case xact_t::CLEARED: + write_escaped_string(out, "*"); + break; + case xact_t::PENDING: + write_escaped_string(out, "!"); + break; + default: + write_escaped_string(out, ""); + break; + } + } + } + out << ','; + + if (xact.entry->code) + write_escaped_string(out, *xact.entry->code); + out << ','; + + { + format_t fmt("%N"); + std::ostringstream str; +#if 0 + fmt.format(str, details_t(xact)); +#endif + write_escaped_string(out, str.str()); + } + out << '\n'; + + xact.xdata().add_flags(XACT_EXT_DISPLAYED); + } +} + +} // namespace ledger diff --git a/src/csv.h b/src/csv.h new file mode 100644 index 00000000..bef58ad2 --- /dev/null +++ b/src/csv.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CSV_H +#define _CSV_H + +#include "handler.h" +#include "format.h" + +namespace ledger { + +class format_csv_xacts : public item_handler<xact_t> +{ + format_csv_xacts(); + +protected: + std::ostream& out; + +public: + format_csv_xacts(std::ostream& _out) : out(_out) { + TRACE_CTOR(format_csv_xacts, "std::ostream&"); + } + ~format_csv_xacts() { + TRACE_DTOR(format_csv_xacts); + } + + virtual void flush() { + out.flush(); + } + virtual void operator()(xact_t& xact); +}; + +} // namespace ledger + +#endif // _REPORT_H diff --git a/src/derive.cc b/src/derive.cc new file mode 100644 index 00000000..dd9dc4ca --- /dev/null +++ b/src/derive.cc @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "derive.h" +#include "session.h" +#include "iterators.h" + +namespace ledger { + +entry_t * derive_new_entry(report_t& report, + strings_list::iterator i, + strings_list::iterator end) +{ + session_t& session(report.session); + + std::auto_ptr<entry_t> added(new entry_t); + + entry_t * matching = NULL; + + added->_date = parse_date(*i++); + if (i == end) + throw std::runtime_error("Too few arguments to 'entry'"); + + mask_t regexp(*i++); + + journals_iterator iter(session); + entries_list::reverse_iterator j; + + for (journal_t * journal = iter(); journal; journal = iter()) { + for (j = journal->entries.rbegin(); + j != journal->entries.rend(); + j++) { + if (regexp.match((*j)->payee)) { + matching = *j; + break; + } + } + if (matching) break; + } + + added->payee = matching ? matching->payee : regexp.expr.str(); + + if (! matching) { + account_t * acct; + if (i == end || ((*i)[0] == '-' || std::isdigit((*i)[0]))) { + acct = session.find_account("Expenses"); + } + else if (i != end) { + acct = session.find_account_re(*i); + if (! acct) + acct = session.find_account(*i); + assert(acct); + i++; + } + + if (i == end) { + added->add_xact(new xact_t(acct)); + } else { + xact_t * xact = new xact_t(acct, amount_t(*i++)); + added->add_xact(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. + + report.sum_all_accounts(); + + value_t total = acct->xdata().total; + if (total.is_type(value_t::AMOUNT)) + xact->amount.set_commodity(total.as_amount().commodity()); + } + } + + acct = NULL; + + if (i != end) { + if (! acct) + acct = session.find_account_re(*i); + if (! acct) + acct = session.find_account(*i); + } + + if (! acct) { + if (matching && matching->journal->basket) + acct = matching->journal->basket; + else + acct = session.find_account("Equity"); + } + + added->add_xact(new xact_t(acct)); + } + else if (i == end) { + // If no argument were given but the payee, assume the user wants + // to see the same xact as last time. + added->code = matching->code; + + foreach (xact_t * xact, matching->xacts) + added->add_xact(new xact_t(*xact)); + } + else if ((*i)[0] == '-' || std::isdigit((*i)[0])) { + xact_t * m_xact, * xact, * first; + m_xact = matching->xacts.front(); + + first = xact = new xact_t(m_xact->account, amount_t(*i++)); + added->add_xact(xact); + + if (! xact->amount.commodity()) + xact->amount.set_commodity(m_xact->amount.commodity()); + + m_xact = matching->xacts.back(); + + xact = new xact_t(m_xact->account, - first->amount); + added->add_xact(xact); + + if (i != end) { + account_t * acct = session.find_account_re(*i); + if (! acct) + acct = session.find_account(*i); + assert(acct); + added->xacts.back()->account = acct; + } + } + else { + account_t * draw_acct = NULL; + + while (i != end) { + string& re_pat(*i++); + account_t * acct = NULL; + amount_t * amt = NULL; + + mask_t acct_regex(re_pat); + + for (; j != matching->journal->entries.rend(); j++) + if (regexp.match((*j)->payee)) { + entry_t * entry = *j; + foreach (xact_t * xact, entry->xacts) + if (acct_regex.match(xact->account->fullname())) { + acct = xact->account; + amt = &xact->amount; + matching = entry; + goto found; + } + } + + found: + xact_t * xact; + if (i == end) { + if (amt) + xact = new xact_t(acct, *amt); + else + xact = new xact_t(acct); + } else { + amount_t amount(*i++); + + strings_list::iterator x = i; + if (i != end && ++x == end) { + draw_acct = session.find_account_re(*i); + if (! draw_acct) + draw_acct = session.find_account(*i); + i++; + } + + if (! acct) + acct = session.find_account_re(re_pat); + if (! acct) + acct = session.find_account(re_pat); + + xact = new xact_t(acct, amount); + if (! xact->amount.commodity()) { + if (amt) + xact->amount.set_commodity(amt->commodity()); + else if (amount_t::current_pool->default_commodity) + xact->amount.set_commodity(*amount_t::current_pool->default_commodity); + } + } + added->add_xact(xact); + } + + if (! draw_acct) { + assert(matching->xacts.back()->account); + draw_acct = matching->xacts.back()->account; + } + if (draw_acct) + added->add_xact(new xact_t(draw_acct)); + } + + if ((matching && + ! matching->journal->entry_finalize_hooks.run_hooks(*added, false)) || + ! added->finalize() || + (matching && + ! matching->journal->entry_finalize_hooks.run_hooks(*added, true))) + throw std::runtime_error("Failed to finalize derived entry (check commodities)"); + + return added.release(); +} + +} // namespace ledger diff --git a/src/derive.h b/src/derive.h new file mode 100644 index 00000000..5de86cc8 --- /dev/null +++ b/src/derive.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _DERIVE_H +#define _DERIVE_H + +#include "report.h" + +namespace ledger { + +entry_t * derive_new_entry(report_t& report, + strings_list::iterator begin, + strings_list::iterator end); + +} // namespace ledger + +#endif // _DERIVE_H diff --git a/src/emacs.cc b/src/emacs.cc new file mode 100644 index 00000000..cf787e75 --- /dev/null +++ b/src/emacs.cc @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "emacs.h" +#include "account.h" + +namespace ledger { + +void format_emacs_xacts::write_entry(entry_t& entry) +{ + int idx = entry.src_idx; + foreach (const path& path, entry.journal->sources) + if (! idx--) { + out << "\"" << path << "\" "; + break; + } + + out << (static_cast<unsigned long>(entry.beg_line) + 1) << " "; + + tm when = gregorian::to_tm(entry.date()); + std::time_t date = std::mktime(&when); // jww (2008-04-20): Is this GMT or local? + + out << "(" << (date / 65536) << " " << (date % 65536) << " 0) "; + + if (! entry.code) + out << "nil "; + else + out << "\"" << *entry.code << "\" "; + + if (entry.payee.empty()) + out << "nil"; + else + out << "\"" << entry.payee << "\""; + + out << "\n"; +} + +void format_emacs_xacts::operator()(xact_t& xact) +{ + if (! xact.has_xdata() || + ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) { + if (! last_entry) { + out << "(("; + write_entry(*xact.entry); + } + else if (xact.entry != last_entry) { + out << ")\n ("; + write_entry(*xact.entry); + } + else { + out << "\n"; + } + + out << " (" << (static_cast<unsigned long>(xact.beg_line) + 1) << " "; + out << "\"" << xact.reported_account()->fullname() << "\" \"" + << xact.amount << "\""; + + switch (xact.state) { + case xact_t::CLEARED: + out << " t"; + break; + case xact_t::PENDING: + out << " pending"; + break; + default: + out << " nil"; + break; + } + + if (xact.cost) + out << " \"" << *xact.cost << "\""; + if (xact.note) + out << " \"" << *xact.note << "\""; + out << ")"; + + last_entry = xact.entry; + + xact.xdata().add_flags(XACT_EXT_DISPLAYED); + } +} + +} // namespace ledger diff --git a/src/emacs.h b/src/emacs.h new file mode 100644 index 00000000..59b937f8 --- /dev/null +++ b/src/emacs.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _EMACS_H +#define _EMACS_H + +#include "handler.h" +#include "format.h" + +namespace ledger { + +class format_emacs_xacts : public item_handler<xact_t> +{ + format_emacs_xacts(); + +protected: + std::ostream& out; + entry_t * last_entry; + +public: + format_emacs_xacts(std::ostream& _out) + : out(_out), last_entry(NULL) { + TRACE_CTOR(format_emacs_xacts, "std::ostream&"); + } + ~format_emacs_xacts() { + TRACE_DTOR(format_emacs_xacts); + } + + virtual void write_entry(entry_t& entry); + virtual void flush() { + if (last_entry) + out << "))\n"; + out.flush(); + } + virtual void operator()(xact_t& xact); +}; + +} // namespace ledger + +#endif // _REPORT_H diff --git a/src/entry.cc b/src/entry.cc new file mode 100644 index 00000000..799de841 --- /dev/null +++ b/src/entry.cc @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "entry.h" +#include "journal.h" +#include "format.h" +#include "session.h" +#include "report.h" + +namespace ledger { + +entry_base_t::entry_base_t(const entry_base_t& e) + : supports_flags<>(), journal(NULL), + beg_pos(0), beg_line(0), end_pos(0), end_line(0) +{ + TRACE_CTOR(entry_base_t, "copy"); + xacts.insert(xacts.end(), e.xacts.begin(), e.xacts.end()); +} + +entry_base_t::~entry_base_t() +{ + TRACE_DTOR(entry_base_t); + + foreach (xact_t * xact, xacts) { + // If the transaction is a temporary, it will be destructed when the + // temporary is. If it's from a binary cache, we can safely destruct it + // but its memory will be deallocated with the cache. + if (! xact->has_flags(XACT_TEMP)) { + if (! xact->has_flags(XACT_IN_CACHE)) + checked_delete(xact); + else + xact->~xact_t(); + } + } +} + +void entry_base_t::add_xact(xact_t * xact) +{ + xacts.push_back(xact); +} + +bool entry_base_t::remove_xact(xact_t * xact) +{ + xacts.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. + + // (let ((balance 0) + // null-xact) + + value_t balance; + xact_t * null_xact = NULL; + + // (do-xacts (xact entry) + // (when (xact-must-balance-p xact) + // (let ((amt (xact-amount* xact))) + // (if amt + // (setf balance (add balance (or (xact-cost xact) amt))) + // (if null-xact + // (error "Only one xact with null amount allowed ~ + // per entry (beg ~S end ~S)" + // (item-position-begin-line (entry-position entry)) + // (item-position-end-line (entry-position entry))) + // (setf null-xact xact)))))) + // + + foreach (xact_t * xact, xacts) { + if (xact->must_balance()) { + amount_t& p(xact->cost ? *xact->cost : xact->amount); + if (! p.is_null()) { + if (balance.is_null()) + balance = p; + else + balance += p; + } else { + if (null_xact) + throw_(std::logic_error, + "Only one xact with null amount allowed per entry"); + else + null_xact = xact; + } + } + } + assert(balance.valid()); + + DEBUG("ledger.journal.finalize", "initial balance = " << balance); + + // If there is only one xact, balance against the default account if + // one has been set. + + // (when (= 1 (length (entry-xacts entry))) + // (if-let ((default-account + // (journal-default-account (entry-journal entry)))) + // (setf null-xact + // (make-xact :entry entry + // :status (xact-status + // (first (entry-xacts entry))) + // :account default-account + // :generatedp t)) + // (add-xact entry null-xact))) + + if (journal && journal->basket && xacts.size() == 1) { + // jww (2008-07-24): Need to make the rest of the code aware of what to do + // when it sees a generated xact. + null_xact = new xact_t(journal->basket, XACT_GENERATED); + null_xact->state = (*xacts.begin())->state; + add_xact(null_xact); + } + + if (null_xact != NULL) { + // If one xact has no value at all, its value will become the + // inverse of the rest. If multiple commodities are involved, multiple + // xacts are generated to balance them all. + + // (progn + // (if (balance-p balance) + // (let ((first t)) + // (dolist (amount (balance-amounts balance)) + // (if first + // (setf (xact-amount* null-xact) (negate amount) + // first nil) + // (add-xact + // entry + // (make-xact :entry entry + // :account (xact-account null-xact) + // :amount (negate amount) + // :generatedp t))))) + // (setf (xact-amount* null-xact) (negate balance) + // (xact-calculatedp null-xact) t)) + // + // (setf balance 0)) + + if (balance.is_balance()) { + bool first = true; + const balance_t& bal(balance.as_balance()); + foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) { + if (first) { + null_xact->amount = pair.second.negate(); + first = false; + } else { + add_xact(new xact_t(null_xact->account, pair.second.negate(), + XACT_GENERATED)); + } + } + } else { + null_xact->amount = balance.as_amount().negate(); + null_xact->add_flags(XACT_CALCULATED); + } + balance = NULL_VALUE; + + } + else if (balance.is_balance() && + balance.as_balance().amounts.size() == 2) { + // When an entry involves two different commodities (regardless of how + // many xacts there are) determine the conversion ratio by dividing + // the total value of one commodity by the total value of the other. This + // establishes the per-unit cost for this xact for both + // commodities. + + // (when (and (balance-p balance) + // (= 2 (balance-commodity-count balance))) + // (destructuring-bind (x y) (balance-amounts balance) + // (let ((a-commodity (amount-commodity x)) + // (per-unit-cost (value-abs (divide x y)))) + // (do-xacts (xact entry) + // (let ((amount (xact-amount* xact))) + // (unless (or (xact-cost xact) + // (not (xact-must-balance-p xact)) + // (commodity-equal (amount-commodity amount) + // a-commodity)) + // (setf balance (subtract balance amount) + // (xact-cost xact) (multiply per-unit-cost amount) + // balance (add balance (xact-cost xact)))))))))) + + const balance_t& bal(balance.as_balance()); + + balance_t::amounts_map::const_iterator a = bal.amounts.begin(); + + const amount_t& x((*a++).second); + const amount_t& y((*a++).second); + + if (! y.is_realzero()) { + amount_t per_unit_cost = (x / y).abs(); + + commodity_t& comm(x.commodity()); + + foreach (xact_t * xact, xacts) { + const amount_t& x_amt(xact->amount); + + if (! (xact->cost || + ! xact->must_balance() || + x_amt.commodity() == comm)) { + DEBUG("ledger.journal.finalize", "before operation 1 = " << balance); + balance -= x_amt; + DEBUG("ledger.journal.finalize", "after operation 1 = " << balance); + DEBUG("ledger.journal.finalize", "x_amt = " << x_amt); + DEBUG("ledger.journal.finalize", "per_unit_cost = " << per_unit_cost); + + xact->cost = per_unit_cost * x_amt; + DEBUG("ledger.journal.finalize", "*xact->cost = " << *xact->cost); + + balance += *xact->cost; + DEBUG("ledger.journal.finalize", "after operation 2 = " << balance); + } + + } + } + + DEBUG("ledger.journal.finalize", "resolved balance = " << balance); + } + + // Now that the xact list has its final form, calculate the balance + // once more in terms of total cost, accounting for any possible gain/loss + // amounts. + + // (do-xacts (xact entry) + // (when (xact-cost xact) + // (let ((amount (xact-amount* xact))) + // (assert (not (commodity-equal (amount-commodity amount) + // (amount-commodity (xact-cost xact))))) + // (multiple-value-bind (annotated-amount total-cost basis-cost) + // (exchange-commodity amount :total-cost (xact-cost xact) + // :moment (entry-date entry) + // :tag (entry-code entry)) + // (if (annotated-commodity-p (amount-commodity amount)) + // (if-let ((price (annotation-price + // (commodity-annotation + // (amount-commodity amount))))) + // (setf balance + // (add balance (subtract basis-cost total-cost)))) + // (setf (xact-amount* xact) annotated-amount)))))) + + foreach (xact_t * xact, xacts) { + if (xact->cost) { + const amount_t& x_amt(xact->amount); + + assert(x_amt.commodity() != xact->cost->commodity()); + + entry_t * entry = dynamic_cast<entry_t *>(this); + + // jww (2008-07-24): Pass the entry's code here if we can, as the + // auto-tag + amount_t final_cost; + amount_t basis_cost; + amount_t ann_amount = + commodity_t::exchange(x_amt, final_cost, basis_cost, xact->cost, none, + datetime_t(xact->actual_date(), + time_duration_t(0, 0, 0)), + entry ? entry->code : optional<string>()); + + if (xact->amount.annotated()) { + if (ann_amount.annotation().price) { + if (balance.is_null()) + balance = basis_cost - final_cost; + else + balance += basis_cost - final_cost; + } + } else { + xact->amount = ann_amount; + } + } + } + + DEBUG("ledger.journal.finalize", "final balance = " << balance); + + // (if (value-zerop balance) + // (prog1 + // entry + // (setf (entry-normalizedp entry) t)) + // (error "Entry does not balance (beg ~S end ~S); remaining balance is:~%~A" + // (item-position-begin-line (entry-position entry)) + // (item-position-end-line (entry-position entry)) + // (format-value balance :width 20))) + + if (! balance.is_null()) { + balance.round(); + if (! balance.is_zero()) { +#if 0 + 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), scope_t(), _date(e._date), _date_eff(e._date_eff), + code(e.code), payee(e.payee) +{ + TRACE_CTOR(entry_t, "copy"); + + foreach (xact_t * xact, xacts) + xact->entry = this; +} + +bool entry_t::get_state(xact_t::state_t * state) const +{ + bool first = true; + bool hetero = false; + + foreach (xact_t * xact, xacts) { + if (first) { + *state = xact->state; + first = false; + } + else if (*state != xact->state) { + hetero = true; + break; + } + } + + return ! hetero; +} + +void entry_t::add_xact(xact_t * xact) +{ + xact->entry = this; + entry_base_t::add_xact(xact); +} + +namespace { + value_t get_date(entry_t& entry) { + return entry.date(); + } + + value_t get_payee(entry_t& entry) { + return string_value(entry.payee); + } + + template <value_t (*Func)(entry_t&)> + value_t get_wrapper(call_scope_t& scope) { + return (*Func)(find_scope<entry_t>(scope)); + } +} + +expr_t::ptr_op_t entry_t::lookup(const string& name) +{ + switch (name[0]) { + case 'd': + if (name[1] == '\0' || name == "date") + return WRAP_FUNCTOR(get_wrapper<&get_date>); + break; + + case 'f': + if (name.find("fmt_") == 0) { + switch (name[4]) { + case 'D': + return WRAP_FUNCTOR(get_wrapper<&get_date>); + case 'P': + return WRAP_FUNCTOR(get_wrapper<&get_payee>); + } + } + break; + + case 'p': + if (name[1] == '\0' || name == "payee") + return WRAP_FUNCTOR(get_wrapper<&get_payee>); + break; + } + return journal->owner->current_report->lookup(name); +} + +bool entry_t::valid() const +{ + if (! is_valid(_date) || ! journal) { + DEBUG("ledger.validate", "entry_t: ! _date || ! journal"); + return false; + } + + foreach (xact_t * xact, xacts) + if (xact->entry != this || ! xact->valid()) { + DEBUG("ledger.validate", "entry_t: xact not valid"); + return false; + } + + return true; +} + +#if 0 +void entry_context::describe(std::ostream& out) const throw() +{ + if (! desc.empty()) + out << desc << std::endl; + + print_entry(out, entry, " "); +} +#endif + +void auto_entry_t::extend_entry(entry_base_t& entry, bool post) +{ + xacts_list initial_xacts(entry.xacts.begin(), + entry.xacts.end()); + + foreach (xact_t * initial_xact, initial_xacts) { + if (predicate(*initial_xact)) { + foreach (xact_t * xact, xacts) { + amount_t amt; + assert(xact->amount); + if (! xact->amount.commodity()) { + if (! post) + continue; + assert(initial_xact->amount); + amt = initial_xact->amount * xact->amount; + } else { + if (post) + continue; + amt = xact->amount; + } + + account_t * account = xact->account; + string fullname = account->fullname(); + assert(! fullname.empty()); + if (fullname == "$account" || fullname == "@account") + account = initial_xact->account; + + xact_t * new_xact + = new xact_t(account, amt, xact->flags() | XACT_AUTO); + + // Copy over details so that the resulting xact is a mirror of + // the automated entry's one. + new_xact->state = xact->state; + new_xact->_date = xact->_date; + new_xact->_date_eff = xact->_date_eff; + new_xact->note = xact->note; + new_xact->beg_pos = xact->beg_pos; + new_xact->beg_line = xact->beg_line; + new_xact->end_pos = xact->end_pos; + new_xact->end_line = xact->end_line; + + entry.add_xact(new_xact); + } + } + } +} + +void extend_entry_base(journal_t * journal, entry_base_t& base, bool post) +{ + foreach (auto_entry_t * entry, journal->auto_entries) + entry->extend_entry(base, post); +} + +} // namespace ledger diff --git a/src/entry.h b/src/entry.h new file mode 100644 index 00000000..2834942f --- /dev/null +++ b/src/entry.h @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _ENTRY_H +#define _ENTRY_H + +#include "xact.h" +#include "predicate.h" + +namespace ledger { + +class journal_t; + +class entry_base_t : public supports_flags<> +{ +public: +#define ENTRY_IN_CACHE 0x1 + + journal_t * journal; + string note; + unsigned long src_idx; + istream_pos_type beg_pos; + unsigned long beg_line; + istream_pos_type end_pos; + unsigned long end_line; + xacts_list xacts; + + 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); + + virtual ~entry_base_t(); + + bool operator==(const entry_base_t& entry) { + return this == &entry; + } + bool operator!=(const entry_base_t& entry) { + return ! (*this == entry); + } + + virtual void add_xact(xact_t * xact); + virtual bool remove_xact(xact_t * xact); + + virtual bool finalize(); + virtual bool valid() const = 0; +}; + +class entry_t : public entry_base_t, public scope_t +{ +public: + date_t _date; + optional<date_t> _date_eff; + optional<string> code; + string payee; + + entry_t() { + TRACE_CTOR(entry_t, ""); + } + entry_t(const entry_t& e); + + virtual ~entry_t() { + TRACE_DTOR(entry_t); + } + + date_t actual_date() const { + return _date; + } + date_t effective_date() const { + if (! _date_eff) + return _date; + return *_date_eff; + } + date_t date() const { + if (xact_t::use_effective_date) + return effective_date(); + else + return actual_date(); + } + + bool get_state(xact_t::state_t * state) const; + + virtual void add_xact(xact_t * xact); + + virtual expr_t::ptr_op_t lookup(const string& name); + + virtual bool valid() const; +}; + +struct entry_finalizer_t { + virtual ~entry_finalizer_t() {} + virtual bool operator()(entry_t& entry, bool post) = 0; +}; + +class auto_entry_t : public entry_base_t +{ +public: + item_predicate<xact_t> predicate; + + auto_entry_t() { + TRACE_CTOR(auto_entry_t, ""); + } + auto_entry_t(const auto_entry_t& other) + : entry_base_t(), predicate(other.predicate) { + TRACE_CTOR(auto_entry_t, "copy"); + } + 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(NULL) { + TRACE_CTOR(auto_entry_finalizer_t, ""); + } + auto_entry_finalizer_t(const auto_entry_finalizer_t& other) + : entry_finalizer_t(), journal(other.journal) { + TRACE_CTOR(auto_entry_finalizer_t, "copy"); + } + auto_entry_finalizer_t(journal_t * _journal) : journal(_journal) { + TRACE_CTOR(auto_entry_finalizer_t, "journal_t *"); + } + ~auto_entry_finalizer_t() throw() { + TRACE_DTOR(auto_entry_finalizer_t); + } + + 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 period_entry_t& e) + : entry_base_t(e), period(e.period), period_string(e.period_string) { + TRACE_CTOR(period_entry_t, "copy"); + } + period_entry_t(const string& _period) + : period(_period), period_string(_period) { + TRACE_CTOR(period_entry_t, "const string&"); + } + + virtual ~period_entry_t() throw() { + TRACE_DTOR(period_entry_t); + } + + virtual bool valid() const { + return period; + } +}; + +class func_finalizer_t : public entry_finalizer_t +{ + func_finalizer_t(); + +public: + typedef function<bool (entry_t& entry, bool post)> func_t; + + func_t func; + + func_finalizer_t(func_t _func) : func(_func) { + TRACE_CTOR(func_finalizer_t, "func_t"); + } + func_finalizer_t(const func_finalizer_t& other) : + entry_finalizer_t(), func(other.func) { + TRACE_CTOR(func_finalizer_t, "copy"); + } + ~func_finalizer_t() throw() { + TRACE_DTOR(func_finalizer_t); + } + + virtual bool operator()(entry_t& entry, bool post) { + return func(entry, post); + } +}; + +void extend_entry_base(journal_t * journal, entry_base_t& entry, bool post); + +inline bool auto_entry_finalizer_t::operator()(entry_t& entry, bool post) { + extend_entry_base(journal, entry, post); + return true; +} + +typedef std::list<entry_t *> entries_list; +typedef std::list<auto_entry_t *> auto_entries_list; +typedef std::list<period_entry_t *> period_entries_list; + +} // namespace ledger + +#endif // _ENTRY_H diff --git a/src/error.h b/src/error.h new file mode 100644 index 00000000..fafef08e --- /dev/null +++ b/src/error.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _ERROR_H +#define _ERROR_H + +namespace ledger { + +extern std::ostringstream _desc_buffer; + +template <typename T> +inline void throw_func(const string& message) { + _desc_buffer.str(""); + throw T(message); +} + +#define throw_(cls, msg) \ + ((_desc_buffer << msg), throw_func<cls>(_desc_buffer.str())) + +extern std::ostringstream _ctxt_buffer; + +#define add_error_context(msg) \ + ((static_cast<unsigned long>(_ctxt_buffer.tellp()) == 0) ? \ + (_ctxt_buffer << msg) : (_ctxt_buffer << std::endl << msg)) + +inline string error_context() { + string context = _ctxt_buffer.str(); + _ctxt_buffer.str(""); + return context; +} + +inline string file_context(const path& file, std::size_t line) { + std::ostringstream buf; + buf << "\"" << file << "\", line " << line << ": "; + return buf.str(); +} + +inline string line_context(const string& line, long pos) { + std::ostringstream buf; + buf << " " << line << std::endl << " "; + long idx = pos < 0 ? line.length() - 1 : pos; + for (int i = 0; i < idx; i++) + buf << " "; + buf << "^" << std::endl; + return buf.str(); +} + +#define DECLARE_EXCEPTION(name, kind) \ + class name : public kind { \ + public: \ + explicit name(const string& why) throw() : kind(why) {} \ + virtual ~name() throw() {} \ + } + +} // namespace ledger + +#endif // _ERROR_H diff --git a/src/expr.cc b/src/expr.cc new file mode 100644 index 00000000..37433179 --- /dev/null +++ b/src/expr.cc @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "expr.h" +#include "parser.h" +#include "op.h" +#include "scope.h" // jww (2008-08-01): not necessary + +namespace ledger { + +std::auto_ptr<expr_t::parser_t> expr_t::parser; + +expr_t::expr_t() : compiled(false) +{ + TRACE_CTOR(expr_t, ""); +} + +expr_t::expr_t(const expr_t& other) + : ptr(other.ptr), str(other.str), compiled(other.compiled) +{ + TRACE_CTOR(expr_t, "copy"); +} + +expr_t::expr_t(const string& _str, const unsigned int flags) + : str(_str), compiled(false) +{ + TRACE_CTOR(expr_t, "const string&"); + + if (! _str.empty()) + ptr = parser->parse(str, flags); +} + +expr_t::expr_t(std::istream& in, const unsigned int flags) + : compiled(false) +{ + TRACE_CTOR(expr_t, "std::istream&"); + + ptr = parser->parse(in, flags); +} + +expr_t::expr_t(const ptr_op_t& _ptr, const string& _str) + : ptr(_ptr), str(_str), compiled(false) +{ + TRACE_CTOR(expr_t, "const ptr_op_t&, const string&"); +} + +expr_t::~expr_t() throw() +{ + TRACE_DTOR(expr_t); +} + +expr_t& expr_t::operator=(const expr_t& _expr) +{ + if (this != &_expr) { + str = _expr.str; + ptr = _expr.ptr; + compiled = _expr.compiled; + } + return *this; +} + +void expr_t::parse(const string& _str, const unsigned int flags) +{ + if (! parser.get()) + throw_(parse_error, "Value expression parser not initialized"); + + str = _str; + ptr = parser->parse(str, flags); + compiled = false; +} + +void expr_t::parse(std::istream& in, const unsigned int flags) +{ + if (! parser.get()) + throw_(parse_error, "Value expression parser not initialized"); + + str = "<stream>"; + ptr = parser->parse(in, flags); + compiled = false; +} + +void expr_t::compile(scope_t& scope) +{ + if (ptr.get() && ! compiled) { + ptr = ptr->compile(scope); + compiled = true; + } +} + +value_t expr_t::calc(scope_t& scope) +{ + if (ptr.get()) { + if (! compiled) + compile(scope); + return ptr->calc(scope); + } + return NULL_VALUE; +} + +bool expr_t::is_constant() const +{ + assert(compiled); + return ptr.get() && ptr->is_value(); +} + +bool expr_t::is_function() const +{ + assert(compiled); + return ptr.get() && ptr->is_function(); +} + +value_t& expr_t::constant_value() +{ + assert(is_constant()); + return ptr->as_value_lval(); +} + +const value_t& expr_t::constant_value() const +{ + assert(is_constant()); + return ptr->as_value(); +} + +function_t& expr_t::get_function() +{ + assert(is_function()); + return ptr->as_function_lval(); +} + +value_t expr_t::eval(const string& _expr, scope_t& scope) +{ + return expr_t(_expr).calc(scope); +} + +void expr_t::print(std::ostream& out, scope_t& scope) const +{ + if (ptr) { + op_t::print_context_t context(scope); + ptr->print(out, context); + } +} + +void expr_t::dump(std::ostream& out) const +{ + if (ptr) ptr->dump(out, 0); +} + +void expr_t::read(const char *& data) +{ + if (ptr) ptr->read(data); +} + +void expr_t::write(std::ostream& out) const +{ + if (ptr) ptr->write(out); +} + +void expr_t::initialize() +{ + parser.reset(new expr_t::parser_t); +} + +void expr_t::shutdown() +{ + parser.reset(); +} + +std::ostream& operator<<(std::ostream& out, const expr_t& expr) { + // jww (2008-08-01): shouldn't be necessary + symbol_scope_t scope; + expr.print(out, scope); + return out; +} + +} // namespace ledger diff --git a/src/expr.h b/src/expr.h new file mode 100644 index 00000000..8c110ad9 --- /dev/null +++ b/src/expr.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _EXPR_H +#define _EXPR_H + +#include "value.h" + +namespace ledger { + +DECLARE_EXCEPTION(parse_error, std::runtime_error); +DECLARE_EXCEPTION(compile_error, std::runtime_error); +DECLARE_EXCEPTION(calc_error, std::runtime_error); +DECLARE_EXCEPTION(usage_error, std::runtime_error); + +class scope_t; +class call_scope_t; + +typedef function<value_t (call_scope_t&)> function_t; + +class expr_t +{ + struct token_t; + + class parser_t; + static std::auto_ptr<parser_t> parser; + +public: + class op_t; + typedef intrusive_ptr<op_t> ptr_op_t; + +private: + ptr_op_t ptr; + string str; + bool compiled; + + static void initialize(); + static void shutdown(); + + friend class session_t; + +public: + expr_t(); + expr_t(const expr_t& other); + expr_t(const ptr_op_t& _ptr, const string& _str = ""); + + expr_t(const string& _str, const unsigned int flags = 0); + expr_t(std::istream& in, const unsigned int flags = 0); + + virtual ~expr_t() throw(); + + expr_t& operator=(const expr_t& _expr); + expr_t& operator=(const string& _expr) { + parse(_expr); + return *this; + } + + operator bool() const throw() { + return ptr.get() != NULL; + } + string text() const throw() { + return str; + } + + // This has special use in the textual parser + void set_text(const string& txt) { + str = txt; + } + + void parse(const string& _str, const unsigned int flags = 0); + void parse(std::istream& in, const unsigned int flags = 0); + + void compile(scope_t& scope); + value_t calc(scope_t& scope); + value_t calc(scope_t& scope) const; + + bool is_constant() const; + bool is_function() const; + + value_t& constant_value(); + const value_t& constant_value() const; + + function_t& get_function(); + + void print(std::ostream& out, scope_t& scope) const; + void dump(std::ostream& out) const; + void read(const char *& data); + void write(std::ostream& out) const; + + static value_t eval(const string& _expr, scope_t& scope); +}; + +std::ostream& operator<<(std::ostream& out, const expr_t& expr); + +} // namespace ledger + +#endif // _EXPR_H diff --git a/src/fdstream.h b/src/fdstream.h new file mode 100644 index 00000000..8a06fba2 --- /dev/null +++ b/src/fdstream.h @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* 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 <istream> +#include <ostream> +#include <streambuf> +// for EOF: +#include <cstdio> +// for memmove(): +#include <cstring> + + +// low-level read and write functions +#ifdef _MSC_VER +# include <io.h> +#else +# include <unistd.h> +//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/filters.cc b/src/filters.cc new file mode 100644 index 00000000..e5455423 --- /dev/null +++ b/src/filters.cc @@ -0,0 +1,755 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "filters.h" +#include "iterators.h" +#include "compare.h" +#include "session.h" +#include "format.h" +#include "textual.h" + +namespace ledger { + +pass_down_xacts::pass_down_xacts(xact_handler_ptr handler, + xacts_iterator& iter) + : item_handler<xact_t>(handler) +{ + TRACE_CTOR(pass_down_xacts, "xact_handler_ptr, xacts_iterator"); + + for (xact_t * xact = iter(); xact; xact = iter()) + item_handler<xact_t>::operator()(*xact); +} + +void truncate_entries::flush() +{ + if (! xacts.size()) + return; + + entry_t * last_entry = (*xacts.begin())->entry; + + int l = 0; + foreach (xact_t * xact, xacts) + if (last_entry != xact->entry) { + l++; + last_entry = xact->entry; + } + l++; + + last_entry = (*xacts.begin())->entry; + + int i = 0; + foreach (xact_t * xact, xacts) { + if (last_entry != xact->entry) { + last_entry = xact->entry; + i++; + } + + bool print = false; + if (head_count) { + if (head_count > 0 && i < head_count) + print = true; + else if (head_count < 0 && i >= - head_count) + print = true; + } + + if (! print && tail_count) { + if (tail_count > 0 && l - i <= tail_count) + print = true; + else if (tail_count < 0 && l - i > - tail_count) + print = true; + } + + if (print) + item_handler<xact_t>::operator()(*xact); + } + xacts.clear(); + + item_handler<xact_t>::flush(); +} + +void set_account_value::operator()(xact_t& xact) +{ + account_t * acct = xact.reported_account(); + + account_t::xdata_t& xdata(acct->xdata()); + xact.add_to_value(xdata.value); + + xdata.count++; + if (xact.has_flags(XACT_VIRTUAL)) + xdata.virtuals++; + + item_handler<xact_t>::operator()(xact); +} + +void sort_xacts::post_accumulated_xacts() +{ + std::stable_sort(xacts.begin(), xacts.end(), + compare_items<xact_t>(sort_order)); + + foreach (xact_t * xact, xacts) { + xact->xdata().drop_flags(XACT_EXT_SORT_CALC); + item_handler<xact_t>::operator()(*xact); + } + + xacts.clear(); +} + +void calc_xacts::operator()(xact_t& xact) +{ + try { + xact_t::xdata_t& xdata(xact.xdata()); + + if (last_xact && last_xact->has_xdata()) { + if (xdata.total.is_null()) + xdata.total = last_xact->xdata().total; + else + xdata.total += last_xact->xdata().total; + xdata.index = last_xact->xdata().index + 1; + } else { + xdata.index = 0; + } + + if (! xdata.has_flags(XACT_EXT_NO_TOTAL)) + xact.add_to_value(xdata.total); + + item_handler<xact_t>::operator()(xact); + + last_xact = &xact; + } + catch (const std::exception& err) { + add_error_context("Calculating transaction at"); +#if 0 + add_error_context(xact_context(xact)); +#endif + throw err; + } +} + +void invert_xacts::operator()(xact_t& xact) +{ + if (xact.has_xdata() && + xact.xdata().has_flags(XACT_EXT_COMPOUND)) { + xact.xdata().value.negate(); + } else { + xact.amount.negate(); + if (xact.cost) + xact.cost->negate(); + } + + item_handler<xact_t>::operator()(xact); +} + + +static inline +void handle_value(const value_t& value, + account_t * account, + entry_t * entry, + unsigned int flags, + std::list<xact_t>& temps, + item_handler<xact_t>& handler, + const date_t& date = date_t(), + xacts_list * component_xacts = NULL) +{ + temps.push_back(xact_t(account)); + xact_t& xact(temps.back()); + xact.entry = entry; + xact.add_flags(XACT_TEMP); + entry->add_xact(&xact); + + // If there are component xacts to associate with this + // temporary, do so now. + + if (component_xacts) + xact.xdata().copy_component_xacts(*component_xacts); + + // If the account for this xact is all virtual, then report + // the xact as such. This allows subtotal reports to show + // "(Account)" for accounts that contain only virtual xacts. + + if (account && account->has_xdata()) + if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS)) { + xact.add_flags(XACT_VIRTUAL); + if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS)) + xact.add_flags(XACT_BALANCE); + } + + xact_t::xdata_t& xdata(xact.xdata()); + + if (is_valid(date)) + xdata.date = date; + + value_t temp(value); + + switch (value.type()) { + case value_t::BOOLEAN: + case value_t::DATETIME: + case value_t::DATE: + case value_t::INTEGER: + temp.cast(value_t::AMOUNT); + // fall through... + + case value_t::AMOUNT: + xact.amount = temp.as_amount(); + break; + + case value_t::BALANCE: + case value_t::BALANCE_PAIR: + xdata.value = temp; + flags |= XACT_EXT_COMPOUND; + break; + + default: + assert(false); // jww (2008-04-24): What to do here? + break; + } + + if (flags) + xdata.add_flags(flags); + + handler(xact); +} + +void collapse_xacts::report_subtotal() +{ + assert(count >= 1); + + if (count == 1) { + item_handler<xact_t>::operator()(*last_xact); + } else { + entry_temps.push_back(entry_t()); + entry_t& entry = entry_temps.back(); + entry.payee = last_entry->payee; + entry._date = last_entry->_date; + + handle_value(subtotal, &totals_account, last_entry, 0, xact_temps, + *handler); + } + + last_entry = NULL; + last_xact = NULL; + subtotal = 0L; + count = 0; +} + +void collapse_xacts::operator()(xact_t& xact) +{ + // If we've reached a new entry, report on the subtotal + // accumulated thus far. + + if (last_entry && last_entry != xact.entry && count > 0) + report_subtotal(); + + xact.add_to_value(subtotal); + count++; + + last_entry = xact.entry; + last_xact = &xact; +} + +void related_xacts::flush() +{ + if (xacts.size() > 0) { + foreach (xact_t * xact, xacts) { + if (xact->entry) { + foreach (xact_t * r_xact, xact->entry->xacts) { + xact_t::xdata_t& xdata(r_xact->xdata()); + if (! xdata.has_flags(XACT_EXT_HANDLED) && + (! xdata.has_flags(XACT_EXT_RECEIVED) ? + ! r_xact->has_flags(XACT_AUTO | XACT_VIRTUAL) : + also_matching)) { + xdata.add_flags(XACT_EXT_HANDLED); + item_handler<xact_t>::operator()(*r_xact); + } + } + } else { + // This code should only be reachable from the "output" + // command, since that is the only command which attempts to + // output auto or period entries. + xact_t::xdata_t& xdata(xact->xdata()); + if (! xdata.has_flags(XACT_EXT_HANDLED) && + ! xact->has_flags(XACT_AUTO)) { + xdata.add_flags(XACT_EXT_HANDLED); + item_handler<xact_t>::operator()(*xact); + } + } + } + } + + item_handler<xact_t>::flush(); +} + +void changed_value_xacts::output_diff(const date_t& date) +{ + value_t cur_bal; + + last_xact->xdata().date = date; +#if 0 + compute_total(cur_bal, details_t(*last_xact)); +#endif + cur_bal.round(); + +#if 0 + // jww (2008-04-24): What does this do? + last_xact->xdata().date = 0; +#endif + + if (value_t diff = cur_bal - last_balance) { + entry_temps.push_back(entry_t()); + entry_t& entry = entry_temps.back(); + entry.payee = "Commodities revalued"; + entry._date = date; + + handle_value(diff, NULL, &entry, XACT_EXT_NO_TOTAL, xact_temps, + *handler); + } +} + +void changed_value_xacts::operator()(xact_t& xact) +{ + if (last_xact) + output_diff(last_xact->reported_date()); + + if (changed_values_only) + xact.xdata().add_flags(XACT_EXT_DISPLAYED); + + item_handler<xact_t>::operator()(xact); + +#if 0 + compute_total(last_balance, details_t(xact)); +#endif + last_balance.round(); + + last_xact = &xact; +} + +void component_xacts::operator()(xact_t& xact) +{ + if (handler && pred(xact)) { + if (xact.has_xdata() && + xact.xdata().has_component_xacts()) +#if 0 + xact.xdata().walk_component_xacts(*handler); +#else + ; +#endif + else + (*handler)(xact); + } +} + +void subtotal_xacts::report_subtotal(const char * spec_fmt) +{ + std::ostringstream out_date; + if (! spec_fmt) { + string fmt = "- "; + fmt += output_date_format; + out_date << format_date(finish, string(fmt)); + } else { + out_date << format_date(finish, string(spec_fmt)); + } + + entry_temps.push_back(entry_t()); + entry_t& entry = entry_temps.back(); + entry.payee = out_date.str(); + entry._date = start; + + foreach (values_map::value_type& pair, values) + handle_value(pair.second.value, pair.second.account, &entry, 0, + xact_temps, *handler, finish, &pair.second.components); + + values.clear(); +} + +void subtotal_xacts::operator()(xact_t& xact) +{ + if (! is_valid(start) || xact.date() < start) + start = xact.date(); + if (! is_valid(finish) || xact.date() > finish) + finish = xact.date(); + + account_t * acct = xact.reported_account(); + assert(acct); + + values_map::iterator i = values.find(acct->fullname()); + if (i == values.end()) { + value_t temp; + xact.add_to_value(temp); + std::pair<values_map::iterator, bool> result + = values.insert(values_pair(acct->fullname(), acct_value_t(acct, temp))); + assert(result.second); + + if (remember_components) + (*result.first).second.components.push_back(&xact); + } else { + xact.add_to_value((*i).second.value); + + if (remember_components) + (*i).second.components.push_back(&xact); + } + + // If the account for this xact is all virtual, mark it as + // such, so that `handle_value' can show "(Account)" for accounts + // that contain only virtual xacts. + + if (! xact.has_flags(XACT_VIRTUAL)) + xact.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS); + else if (! xact.has_flags(XACT_BALANCE)) + xact.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS); +} + +void interval_xacts::report_subtotal(const date_t& date) +{ + assert(last_xact); + + start = interval.begin; + if (is_valid(date)) + finish = date - gregorian::days(1); + else + finish = last_xact->date(); + + subtotal_xacts::report_subtotal(); + + last_xact = NULL; +} + +void interval_xacts::operator()(xact_t& xact) +{ + const date_t& date(xact.date()); + + if ((is_valid(interval.begin) && date < interval.begin) || + (is_valid(interval.end) && date >= interval.end)) + return; + + if (interval) { + if (! started) { + if (! is_valid(interval.begin)) + interval.start(date); + start = interval.begin; + started = true; + } + + date_t quant = interval.increment(interval.begin); + if (date >= quant) { + if (last_xact) + report_subtotal(quant); + + date_t temp; + while (date >= (temp = interval.increment(quant))) { + if (quant == temp) + break; + quant = temp; + } + start = interval.begin = quant; + } + + subtotal_xacts::operator()(xact); + } else { + item_handler<xact_t>::operator()(xact); + } + + last_xact = &xact; +} + +by_payee_xacts::~by_payee_xacts() +{ + TRACE_DTOR(by_payee_xacts); + + foreach (payee_subtotals_map::value_type& pair, payee_subtotals) + checked_delete(pair.second); +} + +void by_payee_xacts::flush() +{ + foreach (payee_subtotals_map::value_type& pair, payee_subtotals) + pair.second->report_subtotal(pair.first.c_str()); + + item_handler<xact_t>::flush(); + + payee_subtotals.clear(); +} + +void by_payee_xacts::operator()(xact_t& xact) +{ + payee_subtotals_map::iterator i = payee_subtotals.find(xact.entry->payee); + if (i == payee_subtotals.end()) { + payee_subtotals_pair + temp(xact.entry->payee, + new subtotal_xacts(handler, remember_components)); + std::pair<payee_subtotals_map::iterator, bool> result + = payee_subtotals.insert(temp); + + assert(result.second); + if (! result.second) + return; + i = result.first; + } + + if (xact.date() > (*i).second->start) + (*i).second->start = xact.date(); + + (*(*i).second)(xact); +} + +void set_comm_as_payee::operator()(xact_t& xact) +{ + entry_temps.push_back(*xact.entry); + entry_t& entry = entry_temps.back(); + entry._date = xact.date(); + entry.code = xact.entry->code; + + if (xact.amount.commodity()) + entry.payee = xact.amount.commodity().symbol(); + else + entry.payee = "<none>"; + + xact_temps.push_back(xact); + xact_t& temp = xact_temps.back(); + temp.entry = &entry; + temp.state = xact.state; + temp.add_flags(XACT_TEMP); + + entry.add_xact(&temp); + + item_handler<xact_t>::operator()(temp); +} + +void set_code_as_payee::operator()(xact_t& xact) +{ + entry_temps.push_back(*xact.entry); + entry_t& entry = entry_temps.back(); + entry._date = xact.date(); + + if (xact.entry->code) + entry.payee = *xact.entry->code; + else + entry.payee = "<none>"; + + xact_temps.push_back(xact); + xact_t& temp = xact_temps.back(); + temp.entry = &entry; + temp.state = xact.state; + temp.add_flags(XACT_TEMP); + + entry.add_xact(&temp); + + item_handler<xact_t>::operator()(temp); +} + +void dow_xacts::flush() +{ + for (int i = 0; i < 7; i++) { + start = finish = date_t(); + foreach (xact_t * xact, days_of_the_week[i]) + subtotal_xacts::operator()(*xact); + subtotal_xacts::report_subtotal("%As"); + days_of_the_week[i].clear(); + } + + subtotal_xacts::flush(); +} + +void generate_xacts::add_period_entries + (period_entries_list& period_entries) +{ + foreach (period_entry_t * entry, period_entries) + foreach (xact_t * xact, entry->xacts) + add_xact(entry->period, *xact); +} + +void generate_xacts::add_xact(const interval_t& period, + xact_t& xact) +{ + pending_xacts.push_back(pending_xacts_pair(period, &xact)); +} + +void budget_xacts::report_budget_items(const date_t& date) +{ + if (pending_xacts.size() == 0) + return; + + bool reported; + do { + reported = false; + foreach (pending_xacts_list::value_type& pair, pending_xacts) { + date_t& begin = pair.first.begin; + if (! is_valid(begin)) { + pair.first.start(date); + begin = pair.first.begin; + } + + if (begin < date && + (! is_valid(pair.first.end) || begin < pair.first.end)) { + xact_t& xact = *pair.second; + + DEBUG("ledger.walk.budget", "Reporting budget for " + << xact.reported_account()->fullname()); + + entry_temps.push_back(entry_t()); + entry_t& entry = entry_temps.back(); + entry.payee = "Budget entry"; + entry._date = begin; + + xact_temps.push_back(xact); + xact_t& temp = xact_temps.back(); + temp.entry = &entry; + temp.add_flags(XACT_AUTO | XACT_TEMP); + temp.amount.negate(); + entry.add_xact(&temp); + + begin = pair.first.increment(begin); + + item_handler<xact_t>::operator()(temp); + + reported = true; + } + } + } while (reported); +} + +void budget_xacts::operator()(xact_t& xact) +{ + bool xact_in_budget = false; + + foreach (pending_xacts_list::value_type& pair, pending_xacts) + for (account_t * acct = xact.reported_account(); + acct; + acct = acct->parent) { + if (acct == (*pair.second).reported_account()) { + xact_in_budget = true; + // Report the xact as if it had occurred in the parent + // account. + if (xact.reported_account() != acct) + xact.xdata().account = acct; + goto handle; + } + } + + handle: + if (xact_in_budget && flags & BUDGET_BUDGETED) { + report_budget_items(xact.date()); + item_handler<xact_t>::operator()(xact); + } + else if (! xact_in_budget && flags & BUDGET_UNBUDGETED) { + item_handler<xact_t>::operator()(xact); + } +} + +void forecast_xacts::add_xact(const interval_t& period, xact_t& xact) +{ + generate_xacts::add_xact(period, xact); + + interval_t& i = pending_xacts.back().first; + if (! is_valid(i.begin)) { + i.start(current_date); + i.begin = i.increment(i.begin); + } else { + while (i.begin < current_date) + i.begin = i.increment(i.begin); + } +} + +void forecast_xacts::flush() +{ + xacts_list passed; + date_t last; + + while (pending_xacts.size() > 0) { + pending_xacts_list::iterator least = pending_xacts.begin(); + for (pending_xacts_list::iterator i = ++pending_xacts.begin(); + i != pending_xacts.end(); + i++) + if ((*i).first.begin < (*least).first.begin) + least = i; + + date_t& begin = (*least).first.begin; + + if (is_valid((*least).first.end) && begin >= (*least).first.end) { + pending_xacts.erase(least); + passed.remove((*least).second); + continue; + } + + xact_t& xact = *(*least).second; + + entry_temps.push_back(entry_t()); + entry_t& entry = entry_temps.back(); + entry.payee = "Forecast entry"; + entry._date = begin; + + xact_temps.push_back(xact); + xact_t& temp = xact_temps.back(); + temp.entry = &entry; + temp.add_flags(XACT_AUTO | XACT_TEMP); + entry.add_xact(&temp); + + date_t next = (*least).first.increment(begin); + if (next < begin || (is_valid(last) && (next - last).days() > 365 * 5)) + break; + begin = next; + + item_handler<xact_t>::operator()(temp); + + if (temp.has_xdata() && + temp.xdata().has_flags(XACT_EXT_MATCHES)) { + if (! pred(temp)) + break; + last = temp.date(); + passed.clear(); + } else { + bool found = false; + foreach (xact_t * x, passed) + if (x == &xact) { + found = true; + break; + } + + if (! found) { + passed.push_back(&xact); + if (passed.size() >= pending_xacts.size()) + break; + } + } + } + + item_handler<xact_t>::flush(); +} + +pass_down_accounts::pass_down_accounts(acct_handler_ptr handler, + accounts_iterator& iter) + : item_handler<account_t>(handler) +{ + TRACE_CTOR(pass_down_accounts, + "acct_handler_ptr, accounts_iterator"); + for (account_t * account = iter(); account; account = iter()) + item_handler<account_t>::operator()(*account); +} + +} // namespace ledger diff --git a/src/filters.h b/src/filters.h new file mode 100644 index 00000000..7f67cbd8 --- /dev/null +++ b/src/filters.h @@ -0,0 +1,702 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FILTERS_H +#define _FILTERS_H + +#include "handler.h" +#include "predicate.h" +#include "entry.h" + +namespace ledger { + +////////////////////////////////////////////////////////////////////// +// +// Transaction filters +// + +class ignore_xacts : public item_handler<xact_t> +{ +public: + virtual void operator()(xact_t&) {} +}; + +class clear_xact_xdata : public item_handler<xact_t> +{ +public: + virtual void operator()(xact_t& xact) { + xact.clear_xdata(); + } +}; + +class xacts_iterator; + +class pass_down_xacts : public item_handler<xact_t> +{ + pass_down_xacts(); + +public: + pass_down_xacts(xact_handler_ptr handler, xacts_iterator& iter); + + virtual ~pass_down_xacts() { + TRACE_DTOR(pass_down_xacts); + } +}; + +class push_to_xacts_list : public item_handler<xact_t> +{ + push_to_xacts_list(); + +public: + xacts_list& xacts; + + push_to_xacts_list(xacts_list& _xacts) : xacts(_xacts) { + TRACE_CTOR(push_to_xacts_list, "xacts_list&"); + } + virtual ~push_to_xacts_list() { + TRACE_DTOR(push_to_xacts_list); + } + + virtual void operator()(xact_t& xact) { + xacts.push_back(&xact); + } +}; + +class truncate_entries : public item_handler<xact_t> +{ + int head_count; + int tail_count; + + xacts_list xacts; + + truncate_entries(); + +public: + truncate_entries(xact_handler_ptr handler, + int _head_count, int _tail_count) + : item_handler<xact_t>(handler), + head_count(_head_count), tail_count(_tail_count) { + TRACE_CTOR(truncate_entries, "xact_handler_ptr, int, int"); + } + virtual ~truncate_entries() { + TRACE_DTOR(truncate_entries); + } + + virtual void flush(); + virtual void operator()(xact_t& xact) { + if (tail_count == 0 && head_count > 0 && + xacts.size() >= static_cast<unsigned int>(head_count)) + return; + xacts.push_back(&xact); + } +}; + +class set_account_value : public item_handler<xact_t> +{ +public: + set_account_value(xact_handler_ptr handler = xact_handler_ptr()) + : item_handler<xact_t>(handler) {} + + virtual void operator()(xact_t& xact); +}; + +class sort_xacts : public item_handler<xact_t> +{ + typedef std::deque<xact_t *> xacts_deque; + + xacts_deque xacts; + const expr_t sort_order; + + sort_xacts(); + +public: + sort_xacts(xact_handler_ptr handler, + const expr_t& _sort_order) + : item_handler<xact_t>(handler), + sort_order(_sort_order) { + TRACE_CTOR(sort_xacts, + "xact_handler_ptr, const value_expr&"); + } + sort_xacts(xact_handler_ptr handler, + const string& _sort_order) + : item_handler<xact_t>(handler), + sort_order(_sort_order) { + TRACE_CTOR(sort_xacts, + "xact_handler_ptr, const string&"); + } + virtual ~sort_xacts() { + TRACE_DTOR(sort_xacts); + } + + virtual void post_accumulated_xacts(); + + virtual void flush() { + post_accumulated_xacts(); + item_handler<xact_t>::flush(); + } + + virtual void operator()(xact_t& xact) { + xacts.push_back(&xact); + } +}; + +class sort_entries : public item_handler<xact_t> +{ + sort_xacts sorter; + entry_t * last_entry; + + sort_entries(); + +public: + sort_entries(xact_handler_ptr handler, + const expr_t& _sort_order) + : sorter(handler, _sort_order) { + TRACE_CTOR(sort_entries, + "xact_handler_ptr, const value_expr&"); + } + sort_entries(xact_handler_ptr handler, + const string& _sort_order) + : sorter(handler, _sort_order) { + TRACE_CTOR(sort_entries, + "xact_handler_ptr, const string&"); + } + virtual ~sort_entries() { + TRACE_DTOR(sort_entries); + } + + virtual void flush() { + sorter.flush(); + item_handler<xact_t>::flush(); + } + + virtual void operator()(xact_t& xact) { + if (last_entry && xact.entry != last_entry) + sorter.post_accumulated_xacts(); + + sorter(xact); + + last_entry = xact.entry; + } +}; + +class filter_xacts : public item_handler<xact_t> +{ + item_predicate<xact_t> pred; + + filter_xacts(); + +public: + filter_xacts(xact_handler_ptr handler, + const expr_t& predicate) + : item_handler<xact_t>(handler), pred(predicate) { + TRACE_CTOR(filter_xacts, + "xact_handler_ptr, const value_expr&"); + } + + filter_xacts(xact_handler_ptr handler, + const string& predicate) + : item_handler<xact_t>(handler), pred(predicate) { + TRACE_CTOR(filter_xacts, + "xact_handler_ptr, const string&"); + } + virtual ~filter_xacts() { + TRACE_DTOR(filter_xacts); + } + + virtual void operator()(xact_t& xact) { + if (pred(xact)) { + xact.xdata().add_flags(XACT_EXT_MATCHES); + (*handler)(xact); + } + } +}; + +class calc_xacts : public item_handler<xact_t> +{ + xact_t * last_xact; + + calc_xacts(); + +public: + calc_xacts(xact_handler_ptr handler) + : item_handler<xact_t>(handler), last_xact(NULL) { + TRACE_CTOR(calc_xacts, "xact_handler_ptr"); + } + virtual ~calc_xacts() { + TRACE_DTOR(calc_xacts); + } + + virtual void operator()(xact_t& xact); +}; + +class invert_xacts : public item_handler<xact_t> +{ + invert_xacts(); + +public: + invert_xacts(xact_handler_ptr handler) + : item_handler<xact_t>(handler) {} + + virtual void operator()(xact_t& xact); +}; + +inline void clear_entries_xacts(std::list<entry_t>& entries_list) { + foreach (entry_t& entry, entries_list) + entry.xacts.clear(); +} + +class collapse_xacts : public item_handler<xact_t> +{ + value_t subtotal; + unsigned int count; + entry_t * last_entry; + xact_t * last_xact; + account_t totals_account; + + std::list<entry_t> entry_temps; + std::list<xact_t> xact_temps; + + collapse_xacts(); + +public: + collapse_xacts(xact_handler_ptr handler) + : item_handler<xact_t>(handler), count(0), + last_entry(NULL), last_xact(NULL), + totals_account(NULL, "<Total>") { + TRACE_CTOR(collapse_xacts, "xact_handler_ptr"); + } + virtual ~collapse_xacts() { + TRACE_DTOR(collapse_xacts); + clear_entries_xacts(entry_temps); + } + + virtual void flush() { + if (subtotal) + report_subtotal(); + item_handler<xact_t>::flush(); + } + + void report_subtotal(); + + virtual void operator()(xact_t& xact); +}; + +class component_xacts : public item_handler<xact_t> +{ + item_predicate<xact_t> pred; + + component_xacts(); + +public: + component_xacts(xact_handler_ptr handler, + const expr_t& predicate) + : item_handler<xact_t>(handler), pred(predicate) { + TRACE_CTOR(component_xacts, + "xact_handler_ptr, const value_expr&"); + } + component_xacts(xact_handler_ptr handler, + const string& predicate) + : item_handler<xact_t>(handler), pred(predicate) { + TRACE_CTOR(component_xacts, + "xact_handler_ptr, const string&"); + } + virtual ~component_xacts() throw() { + TRACE_DTOR(component_xacts); + } + + virtual void operator()(xact_t& xact); +}; + +class related_xacts : public item_handler<xact_t> +{ + xacts_list xacts; + bool also_matching; + + related_xacts(); + +public: + related_xacts(xact_handler_ptr handler, + const bool _also_matching = false) + : item_handler<xact_t>(handler), + also_matching(_also_matching) { + TRACE_CTOR(related_xacts, + "xact_handler_ptr, const bool"); + } + virtual ~related_xacts() throw() { + TRACE_DTOR(related_xacts); + } + + virtual void flush(); + virtual void operator()(xact_t& xact) { + xact.xdata().add_flags(XACT_EXT_RECEIVED); + xacts.push_back(&xact); + } +}; + +class changed_value_xacts : public item_handler<xact_t> +{ + // This filter requires that calc_xacts be used at some point + // later in the chain. + + bool changed_values_only; + xact_t * last_xact; + value_t last_balance; + + std::list<entry_t> entry_temps; + std::list<xact_t> xact_temps; + + changed_value_xacts(); + +public: + changed_value_xacts(xact_handler_ptr handler, + bool _changed_values_only) + : item_handler<xact_t>(handler), + changed_values_only(_changed_values_only), last_xact(NULL) { + TRACE_CTOR(changed_value_xacts, + "xact_handler_ptr, bool"); + } + virtual ~changed_value_xacts() { + TRACE_DTOR(changed_value_xacts); + clear_entries_xacts(entry_temps); + } + + virtual void flush() { + if (last_xact) { + output_diff(current_date); + last_xact = NULL; + } + item_handler<xact_t>::flush(); + } + + void output_diff(const date_t& current); + + virtual void operator()(xact_t& xact); +}; + +class subtotal_xacts : public item_handler<xact_t> +{ + class acct_value_t + { + acct_value_t(); + + public: + account_t * account; + value_t value; + + xacts_list components; + + acct_value_t(account_t * a) : account(a) { + TRACE_CTOR(acct_value_t, "acount_t *"); + } + acct_value_t(account_t * a, value_t& v) : account(a), value(v) { + TRACE_CTOR(acct_value_t, "acount_t *, value_t&"); + } + acct_value_t(const acct_value_t& av) + : account(av.account), value(av.value), + components(av.components) { + TRACE_CTOR(acct_value_t, "copy"); + } + ~acct_value_t() throw() { + TRACE_DTOR(acct_value_t); + } + }; + + typedef std::map<string, acct_value_t> values_map; + typedef std::pair<string, acct_value_t> values_pair; + + subtotal_xacts(); + +protected: + values_map values; + bool remember_components; + + std::list<entry_t> entry_temps; + std::list<xact_t> xact_temps; + +public: + date_t start; + date_t finish; + + subtotal_xacts(xact_handler_ptr handler, + bool _remember_components = false) + : item_handler<xact_t>(handler), + remember_components(_remember_components) { + TRACE_CTOR(subtotal_xacts, + "xact_handler_ptr, bool"); + } + virtual ~subtotal_xacts() { + TRACE_DTOR(subtotal_xacts); + clear_entries_xacts(entry_temps); + } + + void report_subtotal(const char * spec_fmt = NULL); + + virtual void flush() { + if (values.size() > 0) + report_subtotal(); + item_handler<xact_t>::flush(); + } + virtual void operator()(xact_t& xact); +}; + +class interval_xacts : public subtotal_xacts +{ + interval_t interval; + xact_t * last_xact; + bool started; + + interval_xacts(); + +public: + interval_xacts(xact_handler_ptr _handler, + const interval_t& _interval, + bool remember_components = false) + : subtotal_xacts(_handler, remember_components), + interval(_interval), last_xact(NULL), started(false) { + TRACE_CTOR(interval_xacts, + "xact_handler_ptr, const interval_t&, bool"); + } + interval_xacts(xact_handler_ptr _handler, + const string& _interval, + bool remember_components = false) + : subtotal_xacts(_handler, remember_components), + interval(_interval), last_xact(NULL), started(false) { + TRACE_CTOR(interval_xacts, + "xact_handler_ptr, const string&, bool"); + } + virtual ~interval_xacts() throw() { + TRACE_DTOR(interval_xacts); + } + + void report_subtotal(const date_t& moment = date_t()); + + virtual void flush() { + if (last_xact) + report_subtotal(); + subtotal_xacts::flush(); + } + virtual void operator()(xact_t& xact); +}; + +class by_payee_xacts : public item_handler<xact_t> +{ + typedef std::map<string, subtotal_xacts *> payee_subtotals_map; + typedef std::pair<string, subtotal_xacts *> payee_subtotals_pair; + + payee_subtotals_map payee_subtotals; + bool remember_components; + + by_payee_xacts(); + + public: + by_payee_xacts(xact_handler_ptr handler, + bool _remember_components = false) + : item_handler<xact_t>(handler), + remember_components(_remember_components) { + TRACE_CTOR(by_payee_xacts, + "xact_handler_ptr, bool"); + } + virtual ~by_payee_xacts(); + + virtual void flush(); + virtual void operator()(xact_t& xact); +}; + +class set_comm_as_payee : public item_handler<xact_t> +{ + std::list<entry_t> entry_temps; + std::list<xact_t> xact_temps; + + set_comm_as_payee(); + +public: + set_comm_as_payee(xact_handler_ptr handler) + : item_handler<xact_t>(handler) { + TRACE_CTOR(set_comm_as_payee, "xact_handler_ptr"); + } + virtual ~set_comm_as_payee() { + TRACE_DTOR(set_comm_as_payee); + clear_entries_xacts(entry_temps); + } + + virtual void operator()(xact_t& xact); +}; + +class set_code_as_payee : public item_handler<xact_t> +{ + std::list<entry_t> entry_temps; + std::list<xact_t> xact_temps; + + set_code_as_payee(); + +public: + set_code_as_payee(xact_handler_ptr handler) + : item_handler<xact_t>(handler) { + TRACE_CTOR(set_code_as_payee, "xact_handler_ptr"); + } + virtual ~set_code_as_payee() { + TRACE_DTOR(set_code_as_payee); + clear_entries_xacts(entry_temps); + } + + virtual void operator()(xact_t& xact); +}; + +class dow_xacts : public subtotal_xacts +{ + xacts_list days_of_the_week[7]; + + dow_xacts(); + +public: + dow_xacts(xact_handler_ptr handler, + bool remember_components = false) + : subtotal_xacts(handler, remember_components) { + TRACE_CTOR(dow_xacts, "xact_handler_ptr, bool"); + } + virtual ~dow_xacts() throw() { + TRACE_DTOR(dow_xacts); + } + + virtual void flush(); + virtual void operator()(xact_t& xact) { + days_of_the_week[xact.date().day_of_week()].push_back(&xact); + } +}; + +class generate_xacts : public item_handler<xact_t> +{ + generate_xacts(); + +protected: + typedef std::pair<interval_t, xact_t *> pending_xacts_pair; + typedef std::list<pending_xacts_pair> pending_xacts_list; + + pending_xacts_list pending_xacts; + std::list<entry_t> entry_temps; + std::list<xact_t> xact_temps; + +public: + generate_xacts(xact_handler_ptr handler) + : item_handler<xact_t>(handler) { + TRACE_CTOR(dow_xacts, "xact_handler_ptr"); + } + + virtual ~generate_xacts() { + TRACE_DTOR(generate_xacts); + clear_entries_xacts(entry_temps); + } + + void add_period_entries(period_entries_list& period_entries); + + virtual void add_xact(const interval_t& period, xact_t& xact); +}; + +class budget_xacts : public generate_xacts +{ +#define BUDGET_NO_BUDGET 0x00 +#define BUDGET_BUDGETED 0x01 +#define BUDGET_UNBUDGETED 0x02 + + unsigned short flags; + + budget_xacts(); + +public: + budget_xacts(xact_handler_ptr handler, + unsigned long _flags = BUDGET_BUDGETED) + : generate_xacts(handler), flags(_flags) { + TRACE_CTOR(budget_xacts, + "xact_handler_ptr, unsigned long"); + } + virtual ~budget_xacts() throw() { + TRACE_DTOR(budget_xacts); + } + + void report_budget_items(const date_t& date); + + virtual void operator()(xact_t& xact); +}; + +class forecast_xacts : public generate_xacts +{ + item_predicate<xact_t> pred; + + public: + forecast_xacts(xact_handler_ptr handler, + const expr_t& predicate) + : generate_xacts(handler), pred(predicate) { + TRACE_CTOR(forecast_xacts, "xact_handler_ptr, const expr_t&"); + } + forecast_xacts(xact_handler_ptr handler, + const string& predicate) + : generate_xacts(handler), pred(predicate) { + TRACE_CTOR(forecast_xacts, "xact_handler_ptr, const string&"); + } + virtual ~forecast_xacts() throw() { + TRACE_DTOR(forecast_xacts); + } + + virtual void add_xact(const interval_t& period, + xact_t& xact); + virtual void flush(); +}; + +////////////////////////////////////////////////////////////////////// +// +// Account filters +// + +class clear_account_xdata : public item_handler<account_t> +{ +public: + virtual void operator()(account_t& acct) { + acct.clear_xdata(); + } +}; + +class accounts_iterator; + +class pass_down_accounts : public item_handler<account_t> +{ + pass_down_accounts(); + +public: + pass_down_accounts(acct_handler_ptr handler, accounts_iterator& iter); + + virtual ~pass_down_accounts() { + TRACE_DTOR(pass_down_accounts); + } +}; + +} // namespace ledger + +#endif // _FILTERS_H diff --git a/src/flags.h b/src/flags.h new file mode 100644 index 00000000..b75fdc21 --- /dev/null +++ b/src/flags.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FLAGS_H +#define _FLAGS_H + +template <typename T = boost::uint_least8_t> +class supports_flags +{ +public: + typedef T flags_t; + +protected: + flags_t flags_; + +public: + supports_flags() : flags_(0) { + TRACE_CTOR(supports_flags, ""); + } + supports_flags(const flags_t& arg) : flags_(arg) { + TRACE_CTOR(supports_flags, "copy"); + } + ~supports_flags() throw() { + TRACE_DTOR(supports_flags); + } + + flags_t flags() const { + return flags_; + } + bool has_flags(const flags_t arg) const { + return flags_ & arg; + } + + void set_flags(const flags_t arg) { + flags_ = arg; + } + void clear_flags() { + flags_ = 0; + } + void add_flags(const flags_t arg) { + flags_ |= arg; + } + void drop_flags(const flags_t arg) { + flags_ &= ~arg; + } +}; + +template <typename T = boost::uint_least8_t> +class delegates_flags : public boost::noncopyable +{ +public: + typedef T flags_t; + +protected: + supports_flags<T>& flags_; + +public: + delegates_flags() : flags_() { + TRACE_CTOR(delegates_flags, ""); + } + delegates_flags(supports_flags<T>& arg) : flags_(arg) { + TRACE_CTOR(delegates_flags, "const supports_flags<T>&"); + } + ~delegates_flags() throw() { + TRACE_DTOR(delegates_flags); + } + + flags_t flags() const { + return flags_.flags(); + } + bool has_flags(const flags_t arg) const { + return flags_.has_flags(arg); + } + + void set_flags(const flags_t arg) { + flags_.set_flags(arg); + } + void clear_flags() { + flags_.clear_flags(); + } + void add_flags(const flags_t arg) { + flags_.add_flags(arg); + } + void drop_flags(const flags_t arg) { + flags_.drop_flags(arg); + } +}; + +#endif // _FLAGS_H diff --git a/src/format.cc b/src/format.cc new file mode 100644 index 00000000..65dfce95 --- /dev/null +++ b/src/format.cc @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "format.h" +#include "account.h" + +namespace ledger { + +format_t::elision_style_t + format_t::elision_style = ABBREVIATE; +int format_t::abbrev_length = 2; + +bool format_t::ansi_codes = false; +bool format_t::ansi_invert = false; + +void format_t::element_t::dump(std::ostream& out) const +{ + out << "Element: "; + + switch (type) { + case STRING: out << " STRING"; break; + case EXPR: out << " EXPR"; break; + } + + out << " flags: " << int(flags); + out << " min: "; + out << std::right; + out.width(2); + out << int(min_width); + out << " max: "; + out << std::right; + out.width(2); + out << int(max_width); + + switch (type) { + case STRING: out << " str: '" << chars << "'" << std::endl; break; + case EXPR: out << " expr: " << expr << std::endl; break; + } +} + +namespace { + string partial_account_name(account_t& account) + { + string name; + + for (account_t * acct = &account; + acct && acct->parent; + acct = acct->parent) { + if (acct->has_xdata() && + acct->xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) + break; + + if (name.empty()) + name = acct->name; + else + name = acct->name + ":" + name; + } + + return name; + } +} + +format_t::element_t * format_t::parse_elements(const string& fmt) +{ + std::auto_ptr<element_t> result; + + element_t * current = NULL; + + char buf[1024]; + char * q = buf; + + // The following format codes need to be implemented as functions: + // + // d: COMPLETE_DATE_STRING + // D: DATE_STRING + // S: SOURCE; break + // B: ENTRY_BEG_POS + // b: ENTRY_BEG_LINE + // E: ENTRY_END_POS + // e: ENTRY_END_LINE + // X: CLEARED + // Y: ENTRY_CLEARED + // C: CODE + // P: PAYEE + // W: OPT_ACCOUNT + // a: ACCOUNT_NAME + // A: ACCOUNT_FULLNAME + // t: AMOUNT + // o: OPT_AMOUNT + // T: TOTAL + // N: NOTE + // n: OPT_NOTE + // _: DEPTH_SPACER + // + // xB: XACT_BEG_POS + // xb: XACT_BEG_LINE + // xE: XACT_END_POS + // xe: XACT_END_LINE + + for (const char * p = fmt.c_str(); *p; p++) { + if (*p != '%' && *p != '\\') { + *q++ = *p; + continue; + } + + if (! result.get()) { + result.reset(new element_t); + current = result.get(); + } else { + current->next.reset(new element_t); + current = current->next.get(); + } + + if (q != buf) { + current->type = element_t::STRING; + current->chars = string(buf, q); + q = buf; + + current->next.reset(new element_t); + current = current->next.get(); + } + + if (*p == '\\') { + p++; + current->type = element_t::STRING; + switch (*p) { + case 'b': current->chars = "\b"; break; + case 'f': current->chars = "\f"; break; + case 'n': current->chars = "\n"; break; + case 'r': current->chars = "\r"; break; + case 't': current->chars = "\t"; break; + case 'v': current->chars = "\v"; break; + } + continue; + } + + ++p; + while (*p == '!' || *p == '-') { + switch (*p) { + case '-': + current->flags |= ELEMENT_ALIGN_LEFT; + break; + case '!': + current->flags |= ELEMENT_HIGHLIGHT; + break; + } + ++p; + } + + int num = 0; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + current->min_width = num; + + if (*p == '.') { + ++p; + num = 0; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + current->max_width = num; + if (current->min_width == 0) + current->min_width = current->max_width; + } + + switch (*p) { + case '%': + current->type = element_t::STRING; + current->chars = "%"; + break; + + case '|': + current->type = element_t::STRING; + current->chars = " "; + break; + + case '(': + case '[': { + std::istringstream str(p); + current->type = element_t::EXPR; + current->expr.parse(str); + current->expr.set_text(string(p, p + str.tellg())); + p += str.tellg(); + break; + } + + default: { + current->type = element_t::EXPR; + char buf[2]; + buf[0] = *p; + buf[1] = '\0'; + current->chars = buf; + current->expr.parse(string("fmt_") + *p); + break; + } + } + } + + if (q != buf) { + if (! result.get()) { + result.reset(new element_t); + current = result.get(); + } else { + current->next.reset(new element_t); + current = current->next.get(); + } + current->type = element_t::STRING; + current->chars = string(buf, q); + } + + return result.release(); +} + +namespace { + inline void mark_plain(std::ostream& out) { + out << "\e[0m"; + } +} + +void format_t::format(std::ostream& out_str, scope_t& scope) +{ + for (element_t * elem = elements.get(); elem; elem = elem->next.get()) { + std::ostringstream out; + string name; + bool ignore_max_width = false; + + if (elem->flags & ELEMENT_ALIGN_LEFT) + out << std::left; + else + out << std::right; + + if (elem->min_width > 0) + out.width(elem->min_width); + + switch (elem->type) { + case element_t::STRING: + out << elem->chars; + break; + + case element_t::EXPR: + try { + elem->expr.compile(scope); + + value_t value; + if (elem->expr.is_function()) { + call_scope_t args(scope); + args.push_back(long(elem->max_width)); + value = elem->expr.get_function()(args); + } else { + value = elem->expr.calc(scope); + } + value.strip_annotations().dump(out, elem->min_width); + } + catch (const calc_error&) { + out << (string("%") + elem->chars); + } + break; + + default: + assert(false); + break; + } + + string temp = out.str(); + + if (! ignore_max_width && + elem->max_width > 0 && elem->max_width < temp.length()) + out_str << truncate(temp, elem->max_width); + else + out_str << temp; + } +} + +string format_t::truncate(const string& str, unsigned int width, + const bool is_account) +{ + const unsigned int len = str.length(); + if (len <= width) + return str; + + assert(width < 4095); + + 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<string> 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<string>::iterator i = parts.begin(); + i != parts.end(); + i++) { + // Don't contract the last element + std::list<string>::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; +} + +} // namespace ledger diff --git a/src/format.h b/src/format.h new file mode 100644 index 00000000..31f2bb1d --- /dev/null +++ b/src/format.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FORMAT_H +#define _FORMAT_H + +#include "journal.h" +#include "expr.h" + +namespace ledger { + +DECLARE_EXCEPTION(format_error, std::runtime_error); + +class format_t : public noncopyable +{ + struct element_t : public noncopyable + { +#define ELEMENT_ALIGN_LEFT 0x01 +#define ELEMENT_HIGHLIGHT 0x02 + + enum kind_t { + STRING, + EXPR, +#if 0 + DEPTH_SPACER +#endif + }; + + kind_t type; + unsigned char flags; + unsigned char min_width; + unsigned char max_width; + string chars; + expr_t expr; + + scoped_ptr<struct element_t> next; + + element_t() throw() + : type(STRING), flags(false), min_width(0), max_width(0) { + TRACE_CTOR(element_t, ""); + } + ~element_t() throw() { + TRACE_DTOR(element_t); + } + + friend inline void mark_red(std::ostream& out, const element_t * elem) { + out.setf(std::ios::left); + out.width(0); + out << "\e[31m"; + + if (elem->flags & ELEMENT_ALIGN_LEFT) + out << std::left; + else + out << std::right; + + if (elem->min_width > 0) + out.width(elem->min_width); + } + + void dump(std::ostream& out) const; + }; + + string format_string; + scoped_ptr<element_t> elements; + +public: + enum elision_style_t { + TRUNCATE_TRAILING, + TRUNCATE_MIDDLE, + TRUNCATE_LEADING, + ABBREVIATE + }; + +private: + // jww (2008-08-02): Should these four be here, or in session_t? + static elision_style_t elision_style; + static int abbrev_length; + + static bool ansi_codes; + static bool ansi_invert; + + static element_t * parse_elements(const string& fmt); + +public: + format_t() { + TRACE_CTOR(format_t, ""); + } + format_t(const string& _format) { + TRACE_CTOR(format_t, "const string&"); + parse(_format); + } + ~format_t() { + TRACE_DTOR(format_t); + } + + void parse(const string& _format) { + elements.reset(parse_elements(_format)); + format_string = _format; + } + + void format(std::ostream& out, scope_t& scope); + + void dump(std::ostream& out) const { + for (const element_t * elem = elements.get(); + elem; + elem = elem->next.get()) + elem->dump(out); + } + + static string truncate(const string& str, unsigned int width, + const bool is_account = false); +}; + +} // namespace ledger + +#endif // _FORMAT_H diff --git a/src/gnucash.cc b/src/gnucash.cc new file mode 100644 index 00000000..41990cb0 --- /dev/null +++ b/src/gnucash.cc @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gnucash.h" +#include "account.h" + +namespace ledger { + +typedef std::map<const string, account_t *> accounts_map; +typedef std::pair<const string, account_t *> accounts_pair; + +typedef std::map<account_t *, commodity_t *> account_comm_map; +typedef std::pair<account_t *, commodity_t *> account_comm_pair; + +static journal_t * curr_journal; +static account_t * master_account; +static account_t * curr_account; +static string curr_account_id; +static entry_t * curr_entry; +static commodity_t * entry_comm; +static commodity_t * curr_comm; +static amount_t curr_value; +static amount_t curr_quant; +static XML_Parser current_parser; +static accounts_map accounts_by_id; +static account_comm_map account_comms; +static unsigned int count; +static string have_error; + +static std::istream * instreamp; +static unsigned int offset; +static XML_Parser parser; +static path pathname; +static unsigned int src_idx; +static istream_pos_type beg_pos; +static unsigned long beg_line; + +static xact_t::state_t curr_state; + +static 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; + +static void startElement(void *userData, const char *name, const char **atts) +{ + if (std::strcmp(name, "gnc:account") == 0) { + curr_account = new account_t(master_account); + } + else if (std::strcmp(name, "act:name") == 0) + action = ACCOUNT_NAME; + else if (std::strcmp(name, "act:id") == 0) + action = ACCOUNT_ID; + else if (std::strcmp(name, "act:parent") == 0) + action = ACCOUNT_PARENT; + else if (std::strcmp(name, "gnc:commodity") == 0) + curr_comm = NULL; + else if (std::strcmp(name, "cmdty:id") == 0) + action = COMM_SYM; + else if (std::strcmp(name, "cmdty:name") == 0) + action = COMM_NAME; + else if (std::strcmp(name, "cmdty:fraction") == 0) + action = COMM_PREC; + else if (std::strcmp(name, "gnc:xact") == 0) { + assert(! curr_entry); + curr_entry = new entry_t; + } + else if (std::strcmp(name, "trn:num") == 0) + action = ENTRY_NUM; + else if (std::strcmp(name, "trn:date-posted") == 0) + action = ALMOST_ENTRY_DATE; + else if (action == ALMOST_ENTRY_DATE && std::strcmp(name, "ts:date") == 0) + action = ENTRY_DATE; + else if (std::strcmp(name, "trn:description") == 0) + action = ENTRY_DESC; + else if (std::strcmp(name, "trn:split") == 0) { + assert(curr_entry); + curr_entry->add_xact(new xact_t(curr_account)); + } + else if (std::strcmp(name, "split:reconciled-state") == 0) + action = XACT_STATE; + else if (std::strcmp(name, "split:amount") == 0) + action = XACT_AMOUNT; + else if (std::strcmp(name, "split:value") == 0) + action = XACT_VALUE; + else if (std::strcmp(name, "split:quantity") == 0) + action = XACT_QUANTITY; + else if (std::strcmp(name, "split:account") == 0) + action = XACT_ACCOUNT; + else if (std::strcmp(name, "split:memo") == 0) + action = XACT_NOTE; +} + +static void endElement(void *userData, const char *name) +{ + if (std::strcmp(name, "gnc:account") == 0) { + assert(curr_account); + if (curr_account->parent == master_account) + curr_journal->add_account(curr_account); + accounts_by_id.insert(accounts_pair(curr_account_id, curr_account)); + curr_account = NULL; + } + else if (std::strcmp(name, "gnc:commodity") == 0) { + curr_comm = NULL; + } + else if (std::strcmp(name, "gnc:xact") == 0) { + assert(curr_entry); + + // Add the new entry (what gnucash calls a 'xact') to the + // journal + if (! curr_journal->add_entry(curr_entry)) { +#if 0 + print_entry(std::cerr, *curr_entry); +#endif + have_error = "The above entry does not balance"; + checked_delete(curr_entry); + } else { + curr_entry->src_idx = src_idx; + curr_entry->beg_pos = beg_pos; + curr_entry->beg_line = beg_line; + curr_entry->end_pos = instreamp->tellg(); + curr_entry->end_line = XML_GetCurrentLineNumber(parser) - offset; + count++; + } + + // Clear the relevant variables for the next run + curr_entry = NULL; + entry_comm = NULL; + } + else if (std::strcmp(name, "trn:split") == 0) { + xact_t * xact = curr_entry->xacts.back(); + + // Identify the commodity to use for the value of this + // xact. The quantity indicates how many times that value + // the xact is worth. + amount_t value; + commodity_t * default_commodity = NULL; + account_comm_map::iterator ac = account_comms.find(xact->account); + if (ac != account_comms.end()) + default_commodity = (*ac).second; + + if (default_commodity) { + curr_quant.set_commodity(*default_commodity); + value = curr_quant.round(); + + if (curr_value.commodity() == *default_commodity) + curr_value = value; + } else { + value = curr_quant; + } + + xact->state = curr_state; + xact->amount = value; + if (value != curr_value) + xact->cost = curr_value; + + xact->beg_pos = beg_pos; + xact->beg_line = beg_line; + xact->end_pos = instreamp->tellg(); + xact->end_line = XML_GetCurrentLineNumber(parser) - offset; + + // Clear the relevant variables for the next run + curr_state = xact_t::UNCLEARED; + curr_value = amount_t(); + curr_quant = amount_t(); + } + + action = NO_ACTION; +} + + +static amount_t convert_number(const string& number, + int * precision = NULL) +{ + 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); + } +} + +static void dataHandler(void *userData, const char *s, int len) +{ + switch (action) { + case ACCOUNT_NAME: + curr_account->name = string(s, len); + break; + + case ACCOUNT_ID: + curr_account_id = string(s, len); + break; + + case ACCOUNT_PARENT: { + accounts_map::iterator i = accounts_by_id.find(string(s, len)); + assert(i != accounts_by_id.end()); + curr_account->parent = (*i).second; + curr_account->depth = curr_account->parent->depth + 1; + (*i).second->add_account(curr_account); + break; + } + + case COMM_SYM: { + string symbol(s, len); + if (symbol == "USD") symbol = "$"; + + curr_comm = amount_t::current_pool->find_or_create(symbol); + assert(curr_comm); + + if (symbol != "$") + curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); + + if (curr_account) + account_comms.insert(account_comm_pair(curr_account, curr_comm)); + else if (curr_entry) + entry_comm = curr_comm; + break; + } + + case COMM_NAME: + curr_comm->set_name(string(s, len)); + break; + + case COMM_PREC: + curr_comm->set_precision(len - 1); + break; + + case ENTRY_NUM: + curr_entry->code = string(s, len); + break; + + case ENTRY_DATE: + curr_entry->_date = parse_date(string(s, len)); + break; + + case ENTRY_DESC: + curr_entry->payee = string(s, len); + break; + + case XACT_STATE: + if (*s == 'y') + curr_state = xact_t::CLEARED; + else if (*s == 'n') + curr_state = xact_t::UNCLEARED; + else + curr_state = xact_t::PENDING; + break; + + case XACT_VALUE: { + int precision; + assert(entry_comm); + curr_value = convert_number(string(s, len), &precision); + curr_value.set_commodity(*entry_comm); + + if (precision > entry_comm->precision()) + entry_comm->set_precision(precision); + break; + } + + case XACT_QUANTITY: + curr_quant = convert_number(string(s, len)); + break; + + case XACT_ACCOUNT: { + xact_t * xact = curr_entry->xacts.back(); + + accounts_map::iterator i = accounts_by_id.find(string(s, len)); + if (i != accounts_by_id.end()) { + xact->account = (*i).second; + } else { + xact->account = curr_journal->find_account("<Unknown>"); + + have_error = (string("Could not find account ") + + string(s, len)); + } + break; + } + + case XACT_NOTE: + curr_entry->xacts.back()->note = string(s, len); + break; + + case NO_ACTION: + case ALMOST_ENTRY_DATE: + case XACT_AMOUNT: + break; + + default: + assert(false); + 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, "<?xml", 5) == 0; +} + +unsigned int gnucash_parser_t::parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master, + const path * original_file) +{ + char buf[BUFSIZ]; + +#if 0 + // jww (2008-05-08): Replace this + // This is the date format used by Gnucash, so override whatever the + // user specified. + date_t::input_format = "%Y-%m-%d %H:%M:%S %z"; +#endif + + count = 0; + action = NO_ACTION; + curr_journal = &journal; + master_account = master ? master : journal.master; + curr_account = NULL; + curr_entry = NULL; + curr_comm = NULL; + entry_comm = NULL; + curr_state = xact_t::UNCLEARED; + + instreamp = ∈ + pathname = original_file ? *original_file : "<gnucash>"; + src_idx = journal.sources.size() - 1; + + // GnuCash uses the USD commodity without defining it, which really + // means $. + commodity_t * usd = amount_t::current_pool->find_or_create("$"); + usd->set_precision(2); + usd->add_flags(COMMODITY_STYLE_THOUSANDS); + + offset = 2; + parser = current_parser = XML_ParserCreate(NULL); + + XML_SetElementHandler(parser, startElement, endElement); + XML_SetCharacterDataHandler(parser, dataHandler); + + 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_error(msg); + } + + if (! have_error.empty()) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + parse_error err(have_error); + std::cerr << "Error: " << err.what() << std::endl; + 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..b8273a19 --- /dev/null +++ b/src/gnucash.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _GNUCASH_H +#define _GNUCASH_H + +#include "journal.h" + +namespace ledger { + +class gnucash_parser_t : public journal_t::parser_t +{ + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master = NULL, + const path * original_file = NULL); +}; + +} // namespace ledger + +#endif // _GNUCASH_H diff --git a/src/handler.h b/src/handler.h new file mode 100644 index 00000000..6ebd6a5d --- /dev/null +++ b/src/handler.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _HANDLER_H +#define _HANDLER_H + +#include "utils.h" +#include "xact.h" +#include "account.h" + +namespace ledger { + +template <typename T> +struct item_handler : public noncopyable +{ + shared_ptr<item_handler> handler; + +public: + item_handler() { + TRACE_CTOR(item_handler, ""); + } + item_handler(shared_ptr<item_handler> _handler) : handler(_handler) { + TRACE_CTOR(item_handler, "shared_ptr<item_handler>"); + } + virtual ~item_handler() { + TRACE_DTOR(item_handler); + } + + virtual void flush() { + if (handler.get()) + handler->flush(); + } + virtual void operator()(T& item) { + if (handler.get()) + (*handler.get())(item); + } +}; + +typedef shared_ptr<item_handler<xact_t> > xact_handler_ptr; +typedef shared_ptr<item_handler<account_t> > acct_handler_ptr; + +} // namespace ledger + +#endif // _HANDLER_H diff --git a/src/help.cc b/src/help.cc new file mode 100644 index 00000000..84d5a178 --- /dev/null +++ b/src/help.cc @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "help.h" + +namespace ledger { + +void help(std::ostream& out) +{ + out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ +Use -H to see all the help text on one page, or:\n\ + --help-calc calculation options\n\ + --help-disp display options\n\ + --help-comm commodity options\n\n\ +Basic options:\n\ + -h, --help display this help text\n\ + -v, --version show version information\n\ + -f, --file FILE read ledger data from FILE\n\ + -o, --output FILE write output to FILE\n\ + -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ + --cache FILE use FILE as a binary cache when --file is not used\n\ + --no-cache don't use a cache, even if it would be appropriate\n\ + -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ +Commands:\n\ + balance [REGEXP]... show balance totals for matching accounts\n\ + register [REGEXP]... show register of matching transactions\n\ + print [REGEXP]... print all matching entries\n\ + xml [REGEXP]... print matching entries in XML format\n\ + equity [REGEXP]... output equity entries for matching accounts\n\ + prices [REGEXP]... display price history for matching commodities\n\ + entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; +} + +void calc_help(std::ostream& out) +{ + out << "Options to control how a report is calculated:\n\ + -c, --current show only current and past entries (not future)\n\ + -b, --begin DATE set report begin date\n\ + -e, --end DATE set report end date\n\ + -p, --period STR report using the given period\n\ + --period-sort EXPR sort each report period's entries by EXPR\n\ + -C, --cleared consider only cleared transactions\n\ + -U, --uncleared consider only uncleared transactions\n\ + -R, --real consider only real (non-virtual) transactions\n\ + -L, --actual consider only actual (non-automated) transactions\n\ + -r, --related calculate report using related transactions\n\ + --budget generate budget entries based on periodic entries\n\ + --add-budget show all transactions plus the budget\n\ + --unbudgeted show only unbudgeted transactions\n\ + --forecast EXPR generate forecast entries while EXPR is true\n\ + -l, --limit EXPR calculate only transactions matching EXPR\n\ + -t, --amount EXPR use EXPR to calculate the displayed amount\n\ + -T, --total EXPR use EXPR to calculate the displayed total\n"; +} + +void disp_help(std::ostream& out) +{ + out << "Output to control how report results are displayed:\n\ + -n, --collapse register: collapse entries; balance: no grand total\n\ + -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ + -P, --by-payee show summarized totals by payee\n\ + -x, --comm-as-payee set commodity name as the payee, for reporting\n\ + -E, --empty balance: show accounts with zero balance\n\ + -W, --weekly show weekly sub-totals\n\ + -M, --monthly show monthly sub-totals\n\ + -Y, --yearly show yearly sub-totals\n\ + --dow show a days-of-the-week report\n\ + -S, --sort EXPR sort report according to the value expression EXPR\n\ + -w, --wide for the default register report, use 132 columns\n\ + --head COUNT show only the first COUNT entries (negative inverts)\n\ + --tail COUNT show only the last COUNT entries (negative inverts)\n\ + --pager PAGER send all output through the given PAGER program\n\ + -A, --average report average transaction amount\n\ + -D, --deviation report deviation from the average\n\ + -%, --percentage report balance totals as a percentile of the parent\n\ + --totals in the \"xml\" report, include running total\n\ + -j, --amount-data print only raw amount data (useful for scripting)\n\ + -J, --total-data print only raw total data\n\ + -d, --display EXPR display only transactions matching EXPR\n\ + -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ + -F, --format STR use STR as the format; for each report type, use:\n\ + --balance-format --register-format --print-format\n\ + --plot-amount-format --plot-total-format --equity-format\n\ + --prices-format --wide-register-format\n"; +} + +void comm_help(std::ostream& out) +{ + out << "Options to control how commodity values are determined:\n\ + --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ + -Z, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ + -Q, --download download price information when needed\n\ + -O, --quantity report commodity totals (this is the default)\n\ + -B, --basis report cost basis of commodities\n\ + -V, --market report last known market value\n\ + -g, --performance report gain/loss for each displayed transaction\n\ + -G, --gain report net gain/loss\n"; +} + +void full_help(std::ostream& out) +{ + out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ +Basic options:\n\ + -H, --full-help display this help text\n\ + -h, --help display summarized help text\n\ + -v, --version show version information\n\ + -f, --file FILE read ledger data from FILE\n\ + -o, --output FILE write output to FILE\n\ + -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ + --cache FILE use FILE as a binary cache when --file is not used\n\ + --no-cache don't use a cache, even if it would be appropriate\n\ + -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ +Report filtering:\n\ + -c, --current show only current and past entries (not future)\n\ + -b, --begin DATE set report begin date\n\ + -e, --end DATE set report end date\n\ + -p, --period STR report using the given period\n\ + --period-sort EXPR sort each report period's entries by EXPR\n\ + -C, --cleared consider only cleared transactions\n\ + -U, --uncleared consider only uncleared transactions\n\ + -R, --real consider only real (non-virtual) transactions\n\ + -L, --actual consider only actual (non-automated) transactions\n\ + -r, --related calculate report using related transactions\n\ + --budget generate budget entries based on periodic entries\n\ + --add-budget show all transactions plus the budget\n\ + --unbudgeted show only unbudgeted transactions\n\ + --forecast EXPR generate forecast entries while EXPR is true\n\ + -l, --limit EXPR calculate only transactions matching EXPR\n\ + -t, --amount EXPR use EXPR to calculate the displayed amount\n\ + -T, --total EXPR use EXPR to calculate the displayed total\n\n\ +Output customization:\n\ + -n, --collapse register: collapse entries; balance: no grand total\n\ + -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ + -P, --by-payee show summarized totals by payee\n\ + -x, --comm-as-payee set commodity name as the payee, for reporting\n\ + -E, --empty balance: show accounts with zero balance\n\ + -W, --weekly show weekly sub-totals\n\ + -M, --monthly show monthly sub-totals\n\ + -Y, --yearly show yearly sub-totals\n\ + --dow show a days-of-the-week report\n\ + -S, --sort EXPR sort report according to the value expression EXPR\n\ + -w, --wide for the default register report, use 132 columns\n\ + --head COUNT show only the first COUNT entries (negative inverts)\n\ + --tail COUNT show only the last COUNT entries (negative inverts)\n\ + --pager PAGER send all output through the given PAGER program\n\ + -A, --average report average transaction amount\n\ + -D, --deviation report deviation from the average\n\ + -%, --percentage report balance totals as a percentile of the parent\n\ + --totals in the \"xml\" report, include running total\n\ + -j, --amount-data print only raw amount data (useful for scripting)\n\ + -J, --total-data print only raw total data\n\ + -d, --display EXPR display only transactions matching EXPR\n\ + -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ + -F, --format STR use STR as the format; for each report type, use:\n\ + --balance-format --register-format --print-format\n\ + --plot-amount-format --plot-total-format --equity-format\n\ + --prices-format --wide-register-format\n\n\ +Commodity reporting:\n\ + --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ + -L, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ + -Q, --download download price information when needed\n\ + -O, --quantity report commodity totals (this is the default)\n\ + -B, --basis report cost basis of commodities\n\ + -V, --market report last known market value\n\ + -g, --performance report gain/loss for each displayed transaction\n\ + -G, --gain report net gain/loss\n\n\ +Commands:\n\ + balance [REGEXP]... show balance totals for matching accounts\n\ + register [REGEXP]... show register of matching transactions\n\ + print [REGEXP]... print all matching entries\n\ + xml [REGEXP]... print matching entries in XML format\n\ + equity [REGEXP]... output equity entries for matching accounts\n\ + prices [REGEXP]... display price history for matching commodities\n\ + entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; +} + +} // namespace ledger diff --git a/src/help.h b/src/help.h new file mode 100644 index 00000000..14067be4 --- /dev/null +++ b/src/help.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _HELP_H +#define _HELP_H + +#include "utils.h" + +namespace ledger { + +void help(std::ostream& out); + +void calc_help(std::ostream& out); +void disp_help(std::ostream& out); +void comm_help(std::ostream& out); + +void full_help(std::ostream& out); + +} // namespace ledger + +#endif // _HELP_H diff --git a/src/hooks.h b/src/hooks.h new file mode 100644 index 00000000..da197cdd --- /dev/null +++ b/src/hooks.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _HOOKS_H +#define _HOOKS_H + +template <typename T, typename Data> +class hooks_t : public boost::noncopyable +{ +public: + typedef boost::function<bool (Data&, bool)> function_t; + +protected: + std::list<T *> list; + +public: + hooks_t() { + TRACE_CTOR(hooks_t, ""); + } + ~hooks_t() throw() { + TRACE_DTOR(hooks_t); + } + + void add_hook(T * func, const bool prepend = false) { + if (prepend) + list.push_front(func); + else + list.push_back(func); + } + + void remove_hook(T * func) { + list.remove(func); + } + + bool run_hooks(Data& item, bool post) { + foreach (T * func, list) + if (! (*func)(item, post)) + return false; + return true; + } +}; + +#endif // _HOOKS_H diff --git a/src/iterators.cc b/src/iterators.cc new file mode 100644 index 00000000..34beba7e --- /dev/null +++ b/src/iterators.cc @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "iterators.h" +#include "session.h" +#include "compare.h" + +namespace ledger { + +void entries_iterator::reset(session_t& session) +{ + journals_i = session.journals.begin(); + journals_end = session.journals.end(); + + journals_uninitialized = false; + + if (journals_i != journals_end) { + entries_i = (*journals_i).entries.begin(); + entries_end = (*journals_i).entries.end(); + + entries_uninitialized = false; + } else { + entries_uninitialized = true; + } +} + +entry_t * entries_iterator::operator()() +{ + if (entries_i == entries_end) { + journals_i++; + if (journals_i == journals_end) + return NULL; + + entries_i = (*journals_i).entries.begin(); + entries_end = (*journals_i).entries.end(); + } + return *entries_i++; +} + +void session_xacts_iterator::reset(session_t& session) +{ + entries.reset(session); + entry_t * entry = entries(); + if (entry != NULL) + xacts.reset(*entry); +} + +xact_t * session_xacts_iterator::operator()() +{ + xact_t * xact = xacts(); + if (xact == NULL) { + entry_t * entry = entries(); + if (entry != NULL) { + xacts.reset(*entry); + xact = xacts(); + } + } + return xact; +} + +account_t * basic_accounts_iterator::operator()() +{ + while (! accounts_i.empty() && + accounts_i.back() == accounts_end.back()) { + accounts_i.pop_back(); + accounts_end.pop_back(); + } + if (accounts_i.empty()) + return NULL; + + account_t * account = (*(accounts_i.back()++)).second; + assert(account); + + // If this account has children, queue them up to be iterated next. + if (! account->accounts.empty()) + push_back(*account); + + return account; +} + +void sorted_accounts_iterator::sort_accounts(account_t& account, + accounts_deque_t& deque) +{ + foreach (accounts_map::value_type& pair, account.accounts) + deque.push_back(pair.second); + + std::stable_sort(deque.begin(), deque.end(), + compare_items<account_t>(sort_cmp)); +} + +account_t * sorted_accounts_iterator::operator()() +{ + while (! sorted_accounts_i.empty() && + sorted_accounts_i.back() == sorted_accounts_end.back()) { + sorted_accounts_i.pop_back(); + sorted_accounts_end.pop_back(); + assert(! accounts_list.empty()); + accounts_list.pop_back(); + } + if (sorted_accounts_i.empty()) + return NULL; + + account_t * account = *sorted_accounts_i.back()++; + assert(account); + + // If this account has children, queue them up to be iterated next. + if (! account->accounts.empty()) + push_back(*account); + + account->xdata().drop_flags(ACCOUNT_EXT_SORT_CALC); + return account; +} + +void journals_iterator::reset(session_t& session) +{ + journals_i = session.journals.begin(); + journals_end = session.journals.end(); +} + +journal_t * journals_iterator::operator()() +{ + if (journals_i == journals_end) + return NULL; + return &(*journals_i++); +} + +#if 0 +// jww (2008-08-03): This needs to be changed into a commodities->xacts +// iterator. + +// jww (2008-08-03): We then could also use a payees->xacts iterator + +void walk_commodities(commodity_pool_t::commodities_by_ident& commodities, + item_handler<xact_t>& handler) +{ + std::list<xact_t> xact_temps; + std::list<entry_t> entry_temps; + std::list<account_t> acct_temps; + + for (commodity_pool_t::commodities_by_ident::iterator + i = commodities.begin(); + i != commodities.end(); + i++) { + if ((*i)->has_flags(COMMODITY_STYLE_NOMARKET)) + continue; + + entry_temps.push_back(entry_t()); + acct_temps.push_back(account_t(NULL, (*i)->symbol())); + + if ((*i)->history()) + foreach (const commodity_t::history_map::value_type& pair, + (*i)->history()->prices) { + entry_temps.back()._date = pair.first.date(); + + xact_temps.push_back(xact_t(&acct_temps.back())); + xact_t& temp = xact_temps.back(); + temp.entry = &entry_temps.back(); + temp.amount = pair.second; + temp.add_flags(XACT_TEMP); + entry_temps.back().add_xact(&temp); + + handler(xact_temps.back()); + } + } + + handler.flush(); + + clear_entries_xacts(entry_temps); +} +#endif + +} // namespace ledger diff --git a/src/iterators.h b/src/iterators.h new file mode 100644 index 00000000..c52a6827 --- /dev/null +++ b/src/iterators.h @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _ITERATORS_H +#define _ITERATORS_H + +#include "journal.h" +#include "entry.h" +#include "account.h" + +namespace ledger { + +class xacts_iterator : public noncopyable +{ +public: + virtual xact_t * operator()() = 0; +}; + +class entry_xacts_iterator : public xacts_iterator +{ + xacts_list::iterator xacts_i; + xacts_list::iterator xacts_end; + + bool xacts_uninitialized; + +public: + entry_xacts_iterator() : xacts_uninitialized(true) { + TRACE_CTOR(entry_xacts_iterator, ""); + } + entry_xacts_iterator(entry_t& entry) + : xacts_uninitialized(true) { + TRACE_CTOR(entry_xacts_iterator, "entry_t&"); + reset(entry); + } + virtual ~entry_xacts_iterator() throw() { + TRACE_DTOR(entry_xacts_iterator); + } + + void reset(entry_t& entry) { + xacts_i = entry.xacts.begin(); + xacts_end = entry.xacts.end(); + + xacts_uninitialized = false; + } + + virtual xact_t * operator()() { + if (xacts_i == xacts_end || xacts_uninitialized) + return NULL; + return *xacts_i++; + } +}; + +class entries_iterator : public noncopyable +{ + ptr_list<journal_t>::iterator journals_i; + ptr_list<journal_t>::iterator journals_end; + + bool journals_uninitialized; + + entries_list::iterator entries_i; + entries_list::iterator entries_end; + + bool entries_uninitialized; + +public: + entries_iterator() + : journals_uninitialized(true), entries_uninitialized(true) { + TRACE_CTOR(entries_iterator, ""); + } + entries_iterator(session_t& session) + : journals_uninitialized(true), entries_uninitialized(true) { + TRACE_CTOR(entries_iterator, "session_t&"); + reset(session); + } + ~entries_iterator() throw() { + TRACE_DTOR(entries_iterator); + } + + void reset(session_t& session); + + entry_t * operator()(); +}; + +class session_xacts_iterator : public xacts_iterator +{ + entries_iterator entries; + entry_xacts_iterator xacts; + +public: + session_xacts_iterator() { + TRACE_CTOR(session_xacts_iterator, ""); + } + session_xacts_iterator(session_t& session) { + TRACE_CTOR(session_xacts_iterator, "session_t&"); + reset(session); + } + virtual ~session_xacts_iterator() throw() { + TRACE_DTOR(session_xacts_iterator); + } + + void reset(session_t& session); + + virtual xact_t * operator()(); +}; + +class accounts_iterator : public noncopyable +{ +public: + virtual account_t * operator()() = 0; +}; + +class basic_accounts_iterator : public accounts_iterator +{ + std::list<accounts_map::const_iterator> accounts_i; + std::list<accounts_map::const_iterator> accounts_end; + +public: + basic_accounts_iterator() { + TRACE_CTOR(basic_accounts_iterator, ""); + } + basic_accounts_iterator(account_t& account) { + TRACE_CTOR(basic_accounts_iterator, "account_t&"); + push_back(account); + } + virtual ~basic_accounts_iterator() throw() { + TRACE_DTOR(basic_accounts_iterator); + } + + void push_back(account_t& account) { + accounts_i.push_back(account.accounts.begin()); + accounts_end.push_back(account.accounts.end()); + } + + virtual account_t * operator()(); +}; + +class sorted_accounts_iterator : public accounts_iterator +{ + expr_t sort_cmp; + + typedef std::deque<account_t *> accounts_deque_t; + + std::list<accounts_deque_t> accounts_list; + std::list<accounts_deque_t::const_iterator> sorted_accounts_i; + std::list<accounts_deque_t::const_iterator> sorted_accounts_end; + +public: + sorted_accounts_iterator(const string& sort_order) { + TRACE_CTOR(sorted_accounts_iterator, "const string&"); + sort_cmp = expr_t(sort_order); + } + sorted_accounts_iterator(account_t& account, const string& sort_order) { + TRACE_CTOR(sorted_accounts_iterator, "account_t&, const string&"); + sort_cmp = expr_t(sort_order); + push_back(account); + } + virtual ~sorted_accounts_iterator() throw() { + TRACE_DTOR(sorted_accounts_iterator); + } + + void sort_accounts(account_t& account, accounts_deque_t& deque); + + void push_back(account_t& account) { + accounts_list.push_back(accounts_deque_t()); + sort_accounts(account, accounts_list.back()); + + sorted_accounts_i.push_back(accounts_list.back().begin()); + sorted_accounts_end.push_back(accounts_list.back().end()); + } + + virtual account_t * operator()(); +}; + +class journals_iterator : public noncopyable +{ + ptr_list<journal_t>::iterator journals_i; + ptr_list<journal_t>::iterator journals_end; + +public: + journals_iterator() { + TRACE_CTOR(journals_iterator, ""); + } + journals_iterator(session_t& session) { + TRACE_CTOR(journals_iterator, "session_t&"); + reset(session); + } + virtual ~journals_iterator() throw() { + TRACE_DTOR(journals_iterator); + } + + void reset(session_t& session); + + virtual journal_t * operator()(); +}; + +} // namespace ledger + +#endif // _ITERATORS_H diff --git a/src/journal.cc b/src/journal.cc new file mode 100644 index 00000000..7d95d72f --- /dev/null +++ b/src/journal.cc @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "journal.h" +#include "session.h" + +namespace ledger { + +const string version = PACKAGE_VERSION; + +journal_t::journal_t(session_t * _owner) + : owner(_owner), basket(NULL) +{ + TRACE_CTOR(journal_t, ""); + master = owner->master.get(); +} + +journal_t::~journal_t() +{ + TRACE_DTOR(journal_t); + + // Don't bother unhooking each entry's xacts from the + // accounts they refer to, because all accounts are about to + // be deleted. + foreach (entry_t * entry, entries) + if (! entry->has_flags(ENTRY_IN_CACHE)) + checked_delete(entry); + else + entry->~entry_t(); + + foreach (auto_entry_t * entry, auto_entries) + if (! entry->has_flags(ENTRY_IN_CACHE)) + checked_delete(entry); + else + entry->~auto_entry_t(); + + foreach (period_entry_t * entry, period_entries) + if (! entry->has_flags(ENTRY_IN_CACHE)) + checked_delete(entry); + else + entry->~period_entry_t(); +} + +void journal_t::add_account(account_t * acct) +{ + owner->add_account(acct); +} + +bool journal_t::remove_account(account_t * acct) +{ + return owner->remove_account(acct); +} + +account_t * journal_t::find_account(const string& name, bool auto_create) +{ + return owner->find_account(name, auto_create); +} + +account_t * journal_t::find_account_re(const string& regexp) +{ + return owner->find_account_re(regexp); +} + +bool journal_t::add_entry(entry_t * entry) +{ + entry->journal = this; + + if (! entry_finalize_hooks.run_hooks(*entry, false) || + ! entry->finalize() || + ! entry_finalize_hooks.run_hooks(*entry, true)) { + entry->journal = NULL; + return false; + } + + entries.push_back(entry); + + foreach (const xact_t * xact, entry->xacts) + if (xact->cost) { + assert(xact->amount); + xact->amount.commodity().add_price(datetime_t(entry->date(), + time_duration_t(0, 0, 0)), + *xact->cost / xact->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; + } + + foreach (const entry_t * entry, entries) + if (! entry->valid()) { + DEBUG("ledger.validate", "journal_t: entry not valid"); + return false; + } + + return true; +} + +} // namespace ledger diff --git a/src/journal.h b/src/journal.h new file mode 100644 index 00000000..293439bb --- /dev/null +++ b/src/journal.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _JOURNAL_H +#define _JOURNAL_H + +#include "utils.h" +#include "hooks.h" +#include "entry.h" + +namespace ledger { + +typedef std::list<path> paths_list; + +class session_t; +class account_t; + +class journal_t : public noncopyable +{ +public: + session_t * owner; + account_t * master; + account_t * basket; + entries_list entries; + paths_list sources; + optional<path> price_db; + + auto_entries_list auto_entries; + period_entries_list period_entries; + + hooks_t<entry_finalizer_t, entry_t> entry_finalize_hooks; + + journal_t(session_t * _owner); + ~journal_t(); + + // These four methods are delegated to 'owner', since all accounts processed + // are gathered together at the session level. + void add_account(account_t * acct); + bool remove_account(account_t * acct); + account_t * find_account(const string& name, bool auto_create = true); + 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) { + entry_finalize_hooks.add_hook(finalizer); + } + void remove_entry_finalizer(entry_finalizer_t * finalizer) { + entry_finalize_hooks.remove_hook(finalizer); + } + + /** + * @class journal_t::parser_t + * + * @brief Provides an abstract interface for writing journal parsers. + * + * Any data format for Ledger data is possible, as long as it can be parsed + * into a journal_t data tree. This class provides the basic interface which + * must be implemented by every such journal parser. + */ + class parser_t : public noncopyable + { + public: + parser_t() { + TRACE_CTOR(journal_t::parser_t, ""); + } + virtual ~parser_t() { + TRACE_DTOR(journal_t::parser_t); + } + + virtual bool test(std::istream& in) const = 0; + + virtual unsigned int parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master = NULL, + const path * original_file = NULL) = 0; + }; + + class binary_parser_t : public parser_t + { + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master = NULL, + const path * original_file = NULL); + }; + + bool valid() const; +}; + +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..a87c3d55 --- /dev/null +++ b/src/ledger.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _LEDGER_H +#define _LEDGER_H + +////////////////////////////////////////////////////////////////////// +// +// Ledger Accounting Tool +// +// A command-line tool for general double-entry accounting. +// +// Copyright (c) 2003-2008, John Wiegley <johnw@newartisans.com> +// + +#include <utils.h> +#include <value.h> +#include <expr.h> +#include <scope.h> +#include <predicate.h> +#include <format.h> +#include <option.h> + +#include <journal.h> +#include <entry.h> +#include <xact.h> +#include <account.h> +#include <iterators.h> +#include <compare.h> + +#include <textual.h> +#include <cache.h> +#include <emacs.h> +#include <qif.h> +#include <xml.h> +#include <csv.h> +#include <gnucash.h> +#include <ofx.h> + +#include <session.h> +#include <report.h> +#include <handler.h> +#include <filters.h> +#include <output.h> +#include <help.h> + +#include <derive.h> +#include <reconcile.h> +#include <quotes.h> + +#include <ledger.h> + +#endif // _LEDGER_H diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 00000000..a5ca02d0 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,593 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "session.h" +#include "report.h" +#include "option.h" +#include "output.h" +#include "help.h" + +#include "textual.h" +#include "qif.h" +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) +#include "xml.h" +#include "gnucash.h" +#endif +#ifdef HAVE_LIBOFX +#include "ofx.h" +#endif + +#ifdef HAVE_UNIX_PIPES +#include <sys/types.h> +#include <sys/wait.h> +#include "fdstream.h" +#endif + +namespace ledger { + string args_to_predicate(value_t::sequence_t::const_iterator begin, + value_t::sequence_t::const_iterator end) + { + string acct_value_expr; + string payee_value_expr; + string note_value_expr; + + string * value_expr; + + enum regexp_kind_t { + ACCOUNT_REGEXP, + PAYEE_REGEXP, + NOTE_REGEXP + } + kind = ACCOUNT_REGEXP; + + value_expr = &acct_value_expr; + + for ( ; begin != end; begin++) { + const string& arg((*begin).as_string()); + + if (arg == "--") { + kind = PAYEE_REGEXP; + value_expr = &payee_value_expr; + } + else if (arg == "/") { + kind = NOTE_REGEXP; + value_expr = ¬e_value_expr; + } + else { + if (! value_expr->empty()) + *value_expr += "|"; + + switch (kind) { + case ACCOUNT_REGEXP: + *value_expr += "account =~ "; + break; + case PAYEE_REGEXP: + *value_expr += "payee =~ "; + break; + case NOTE_REGEXP: + *value_expr += "note =~ "; + break; + } + + const char * p = arg.c_str(); + if (*p == '-') { + *value_expr += "!"; + p++; + } + + *value_expr += "/"; + if (kind == NOTE_REGEXP) *value_expr += ":"; + while (*p) { + if (*p == '/') + *value_expr += "\\"; + *value_expr += *p; + p++; + } + if (kind == NOTE_REGEXP) *value_expr += ":"; + *value_expr += "/"; + } + } + + string final_value_expr; + + if (! acct_value_expr.empty()) { + if (! payee_value_expr.empty() || + ! note_value_expr.empty()) + final_value_expr = string("(") + acct_value_expr + ")"; + else + final_value_expr = acct_value_expr; + } + + if (! payee_value_expr.empty()) { + if (! acct_value_expr.empty()) + final_value_expr += string("&(") + payee_value_expr + ")"; + else if (! note_value_expr.empty()) + final_value_expr = string("(") + payee_value_expr + ")"; + else + final_value_expr = payee_value_expr; + } + + if (! note_value_expr.empty()) { + if (! acct_value_expr.empty() || + ! payee_value_expr.empty()) + final_value_expr += string("&(") + note_value_expr + ")"; + else if (acct_value_expr.empty() && + payee_value_expr.empty()) + final_value_expr = note_value_expr; + } + + DEBUG("report.predicate", + "Regexp predicate expression = " << final_value_expr); + + return final_value_expr; + } + + template <class Formatter = format_xacts, + class handler_ptr = xact_handler_ptr, + void (report_t::*report_method)(handler_ptr) = + &report_t::xacts_report> + class reporter + { + string format_name; + + public: + reporter(const string& _format_name) + : format_name(_format_name) {} + + value_t operator()(call_scope_t& args) + { + report_t& report(find_scope<report_t>(args)); + var_t<string> format(args, format_name); + + if (! report.format_string.empty()) + *format = report.format_string; + + if (args.value().is_sequence() && + args.value().size() > 1) { + if (! report.predicate.empty()) + report.predicate = string("(") + report.predicate + ")&"; + report.predicate += + args_to_predicate(++args.value().as_sequence().begin(), + args.value().as_sequence().end()); + } + + (report.*report_method)(handler_ptr(new Formatter(report, *format))); + + return true; + } + }; + + int read_and_report(ledger::report_t& report, + int argc, char * argv[], char * envp[]) + { + using namespace ledger; + + session_t& session(report.session); + + // Handle the command-line arguments + + strings_list args; + process_arguments(argc - 1, argv + 1, false, report, args); + + if (args.empty()) { + ledger::help(std::cout); + 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; + + 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<const char **>(envp), "LEDGER_", report); + TRACE_FINISH(environment, 1); + + optional<path> home; + if (const char * home_var = std::getenv("HOME")) + home = home_var; + + if (! session.init_file) + session.init_file = home ? *home / ".ledgerrc" : "./.ledgerrc"; + if (! session.price_db) + session.price_db = home ? *home / ".pricedb" : "./.pricedb"; + + if (! session.cache_file) + session.cache_file = home ? *home / ".ledger-cache" : "./.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->string()); + INFO("Price database is " << session.price_db->string()); + INFO("Binary cache is " << session.cache_file->string()); + INFO("Journal file is " << session.data_file.string()); + + if (! session.use_cache) + INFO("Binary cache mechanism will not be used"); + + // Configure the output stream + +#ifdef HAVE_UNIX_PIPES + int status, pfd[2]; // Pipe file descriptors +#endif + report.output_stream = &std::cout; + + if (report.output_file) { + report.output_stream = new ofstream(*report.output_file); + } +#ifdef HAVE_UNIX_PIPES + else if (report.pager) { + status = pipe(pfd); + if (status == -1) + throw_(std::logic_error, "Failed to create pipe"); + + status = fork(); + if (status < 0) { + throw_(std::logic_error, "Failed to fork child process"); + } + else if (status == 0) { // child + // 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. + execlp(report.pager->native_file_string().c_str(), + basename(*report.pager).c_str(), NULL); + perror("execl"); + exit(1); + } + else { // parent + close(pfd[0]); + report.output_stream = new boost::fdostream(pfd[1]); + } + } +#endif + + // Read the command word and see if it's any of the debugging commands + // that Ledger supports. + + std::ostream& out(*report.output_stream); + + string verb = *arg++; + + if (verb == "parse") { + expr_t expr(*arg); + + out << "Value expression as input: " << *arg << std::endl; + + out << "Value expression as parsed: "; + expr.print(out, report); + out << std::endl; + + out << std::endl; + out << "--- Parsed tree ---" << std::endl; + expr.dump(out); + + out << std::endl; + out << "--- Calculated value ---" << std::endl; + expr.calc(report).print(out); + out << std::endl; + + return 0; + } + else if (verb == "compile") { + expr_t expr(*arg); + + out << "Value expression as input: " << *arg << std::endl; + out << "Value expression as parsed: "; + expr.print(out, report); + out << std::endl; + + out << std::endl; + out << "--- Parsed tree ---" << std::endl; + expr.dump(out); + + expr.compile(report); + + out << std::endl; + out << "--- Compiled tree ---" << std::endl; + expr.dump(out); + + out << std::endl; + out << "--- Calculated value ---" << std::endl; + expr.calc(report).print(out); + out << std::endl; + + return 0; + } + else if (verb == "eval") { + expr_t expr(*arg); + out << expr.calc(report).strip_annotations() << std::endl; + return 0; + } + else if (verb == "format") { + format_t fmt(*arg); + fmt.dump(out); + return 0; + } + else if (verb == "period") { + interval_t interval(*arg); + + if (! is_valid(interval.begin)) { + out << "Time period has no beginning." << std::endl; + } else { + out << "begin: " << format_date(interval.begin) << std::endl; + out << " end: " << format_date(interval.end) << std::endl; + out << std::endl; + + date_t date = interval.first(); + + for (int i = 0; i < 20; i++) { + out << std::right; + out.width(2); + + out << i << ": " << format_date(date) << std::endl; + + date = interval.increment(date); + if (is_valid(interval.end) && date >= interval.end) + break; + } + } + return 0; + } + + // 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.create_journal()); + + std::size_t count = session.read_data(journal, report.account); + if (count == 0) + throw_(parse_error, "Failed to locate any journal entries; " + "did you specify a valid file with -f?"); + + INFO_FINISH(journal); + + INFO("Found " << count << " entries"); + + 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); + + // Create a command object based on the command verb that was seen + // above. + + function_t command; + + if (verb == "register" || verb == "reg" || verb == "r") + command = reporter<>("register_format"); + else if (verb == "print" || verb == "p") + command = reporter<>("print_format"); + else if (verb == "balance" || verb == "bal" || verb == "b") + command = reporter<format_accounts, acct_handler_ptr, + &report_t::accounts_report>("balance_format"); + else if (verb == "equity") + command = reporter<format_equity, acct_handler_ptr, + &report_t::accounts_report>("print_format"); +#if 0 + else if (verb == "entry") + command = entry_command(); + else if (verb == "dump") + command = dump_command(); + else if (verb == "output") + command = output_command(); + else if (verb == "prices") + command = prices_command(); + else if (verb == "pricesdb") + command = pricesdb_command(); + else if (verb == "csv") + command = csv_command(); + else if (verb == "emacs" || verb == "lisp") + command = emacs_command(); + else if (verb == "xml") + command = xml_command(); +#endif + else { + char buf[128]; + std::strcpy(buf, "cmd_"); + std::strcat(buf, verb.c_str()); + + if (expr_t::ptr_op_t def = report.lookup(buf)) + command = def->as_function(); + + if (! command) + throw_(std::logic_error, string("Unrecognized command '") + verb + "'"); + } + + // Create an argument scope containing the report command's + // arguments, and then invoke the command. + + call_scope_t command_args(report); + + for (strings_list::iterator i = arg; i != args.end(); i++) + command_args.push_back(string_value(*i)); + + INFO_START(command, "Did user command '" << verb << "'"); + + command(command_args); + + INFO_FINISH(command); + +#if 0 + // Write out the binary cache, if need be + + if (session.use_cache && session.cache_dirty && session.cache_file) { + TRACE_START(binary_cache, 1, "Wrote binary journal file"); + + ofstream stream(*session.cache_file); + journal.write(stream); + + TRACE_FINISH(binary_cache, 1); + } +#endif + + // If the user specified a pager, wait for it to exit now + +#ifdef HAVE_UNIX_PIPES + if (! report.output_file && report.pager) { + checked_delete(report.output_stream); + close(pfd[1]); + + // Wait for child to finish + wait(&status); + if (status & 0xffff != 0) + throw_(std::logic_error, "Something went wrong in the pager"); + } +#endif + else if (DO_VERIFY() && report.output_file) { + checked_delete(report.output_stream); + } + + 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 (std::strcmp(argv[i], "--verify") == 0) { +#if defined(VERIFY_ON) + ledger::verify_enabled = true; +#endif + } + else if (std::strcmp(argv[i], "--verbose") == 0 || + std::strcmp(argv[i], "-v") == 0) { +#if defined(LOGGING_ON) + ledger::_log_level = ledger::LOG_INFO; +#endif + } + else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { +#if defined(DEBUG_ON) + ledger::_log_level = ledger::LOG_DEBUG; + ledger::_log_category = argv[i + 1]; + i++; +#endif + } + else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) { +#if defined(TRACING_ON) + ledger::_log_level = ledger::LOG_TRACE; + try { + ledger::_trace_level = boost::lexical_cast<int>(argv[i + 1]); + } + catch (const boost::bad_lexical_cast& e) { + std::cerr << "Argument to --trace must be an integer." + << std::endl; + return 1; + } + i++; +#endif + } + } + + IF_VERIFY() + ledger::initialize_memory_tracing(); + + try { + std::ios::sync_with_stdio(false); + + boost::filesystem::path::default_name_check + (boost::filesystem::portable_posix_name); + + INFO("Ledger starting"); + + std::auto_ptr<ledger::session_t> session(new ledger::session_t); + + ledger::set_session_context(session.get()); + +#if 0 + session->register_parser(new ledger::journal_t::binary_parser_t); +#endif +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + session->register_parser(new ledger::xml_parser_t); + session->register_parser(new ledger::gnucash_parser_t); +#endif +#ifdef HAVE_LIBOFX + session->register_parser(new ledger::ofx_parser_t); +#endif + session->register_parser(new ledger::qif_parser_t); + session->register_parser(new ledger::textual_parser_t); + + session->current_report.reset(new ledger::report_t(*session.get())); + + status = read_and_report(*session->current_report.get(), argc, argv, envp); + + if (DO_VERIFY()) + ledger::set_session_context(); + else + session.release(); // don't free anything! just let it leak + } + catch (const std::exception& err) { + std::cout.flush(); + std::cerr << "Error: " << ledger::error_context() << err.what() + << std::endl; + } + catch (int _status) { + status = _status; + } + + IF_VERIFY() { + INFO("Ledger ended (Boost/libstdc++ may still hold memory)"); + ledger::shutdown_memory_tracing(); + } else { + INFO("Ledger ended"); + } + + return status; +} + +// main.cc ends here. diff --git a/src/mask.cc b/src/mask.cc new file mode 100644 index 00000000..b32be359 --- /dev/null +++ b/src/mask.cc @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "mask.h" +#include "binary.h" + +namespace ledger { + +mask_t::mask_t(const string& pat) : expr() +{ + TRACE_CTOR(mask_t, "const string&"); + *this = pat; +} + +mask_t& mask_t::operator=(const string& pat) +{ + expr.assign(pat.c_str(), regex::perl | regex::icase); + return *this; +} + +void mask_t::read(const char *& data) +{ + *this = binary::read_string(data); +} + +void mask_t::write(std::ostream& out) const +{ + binary::write_string(out, expr.str()); +} + +} // namespace ledger diff --git a/src/mask.h b/src/mask.h new file mode 100644 index 00000000..06161d12 --- /dev/null +++ b/src/mask.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _MASK_H +#define _MASK_H + +#include "utils.h" + +namespace ledger { + +class mask_t +{ + mask_t(); + +public: + boost::regex expr; + + explicit mask_t(const string& pattern); + + mask_t(const mask_t& m) : expr(m.expr) { + TRACE_CTOR(mask_t, "copy"); + } + ~mask_t() throw() { + TRACE_DTOR(mask_t); + } + + mask_t& operator=(const string& other); + + bool match(const string& str) const { + return boost::regex_search(str, expr); + } + + void read(const char *& data); + void write(std::ostream& out) const; +}; + +} // namespace ledger + +#endif // _MASK_H diff --git a/src/ofx.cc b/src/ofx.cc new file mode 100644 index 00000000..5b49bb29 --- /dev/null +++ b/src/ofx.cc @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "journal.h" +#include "ofx.h" +#include "format.h" +#include "datetime.h" +#include "error.h" +#include "debug.h" +#include "util.h" + +#include <libofx.h> + +namespace ledger { + +typedef std::map<const string, account_t *> accounts_map; +typedef std::pair<const string, account_t *> accounts_pair; + +typedef std::map<const string, commodity_t *> commodities_map; +typedef std::pair<const string, commodity_t *> 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_xact_cb(struct OfxXactData data, + void * xact_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_xact(new xact_t(account)); + xact_t * xact = entry->xacts.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; + xact->cost = new amount_t(stream.str() + " " + default_commodity->base_symbol()); + } + + DEBUG("ledger.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 <Unknown>, since it is not specified. + account = curr_journal->find_account("<Unknown>"); + entry->add_xact(new xact_t(account)); + + if (! curr_journal->add_entry(entry)) { + print_entry(std::cerr, *entry); + // jww (2005-02-09): uncomment + //have_error = "The above entry does not balance"; + checked_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, "<?xml", 5) != 0) { + in.clear(); + in.seekg(0, std::ios::beg); + return false; + } + + in.getline(buf, 79); + if (std::strncmp(buf, "<?OFX", 5) != 0 && + std::strncmp(buf, "<?ofx", 5) != 0) { + in.clear(); + in.seekg(0, std::ios::beg); + return false; + } + + in.clear(); + in.seekg(0, std::ios::beg); + return true; +} + +unsigned int ofx_parser_t::parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master, + const path * original_file) +{ + if (! original_file) + return 0; + + curr_journal = journal; + master_account = master ? master : journal->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_xact_cb (libofx_context, ofx_proc_xact_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..58512e8b --- /dev/null +++ b/src/ofx.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _OFX_H +#define _OFX_H + +#include "journal.h" + +namespace ledger { + +class ofx_parser_t : public journal_t::parser_t +{ +public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master = NULL, + const path * original_file = NULL); +}; + +} // namespace ledger + +#endif // _OFX_H diff --git a/src/op.cc b/src/op.cc new file mode 100644 index 00000000..b412c47f --- /dev/null +++ b/src/op.cc @@ -0,0 +1,1130 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "op.h" +#include "scope.h" +#include "binary.h" + +namespace ledger { + +#if 0 +void expr_t::op_t::compute(value_t& result, + const details_t& details, + ptr_op_t context) const +{ + try { + switch (kind) { + case INDEX: + throw compute_error("Cannot directly compute an argument index"); + + case VALUE: + result = as_value(); + break; + + case F_NOW: + result = terminus; + break; + + case AMOUNT: + if (details.xact) { + if (xact_has_xdata(*details.xact) && + xact_xdata_(*details.xact).dflags & XACT_COMPOUND) + result = xact_xdata_(*details.xact).value; + else + result = details.xact->amount; + } + else if (details.account && account_has_xdata(*details.account)) { + result = account_xdata(*details.account).value; + } + else { + result = 0L; + } + break; + + case PRICE: + if (details.xact) { + bool set = false; + if (xact_has_xdata(*details.xact)) { + xact_xdata_t& xdata(xact_xdata_(*details.xact)); + if (xdata.dflags & XACT_COMPOUND) { + result = xdata.value.value(); + set = true; + } + } + if (! set) { + optional<amount_t> value = details.xact->amount.value(); + if (value) + result = *value; + else + result = 0L; + } + } + else if (details.account && account_has_xdata(*details.account)) { + result = account_xdata(*details.account).value.value(); + } + else { + result = 0L; + } + break; + + case COST: + if (details.xact) { + bool set = false; + if (xact_has_xdata(*details.xact)) { + xact_xdata_t& xdata(xact_xdata_(*details.xact)); + if (xdata.dflags & XACT_COMPOUND) { + result = xdata.value.cost(); + set = true; + } + } + + if (! set) { + if (details.xact->cost) + result = *details.xact->cost; + else + result = details.xact->amount; + } + } + else if (details.account && account_has_xdata(*details.account)) { + result = account_xdata(*details.account).value.cost(); + } + else { + result = 0L; + } + break; + + case TOTAL: + if (details.xact && xact_has_xdata(*details.xact)) + result = xact_xdata_(*details.xact).total; + else if (details.account && account_has_xdata(*details.account)) + result = account_xdata(*details.account).total; + else + result = 0L; + break; + case PRICE_TOTAL: + if (details.xact && xact_has_xdata(*details.xact)) + result = xact_xdata_(*details.xact).total.value(); + else if (details.account && account_has_xdata(*details.account)) + result = account_xdata(*details.account).total.value(); + else + result = 0L; + break; + case COST_TOTAL: + if (details.xact && xact_has_xdata(*details.xact)) + result = xact_xdata_(*details.xact).total.cost(); + else if (details.account && account_has_xdata(*details.account)) + result = account_xdata(*details.account).total.cost(); + else + result = 0L; + break; + + case VALUE_EXPR: + if (value_expr::amount_expr.get()) + value_expr::amount_expr->compute(result, details, context); + else + result = 0L; + break; + case TOTAL_EXPR: + if (value_expr::total_expr.get()) + value_expr::total_expr->compute(result, details, context); + else + result = 0L; + break; + + case DATE: + if (details.xact && xact_has_xdata(*details.xact) && + is_valid(xact_xdata_(*details.xact).date)) + result = xact_xdata_(*details.xact).date; + else if (details.xact) + result = details.xact->date(); + else if (details.entry) + result = details.entry->date(); + else + result = terminus; + break; + + case ACT_DATE: + if (details.xact && xact_has_xdata(*details.xact) && + is_valid(xact_xdata_(*details.xact).date)) + result = xact_xdata_(*details.xact).date; + else if (details.xact) + result = details.xact->actual_date(); + else if (details.entry) + result = details.entry->actual_date(); + else + result = terminus; + break; + + case EFF_DATE: + if (details.xact && xact_has_xdata(*details.xact) && + is_valid(xact_xdata_(*details.xact).date)) + result = xact_xdata_(*details.xact).date; + else if (details.xact) + result = details.xact->effective_date(); + else if (details.entry) + result = details.entry->effective_date(); + else + result = terminus; + break; + + case CLEARED: + if (details.xact) + result = details.xact->state == xact_t::CLEARED; + else + result = false; + break; + case PENDING: + if (details.xact) + result = details.xact->state == xact_t::PENDING; + else + result = false; + break; + + case REAL: + if (details.xact) + result = ! (details.xact->has_flags(XACT_VIRTUAL)); + else + result = true; + break; + + case ACTUAL: + if (details.xact) + result = ! (details.xact->has_flags(XACT_AUTO)); + else + result = true; + break; + + case INDEX: + if (details.xact && xact_has_xdata(*details.xact)) + result = long(xact_xdata_(*details.xact).index + 1); + else if (details.account && account_has_xdata(*details.account)) + result = long(account_xdata(*details.account).count); + else + result = 0L; + break; + + case COUNT: + if (details.xact && xact_has_xdata(*details.xact)) + result = long(xact_xdata_(*details.xact).index + 1); + else if (details.account && account_has_xdata(*details.account)) + result = long(account_xdata(*details.account).total_count); + else + result = 0L; + break; + + case DEPTH: + if (details.account) + result = long(details.account->depth); + else + result = 0L; + break; + + case F_PRICE: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + result = result.value(); + break; + } + + case F_DATE: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + result = result.as_datetime(); + break; + } + + case F_DATECMP: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + result = result.as_datetime(); + if (! result) + break; + + arg_index = 0; + expr = find_leaf(context, 1, arg_index); + value_t moment; + expr->compute(moment, details, context); + if (moment.is_type(value_t::DATETIME)) { + result.cast(value_t::INTEGER); + moment.cast(value_t::INTEGER); + result -= moment; + } else { + add_error_context(expr_context(expr)); + throw compute_error("Invalid date passed to datecmp(value,date)"); + } + break; + } + + case F_YEAR: + case F_MONTH: + case F_DAY: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + + if (! result.is_type(value_t::DATETIME)) { + add_error_context(expr_context(expr)); + throw compute_error("Invalid date passed to year|month|day(date)"); + } + + const date_t& moment(result.as_date()); + switch (kind) { + case F_YEAR: + result = (long)moment.year(); + break; + case F_MONTH: + result = (long)moment.month(); + break; + case F_DAY: + result = (long)moment.day(); + break; + default: + break; + } + break; + } + + case F_ARITH_MEAN: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + if (details.xact && xact_has_xdata(*details.xact)) { + expr->compute(result, details, context); + result /= amount_t(long(xact_xdata_(*details.xact).index + 1)); + } + else if (details.account && account_has_xdata(*details.account) && + account_xdata(*details.account).total_count) { + expr->compute(result, details, context); + result /= amount_t(long(account_xdata(*details.account).total_count)); + } + else { + result = 0L; + } + break; + } + + case F_PARENT: + if (details.account && details.account->parent) + left()->compute(result, details_t(*details.account->parent), context); + break; + + case F_ABS: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + result.abs(); + break; + } + + case F_ROUND: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + result.round(); + break; + } + + case F_COMMODITY: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + if (! result.is_type(value_t::AMOUNT)) { + add_error_context(expr_context(expr)); + throw compute_error("Argument to commodity() must be a commoditized amount"); + } + amount_t temp("1"); + temp.set_commodity(result.as_amount().commodity()); + result = temp; + break; + } + + case F_SET_COMMODITY: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + value_t temp; + expr->compute(temp, details, context); + + arg_index = 0; + expr = find_leaf(context, 1, arg_index); + expr->compute(result, details, context); + if (! result.is_type(value_t::AMOUNT)) { + add_error_context(expr_context(expr)); + throw compute_error("Second argument to set_commodity() must be a commoditized amount"); + } + amount_t one("1"); + one.set_commodity(result.as_amount().commodity()); + result = one; + + result *= temp; + break; + } + + case F_QUANTITY: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + + const balance_t * bal = NULL; + switch (result.type()) { + case value_t::BALANCE_PAIR: + bal = &(result.as_balance_pair().quantity()); + // fall through... + + case value_t::BALANCE: + if (! bal) + bal = &result.as_balance(); + + if (bal->amounts.size() < 2) { + result.cast(value_t::AMOUNT); + } else { + value_t temp; + for (balance_t::amounts_map::value_type pair, bal->amounts) { + amount_t x = pair.second; + x.clear_commodity(); + temp += x; + } + result = temp; + assert(temp.is_type(value_t::AMOUNT)); + } + // fall through... + + case value_t::AMOUNT: + result.as_amount_lval().clear_commodity(); + break; + + default: + break; + } + break; + } + + case O_ARG: { + long arg_index = 0; + assert(left()->kind == INDEX); + ptr_op_t expr = find_leaf(context, left()->as_long(), arg_index); + if (expr) + expr->compute(result, details, context); + else + result = 0L; + break; + } + + case O_COMMA: + if (! left()) { + add_error_context(expr_context(*this)); + throw compute_error("Comma operator missing left operand"); + } + if (! right()) { + add_error_context(expr_context(*this)); + throw compute_error("Comma operator missing right operand"); + } + right()->compute(result, details, context); + break; + + case O_DEF: + result = 0L; + break; + + case O_REF: { + assert(left()); + if (right()) { + value_expr args(reduce_leaves(right(), details, context)); + left()->compute(result, details, args.get()); + } else { + left()->compute(result, details, context); + } + break; + } + + case F_VALUE: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + + arg_index = 0; + expr = find_leaf(context, 1, arg_index); + value_t moment; + expr->compute(moment, details, context); + if (! moment.is_type(value_t::DATETIME)) { + add_error_context(expr_context(expr)); + throw compute_error("Invalid date passed to P(value,date)"); + } + result = result.value(moment.as_datetime()); + break; + } + + case O_NOT: + left()->compute(result, details, context); + if (result.strip_annotations()) + result = false; + else + result = true; + break; + + case O_QUES: { + assert(left()); + assert(right()); + assert(right()->kind == O_COL); + left()->compute(result, details, context); + if (result.strip_annotations()) + right()->left()->compute(result, details, context); + else + right()->right()->compute(result, details, context); + break; + } + + case O_AND: + assert(left()); + assert(right()); + left()->compute(result, details, context); + result = result.strip_annotations(); + if (result) + right()->compute(result, details, context); + break; + + case O_OR: + assert(left()); + assert(right()); + left()->compute(result, details, context); + if (! result.strip_annotations()) + right()->compute(result, details, context); + break; + + case O_NEQ: + case O_EQ: + case O_LT: + case O_LTE: + case O_GT: + case O_GTE: { + assert(left()); + assert(right()); + value_t temp; + left()->compute(temp, details, context); + right()->compute(result, details, context); + switch (kind) { + case O_NEQ: result = temp != result; break; + case O_EQ: result = temp == result; break; + case O_LT: result = temp < result; break; + case O_LTE: result = temp <= result; break; + case O_GT: result = temp > result; break; + case O_GTE: result = temp >= result; break; + default: assert(false); break; + } + break; + } + + case O_NEG: + assert(left()); + left()->compute(result, details, context); + result.negate(); + break; + + case O_ADD: + case O_SUB: + case O_MUL: + case O_DIV: { + assert(left()); + assert(right()); + value_t temp; + right()->compute(temp, details, context); + left()->compute(result, details, context); + switch (kind) { + case O_ADD: result += temp; break; + case O_SUB: result -= temp; break; + case O_MUL: result *= temp; break; + case O_DIV: result /= temp; break; + default: assert(false); break; + } + break; + } + + case O_PERC: { + assert(left()); + result = "100.0%"; + value_t temp; + left()->compute(temp, details, context); + result *= temp; + break; + } + + case LAST: + default: + assert(false); + break; + } + } + catch (const std::exception& err) { + add_error_context(expr_context(*this)); + throw err; + } +} +#endif + +expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope) +{ + switch (kind) { + case IDENT: + if (ptr_op_t def = scope.lookup(as_ident())) { +#if 1 + return def; +#else + // jww (2008-08-02): Aren't definitions compiled when they go in? + // Would recompiling here really add any benefit? + return def->compile(scope); +#endif + } + return this; + + default: + break; + } + + if (kind < TERMINALS) + return this; + + ptr_op_t lhs(left()->compile(scope)); + ptr_op_t rhs(kind > UNARY_OPERATORS ? right()->compile(scope) : ptr_op_t()); + + if (lhs == left() && (! rhs || rhs == right())) + return this; + + ptr_op_t intermediate(copy(lhs, rhs)); + + if (lhs->is_value() && (! rhs || rhs->is_value())) + return wrap_value(intermediate->calc(scope)); + + return intermediate; +} + +value_t expr_t::op_t::calc(scope_t& scope) +{ + switch (kind) { + case VALUE: + return as_value(); + + case IDENT: +#if 0 + if (ptr_op_t reference = compile(scope)) { + if (reference != this) + return reference->calc(scope); + } +#endif + throw_(calc_error, "Unknown identifier '" << as_ident() << "'"); + + case FUNCTION: { + // Evaluating a FUNCTION is the same as calling it directly; this happens + // when certain functions-that-look-like-variables (such as "amount") are + // resolved. + call_scope_t call_args(scope); + return as_function()(call_args); + } + + case O_CALL: { + call_scope_t call_args(scope); + + if (right()) + call_args.set_args(right()->calc(scope)); + + ptr_op_t func = left(); + string name; + +#if 0 + // The expression must be compiled beforehand in order to resolve this + // into a function. + if (func->kind == IDENT) { + name = func->as_ident(); + ptr_op_t def = func->compile(scope); + if (def == func) + throw_(calc_error, + "Calling unknown function '" << name << "'"); + func = def; + } +#endif + + if (func->kind != FUNCTION) + throw_(calc_error, "Calling non-function"); + + return func->as_function()(call_args); + } + + case O_MATCH: + assert(right()->is_mask()); + return right()->as_mask().match(left()->calc(scope).to_string()); + + case INDEX: { + const call_scope_t& args(downcast<const call_scope_t>(scope)); + + if (as_index() < args.size()) + return args[as_index()]; + else + throw_(calc_error, "Reference to non-existing argument " << as_index()); + break; + } + + case O_NEQ: + return left()->calc(scope) != right()->calc(scope); + case O_EQ: + return left()->calc(scope) == right()->calc(scope); + case O_LT: + return left()->calc(scope) < right()->calc(scope); + case O_LTE: + return left()->calc(scope) <= right()->calc(scope); + case O_GT: + return left()->calc(scope) > right()->calc(scope); + case O_GTE: + return left()->calc(scope) >= right()->calc(scope); + + case O_ADD: + return left()->calc(scope) + right()->calc(scope); + case O_SUB: + return left()->calc(scope) - right()->calc(scope); + case O_MUL: + return left()->calc(scope) * right()->calc(scope); + case O_DIV: + return left()->calc(scope) / right()->calc(scope); + + case O_NEG: + assert(! right()); + return left()->calc(scope).negate(); + + case O_NOT: + assert(! right()); + return ! left()->calc(scope); + + case O_AND: + return ! left()->calc(scope) ? value_t(false) : right()->calc(scope); + + case O_OR: + if (value_t temp = left()->calc(scope)) + return temp; + else + return right()->calc(scope); + + case O_COMMA: { + value_t result(left()->calc(scope)); + + ptr_op_t next = right(); + while (next) { + ptr_op_t value_op; + if (next->kind == O_COMMA /* || next->kind == O_UNION */) { + value_op = next->left(); + next = next->right(); + } else { + value_op = next; + next = NULL; + } + + result.push_back(value_op->calc(scope)); + } + return result; + } + + case LAST: + default: + assert(false); + break; + } + + return NULL_VALUE; +} + +bool expr_t::op_t::print(std::ostream& out, print_context_t& context) const +{ + bool found = false; + + if (context.start_pos && this == context.op_to_find) { + *context.start_pos = static_cast<unsigned long>(out.tellp()) - 1; + found = true; + } + + string symbol; + + switch (kind) { + case VALUE: { + as_value().print(out, context.relaxed); + break; + } + + case IDENT: + out << as_ident(); + break; + + case FUNCTION: + out << "<FUNCTION>"; + break; + + case INDEX: + out << '@' << as_index(); + break; + + case O_NOT: + out << "!"; + if (left() && left()->print(out, context)) + found = true; + break; + case O_NEG: + out << "-"; + if (left() && left()->print(out, context)) + found = true; + break; + + case O_ADD: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " + "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_SUB: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " - "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_MUL: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " * "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_DIV: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " / "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case O_NEQ: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " != "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_EQ: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " == "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_LT: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " < "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_LTE: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " <= "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_GT: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " > "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_GTE: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " >= "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case O_AND: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " & "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_OR: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " | "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case O_COMMA: + if (left() && left()->print(out, context)) + found = true; + out << ", "; + if (right() && right()->print(out, context)) + found = true; + break; + + case O_CALL: + if (left() && left()->print(out, context)) + found = true; + out << "("; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case O_MATCH: + out << '/'; + if (left() && left()->print(out, context)) + found = true; + out << "/ =~ "; + if (right() && right()->print(out, context)) + found = true; + break; + + case LAST: + default: + assert(false); + break; + } + + if (! symbol.empty()) { + if (amount_t::current_pool->find(symbol)) + out << '@'; + out << symbol; + } + + if (context.end_pos && this == context.op_to_find) + *context.end_pos = static_cast<unsigned long>(out.tellp()) - 1; + + return found; +} + +void expr_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 - " << as_value(); + break; + + case IDENT: + out << "IDENT - " << as_ident(); + break; + + case INDEX: + out << "INDEX - " << as_index(); + break; + + case FUNCTION: + out << "FUNCTION"; + break; + + case O_CALL: out << "O_CALL"; break; + case O_MATCH: out << "O_MATCH"; break; + + case O_NOT: out << "O_NOT"; break; + case O_NEG: out << "O_NEG"; 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_COMMA: out << "O_COMMA"; break; + + case LAST: + default: + assert(false); + 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()); + } + } +} + +void expr_t::op_t::read(const char *& data) +{ +#if 0 + if (! read_bool(data)) + return expr_t::ptr_op_t(); + + expr_t::op_t::kind_t kind; + read_number(data, kind); + + expr_t::ptr_op_t expr = new expr_t::op_t(kind); + + if (kind > expr_t::op_t::TERMINALS) + expr->set_left(read_value_expr(data)); + + switch (expr->kind) { + case expr_t::op_t::INDEX: { + long temp; + read_long(data, temp); + expr->set_index(temp); + break; + } + case expr_t::op_t::VALUE: { + value_t temp; + read_value(data, temp); + expr->set_value(temp); + break; + } + + case expr_t::op_t::MASK: + if (read_bool(data)) + read_mask(data, expr->as_mask_lval()); + break; + + default: + if (kind > expr_t::op_t::TERMINALS) + expr->set_right(read_value_expr(data)); + break; + } + + return expr; +#endif +} + +void expr_t::op_t::write(std::ostream& out) const +{ +#if 0 + if (! expr) { + write_bool(out, false); + return; + } + + write_bool(out, true); + write_number(out, expr->kind); + + if (expr->kind > expr_t::op_t::TERMINALS) + write_value_expr(out, expr->left()); + + switch (expr->kind) { + case expr_t::op_t::INDEX: + write_long(out, expr->as_index()); + break; + case expr_t::op_t::IDENT: + write_long(out, expr->as_ident()); + break; + case expr_t::op_t::VALUE: + write_value(out, expr->as_value()); + break; + + case expr_t::op_t::MASK: + if (expr->as_mask()) { + write_bool(out, true); + write_mask(out, expr->as_mask()); + } else { + write_bool(out, false); + } + break; + + default: + if (expr->kind > expr_t::op_t::TERMINALS) + write_value_expr(out, expr->right()); + break; + } +#endif +} + +#if 0 +class op_predicate : public noncopyable +{ + ptr_op_t op; + + op_predicate(); + +public: + explicit op_predicate(ptr_op_t _op) : op(_op) { + TRACE_CTOR(op_predicate, "ptr_op_t"); + } + ~op_predicate() throw() { + TRACE_DTOR(op_predicate); + } + bool operator()(scope_t& scope) { + return op->calc(scope).to_boolean(); + } +}; + +#endif + +} // namespace ledger diff --git a/src/op.h b/src/op.h new file mode 100644 index 00000000..66618360 --- /dev/null +++ b/src/op.h @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _OP_H +#define _OP_H + +#include "expr.h" +#include "mask.h" + +namespace ledger { + +class expr_t::op_t : public noncopyable +{ + friend class expr_t; + friend class expr_t::parser_t; + + op_t(); + +public: + typedef expr_t::ptr_op_t ptr_op_t; + +private: + mutable short refc; + ptr_op_t left_; + + variant<unsigned int, // used by constant INDEX + value_t, // used by constant VALUE + string, // used by constant IDENT + mask_t, // used by constant MASK + function_t, // used by terminal FUNCTION + ptr_op_t> // used by all binary operators + data; + +public: + enum kind_t { + // Constants + VALUE, + IDENT, + MASK, + INDEX, + + CONSTANTS, + + FUNCTION, + + TERMINALS, + + // Binary operators + O_NOT, + O_NEG, + + UNARY_OPERATORS, + + O_EQ, + O_NEQ, + O_LT, + O_LTE, + O_GT, + O_GTE, + + O_AND, + O_OR, + + O_ADD, + O_SUB, + O_MUL, + O_DIV, + + O_QUERY, + O_COLON, + + O_COMMA, + + O_CALL, + O_MATCH, + + BINARY_OPERATORS, + + OPERATORS, + + LAST + }; + + kind_t kind; + + explicit op_t(const kind_t _kind) : refc(0), kind(_kind) { + TRACE_CTOR(op_t, "const kind_t"); + } + ~op_t() { + TRACE_DTOR(op_t); + assert(refc == 0); + } + + bool is_index() const { + return data.type() == typeid(unsigned int); + } + unsigned int& as_index_lval() { + assert(kind == INDEX); + return boost::get<unsigned int>(data); + } + const unsigned int& as_index() const { + return const_cast<op_t *>(this)->as_index_lval(); + } + void set_index(unsigned int val) { + data = val; + } + + bool is_value() const { + if (kind == VALUE) { + assert(data.type() == typeid(value_t)); + return true; + } + return false; + } + value_t& as_value_lval() { + assert(is_value()); + value_t& val(boost::get<value_t>(data)); + assert(val.valid()); + return val; + } + const value_t& as_value() const { + return const_cast<op_t *>(this)->as_value_lval(); + } + void set_value(const value_t& val) { + assert(val.valid()); + data = val; + } + + bool is_ident() const { + if (kind == IDENT) { + assert(data.type() == typeid(string)); + return true; + } + return false; + } + string& as_ident_lval() { + assert(is_ident()); + return boost::get<string>(data); + } + const string& as_ident() const { + return const_cast<op_t *>(this)->as_ident_lval(); + } + void set_ident(const string& val) { + data = val; + } + + bool is_mask() const { + if (kind == MASK) { + assert(data.type() == typeid(mask_t)); + return true; + } + return false; + } + mask_t& as_mask_lval() { + assert(is_mask()); + return boost::get<mask_t>(data); + } + const mask_t& as_mask() const { + return const_cast<op_t *>(this)->as_mask_lval(); + } + void set_mask(const mask_t& val) { + data = val; + } + void set_mask(const string& expr) { + data = mask_t(expr); + } + + bool is_function() const { + return kind == FUNCTION; + } + function_t& as_function_lval() { + assert(kind == FUNCTION); + return boost::get<function_t>(data); + } + const function_t& as_function() const { + return const_cast<op_t *>(this)->as_function_lval(); + } + void set_function(const function_t& val) { + data = val; + } + + ptr_op_t& left() { + return left_; + } + const ptr_op_t& left() const { + assert(kind > TERMINALS); + return left_; + } + void set_left(const ptr_op_t& expr) { + assert(kind > TERMINALS); + left_ = expr; + } + + ptr_op_t& as_op_lval() { + assert(kind > TERMINALS); + return boost::get<ptr_op_t>(data); + } + const ptr_op_t& as_op() const { + return const_cast<op_t *>(this)->as_op_lval(); + } + + ptr_op_t& right() { + assert(kind > TERMINALS); + return as_op_lval(); + } + const ptr_op_t& right() const { + assert(kind > TERMINALS); + return as_op(); + } + void set_right(const ptr_op_t& expr) { + assert(kind > TERMINALS); + data = expr; + } + +private: + void acquire() const { + DEBUG("ledger.xpath.memory", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + } + void release() const { + DEBUG("ledger.xpath.memory", + "Releasing " << this << ", refc now " << refc - 1); + assert(refc > 0); + if (--refc == 0) + checked_delete(this); + } + + friend inline void intrusive_ptr_add_ref(op_t * op) { + op->acquire(); + } + friend inline void intrusive_ptr_release(op_t * op) { + op->release(); + } + + static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL, + ptr_op_t _right = NULL); + + ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const { + return new_node(kind, _left, _right); + } + +public: + ptr_op_t compile(scope_t& scope); + value_t calc(scope_t& scope); + + struct print_context_t + { + scope_t& scope; + const bool relaxed; + const ptr_op_t& op_to_find; + unsigned long * start_pos; + unsigned long * end_pos; + + // jww (2008-08-01): Is a scope needed here? + print_context_t(scope_t& _scope, + const bool _relaxed = false, + const ptr_op_t& _op_to_find = ptr_op_t(), + unsigned long * _start_pos = NULL, + unsigned long * _end_pos = NULL) + : scope(_scope), relaxed(_relaxed), op_to_find(_op_to_find), + start_pos(_start_pos), end_pos(_end_pos) {} + }; + + bool print(std::ostream& out, print_context_t& context) const; + void dump(std::ostream& out, const int depth) const; + + void read(const char *& data); + void write(std::ostream& out) const; + + static ptr_op_t wrap_value(const value_t& val); + static ptr_op_t wrap_functor(const function_t& fobj); +}; + +inline expr_t::ptr_op_t +expr_t::op_t::new_node(kind_t _kind, ptr_op_t _left, ptr_op_t _right) +{ + ptr_op_t node(new op_t(_kind)); + node->set_left(_left); + node->set_right(_right); + return node; +} + +inline expr_t::ptr_op_t expr_t::op_t::wrap_value(const value_t& val) { + ptr_op_t temp(new op_t(op_t::VALUE)); + temp->set_value(val); + return temp; +} + +inline expr_t::ptr_op_t expr_t::op_t::wrap_functor(const function_t& fobj) { + ptr_op_t temp(new op_t(op_t::FUNCTION)); + temp->set_function(fobj); + return temp; +} + +#define MAKE_FUNCTOR(x) expr_t::op_t::wrap_functor(bind(&x, this, _1)) +#define WRAP_FUNCTOR(x) expr_t::op_t::wrap_functor(x) + +} // namespace ledger + +#endif // _OP_H diff --git a/src/option.cc b/src/option.cc new file mode 100644 index 00000000..b633de32 --- /dev/null +++ b/src/option.cc @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "option.h" + +namespace ledger { + +namespace { + typedef tuple<expr_t::ptr_op_t, bool> op_bool_tuple; + + op_bool_tuple find_option(scope_t& scope, const string& name) + { + char buf[128]; + std::strcpy(buf, "opt_"); + char * p = &buf[4]; + foreach (char ch, name) { + if (ch == '-') + *p++ = '_'; + else + *p++ = ch; + } + *p = '\0'; + + expr_t::ptr_op_t op = scope.lookup(buf); + if (op) + return op_bool_tuple(op, false); + + *p++ = '_'; + *p = '\0'; + + return op_bool_tuple(scope.lookup(buf), true); + } + + op_bool_tuple find_option(scope_t& scope, const char letter) + { + char buf[10]; + std::strcpy(buf, "opt_"); + buf[4] = letter; + buf[5] = '\0'; + + expr_t::ptr_op_t op = scope.lookup(buf); + if (op) + return op_bool_tuple(op, false); + + buf[5] = '_'; + buf[6] = '\0'; + + return op_bool_tuple(scope.lookup(buf), true); + } + + void process_option(const function_t& opt, scope_t& scope, + const char * arg) + { + try { + call_scope_t args(scope); + if (arg) + args.push_back(string_value(arg)); + + opt(args); + } + catch (const std::exception& err) { +#if 0 + add_error_context("While parsing option '--" << opt->long_opt + << "'" << (opt->short_opt != '\0' ? + (string(" (-") + opt->short_opt + "):") : + ": ")); +#endif + throw err; + } + } +} + +void process_option(const string& name, scope_t& scope, + const char * arg) +{ + op_bool_tuple opt(find_option(scope, name)); + if (opt.get<0>()) + process_option(opt.get<0>()->as_function(), scope, arg); +} + +void process_environment(const char ** envp, const string& tag, + 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 == '=') { + try { + process_option(string(buf), scope, q + 1); + } + catch (const std::exception& err) { + add_error_context("While parsing environment variable option '" + << *p << "':"); + throw err; + } + } + } +} + +void process_arguments(int, char ** argv, const bool anywhere, + scope_t& scope, std::list<string>& 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 + 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; + } + + op_bool_tuple opt(find_option(scope, name)); + if (! opt.get<0>()) + throw_(option_error, "illegal option --" << name); + + if (opt.get<1>() && value == NULL) { + value = *++i; + if (value == NULL) + throw_(option_error, "missing option argument for --" << name); + } + process_option(opt.get<0>()->as_function(), scope, value); + } + else if ((*i)[1] == '\0') { + throw_(option_error, "illegal option -"); + } + else { + typedef tuple<expr_t::ptr_op_t, bool, char> op_bool_char_tuple; + + std::list<op_bool_char_tuple> option_queue; + + int x = 1; + for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) { + op_bool_tuple opt(find_option(scope, c)); + if (! opt.get<0>()) + throw_(option_error, "illegal option -" << c); + + option_queue.push_back + (op_bool_char_tuple(opt.get<0>(), opt.get<1>(), c)); + } + + foreach (op_bool_char_tuple& o, option_queue) { + char * value = NULL; + if (o.get<1>()) { + value = *++i; + if (value == NULL) + throw_(option_error, + "missing option argument for -" << o.get<2>()); + } + process_option(o.get<0>()->as_function(), scope, value); + } + } + } +} + +} // namespace ledger diff --git a/src/option.h b/src/option.h new file mode 100644 index 00000000..23439b9d --- /dev/null +++ b/src/option.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _OPTION_H +#define _OPTION_H + +#include "scope.h" + +namespace ledger { + +void process_option(const string& name, scope_t& scope, + const char * arg = NULL); + +void process_environment(const char ** envp, const string& tag, + scope_t& scope); + +void process_arguments(int argc, char ** argv, const bool anywhere, + scope_t& scope, std::list<string>& args); + +DECLARE_EXCEPTION(option_error, std::runtime_error); + +} // namespace ledger + +#endif // _OPTION_H diff --git a/src/output.cc b/src/output.cc new file mode 100644 index 00000000..324a20e4 --- /dev/null +++ b/src/output.cc @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "output.h" + +namespace ledger { + +format_xacts::format_xacts(report_t& _report, const string& format) + : report(_report), last_entry(NULL), last_xact(NULL) +{ + TRACE_CTOR(format_xacts, "report&, const string&"); + + const char * f = format.c_str(); + + if (const char * p = std::strstr(f, "%/")) { + first_line_format.parse(string(f, 0, p - f)); + next_lines_format.parse(string(p + 2)); + } else { + first_line_format.parse(format); + next_lines_format.parse(format); + } +} + +void format_xacts::operator()(xact_t& xact) +{ + std::ostream& out(*report.output_stream); + + if (! xact.has_xdata() || + ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) { + if (last_entry != xact.entry) { + first_line_format.format(out, xact); + last_entry = xact.entry; + } + else if (last_xact && last_xact->date() != xact.date()) { + first_line_format.format(out, xact); + } + else { + next_lines_format.format(out, xact); + } + + xact.xdata().add_flags(XACT_EXT_DISPLAYED); + last_xact = &xact; + } +} + +void format_entries::format_last_entry() +{ + bool first = true; + std::ostream& out(*report.output_stream); + + foreach (xact_t * xact, last_entry->xacts) { + if (xact->has_xdata() && + xact->xdata().has_flags(XACT_EXT_TO_DISPLAY)) { + if (first) { + first_line_format.format(out, *xact); + first = false; + } else { + next_lines_format.format(out, *xact); + } + xact->xdata().add_flags(XACT_EXT_DISPLAYED); + } + } +} + +void format_entries::operator()(xact_t& xact) +{ + xact.xdata().add_flags(XACT_EXT_TO_DISPLAY); + + if (last_entry && xact.entry != last_entry) + format_last_entry(); + + last_entry = xact.entry; +} + +void print_entry(std::ostream& out, const entry_base_t& entry_base, + const string& prefix) +{ + string print_format; + + if (dynamic_cast<const entry_t *>(&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<const auto_entry_t *>(&entry_base)) { + out << "= " << entry->predicate.predicate.text() << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else if (const period_entry_t * entry = + dynamic_cast<const period_entry_t *>(&entry_base)) { + out << "~ " << entry->period_string << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else { + assert(false); + } + +#if 0 + format_entries formatter(out, print_format); + walk_xacts(const_cast<xacts_list&>(entry_base.xacts), formatter); + formatter.flush(); + + clear_xact_xdata cleaner; + walk_xacts(const_cast<xacts_list&>(entry_base.xacts), cleaner); +#endif +} + +void format_accounts::flush() +{ + std::ostream& out(*report.output_stream); + +#if 0 + // jww (2008-08-02): I need access to the output formatter before this is + // going to work. + if (print_final_total) { + assert(out); + assert(account_has_xdata(*session.master)); + + account_xdata_t& xdata(account_xdata(*session.master)); + + if (! show_collapsed && xdata.total) { + out << "--------------------\n"; + xdata.value = xdata.total; + handler->format.format(out, *session.master); + } + } +#endif + + out.flush(); +} + +void format_accounts::operator()(account_t& account) +{ + if (display_account(account)) { + if (! account.parent) { + account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY); + } else { + format.format(*report.output_stream, account); + account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED); + } + } +} + +bool format_accounts::disp_subaccounts_p(account_t& account, + account_t *& to_show) +{ + bool display = false; + unsigned int counted = 0; + bool matches = disp_pred(account); + bool computed = false; + value_t acct_total; + value_t result; + + to_show = NULL; + + foreach (accounts_map::value_type pair, account.accounts) { + if (! disp_pred(*pair.second)) + continue; + + call_scope_t args(*pair.second); + result = report.get_total_expr(args); + if (! computed) { + call_scope_t args(account); + acct_total = report.get_total_expr(args); + computed = true; + } + + if ((result != acct_total) || counted > 0) { + display = matches; + break; + } + to_show = pair.second; + counted++; + } + + return display; +} + +bool format_accounts::display_account(account_t& account) +{ + // Never display an account that has already been displayed. + if (account.has_xdata() && + account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) + return false; + + // At this point, one of two possibilities exists: the account is a + // leaf which matches the predicate restrictions; or it is a parent + // and two or more children must be subtotaled; or it is a parent + // and its child has been hidden by the predicate. So first, + // determine if it is a parent that must be displayed regardless of + // the predicate. + + account_t * account_to_show = NULL; + if (disp_subaccounts_p(account, account_to_show)) + return true; + + return ! account_to_show && disp_pred(account); +} + +format_equity::format_equity(report_t& _report, + const string& _format, + const string& display_predicate) + : format_accounts(_report, "", display_predicate) +{ + const char * f = _format.c_str(); + + if (const char * p = std::strstr(f, "%/")) { + first_line_format.parse(string(f, 0, p - f)); + next_lines_format.parse(string(p + 2)); + } else { + first_line_format.parse(_format); + next_lines_format.parse(_format); + } + + entry_t header_entry; + header_entry.payee = "Opening Balances"; + header_entry._date = current_date; + first_line_format.format(*report.output_stream, header_entry); +} + +void format_equity::flush() +{ + account_t summary(NULL, "Equity:Opening Balances"); + + account_t::xdata_t& xdata(summary.xdata()); + std::ostream& out(*report.output_stream); + + xdata.value = total.negate(); + + if (total.type() >= value_t::BALANCE) { + const balance_t * bal; + if (total.is_type(value_t::BALANCE)) + bal = &(total.as_balance()); + else if (total.is_type(value_t::BALANCE_PAIR)) + bal = &(total.as_balance_pair().quantity()); + else + assert(false); + + foreach (balance_t::amounts_map::value_type pair, bal->amounts) { + xdata.value = pair.second; + xdata.value.negate(); + next_lines_format.format(out, summary); + } + } else { + next_lines_format.format(out, summary); + } + out.flush(); +} + +void format_equity::operator()(account_t& account) +{ + std::ostream& out(*report.output_stream); + + if (display_account(account)) { + if (account.has_xdata()) { + value_t val = account.xdata().value; + + if (val.type() >= value_t::BALANCE) { + const balance_t * bal; + if (val.is_type(value_t::BALANCE)) + bal = &(val.as_balance()); + else if (val.is_type(value_t::BALANCE_PAIR)) + bal = &(val.as_balance_pair().quantity()); + else + assert(false); + + foreach (balance_t::amounts_map::value_type pair, bal->amounts) { + account.xdata().value = pair.second; + next_lines_format.format(out, account); + } + account.xdata().value = val; + } else { + next_lines_format.format(out, account); + } + total += val; + } + account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED); + } +} + +} // namespace ledger diff --git a/src/output.h b/src/output.h new file mode 100644 index 00000000..885b8bc9 --- /dev/null +++ b/src/output.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _OUTPUT_H +#define _OUTPUT_H + +#include "report.h" +#include "handler.h" +#include "format.h" + +namespace ledger { + +class format_xacts : public item_handler<xact_t> +{ +protected: + report_t& report; + format_t first_line_format; + format_t next_lines_format; + entry_t * last_entry; + xact_t * last_xact; + +public: + format_xacts(report_t& _report, const string& format); + virtual ~format_xacts() { + TRACE_DTOR(format_xacts); + } + + virtual void flush() { + report.output_stream->flush(); + } + virtual void operator()(xact_t& xact); +}; + +class format_entries : public format_xacts +{ + public: + format_entries(report_t& _report, const string& format) + : format_xacts(_report, format) { + TRACE_CTOR(format_entries, "report_t&, const string&"); + } + virtual ~format_entries() { + TRACE_DTOR(format_entries); + } + + virtual void format_last_entry(); + + virtual void flush() { + if (last_entry) { + format_last_entry(); + last_entry = NULL; + } + format_xacts::flush(); + } + virtual void operator()(xact_t& xact); + +private: + void print_entry(std::ostream& out, + const entry_base_t& entry, + const string& prefix = ""); +}; + +class format_accounts : public item_handler<account_t> +{ +protected: + report_t& report; + + item_predicate<account_t> disp_pred; + + bool disp_subaccounts_p(account_t& account, account_t *& to_show); + bool display_account(account_t& account); + +public: + format_t format; + + format_accounts(report_t& _report, + const string& _format, + const string& display_predicate = "" /*, + const bool print_final_total = true */) + : report(_report), disp_pred(display_predicate), format(_format) { + TRACE_CTOR(format_accounts, "report&, const string&, const string&"); + } + virtual ~format_accounts() { + TRACE_DTOR(format_accounts); + } + + virtual void flush(); + + virtual void operator()(account_t& account); +}; + +class format_equity : public format_accounts +{ + format_t first_line_format; + format_t next_lines_format; + + mutable value_t total; + + public: + format_equity(report_t& _report, + const string& _format, + const string& display_predicate = ""); + virtual ~format_equity() { + TRACE_DTOR(format_equity); + } + + virtual void flush(); + virtual void operator()(account_t& account); +}; + +} // namespace ledger + +#endif // _OUTPUT_H diff --git a/src/parser.cc b/src/parser.cc new file mode 100644 index 00000000..4795c8f7 --- /dev/null +++ b/src/parser.cc @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "parser.h" + +namespace ledger { + +expr_t::ptr_op_t +expr_t::parser_t::parse_value_term(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node; + + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::VALUE: + node = new op_t(op_t::VALUE); + node->set_value(tok.value); + break; + + case token_t::MASK: + node = new op_t(op_t::MASK); + node->set_mask(tok.value.as_string()); + break; + + case token_t::IDENT: { +#if 0 +#ifdef USE_BOOST_PYTHON + if (tok.value->as_string() == "lambda") // special + try { + char c, buf[4096]; + + std::strcpy(buf, "lambda "); + READ_INTO(in, &buf[7], 4000, c, true); + + ptr_op_t eval = new op_t(op_t::O_EVAL); + ptr_op_t lambda = new op_t(op_t::FUNCTION); + lambda->functor = new python_functor_t(python_eval(buf)); + eval->set_left(lambda); + ptr_op_t sym = new op_t(op_t::SYMBOL); + sym->name = new string("__ptr"); + eval->set_right(sym); + + node = eval; + + goto done; + } + catch(const boost::python::error_already_set&) { + throw_(parse_error, "Error parsing lambda expression"); + } +#endif /* USE_BOOST_PYTHON */ +#endif + + string ident = tok.value.as_string(); + + // An identifier followed by ( represents a function call + tok = next_token(in, tflags); + + if (tok.kind == token_t::LPAREN) { + node = new op_t(op_t::IDENT); + node->set_ident(ident); + + ptr_op_t call_node(new op_t(op_t::O_CALL)); + call_node->set_left(node); + call_node->set_right(parse_value_expr(in, tflags | EXPR_PARSE_PARTIAL)); + + tok = next_token(in, tflags); + if (tok.kind != token_t::RPAREN) + tok.expected(')'); + + node = call_node; + } else { + if (std::isdigit(ident[0])) { + node = new op_t(op_t::INDEX); + node->set_index(lexical_cast<unsigned int>(ident.c_str())); + } else { + node = new op_t(op_t::IDENT); + node->set_ident(ident); + } + push_token(tok); + } + break; + } + + case token_t::LPAREN: + node = parse_value_expr(in, tflags | EXPR_PARSE_PARTIAL); + if (! node) + throw_(parse_error, tok.symbol << " operator not followed by expression"); + + tok = next_token(in, tflags); + if (tok.kind != token_t::RPAREN) + tok.expected(')'); + break; + + default: + push_token(tok); + break; + } + +#if 0 +#ifdef USE_BOOST_PYTHON + done: +#endif +#endif + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_unary_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node; + + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::EXCLAM: { + ptr_op_t term(parse_value_term(in, tflags)); + if (! term) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + + // A very quick optimization + if (term->kind == op_t::VALUE) { + term->as_value_lval().in_place_negate(); + node = term; + } else { + node = new op_t(op_t::O_NOT); + node->set_left(term); + } + break; + } + + case token_t::MINUS: { + ptr_op_t term(parse_value_term(in, tflags)); + if (! term) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + + // A very quick optimization + if (term->kind == op_t::VALUE) { + term->as_value_lval().in_place_negate(); + node = term; + } else { + node = new op_t(op_t::O_NEG); + node->set_left(term); + } + break; + } + + default: + push_token(tok); + node = parse_value_term(in, tflags); + break; + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_mul_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_unary_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::STAR || tok.kind == token_t::KW_DIV) { + ptr_op_t prev(node); + node = new op_t(tok.kind == token_t::STAR ? + op_t::O_MUL : op_t::O_DIV); + node->set_left(prev); + node->set_right(parse_mul_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + + tok = next_token(in, tflags); + } + push_token(tok); + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_add_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_mul_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::PLUS || + tok.kind == token_t::MINUS) { + ptr_op_t prev(node); + node = new op_t(tok.kind == token_t::PLUS ? + op_t::O_ADD : op_t::O_SUB); + node->set_left(prev); + node->set_right(parse_add_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + + tok = next_token(in, tflags); + } + push_token(tok); + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_logic_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_add_expr(in, tflags)); + + if (node) { + op_t::kind_t kind = op_t::LAST; + flags_t _flags = tflags; + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::EQUAL: + if (tflags & EXPR_PARSE_NO_ASSIGN) + tok.rewind(in); + else + kind = op_t::O_EQ; + break; + case token_t::NEQUAL: + kind = op_t::O_NEQ; + break; + case token_t::MATCH: + kind = op_t::O_MATCH; + break; + 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) { + ptr_op_t prev(node); + node = new op_t(kind); + node->set_left(prev); + node->set_right(parse_add_expr(in, _flags)); + + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + } + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_and_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_logic_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::KW_AND) { + ptr_op_t prev(node); + node = new op_t(op_t::O_AND); + node->set_left(prev); + node->set_right(parse_and_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_or_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_and_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::KW_OR) { + ptr_op_t prev(node); + node = new op_t(op_t::O_OR); + node->set_left(prev); + node->set_right(parse_or_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_querycolon_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_or_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::QUERY) { + ptr_op_t prev(node); + node = new op_t(op_t::O_AND); + node->set_left(prev); + node->set_right(parse_or_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + + token_t& next_tok = next_token(in, tflags); + if (next_tok.kind != token_t::COLON) + next_tok.expected(':'); + + prev = node; + node = new op_t(op_t::O_OR); + node->set_left(prev); + node->set_right(parse_or_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_value_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_querycolon_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::COMMA) { + ptr_op_t prev(node); + node = new op_t(op_t::O_COMMA); + node->set_left(prev); + node->set_right(parse_value_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + tok = next_token(in, tflags); + } + + if (tok.kind != token_t::TOK_EOF) { + if (tflags & EXPR_PARSE_PARTIAL) + push_token(tok); + else + tok.unexpected(); + } + } + else if (! (tflags & EXPR_PARSE_PARTIAL)) { + throw_(parse_error, "Failed to parse value expression"); + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse(std::istream& in, const flags_t flags) +{ + try { + ptr_op_t top_node = parse_value_expr(in, flags); + + if (use_lookahead) { + use_lookahead = false; + lookahead.rewind(in); + } + lookahead.clear(); + + return top_node; + } + catch (const std::exception& err) { + add_error_context("While parsing value expression:\n"); +#if 0 + add_error_context(line_context(str, (long)in.tellg() - 1)); +#endif + throw err; + } +} + +} // namespace ledger diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 00000000..294d28ff --- /dev/null +++ b/src/parser.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PARSER_H +#define _PARSER_H + +#include "token.h" +#include "op.h" + +namespace ledger { + +class expr_t::parser_t : public noncopyable +{ +#define EXPR_PARSE_NORMAL 0x00 +#define EXPR_PARSE_PARTIAL 0x01 +#define EXPR_PARSE_NO_MIGRATE 0x02 +#define EXPR_PARSE_NO_REDUCE 0x04 +#define EXPR_PARSE_NO_ASSIGN 0x08 +#define EXPR_PARSE_NO_DATES 0x10 + +public: + typedef uint_least8_t flags_t; + +private: + mutable token_t lookahead; + mutable bool use_lookahead; + + token_t& next_token(std::istream& in, flags_t tflags) const + { + if (use_lookahead) + use_lookahead = false; + else + lookahead.next(in, tflags); + return lookahead; + } + + void push_token(const token_t& tok) const + { + assert(&tok == &lookahead); + use_lookahead = true; + } + + void push_token() const + { + use_lookahead = true; + } + + ptr_op_t parse_value_term(std::istream& in, const flags_t flags) const; + ptr_op_t parse_unary_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_mul_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_add_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_logic_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_and_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_or_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_querycolon_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_value_expr(std::istream& in, const flags_t flags) const; + +public: + parser_t() : use_lookahead(false) { + TRACE_CTOR(parser_t, ""); + } + ~parser_t() throw() { + TRACE_DTOR(parser_t); + } + + ptr_op_t parse(std::istream& in, const flags_t flags = EXPR_PARSE_NORMAL); + ptr_op_t parse(string& str, const flags_t flags = EXPR_PARSE_NORMAL) { + std::istringstream stream(str); + return parse(stream, flags); + } +}; + +} // namespace ledger + +#endif // _PARSER_H diff --git a/src/predicate.h b/src/predicate.h new file mode 100644 index 00000000..624d3d65 --- /dev/null +++ b/src/predicate.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PREDICATE_H +#define _PREDICATE_H + +#include "expr.h" +#include "scope.h" + +namespace ledger { + +template <typename T> +class item_predicate +{ +public: + expr_t predicate; + + item_predicate() { + TRACE_CTOR(item_predicate, ""); + } + item_predicate(const item_predicate& other) : predicate(other.predicate) { + TRACE_CTOR(item_predicate, "copy"); + } + item_predicate(const expr_t& _predicate) : predicate(_predicate) { + TRACE_CTOR(item_predicate, "const expr_t&"); + } + item_predicate(const string& _predicate) : predicate(expr_t(_predicate)) { + TRACE_CTOR(item_predicate, "const string&"); + } + ~item_predicate() throw() { + TRACE_DTOR(item_predicate); + } + + bool operator()(T& item) { + return ! predicate || predicate.calc(item).strip_annotations(); + } +}; + +} // namespace ledger + +#endif // _PREDICATE_H diff --git a/src/pushvar.h b/src/pushvar.h new file mode 100644 index 00000000..a6ec0fab --- /dev/null +++ b/src/pushvar.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file pushvar.h + * @author John Wiegley + * @date Sun May 6 20:10:52 2007 + * + * @brief Adds a facility to C++ for handling "scoped yet global". + */ + +#ifndef _PUSHVAR_H +#define _PUSHVAR_H + +template <typename T> +class push_variable : public boost::noncopyable +{ + push_variable(); + +public: + T& var; + T prev; + bool enabled; + + explicit push_variable(T& _var) + : var(_var), prev(var), enabled(true) { + TRACE_CTOR(push_variable, "T&"); + } + explicit push_variable(T& _var, const T& value) + : var(_var), prev(var), enabled(true) { + TRACE_CTOR(push_variable, "T&, constT&"); + var = value; + } + ~push_variable() throw() { + TRACE_DTOR(push_variable); + if (enabled) + var = prev; + } + + T& operator*() { + return var; + } + T * operator->() { + return &var; + } + + void clear() { + enabled = false; + } +}; + +#endif // _PUSHVAR_H diff --git a/src/qif.cc b/src/qif.cc new file mode 100644 index 00000000..1c0f01b7 --- /dev/null +++ b/src/qif.cc @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "journal.h" +#include "qif.h" +#include "utils.h" + +namespace ledger { + +#define MAX_LINE 1024 + +static char line[MAX_LINE + 1]; +static path pathname; +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, + session_t& session, + journal_t& journal, + account_t * master, + const path * original_file) +{ + std::auto_ptr<entry_t> entry; + std::auto_ptr<amount_t> amount; + + xact_t * xact; + unsigned int count = 0; + account_t * misc = NULL; + commodity_t * def_commodity = NULL; + bool saw_splits = false; + bool saw_category = false; + xact_t * total = NULL; + + entry.reset(new entry_t); + xact = new xact_t(master); + entry->add_xact(xact); + + pathname = journal.sources.back(); + src_idx = journal.sources.size() - 1; + linenum = 1; + + istream_pos_type 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_error("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_error, "QIF files of type " << line << " are not supported."); + break; + + case 'D': + SET_BEG_POS_AND_LINE(); + get_line(in); + // jww (2008-08-01): Is this just a date? + entry->_date = parse_date(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 = amount_t::current_pool->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.negate(); + } else { + total = xact; + } + break; + } + + case 'C': + SET_BEG_POS_AND_LINE(); + c = in.peek(); + if (c == '*' || c == 'X') { + in.get(c); + xact->state = xact_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 xact_t(NULL); + entry->add_xact(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.negate(); // negate, to show correct flow + else + total->account = other; + } + + if (! saw_splits) { + xact_t * nxact = new xact_t(other); + // The amount doesn't need to be set because the code below + // will balance this xact against the other. + entry->add_xact(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 xact_t(master); + entry->add_xact(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..018f85bf --- /dev/null +++ b/src/qif.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QIF_H +#define _QIF_H + +#include "journal.h" + +namespace ledger { + +class qif_parser_t : public journal_t::parser_t +{ + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master = NULL, + const path * original_file = NULL); +}; + +} // namespace ledger + +#endif // _QIF_H diff --git a/src/quotes.cc b/src/quotes.cc new file mode 100644 index 00000000..ac6cb96e --- /dev/null +++ b/src/quotes.cc @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "quotes.h" +#include "utils.h" + +namespace ledger { + +#if 0 +void quotes_by_script::operator()(commodity_base_t& commodity, + const datetime_t& moment, + const datetime_t& date, + const datetime_t& last, + amount_t& price) +{ + DEBUG_CLASS("ledger.quotes.download"); + + DEBUG_("commodity: " << commodity.symbol); + DEBUG_TIME_(current_moment); + DEBUG_TIME_(moment); + DEBUG_TIME_(date); + DEBUG_TIME_(last); + if (commodity.history) + DEBUG_TIME_(commodity.history->last_lookup); + DEBUG_("pricing_leeway is " << pricing_leeway); + + if ((commodity.history && + (current_moment - commodity.history->last_lookup) < pricing_leeway) || + (current_moment - last) < pricing_leeway || + (price && moment > date && (moment - date) <= pricing_leeway)) + return; + + using namespace std; + + 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(current_moment, price); + + commodity.history->last_lookup = current_moment; + cache_dirty = true; + + if (price && ! price_db.empty()) { +#if defined(__GNUG__) && __GNUG__ < 3 + ofstream database(price_db.c_str(), ios::out | ios::app); +#else + ofstream database(price_db.c_str(), ios_base::out | ios_base::app); +#endif + database << "P " << current_moment.to_string("%Y/%m/%d %H:%M:%S") + << " " << commodity.symbol << " " << price << endl; + } + } else { + throw_(std::runtime_error, + "Failed to download price for '" << commodity.symbol + << "' (command: \"getquote " << commodity.symbol << "\")"); + } +} +#endif + +} // namespace ledger diff --git a/src/quotes.h b/src/quotes.h new file mode 100644 index 00000000..3ff70c68 --- /dev/null +++ b/src/quotes.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _QUOTES_H +#define _QUOTES_H + +#include "amount.h" + +namespace ledger { + +#if 0 +class quotes_by_script : public noncopyable, public commodity_t::base_t::updater_t +{ + string price_db; + unsigned long pricing_leeway; + bool& cache_dirty; + + quotes_by_script(); + +public: + quotes_by_script(path _price_db, + unsigned long _pricing_leeway, + bool& _cache_dirty) + : price_db(_price_db), pricing_leeway(_pricing_leeway), + cache_dirty(_cache_dirty) { + TRACE_CTOR(quotes_by_script, "path, unsigned long, bool&"); + } + ~quotes_by_script() throw() { + TRACE_DTOR(quotes_by_script); + } + + virtual void operator()(commodity_base_t& commodity, + const datetime_t& moment, + const datetime_t& date, + const datetime_t& last, + amount_t& price); +}; +#endif + +} // namespace ledger + +#endif // _QUOTES_H diff --git a/src/reconcile.cc b/src/reconcile.cc new file mode 100644 index 00000000..aca1732e --- /dev/null +++ b/src/reconcile.cc @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "reconcile.h" + +namespace ledger { + +#define xact_next(x) reinterpret_cast<xact_t *>(x->xdata().ptr) +#define xact_next_ptr(x) reinterpret_cast<xact_t **>(&x->xdata().ptr) + +static bool search_for_balance(amount_t& amount, + xact_t ** prev, xact_t * next) +{ + for (; next; next = xact_next(next)) { + xact_t * temp = *prev; + *prev = next; + + amount -= next->amount; + if (! amount || + search_for_balance(amount, xact_next_ptr(next), xact_next(next))) + return true; + amount += next->amount; + + *prev = temp; + } + return false; +} + +void reconcile_xacts::push_to_handler(xact_t * first) +{ + for (; first; first = xact_next(first)) + item_handler<xact_t>::operator()(*first); + + item_handler<xact_t>::flush(); +} + +void reconcile_xacts::flush() +{ + value_t cleared_balance; + value_t pending_balance; + + xact_t * first = NULL; + xact_t ** last_ptr = &first; + + foreach (xact_t * xact, xacts) { + if (! is_valid(cutoff) || xact->date() < cutoff) { + switch (xact->state) { + case xact_t::CLEARED: + cleared_balance += xact->amount; + break; + case xact_t::UNCLEARED: + case xact_t::PENDING: + pending_balance += xact->amount; + *last_ptr = xact; + last_ptr = xact_next_ptr(xact); + break; + } + } + } + + if (cleared_balance.type() >= value_t::BALANCE) + throw std::runtime_error("Cannot reconcile accounts with multiple commodities"); + + cleared_balance.cast(value_t::AMOUNT); + balance.cast(value_t::AMOUNT); + + commodity_t& cb_comm = cleared_balance.as_amount().commodity(); + commodity_t& b_comm = balance.as_amount().commodity(); + + balance -= cleared_balance; + if (balance.type() >= value_t::BALANCE) + throw_(std::runtime_error, + "Reconcile balance is not of the same commodity ('" + << b_comm.symbol() << "' != '" << cb_comm.symbol() << "')"); + + // If the amount to reconcile is the same as the pending balance, + // then assume an exact match and return the results right away. + amount_t& to_reconcile(balance.as_amount_lval()); + pending_balance.cast(value_t::AMOUNT); + if (to_reconcile == pending_balance.as_amount() || + search_for_balance(to_reconcile, &first, first)) { + push_to_handler(first); + } else { + throw std::runtime_error("Could not reconcile account!"); + } +} + +} // namespace ledger diff --git a/src/reconcile.h b/src/reconcile.h new file mode 100644 index 00000000..2e133087 --- /dev/null +++ b/src/reconcile.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _RECONCILE_H +#define _RECONCILE_H + +#include "value.h" +#include "iterators.h" +#include "filters.h" + +namespace ledger { + +class reconcile_xacts : public item_handler<xact_t> +{ + value_t balance; + date_t cutoff; + xacts_list xacts; + + reconcile_xacts(); + +public: + reconcile_xacts(xact_handler_ptr handler, + const value_t& _balance, + const date_t& _cutoff) + : item_handler<xact_t>(handler), + balance(_balance), cutoff(_cutoff) { + TRACE_CTOR(reconcile_xacts, + "xact_handler_ptr, const value_t&, const date_t&"); + } + virtual ~reconcile_xacts() throw() { + TRACE_DTOR(reconcile_xacts); + } + + void push_to_handler(xact_t * first); + + virtual void flush(); + virtual void operator()(xact_t& xact) { + xacts.push_back(&xact); + } +}; + +} // namespace ledger + +#endif // _RECONCILE_H diff --git a/src/report.cc b/src/report.cc new file mode 100644 index 00000000..92f20d4f --- /dev/null +++ b/src/report.cc @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "report.h" +#include "reconcile.h" + +namespace ledger { + +#if 0 +void report_t::process_options(const std::string& command, + strings_list::iterator arg, + strings_list::iterator args_end) +{ + // Configure some other options depending on report type + + if (command == "p" || command == "e" || command == "w") { + show_related = + show_all_related = true; + } + else if (command == "E") { + show_subtotal = true; + } + else if (show_related) { + if (command == "r") { + show_inverted = true; + } else { + show_subtotal = true; + show_all_related = true; + } + } + + if (command != "b" && command != "r") + amount_t::keep_base = true; + + // Process remaining command-line arguments + + if (command != "e") { + // Treat the remaining command-line arguments as regular + // expressions, used for refining report results. + + std::list<std::string>::iterator i = arg; + for (; i != args_end; i++) + if (*i == "--") + break; + + if (i != arg) + regexps_to_predicate(command, arg, i, true, + (command == "b" && ! show_subtotal && + display_predicate.empty())); + if (i != args_end && ++i != args_end) + regexps_to_predicate(command, i, args_end); + } + + // Setup the default value for the display predicate + + if (display_predicate.empty()) { + if (command == "b") { + if (! show_empty) + display_predicate = "T"; + if (! show_subtotal) { + if (! display_predicate.empty()) + display_predicate += "&"; + display_predicate += "l<=1"; + } + } + else if (command == "E") { + display_predicate = "t"; + } + else if (command == "r" && ! show_empty) { + display_predicate = "a"; + } + } + + DEBUG_PRINT("ledger.config.predicates", "Predicate: " << predicate); + DEBUG_PRINT("ledger.config.predicates", "Display P: " << display_predicate); + + // Setup the values of %t and %T, used in format strings + + if (! amount_expr.empty()) + ledger::amount_expr = amount_expr; + if (! total_expr.empty()) + ledger::total_expr = total_expr; + + // Now setup the various formatting strings + + if (! date_output_format.empty()) + date_t::output_format = date_output_format; + + amount_t::keep_price = keep_price; + amount_t::keep_date = keep_date; + amount_t::keep_tag = keep_tag; + + if (! report_period.empty() && ! sort_all) + entry_sort = true; +} +#endif + +xact_handler_ptr +report_t::chain_xact_handlers(xact_handler_ptr base_handler, + const bool handle_individual_xacts) +{ + bool remember_components = false; + + xact_handler_ptr handler(base_handler); + + // format_xacts write each xact received to the + // output stream. + if (handle_individual_xacts) { + // truncate_entries cuts off a certain number of _entries_ from + // being displayed. It does not affect calculation. + if (head_entries || tail_entries) + handler.reset(new truncate_entries(handler, head_entries, tail_entries)); + + // filter_xacts will only pass through xacts + // matching the `display_predicate'. + if (! display_predicate.empty()) + handler.reset(new filter_xacts(handler, display_predicate)); + + // calc_xacts computes the running total. When this + // appears will determine, for example, whether filtered + // xacts are included or excluded from the running total. + handler.reset(new calc_xacts(handler)); + + // component_xacts looks for reported xact that + // match the given `descend_expr', and then reports the + // xacts which made up the total for that reported + // xact. + if (! descend_expr.empty()) { + std::list<std::string> descend_exprs; + + std::string::size_type beg = 0; + for (std::string::size_type pos = descend_expr.find(';'); + pos != std::string::npos; + beg = pos + 1, pos = descend_expr.find(';', beg)) + descend_exprs.push_back(std::string(descend_expr, beg, pos - beg)); + descend_exprs.push_back(std::string(descend_expr, beg)); + + for (std::list<std::string>::reverse_iterator i = + descend_exprs.rbegin(); + i != descend_exprs.rend(); + i++) + handler.reset(new component_xacts(handler, *i)); + + remember_components = true; + } + + // reconcile_xacts will pass through only those + // xacts which can be reconciled to a given balance + // (calculated against the xacts which it receives). + if (! reconcile_balance.empty()) { + date_t cutoff = current_date; + if (! reconcile_date.empty()) + cutoff = parse_date(reconcile_date); + handler.reset(new reconcile_xacts + (handler, value_t(reconcile_balance), cutoff)); + } + + // filter_xacts will only pass through xacts + // matching the `secondary_predicate'. + if (! secondary_predicate.empty()) + handler.reset(new filter_xacts(handler, secondary_predicate)); + + // sort_xacts will sort all the xacts it sees, based + // on the `sort_order' value expression. + if (! sort_string.empty()) { + if (entry_sort) + handler.reset(new sort_entries(handler, sort_string)); + else + handler.reset(new sort_xacts(handler, sort_string)); + } + + // changed_value_xacts adds virtual xacts to the + // list to account for changes in market value of commodities, + // which otherwise would affect the running total unpredictably. + if (show_revalued) + handler.reset(new changed_value_xacts(handler, show_revalued_only)); + + // collapse_xacts causes entries with multiple xacts + // to appear as entries with a subtotaled xact for each + // commodity used. + if (show_collapsed) + handler.reset(new collapse_xacts(handler)); + + // subtotal_xacts combines all the xacts it receives + // into one subtotal entry, which has one xact for each + // commodity in each account. + // + // period_xacts is like subtotal_xacts, but it + // subtotals according to time periods rather than totalling + // everything. + // + // dow_xacts is like period_xacts, except that it + // reports all the xacts that fall on each subsequent day + // of the week. + if (show_subtotal) + handler.reset(new subtotal_xacts(handler, remember_components)); + + if (days_of_the_week) + handler.reset(new dow_xacts(handler, remember_components)); + else if (by_payee) + handler.reset(new by_payee_xacts(handler, remember_components)); + + // interval_xacts groups xacts together based on a + // time period, such as weekly or monthly. + if (! report_period.empty()) { + handler.reset(new interval_xacts(handler, report_period, + remember_components)); + handler.reset(new sort_xacts(handler, "d")); + } + } + + // invert_xacts inverts the value of the xacts it + // receives. + if (show_inverted) + handler.reset(new invert_xacts(handler)); + + // related_xacts will pass along all xacts related + // to the xact received. If `show_all_related' is true, + // then all the entry's xacts are passed; meaning that if + // one xact of an entry is to be printed, all the + // xact for that entry will be printed. + if (show_related) + handler.reset(new related_xacts(handler, show_all_related)); + + // This filter_xacts will only pass through xacts + // matching the `predicate'. + if (! predicate.empty()) { + DEBUG("report.predicate", + "Report predicate expression = " << predicate); + handler.reset(new filter_xacts(handler, predicate)); + } + +#if 0 + // budget_xacts takes a set of xacts from a data + // file and uses them to generate "budget xacts" which + // balance against the reported xacts. + // + // forecast_xacts is a lot like budget_xacts, except + // that it adds entries only for the future, and does not balance + // them against anything but the future balance. + + if (budget_flags) { + budget_xacts * budget_handler + = new budget_xacts(handler, budget_flags); + budget_handler->add_period_entries(journal->period_entries); + handler.reset(budget_handler); + + // Apply this before the budget handler, so that only matching + // xacts are calculated toward the budget. The use of + // filter_xacts above will further clean the results so + // that no automated xacts that don't match the filter get + // reported. + if (! predicate.empty()) + handler.reset(new filter_xacts(handler, predicate)); + } + else if (! forecast_limit.empty()) { + forecast_xacts * forecast_handler + = new forecast_xacts(handler, forecast_limit); + forecast_handler->add_period_entries(journal->period_entries); + handler.reset(forecast_handler); + + // See above, under budget_xacts. + if (! predicate.empty()) + handler.reset(new filter_xacts(handler, predicate)); + } +#endif + + if (comm_as_payee) + handler.reset(new set_comm_as_payee(handler)); + else if (code_as_payee) + handler.reset(new set_code_as_payee(handler)); + + return handler; +} + +void report_t::xacts_report(xact_handler_ptr handler) +{ + session_xacts_iterator walker(session); + pass_down_xacts(chain_xact_handlers(handler), walker); + handler->flush(); + + if (DO_VERIFY()) + session.clean_xacts(); +} + +void report_t::entry_report(xact_handler_ptr handler, entry_t& entry) +{ + entry_xacts_iterator walker(entry); + pass_down_xacts(chain_xact_handlers(handler), walker); + handler->flush(); + + if (DO_VERIFY()) + session.clean_xacts(entry); +} + +void report_t::sum_all_accounts() +{ + session_xacts_iterator walker(session); + pass_down_xacts + (chain_xact_handlers(xact_handler_ptr(new set_account_value), false), + walker); + // no flush() needed with set_account_value + session.master->calculate_sums(); +} + +void report_t::accounts_report(acct_handler_ptr handler) +{ + sum_all_accounts(); + + if (sort_string.empty()) { + basic_accounts_iterator walker(*session.master); + pass_down_accounts(handler, walker); + } else { + sorted_accounts_iterator walker(*session.master, sort_string); + pass_down_accounts(handler, walker); + } + handler->flush(); + + if (DO_VERIFY()) { + session.clean_xacts(); + session.clean_accounts(); + } +} + +void report_t::commodities_report(const string& format) +{ +} + +value_t report_t::get_amount_expr(call_scope_t& scope) +{ + return amount_expr.calc(scope); +} + +value_t report_t::get_total_expr(call_scope_t& scope) +{ + return total_expr.calc(scope); +} + +expr_t::ptr_op_t report_t::lookup(const string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'f': + if (std::strncmp(p, "fmt_", 4) == 0) { + p = p + 4; + switch (*p) { + case 't': + return MAKE_FUNCTOR(report_t::get_amount_expr); + case 'T': + return MAKE_FUNCTOR(report_t::get_total_expr); + } + } + break; + + case 'o': + if (std::strncmp(p, "opt_", 4) == 0) { + p = p + 4; + switch (*p) { + case 'a': + if (std::strcmp(p, "amount_") == 0) + return MAKE_FUNCTOR(report_t::option_amount_); + break; + + case 'f': + if (std::strcmp(p, "F_") == 0 || + std::strcmp(p, "format_") == 0) + return MAKE_FUNCTOR(report_t::option_format_); + break; + + case 'j': + if (! (*p + 1)) + return MAKE_FUNCTOR(report_t::option_amount_data); + break; + + case 'J': + if (! (*p + 1)) + return MAKE_FUNCTOR(report_t::option_total_data); + break; + + case 'l': + if (std::strcmp(p, "l_") || std::strcmp(p, "limit_")) + return MAKE_FUNCTOR(report_t::option_limit_); + break; + + case 't': + if (std::strcmp(p, "t_")) + 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 (std::strcmp(p, "T_")) + return MAKE_FUNCTOR(report_t::option_total_); + break; + } + } + break; + } + + return session.lookup(name); +} + +} // namespace ledger diff --git a/src/report.h b/src/report.h new file mode 100644 index 00000000..98b60ce1 --- /dev/null +++ b/src/report.h @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _REPORT_H +#define _REPORT_H + +#include "session.h" +#include "handler.h" + +namespace ledger { + +// These are the elements of any report: +// +// 1. Formatting string used for outputting the underlying ReportedType. +// +// 2. Handler object for the ReportedType. This is constructed using #1, or +// else #1 is ignored completely. This handler object is also constructed +// with the output stream that will be used during formatting. +// +// --- The details of #1 and #2 together represent the ItemHandler. +// +// 3. Mode of the report. Currently there are four modes: +// +// a. Transaction or commodity iteration. In this mode, all the journal's +// entries, the transactions of a specific entry, or all the journal's +// commodities are walked. In the first two cases, it's the underlying +// transactions which are passed to #2; in the second case, each +// commodity is passed to #2. +// +// b. Account iteration. This employs step 'a', but add a prologue and +// epilogue to it. In the prologue it "sums" all account totals and +// subtotals; in the epilogue it calls yet another handler whose job is +// reporting (the handler used in 'a' is only for calculation). +// +// There is one variation on 'b' in which a "totals" line is also +// displayed. +// +// c. Write journal. In this mode, a single function is called that output +// the journal object as a textual file. #2 is used to print out each +// transaction in the journal. +// +// d. Dump binary file. This is just like 'c', except that it dumps out a +// binary file and #2 is completely ignored. +// +// 4. For 'a' and 'b' in #3, there is a different iteration function called, +// depending on whether we're iterating: +// +// a. The transactions of an entry: walk_transactions. +// b. The entries of a journal: walk_entries. +// c. The commodities of a journal: walk_commodities. +// +// 5. Finally, for the 'a' and 'b' reporting modes, there is a variant which +// says that the formatter should be "flushed" after the entities are +// iterated. This does not happen for the commodities iteration, however. + +class report_t : public noncopyable, public scope_t +{ + report_t(); + +public: + optional<path> output_file; + std::ostream * output_stream; + + string format_string; + string date_output_format; + string predicate; + string secondary_predicate; + string display_predicate; + string report_period; + string report_period_sort; + string sort_string; + string descend_expr; + string forecast_limit; + string reconcile_balance; + string reconcile_date; + + expr_t amount_expr; + expr_t total_expr; + + unsigned long budget_flags; + + int head_entries; + int tail_entries; + + bool show_collapsed; + bool show_subtotal; + bool show_totals; + bool show_related; + bool show_all_related; + bool show_inverted; + bool show_empty; + bool days_of_the_week; + bool by_payee; + bool comm_as_payee; + bool code_as_payee; + bool show_revalued; + bool show_revalued_only; + bool keep_price; + bool keep_date; + bool keep_tag; + bool entry_sort; + bool sort_all; + + string account; + optional<path> pager; + + bool raw_mode; + + session_t& session; + + explicit report_t(session_t& _session) + : amount_expr("amount"), + total_expr("total"), + + head_entries(0), + tail_entries(0), + + show_collapsed(false), + show_subtotal(false), + show_totals(false), + show_related(false), + show_all_related(false), + show_inverted(false), + show_empty(false), + days_of_the_week(false), + by_payee(false), + comm_as_payee(false), + code_as_payee(false), + show_revalued(false), + show_revalued_only(false), + keep_price(false), + keep_date(false), + keep_tag(false), + entry_sort(false), + sort_all(false), + + raw_mode(false), + + session(_session) + { + TRACE_CTOR(report_t, "session_t&"); + } + + virtual ~report_t() { + TRACE_DTOR(report_t); + } + + // + // Actual report generation; this is why we're here... + // + + void xacts_report(xact_handler_ptr handler); + void entry_report(xact_handler_ptr handler, entry_t& entry); + void sum_all_accounts(); + void accounts_report(acct_handler_ptr handler); + void commodities_report(const string& format); + + xact_handler_ptr + chain_xact_handlers(xact_handler_ptr handler, + const bool handle_individual_transactions = true); + +#if 0 + ////////////////////////////////////////////////////////////////////// + // + // Basic options + + value_t option_full_help(call_scope_t& args) { // H + option_full_help(std::cout); + throw 0; + } + + value_t option_help(call_scope_t& args) { // h + option_help(std::cout); + throw 0; + } + + value_t option_help_calc(call_scope_t& args) { + option_calc_help(std::cout); + throw 0; + } + + value_t option_help_disp(call_scope_t& args) { + option_disp_help(std::cout); + throw 0; + } + + value_t option_help_comm(call_scope_t& args) { + option_comm_help(std::cout); + throw 0; + } + + value_t option_version(call_scope_t& args) { // v + show_version(std::cout); + throw 0; + } + + value_t option_init_file(call_scope_t& args) { // i: + std::string path = resolve_path(optarg); + if (access(path.c_str(), R_OK) != -1) + config->init_file = path; + else + throw_(std::invalid_argument, + "The init file '" << path << "' does not exist or is not readable"); + } + + value_t option_file(call_scope_t& args) { // f: + if (std::string(optarg) == "-") { + config->data_file = optarg; + } else { + std::string path = resolve_path(optarg); + if (access(path.c_str(), R_OK) != -1) + config->data_file = path; + else + throw_(std::invalid_argument, + "The ledger file '" << path << "' does not exist or is not readable"); + } + } + + value_t option_cache(call_scope_t& args) { // : + config->cache_file = resolve_path(optarg); + } + + value_t option_no_cache(call_scope_t& args) { + config->cache_file = "<none>"; + } + + value_t option_output(call_scope_t& args) { // o: + if (std::string(optarg) != "-") { + std::string path = resolve_path(optarg); + report->output_file = path; + } + } + + value_t option_account(call_scope_t& args) { // a: + config->account = optarg; + } + + value_t option_debug(call_scope_t& args) { // : + config->debug_mode = true; + ::setenv("DEBUG_CLASS", optarg, 1); + } + + value_t option_verbose(call_scope_t& args) { + config->verbose_mode = true; + } + + value_t option_trace(call_scope_t& args) { + config->trace_mode = true; + } + + ////////////////////////////////////////////////////////////////////// + // + // Report filtering + + value_t option_effective(call_scope_t& args) { + xact_t::use_effective_date = true; + } + + value_t option_begin(call_scope_t& args) { // b: + char buf[128]; + interval_t interval(optarg); + if (! interval.begin) + throw_(std::invalid_argument, + "Could not determine beginning of period '" << optarg << "'"); + + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "d>=["; + report->predicate += interval.begin.to_string(); + report->predicate += "]"; + } + + value_t option_end(call_scope_t& args) { // e: + char buf[128]; + interval_t interval(optarg); + if (! interval.begin) + throw_(std::invalid_argument, + "Could not determine end of period '" << optarg << "'"); + + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "d<["; + report->predicate += interval.begin.to_string(); + report->predicate += "]"; + + terminus = interval.begin; + } + + value_t option_current(call_scope_t& args) { // c + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "d<=m"; + } + + value_t option_cleared(call_scope_t& args) { // C + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "X"; + } + + value_t option_uncleared(call_scope_t& args) { // U + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "!X"; + } + + value_t option_real(call_scope_t& args) { // R + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "R"; + } + + value_t option_actual(call_scope_t& args) { // L + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "L"; + } + + value_t option_lots(call_scope_t& args) { + report->keep_price = + report->keep_date = + report->keep_tag = true; + } + + value_t option_lot_prices(call_scope_t& args) { + report->keep_price = true; + } + + value_t option_lot_dates(call_scope_t& args) { + report->keep_date = true; + } + + value_t option_lot_tags(call_scope_t& args) { + report->keep_tag = true; + } +#endif + + ////////////////////////////////////////////////////////////////////// + // + // Output customization + + value_t option_format_(call_scope_t& args) { // F: + format_string = args[0].as_string(); + return true; + } + +#if 0 + value_t option_date_format(call_scope_t& args) { // y: + report->date_output_format = optarg; + } + + value_t option_input_date_format(call_scope_t& args) { // : + config->date_input_format = optarg; + } + + value_t option_balance_format(call_scope_t& args) { // : + config->balance_format = optarg; + } + + value_t option_register_format(call_scope_t& args) { // : + config->register_format = optarg; + } + + value_t option_wide_register_format(call_scope_t& args) { // : + config->wide_register_format = optarg; + } + + value_t option_plot_amount_format(call_scope_t& args) { // : + config->plot_amount_format = optarg; + } + + value_t option_plot_total_format(call_scope_t& args) { // : + config->plot_total_format = optarg; + } + + value_t option_print_format(call_scope_t& args) { // : + config->print_format = optarg; + } + + value_t option_write_hdr_format(call_scope_t& args) { // : + config->write_hdr_format = optarg; + } + + value_t option_write_xact_format(call_scope_t& args) { // : + config->write_xact_format = optarg; + } + + value_t option_equity_format(call_scope_t& args) { // : + config->equity_format = optarg; + } + + value_t option_prices_format(call_scope_t& args) { // : + config->prices_format = optarg; + } + + value_t option_wide(call_scope_t& args) { // w + config->register_format = config->wide_register_format; + } + + value_t option_head(call_scope_t& args) { // : + report->head_entries = std::atoi(optarg); + } + + value_t option_tail(call_scope_t& args) { // : + report->tail_entries = std::atoi(optarg); + } + + value_t option_pager(call_scope_t& args) { // : + config->pager = optarg; + } + + value_t option_truncate(call_scope_t& args) { // : + std::string style(optarg); + if (style == "leading") + format_t::elision_style = format_t::TRUNCATE_LEADING; + else if (style == "middle") + format_t::elision_style = format_t::TRUNCATE_MIDDLE; + else if (style == "trailing") + format_t::elision_style = format_t::TRUNCATE_TRAILING; + else if (style == "abbrev") + format_t::elision_style = format_t::ABBREVIATE; + } + + value_t option_abbrev_len(call_scope_t& args) { // : + format_t::abbrev_length = std::atoi(optarg); + } + + value_t option_empty(call_scope_t& args) { // E + report->show_empty = true; + } + + value_t option_collapse(call_scope_t& args) { // n + report->show_collapsed = true; + } + + value_t option_subtotal(call_scope_t& args) { // s + report->show_subtotal = true; + } + + value_t option_totals(call_scope_t& args) { + report->show_totals = true; + } + + value_t option_sort(call_scope_t& args) { // S: + report->sort_string = optarg; + } + + value_t option_sort_entries(call_scope_t& args) { + report->sort_string = optarg; + report->entry_sort = true; + } + + value_t option_sort_all(call_scope_t& args) { + report->sort_string = optarg; + report->entry_sort = false; + report->sort_all = true; + } + + value_t option_period_sort(call_scope_t& args) { // : + report->sort_string = optarg; + report->entry_sort = true; + } + + value_t option_related(call_scope_t& args) { // r + report->show_related = true; + } + + value_t option_descend(call_scope_t& args) { + std::string arg(optarg); + std::string::size_type beg = 0; + report->descend_expr = ""; + for (std::string::size_type pos = arg.find(';'); + pos != std::string::npos; + beg = pos + 1, pos = arg.find(';', beg)) + report->descend_expr += (std::string("t=={") + + std::string(arg, beg, pos - beg) + "};"); + report->descend_expr += (std::string("t=={") + + std::string(arg, beg) + "}"); + } + + value_t option_descend_if(call_scope_t& args) { + report->descend_expr = optarg; + } + + value_t option_period(call_scope_t& args) { // p: + if (report->report_period.empty()) { + report->report_period = optarg; + } else { + report->report_period += " "; + report->report_period += optarg; + } + + // If the period gives a beginning and/or ending date, make sure to + // modify the calculation predicate (via the --begin and --end + // options) to take this into account. + + interval_t interval(report->report_period); + + if (interval.begin) { + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "d>=["; + report->predicate += interval.begin.to_string(); + report->predicate += "]"; + } + + if (interval.end) { + if (! report->predicate.empty()) + report->predicate += "&"; + report->predicate += "d<["; + report->predicate += interval.end.to_string(); + report->predicate += "]"; + + terminus = interval.end; + } + } + + value_t option_daily(call_scope_t& args) { + if (report->report_period.empty()) + report->report_period = "daily"; + else + report->report_period = std::string("daily ") + report->report_period; + } + + value_t option_weekly(call_scope_t& args) { // W + if (report->report_period.empty()) + report->report_period = "weekly"; + else + report->report_period = std::string("weekly ") + report->report_period; + } + + value_t option_monthly(call_scope_t& args) { // M + if (report->report_period.empty()) + report->report_period = "monthly"; + else + report->report_period = std::string("monthly ") + report->report_period; + } + + value_t option_quarterly(call_scope_t& args) { + if (report->report_period.empty()) + report->report_period = "quarterly"; + else + report->report_period = std::string("quarterly ") + report->report_period; + } + + value_t option_yearly(call_scope_t& args) { // Y + if (report->report_period.empty()) + report->report_period = "yearly"; + else + report->report_period = std::string("yearly ") + report->report_period; + } + + value_t option_dow(call_scope_t& args) { + report->days_of_the_week = true; + } + + value_t option_by_payee(call_scope_t& args) { // P + report->by_payee = true; + } + + value_t option_comm_as_payee(call_scope_t& args) { // x + report->comm_as_payee = true; + } + + value_t option_code_as_payee(call_scope_t& args) { + report->code_as_payee = true; + } + + value_t option_budget(call_scope_t& args) { + report->budget_flags = BUDGET_BUDGETED; + } + + value_t option_add_budget(call_scope_t& args) { + report->budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED; + } + + value_t option_unbudgeted(call_scope_t& args) { + report->budget_flags = BUDGET_UNBUDGETED; + } + + value_t option_forecast(call_scope_t& args) { // : + report->forecast_limit = optarg; + } + + value_t option_reconcile(call_scope_t& args) { // : + report->reconcile_balance = optarg; + } + + value_t option_reconcile_date(call_scope_t& args) { // : + report->reconcile_date = optarg; + } +#endif + + value_t option_limit_(call_scope_t& args) { // l: + if (! predicate.empty()) + predicate += "&"; + predicate += args[0].as_string(); + return true; + } + +#if 0 + value_t option_only(call_scope_t& args) { // : + if (! report->secondary_predicate.empty()) + report->secondary_predicate += "&"; + report->secondary_predicate += "("; + report->secondary_predicate += optarg; + report->secondary_predicate += ")"; + } + + value_t option_display(call_scope_t& args) { // d: + if (! report->display_predicate.empty()) + report->display_predicate += "&"; + report->display_predicate += "("; + report->display_predicate += optarg; + report->display_predicate += ")"; + } +#endif + + value_t option_amount_(call_scope_t& args) { // t: + amount_expr = args[0].as_string(); + return true; + } + + value_t option_total_(call_scope_t& args) { // T: + total_expr = args[0].as_string(); + return true; + } + + value_t get_amount_expr(call_scope_t& scope); + value_t get_total_expr(call_scope_t& scope); + + value_t option_amount_data(call_scope_t&) { // j + format_string = session.plot_amount_format; + return true; + } + + value_t option_total_data(call_scope_t&) { // J + format_string = session.plot_total_format; + return true; + } + +#if 0 + value_t option_ansi(call_scope_t& args) { + format_t::ansi_codes = true; + format_t::ansi_invert = false; + } + + value_t option_ansi_invert(call_scope_t& args) { + format_t::ansi_codes = + format_t::ansi_invert = true; + } + + ////////////////////////////////////////////////////////////////////// + // + // Commodity reporting + + value_t option_base(call_scope_t& args) { // : + amount_t::keep_base = true; + } + + value_t option_price_db(call_scope_t& args) { // : + config->price_db = optarg; + } + + value_t option_price_exp(call_scope_t& args) { // Z: + config->pricing_leeway = std::atol(optarg) * 60; + } + + value_t option_download(call_scope_t& args) { // Q + config->download_quotes = true; + } + + value_t option_quantity(call_scope_t& args) { // O + ledger::amount_expr = "amount"; + ledger::total_expr = "total"; + } + + value_t option_basis(call_scope_t& args) { // B + ledger::amount_expr = "b"; + ledger::total_expr = "B"; + } + + value_t option_price(call_scope_t& args) { // I + ledger::amount_expr = "i"; + ledger::total_expr = "I"; + } + + value_t option_market(call_scope_t& args) { // V + report->show_revalued = true; + + ledger::amount_expr = "v"; + ledger::total_expr = "V"; + } + +#if 0 + namespace { + void parse_price_setting(const char * optarg) + { + char * equals = std::strchr(optarg, '='); + if (! equals) + return; + + while (std::isspace(*optarg)) + optarg++; + while (equals > optarg && std::isspace(*(equals - 1))) + equals--; + + std::string symbol(optarg, 0, equals - optarg); + amount_t price(equals + 1); + + if (commodity_t * commodity = commodity_t::find_or_create(symbol)) { + commodity->add_price(datetime_t::now, price); + commodity->history()->bogus_time = datetime_t::now; + } + } + } +#endif +#endif + + // + // Scope members + // + + virtual expr_t::ptr_op_t lookup(const string& name); +}; + +} // namespace ledger + +#endif // _REPORT_H diff --git a/src/scope.cc b/src/scope.cc new file mode 100644 index 00000000..7a349949 --- /dev/null +++ b/src/scope.cc @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "scope.h" + +namespace ledger { + +void symbol_scope_t::define(const string& name, expr_t::ptr_op_t def) +{ + DEBUG("ledger.xpath.syms", "Defining '" << name << "' = " << def); + + std::pair<symbol_map::iterator, bool> result + = symbols.insert(symbol_map::value_type(name, def)); + if (! result.second) { + symbol_map::iterator i = symbols.find(name); + assert(i != symbols.end()); + symbols.erase(i); + + std::pair<symbol_map::iterator, bool> result2 + = symbols.insert(symbol_map::value_type(name, def)); + if (! result2.second) + throw_(compile_error, + "Redefinition of '" << name << "' in same scope"); + } +} + +expr_t::ptr_op_t symbol_scope_t::lookup(const string& name) +{ + symbol_map::const_iterator i = symbols.find(name); + if (i != symbols.end()) + return (*i).second; + + return child_scope_t::lookup(name); +} + +#if 0 +namespace { + int count_leaves(expr_t::ptr_op_t expr) + { + int count = 0; + if (expr->kind != expr_t::op_t::O_COMMA) { + count = 1; + } else { + count += count_leaves(expr->left()); + count += count_leaves(expr->right()); + } + return count; + } + + expr_t::ptr_op_t reduce_leaves(expr_t::ptr_op_t expr, + expr_t::ptr_op_t context) + { + if (! expr) + return NULL; + + expr_t::ptr_op_t temp; + + if (expr->kind != expr_t::op_t::O_COMMA) { + if (expr->kind < expr_t::op_t::TERMINALS) { + temp.reset(expr); + } else { + temp.reset(new op_t(expr_t::op_t::VALUE)); + temp->set_value(NULL_VALUE); + expr->compute(temp->as_value_lval(), context); + } + } else { + temp.reset(new op_t(expr_t::op_t::O_COMMA)); + temp->set_left(reduce_leaves(expr->left(), context)); + temp->set_right(reduce_leaves(expr->right(), context)); + } + return temp.release(); + } + + expr_t::ptr_op_t find_leaf(expr_t::ptr_op_t context, int goal, long& found) + { + if (! context) + return NULL; + + if (context->kind != expr_t::op_t::O_COMMA) { + if (goal == found++) + return context; + } else { + expr_t::ptr_op_t expr = find_leaf(context->left(), goal, found); + if (expr) + return expr; + expr = find_leaf(context->right(), goal, found); + if (expr) + return expr; + } + return NULL; + } +} +#endif + +} // namespace ledger diff --git a/src/scope.h b/src/scope.h new file mode 100644 index 00000000..88b45d84 --- /dev/null +++ b/src/scope.h @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SCOPE_H +#define _SCOPE_H + +#include "expr.h" +#include "op.h" + +namespace ledger { + +class scope_t +{ +public: + explicit scope_t() { + TRACE_CTOR(scope_t, ""); + } + virtual ~scope_t() { + TRACE_DTOR(scope_t); + } + + virtual expr_t::ptr_op_t lookup(const string& name) = 0; + + value_t resolve(const string& name) { + expr_t::ptr_op_t definition = lookup(name); + if (definition) + return definition->calc(*this); + else + return NULL_VALUE; + } + + virtual optional<scope_t&> find_scope(const std::type_info&, bool = true) { + return none; + } +}; + +template <typename T> +inline T& find_scope(scope_t& scope, bool skip_this = true) { + optional<scope_t&> found = scope.find_scope(typeid(T), skip_this); + assert(found); + return static_cast<T&>(*found); +} + +template <typename T> +inline optional<T&> maybe_find_scope(scope_t& scope, bool skip_this = true) { + optional<scope_t&> found = scope.find_scope(typeid(T), skip_this); + if (found) + return optional<T&>(static_cast<T&>(*found)); + else + return none; +} + +class child_scope_t : public noncopyable, public scope_t +{ +public: + scope_t * parent; + + explicit child_scope_t() : parent(NULL) { + TRACE_CTOR(child_scope_t, ""); + } + explicit child_scope_t(scope_t& _parent) + : parent(&_parent) { + TRACE_CTOR(child_scope_t, "scope_t&"); + } + virtual ~child_scope_t() { + TRACE_DTOR(child_scope_t); + } + + virtual expr_t::ptr_op_t lookup(const string& name) { + if (parent) + return parent->lookup(name); + return expr_t::ptr_op_t(); + } + + virtual optional<scope_t&> find_scope(const std::type_info& type, + bool skip_this = true) { + for (scope_t * ptr = (skip_this ? parent : this); ptr; ) { + if (typeid(*ptr) == type) + return *ptr; + if (child_scope_t * scope = dynamic_cast<child_scope_t *>(ptr)) + ptr = scope->parent; + else + ptr = NULL; + } + return none; + } +}; + +class symbol_scope_t : public child_scope_t +{ + typedef std::map<const string, expr_t::ptr_op_t> symbol_map; + + symbol_map symbols; + +public: + explicit symbol_scope_t() { + TRACE_CTOR(symbol_scope_t, ""); + } + explicit symbol_scope_t(scope_t& _parent) : child_scope_t(_parent) { + TRACE_CTOR(symbol_scope_t, "scope_t&"); + } + virtual ~symbol_scope_t() { + TRACE_DTOR(symbol_scope_t); + } + + void define(const string& name, const value_t& val) { + define(name, expr_t::op_t::wrap_value(val)); + } + void define(const string& name, const function_t& func) { + define(name, expr_t::op_t::wrap_functor(func)); + } + virtual void define(const string& name, expr_t::ptr_op_t def); + + virtual expr_t::ptr_op_t lookup(const string& name); +}; + +class call_scope_t : public child_scope_t +{ + value_t args; + + call_scope_t(); + +public: + explicit call_scope_t(scope_t& _parent) : child_scope_t(_parent) { + TRACE_CTOR(call_scope_t, "scope_t&"); + } + virtual ~call_scope_t() { + TRACE_DTOR(call_scope_t); + } + + void set_args(const value_t& _args) { + args = _args; + } + value_t& value() { + return args; + } + + value_t& operator[](const unsigned int index) { + // jww (2008-07-21): exception here if it's out of bounds + return args[index]; + } + const value_t& operator[](const unsigned int index) const { + // jww (2008-07-21): exception here if it's out of bounds + return args[index]; + } + + void push_back(const value_t& val) { + args.push_back(val); + } + void pop_back() { + args.pop_back(); + } + + const std::size_t size() const { + return args.size(); + } +}; + +template <typename T> +class ptr_t : public noncopyable +{ + T * value; + + ptr_t(); + +public: + ptr_t(scope_t& scope, const string& name) + : value(scope.resolve(name).template as_pointer<T>()) { + TRACE_CTOR(ptr_t, "scope_t&, const string&"); + } + ptr_t(call_scope_t& scope, const unsigned int idx) + : value(scope[idx].template as_pointer<T>()) { + TRACE_CTOR(ptr_t, "call_scope_t&, const unsigned int"); + } + ~ptr_t() throw() { + TRACE_DTOR(ptr_t); + } + + T& operator *() { return *value; } + T * operator->() { return value; } +}; + +template <typename T> +class var_t : public noncopyable +{ + optional<value_t> value; + + var_t(); + +public: + var_t(scope_t& scope, const string& name) + { + TRACE_CTOR(var_t, "scope_t&, const string&"); + + try { + value = scope.resolve(name); + } + catch (...) { + DEBUG("scope.var_t", "Failed lookup var_t(\"" << name << "\")"); + value = none; + } + } + + var_t(call_scope_t& scope, const unsigned int idx) + { + TRACE_CTOR(var_t, "call_scope_t&, const unsigned int"); + + if (idx < scope.size()) + value = scope[idx]; + else + value = none; + } + + ~var_t() throw() { + TRACE_DTOR(var_t); + } + + operator bool() { return value; } + + T& operator *(); + const T& operator *() const; + + T * operator->() { + return &**this; + } + const T * operator->() const { + return &**this; + } +}; + +template <> +inline long& var_t<long>::operator *() { + return value->as_long_lval(); +} +template <> +inline const long& var_t<long>::operator *() const { + return value->as_long(); +} + +template <> +inline string& var_t<string>::operator *() { + return value->as_string_lval(); +} +template <> +inline const string& var_t<string>::operator *() const { + return value->as_string(); +} + +} // namespace ledger + +#endif // _SCOPE_H + diff --git a/src/session.cc b/src/session.cc new file mode 100644 index 00000000..06288444 --- /dev/null +++ b/src/session.cc @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "session.h" +#include "report.h" +#include "handler.h" +#include "iterators.h" +#include "filters.h" + +namespace ledger { + +session_t * session_t::current = NULL; + +#if 0 +boost::mutex session_t::session_mutex; +#endif + +void set_session_context(session_t * session) +{ +#if 0 + session_t::session_mutex.lock(); +#endif + + if (session && ! session_t::current) { + session_t::initialize(); + } + else if (! session && session_t::current) { + session_t::shutdown(); +#if 0 + session_t::session_mutex.unlock(); +#endif + } + + session_t::current = session; +} + +void release_session_context() +{ +#if 0 + session_t::session_mutex.unlock(); +#endif +} + +session_t::session_t() + : register_format + ("%-.10D %-.20P %-.22A %12.67t %!12.80T\n%/" + "%32|%-.22A %12.67t %!12.80T\n"), + wide_register_format + ("%-.10D %-.35P %-.38A %22.108t %!22.132T\n%/" + "%48|%-.38A %22.108t %!22.132T\n"), + print_format + ("\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"), + balance_format + ("%20T %2_%-a\n"), + equity_format + ("\n%D %Y%C%P\n%/ %-34W %12t\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), + +#if 0 + elision_style(ABBREVIATE), +#endif + abbrev_length(2), + + ansi_codes(false), + ansi_invert(false), + + master(new account_t(NULL, "")) +{ + TRACE_CTOR(session_t, ""); +} + +session_t::~session_t() +{ + TRACE_DTOR(session_t); +} + +std::size_t session_t::read_journal(journal_t& journal, + std::istream& in, + const path& pathname, + account_t * master) +{ + if (! master) + master = journal.master; + + foreach (journal_t::parser_t& parser, parsers) + if (parser.test(in)) + return parser.parse(in, *this, journal, master, &pathname); + + return 0; +} + +std::size_t session_t::read_journal(journal_t& journal, + const path& pathname, + account_t * master) +{ + journal.sources.push_back(pathname); + + if (! exists(pathname)) + throw_(std::logic_error, "Cannot read file" << pathname); + + ifstream stream(pathname); + return read_journal(journal, stream, pathname, master); +} + +void session_t::read_init() +{ + if (! init_file) + return; + + if (! exists(*init_file)) + throw_(std::logic_error, "Cannot read init file" << *init_file); + + ifstream init(*init_file); + + // jww (2006-09-15): Read initialization options here! +} + +std::size_t session_t::read_data(journal_t& journal, + const string& master_account) +{ + if (data_file.empty()) + throw_(parse_error, "No journal file was specified (please use -f)"); + + TRACE_START(parser, 1, "Parsing journal file"); + + std::size_t entry_count = 0; + + DEBUG("ledger.cache", "3. use_cache = " << use_cache); + + if (use_cache && cache_file) { + DEBUG("ledger.cache", "using_cache " << cache_file->string()); + cache_dirty = true; + if (exists(*cache_file)) { + push_variable<optional<path> > + save_price_db(journal.price_db, price_db); + + entry_count += read_journal(journal, *cache_file); + if (entry_count > 0) + cache_dirty = false; + } + } + + if (entry_count == 0) { + account_t * acct = NULL; + if (! master_account.empty()) + acct = journal.find_account(master_account); + + journal.price_db = price_db; + if (journal.price_db && exists(*journal.price_db)) { + if (read_journal(journal, *journal.price_db)) { + throw_(parse_error, "Entries not allowed in price history file"); + } else { + DEBUG("ledger.cache", + "read price database " << journal.price_db->string()); + journal.sources.pop_back(); + } + } + + DEBUG("ledger.cache", "rejected cache, parsing " << data_file.string()); + if (data_file == "-") { + use_cache = false; + journal.sources.push_back("/dev/stdin"); + entry_count += read_journal(journal, std::cin, "/dev/stdin", acct); + } + else if (exists(data_file)) { + entry_count += read_journal(journal, data_file, acct); + if (journal.price_db) + journal.sources.push_back(*journal.price_db); + clean_accounts(); + } + } + + VERIFY(journal.valid()); + + TRACE_STOP(parser, 1); + + return entry_count; +} + +namespace { + account_t * find_account_re_(account_t * account, const mask_t& regexp) + { + if (regexp.match(account->fullname())) + return account; + + foreach (accounts_map::value_type& pair, account->accounts) + if (account_t * a = find_account_re_(pair.second, regexp)) + return a; + + return NULL; + } +} + +account_t * session_t::find_account_re(const string& regexp) +{ + return find_account_re_(master.get(), mask_t(regexp)); +} + +void session_t::clean_xacts() +{ + session_xacts_iterator walker(*this); + pass_down_xacts + (xact_handler_ptr(new clear_xact_xdata), walker); +} + +void session_t::clean_xacts(entry_t& entry) +{ + entry_xacts_iterator walker(entry); + pass_down_xacts(xact_handler_ptr(new clear_xact_xdata), walker); +} + +void session_t::clean_accounts() +{ + basic_accounts_iterator acct_walker(*master); + pass_down_accounts(acct_handler_ptr(new clear_account_xdata), + acct_walker); +} + +#if 0 +value_t session_t::resolve(const string& name, expr_t::scope_t& locals) +{ + const char * p = name.c_str(); + switch (*p) { + case 'd': +#if 0 + if (name == "date_format") { + // jww (2007-04-18): What to do here? + return string_value(moment_t::output_format); + } +#endif + break; + + case 'n': + switch (*++p) { + case 'o': + if (name == "now") + return value_t(now); + break; + } + break; + + case 'r': + if (name == "register_format") + return string_value(register_format); + break; + } + return expr_t::scope_t::resolve(name, locals); +} +#endif + +expr_t::ptr_op_t session_t::lookup(const string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'b': + if (std::strcmp(p, "balance_format") == 0) + return expr_t::op_t::wrap_value(string_value(balance_format)); + break; + + case 'e': + if (std::strcmp(p, "equity_format") == 0) + return expr_t::op_t::wrap_value(string_value(equity_format)); + break; + + case 'p': + if (std::strcmp(p, "plot_amount_format") == 0) + return expr_t::op_t::wrap_value(string_value(plot_amount_format)); + else if (std::strcmp(p, "plot_total_format") == 0) + return expr_t::op_t::wrap_value(string_value(plot_total_format)); + else if (std::strcmp(p, "prices_format") == 0) + return expr_t::op_t::wrap_value(string_value(prices_format)); + else if (std::strcmp(p, "pricesdb_format") == 0) + return expr_t::op_t::wrap_value(string_value(pricesdb_format)); + else if (std::strcmp(p, "print_format") == 0) + return expr_t::op_t::wrap_value(string_value(print_format)); + break; + + case 'r': + if (std::strcmp(p, "register_format") == 0) + return expr_t::op_t::wrap_value(string_value(register_format)); + break; + + case 'w': + if (std::strcmp(p, "wide_register_format") == 0) + return expr_t::op_t::wrap_value(string_value(wide_register_format)); + else if (std::strcmp(p, "write_hdr_format") == 0) + return expr_t::op_t::wrap_value(string_value(write_hdr_format)); + else if (std::strcmp(p, "write_xact_format") == 0) + return expr_t::op_t::wrap_value(string_value(write_xact_format)); + break; + + case 'o': + if (std::strncmp(p, "opt_", 4) == 0) { + p = p + 4; + switch (*p) { + case 'd': + if (std::strcmp(p, "debug_") == 0) + return MAKE_FUNCTOR(session_t::option_debug_); + break; + + case 'f': + if ((*(p + 1) == '_' && ! *(p + 2)) || + 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, "version") == 0) + return MAKE_FUNCTOR(session_t::option_version); + else if (std::strcmp(p, "verify") == 0) + return MAKE_FUNCTOR(session_t::option_verify); + break; + } + } + break; + } + + return expr_t::ptr_op_t(); +} + +// jww (2007-04-26): All of Ledger should be accessed through a +// session_t object +void session_t::initialize() +{ + amount_t::initialize(); + value_t::initialize(); + expr_t::initialize(); +} + +void session_t::shutdown() +{ + expr_t::shutdown(); + value_t::shutdown(); + amount_t::shutdown(); +} + +} // namespace ledger diff --git a/src/session.h b/src/session.h new file mode 100644 index 00000000..dc78d6f5 --- /dev/null +++ b/src/session.h @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SESSION_H +#define _SESSION_H + +#include "scope.h" +#include "journal.h" +#include "account.h" +#include "format.h" + +namespace ledger { + +class report_t; + +class session_t : public noncopyable, public scope_t +{ + static void initialize(); + static void shutdown(); + + friend void set_session_context(session_t * session); + friend void release_session_context(); + +public: + static session_t * current; + + scoped_ptr<report_t> current_report; + + path data_file; + optional<path> init_file; + optional<path> cache_file; + optional<path> 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; + + datetime_t now; + date_t today; + + format_t::elision_style_t elision_style; + int abbrev_length; + + bool ansi_codes; + bool ansi_invert; + + ptr_list<journal_t> journals; + ptr_list<journal_t::parser_t> parsers; + scoped_ptr<commodity_pool_t> commdity_pool; + scoped_ptr<account_t> master; + mutable accounts_map accounts_cache; + + session_t(); + virtual ~session_t(); + + journal_t * create_journal() { + journal_t * journal = new journal_t(this); + journals.push_back(journal); + return journal; + } + void close_journal(journal_t * journal) { + for (ptr_list<journal_t>::iterator i = journals.begin(); + i != journals.end(); + i++) + if (&*i == journal) { + journals.erase(i); + return; + } + assert(false); + checked_delete(journal); + } + + std::size_t read_journal(journal_t& journal, + std::istream& in, + const path& pathname, + account_t * master = NULL); + std::size_t read_journal(journal_t& journal, + const path& pathname, + account_t * master = NULL); + + void read_init(); + + std::size_t read_data(journal_t& journal, + const string& master_account = ""); + + void register_parser(journal_t::parser_t * parser) { + parsers.push_back(parser); + } + void unregister_parser(journal_t::parser_t * parser) { + for (ptr_list<journal_t::parser_t>::iterator i = parsers.begin(); + i != parsers.end(); + i++) + if (&*i == parser) { + parsers.erase(i); + return; + } + assert(false); + checked_delete(parser); + } + + // + // Dealing with accounts + // + + void add_account(account_t * acct) { + master->add_account(acct); + } + bool remove_account(account_t * acct) { + return master->remove_account(acct); + } + + 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_map::value_type(name, account)); + return account; + } + account_t * find_account_re(const string& regexp); + + void clean_accounts(); + + void clean_xacts(); + void clean_xacts(entry_t& entry); + + // + // Scope members + // + + virtual expr_t::ptr_op_t lookup(const string& name); + + // + // Help options + // + + value_t option_version(scope_t&) { + std::cout << "Ledger " << ledger::version << ", the command-line accounting tool"; + std::cout << "\n\nCopyright (c) 2003-2008, John Wiegley. All rights reserved.\n\n\ +This program is made available under the terms of the BSD Public License.\n\ +See LICENSE file included with the distribution for details and disclaimer.\n"; + std::cout << "\n(modules: gmp, pcre"; +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + std::cout << ", xml"; +#endif +#ifdef HAVE_LIBOFX + std::cout << ", ofx"; +#endif + std::cout << ")\n"; + return NULL_VALUE; + } + + // + // Debug options + // + + value_t option_trace_(scope_t&) { + return NULL_VALUE; + } + value_t option_debug_(scope_t&) { + return NULL_VALUE; + } + value_t option_verify(scope_t&) { + return NULL_VALUE; + } + + value_t option_verbose(scope_t&) { +#if defined(LOGGING_ON) + if (_log_level < LOG_INFO) + _log_level = LOG_INFO; +#endif + return NULL_VALUE; + } + + // + // Option handlers + // + + value_t option_file_(call_scope_t& args) { + assert(args.size() == 1); + data_file = args[0].as_string(); + return NULL_VALUE; + } + +#if 0 +#if defined(USE_BOOST_PYTHON) + value_t option_import_(call_scope_t& args) { + python_import(optarg); + return NULL_VALUE; + } + value_t option_import_stdin(call_scope_t& args) { + python_eval(std::cin, PY_EVAL_MULTI); + return NULL_VALUE; + } +#endif +#endif +}; + +/** + * This sets the current session context, transferring all static + * globals to point at the data structures related to this session. + * Although Ledger itself is not thread-safe, by locking, switching + * session context, then unlocking after the operation is done, + * multiple threads can sequentially make use of the library. Thus, a + * session_t maintains all of the information relating to a single + * usage of the Ledger library. + */ +void set_session_context(session_t * session = NULL); + +} // namespace ledger + +#endif // _SESSION_H diff --git a/src/system.hh b/src/system.hh new file mode 100644 index 00000000..221b81ef --- /dev/null +++ b/src/system.hh @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#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 <algorithm> +#include <exception> +#include <typeinfo> +#include <stdexcept> +#include <iostream> +#include <streambuf> +#include <iomanip> +#include <fstream> +#include <sstream> +#include <iterator> +#include <list> +#include <map> +#include <memory> +#include <new> +#include <stack> +#include <string> +#include <vector> + +#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; + } +} + +typedef unsigned long istream_pos_type; +typedef unsigned long ostream_pos_type; + +#else // ! (defined(__GNUG__) && __GNUG__ < 3) + +typedef std::istream::pos_type istream_pos_type; +typedef std::ostream::pos_type ostream_pos_type; + +#endif + +#include <cassert> +#include <cctype> +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> + +#if defined __FreeBSD__ && __FreeBSD__ <= 4 +// FreeBSD has a broken isspace macro, so don't use it +#undef isspace(c) +#endif + +#include <sys/stat.h> + +#ifdef WIN32 +#include <io.h> +#else +#include <unistd.h> +#endif + +#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM) +#include <pwd.h> +#endif + +#if defined(HAVE_NL_LANGINFO) +#include <langinfo.h> +#endif + +#include <gmp.h> + +extern "C" { +#if defined(HAVE_EXPAT) +#include <expat.h> // expat XML parser +#elif defined(HAVE_XMLPARSE) +#include <xmlparse.h> // expat XML parser +#endif +} + +#if defined(HAVE_LIBOFX) +#include <libofx.h> +#endif + +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/any.hpp> +#include <boost/cast.hpp> +#include <boost/current_function.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/filesystem/convenience.hpp> +#include <boost/filesystem/exception.hpp> +#include <boost/filesystem/fstream.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/path.hpp> +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> +#include <boost/lambda/bind.hpp> +#include <boost/lambda/lambda.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/multi_index/hashed_index.hpp> +#include <boost/multi_index/key_extractors.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include <boost/multi_index/random_access_index.hpp> +#include <boost/multi_index/sequenced_index.hpp> +#include <boost/multi_index_container.hpp> +#include <boost/operators.hpp> +#include <boost/optional.hpp> +#include <boost/ptr_container/ptr_list.hpp> +#include <boost/ptr_container/ptr_vector.hpp> +#include <boost/regex.hpp> +#include <boost/variant.hpp> + +#endif // _SYSTEM_HH diff --git a/src/textual.cc b/src/textual.cc new file mode 100644 index 00000000..e46ae22a --- /dev/null +++ b/src/textual.cc @@ -0,0 +1,1131 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(__GNUG__) && __GNUG__ < 3 +#define _XOPEN_SOURCE +#endif + +#include "textual.h" +#include "expr.h" +#include "parser.h" +#include "session.h" +#include "option.h" +#include "acconf.h" + +#define TIMELOG_SUPPORT 1 + +namespace ledger { + +#define MAX_LINE 1024 + +static path pathname; +static unsigned int linenum; +static unsigned int src_idx; +static accounts_map account_aliases; + +static std::list<std::pair<path, int> > include_stack; + +#ifdef TIMELOG_SUPPORT +struct time_entry_t +{ + datetime_t checkin; + account_t * account; + string desc; + + time_entry_t() : account(NULL) { + TRACE_CTOR(time_entry_t, ""); + } + time_entry_t(const datetime_t& _checkin, + account_t * _account = NULL, + const string& _desc = "") + : checkin(_checkin), account(_account), desc(_desc) { + TRACE_CTOR(time_entry_t, "const datetime_t&, account_t *, const string&"); + } + time_entry_t(const time_entry_t& entry) + : checkin(entry.checkin), account(entry.account), + desc(entry.desc) { + TRACE_CTOR(time_entry_t, "copy"); + } + ~time_entry_t() throw() { + TRACE_DTOR(time_entry_t); + } +}; +#endif + +namespace { + optional<expr_t> parse_amount_expr(std::istream& in, + amount_t& amount, + xact_t * xact, + unsigned short flags = 0) + { + expr_t expr(in, flags | EXPR_PARSE_PARTIAL); + + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Parsed an amount expression"); + +#ifdef DEBUG_ENABLED + DEBUG_IF("ledger.textual.parse") { + if (_debug_stream) { + ledger::dump_value_expr(*_debug_stream, expr); + *_debug_stream << std::endl; + } + } +#endif + + if (expr) { + amount = expr.calc(*xact).as_amount(); + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "The transaction amount is " << amount); + return expr; + } + return none; + } +} + +xact_t * parse_xact(char * line, account_t * account, entry_t * entry = NULL) +{ + std::istringstream in(line); + + string err_desc; + try { + + // The account will be determined later... + std::auto_ptr<xact_t> xact(new xact_t(NULL)); + if (entry) + xact->entry = entry; + + // Parse the state flag + + char p = peek_next_nonws(in); + switch (p) { + case '*': + xact->state = xact_t::CLEARED; + in.get(p); + p = peek_next_nonws(in); + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Parsed the CLEARED flag"); + break; + case '!': + xact->state = xact_t::PENDING; + in.get(p); + p = peek_next_nonws(in); + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Parsed the PENDING flag"); + break; + } + + // Parse the account name + + unsigned long account_beg = static_cast<unsigned long>(in.tellg()); + unsigned long account_end = account_beg; + while (! in.eof()) { + in.get(p); + if (in.eof() || (std::isspace(p) && + (p == '\t' || in.peek() == EOF || + std::isspace(in.peek())))) + break; + account_end++; + } + + if (account_beg == account_end) + throw parse_error("No account was specified"); + + char * b = &line[account_beg]; + char * e = &line[account_end]; + if ((*b == '[' && *(e - 1) == ']') || + (*b == '(' && *(e - 1) == ')')) { + xact->add_flags(XACT_VIRTUAL); + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a virtual account name"); + if (*b == '[') { + xact->add_flags(XACT_BALANCE); + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a balanced virtual account name"); + } + b++; e--; + } + + string name(b, e - b); + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Parsed account name " << name); + if (account_aliases.size() > 0) { + accounts_map::const_iterator i = account_aliases.find(name); + if (i != account_aliases.end()) + xact->account = (*i).second; + } + if (! xact->account) + xact->account = account->find_account(name); + + // Parse the optional amount + + bool saw_amount = false; + + if (in.good() && ! in.eof()) { + p = peek_next_nonws(in); + if (in.eof()) + goto finished; + if (p == ';') + goto parse_note; + if (p == '=' && entry) + goto parse_assign; + + try { + unsigned long beg = static_cast<unsigned long>(in.tellg()); + + xact->amount_expr = + parse_amount_expr(in, xact->amount, xact.get(), + EXPR_PARSE_NO_REDUCE | EXPR_PARSE_NO_ASSIGN); + saw_amount = true; + + if (! xact->amount.is_null()) { + xact->amount.reduce(); + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Reduced amount is " << xact->amount); + } + + // We don't need to store the actual expression that resulted in the + // amount if it's constant + if (xact->amount_expr) { + if (xact->amount_expr->is_constant()) + xact->amount_expr = expr_t(); + + unsigned long end = static_cast<unsigned long>(in.tellg()); + xact->amount_expr->set_text(string(line, beg, end - beg)); + } + } + catch (const std::exception& err) { + add_error_context("While parsing transaction amount:\n"); + throw err; + } + } + + // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST) + + if (in.good() && ! in.eof()) { + p = peek_next_nonws(in); + if (p == '@') { + if (! saw_amount) + throw parse_error + ("Transaction cannot have a cost expression with an amount"); + + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Found a price indicator"); + bool per_unit = true; + in.get(p); + if (in.peek() == '@') { + in.get(p); + per_unit = false; + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "And it's for a total price"); + } + + if (in.good() && ! in.eof()) { + xact->cost = amount_t(); + + try { + unsigned long beg = static_cast<unsigned long>(in.tellg()); + + xact->cost_expr = + parse_amount_expr(in, *xact->cost, xact.get(), + EXPR_PARSE_NO_MIGRATE | + EXPR_PARSE_NO_ASSIGN); + + if (xact->cost_expr) { + unsigned long end = static_cast<unsigned long>(in.tellg()); + if (per_unit) + xact->cost_expr->set_text(string("@") + + string(line, beg, end - beg)); + else + xact->cost_expr->set_text(string("@@") + + string(line, beg, end - beg)); + } + } + catch (const std::exception& err) { + add_error_context("While parsing transaction cost:\n"); + throw err; + } + + if (xact->cost->sign() < 0) + throw parse_error("A transaction's cost may not be negative"); + + amount_t per_unit_cost(*xact->cost); + if (per_unit) + *xact->cost *= xact->amount; + else + per_unit_cost /= xact->amount; + + if (xact->amount.commodity() && + ! xact->amount.commodity().annotated) { + if (xact->entry) + xact->amount.annotate(annotation_t(per_unit_cost, + xact->entry->actual_date(), + xact->entry->code)); + else + xact->amount.annotate(annotation_t(per_unit_cost)); + } + + 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); + } + } + } + + parse_assign: + if (entry != NULL) { + // Add this amount to the related account now + + account_t::xdata_t& xdata(xact->account->xdata()); + + if (! xact->amount.is_null()) { + if (xdata.value.is_null()) + xdata.value = xact->amount; + else + xdata.value += xact->amount; + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "XACT assign: account total = " << xdata.value); + } + + // Parse the optional assigned (= AMOUNT) + + if (in.good() && ! in.eof()) { + p = peek_next_nonws(in); + if (p == '=') { + in.get(p); + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Found a balance assignment indicator"); + if (in.good() && ! in.eof()) { + amount_t amt; + + try { + unsigned long beg = static_cast<unsigned long>(in.tellg()); + + optional<expr_t> total_expr = + parse_amount_expr(in, amt, xact.get(), EXPR_PARSE_NO_MIGRATE); + + if (amt.is_null()) + throw parse_error + ("An assigned balance must evaluate to a constant value"); + + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "XACT assign: parsed amt = " << amt); + + if (total_expr) { + unsigned long end = static_cast<unsigned long>(in.tellg()); + total_expr->set_text(string("=") + + string(line, beg, end - beg)); + } + + // jww (2008-08-02): Save total_expr somewhere! + + amount_t diff; + if (xdata.value.is_amount()) { + diff = amt - xdata.value.as_amount(); + } + else if (xdata.value.is_balance()) { + optional<amount_t> comm_bal = + xdata.value.as_balance().commodity_amount(amt.commodity()); + diff = amt - (comm_bal ? *comm_bal : amount_t(0L)); + } + else if (xdata.value.is_balance_pair()) { + optional<amount_t> comm_bal = + xdata.value.as_balance_pair().commodity_amount(amt.commodity()); + diff = amt - (comm_bal ? *comm_bal : amount_t(0L)); + } + else { + diff = amt; + } + + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "XACT assign: diff = " << diff); + + if (! diff.is_realzero()) { + if (! xact->amount.is_null()) { + xact_t * temp = + new xact_t(xact->account, diff, + XACT_GENERATED | XACT_CALCULATED); + entry->add_xact(temp); + + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Created balancing transaction"); + } else { + xact->amount = diff; + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Overwrite null transaction"); + } + xdata.value = amt; + } + } + catch (const std::exception& err) { + add_error_context("While parsing assigned balance:\n"); + throw err; + } + } + } + } + } + + // Parse the optional note + + parse_note: + if (in.good() && ! in.eof()) { + p = peek_next_nonws(in); + if (p == ';') { + in.get(p); + p = peek_next_nonws(in); + xact->note = &line[static_cast<unsigned long>(in.tellg())]; + 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_date(p); + } + if (buf[0]) + xact->_date = parse_date(buf); + } + } + } + + finished: + return xact.release(); + + } + catch (const std::exception& err) { + add_error_context("While parsing transaction:\n"); + add_error_context(line_context(line, static_cast<unsigned long>(in.tellg()) - 1)); + throw err; + } +} + +bool parse_xacts(std::istream& in, + account_t * account, + entry_base_t& entry, + const string& kind, + unsigned long beg_pos) +{ + TRACE_START(entry_xacts, 1, "Time spent parsing transactions:"); + + 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; + + int len = std::strlen(line); + if (line[len - 1] == '\r') + line[--len] = '\0'; + + beg_pos += len + 1; + linenum++; + + if (line[0] == ' ' || line[0] == '\t') { + char * p = skip_ws(line); + if (! *p) + break; + } + if (xact_t * xact = parse_xact(line, account)) { + entry.add_xact(xact); + added = true; + } + } + + TRACE_STOP(entry_xacts, 1); + + return added; +} + +entry_t * parse_entry(std::istream& in, char * line, account_t * master, + textual_parser_t& parser, unsigned long& pos) +{ + TRACE_START(entry_text, 1, "Time spent preparing entry text:"); + + std::auto_ptr<entry_t> curr(new entry_t); + + // Parse the date + + char * next = next_element(line); + + if (char * p = std::strchr(line, '=')) { + *p++ = '\0'; + curr->_date_eff = parse_date(p); + } + curr->_date = parse_date(line); + + // Parse the optional cleared flag: * + + xact_t::state_t state = xact_t::UNCLEARED; + if (next) { + switch (*next) { + case '*': + state = xact_t::CLEARED; + next = skip_ws(++next); + break; + case '!': + state = xact_t::PENDING; + next = skip_ws(++next); + break; + } + } + + // Parse the optional code: (TEXT) + + if (next && *next == '(') { + if (char * p = std::strchr(next++, ')')) { + *p++ = '\0'; + curr->code = next; + next = skip_ws(p); + } + } + + // Parse the description text + + curr->payee = next ? next : "<Unspecified payee>"; + + TRACE_STOP(entry_text, 1); + + // Parse all of the xacts associated with this entry + + TRACE_START(entry_details, 1, "Time spent parsing entry details:"); + + unsigned long end_pos; + unsigned long beg_line = linenum; + + while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { + unsigned long beg_pos = static_cast<unsigned long>(in.tellg()); + + line[0] = '\0'; + in.getline(line, MAX_LINE); + if (in.eof() && line[0] == '\0') + break; + + int len = std::strlen(line); + if (line[len - 1] == '\r') + line[--len] = '\0'; + + end_pos = beg_pos + len + 1; + linenum++; + + if (line[0] == ' ' || line[0] == '\t') { + char * p = skip_ws(line); + if (! *p) + break; + } + + if (xact_t * xact = parse_xact(line, master, curr.get())) { + if (state != xact_t::UNCLEARED && + xact->state == xact_t::UNCLEARED) + xact->state = state; + + xact->beg_pos = beg_pos; + xact->beg_line = beg_line; + xact->end_pos = end_pos; + xact->end_line = linenum; + pos = end_pos; + + curr->add_xact(xact); + } + + if (in.eof()) + break; + } + + TRACE_STOP(entry_details, 1); + + return curr.release(); +} + +static inline void parse_symbol(char *& p, string& symbol) +{ + if (*p == '"') { + char * q = std::strchr(p + 1, '"'); + if (! q) + throw parse_error("Quoted commodity symbol lacks closing quote"); + symbol = string(p + 1, 0, q - p - 1); + p = q + 2; + } else { + char * q = next_element(p); + symbol = p; + if (q) + p = q; + else + p += symbol.length(); + } + if (symbol.empty()) + throw parse_error("Failed to parse commodity"); +} + +bool textual_parser_t::test(std::istream& in) const +{ + char buf[5]; + + in.read(buf, 5); + if (std::strncmp(buf, "<?xml", 5) == 0) { +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + throw parse_error("Ledger file contains XML data, but format was not recognized"); +#else + throw parse_error("Ledger file contains XML data, but no XML support present"); +#endif + } + + in.clear(); + in.seekg(0, std::ios::beg); + assert(in.good()); + return true; +} + +static void clock_out_from_timelog(std::list<time_entry_t>& time_entries, + const datetime_t& when, + account_t * account, + const char * desc, + journal_t& journal) +{ + time_entry_t event; + + if (time_entries.size() == 1) { + event = time_entries.back(); + time_entries.clear(); + } + else if (time_entries.empty()) { + throw parse_error("Timelog check-out event without a check-in"); + } + else if (! account) { + throw parse_error + ("When multiple check-ins are active, checking out requires an account"); + } + else { + bool found = false; + + for (std::list<time_entry_t>::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_error + ("Timelog check-out event does not match any current check-ins"); + } + + if (desc && event.desc.empty()) { + event.desc = desc; + desc = NULL; + } + + std::auto_ptr<entry_t> curr(new entry_t); + curr->_date = when.date(); + curr->code = desc ? desc : ""; + curr->payee = event.desc; + + if (when < event.checkin) + throw parse_error + ("Timelog check-out date less than corresponding check-in"); + + char buf[32]; + std::sprintf(buf, "%lds", long((when - event.checkin).seconds())); + amount_t amt; + amt.parse(buf); + assert(amt.valid()); + + xact_t * xact + = new xact_t(event.account, amt, XACT_VIRTUAL); + xact->state = xact_t::CLEARED; + curr->add_xact(xact); + + if (! journal.add_entry(curr.get())) + throw parse_error("Failed to record 'out' timelog entry"); + else + curr.release(); +} + +unsigned int textual_parser_t::parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master, + const path * original_file) +{ + TRACE_START(parsing_total, 1, "Total time spent parsing text:"); + + static bool added_auto_entry_hook = false; + static char line[MAX_LINE + 1]; + unsigned int count = 0; + unsigned int errors = 0; + + std::list<account_t *> account_stack; + auto_entry_finalizer_t auto_entry_finalizer(&journal); + std::list<time_entry_t> time_entries; + + if (! master) + master = journal.master; + + account_stack.push_front(master); + + pathname = journal.sources.back(); + src_idx = journal.sources.size() - 1; + linenum = 1; + + INFO("Parsing file '" << pathname.string() << "'"); + + unsigned long beg_pos = static_cast<unsigned long>(in.tellg()); + unsigned long end_pos; + unsigned long beg_line = linenum; + + while (in.good() && ! in.eof()) { + try { + in.getline(line, MAX_LINE); + if (in.eof()) + break; + + int len = std::strlen(line); + if (line[len - 1] == '\r') + line[--len] = '\0'; + + end_pos = beg_pos + len + 1; + linenum++; + + switch (line[0]) { + case '\0': + break; + + case ' ': + case '\t': { + char * p = skip_ws(line); + if (*p) + throw parse_error("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(parse_datetime(date), + account_stack.front()->find_account(p), n ? n : ""); + + if (! time_entries.empty()) + foreach (time_entry_t& time_entry, time_entries) + if (event.account == time_entry.account) + throw parse_error("Cannot double check-in to the same account"); + + time_entries.push_back(event); + break; + } + + case 'o': + case 'O': + if (time_entries.empty()) { + throw parse_error("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 + (time_entries, 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)); + assert(amt.valid()); + amount_t::current_pool->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'; + // jww (2008-04-22): NYI! +#if 0 + parse_conversion(line + 1, p); +#endif + } + 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; + datetime_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); + assert(price.valid()); + + if (commodity_t * commodity = + amount_t::current_pool->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 = + amount_t::current_pool->find_or_create(symbol)) + commodity->add_flags(COMMODITY_STYLE_NOMARKET); + break; + } + + case 'Y': // set the current year + current_year = std::atoi(skip_ws(line + 1)); + break; + +#ifdef TIMELOG_SUPPORT + case 'h': + case 'b': +#endif + case '*': // comment line + case ';': // comment line + break; + + case '-': { // option setting + char * p = next_element(line); + if (! p) { + p = std::strchr(line, '='); + if (p) + *p++ = '\0'; + } + process_option(line + 2, session, p); + break; + } + + 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_xacts(in, 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_error, "Parsing time period '" << line << "'"); + + if (parse_xacts(in, 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 new parse_error("Period entry failed to balance"); + } + } + break; + } + + case '@': + case '!': { // directive + char * p = next_element(line); + string word(line + 1); + if (word == "include") { + push_variable<path> save_pathname(pathname); + push_variable<unsigned int> save_src_idx(src_idx); + push_variable<unsigned long> save_beg_pos(beg_pos); + push_variable<unsigned long> save_end_pos(end_pos); + push_variable<unsigned int> save_linenum(linenum); + + pathname = p; +#if 0 + if (pathname[0] != '/' && pathname[0] != '\\' && pathname[0] != '~') { + string::size_type pos = save_pathname.prev.rfind('/'); + if (pos == string::npos) + pos = save_pathname.prev.rfind('\\'); + if (pos != string::npos) + pathname = string(save_pathname.prev, 0, pos + 1) + pathname; + } + pathname = resolve_path(pathname); + + DEBUG("ledger.textual.include", "line " << linenum << ": " << + "Including path '" << pathname << "'"); + + include_stack.push_back(std::pair<path, int> + (journal.sources.back(), linenum - 1)); + count += parse_journal_file(pathname, config, journal, + account_stack.front()); + include_stack.pop_back(); +#endif + } + 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 xact + // parser to resolve alias references. + account_t * acct = account_stack.front()->find_account(e); + std::pair<accounts_map::iterator, bool> result + = account_aliases.insert(accounts_map::value_type(b, acct)); + assert(result.second); + } + } + else if (word == "def") { + expr_t def(p); + def.compile(session); // causes definitions to be established + } + break; + } + + default: { + unsigned long pos = beg_pos; + TRACE_START(entries, 1, "Time spent handling entries:"); + if (entry_t * entry = + parse_entry(in, line, 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 = pos; + entry->end_line = linenum; + count++; + } else { + checked_delete(entry); + throw parse_error("Entry does not balance"); + } + } else { + throw parse_error("Failed to parse entry"); + } + end_pos = pos; + TRACE_STOP(entries, 1); + break; + } + } + } + catch (const std::exception& err) { + for (std::list<std::pair<path, int> >::reverse_iterator i = + include_stack.rbegin(); + i != include_stack.rend(); + i++) { + add_error_context("In file included from "); +#if 0 + add_error_context(include_context((*i).first, (*i).second)); +#endif + } + add_error_context(file_context(pathname, linenum - 1)); + + std::cout.flush(); + std::cerr << "Error: " << error_context() << err.what() + << std::endl; + errors++; + } + beg_pos = end_pos; + } + + if (! time_entries.empty()) { + std::list<account_t *> accounts; + + foreach (time_entry_t& time_entry, time_entries) + accounts.push_back(time_entry.account); + + foreach (account_t * account, accounts) + clock_out_from_timelog(time_entries, current_time, account, NULL, + journal); + + assert(time_entries.empty()); + } + + if (added_auto_entry_hook) + journal.remove_entry_finalizer(&auto_entry_finalizer); + + if (errors > 0) + throw static_cast<int>(errors); + + TRACE_STOP(parsing_total, 1); + + return count; +} + +void write_textual_journal(journal_t& journal, + const path& pathname, + xact_handler_ptr formatter, + const string& write_hdr_format, + std::ostream& out) +{ + unsigned long index = 0; + path found; + + if (pathname.empty()) { + if (! journal.sources.empty()) + found = *journal.sources.begin(); + } else { +#ifdef HAVE_REALPATH + char buf1[PATH_MAX]; + char buf2[PATH_MAX]; + + ::realpath(pathname.string().c_str(), buf1); + + foreach (const path& path, journal.sources) { + ::realpath(path.string().c_str(), buf2); + if (std::strcmp(buf1, buf2) == 0) { + found = path; + break; + } + index++; + } +#else + foreach (const path& path, journal.sources) { + if (pathname == path) { + found = path; + break; + } + index++; + } +#endif + } + + if (found.empty()) + throw_(std::runtime_error, + "Journal does not refer to file '" << pathname << "'"); + + entries_list::iterator el = journal.entries.begin(); + auto_entries_list::iterator al = journal.auto_entries.begin(); + period_entries_list::iterator pl = journal.period_entries.begin(); + + unsigned long pos = 0; + + format_t hdr_fmt(write_hdr_format); + boost::filesystem::ifstream in(found); + + while (! in.eof()) { + entry_base_t * base = NULL; + if (el != journal.entries.end() && pos == (*el)->beg_pos) { + hdr_fmt.format(out, **el); + base = *el++; + } + else if (al != journal.auto_entries.end() && pos == (*al)->beg_pos) { + out << "= " << (*al)->predicate.predicate.text() << '\n'; + base = *al++; + } + else if (pl != journal.period_entries.end() && pos == (*pl)->beg_pos) { + out << "~ " << (*pl)->period_string << '\n'; + base = *pl++; + } + + char c; + if (base) { + foreach (xact_t * xact, base->xacts) { + if (! xact->has_flags(XACT_AUTO)) { + xact->xdata().add_flags(XACT_EXT_TO_DISPLAY); + (*formatter)(*xact); + } + } + formatter->flush(); + + while (pos < base->end_pos) { + in.get(c); + pos = static_cast<unsigned long>(in.tellg()); // pos++; + } + } else { + in.get(c); + pos = static_cast<unsigned long>(in.tellg()); // pos++; + out.put(c); + } + } +} + +} // namespace ledger diff --git a/src/textual.h b/src/textual.h new file mode 100644 index 00000000..8064d0db --- /dev/null +++ b/src/textual.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TEXTUAL_H +#define _TEXTUAL_H + +#include "journal.h" +#include "handler.h" + +namespace ledger { + +class textual_parser_t : public journal_t::parser_t +{ +public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master = NULL, + const path * original_file = NULL); +}; + +xact_t * parse_xact_text(char * line, account_t * account); +xact_t * parse_xact(std::istream& in, account_t * account); + +void write_textual_journal(journal_t& journal, + const path& pathname, + xact_handler_ptr& formatter, + const string& write_hdr_format, + std::ostream& out); + +#if 0 +class include_context : public file_context +{ + public: + include_context(const path& 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.string() << "\", 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..97c0c242 --- /dev/null +++ b/src/times.cc @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils.h" // this brings in times.h + +namespace ledger { + +namespace { +#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(); +} + +const datetime_t& current_time(time_now); +const date_t& current_date(date_now); + int current_year(current_date.year()); + +namespace { + const char * formats[] = { + "%y/%m/%d", + "%Y/%m/%d", + "%m/%d", + "%y.%m.%d", + "%Y.%m.%d", + "%m.%d", + "%y-%m-%d", + "%Y-%m-%d", + "%m-%d", + "%a", + "%A", + "%b", + "%B", + "%Y", + NULL + }; +} + +optional<string> input_date_format; +string output_date_format = "%Y/%m/%d"; + +namespace { + bool parse_date_mask(const char * date_str, std::tm& result) + { + if (input_date_format) { + std::memset(&result, -1, sizeof(std::tm)); + if (strptime(date_str, input_date_format->c_str(), &result)) + return true; + } + for (const char ** f = formats; *f; f++) { + std::memset(&result, -1, sizeof(std::tm)); + if (strptime(date_str, *f, &result)) + return true; + } + return false; + } + + bool parse_date(const char * date_str, std::tm& result, const int year) + { + if (! parse_date_mask(date_str, result)) + return false; + + result.tm_hour = 0; + result.tm_min = 0; + result.tm_sec = 0; + + if (result.tm_year == -1) + result.tm_year = ((year == -1) ? current_year : year) - 1900; + + if (result.tm_mon == -1) + result.tm_mon = 0; + + if (result.tm_mday == -1) + result.tm_mday = 1; + + return true; + } + + bool quick_parse_date(const char * date_str, std::tm& result) + { + return parse_date(date_str, result, current_year); + } +} + +datetime_t parse_datetime(const char * str) +{ + std::tm when; + // jww (2008-08-01): This needs to look for HH:MM:SS as well. + quick_parse_date(str, when); + return posix_time::ptime_from_tm(when); +} + +date_t parse_date(const char * str) +{ + std::tm when; + quick_parse_date(str, when); + return gregorian::date_from_tm(when); +} + +date_t interval_t::first(const optional<date_t>& moment) const +{ + if (! is_valid(begin)) + throw_(date_error, + "Use of interval_t::first() with specifying a range start"); + + date_t quant(begin); + + if (! advanced) + advanced = true; + + if (moment && *moment > quant) { + // Find an efficient starting point for the upcoming while loop. + // We want a date early enough that the range will be correct, but + // late enough that we don't spend hundreds of thousands of loops + // skipping through time. + + date_t quant(moment->year(), gregorian::Jan, 1); + date_t temp; + while (*moment >= (temp = increment(quant))) { + if (quant == temp) + break; + quant = temp; + } + } + return quant; +} + +date_t interval_t::increment(const date_t& moment) const +{ + date_t future(moment); + + if (years) future += gregorian::years(years); + if (months) future += gregorian::months(months); + if (days) future += gregorian::days(days); + + return future; +} + +namespace { + void parse_inclusion_specifier(const string& word, + date_t * begin, date_t * end) + { + struct std::tm when; + + if (! parse_date_mask(word.c_str(), when)) + throw_(date_error, "Could not parse date mask: " << word); + + when.tm_hour = 0; + when.tm_min = 0; + when.tm_sec = 0; + when.tm_isdst = -1; + + bool saw_year = true; + bool saw_mon = true; + bool saw_day = true; + + if (when.tm_year == -1) { + when.tm_year = current_year - 1900; + saw_year = false; + } + if (when.tm_mon == -1) { + when.tm_mon = 0; + saw_mon = false; + } else { + saw_year = false; // don't increment by year if month used + } + if (when.tm_mday == -1) { + when.tm_mday = 1; + saw_day = false; + } else { + saw_mon = false; // don't increment by month if day used + saw_year = false; // don't increment by year if day used + } + + if (begin) { + *begin = gregorian::date_from_tm(when); + if (end) + *end = interval_t(saw_day ? 1 : 0, saw_mon ? 1 : 0, + saw_year ? 1 : 0).increment(*begin); + } + else if (end) { + *end = gregorian::date_from_tm(when); + } + } + + inline void read_lower_word(std::istream& in, string& word) { + in >> word; + for (int i = 0, l = word.length(); i < l; i++) + word[i] = std::tolower(word[i]); + } + + void parse_date_words(std::istream& in, string& word, + date_t * begin, date_t * end) + { + string type; + + bool mon_spec = false; + char buf[32]; + + if (word == "this" || word == "last" || word == "next") { + type = word; + if (! in.eof()) + read_lower_word(in, word); + else + word = "month"; + } else { + type = "this"; + } + + if (word == "month") { + time_t now = to_time_t(current_time); + std::strftime(buf, 31, "%B", localtime(&now)); + word = buf; + mon_spec = true; + } + else if (word == "year") { + std::sprintf(buf, "%04d", current_year); + word = buf; + } + + parse_inclusion_specifier(word, begin, end); + + if (type == "last") { + if (mon_spec) { + if (begin) + *begin = interval_t(0, -1, 0).increment(*begin); + if (end) + *end = interval_t(0, -1, 0).increment(*end); + } else { + if (begin) + *begin = interval_t(0, 0, -1).increment(*begin); + if (end) + *end = interval_t(0, 0, -1).increment(*end); + } + } + else if (type == "next") { + if (mon_spec) { + if (begin) + *begin = interval_t(0, 1, 0).increment(*begin); + if (end) + *end = interval_t(0, 1, 0).increment(*end); + } else { + if (begin) + *begin = interval_t(0, 0, 1).increment(*begin); + if (end) + *end = interval_t(0, 0, 1).increment(*end); + } + } + } +} + +void interval_t::parse(std::istream& in) +{ + string word; + + while (! in.eof()) { + read_lower_word(in, word); + if (word == "every") { + read_lower_word(in, word); + if (std::isdigit(word[0])) { + int quantity = std::atol(word.c_str()); + read_lower_word(in, word); + if (word == "days") + days = quantity; + else if (word == "weeks") + days = 7 * quantity; + else if (word == "months") + months = quantity; + else if (word == "quarters") + months = 3 * quantity; + else if (word == "years") + years = quantity; + } + else if (word == "day") + days = 1; + else if (word == "week") + days = 7; + else if (word == "month") + months = 1; + else if (word == "quarter") + months = 3; + else if (word == "year") + years = 1; + } + else if (word == "daily") + days = 1; + else if (word == "weekly") + days = 7; + else if (word == "biweekly") + days = 14; + else if (word == "monthly") + months = 1; + else if (word == "bimonthly") + months = 2; + else if (word == "quarterly") + months = 3; + else if (word == "yearly") + years = 1; + else if (word == "this" || word == "last" || word == "next") { + parse_date_words(in, word, &begin, &end); + } + else if (word == "in") { + read_lower_word(in, word); + parse_date_words(in, word, &begin, &end); + } + else if (word == "from" || word == "since") { + read_lower_word(in, word); + parse_date_words(in, word, &begin, NULL); + } + else if (word == "to" || word == "until") { + read_lower_word(in, word); + parse_date_words(in, word, NULL, &end); + } + else { + parse_inclusion_specifier(word, &begin, &end); + } + } +} + +} // namespace ledger diff --git a/src/times.h b/src/times.h new file mode 100644 index 00000000..306477f5 --- /dev/null +++ b/src/times.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TIMES_H +#define _TIMES_H + +namespace ledger { + +DECLARE_EXCEPTION(datetime_error, std::runtime_error); +DECLARE_EXCEPTION(date_error, std::runtime_error); + +typedef boost::posix_time::ptime datetime_t; +typedef datetime_t::time_duration_type time_duration_t; + +inline bool is_valid(const datetime_t& moment) { + return ! moment.is_not_a_date_time(); +} + +typedef boost::gregorian::date date_t; +typedef boost::gregorian::date_duration date_duration_t; + +inline bool is_valid(const date_t& moment) { + return ! moment.is_not_a_date(); +} + +extern const datetime_t& current_time; +extern const date_t& current_date; +extern int current_year; +extern optional<string> input_date_format; +extern string output_date_format; + +inline datetime_t parse_datetime(const string& str) { + return parse_datetime(str.c_str()); +} +datetime_t parse_datetime(const char * str); + +inline date_t parse_date(const string& str) { + return parse_date(str.c_str()); +} +date_t parse_date(const char * str); + +inline std::time_t to_time_t(const ptime& t) +{ + if( t == posix_time::neg_infin ) + return 0; + else if( t == posix_time::pos_infin ) + return LONG_MAX; + ptime start(date(1970,1,1)); + return (t-start).total_seconds(); +} + +inline string format_datetime(const datetime_t& when) +{ + char buf[256]; + time_t moment = to_time_t(when); + std::strftime(buf, 255, (output_date_format + " %H:%M:%S").c_str(), + std::localtime(&moment)); + return buf; +} + +inline string format_date(const date_t& when, + const optional<string>& format = none) +{ + if (format) { + char buf[256]; + std::tm moment = gregorian::to_tm(when); + std::strftime(buf, 255, format->c_str(), &moment); + return buf; + } else { + return to_iso_extended_string(when); + } +} + +struct interval_t +{ + int years; + int months; + int days; + date_t begin; + date_t end; + + mutable bool advanced; + + interval_t(int _days = 0, int _months = 0, int _years = 0, + const date_t& _begin = date_t(), + const date_t& _end = date_t()) + : years(_years), months(_months), days(_days), + begin(_begin), end(_end), advanced(false) { + TRACE_CTOR(interval_t, "int, int, int, const date_t&, const date_t&"); + } + interval_t(const interval_t& other) + : years(other.years), + months(other.months), + days(other.days), + begin(other.begin), + end(other.end), + advanced(other.advanced) { + TRACE_CTOR(interval_t, "copy"); + } + interval_t(const string& desc) + : years(0), months(0), days(0), begin(), end(), advanced(false) { + TRACE_CTOR(interval_t, "const string&"); + std::istringstream stream(desc); + parse(stream); + } + + ~interval_t() throw() { + TRACE_DTOR(interval_t); + } + + operator bool() const { + return years != 0 || months != 0 || days != 0; + } + + void start(const date_t& moment) { + begin = first(moment); + } + date_t first(const optional<date_t>& moment = none) const; + date_t increment(const date_t&) const; + + void parse(std::istream& in); +}; + +} // namespace ledger + +#endif // _TIMES_H diff --git a/src/token.cc b/src/token.cc new file mode 100644 index 00000000..d1d70a38 --- /dev/null +++ b/src/token.cc @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "token.h" +#include "parser.h" + +namespace ledger { + +void expr_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 == '.' || c == '-'); + + switch (buf[0]) { +#if 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; +#endif + case 'f': + if (std::strcmp(buf, "false") == 0) { + kind = VALUE; + value = false; + } + break; +#if 0 + 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; +#endif + case 't': + if (std::strcmp(buf, "true") == 0) { + kind = VALUE; + value = true; + } + break; + } + + if (kind == IDENT) + value.set_string(buf); +} + +void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags) +{ + 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; + + switch (c) { + case '&': + in.get(c); + kind = KW_AND; + break; + case '|': + in.get(c); + kind = KW_OR; + break; + + case '(': + in.get(c); + kind = LPAREN; + break; + case ')': + in.get(c); + kind = RPAREN; + break; + + case '[': { + in.get(c); + + char buf[256]; + READ_INTO_(in, buf, 255, c, length, c != ']'); + if (c != ']') + expected(']', c); + + in.get(c); + length++; + + interval_t timespan(buf); + kind = VALUE; + value = timespan.first(); + break; + } + + case '\'': + case '"': { + char delim; + in.get(delim); + char buf[4096]; + READ_INTO_(in, buf, 4095, c, length, c != delim); + if (c != delim) + expected(delim, 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 != '}') + expected('}', 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; + } + kind = EXCLAM; + break; + + case '-': + in.get(c); + kind = MINUS; + break; + case '+': + in.get(c); + kind = PLUS; + break; + + case '*': + in.get(c); + kind = STAR; + break; + + case '?': + in.get(c); + kind = QUERY; + break; + + case ':': + in.get(c); + kind = COLON; + break; + + case '/': { + in.get(c); + + // Read in the regexp + char buf[256]; + READ_INTO_(in, buf, 255, c, length, c != '/'); + if (c != '/') + expected('/', c); + in.get(c); + length++; + + kind = MASK; + value.set_string(buf); + break; + } + + case '=': + in.get(c); + c = in.peek(); + if (c == '~') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = MATCH; + length = 2; + break; + } + 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 = COMMA; + break; + + default: { + 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. + pos = static_cast<unsigned long>(in.tellg()); + + amount_t::flags_t parse_flags = 0; + if (pflags & EXPR_PARSE_NO_MIGRATE) + parse_flags |= AMOUNT_PARSE_NO_MIGRATE; + if (pflags & EXPR_PARSE_NO_REDUCE) + parse_flags |= AMOUNT_PARSE_NO_REDUCE; + + if (! temp.parse(in, parse_flags | AMOUNT_PARSE_SOFT_FAIL)) { + // If the amount had no commodity, it must be an unambiguous + // variable reference + + in.clear(); + in.seekg(pos, std::ios::beg); + + c = in.peek(); + assert(! (std::isdigit(c) || c == '.')); + parse_ident(in); + } else { + kind = VALUE; + value = temp; + } + break; + } + } +} + +void expr_t::token_t::rewind(std::istream& in) +{ + for (unsigned int i = 0; i < length; i++) + in.unget(); +} + + +void expr_t::token_t::unexpected() +{ + switch (kind) { + case TOK_EOF: + throw_(parse_error, "Unexpected end of expression"); + case IDENT: + throw_(parse_error, "Unexpected symbol '" << value << "'"); + case VALUE: + throw_(parse_error, "Unexpected value '" << value << "'"); + default: + throw_(parse_error, "Unexpected operator '" << symbol << "'"); + } +} + +void expr_t::token_t::expected(char wanted, char c) +{ + if (c == '\0') { + if (wanted) + throw_(parse_error, "Missing '" << wanted << "'"); + else + throw_(parse_error, "Unexpected end"); + } else { + if (wanted) + throw_(parse_error, "Invalid char '" << c + << "' (wanted '" << wanted << "')"); + else + throw_(parse_error, "Invalid char '" << c << "'"); + } +} + +} // namespace ledger diff --git a/src/token.h b/src/token.h new file mode 100644 index 00000000..04ca39c1 --- /dev/null +++ b/src/token.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TOKEN_H +#define _TOKEN_H + +#include "expr.h" + +namespace ledger { + +struct expr_t::token_t : public noncopyable +{ + enum kind_t { + VALUE, // any kind of literal value + IDENT, // [A-Za-z_][-A-Za-z0-9_:]* + MASK, // /regexp/ + + LPAREN, // ( + RPAREN, // ) + + EQUAL, // == + NEQUAL, // != + LESS, // < + LESSEQ, // <= + GREATER, // > + GREATEREQ, // >= + + ASSIGN, // = + MATCH, // =~ + MINUS, // - + PLUS, // + + STAR, // * + KW_DIV, // / + + EXCLAM, // ! + KW_AND, // & + KW_OR, // | + KW_MOD, // % + + QUERY, // ? + COLON, // : + + COMMA, // , + + TOK_EOF, + UNKNOWN + + } kind; + + char symbol[3]; + value_t value; + std::size_t length; + + explicit token_t() : kind(UNKNOWN), length(0) { + TRACE_CTOR(token_t, ""); + } + ~token_t() throw() { + TRACE_DTOR(token_t); + } + + token_t& operator=(const token_t& other) { + if (&other == this) + return *this; + assert(false); + return *this; + } + + void clear() { + kind = UNKNOWN; + length = 0; + value = NULL_VALUE; + + symbol[0] = '\0'; + symbol[1] = '\0'; + symbol[2] = '\0'; + } + + void parse_ident(std::istream& in); + void next(std::istream& in, const uint_least8_t flags); + void rewind(std::istream& in); + void unexpected(); + + static void expected(char wanted, char c = '\0'); +}; + +} // namespace ledger + +#endif // _TOKEN_H diff --git a/src/utils.cc b/src/utils.cc new file mode 100644 index 00000000..5ac5cef0 --- /dev/null +++ b/src/utils.cc @@ -0,0 +1,716 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils.h" + +/********************************************************************** + * + * Assertions + */ + +#if defined(ASSERTS_ON) + +namespace ledger { + +DECLARE_EXCEPTION(assertion_failed, std::logic_error); + +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 + << ": " << func << ": " << reason; + throw assertion_failed(buf.str()); +} + +} // namespace ledger + +#endif + +/********************************************************************** + * + * Verification (basically, very slow asserts) + */ + +#if defined(VERIFY_ON) + +namespace ledger { + +bool verify_enabled = false; + +typedef std::pair<std::string, std::size_t> allocation_pair; +typedef std::map<void *, allocation_pair> live_memory_map; +typedef std::multimap<void *, allocation_pair> live_objects_map; + +typedef std::pair<unsigned int, std::size_t> count_size_pair; +typedef std::map<std::string, count_size_pair> object_count_map; + +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); + } + + checked_delete(live_memory); live_memory = NULL; + checked_delete(live_memory_count); live_memory_count = NULL; + checked_delete(total_memory_count); total_memory_count = NULL; + + checked_delete(live_objects); live_objects = NULL; + checked_delete(live_object_count); live_object_count = NULL; + checked_delete(total_object_count); total_object_count = NULL; + checked_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<object_count_map::iterator, bool> result = + the_map.insert(object_count_map::value_type(name, count_size_pair(1, size))); + VERIFY(result.second); + } +} + +std::size_t current_memory_size() +{ + std::size_t memory_size = 0; + + foreach (const object_count_map::value_type& pair, *live_memory_count) + memory_size += pair.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_map::value_type(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) +{ + foreach (object_count_map::value_type& pair, the_map) + out << " " << std::right << std::setw(12) << pair.second.first + << " " << std::right << std::setw(7) << pair.second.second + << " " << std::left << pair.first + << std::endl; +} + +std::size_t current_objects_size() +{ + std::size_t objects_size = 0; + + foreach (const object_count_map::value_type& pair, *live_object_count) + objects_size += pair.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("memory.debug", "TRACE_CTOR " << ptr << " " << name); + + live_objects->insert + (live_objects_map::value_type(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("memory.debug", "TRACE_DTOR " << ptr << " " << cls_name); + + live_objects_map::iterator i = live_objects->find(ptr); + if (i == live_objects->end()) { + std::cerr << "Attempting to delete " << ptr << " a non-living " << cls_name + << std::endl; + assert(false); + } + + 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; + + foreach (const live_memory_map::value_type& pair, *live_memory) + out << " " << std::right << std::setw(12) << pair.first + << " " << std::right << std::setw(7) << pair.second.second + << " " << std::left << pair.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; + + foreach (const live_objects_map::value_type& pair, *live_objects) + out << " " << std::right << std::setw(12) << pair.first + << " " << std::right << std::setw(7) << pair.second.second + << " " << std::left << pair.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); + } + } +} + + +string::string() : std::string() { + TRACE_CTOR(string, ""); +} +string::string(const string& str) : std::string(str) { + TRACE_CTOR(string, "copy"); +} +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() throw() { + TRACE_DTOR(string); +} + +} // namespace ledger + +#endif // VERIFY_ON + +ledger::string empty_string(""); + +/********************************************************************** + * + * Logging + */ + +#if defined(LOGGING_ON) + +namespace ledger { + +log_level_t _log_level = LOG_WARN; +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 + out << (double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G'; +} + +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 - current_time).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) + +namespace ledger { + +optional<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<std::string, timer_t> timer_map; + +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_map::value_type(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 _desc_buffer; +std::ostringstream _ctxt_buffer; + +} // namespace ledger + +/********************************************************************** + * + * General utility functions + */ + +namespace ledger { + +path expand_path(const path& pathname) +{ + if (pathname.empty()) + return pathname; + +#if 0 + // jww (2007-04-30): I need to port this code to use + // boost::filesystem::path + const char * pfx = NULL; + string::size_type pos = pathname.find_first_of('/'); + + if (pathname.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(pathname, 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 pathname; + + string result(pfx); + + if (pos == string::npos) + return result; + + if (result.length() == 0 || result[result.length() - 1] != '/') + result += '/'; + + result += pathname.substr(pos + 1); + + return result; +#else + return pathname; +#endif +} + +path resolve_path(const path& pathname) +{ + path temp = pathname; + if (temp.string()[0] == '~') + temp = expand_path(temp); + temp.normalize(); + return temp; +} + +} // namespace ledger diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 00000000..60d47ec1 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,578 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file utils.h + * @author John Wiegley + * @date Sun May 6 21:20:00 2007 + * + * @brief This file contains general utility facilities used by Ledger. + * + * Ledger has need of the following utility code, which this file + * provides or includes in: + * + * - system headers + * - asserts + * - verification (basically, "heavy asserts") + * - tracing code + * - debug logging code + * - timing code + * - current error context + * - exception framework + * - date/time type + * - supports_flags<> for objects that use flags + * - push_variable<> for restoring variable values + */ + +#ifndef _UTILS_H +#define _UTILS_H + +#if defined(DEBUG_MODE) +#define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE 1 +#define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING 1 +#endif + +#include <system.hh> + +/********************************************************************** + * + * Default values + */ + +#if defined(DEBUG_MODE) +#define VERIFY_ON 1 +#define TRACING_ON 1 +#define DEBUG_ON 1 +#define TIMERS_ON 1 +#elif defined(NDEBUG) +#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 +#endif + +/********************************************************************** + * + * Forward declarations + */ + +namespace ledger { + using namespace boost; + +#if defined(VERIFY_ON) + class string; +#else + typedef std::string string; +#endif + + typedef std::list<string> strings_list; + + typedef posix_time::ptime ptime; + typedef ptime::time_duration_type time_duration; + typedef gregorian::date date; + typedef gregorian::date_duration date_duration; + typedef posix_time::seconds seconds; + + typedef boost::filesystem::path path; + typedef boost::filesystem::ifstream ifstream; + typedef boost::filesystem::ofstream ofstream; + typedef boost::filesystem::filesystem_error filesystem_error; +} + +/********************************************************************** + * + * Assertions + */ + +#ifdef assert +#undef assert +#endif + +#if ! defined(NO_ASSERTS) +#define ASSERTS_ON 1 +#endif +#if defined(ASSERTS_ON) + +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__)) + +#else // ! ASSERTS_ON + +#define assert(x) + +#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 + +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() ? \ + ledger::trace_ctor_func(this, #cls, args, sizeof(cls)) : ((void)0)) +#define TRACE_DTOR(cls) \ + (DO_VERIFY() ? \ + ledger::trace_dtor_func(this, #cls, sizeof(cls)) : ((void)0)) + +void report_memory(std::ostream& out, bool report_all = false); + +/** + * This string type is a wrapper around std::string that allows us to + * trace constructor and destructor calls. + */ +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() throw(); +}; + +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; } + +} // namespace ledger + +#else // ! VERIFY_ON + +#define VERIFY(x) +#define DO_VERIFY() true +#define TRACE_CTOR(cls, args) +#define TRACE_DTOR(cls) + +#endif // VERIFY_ON + +extern ledger::string empty_string; + +#define IF_VERIFY() if (DO_VERIFY()) + +/********************************************************************** + * + * 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) \ + (ledger::_log_level >= ledger::LOG_TRACE && lvl <= ledger::_trace_level) +#define TRACE(lvl, msg) \ + (SHOW_TRACE(lvl) ? \ + ((ledger::_log_buffer << msg), \ + ledger::logger_func(ledger::LOG_TRACE)) : false) + +#else // TRACING_ON + +#define SHOW_TRACE(lvl) false +#define TRACE(lvl, msg) + +#endif // TRACING_ON + +#if defined(DEBUG_ON) + +extern optional<std::string> _log_category; + +inline bool category_matches(const char * cat) { + return _log_category && starts_with(cat, *_log_category); +} + +#define SHOW_DEBUG(cat) \ + (ledger::_log_level >= ledger::LOG_DEBUG && ledger::category_matches(cat)) +#define SHOW_DEBUG_() SHOW_DEBUG(_this_category) + +#define DEBUG(cat, msg) \ + (SHOW_DEBUG(cat) ? \ + ((ledger::_log_buffer << msg), \ + ledger::logger_func(ledger::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) \ + (ledger::_log_level >= level ? \ + ((ledger::_log_buffer << msg), ledger::logger_func(level)) : false) + +#define SHOW_INFO() (ledger::_log_level >= ledger::LOG_INFO) +#define SHOW_WARN() (ledger::_log_level >= ledger::LOG_WARN) +#define SHOW_ERROR() (ledger::_log_level >= ledger::LOG_ERROR) +#define SHOW_FATAL() (ledger::_log_level >= ledger::LOG_FATAL) +#define SHOW_CRITICAL() (ledger::_log_level >= ledger::LOG_CRIT) + +#define INFO(msg) LOG_MACRO(ledger::LOG_INFO, msg) +#define WARN(msg) LOG_MACRO(ledger::LOG_WARN, msg) +#define ERROR(msg) LOG_MACRO(ledger::LOG_ERROR, msg) +#define FATAL(msg) LOG_MACRO(ledger::LOG_FATAL, msg) +#define CRITICAL(msg) LOG_MACRO(ledger::LOG_CRIT, msg) +#define EXCEPTION(msg) LOG_MACRO(ledger::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(cat, msg) +#define DEBUG_(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) ? \ + ((ledger::_log_buffer << msg), \ + ledger::start_timer(#name, ledger::LOG_TRACE)) : ((void)0)) +#define TRACE_STOP(name, lvl) \ + (SHOW_TRACE(lvl) ? ledger::stop_timer(#name) : ((void)0)) +#define TRACE_FINISH(name, lvl) \ + (SHOW_TRACE(lvl) ? ledger::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) ? \ + ((ledger::_log_buffer << msg), \ + ledger::start_timer(#name, ledger::LOG_DEBUG)) : ((void)0)) +#define DEBUG_START_(name, msg) \ + DEBUG_START_(name, _this_category, msg) +#define DEBUG_STOP(name, cat) \ + (SHOW_DEBUG(cat) ? ledger::stop_timer(#name) : ((void)0)) +#define DEBUG_STOP_(name) \ + DEBUG_STOP_(name, _this_category) +#define DEBUG_FINISH(name, cat) \ + (SHOW_DEBUG(cat) ? ledger::finish_timer(#name) : ((void)0)) +#define DEBUG_FINISH_(name) \ + DEBUG_FINISH_(name, _this_category) +#else +#define DEBUG_START(name, cat, msg) +#define DEBUG_START_(name, msg) +#define DEBUG_STOP(name) +#define DEBUG_FINISH(name) +#endif + +#define INFO_START(name, msg) \ + (SHOW_INFO() ? \ + ((ledger::_log_buffer << msg), \ + ledger::start_timer(#name, ledger::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 helpers + * - Date/time support classes + * - General support for objects with "flags" + * - Support for scoped execution and variable restoration + */ + +#include "error.h" +#include "times.h" +#include "flags.h" +#include "pushvar.h" + +/********************************************************************** + * + * General utility functions + */ + +#define foreach BOOST_FOREACH + +namespace ledger { + +template <typename T, typename U> +inline T& downcast(U& object) { + return *polymorphic_downcast<T *>(&object); +} + +path resolve_path(const path& pathname); + +#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; +} + +inline char * skip_ws(char * ptr) { + while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') + ptr++; + return ptr; +} + +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; +} + +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 // _UTILS_H diff --git a/src/value.cc b/src/value.cc new file mode 100644 index 00000000..ea52af1f --- /dev/null +++ b/src/value.cc @@ -0,0 +1,1773 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "value.h" +#include "binary.h" + +namespace ledger { + +intrusive_ptr<value_t::storage_t> value_t::true_value; +intrusive_ptr<value_t::storage_t> value_t::false_value; + +value_t::storage_t& value_t::storage_t::operator=(const value_t::storage_t& rhs) +{ + type = rhs.type; + + switch (type) { + case DATETIME: + new(reinterpret_cast<datetime_t *>(data)) + datetime_t(*reinterpret_cast<datetime_t *> + (const_cast<char *>(rhs.data))); + break; + + case DATE: + new(reinterpret_cast<date_t *>(data)) + date_t(*reinterpret_cast<date_t *>(const_cast<char *>(rhs.data))); + break; + + case AMOUNT: + new(reinterpret_cast<amount_t *>(data)) + amount_t(*reinterpret_cast<amount_t *> + (const_cast<char *>(rhs.data))); + break; + + case BALANCE: + *reinterpret_cast<balance_t **>(data) = + new balance_t(**reinterpret_cast<balance_t **> + (const_cast<char *>(rhs.data))); + break; + + case BALANCE_PAIR: + *reinterpret_cast<balance_pair_t **>(data) = + new balance_pair_t(**reinterpret_cast<balance_pair_t **> + (const_cast<char *>(rhs.data))); + break; + + case STRING: + new(reinterpret_cast<string *>(data)) + string(*reinterpret_cast<string *>(const_cast<char *>(rhs.data))); + break; + + case SEQUENCE: + *reinterpret_cast<sequence_t **>(data) = + new sequence_t(**reinterpret_cast<sequence_t **> + (const_cast<char *>(rhs.data))); + break; + + default: + // The rest are fundamental types, which can be copied using + // std::memcpy + std::memcpy(data, rhs.data, sizeof(data)); + break; + } + + return *this; +} + +void value_t::storage_t::destroy() +{ + switch (type) { + case AMOUNT: + reinterpret_cast<amount_t *>(data)->~amount_t(); + break; + case BALANCE: + checked_delete(*reinterpret_cast<balance_t **>(data)); + break; + case BALANCE_PAIR: + checked_delete(*reinterpret_cast<balance_pair_t **>(data)); + break; + case STRING: + reinterpret_cast<string *>(data)->~string(); + break; + case SEQUENCE: + checked_delete(*reinterpret_cast<sequence_t **>(data)); + break; + case POINTER: + reinterpret_cast<boost::any *>(data)->~any(); + break; + + default: + break; + } + type = VOID; +} + +void value_t::initialize() +{ + LOGGER("value.initialize"); + + true_value = new storage_t; + true_value->type = BOOLEAN; + *reinterpret_cast<bool *>(true_value->data) = true; + + false_value = new storage_t; + false_value->type = BOOLEAN; + *reinterpret_cast<bool *>(false_value->data) = false; + +#if 0 + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(bool)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(datetime_t)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(date_t)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(long)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(amount_t)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_t *)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_pair_t *)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(string)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(sequence_t *)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(boost::any)); +#endif + + DEBUG_(std::setw(3) << std::right << sizeof(bool) + << " sizeof(bool)"); + DEBUG_(std::setw(3) << std::right << sizeof(datetime_t) + << " sizeof(datetime_t)"); + DEBUG_(std::setw(3) << std::right << sizeof(date_t) + << " sizeof(date_t)"); + DEBUG_(std::setw(3) << std::right << sizeof(long) + << " sizeof(long)"); + DEBUG_(std::setw(3) << std::right << sizeof(amount_t) + << " sizeof(amount_t)"); + DEBUG_(std::setw(3) << std::right << sizeof(balance_t *) + << " sizeof(balance_t *)"); + DEBUG_(std::setw(3) << std::right << sizeof(balance_pair_t *) + << " sizeof(balance_pair_t *)"); + DEBUG_(std::setw(3) << std::right << sizeof(string) + << " sizeof(string)"); + DEBUG_(std::setw(3) << std::right << sizeof(sequence_t *) + << " sizeof(sequence_t *)"); + DEBUG_(std::setw(3) << std::right << sizeof(boost::any) + << " sizeof(boost::any)"); +} + +void value_t::shutdown() +{ + true_value = intrusive_ptr<storage_t>(); + false_value = intrusive_ptr<storage_t>(); +} + +void value_t::_dup() +{ + assert(storage); + if (storage->refc > 1) + storage = new storage_t(*storage.get()); +} + +value_t::operator bool() const +{ + switch (type()) { + case BOOLEAN: + return as_boolean(); + case DATETIME: + return is_valid(as_datetime()); + case DATE: + return is_valid(as_date()); + case INTEGER: + return as_long(); + case AMOUNT: + return as_amount(); + case BALANCE: + return as_balance(); + case BALANCE_PAIR: + return as_balance_pair(); + case STRING: + return ! as_string().empty(); + case SEQUENCE: + return ! as_sequence().empty(); + case POINTER: + return ! as_any_pointer().empty(); + default: + assert(false); + break; + } + assert(false); + return 0; +} + +bool value_t::to_boolean() const +{ + if (is_boolean()) { + return as_boolean(); + } else { + value_t temp(*this); + temp.in_place_cast(BOOLEAN); + return temp.as_boolean(); + } +} + +datetime_t value_t::to_datetime() const +{ + if (is_datetime()) { + return as_datetime(); + } else { + value_t temp(*this); + temp.in_place_cast(DATETIME); + return temp.as_datetime(); + } +} + +date_t value_t::to_date() const +{ + if (is_date()) { + return as_date(); + } else { + value_t temp(*this); + temp.in_place_cast(DATE); + return temp.as_date(); + } +} + +long value_t::to_long() const +{ + if (is_long()) { + return as_long(); + } else { + value_t temp(*this); + temp.in_place_cast(INTEGER); + return temp.as_long(); + } +} + +amount_t value_t::to_amount() const +{ + if (is_amount()) { + return as_amount(); + } else { + value_t temp(*this); + temp.in_place_cast(AMOUNT); + return temp.as_amount(); + } +} + +balance_t value_t::to_balance() const +{ + if (is_balance()) { + return as_balance(); + } else { + value_t temp(*this); + temp.in_place_cast(BALANCE); + return temp.as_balance(); + } +} + +balance_pair_t value_t::to_balance_pair() const +{ + if (is_balance_pair()) { + return as_balance_pair(); + } else { + value_t temp(*this); + temp.in_place_cast(BALANCE_PAIR); + return temp.as_balance_pair(); + } +} + +string value_t::to_string() const +{ + if (is_string()) { + return as_string(); + } else { + value_t temp(*this); + temp.in_place_cast(STRING); + return temp.as_string(); + } +} + +value_t::sequence_t value_t::to_sequence() const +{ + if (is_sequence()) { + return as_sequence(); + } else { + value_t temp(*this); + temp.in_place_cast(SEQUENCE); + return temp.as_sequence(); + } +} + + +void value_t::in_place_simplify() +{ + LOGGER("amounts.values.simplify"); + + if (is_realzero()) { + DEBUG_("Zeroing type " << static_cast<int>(type())); + set_long(0L); + return; + } + + if (is_balance_pair() && + (! as_balance_pair().cost || as_balance_pair().cost->is_realzero())) { + DEBUG_("Reducing balance pair to balance"); + in_place_cast(BALANCE); + } + + if (is_balance() && as_balance().amounts.size() == 1) { + DEBUG_("Reducing balance to amount"); + DEBUG("ledger.value.reduce", "as a balance it looks like: " << *this); + in_place_cast(AMOUNT); + DEBUG("ledger.value.reduce", "as an amount it looks like: " << *this); + } + +#if 0 + if (is_amount() && ! as_amount().has_commodity() && + as_amount().fits_in_long()) { + DEBUG_("Reducing amount to integer"); + in_place_cast(INTEGER); + } +#endif +} + +value_t& value_t::operator+=(const value_t& val) +{ + if (is_string()) { + if (val.is_string()) + as_string_lval() += val.as_string(); + else + as_string_lval() += val.to_string(); + return *this; + } + else if (is_sequence()) { + if (val.is_sequence()) { + sequence_t& seq(as_sequence_lval()); + seq.insert(seq.end(), val.as_sequence().begin(), + val.as_sequence().end()); + } else { + as_sequence_lval().push_back(val); + } + return *this; + } + + switch (type()) { + case DATETIME: + switch (val.type()) { + case INTEGER: + as_datetime_lval() += date_duration(val.as_long()); + return *this; + case AMOUNT: + as_datetime_lval() += date_duration(val.as_amount().to_long()); + return *this; + default: + break; + } + break; + + case DATE: + switch (val.type()) { + case INTEGER: + as_date_lval() += date_duration_t(val.as_long()); + return *this; + case AMOUNT: + as_date_lval() += date_duration_t(val.as_amount().to_long()); + return *this; + default: + break; + } + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() += val.as_long(); + return *this; + case AMOUNT: + in_place_cast(AMOUNT); + as_amount_lval() += val.as_amount(); + return *this; + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() += val.as_balance(); + return *this; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() += val.as_balance_pair(); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + if (as_amount().has_commodity()) { + in_place_cast(BALANCE); + return *this += val; + } else { + as_amount_lval() += val.as_long(); + return *this; + } + break; + + case AMOUNT: + if (as_amount().commodity() != val.as_amount().commodity()) { + in_place_cast(BALANCE); + return *this += val; + } else { + as_amount_lval() += val.as_amount(); + return *this; + } + break; + + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() += val.as_balance(); + return *this; + + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() += val.as_balance_pair(); + return *this; + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() += val.to_amount(); + return *this; + case AMOUNT: + as_balance_lval() += val.as_amount(); + return *this; + case BALANCE: + as_balance_lval() += val.as_balance(); + return *this; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() += val.as_balance_pair(); + return *this; + default: + break; + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + as_balance_pair_lval() += val.to_amount(); + return *this; + case AMOUNT: + as_balance_pair_lval() += val.as_amount(); + return *this; + case BALANCE: + as_balance_pair_lval() += val.as_balance(); + return *this; + case BALANCE_PAIR: + as_balance_pair_lval() += val.as_balance_pair(); + return *this; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, "Cannot add " << val.label() << " to " << label()); + return *this; +} + +value_t& value_t::operator-=(const value_t& val) +{ + if (is_sequence()) { + sequence_t& seq(as_sequence_lval()); + + if (val.is_sequence()) { + foreach (const value_t& v, val.as_sequence()) { + sequence_t::iterator j = std::find(seq.begin(), seq.end(), v); + if (j != seq.end()) + seq.erase(j); + } + } else { + sequence_t::iterator i = std::find(seq.begin(), seq.end(), val); + if (i != seq.end()) + seq.erase(i); + } + return *this; + } + + switch (type()) { + case DATETIME: + switch (val.type()) { + case INTEGER: + as_datetime_lval() -= date_duration(val.as_long()); + return *this; + case AMOUNT: + as_datetime_lval() -= date_duration(val.as_amount().to_long()); + return *this; + default: + break; + } + break; + + case DATE: + switch (val.type()) { + case INTEGER: + as_date_lval() -= date_duration_t(val.as_long()); + return *this; + case AMOUNT: + as_date_lval() -= date_duration_t(val.as_amount().to_long()); + return *this; + default: + break; + } + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() -= val.as_long(); + return *this; + case AMOUNT: + in_place_cast(AMOUNT); + as_amount_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + if (as_amount().has_commodity()) { + in_place_cast(BALANCE); + *this -= val; + in_place_simplify(); + return *this; + } else { + as_amount_lval() -= val.as_long(); + in_place_simplify(); + return *this; + } + break; + + case AMOUNT: + if (as_amount().commodity() != val.as_amount().commodity()) { + in_place_cast(BALANCE); + *this -= val; + in_place_simplify(); + return *this; + } else { + as_amount_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + } + break; + + case BALANCE: + in_place_cast(BALANCE); + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() -= val.to_amount(); + in_place_simplify(); + return *this; + case AMOUNT: + as_balance_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + case BALANCE: + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; + default: + break; + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + as_balance_pair_lval() -= val.to_amount(); + in_place_simplify(); + return *this; + case AMOUNT: + as_balance_pair_lval() -= val.as_amount(); + in_place_simplify(); + return *this; + case BALANCE: + as_balance_pair_lval() -= val.as_balance(); + in_place_simplify(); + return *this; + case BALANCE_PAIR: + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, "Cannot subtract " << val.label() << " from " << label()); + + return *this; +} + +value_t& value_t::operator*=(const value_t& val) +{ + if (is_string()) { + string temp; + long count = val.to_long(); + for (long i = 0; i < count; i++) + temp += as_string(); + set_string(temp); + return *this; + } + else if (is_sequence()) { + value_t temp; + long count = val.to_long(); + for (long i = 0; i < count; i++) + temp += as_sequence(); + return *this = temp; + } + + switch (type()) { + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() *= val.as_long(); + return *this; + case AMOUNT: + set_amount(val.as_amount() * as_long()); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + as_amount_lval() *= val.as_long(); + return *this; + case AMOUNT: + if (as_amount().commodity() == val.as_amount().commodity() || + ! val.as_amount().has_commodity()) { + as_amount_lval() *= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() *= val.as_long(); + return *this; + case AMOUNT: + if (! val.as_amount().has_commodity()) { + as_balance_lval() *= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + as_balance_pair_lval() *= val.as_long(); + return *this; + case AMOUNT: + if (! val.as_amount().has_commodity()) { + as_balance_pair_lval() *= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, "Cannot multiply " << label() << " with " << val.label()); + + return *this; +} + +value_t& value_t::operator/=(const value_t& val) +{ + switch (type()) { + case INTEGER: + switch (val.type()) { + case INTEGER: + as_long_lval() /= val.as_long(); + return *this; + case AMOUNT: + set_amount(val.as_amount() / as_long()); + return *this; + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + as_amount_lval() /= val.as_long(); + return *this; + + case AMOUNT: + if (as_amount().commodity() == val.as_amount().commodity() || + ! val.as_amount().has_commodity()) { + as_amount_lval() /= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() /= val.as_long(); + return *this; + case AMOUNT: + if (! val.as_amount().has_commodity()) { + as_balance_lval() /= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + as_balance_pair_lval() /= val.as_long(); + return *this; + case AMOUNT: + if (! val.as_amount().has_commodity()) { + as_balance_pair_lval() /= val.as_amount(); + return *this; + } + break; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, "Cannot divide " << label() << " by " << val.label()); + + return *this; +} + + +bool value_t::operator==(const value_t& val) const +{ + switch (type()) { + case VOID: + return val.type() == VOID; + + case BOOLEAN: + if (val.is_boolean()) + return as_boolean() == val.as_boolean(); + break; + + case DATETIME: + if (val.is_datetime()) + return as_datetime() == val.as_datetime(); + break; + + case DATE: + if (val.is_date()) + return as_date() == val.as_date(); + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() == val.as_long(); + case AMOUNT: + return val.as_amount() == to_amount(); + case BALANCE: + return val.as_balance() == to_amount(); + case BALANCE_PAIR: + return val.as_balance_pair() == to_amount(); + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + return as_amount() == val.as_long(); + case AMOUNT: + return as_amount() == val.as_amount(); + case BALANCE: + return val.as_balance() == as_amount(); + case BALANCE_PAIR: + return val.as_balance_pair() == as_amount(); + default: + break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + return as_balance() == val.to_amount(); + case AMOUNT: + return as_balance() == val.as_amount(); + case BALANCE: + return as_balance() == val.as_balance(); + case BALANCE_PAIR: + return val.as_balance_pair() == as_balance(); + default: + break; + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + return as_balance_pair() == val.to_amount(); + case AMOUNT: + return as_balance_pair() == val.as_amount(); + case BALANCE: + return as_balance_pair() == val.as_balance(); + case BALANCE_PAIR: + return as_balance_pair() == val.as_balance_pair(); + default: + break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() == val.as_string(); + break; + + case SEQUENCE: + if (val.is_sequence()) + return as_sequence() == val.as_sequence(); + break; + + default: + break; + } + + throw_(value_error, "Cannot compare " << label() << " to " << val.label()); + + return *this; +} + +bool value_t::operator<(const value_t& val) const +{ + switch (type()) { + case DATETIME: + if (val.is_datetime()) + return as_datetime() < val.as_datetime(); + break; + + case DATE: + if (val.is_date()) + return as_date() < val.as_date(); + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() < val.as_long(); + case AMOUNT: + return val.as_amount() < as_long(); + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + return as_amount() < val.as_long(); + case AMOUNT: + return as_amount() < val.as_amount(); + default: + break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() < val.as_string(); + break; + + default: + break; + } + + throw_(value_error, "Cannot compare " << label() << " to " << val.label()); + + return *this; +} + +#if 0 +bool value_t::operator>(const value_t& val) const +{ + switch (type()) { + case DATETIME: + if (val.is_datetime()) + return as_datetime() > val.as_datetime(); + break; + + case DATE: + if (val.is_date()) + return as_date() > val.as_date(); + break; + + case INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() > val.as_long(); + case AMOUNT: + return val.as_amount() > as_long(); + default: + break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + return as_amount() > val.as_long(); + case AMOUNT: + return as_amount() > val.as_amount(); + default: + break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() > val.as_string(); + break; + + default: + break; + } + + throw_(value_error, "Cannot compare " << label() << " to " << val.label()); + + return *this; +} +#endif + +void value_t::in_place_cast(type_t cast_type) +{ + if (type() == cast_type) + return; + + if (cast_type == BOOLEAN) { + set_boolean(bool(*this)); + return; + } + else if (cast_type == SEQUENCE) { + sequence_t temp; + if (! is_null()) + temp.push_back(*this); + set_sequence(temp); + return; + } + + switch (type()) { + case BOOLEAN: + switch (cast_type) { + case STRING: + set_string(as_boolean() ? "true" : "false"); + return; + default: + break; + } + break; + + case INTEGER: + switch (cast_type) { + case AMOUNT: + set_amount(as_long()); + return; + case BALANCE: + set_balance(to_amount()); + return; + case BALANCE_PAIR: + set_balance_pair(to_amount()); + return; + case STRING: + set_string(lexical_cast<string>(as_long())); + return; + default: + break; + } + break; + + case AMOUNT: { + const amount_t& amt(as_amount()); + switch (cast_type) { + case INTEGER: + if (amt.is_null()) + set_long(0L); + else + set_long(as_amount().to_long()); + return; + case BALANCE: + if (amt.is_null()) + set_balance(balance_t()); + else + set_balance(as_amount()); // creates temporary + return; + case BALANCE_PAIR: + if (amt.is_null()) + set_balance_pair(balance_pair_t()); + else + set_balance_pair(as_amount()); // creates temporary + return; + case STRING: + if (amt.is_null()) + set_string(""); + else + set_string(as_amount().to_string()); + return; + default: + break; + } + break; + } + + case BALANCE: + switch (cast_type) { + case AMOUNT: { + const balance_t& temp(as_balance()); + if (temp.amounts.size() == 1) { + // Because we are changing the current balance value to an amount + // value, and because set_amount takes a reference (and that memory is + // about to be repurposed), we must pass in a copy. + set_amount(amount_t((*temp.amounts.begin()).second)); + return; + } + else if (temp.amounts.size() == 0) { + set_amount(0L); + return; + } + else { + throw_(value_error, "Cannot convert " << label() << + " with multiple commodities to " << label(cast_type)); + } + break; + } + case BALANCE_PAIR: + set_balance_pair(as_balance()); + return; + default: + break; + } + break; + + case BALANCE_PAIR: + switch (cast_type) { + case AMOUNT: { + const balance_t& temp(as_balance_pair().quantity()); + if (temp.amounts.size() == 1) { + set_amount(amount_t((*temp.amounts.begin()).second)); + return; + } + else if (temp.amounts.size() == 0) { + set_amount(0L); + return; + } + else { + throw_(value_error, "Cannot convert " << label() << + " with multiple commodities to " << label(cast_type)); + } + break; + } + case BALANCE: + // A temporary is required, becaues set_balance is going to wipe us out + // before assigned the value passed in. + set_balance(balance_t(as_balance_pair().quantity())); + return; + default: + break; + } + break; + + case STRING: + switch (cast_type) { + case INTEGER: { + if (all(as_string(), is_digit())) { + set_long(lexical_cast<long>(as_string())); + return; + } else { + throw_(value_error, + "Cannot convert string '" << *this << "' to an integer"); + } + break; + } + case AMOUNT: + set_amount(amount_t(as_string())); + return; + default: + break; + } + break; + + default: + break; + } + + throw_(value_error, + "Cannot convert " << label() << " to " << label(cast_type)); +} + +void value_t::in_place_negate() +{ + switch (type()) { + case BOOLEAN: + set_boolean(! as_boolean()); + return; + case INTEGER: + case DATETIME: + set_long(- as_long()); + return; + case DATE: + set_long(- as_long()); + return; + case AMOUNT: + as_amount_lval().in_place_negate(); + return; + case BALANCE: + as_balance_lval().in_place_negate(); + return; + case BALANCE_PAIR: + as_balance_pair_lval().in_place_negate(); + return; + default: + break; + } + + throw_(value_error, "Cannot negate " << label()); +} + +bool value_t::is_realzero() const +{ + switch (type()) { + case BOOLEAN: + return ! as_boolean(); + case INTEGER: + return as_long() == 0; + case DATETIME: + return ! is_valid(as_datetime()); + case DATE: + return ! is_valid(as_date()); + case AMOUNT: + return as_amount().is_realzero(); + case BALANCE: + return as_balance().is_realzero(); + case BALANCE_PAIR: + return as_balance_pair().is_realzero(); + case STRING: + return as_string().empty(); + case SEQUENCE: + return as_sequence().empty(); + + case POINTER: + return as_any_pointer().empty(); + + default: + assert(false); + break; + } + assert(false); + return true; +} + +bool value_t::is_zero() const +{ + switch (type()) { + case BOOLEAN: + return ! as_boolean(); + case INTEGER: + return as_long() == 0; + case DATETIME: + return ! is_valid(as_datetime()); + case DATE: + return ! is_valid(as_date()); + case AMOUNT: + return as_amount().is_zero(); + case BALANCE: + return as_balance().is_zero(); + case BALANCE_PAIR: + return as_balance_pair().is_zero(); + case STRING: + return as_string().empty(); + case SEQUENCE: + return as_sequence().empty(); + + case POINTER: + return as_any_pointer().empty(); + + default: + assert(false); + break; + } + assert(false); + return true; +} + +value_t value_t::value(const optional<datetime_t>& moment) const +{ + switch (type()) { + case INTEGER: + return *this; + + case AMOUNT: { + if (optional<amount_t> val = as_amount().value(moment)) + return *val; + return false; + } + case BALANCE: { + if (optional<balance_t> bal = as_balance().value(moment)) + return *bal; + return false; + } + case BALANCE_PAIR: { + if (optional<balance_t> bal_pair = + as_balance_pair().quantity().value(moment)) + return *bal_pair; + return false; + } + + default: + break; + } + + throw_(value_error, "Cannot find the value of " << label()); + return NULL_VALUE; +} + +void value_t::in_place_reduce() +{ + switch (type()) { + case INTEGER: + return; + case AMOUNT: + as_amount_lval().in_place_reduce(); + return; + case BALANCE: + as_balance_lval().in_place_reduce(); + return; + case BALANCE_PAIR: + as_balance_pair_lval().in_place_reduce(); + return; + default: + break; + } + + throw_(value_error, "Cannot reduce " << label()); +} + +value_t value_t::abs() const +{ + switch (type()) { + case INTEGER: { + long val = as_long(); + if (val < 0) + return - val; + return val; + } + case AMOUNT: + return as_amount().abs(); + case BALANCE: + return as_balance().abs(); + case BALANCE_PAIR: + return as_balance_pair().abs(); + default: + break; + } + + throw_(value_error, "Cannot abs " << label()); + return NULL_VALUE; +} + +value_t value_t::round() const +{ + switch (type()) { + case INTEGER: + return *this; + case AMOUNT: + return as_amount().round(); + case BALANCE: + return as_balance().round(); + case BALANCE_PAIR: + return as_balance_pair().round(); + default: + break; + } + + throw_(value_error, "Cannot round " << label()); + return NULL_VALUE; +} + +value_t value_t::unround() const +{ + switch (type()) { + case INTEGER: + return *this; + case AMOUNT: + return as_amount().unround(); + default: + break; + } + + throw_(value_error, "Cannot unround " << label()); + return NULL_VALUE; +} + +#if 0 +value_t value_t::annotated_price() const +{ + switch (type()) { + case AMOUNT: { + optional<amount_t> temp = as_amount().annotation_details().price; + if (! temp) + return false; + return *temp; + } + + default: + break; + } + + throw_(value_error, "Cannot find the annotated price of " << label()); + return NULL_VALUE; +} + +value_t value_t::annotated_date() const +{ + switch (type()) { + case DATETIME: + return *this; + case DATE: + return *this; + + case AMOUNT: { + optional<datetime_t> temp = as_amount().annotation_details().date; + if (! temp) + return false; + return *temp; + } + + default: + break; + } + + throw_(value_error, "Cannot find the annotated date of " << label()); + return NULL_VALUE; +} + +value_t value_t::annotated_tag() const +{ + switch (type()) { + case AMOUNT: { + optional<string> temp = as_amount().annotation_details().tag; + if (! temp) + return false; + return string_value(*temp); + } + + case STRING: + return *this; + + default: + break; + } + + throw_(value_error, "Cannot find the annotated tag of " << label()); + return NULL_VALUE; +} +#endif + +value_t value_t::strip_annotations(const bool keep_price, + const bool keep_date, + const bool keep_tag) const +{ + switch (type()) { + case VOID: + case BOOLEAN: + case INTEGER: + case DATETIME: + case DATE: + case STRING: + case POINTER: + return *this; + + case SEQUENCE: { + sequence_t temp; + foreach (const value_t& value, as_sequence()) + temp.push_back(value.strip_annotations(keep_price, keep_date, keep_tag)); + return temp; + } + + case AMOUNT: + return as_amount().strip_annotations(keep_price, keep_date, keep_tag); + case BALANCE: + return as_balance().strip_annotations(keep_price, keep_date, keep_tag); + case BALANCE_PAIR: + return as_balance_pair().quantity().strip_annotations(keep_price, keep_date, + keep_tag); + + default: + assert(false); + break; + } + assert(false); + return NULL_VALUE; +} + +value_t value_t::cost() const +{ + switch (type()) { + case INTEGER: + case AMOUNT: + case BALANCE: + return *this; + + case BALANCE_PAIR: + assert(as_balance_pair().cost); + if (as_balance_pair().cost) + return *(as_balance_pair().cost); + else + return as_balance_pair().quantity(); + + default: + break; + } + + throw_(value_error, "Cannot find the cost of " << label()); + return NULL_VALUE; +} + +value_t& value_t::add(const amount_t& amount, const optional<amount_t>& tcost) +{ + switch (type()) { + case INTEGER: + case AMOUNT: + if (tcost) { + in_place_cast(BALANCE_PAIR); + return add(amount, tcost); + } + else if ((is_amount() && + as_amount().commodity() != amount.commodity()) || + (! is_amount() && amount.commodity())) { + in_place_cast(BALANCE); + return add(amount, tcost); + } + else if (! is_amount()) { + in_place_cast(AMOUNT); + } + return *this += amount; + + case BALANCE: + if (tcost) { + in_place_cast(BALANCE_PAIR); + return add(amount, tcost); + } + return *this += amount; + + case BALANCE_PAIR: + as_balance_pair_lval().add(amount, tcost); + return *this; + + default: + break; + } + + throw_(value_error, "Cannot add an amount to " << label()); + return *this; +} + +void value_t::dump(std::ostream& out, const int first_width, + const int latter_width) const +{ + switch (type()) { + case VOID: + out << "VOID"; + break; + + case BOOLEAN: + out << as_boolean(); + break; + + case DATETIME: + out << format_datetime(as_datetime()); + break; + + case DATE: + out << format_date(as_date()); + break; + + case INTEGER: + out << as_long(); + break; + + case AMOUNT: + out << as_amount(); + break; + + case STRING: + out << as_string(); + break; + + case POINTER: + out << boost::unsafe_any_cast<const void *>(&as_any_pointer()); + break; + + case SEQUENCE: { + out << '('; + bool first = true; + foreach (const value_t& value, as_sequence()) { + if (first) + first = false; + else + out << ", "; + + value.dump(out, first_width, latter_width); + } + out << ')'; + break; + } + + case BALANCE: + as_balance().print(out, first_width, latter_width); + break; + case BALANCE_PAIR: + as_balance_pair().print(out, first_width, latter_width); + break; + default: + assert(false); + break; + } +} + +void value_t::print(std::ostream& out, const bool relaxed) const +{ + switch (type()) { + case VOID: + out << ""; + break; + + case BOOLEAN: + if (as_boolean()) + out << "true"; + else + out << "false"; + break; + + case INTEGER: + out << as_long(); + break; + + case AMOUNT: + if (! relaxed) + out << '{'; + out << as_amount(); + if (! relaxed) + out << '}'; + break; + + case BALANCE: + case BALANCE_PAIR: + assert(false); + break; + + case DATETIME: + assert(false); + break; + case DATE: + out << '[' << format_date(as_date()) << ']'; + break; + + case STRING: + out << '"' << as_string() << '"'; + break; + + case POINTER: + assert(false); + break; + + case SEQUENCE: { + out << '('; + bool first = true; + foreach (const value_t& value, as_sequence()) { + if (first) + first = false; + else + out << ", "; + + value.print(out, relaxed); + } + out << ')'; + break; + } + } +} + +void value_t::read(const char *& data) +{ + switch (static_cast<value_t::type_t>(binary::read_long<int>(data))) { + case BOOLEAN: + set_boolean(binary::read_bool(data)); + break; + case INTEGER: + set_long(binary::read_long<unsigned long>(data)); + break; + case DATETIME: +#if 0 + // jww (2008-04-22): I need to record and read a datetime_t directly + set_datetime(read_long<unsigned long>(data)); +#endif + break; + case DATE: +#if 0 + // jww (2008-04-22): I need to record and read a date_t directly + set_date(read_long<unsigned long>(data)); +#endif + break; + case AMOUNT: { + amount_t temp; + temp.read(data); + set_amount(temp); + break; + } + default: + break; + } + + throw_(value_error, "Cannot read " << label() << " from a stream"); +} + +void value_t::write(std::ostream& out) const +{ + binary::write_long(out, static_cast<int>(type())); + + switch (type()) { + case BOOLEAN: + binary::write_bool(out, as_boolean()); + break; + case INTEGER: + binary::write_long(out, as_long()); + break; + case DATETIME: +#if 0 + binary::write_number(out, as_datetime()); +#endif + break; + case DATE: +#if 0 + binary::write_number(out, as_date()); +#endif + break; + case AMOUNT: + as_amount().write(out); + break; + default: + break; + } + + throw_(value_error, "Cannot read " << label() << " to a stream"); +} + +bool value_t::valid() const +{ + switch (type()) { + case AMOUNT: + return as_amount().valid(); + case BALANCE: + return as_balance().valid(); + case BALANCE_PAIR: + return as_balance_pair().valid(); + default: + break; + } + return true; +} + +} // namespace ledger diff --git a/src/value.h b/src/value.h new file mode 100644 index 00000000..4754de61 --- /dev/null +++ b/src/value.h @@ -0,0 +1,922 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file value.h + * @author John Wiegley + * @date Thu Jun 14 21:54:00 2007 + * + * @brief Abstract dynamic type representing various numeric types. + * + * A value_t object can be one of many types, and changes its type + * dynamically based on how it is used. For example, if you assign + * the number 10 to a value object, it's internal type will be + * INTEGER. + */ +#ifndef _VALUE_H +#define _VALUE_H + +#include "balpair.h" // pulls in balance.h and amount.h + +namespace ledger { + +DECLARE_EXCEPTION(value_error, std::runtime_error); + +/** + * @class value_t + * + * @brief Dynamic type representing various numeric types. + * + * The following type is a polymorphous value type used solely for + * performance reasons. The alternative is to compute value + * expressions (valexpr.cc) in terms of the largest data type, + * balance_t. This was found to be prohibitively expensive, especially + * when large logic chains were involved, since many temporary + * allocations would occur for every operator. With value_t, and the + * fact that logic chains only need boolean values to continue, no + * memory allocations need to take place at all. + */ +class value_t + : public ordered_field_operators<value_t, + equality_comparable<value_t, balance_pair_t, + equality_comparable<value_t, balance_t, + additive<value_t, balance_pair_t, + additive<value_t, balance_t, + multiplicative<value_t, balance_pair_t, + multiplicative<value_t, balance_t, + ordered_field_operators<value_t, amount_t, +#ifdef HAVE_GDTOA + ordered_field_operators<value_t, double, +#endif + ordered_field_operators<value_t, unsigned long, + ordered_field_operators<value_t, long> > > > > > > > > > +#ifdef HAVE_GDTOA + > +#endif +{ +public: + /** + * The sequence_t member type abstracts the type used to represent a + * resizable "array" of value_t objects. + */ + typedef std::vector<value_t> sequence_t; + + typedef sequence_t::iterator iterator; + typedef sequence_t::const_iterator const_iterator; + typedef sequence_t::difference_type difference_type; + + /** + * type_t gives the type of the data contained or referenced by a + * value_t object. Use the type() method to get a value of type + * type_t. + */ + enum type_t { + VOID, // a null value (i.e., uninitialized) + BOOLEAN, // a boolean + DATETIME, // a date and time (Boost posix_time) + DATE, // a date (Boost gregorian::date) + INTEGER, // a signed integer value + AMOUNT, // a ledger::amount_t + BALANCE, // a ledger::balance_t + BALANCE_PAIR, // a ledger::balance_pair_t + STRING, // a string object + SEQUENCE, // a vector of value_t objects + POINTER // an opaque pointer of any type + }; + +private: + class storage_t + { + friend class value_t; + + /** + * The `data' member holds the actual bytes relating to whatever + * has been stuffed into this storage object. There is a set of + * asserts in value.cc to guarantee that the sizeof expression + * used here is indeed at least as big as the largest object that + * will ever be copied into `data'. + * + * The `type' member holds the value_t::type_t value representing + * the type of the object stored. + */ + char data[sizeof(amount_t)]; + type_t type; + + /** + * `refc' holds the current reference count for each storage_t + * object. + */ + mutable int refc; + + /** + * Constructor. Since all storage object are assigned to after + * construction, the only constructors allowed are explicit, and + * copy (see below). The default starting type is VOID, which + * should rarely ever be seen in practice, since the first thing + * that value_t typically does is to assign a valid value. + */ + explicit storage_t() : type(VOID), refc(0) { + TRACE_CTOR(value_t::storage_t, ""); + } + + public: // so `checked_delete' can access it + /** + * Destructor. Must only be called when the reference count has + * reached zero. The `destroy' method is used to do the actual + * cleanup of the data, since it's quite possible for `destroy' to + * be called while the object is still active -- to clear the + * stored data for subsequent reuse of the storage_t object. + */ + ~storage_t() { + TRACE_DTOR(value_t::storage_t); + DEBUG("value.storage.refcount", "Destroying " << this); + assert(refc == 0); + destroy(); + } + + void destroy(); + + private: + /** + * Assignment and copy operators. These are called when making a + * new copy of a storage object in order to modify the copy. + */ + explicit storage_t(const storage_t& rhs) + : type(rhs.type), refc(0) { + TRACE_CTOR(value_t::storage_t, "copy"); + *this = rhs; + } + storage_t& operator=(const storage_t& rhs); + + /** + * Reference counting methods. The intrusive_ptr_* methods are + * used by boost::intrusive_ptr to manage the calls to acquire and + * release. + */ + void acquire() const { + DEBUG("value.storage.refcount", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + } + void release() const { + DEBUG("value.storage.refcount", + "Releasing " << this << ", refc now " << refc - 1); + assert(refc > 0); + if (--refc == 0) + checked_delete(this); + } + + friend inline void intrusive_ptr_add_ref(value_t::storage_t * storage) { + storage->acquire(); + } + friend inline void intrusive_ptr_release(value_t::storage_t * storage) { + storage->release(); + } + }; + + /** + * The actual data for each value_t is kept in the `storage' member. + * Data is modified using a copy-on-write policy. + */ + intrusive_ptr<storage_t> storage; + + /** + * _dup() makes a private copy of the current value (if necessary) + * so it can subsequently be modified. + * + * _clear() removes our pointer to the current value and initializes + * a new storage bin for things to be stored in. + * + * _reset() makes the current object appear as if it were + * uninitialized. + */ + void _dup(); + void _clear() { + if (! storage || storage->refc > 1) + storage = new storage_t; + else + storage->destroy(); + } + void _reset() { + if (storage) + storage = intrusive_ptr<storage_t>(); + } + + /** + * Because boolean "true" and "false" are so common, a pair of + * static references are kept to prevent the creation of throwaway + * storage_t objects just to represent these two common values. + */ + static intrusive_ptr<storage_t> true_value; + static intrusive_ptr<storage_t> false_value; + +public: + // jww (2007-05-03): Make these private, and make ledger::initialize + // a member function of session_t. + static void initialize(); + static void shutdown(); + +public: + /** + * Constructors. value_t objects may be constructed from almost any + * value type that they can contain, including variations on those + * types (such as long, unsigned long, etc). The ordering of the + * methods here reflects the ordering of the constants in type_t + * above. + * + * One constructor of special note is that taking a string or + * character pointer as an argument. Because value_t("$100") is + * interpreted as a commoditized amount, the form value_t("$100", + * true) is required to represent the literal string "$100", and not + * the amount "one hundred dollars". + */ + value_t() { + TRACE_CTOR(value_t, ""); + } + + value_t(const bool val) { + TRACE_CTOR(value_t, "const bool"); + set_boolean(val); + } + + value_t(const datetime_t& val) { + TRACE_CTOR(value_t, "const datetime_t&"); + set_datetime(val); + } + value_t(const date_t& val) { + TRACE_CTOR(value_t, "const date_t&"); + set_date(val); + } + + value_t(const long val) { + TRACE_CTOR(value_t, "const long"); + set_long(val); + } + value_t(const unsigned long val) { + TRACE_CTOR(value_t, "const unsigned long"); + set_amount(val); + } +#ifdef HAVE_GDTOA + value_t(const double val) { + TRACE_CTOR(value_t, "const double"); + set_amount(val); + } +#endif + value_t(const amount_t& val) { + TRACE_CTOR(value_t, "const amount_t&"); + set_amount(val); + } + value_t(const balance_t& val) { + TRACE_CTOR(value_t, "const balance_t&"); + set_balance(val); + } + value_t(const balance_pair_t& val) { + TRACE_CTOR(value_t, "const balance_pair_t&"); + set_balance_pair(val); + } + + explicit value_t(const string& val, bool literal = false) { + TRACE_CTOR(value_t, "const string&, bool"); + if (literal) + set_string(val); + else + set_amount(amount_t(val)); + } + explicit value_t(const char * val, bool literal = false) { + TRACE_CTOR(value_t, "const char *"); + if (literal) + set_string(val); + else + set_amount(amount_t(val)); + } + + value_t(const sequence_t& val) { + TRACE_CTOR(value_t, "const sequence_t&"); + set_sequence(val); + } + + template <typename T> + explicit value_t(T * item) { + TRACE_CTOR(value_t, "T *"); + set_pointer(item); + } + + /** + * Destructor. This does not do anything, because the intrusive_ptr + * that refers to our storage object will decrease its reference + * count itself upon destruction. + */ + ~value_t() { + TRACE_DTOR(value_t); + } + + /** + * Assignment and copy operators. Values are cheaply copied by + * simply creating another reference to the other value's storage + * object. A true copy is only ever made prior to modification. + */ + value_t(const value_t& val) { + TRACE_CTOR(value_t, "copy"); + *this = val; + } + value_t& operator=(const value_t& val) { + if (! (this == &val || storage == val.storage)) + storage = val.storage; + return *this; + } + + /** + * Comparison operators. Values can be compared to other values + */ + bool operator==(const value_t& val) const; + bool operator<(const value_t& val) const; + + template <typename T> + bool operator==(const T& amt) const { + return *this == value_t(amt); + } + template <typename T> + bool operator<(const T& amt) const { + return *this < value_t(amt); + } + + /** + * Binary arithmetic operators. + * + * add(amount_t, optional<amount_t>) allows for the possibility of + * adding both an amount and its cost in a single operation. + * Otherwise, there is no way to separately represent the "cost" + * part of an amount addition statement. + */ + 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); + + // This special form of add is use to produce a balance pair by + // simultaneously adding both an amount and its cost. + value_t& add(const amount_t& amount, + const optional<amount_t>& cost = none); + + /** + * Unary arithmetic operators. + */ + value_t negate() const { + value_t temp = *this; + temp.in_place_negate(); + return temp; + } + void in_place_negate(); // exists for efficiency's sake + + value_t operator-() const { + return negate(); + } + + value_t abs() const; + value_t round() const; + value_t unround() const; + + value_t reduce() const { + value_t temp(*this); + temp.in_place_reduce(); + return temp; + } + void in_place_reduce(); // exists for efficiency's sake + + // Return the "market value" of a given value at a specific time. + value_t value(const optional<datetime_t>& moment = none) const; + value_t cost() const; + + + /** + * Truth tests. + */ + operator bool() const; + + bool is_realzero() const; + bool is_zero() const; + bool is_null() const { + if (! storage) { + assert(is_type(VOID)); + return true; + } else { + assert(! is_type(VOID)); + return false; + } + } + + type_t type() const { + type_t result = storage ? storage->type : VOID; + assert(result >= VOID && result <= POINTER); + return result; + } + bool is_type(type_t _type) const { + return type() == _type; + } + +private: + void set_type(type_t new_type) { + assert(new_type >= VOID && new_type <= POINTER); + if (new_type == VOID) { + _reset(); + assert(is_null()); + } else { + _clear(); + storage->type = new_type; + assert(is_type(new_type)); + } + } + +public: + /** + * Data manipulation methods. A value object may be truth tested for the + * existence of every type it can contain: + * + * is_boolean() + * is_long() + * is_datetime() + * is_date() + * is_amount() + * is_balance() + * is_balance_pair() + * is_string() + * is_sequence() + * is_pointer() + * + * There are corresponding as_*() methods that represent a value as a + * reference to its underlying type. For example, as_long() returns a + * reference to a "const long". + * + * There are also as_*_lval() methods, which represent the underlying data + * as a reference to a non-const type. The difference here is that an + * _lval() call causes the underlying data to be fully copied before the + * resulting reference is returned. + * + * Lastly, there are corresponding set_*(data) methods for directly + * assigning data of a particular type, rather than using the regular + * assignment operator (whose implementation simply calls the various set_ + * methods). + */ + bool is_boolean() const { + return is_type(BOOLEAN); + } + bool& as_boolean_lval() { + assert(is_boolean()); + _dup(); + return *reinterpret_cast<bool *>(storage->data); + } + const bool& as_boolean() const { + assert(is_boolean()); + return *reinterpret_cast<bool *>(storage->data); + } + void set_boolean(const bool val) { + set_type(BOOLEAN); + storage = val ? true_value : false_value; + } + + bool is_datetime() const { + return is_type(DATETIME); + } + datetime_t& as_datetime_lval() { + assert(is_datetime()); + _dup(); + return *reinterpret_cast<datetime_t *>(storage->data); + } + const datetime_t& as_datetime() const { + assert(is_datetime()); + return *reinterpret_cast<datetime_t *>(storage->data); + } + void set_datetime(const datetime_t& val) { + set_type(DATETIME); + new(reinterpret_cast<datetime_t *>(storage->data)) datetime_t(val); + } + + bool is_date() const { + return is_type(DATE); + } + date_t& as_date_lval() { + assert(is_date()); + _dup(); + return *reinterpret_cast<date_t *>(storage->data); + } + const date_t& as_date() const { + assert(is_date()); + return *reinterpret_cast<date_t *>(storage->data); + } + void set_date(const date_t& val) { + set_type(DATE); + new(reinterpret_cast<date_t *>(storage->data)) date_t(val); + } + + bool is_long() const { + return is_type(INTEGER); + } + long& as_long_lval() { + assert(is_long()); + _dup(); + return *reinterpret_cast<long *>(storage->data); + } + const long& as_long() const { + assert(is_long()); + return *reinterpret_cast<long *>(storage->data); + } + void set_long(const long val) { + set_type(INTEGER); + *reinterpret_cast<long *>(storage->data) = val; + } + + bool is_amount() const { + return is_type(AMOUNT); + } + amount_t& as_amount_lval() { + assert(is_amount()); + _dup(); + amount_t& amt(*reinterpret_cast<amount_t *>(storage->data)); + assert(amt.valid()); + return amt; + } + const amount_t& as_amount() const { + assert(is_amount()); + amount_t& amt(*reinterpret_cast<amount_t *>(storage->data)); + assert(amt.valid()); + return amt; + } + void set_amount(const amount_t& val) { + assert(val.valid()); + set_type(AMOUNT); + new(reinterpret_cast<amount_t *>(storage->data)) amount_t(val); + } + + bool is_balance() const { + return is_type(BALANCE); + } + balance_t& as_balance_lval() { + assert(is_balance()); + _dup(); + balance_t& bal(**reinterpret_cast<balance_t **>(storage->data)); + assert(bal.valid()); + return bal; + } + const balance_t& as_balance() const { + assert(is_balance()); + balance_t& bal(**reinterpret_cast<balance_t **>(storage->data)); + assert(bal.valid()); + return bal; + } + void set_balance(const balance_t& val) { + assert(val.valid()); + set_type(BALANCE); + *reinterpret_cast<balance_t **>(storage->data) = new balance_t(val); + } + + bool is_balance_pair() const { + return is_type(BALANCE_PAIR); + } + balance_pair_t& as_balance_pair_lval() { + assert(is_balance_pair()); + _dup(); + balance_pair_t& bal_pair(**reinterpret_cast<balance_pair_t **>(storage->data)); + assert(bal_pair.valid()); + return bal_pair; + } + const balance_pair_t& as_balance_pair() const { + assert(is_balance_pair()); + balance_pair_t& bal_pair(**reinterpret_cast<balance_pair_t **>(storage->data)); + assert(bal_pair.valid()); + return bal_pair; + } + void set_balance_pair(const balance_pair_t& val) { + assert(val.valid()); + set_type(BALANCE_PAIR); + *reinterpret_cast<balance_pair_t **>(storage->data) = new balance_pair_t(val); + } + + bool is_string() const { + return is_type(STRING); + } + string& as_string_lval() { + assert(is_string()); + _dup(); + return *reinterpret_cast<string *>(storage->data); + } + const string& as_string() const { + assert(is_string()); + return *reinterpret_cast<string *>(storage->data); + } + void set_string(const string& val = "") { + set_type(STRING); + new(reinterpret_cast<string *>(storage->data)) string(val); + } + void set_string(const char * val = "") { + set_type(STRING); + new(reinterpret_cast<string *>(storage->data)) string(val); + } + + bool is_sequence() const { + return is_type(SEQUENCE); + } + sequence_t& as_sequence_lval() { + assert(is_sequence()); + _dup(); + return **reinterpret_cast<sequence_t **>(storage->data); + } + const sequence_t& as_sequence() const { + assert(is_sequence()); + return **reinterpret_cast<sequence_t **>(storage->data); + } + void set_sequence(const sequence_t& val) { + set_type(SEQUENCE); + *reinterpret_cast<sequence_t **>(storage->data) = new sequence_t(val); + } + + /** + * Dealing with pointers is bit involved because we actually deal + * with typed pointers. For example, if you call as_pointer it + * returns a boost::any object, but if you use as_pointer<void>, + * then it returns a void *. The latter form only succeeds if the + * stored pointers was assigned to the value as a void*, otherwise + * it throws an exception. + */ + bool is_pointer() const { + return is_type(POINTER); + } + boost::any& as_any_pointer_lval() { + assert(is_pointer()); + _dup(); + return *reinterpret_cast<boost::any *>(storage->data); + } + template <typename T> + T * as_pointer_lval() { + assert(is_pointer()); + _dup(); + return any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data)); + } + template <typename T> + T& as_ref_lval() { + assert(is_pointer()); + _dup(); + return *any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data)); + } + const boost::any& as_any_pointer() const { + assert(is_pointer()); + return *reinterpret_cast<boost::any *>(storage->data); + } + template <typename T> + T * as_pointer() const { + assert(is_pointer()); + return any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data)); + } + template <typename T> + T& as_ref() const { + assert(is_pointer()); + return *any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data)); + } + void set_any_pointer(const boost::any& val) { + set_type(POINTER); + new(reinterpret_cast<boost::any *>(storage->data)) boost::any(val); + } + template <typename T> + void set_pointer(T * val) { + set_type(POINTER); + new(reinterpret_cast<boost::any *>(storage->data)) boost::any(val); + } + + /** + * Data conversion methods. These methods convert a value object to + * its underlying type, where possible. If not possible, an + * exception is thrown. + */ + bool to_boolean() const; + long to_long() const; + datetime_t to_datetime() const; + date_t to_date() const; + amount_t to_amount() const; + balance_t to_balance() const; + balance_pair_t to_balance_pair() const; + string to_string() const; + sequence_t to_sequence() const; + + /** + * Dynamic typing conversion methods. + * + * `cast(type_t)' returns a new value whose type has been cast to + * the given type, but whose value is based on the original value. + * For example, the uncommoditized AMOUNT "100.00" could be cast to + * an INTEGER value. If a cast would lose information or is not + * meaningful, an exception is thrown. + * + * `simplify()' is an automatic cast to the simplest type that can + * still represent the original value. + * + * There are also "in-place" versions of these two methods: + * in_place_cast + * in_place_simplify + */ + value_t cast(type_t cast_type) const { + value_t temp(*this); + temp.in_place_cast(cast_type); + return temp; + } + void in_place_cast(type_t cast_type); + + value_t simplify() const { + value_t temp = *this; + temp.in_place_simplify(); + return temp; + } + void in_place_simplify(); + + /** + * Annotated commodity methods. + */ +#if 0 + // These helper methods only apply to AMOUNT values. + value_t annotated_price() const; + value_t annotated_date() const; + value_t annotated_tag() const; +#endif + + 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; + + /** + * Collection-style access methods for SEQUENCE values. + */ + value_t& operator[](const int index) { + assert(! is_null()); + if (is_sequence()) + return as_sequence_lval()[index]; + else if (index == 0) + return *this; + + assert(false); + static value_t null; + return null; + } + const value_t& operator[](const int index) const { + assert(! is_null()); + if (is_sequence()) + return as_sequence()[index]; + else if (index == 0) + return *this; + + assert(false); + static value_t null; + return null; + } + + void push_back(const value_t& val) { + if (! val.is_null()) { + if (is_null()) { + *this = val; + } else { + if (! is_sequence()) + in_place_cast(SEQUENCE); + + if (! val.is_sequence()) { + if (! val.is_null()) + as_sequence_lval().push_back(val); + } else { + const value_t::sequence_t& val_seq(val.as_sequence()); + std::copy(val_seq.begin(), val_seq.end(), + back_inserter(as_sequence_lval())); + } + } + } + } + + void pop_back() { + assert(! is_null()); + + if (! is_sequence()) { + _reset(); + } else { + as_sequence_lval().pop_back(); + + const value_t::sequence_t& seq(as_sequence()); + std::size_t new_size = seq.size(); + if (new_size == 0) + _reset(); + else if (new_size == 1) + *this = seq.front(); + } + } + + const std::size_t size() const { + if (is_null()) + return 0; + else if (is_sequence()) + return as_sequence().size(); + else + return 1; + } + + /** + * Informational methods. + */ + string label(optional<type_t> the_type = none) const { + switch (the_type ? *the_type : type()) { + case VOID: + return "an uninitialized value"; + case BOOLEAN: + return "a boolean"; + case DATETIME: + return "a date/time"; + case DATE: + return "a date"; + case INTEGER: + return "an integer"; + case AMOUNT: + return "an amount"; + case BALANCE: + return "a balance"; + case BALANCE_PAIR: + return "a balance pair"; + case STRING: + return "a string"; + case SEQUENCE: + return "a sequence"; + case POINTER: + return "a pointer"; + default: + assert(false); + break; + } + assert(false); + return "<invalid>"; + } + + /** + * Printing methods. + */ + void dump(std::ostream& out, const int first_width, + const int latter_width = -1) const; + void print(std::ostream& out, const bool relaxed = true) const; + + /** + * Serialization methods. A value may be deserialized from an input + * stream or a character pointer, and it may be serialized to an + * output stream. The methods used are: + */ + void read(const char *& data); + void write(std::ostream& out) const; + + /** + * Debugging methods. + */ + bool valid() const; +}; + +#define NULL_VALUE (value_t()) + +inline value_t string_value(const string& str) { + return value_t(str, true); +} + +inline std::ostream& operator<<(std::ostream& out, const value_t& val) { + val.print(out, 12); + return out; +} + +inline string value_context(const value_t& val) { + std::ostringstream buf; + buf << std::right; + buf.width(20); + val.print(buf); + buf << std::endl; + return buf.str(); +} + +} // namespace ledger + +#endif // _VALUE_H diff --git a/src/xact.cc b/src/xact.cc new file mode 100644 index 00000000..ce4da0d7 --- /dev/null +++ b/src/xact.cc @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "xact.h" +#include "journal.h" +#include "account.h" +#include "format.h" + +namespace ledger { + +bool xact_t::use_effective_date = false; + +xact_t::~xact_t() +{ + TRACE_DTOR(xact_t); +} + +date_t xact_t::actual_date() const +{ + if (! _date && entry) + return entry->actual_date(); + return *_date; +} + +date_t xact_t::effective_date() const +{ + if (! _date_eff && entry) + return entry->effective_date(); + return *_date_eff; +} + +namespace { + value_t get_state(xact_t& xact) { + return long(xact.state); + } + + value_t state_uncleared(call_scope_t&) { + return 0L; + } + + value_t state_cleared(call_scope_t&) { + return 1L; + } + + value_t state_pending(call_scope_t&) { + return 2L; + } + + value_t get_date(xact_t& xact) { + return xact.date(); + } + + value_t get_payee(xact_t& xact) { + return string_value(xact.entry->payee); + } + + value_t get_amount(xact_t& xact) { + return xact.amount; + } + + value_t get_total(xact_t& xact) { + if (xact.xdata_) + return xact.xdata_->total; + else + return xact.amount; + } + + value_t get_cost(xact_t& xact) { + return xact.cost ? *xact.cost : xact.amount; + } + + value_t get_note(xact_t& xact) { + return string_value(xact.note ? *xact.note : ":NOTELESS:"); + } + + value_t get_account(call_scope_t& scope) + { + xact_t& xact(downcast<xact_t>(*scope.parent)); + + var_t<long> max_width(scope, 0); + + string name = xact.reported_account()->fullname(); + + if (max_width && *max_width > 2) + name = format_t::truncate(name, *max_width - 2, true); + + if (xact.has_flags(XACT_VIRTUAL)) { + if (xact.must_balance()) + name = string("[") + name + "]"; + else + name = string("(") + name + ")"; + } + return string_value(name); + } + + value_t get_account_base(xact_t& xact) { + return string_value(xact.reported_account()->name); + } + + value_t get_beg_pos(xact_t& xact) { + return long(xact.beg_pos); + } + + value_t get_beg_line(xact_t& xact) { + return long(xact.beg_line); + } + + value_t get_end_pos(xact_t& xact) { + return long(xact.end_pos); + } + + value_t get_end_line(xact_t& xact) { + return long(xact.end_line); + } + + template <value_t (*Func)(xact_t&)> + value_t get_wrapper(call_scope_t& scope) { + return (*Func)(find_scope<xact_t>(scope)); + } +} + +expr_t::ptr_op_t xact_t::lookup(const string& name) +{ + switch (name[0]) { + case 'a': + if (name[1] == '\0' || name == "amount") + return WRAP_FUNCTOR(get_wrapper<&get_amount>); + else if (name == "account") + return WRAP_FUNCTOR(get_account); + else if (name == "account_base") + return WRAP_FUNCTOR(get_wrapper<&get_account_base>); + break; + + case 'c': + if (name == "cleared") + return expr_t::op_t::wrap_value(0L); + break; + + case 'd': + if (name[1] == '\0' || name == "date") + return WRAP_FUNCTOR(get_wrapper<&get_date>); + break; + + case 'f': + if (name.find("fmt_") == 0) { + switch (name[4]) { + case 'A': + return WRAP_FUNCTOR(get_account); + case 'D': + return WRAP_FUNCTOR(get_wrapper<&get_date>); + case 'P': + return WRAP_FUNCTOR(get_wrapper<&get_payee>); + } + } + break; + + case 'p': + if (name == "pending") + return expr_t::op_t::wrap_value(2L); + else if (name == "payee") + return WRAP_FUNCTOR(get_wrapper<&get_payee>); + break; + + case 't': + if (name[1] == '\0' || name == "total") + return WRAP_FUNCTOR(get_wrapper<&get_total>); + break; + + case 'u': + if (name == "uncleared") + return expr_t::op_t::wrap_value(1L); + break; + } + return entry->lookup(name); +} + +bool xact_t::valid() const +{ + if (! entry) { + DEBUG("ledger.validate", "xact_t: ! entry"); + return false; + } + + if (state != UNCLEARED && state != CLEARED && state != PENDING) { + DEBUG("ledger.validate", "xact_t: state is bad"); + return false; + } + + xacts_list::const_iterator i = + std::find(entry->xacts.begin(), + entry->xacts.end(), this); + if (i == entry->xacts.end()) { + DEBUG("ledger.validate", "xact_t: ! found"); + return false; + } + + if (! account) { + DEBUG("ledger.validate", "xact_t: ! account"); + return false; + } + + if (! amount.valid()) { + DEBUG("ledger.validate", "xact_t: ! amount.valid()"); + return false; + } + + if (cost && ! cost->valid()) { + DEBUG("ledger.validate", "xact_t: cost && ! cost->valid()"); + return false; + } + + if (flags() & ~0x003f) { + DEBUG("ledger.validate", "xact_t: flags are bad"); + return false; + } + + return true; +} + +#if 0 +xact_context::xact_context(const xact_t& _xact, const string& desc) throw() + : file_context("", 0, desc), xact(_xact) +{ + const paths_list& sources(xact.entry->journal->sources); + unsigned int x = 0; + foreach (const path& path, sources) + if (x == xact.entry->src_idx) { + file = path; + break; + } + line = xact.beg_line; +} +#endif + +void xact_t::add_to_value(value_t& value) +{ + if (xdata_ && xdata_->has_flags(XACT_EXT_COMPOUND)) { + value += xdata_->value; + } + else if (cost || (! value.is_null() && ! value.is_realzero())) { + if (value.is_null()) + value = amount_t(); + value.add(amount, cost); + } + else { + value = amount; + } +} + +} // namespace ledger diff --git a/src/xact.h b/src/xact.h new file mode 100644 index 00000000..5d4950f9 --- /dev/null +++ b/src/xact.h @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XACT_H +#define _XACT_H + +#include "utils.h" +#include "scope.h" + +namespace ledger { + +class entry_t; +class account_t; + +class xact_t; +typedef std::list<xact_t *> xacts_list; + +class xact_t : public supports_flags<>, public scope_t +{ +public: +#define XACT_NORMAL 0x0000 // no flags at all, a basic transaction +#define XACT_VIRTUAL 0x0001 // the account was specified with (parens) +#define XACT_BALANCE 0x0002 // the account was specified with [brackets] +#define XACT_AUTO 0x0004 // transaction created by automated entry +#define XACT_IN_CACHE 0x0008 // transaction allocated by the binary cache +#define XACT_CALCULATED 0x0010 // transaction's amount was auto-calculated +#define XACT_GENERATED 0x0020 // transaction was not found in a journal +#define XACT_TEMP 0x0040 // transaction is a temporary object + + enum state_t { UNCLEARED, CLEARED, PENDING }; + + entry_t * entry; + account_t * account; + state_t state; + + optional<date_t> _date; + optional<date_t> _date_eff; + optional<string> note; + + amount_t amount; + optional<expr_t> amount_expr; + optional<amount_t> cost; + optional<expr_t> cost_expr; + + istream_pos_type beg_pos; + unsigned long beg_line; + istream_pos_type end_pos; + unsigned long end_line; + + static bool use_effective_date; + + xact_t(account_t * _account = NULL, + flags_t _flags = XACT_NORMAL) + : supports_flags<>(_flags), entry(NULL), account(_account), + state(UNCLEARED), beg_pos(0), beg_line(0), end_pos(0), end_line(0) + { + TRACE_CTOR(xact_t, "account_t *, flags_t"); + } + xact_t(account_t * _account, + const amount_t& _amount, + flags_t _flags = XACT_NORMAL, + const optional<string>& _note = none) + : supports_flags<>(_flags), entry(NULL), account(_account), + state(UNCLEARED), note(_note), amount(_amount), + beg_pos(0), beg_line(0), end_pos(0), end_line(0) + { + TRACE_CTOR(xact_t, + "account_t *, const amount_t&, flags_t, const string&"); + } + xact_t(const xact_t& xact) + : supports_flags<>(xact), + scope_t(), + entry(xact.entry), + account(xact.account), + state(xact.state), + _date(xact._date), + _date_eff(xact._date_eff), + note(xact.note), + amount(xact.amount), + cost(xact.cost), + beg_pos(xact.beg_pos), + beg_line(xact.beg_line), + end_pos(xact.end_pos), + end_line(xact.end_line), + xdata_(xact.xdata_) // jww (2008-07-19): What are the copy semantics? + { + TRACE_CTOR(xact_t, "copy"); + } + ~xact_t(); + + date_t actual_date() const; + date_t effective_date() const; + date_t date() const { + if (use_effective_date) + return effective_date(); + else + return actual_date(); + } + + bool must_balance() const { + return ! has_flags(XACT_VIRTUAL) || has_flags(XACT_BALANCE); + } + + virtual expr_t::ptr_op_t lookup(const string& name); + + bool valid() const; + + struct xdata_t : public supports_flags<> + { +#define XACT_EXT_RECEIVED 0x01 +#define XACT_EXT_HANDLED 0x02 +#define XACT_EXT_TO_DISPLAY 0x04 +#define XACT_EXT_DISPLAYED 0x08 +#define XACT_EXT_NO_TOTAL 0x10 +#define XACT_EXT_SORT_CALC 0x20 +#define XACT_EXT_COMPOUND 0x40 +#define XACT_EXT_MATCHES 0x80 + + value_t total; + value_t sort_value; + value_t value; + unsigned int index; + date_t date; + account_t * account; + void * ptr; + + optional<xacts_list> component_xacts; + + xdata_t() : supports_flags<>(), index(0), account(NULL), ptr(NULL) { + TRACE_CTOR(xdata_t, ""); + } + ~xdata_t() throw() { + TRACE_DTOR(xdata_t); + } + + void remember_xact(xact_t& xact) { + if (! component_xacts) + component_xacts = xacts_list(); + component_xacts->push_back(&xact); + } + + bool has_component_xacts() const { + return component_xacts && ! component_xacts->empty(); + } + + void copy_component_xacts(xacts_list& xacts) { + foreach (xact_t * xact, xacts) + remember_xact(*xact); + } + +#if 0 + void walk_component_xacts(item_handler<xact_t>& handler) const { + foreach (xact_t * xact, *component_xacts) + handler(*xact); + } +#endif + }; + + // This variable holds optional "extended data" which is usually produced + // only during reporting, and only for the transaction set being reported. + // It's a memory-saving measure to delay allocation until the last possible + // moment. + mutable optional<xdata_t> xdata_; + + bool has_xdata() const { + return xdata_; + } + void clear_xdata() { + xdata_ = none; + } + xdata_t& xdata() { + if (! xdata_) + xdata_ = xdata_t(); + return *xdata_; + } + + void add_to_value(value_t& value); + + date_t reported_date() const { + if (xdata_ && is_valid(xdata_->date)) + return xdata_->date; + return + date(); + } + + account_t * reported_account() { + if (xdata_) + if (account_t * acct = xdata_->account) + return acct; + return account; + } + + const account_t * reported_account() const { + return const_cast<xact_t *>(this)->reported_account(); + } +}; + +} // namespace ledger + +#endif // _XACT_H diff --git a/src/xml.cc b/src/xml.cc new file mode 100644 index 00000000..715965cb --- /dev/null +++ b/src/xml.cc @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "xml.h" +#include "journal.h" +#include "utils.h" + +namespace ledger { + +#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 xact_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 = xact_t::UNCLEARED; + } + else if (std::strcmp(name, "xact") == 0) { + assert(curr_entry); + curr_entry->add_xact(new xact_t); + if (curr_state != xact_t::UNCLEARED) + curr_entry->xacts.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("<Unknown>"); + curr_entry->add_xact(new xact_t(acct)); + if (curr_journal->add_entry(curr_entry)) { + count++; + } else { + checked_delete(curr_entry); + have_error = "Entry cannot be balanced"; + } + } + curr_entry = NULL; + } + else if (std::strcmp(name, "en:date") == 0) { + curr_entry->_date = parse_date(data); + } + else if (std::strcmp(name, "en:date_eff") == 0) { + curr_entry->_date_eff = parse_date(data); + } + else if (std::strcmp(name, "en:code") == 0) { + curr_entry->code = data; + } + else if (std::strcmp(name, "en:cleared") == 0) { + curr_state = xact_t::CLEARED; + } + else if (std::strcmp(name, "en:pending") == 0) { + curr_state = xact_t::PENDING; + } + else if (std::strcmp(name, "en:payee") == 0) { + curr_entry->payee = data; + } + else if (std::strcmp(name, "tr:account") == 0) { + curr_entry->xacts.back()->account = curr_journal->find_account(data); + } + else if (std::strcmp(name, "tr:cleared") == 0) { + curr_entry->xacts.back()->state = xact_t::CLEARED; + } + else if (std::strcmp(name, "tr:pending") == 0) { + curr_entry->xacts.back()->state = xact_t::PENDING; + } + else if (std::strcmp(name, "tr:virtual") == 0) { + curr_entry->xacts.back()->add_flags(XACT_VIRTUAL); + } + else if (std::strcmp(name, "tr:generated") == 0) { + curr_entry->xacts.back()->add_flags(XACT_AUTO); + } + else if (std::strcmp(name, "symbol") == 0) { + assert(! curr_comm); + curr_comm = amount_t::current_pool->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->xacts.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->xacts.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, "<?xml", 5) != 0) { + in.clear(); + in.seekg(0, std::ios::beg); + return false; + } + + in.getline(buf, 79); + if (! std::strstr(buf, "<ledger")) { + in.clear(); + in.seekg(0, std::ios::beg); + return false; + } + + in.clear(); + in.seekg(0, std::ios::beg); + return true; +} + +unsigned int xml_parser_t::parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master, + const path * original_file) +{ + char buf[BUFSIZ]; + + count = 0; + curr_journal = &journal; + curr_entry = NULL; + curr_comm = NULL; + ignore = false; + + XML_Parser parser = XML_ParserCreate(NULL); + current_parser = parser; + + XML_SetElementHandler(parser, startElement, endElement); + XML_SetCharacterDataHandler(parser, dataHandler); + + 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_error(err.what()); + } + + if (! have_error.empty()) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + parse_error err(have_error); + std::cerr << "Error: " << err.what() << std::endl; + have_error = ""; + } + + if (! result) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + const char * err = XML_ErrorString(XML_GetErrorCode(parser)); + XML_ParserFree(parser); + throw parse_error(err); + } + } + + XML_ParserFree(parser); + + return count; +} + +#endif // defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +void xml_write_amount(std::ostream& out, const amount_t& amount, + const int depth = 0) +{ + for (int i = 0; i < depth; i++) out << ' '; + out << "<amount>\n"; + + commodity_t& c = amount.commodity(); + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<commodity flags=\""; + if (! (c.flags() & COMMODITY_STYLE_SUFFIXED)) out << 'P'; + if (c.flags() & COMMODITY_STYLE_SEPARATED) out << 'S'; + if (c.flags() & COMMODITY_STYLE_THOUSANDS) out << 'T'; + if (c.flags() & COMMODITY_STYLE_EUROPEAN) out << 'E'; + out << "\">\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; +#if 0 + // jww (2006-03-02): !!! + if (c.price) { + out << "<symbol>" << c.base->symbol << "</symbol>\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "<price>\n"; + xml_write_amount(out, *c.price, depth + 6); + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "</price>\n"; + } else { + out << "<symbol>" << c.symbol << "</symbol>\n"; + } +#endif + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "</commodity>\n"; + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<quantity>"; + out << amount.quantity_string() << "</quantity>\n"; + + for (int i = 0; i < depth; i++) out << ' '; + out << "</amount>\n"; +} + +void xml_write_value(std::ostream& out, const value_t& value, + const int depth = 0) +{ + const balance_t * bal = NULL; + + for (int i = 0; i < depth; i++) out << ' '; + out << "<value type=\""; + switch (value.type()) { + case value_t::BOOLEAN: out << "boolean"; break; + case value_t::INTEGER: out << "integer"; break; + case value_t::AMOUNT: out << "amount"; break; + case value_t::BALANCE: + case value_t::BALANCE_PAIR: out << "balance"; break; + default: + assert(false); + break; + } + out << "\">\n"; + + switch (value.type()) { + case value_t::BOOLEAN: + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<boolean>" << value.as_boolean() << "</boolean>\n"; + break; + + case value_t::INTEGER: + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<integer>" << value.as_long() << "</integer>\n"; + break; + + case value_t::AMOUNT: + xml_write_amount(out, value.as_amount(), depth + 2); + break; + + case value_t::BALANCE: + bal = &(value.as_balance()); + // fall through... + + case value_t::BALANCE_PAIR: + if (! bal) + bal = &(value.as_balance_pair().quantity()); + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<balance>\n"; + + foreach (const balance_t::amounts_map::value_type& pair, bal->amounts) + xml_write_amount(out, pair.second, depth + 4); + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "</balance>\n"; + break; + + default: + assert(false); + break; + } + + for (int i = 0; i < depth; i++) out << ' '; + out << "</value>\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() +{ + std::ostream& out(*report.output_stream); + +#if 0 + // jww (2008-05-08): Need to format these dates + out << " <entry>\n" + << " <en:date>" << last_entry->_date.to_string("%Y/%m/%d") + << "</en:date>\n"; + + if (is_valid(last_entry->_date_eff)) + out << " <en:date_eff>" + << last_entry->_date_eff.to_string("%Y/%m/%d") + << "</en:date_eff>\n"; +#endif + + if (last_entry->code) { + out << " <en:code>"; + output_xml_string(out, *last_entry->code); + out << "</en:code>\n"; + } + + if (! last_entry->payee.empty()) { + out << " <en:payee>"; + output_xml_string(out, last_entry->payee); + out << "</en:payee>\n"; + } + + bool first = true; + foreach (xact_t * xact, last_entry->xacts) { + if (xact->has_xdata() && + xact->xdata().has_flags(XACT_EXT_TO_DISPLAY)) { + if (first) { + out << " <en:xacts>\n"; + first = false; + } + + out << " <xact>\n"; + +#if 0 + // jww (2008-05-08): Need to format these + if (xact->_date) + out << " <tr:date>" + << xact->_date.to_string("%Y/%m/%d") + << "</tr:date>\n"; + + if (is_valid(xact->_date_eff)) + out << " <tr:date_eff>" + << xact->_date_eff.to_string("%Y/%m/%d") + << "</tr:date_eff>\n"; +#endif + + if (xact->state == xact_t::CLEARED) + out << " <tr:cleared/>\n"; + else if (xact->state == xact_t::PENDING) + out << " <tr:pending/>\n"; + + if (xact->has_flags(XACT_VIRTUAL)) + out << " <tr:virtual/>\n"; + if (xact->has_flags(XACT_AUTO)) + out << " <tr:generated/>\n"; + + if (xact->account) { + string name = xact->account->fullname(); + if (name == "<Total>") + name = "[TOTAL]"; + else if (name == "<Unknown>") + name = "[UNKNOWN]"; + + out << " <tr:account>"; + output_xml_string(out, name); + out << "</tr:account>\n"; + } + + out << " <tr:amount>\n"; + if (xact->xdata().has_flags(XACT_EXT_COMPOUND)) + xml_write_value(out, xact->xdata().value, 10); + else + xml_write_value(out, value_t(xact->amount), 10); + out << " </tr:amount>\n"; + + if (xact->cost) { + out << " <tr:cost>\n"; + xml_write_value(out, value_t(*xact->cost), 10); + out << " </tr:cost>\n"; + } + + if (xact->note) { + out << " <tr:note>"; + output_xml_string(out, *xact->note); + out << "</tr:note>\n"; + } + + if (show_totals) { + out << " <total>\n"; + xml_write_value(out, xact->xdata().total, 10); + out << " </total>\n"; + } + + out << " </xact>\n"; + + xact->xdata().add_flags(XACT_EXT_DISPLAYED); + } + } + + if (! first) + out << " </en:xacts>\n"; + + out << " </entry>\n"; +} + +} // namespace ledger diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 00000000..38768cff --- /dev/null +++ b/src/xml.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _XML_H +#define _XML_H + +#include "journal.h" +#include "report.h" +#include "output.h" + +namespace ledger { + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +class xml_parser_t : public journal_t::parser_t +{ + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master = NULL, + const path * original_file = NULL); +}; + +#endif + +class format_xml_entries : public format_entries +{ + bool show_totals; + + format_xml_entries(); + +public: + format_xml_entries(report_t& _report, + const bool _show_totals = false) + : format_entries(_report, ""), show_totals(_show_totals) { + TRACE_CTOR(format_xml_entries, "std::ostream&, const bool"); + *report.output_stream << "<?xml version=\"1.0\"?>\n" + << "<ledger version=\"2.5\">\n"; + } + virtual ~format_xml_entries() throw() { + TRACE_DTOR(format_xml_entries); + } + + virtual void flush() { + format_entries::flush(); + *report.output_stream << "</ledger>" << std::endl; + } + + virtual void format_last_entry(); +}; + +} // namespace ledger + +#endif // _XML_H |