From b45037e334ca336498217d4f54756c319c074d7b Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sat, 12 Apr 2008 01:40:49 -0400 Subject: Migrated over both code and build environment from was-v3.0 branch. --- Makefile.am | 230 +++-- acprep | 147 +++- amount.cc | 2523 +++++++++++++++++++++---------------------------------- amount.h | 1156 +++++++++++++------------ balance.cc | 657 +++++---------- balance.h | 1242 +++++++++------------------ balpair.h | 367 ++++++++ binary.cc | 1335 ++--------------------------- binary.h | 275 +++++- commodity.cc | 598 +++++++++++++ commodity.h | 394 +++++++++ configure.in | 299 +++++-- datetime.cc | 363 -------- datetime.h | 314 ------- debug.cc | 125 --- debug.h | 146 ---- error.h | 33 +- fdstream.hpp | 31 + flags.h | 103 +++ journal.h | 5 +- main.py | 373 ++++++++ mask.cc | 70 +- mask.h | 56 +- parser.h | 79 ++ pushvar.h | 67 ++ py_amount.cc | 320 +++++++ py_commodity.cc | 63 ++ py_times.cc | 132 +++ py_utils.cc | 172 ++++ pyfstream.h | 169 ++++ pyinterp.cc | 238 ++++++ pyinterp.h | 98 +++ pyledger.cc | 46 + pyledger.h | 47 ++ pyutils.h | 112 +++ setup.py | 18 +- system.hh | 156 ++++ times.cc | 80 ++ times.h | 123 +++ timing.h | 62 -- tuples.hpp | 281 +++++++ util.h | 62 -- utils.cc | 720 ++++++++++++++++ utils.h | 540 ++++++++++++ valgrind.sh | 9 + value.cc | 2148 +++++++++++++++++++++------------------------- value.h | 1085 ++++++++++++++++-------- version | 5 + 48 files changed, 10135 insertions(+), 7539 deletions(-) create mode 100644 balpair.h create mode 100644 commodity.cc create mode 100644 commodity.h delete mode 100644 datetime.cc delete mode 100644 datetime.h delete mode 100644 debug.cc delete mode 100644 debug.h create mode 100644 flags.h create mode 100644 main.py create mode 100644 pushvar.h create mode 100644 py_amount.cc create mode 100644 py_commodity.cc create mode 100644 py_times.cc create mode 100644 py_utils.cc create mode 100644 pyfstream.h create mode 100644 pyinterp.cc create mode 100644 pyinterp.h create mode 100644 pyledger.cc create mode 100644 pyledger.h create mode 100644 pyutils.h create mode 100644 system.hh create mode 100644 times.cc create mode 100644 times.h delete mode 100644 timing.h create mode 100644 tuples.hpp delete mode 100644 util.h create mode 100644 utils.cc create mode 100644 utils.h create mode 100755 valgrind.sh create mode 100755 version diff --git a/Makefile.am b/Makefile.am index 5de39382..7ced6a31 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,20 +1,31 @@ +BUILT_SOURCES = +CLEANFILES = +EXTRA_DIST = LICENSE doc test contrib scripts setup.py \ + acprep valgrind.sh version + +ESC_srcdir=`echo "$(srcdir)" | sed 's/\//\\\\\//g'` +ESC_builddir=`echo "$(top_builddir)" | sed 's/\//\\\\\//g'` +ESC_distdir=`echo "$(distdir)" | sed 's/\//\\\\\//g'` + +dist-hook: + rm -fr .git + lib_LTLIBRARIES = libamounts.la libledger.la -libamounts_la_CXXFLAGS = +libamounts_la_CPPFLAGS = libamounts_la_SOURCES = \ amount.cc \ balance.cc \ - datetime.cc \ value.cc if HAVE_BOOST_PYTHON -libamounts_la_CXXFLAGS += -DUSE_BOOST_PYTHON=1 +libamounts_la_CPPFLAGS += -DUSE_BOOST_PYTHON=1 endif if DEBUG -libamounts_la_CXXFLAGS += -DDEBUG_LEVEL=4 -libamounts_la_SOURCES += debug.cc +libamounts_la_CPPFLAGS += -DDEBUG_MODE +libamounts_la_SOURCES += utils.cc endif -libledger_la_CXXFLAGS = +libledger_la_CPPFLAGS = libledger_la_SOURCES = \ binary.cc \ config.cc \ @@ -36,19 +47,19 @@ libledger_la_SOURCES = \ walk.cc \ xml.cc if HAVE_EXPAT -libledger_la_CXXFLAGS += -DHAVE_EXPAT=1 +libledger_la_CPPFLAGS += -DHAVE_EXPAT=1 libledger_la_SOURCES += gnucash.cc endif if HAVE_XMLPARSE -libledger_la_CXXFLAGS += -DHAVE_XMLPARSE=1 +libledger_la_CPPFLAGS += -DHAVE_XMLPARSE=1 libledger_la_SOURCES += gnucash.cc endif if HAVE_LIBOFX -libledger_la_CXXFLAGS += -DHAVE_LIBOFX=1 +libledger_la_CPPFLAGS += -DHAVE_LIBOFX=1 libledger_la_SOURCES += ofx.cc endif if DEBUG -libledger_la_CXXFLAGS += -DDEBUG_LEVEL=4 +libledger_la_CPPFLAGS += -DDEBUG_MODE endif libledger_la_LDFLAGS = -release 2.6.0.90 @@ -85,92 +96,199 @@ pkginclude_HEADERS = \ walk.h \ xml.h +if USE_PCH +nodist_libledger_la_SOURCES = system.hh.gch + +BUILT_SOURCES += system.hh.gch +CLEANFILES += system.hh.gch system.hh + +$(top_builddir)/system.hh.gch: $(srcdir)/src/utility/system.hh acconf.h + echo "#include \"src/utility/system.hh\"" > $(top_builddir)/system.hh + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(libledger_la_CPPFLAGS) \ + -o $@ $(srcdir)/src/utility/system.hh +endif + ###################################################################### bin_PROGRAMS = ledger -ledger_CXXFLAGS = +ledger_CPPFLAGS = ledger_SOURCES = main.cc ledger_LDADD = $(LIBOBJS) libamounts.la libledger.la if HAVE_EXPAT -ledger_CXXFLAGS += -DHAVE_EXPAT=1 +ledger_CPPFLAGS += -DHAVE_EXPAT=1 endif if HAVE_XMLPARSE -ledger_CXXFLAGS += -DHAVE_XMLPARSE=1 +ledger_CPPFLAGS += -DHAVE_XMLPARSE=1 endif if HAVE_LIBOFX -ledger_CXXFLAGS += -DHAVE_LIBOFX=1 +ledger_CPPFLAGS += -DHAVE_LIBOFX=1 endif if DEBUG -ledger_CXXFLAGS += -DDEBUG_LEVEL=4 +ledger_CPPFLAGS += -DDEBUG_MODE endif ledger_LDFLAGS = -static # for the sake of command-line speed -info_TEXINFOS = ledger.texi +info_TEXINFOS = doc/ledger.texi ###################################################################### -lisp_LISP = ledger.el timeclock.el dist_lisp_LISP = ledger.el timeclock.el +DISTCLEANFILES = ledger.elc timeclock.elc + ###################################################################### if HAVE_BOOST_PYTHON noinst_PROGRAMS = amounts.so +CLEANFILES += amounts.so + +clean-local: + rm -fr build + +PYLIBS = amounts gmp + amounts.so: amounts.cc libamounts.la - CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L. -L.libs" \ - python setup.py build --build-lib=. + CFLAGS="$(CPPFLAGS) -I$(srcdir) $(libamounts_la_CPPFLAGS)" \ + LDFLAGS="$(LDFLAGS) -L. -L.libs" \ + PYLIBS="$(PYLIBS)" SRCDIR="$(srcdir)" \ + python $(srcdir)/setup.py build --build-lib=. install-exec-hook: - CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L. -L.libs" \ - python setup.py install --prefix=$(prefix) + CFLAGS="$(CPPFLAGS) -I$(srcdir) $(libamounts_la_CPPFLAGS)" \ + LDFLAGS="$(LDFLAGS) -L. -L.libs" \ + PYLIBS="$(PYLIBS)" SRCDIR="$(srcdir)" \ + python $(srcdir)/setup.py install --prefix=$(prefix) endif ###################################################################### -TESTS = alltests +TESTS = UnitTests +if HAVE_BOOST_PYTHON +TESTS += PyUnitTests +endif -CXXTEST_DIR = /usr/local/cxxtest -TESTGEN = $(CXXTEST_DIR)/cxxtestgen.py -TESTSUITES = tests/*.h +check_PROGRAMS = $(TESTS) -AM_CXXFLAGS = -if HAVE_EXPAT -AM_CXXFLAGS += -DHAVE_EXPAT=1 -endif -if HAVE_XMLPARSE -AM_CXXFLAGS += -DHAVE_XMLPARSE=1 -endif -if HAVE_LIBOFX -AM_CXXFLAGS += -DHAVE_LIBOFX=1 -endif -if DEBUG -AM_CXXFLAGS += -DDEBUG_LEVEL=4 -endif +nodist_UnitTests_SOURCES = tests/UnitTests.cc \ + \ + tests/utility/t_utils.cc \ + tests/utility/t_times.cc \ + tests/numerics/t_commodity.cc \ + tests/numerics/t_amount.cc \ + tests/numerics/t_balance.cc + +UnitTests_CPPFLAGS = -I$(srcdir)/tests $(libledger_la_CPPFLAGS) +UnitTests_LDFLAGS = $(LIBADD_DL) +UnitTests_LDADD = $(lib_LTLIBRARIES) gdtoa/libgdtoa.la -lcppunit + +nodist_PyUnitTests_SOURCES = tests/python/PyUnitTests.py + +# jww (2007-05-10): This rule will not be triggered on systems that +# define an EXEEXT. +PyUnitTests: $(srcdir)/tests/python/PyUnitTests.py + cat $(srcdir)/tests/python/PyUnitTests.py \ + | sed "s/%srcdir%/$(ESC_srcdir)/g" \ + | sed "s/%builddir%/$(ESC_builddir)/g" > $@ + chmod 755 $@ -alltests.cc: $(TESTSUITES) - test -f $(TESTGEN) && python $(TESTGEN) -o $@ --error-printer $(TESTSUITES) +fullcheck: check + MallocGuardEdges=1 \ + MallocScribble=1 \ + MallocPreScribble=1 \ + MallocCheckHeapStart=100 \ + MallocCheckHeapEach=100 \ + DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib \ + $(srcdir)/valgrind.sh $(top_builddir)/UnitTests$(EXEEXT) --verify -alltests: alltests.cc ledger - $(CXXCOMPILE) -I$(CXXTEST_DIR) -lexpat -lgmp -lpcre -o $@ \ - alltests.cc -L. -L.libs -lamounts -lledger +############################################################################### -runtests: alltests - LD_LIBRARY_PATH=.libs ./alltests && tests/regress && tests/regtest +DISTCLEANFILES += Doxyfile.gen -verify: runtests - python tests/runtests.py +alldocs: docs/ledger.info docs/ledger.pdf doxygen-docs + +$(top_builddir)/Doxyfile.gen: $(srcdir)/docs/Doxyfile + cat $(srcdir)/docs/Doxyfile \ + | sed "s/%srcdir%/$(ESC_srcdir)/g" \ + | sed "s/%builddir%/$(ESC_builddir)/g" > $@ + +doxygen-docs: $(top_builddir)/Doxyfile.gen + doxygen $(top_builddir)/Doxyfile.gen ###################################################################### -all-clean: maintainer-clean - rm -fr *~ .*~ .\#* *.html *.info *.pdf *.a *.so *.o *.lo *.la \ - *.elc *.aux *.cp *.fn *.ky *.log *.pg *.toc *.tp *.vr \ - .gdb_history gmon.out h out TAGS ledger valexpr .deps \ - .libs build AUTHORS COPYING INSTALL Makefile acconf.h \ - acconf.h.in aclocal.m4 autom4te config.guess config.sub \ - configure depcomp install-sh libtool ltconfig ltmain.sh \ - missing stamp texinfo.tex Makefile.in mkinstalldirs \ - elisp-comp elc-stamp py-compile +clean-backupfiles: + rm -fr *~ \ + .*~ \ + .\#* + +clean-documentation: + (cd doc; \ + rm -fr *.aux \ + *.cp \ + *.fn \ + *.info \ + *.ky \ + *.log \ + *.pdf \ + *.pg \ + *.toc \ + *.tp \ + *.vr) + +clean-buildproducts: + rm -fr *.Plo \ + *.Po \ + *.a \ + *.elc \ + *.gcno \ + *.gdca \ + *.la \ + *.lo \ + *.o \ + *.so \ + .deps \ + .libs \ + build + +clean-debugdata: + rm -fr .gdb_history \ + TAGS \ + gmon.out \ + h \ + out + +clean-autoconf: + rm -fr Makefile \ + Makefile.in \ + acconf.h \ + acconf.h.in \ + aclocal.m4 \ + autom4te.cache \ + compile \ + config.guess \ + config.sub \ + configure \ + depcomp \ + elc-stamp \ + elc-temp \ + elisp-comp \ + install-sh \ + libtool \ + ltconfig \ + ltmain.sh \ + missing \ + mkinstalldirs \ + py-compile \ + stamp \ + texinfo.tex \ + ylwrap + +all-clean: maintainer-clean \ + clean-buildproducts \ + clean-backupfiles \ + clean-debugdata \ + clean-documentation \ + clean-autoconf diff --git a/acprep b/acprep index 252a9aab..ccd6541e 100755 --- a/acprep +++ b/acprep @@ -1,5 +1,14 @@ #!/bin/sh +# acprep, version 3.0 +# +# This script configures my ledger source tree on my Mac OS/X machine. +# This is not necessary, however, since I keep all the files necessary +# for building checked in to the source tree. Users can just type +# './configure && make'. This script simply sets up the compiler and +# linker flags for all the various build permutations I use for testing +# and profiling. + export AUTOCONF_VERSION=2.61 export AUTOMAKE_VERSION=1.9 @@ -11,23 +20,15 @@ if [ -x "$cmd" ]; then fi autoreconf --force --install -HERE="$PWD" +INCDIRS="-I/sw/include -I/opt/local/include" +INCDIRS="$INCDIRS -I/usr/local/include" +INCDIRS="$INCDIRS -I/usr/local/include/boost-1_35" -if [ ! "$1" = "--local" ]; then - if [ -d "$HOME/Products" ]; then - projdir="$HOME/Products/$(basename $HERE)" - if [ ! -d "$projdir" ]; then - mkdir -p "$projdir" - fi - cd "$projdir" || (echo "Cannot change to $projdir"; exit 1) - fi -else - shift 1 -fi +LIBDIRS="-L/sw/lib -L/opt/local/lib" +LIBDIRS="$LIBDIRS -L/usr/local/lib" + +PYTHON_HOME="/Library/Frameworks/Python.framework/Versions/2.5" -INCDIRS="-I/opt/local/include -I/usr/local/include -I/usr/include/httpd/xml" -INCDIRS="$INCDIRS -I/usr/include/python2.5" -LIBDIRS="-L/opt/local/lib -L/usr/local/lib" SYSTEM=`uname -s` @@ -43,33 +44,93 @@ else CXXFLAGS="" fi -if [ "$1" = "--debug" ]; then - shift 1 - $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ - CXXFLAGS="$CXXFLAGS -g" --enable-debug --enable-python "$@" -elif [ "$1" = "--opt" ]; then - shift 1 - $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ - CXXFLAGS="$CXXFLAGS -fomit-frame-pointer -O3 -mcpu=7450 -fPIC" "$@" -elif [ "$1" = "--flat-opt" ]; then - shift 1 - $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ - CXXFLAGS="$CXXFLAGS -fomit-frame-pointer -O3 -mcpu=7450" "$@" -elif [ "$1" = "--safe-opt" ]; then - shift 1 - $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ - CXXFLAGS="$CXXFLAGS -fomit-frame-pointer -O3 -mcpu=7450 -fPIC -DDEBUG_LEVEL=1" "$@" -elif [ "$1" = "--perf" ]; then - shift 1 - $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ - CXXFLAGS="$CXXFLAGS -g -pg" "$@" -elif [ "$1" = "--python" ]; then - shift 1 - $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ - CXXFLAGS="$CXXFLAGS -g" --enable-python "$@" -else - $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ - CXXFLAGS="$CXXFLAGS -g" "$@" +# Building the command-line tool as a shared library is a luxury, +# since there are no clients except a GUI tool which might use it (and +# that is built again anyway by Xcode). +SWITCHES="" +CPPFLAGS="$INCDIRS" +CXXFLAGS="-pipe" +LDFLAGS="$LIBDIRS" +LOCAL=false + +# Warning flags +CXXFLAGS="$CXXFLAGS -Wall -ansi" +#CXXFLAGS="$CXXFLAGS -Wextra" +#CXXFLAGS="$CXXFLAGS -Weffc++" +#CXXFLAGS="$CXXFLAGS -Wcast-align" +#CXXFLAGS="$CXXFLAGS -Wcast-qual" +#CXXFLAGS="$CXXFLAGS -Wconversion" +#CXXFLAGS="$CXXFLAGS -Wfloat-equal" +#CXXFLAGS="$CXXFLAGS -Wmissing-field-initializers" +#CXXFLAGS="$CXXFLAGS -Wno-endif-labels" +#CXXFLAGS="$CXXFLAGS -Wold-style-cast" +#CXXFLAGS="$CXXFLAGS -Woverloaded-virtual" +#CXXFLAGS="$CXXFLAGS -Wshorten-64-to-32" +#CXXFLAGS="$CXXFLAGS -Wsign-compare" +#CXXFLAGS="$CXXFLAGS -Wsign-promo" +#CXXFLAGS="$CXXFLAGS -Wstrict-null-sentinel" +#CXXFLAGS="$CXXFLAGS -Wwrite-strings" + + +while [ -n "$1" ]; do + case "$1" in + --devel) + SWITCHES="$SWITCHES --disable-shared --enable-pch" + ;; + + --debug) + SWITCHES="$SWITCHES --enable-debug" + #CPPFLAGS="$CPPFLAGS -D_GLIBCXX_DEBUG=1" + CXXFLAGS="$CXXFLAGS -g" ;; + + --boost) + shift 1 + SWITCHES="$SWITCHES --with-boost-suffix=$1" + ;; + + --gcov) + CXXFLAGS="$CXXFLAGS -fprofile-arcs -ftest-coverage" ;; + + --gprof) + CXXFLAGS="$CXXFLAGS -g -pg" ;; + + --python) + if [ -d "$PYTHON_HOME" ]; then + SWITCHES="$SWITCHES --enable-python" + CPPFLAGS="$CPPFLAGS -I$PYTHON_HOME/include/python2.5" + LDFLAGS="$LDFLAGS -L$PYTHON_HOME/lib/python2.5/config" + fi ;; + + --pic) + CXXFLAGS="$CXXFLAGS -fPIC" ;; + + --opt) + CXXFLAGS="$CXXFLAGS -fomit-frame-pointer -O3" ;; + + --local) + LOCAL=true ;; + + *) + break ;; + esac + shift 1 +done + + +HERE="$PWD" + +if [ "$LOCAL" = "false" -a -d "$HOME/Products" ]; then + version="" + if [ -x version ]; then + version="-$(./version)" + fi + projdir="$HOME/Products/$(basename $HERE)$version" + if [ ! -d "$projdir" ]; then + mkdir -p "$projdir" + fi + cd "$projdir" || (echo "Cannot change to $projdir"; exit 1) fi -rm -f AUTHORS COPYING +"$HERE/configure" --srcdir="$HERE" \ + CPPFLAGS="$CPPFLAGS" CXXFLAGS="$CXXFLAGS $local_cxxflags" \ + LDFLAGS="$LDFLAGS" LIBS="$LIBS" $SWITCHES "$@" diff --git a/amount.cc b/amount.cc index 4f07b0b0..2424f0ac 100644 --- a/amount.cc +++ b/amount.cc @@ -1,287 +1,313 @@ -#include "amount.h" -#include "util.h" - -#include -#include -#include +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file 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 +#include "amount.h" +#include "parser.h" namespace ledger { -bool do_cleanup = true; +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::keep_base = 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 -#define BIGINT_BULK_ALLOC 0x0001 -#define BIGINT_KEEP_PREC 0x0002 +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; -class amount_t::bigint_t { - public: - mpz_t val; - unsigned char prec; - unsigned char flags; - unsigned int ref; - unsigned int index; +#define MPZ(bigint) ((bigint)->val) - bigint_t() : prec(0), flags(0), ref(1), index(0) { + bigint_t() : prec(0), ref(1), index(0) { + TRACE_CTOR(bigint_t, ""); mpz_init(val); } - bigint_t(mpz_t _val) : prec(0), flags(0), ref(1), index(0) { + 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) - : prec(other.prec), flags(other.flags & BIGINT_KEEP_PREC), - ref(1), index(0) { + : supports_flags<>(other.flags() & BIGINT_KEEP_PREC), + prec(other.prec), ref(1), index(0) { + TRACE_CTOR(bigint_t, "copy"); mpz_init_set(val, other.val); } - ~bigint_t(); + ~bigint_t() { + TRACE_DTOR(bigint_t); + assert(ref == 0); + mpz_clear(val); + } }; -unsigned int sizeof_bigint_t() { - return sizeof(amount_t::bigint_t); -} - -#define MPZ(x) ((x)->val) - -static mpz_t temp; // these are the global temp variables -static mpz_t divisor; - -static amount_t::bigint_t true_value; - -inline amount_t::bigint_t::~bigint_t() { - assert(ref == 0 || (! do_cleanup && this == &true_value)); - mpz_clear(val); -} - -base_commodities_map commodity_base_t::commodities; - -commodity_base_t::updater_t * commodity_base_t::updater = NULL; - -commodities_map commodity_t::commodities; -bool commodity_t::commodities_sorted = false; -commodity_t * commodity_t::null_commodity; -commodity_t * commodity_t::default_commodity = NULL; - -static struct _init_amounts { - _init_amounts() { - mpz_init(temp); - mpz_init(divisor); - - mpz_set_ui(true_value.val, 1); - - commodity_base_t::updater = NULL; - commodity_t::null_commodity = commodity_t::create(""); - commodity_t::default_commodity = NULL; - - commodity_t::null_commodity->add_flags(COMMODITY_STYLE_NOMARKET | - COMMODITY_STYLE_BUILTIN); +void amount_t::initialize() +{ + mpz_init(temp); + mpz_init(divisor); - // Add time commodity conversions, so that timelog's may be parsed - // in terms of seconds, but reported as minutes or hours. - commodity_t * commodity; + // jww (2007-05-02): Be very careful here! + if (! current_pool) + current_pool = new commodity_pool_t; - commodity = commodity_t::create("s"); + // 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"); - -#if 0 - commodity = commodity_t::create("b"); - commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); - - parse_conversion("1.00 Kb", "1024 b"); - parse_conversion("1.00 Mb", "1024 Kb"); - parse_conversion("1.00 Gb", "1024 Mb"); - parse_conversion("1.00 Tb", "1024 Gb"); -#endif + } else { + assert(false); } +} - ~_init_amounts() { - if (! do_cleanup) - return; - - mpz_clear(temp); - mpz_clear(divisor); - - if (commodity_base_t::updater) { - delete commodity_base_t::updater; - commodity_base_t::updater = NULL; - } - - for (commodities_map::iterator i = commodity_t::commodities.begin(); - i != commodity_t::commodities.end(); - i++) - delete (*i).second; - - commodity_t::commodities.clear(); +void amount_t::shutdown() +{ + mpz_clear(temp); + mpz_clear(divisor); - true_value.ref--; + // jww (2007-05-02): Be very careful here! + if (current_pool) { + checked_delete(current_pool); + current_pool = NULL; } -} _init_obj; +} -static void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec) +void amount_t::_copy(const amount_t& amt) { - // 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); + if (quantity != amt.quantity) { + if (quantity) + _release(); - 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); + // 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 { - mpz_sub(out, value, remainder); + quantity = amt.quantity; + DEBUG("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; } } - 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); + commodity_ = amt.commodity_; } -amount_t::amount_t(const bool value) +void amount_t::_dup() { - if (value) { - quantity = &true_value; - quantity->ref++; - } else { - quantity = NULL; + if (quantity->ref > 1) { + bigint_t * q = new bigint_t(*quantity); + _release(); + quantity = q; } - commodity_ = NULL; } -amount_t::amount_t(const long value) +void amount_t::_resize(precision_t prec) { - if (value != 0) { - quantity = new bigint_t; - mpz_set_si(MPZ(quantity), value); - } else { - quantity = NULL; - } - commodity_ = NULL; -} + assert(prec < 256); -amount_t::amount_t(const unsigned long value) -{ - if (value != 0) { - quantity = new bigint_t; - mpz_set_ui(MPZ(quantity), value); - } else { - quantity = NULL; - } - commodity_ = NULL; + 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; } -amount_t::amount_t(const double value) +void amount_t::_clear() { - if (value != 0.0) { - quantity = new bigint_t; - mpz_set_d(MPZ(quantity), value); + if (quantity) { + _release(); + quantity = NULL; + commodity_ = NULL; } else { - quantity = NULL; + assert(! commodity_); } - commodity_ = NULL; } void amount_t::_release() { - DEBUG_PRINT("amounts.refs", - quantity << " ref--, now " << (quantity->ref - 1)); + DEBUG("amounts.refs", quantity << " ref--, now " << (quantity->ref - 1)); + if (--quantity->ref == 0) { - if (! (quantity->flags & BIGINT_BULK_ALLOC)) - delete quantity; - else + if (quantity->has_flags(BIGINT_BULK_ALLOC)) quantity->~bigint_t(); + else + checked_delete(quantity); } } -void amount_t::_init() -{ - if (! quantity) { - quantity = new bigint_t; - } - else if (quantity->ref > 1) { - _release(); - quantity = new bigint_t; - } -} -void amount_t::_dup() -{ - if (quantity->ref > 1) { - bigint_t * q = new bigint_t(*quantity); - _release(); - quantity = q; - } -} +namespace { + amount_t::precision_t convert_double(mpz_t dest, double val) + { +#ifndef HAVE_GDTOA + // This code is far too imprecise to be worthwhile. -void amount_t::_copy(const amount_t& amt) -{ - if (quantity != amt.quantity) { - if (quantity) - _release(); + mpf_t temp; + mpf_init_set_d(temp, val); - // Never maintain a pointer into a bulk allocation pool; such - // pointers are not guaranteed to remain. - if (amt.quantity->flags & BIGINT_BULK_ALLOC) { - quantity = new bigint_t(*amt.quantity); + mp_exp_t exp; + char * buf = mpf_get_str(NULL, &exp, 10, 1000, temp); + + int len = std::strlen(buf); + if (len > 0 && buf[0] == '-') + exp++; + + if (exp <= len) { + exp = len - exp; } else { - quantity = amt.quantity; - DEBUG_PRINT("amounts.refs", - quantity << " ref++, now " << (quantity->ref + 1)); - quantity->ref++; + // There were trailing zeros, which we have to put back on in + // order to convert this buffer into an integer. + + int zeroes = exp - len; + + char * newbuf = (char *)std::malloc(len + zeroes); + std::strcpy(newbuf, buf); + + int i; + for (i = 0; i < zeroes; i++) + newbuf[len + i] = '0'; + newbuf[len + i] = '\0'; + + free(buf); + buf = newbuf; + + exp = (len - exp) + zeroes; + } + + mpz_set_str(dest, buf, 10); + free(buf); + + return amount_t::precision_t(exp); +#else + int decpt, sign; + char * buf = dtoa(val, 0, 0, &decpt, &sign, NULL); + char * result; + int len = std::strlen(buf); + + if (decpt <= len) { + decpt = len - decpt; + result = NULL; + } else { + // There were trailing zeros, which we have to put back on in + // order to convert this buffer into an integer. + + int zeroes = decpt - len; + result = new char[len + zeroes + 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; +#endif } - commodity_ = amt.commodity_; } -amount_t& amount_t::operator=(const std::string& value) +amount_t::amount_t(const double val) : commodity_(NULL) { - std::istringstream str(value); - parse(str); - return *this; + TRACE_CTOR(amount_t, "const double"); + quantity = new bigint_t; + quantity->prec = convert_double(MPZ(quantity), val); } -amount_t& amount_t::operator=(const char * value) +amount_t::amount_t(const unsigned long val) : commodity_(NULL) { - std::string valstr(value); - std::istringstream str(valstr); - parse(str); - return *this; + 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); } -// assignment operator + amount_t& amount_t::operator=(const amount_t& amt) { if (this != &amt) { @@ -293,99 +319,59 @@ amount_t& amount_t::operator=(const amount_t& amt) return *this; } -amount_t& amount_t::operator=(const bool value) -{ - if (! value) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - if (quantity) - _release(); - quantity = &true_value; - quantity->ref++; - } - return *this; -} -amount_t& amount_t::operator=(const long value) +int amount_t::compare(const amount_t& amt) const { - if (value == 0) { + if (! quantity || ! amt.quantity) { if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - mpz_set_si(MPZ(quantity), value); + 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"); } - return *this; -} -amount_t& amount_t::operator=(const unsigned long value) -{ - if (value == 0) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - mpz_set_ui(MPZ(quantity), value); - } - return *this; -} + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + "Cannot compare amounts with different commodities: " << + commodity().symbol() << " and " << amt.commodity().symbol()); -amount_t& amount_t::operator=(const double value) -{ - if (value == 0.0) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - mpz_set_d(MPZ(quantity), value); + if (quantity->prec == amt.quantity->prec) { + return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)); } - return *this; -} - - -void amount_t::_resize(unsigned int prec) -{ - assert(prec < 256); - - if (! quantity || prec == quantity->prec) - return; - - _dup(); - - if (prec < quantity->prec) { - mpz_ui_pow_ui(divisor, 10, quantity->prec - prec); - mpz_tdiv_q(MPZ(quantity), MPZ(quantity), divisor); - } else { - mpz_ui_pow_ui(divisor, 10, prec - quantity->prec); - mpz_mul(MPZ(quantity), MPZ(quantity), divisor); + 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)); } - - quantity->prec = prec; } amount_t& amount_t::operator+=(const amount_t& amt) { - if (! amt.quantity) - return *this; - - if (! quantity) { - _copy(amt); - return *this; + 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"); } - _dup(); - if (commodity() != amt.commodity()) - throw new amount_error - (std::string("Adding amounts with different commodities: ") + - commodity_->qualified_symbol + " != " + - amt.commodity_->qualified_symbol); + 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)); @@ -395,9 +381,9 @@ amount_t& amount_t::operator+=(const amount_t& amt) mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); } else { - amount_t temp = amt; - temp._resize(quantity->prec); - mpz_add(MPZ(quantity), MPZ(quantity), MPZ(temp.quantity)); + amount_t t = amt; + t._resize(quantity->prec); + mpz_add(MPZ(quantity), MPZ(quantity), MPZ(t.quantity)); } return *this; @@ -405,23 +391,23 @@ amount_t& amount_t::operator+=(const amount_t& amt) amount_t& amount_t::operator-=(const amount_t& amt) { - if (! amt.quantity) - return *this; - - if (! quantity) { - quantity = new bigint_t(*amt.quantity); - commodity_ = amt.commodity_; - mpz_neg(MPZ(quantity), MPZ(quantity)); - return *this; + 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"); } - _dup(); - if (commodity() != amt.commodity()) - throw new amount_error - (std::string("Subtracting amounts with different commodities: ") + - commodity_->qualified_symbol + " != " + - amt.commodity_->qualified_symbol); + 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)); @@ -431,30 +417,95 @@ amount_t& amount_t::operator-=(const amount_t& amt) mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); } else { - amount_t temp = amt; - temp._resize(quantity->prec); - mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(temp.quantity)); + 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) { - if (! amt.quantity) - return (*this = amt); - else if (! quantity) - return *this; + 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 (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")); _dup(); mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); quantity->prec += amt.quantity->prec; - unsigned int comm_prec = commodity().precision(); - if (quantity->prec > comm_prec + 6U) { - mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); - quantity->prec = comm_prec + 6U; + 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; @@ -462,121 +513,194 @@ amount_t& amount_t::operator*=(const amount_t& amt) amount_t& amount_t::operator/=(const amount_t& amt) { - if (! amt.quantity || ! amt) - throw new amount_error("Divide by zero"); - else if (! quantity) - return *this; + 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 (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")); + + if (! amt) + throw_(amount_error, "Divide by zero"); _dup(); // Increase the value's precision, to capture fractional parts after - // the divide. - mpz_ui_pow_ui(divisor, 10, amt.quantity->prec + 6U); + // 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 += 6U; + 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. - unsigned int comm_prec = commodity().precision(); - if (quantity->prec > comm_prec + 6U) { - mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); - quantity->prec = comm_prec + 6U; + 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; } -// unary negation -void amount_t::negate() + +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; } -int amount_t::sign() const +amount_t amount_t::round() const { - return quantity ? mpz_sgn(MPZ(quantity)) : 0; + if (! quantity) + throw_(amount_error, "Cannot round an uninitialized amount"); + + if (! has_commodity()) + return *this; + + return round(commodity().precision()); } -int amount_t::compare(const amount_t& amt) const +amount_t amount_t::round(precision_t prec) const { - if (! quantity) { - if (! amt.quantity) - return 0; - return - amt.sign(); - } - if (! amt.quantity) - return sign(); + if (! quantity) + throw_(amount_error, "Cannot round an uninitialized amount"); - if (commodity() && amt.commodity() && commodity() != amt.commodity()) - throw new amount_error - (std::string("Cannot compare amounts with different commodities: ") + - commodity().symbol() + " and " + amt.commodity().symbol()); + amount_t t(*this); - if (quantity->prec == amt.quantity->prec) { - return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)); - } - else if (quantity->prec < amt.quantity->prec) { - amount_t temp = *this; - temp._resize(amt.quantity->prec); - return mpz_cmp(MPZ(temp.quantity), MPZ(amt.quantity)); - } - else { - amount_t temp = amt; - temp._resize(quantity->prec); - return mpz_cmp(MPZ(quantity), MPZ(temp.quantity)); + 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; } -bool amount_t::operator==(const amount_t& amt) const +amount_t amount_t::unround() const { - if (commodity() != amt.commodity()) - return false; - return compare(amt) == 0; + 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; } -bool amount_t::operator!=(const amount_t& amt) const +amount_t& amount_t::in_place_reduce() { - if (commodity() != amt.commodity()) - return true; - return compare(amt) != 0; + 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::operator bool() const +amount_t& amount_t::in_place_unreduce() { if (! quantity) - return false; + throw_(amount_error, "Cannot unreduce an uninitialized amount"); + + while (commodity_ && commodity().larger()) { + *this /= commodity().larger()->number(); + commodity_ = commodity().larger()->commodity_; + if (abs() < amount_t(1.0)) + break; + } + return *this; +} - if (quantity->prec <= commodity().precision()) { - return mpz_sgn(MPZ(quantity)) != 0; +optional amount_t::value(const optional& moment) const +{ + if (quantity) { + optional amt(commodity().value(moment)); + if (amt) + return (*amt * number()).round(); } else { - mpz_set(temp, MPZ(quantity)); - if (quantity->flags & BIGINT_KEEP_PREC) - mpz_ui_pow_ui(divisor, 10, quantity->prec); - else - mpz_ui_pow_ui(divisor, 10, quantity->prec - commodity().precision()); - mpz_tdiv_q(temp, temp, divisor); - bool zero = mpz_sgn(temp) == 0; - return ! zero; + throw_(amount_error, "Cannot determine value of an uninitialized amount"); } + return none; } -amount_t::operator long() const + +int amount_t::sign() const { if (! quantity) - return 0; + throw_(amount_error, "Cannot determine sign of an uninitialized amount"); - mpz_set(temp, MPZ(quantity)); - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_q(temp, temp, divisor); - return mpz_get_si(temp); + return mpz_sgn(MPZ(quantity)); +} + +bool amount_t::is_zero() const +{ + if (! quantity) + throw_(amount_error, "Cannot determine sign if an uninitialized amount is zero"); + + if (has_commodity()) { + if (quantity->prec <= commodity().precision()) + return is_realzero(); + else + return round(commodity().precision()).sign() == 0; + } + return is_realzero(); } -amount_t::operator double() const + +double amount_t::to_double(bool no_check) const { if (! quantity) - return 0.0; + throw_(amount_error, "Cannot convert an uninitialized amount to a double"); mpz_t remainder; mpz_init(remainder); @@ -596,259 +720,408 @@ amount_t::operator double() const mpz_clear(remainder); - return std::atof(num.str().c_str()); + double value = lexical_cast(num.str()); + + if (! no_check && *this != value) + throw_(amount_error, "Conversion of amount to_double loses precision"); + + return value; } -bool amount_t::realzero() const +long amount_t::to_long(bool no_check) const { if (! quantity) - return true; - return mpz_sgn(MPZ(quantity)) == 0; + 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; } -amount_t amount_t::value(const datetime_t& moment) const +bool amount_t::fits_in_double() const { - if (quantity) { - amount_t amt(commodity().value(moment)); - if (! amt.realzero()) - return (amt * *this).round(); - } - return *this; + double value = to_double(true); + return *this == amount_t(value); } -amount_t amount_t::round(unsigned int prec) const +bool amount_t::fits_in_long() const { - amount_t temp = *this; + long value = to_long(true); + return *this == amount_t(value); +} - if (! quantity || quantity->prec <= prec) { - if (quantity && quantity->flags & BIGINT_KEEP_PREC) { - temp._dup(); - temp.quantity->flags &= ~BIGINT_KEEP_PREC; - } - return temp; + +void amount_t::annotate_commodity(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); - temp._dup(); + 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 - mpz_round(MPZ(temp.quantity), MPZ(temp.quantity), temp.quantity->prec, prec); + DEBUG("amounts.commodities", " Annotated amount is " << *this); +} - temp.quantity->prec = prec; - temp.quantity->flags &= ~BIGINT_KEEP_PREC; +bool amount_t::commodity_annotated() const +{ + if (! quantity) + throw_(amount_error, + "Cannot determine if an uninitialized amount's commodity is annotated"); - return temp; + assert(! commodity().annotated || as_annotated_commodity(commodity()).details); + return commodity().annotated; } -amount_t amount_t::unround() const +annotation_t amount_t::annotation_details() const { - if (! quantity) { - amount_t temp(0L); - assert(temp.quantity); - temp.quantity->flags |= BIGINT_KEEP_PREC; - return temp; - } - else if (quantity->flags & BIGINT_KEEP_PREC) { - return *this; - } + if (! quantity) + throw_(amount_error, + "Cannot return commodity annotation details of an uninitialized amount"); - amount_t temp = *this; - temp._dup(); - temp.quantity->flags |= BIGINT_KEEP_PREC; + assert(! commodity().annotated || as_annotated_commodity(commodity()).details); - return temp; + if (commodity().annotated) { + annotated_commodity_t& ann_comm(as_annotated_commodity(commodity())); + return ann_comm.details; + } + return annotation_t(); } -std::string amount_t::quantity_string() const +amount_t amount_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) const { if (! quantity) - return "0"; + throw_(amount_error, + "Cannot strip commodity annotations from an uninitialized amount"); - std::ostringstream out; - - mpz_t quotient; - mpz_t rquotient; - mpz_t remainder; + if (! commodity().annotated || + (_keep_price && _keep_date && _keep_tag)) + return *this; - mpz_init(quotient); - mpz_init(rquotient); - mpz_init(remainder); + amount_t t(*this); + t.set_commodity(as_annotated_commodity(commodity()). + strip_annotations(_keep_price, _keep_date, _keep_tag)); + return t; +} - 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! +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 == ','); - commodity_t& comm(commodity()); - unsigned char precision; + int len = std::strlen(buf); + while (len > 0 && ! std::isdigit(buf[len - 1])) { + buf[--len] = '\0'; + in.unget(); + } - if (! comm || quantity->flags & BIGINT_KEEP_PREC) { - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); - precision = quantity->prec; - } - else if (comm.precision() < quantity->prec) { - mpz_round(rquotient, MPZ(quantity), quantity->prec, comm.precision()); - mpz_ui_pow_ui(divisor, 10, comm.precision()); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision(); - } - else if (comm.precision() > quantity->prec) { - mpz_ui_pow_ui(divisor, 10, comm.precision() - quantity->prec); - mpz_mul(rquotient, MPZ(quantity), divisor); - mpz_ui_pow_ui(divisor, 10, comm.precision()); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision(); - } - else if (quantity->prec) { - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); - precision = quantity->prec; - } - else { - mpz_set(quotient, MPZ(quantity)); - mpz_set_ui(remainder, 0); - precision = 0; + value = buf; } +} - if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { - negative = true; +void amount_t::parse(std::istream& in, flags_t flags) +{ + // The possible syntax for an amount is: + // + // [-]NUM[ ]SYM [@ AMOUNT] + // SYM[ ][-]NUM [@ AMOUNT] - mpz_abs(quotient, quotient); - mpz_abs(remainder, remainder); + 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); } - mpz_set(rquotient, remainder); - if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) - return "0"; + 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; - if (negative) - out << "-"; + commodity_t::parse_symbol(in, symbol); - if (mpz_sgn(quotient) == 0) { - out << '0'; + if (! symbol.empty()) + comm_flags |= COMMODITY_STYLE_SUFFIXED; + + if (! in.eof() && ((n = in.peek()) != '\n')) + details.parse(in); + } } else { - char * p = mpz_get_str(NULL, 10, quotient); - out << p; - std::free(p); - } + commodity_t::parse_symbol(in, symbol); - if (precision) { - out << '.'; + if (! in.eof() && ((n = in.peek()) != '\n')) { + if (std::isspace(in.peek())) + comm_flags |= COMMODITY_STYLE_SEPARATED; - out.width(precision); - out.fill('0'); + parse_quantity(in, quant); - char * p = mpz_get_str(NULL, 10, rquotient); - out << p; - std::free(p); + if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n')) + details.parse(in); + } } - mpz_clear(quotient); - mpz_clear(rquotient); - mpz_clear(remainder); + if (quant.empty()) + throw_(amount_error, "No quantity specified for amount"); - return out.str(); -} + // 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::ostream& operator<<(std::ostream& _out, const amount_t& amt) -{ - if (! amt.quantity) { - _out << "0"; - return _out; - } - - amount_t base(amt); - if (! amount_t::keep_base && amt.commodity().larger()) { - amount_t last(amt); - while (last.commodity().larger()) { - last /= *last.commodity().larger(); - last.commodity_ = last.commodity().larger()->commodity_; - if (ledger::abs(last) < 1) - break; - base = last.round(); - } + std::auto_ptr 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); } - std::ostringstream out; + // Create the commodity if has not already been seen, and update the + // precision if something greater was used for the quantity. - mpz_t quotient; - mpz_t rquotient; - mpz_t remainder; + bool newly_created = false; - mpz_init(quotient); - mpz_init(rquotient); - mpz_init(remainder); + if (symbol.empty()) { + commodity_ = NULL; + } else { + commodity_ = current_pool->find(symbol); + if (! commodity_) { + commodity_ = current_pool->create(symbol); + newly_created = true; + } + assert(commodity_); - bool negative = false; + if (details) + commodity_ = current_pool->find_or_create(*commodity_, details); + } - // Ensure the value is rounded to the commodity's precision before - // outputting it. NOTE: `rquotient' is used here as a temp variable! + // 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('.'); - commodity_t& comm(base.commodity()); - unsigned char precision; - - if (! comm || base.quantity->flags & BIGINT_KEEP_PREC) { - mpz_ui_pow_ui(divisor, 10, base.quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor); - precision = base.quantity->prec; - } - else if (comm.precision() < base.quantity->prec) { - mpz_round(rquotient, MPZ(base.quantity), base.quantity->prec, - comm.precision()); - mpz_ui_pow_ui(divisor, 10, comm.precision()); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision(); - } - else if (comm.precision() > base.quantity->prec) { - mpz_ui_pow_ui(divisor, 10, comm.precision() - base.quantity->prec); - mpz_mul(rquotient, MPZ(base.quantity), divisor); - mpz_ui_pow_ui(divisor, 10, comm.precision()); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision(); - } - else if (base.quantity->prec) { - mpz_ui_pow_ui(divisor, 10, base.quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor); - precision = base.quantity->prec; + 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 { - mpz_set(quotient, MPZ(base.quantity)); - mpz_set_ui(remainder, 0); - precision = 0; + quantity->prec = 0; } - if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { - negative = true; + // Set the commodity's flags and precision accordingly - mpz_abs(quotient, quotient); - mpz_abs(remainder, remainder); - } - mpz_set(rquotient, remainder); + if (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) { + commodity().add_flags(comm_flags); - if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) { - _out << "0"; - return _out; + if (quantity->prec > commodity().precision()) + commodity().set_precision(quantity->prec); } - if (! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) { - comm.write(out); + // Setup the amount's own flags - if (comm.flags() & COMMODITY_STYLE_SEPARATED) - out << " "; + 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) - out << "-"; + in_place_negate(); - if (mpz_sgn(quotient) == 0) { - out << '0'; - } - else if (! (comm.flags() & COMMODITY_STYLE_THOUSANDS)) { - char * p = mpz_get_str(NULL, 10, quotient); - out << p; - std::free(p); - } + if (! (flags & AMOUNT_PARSE_NO_REDUCE)) + in_place_reduce(); + + safe_holder.release(); // `this->quantity' owns the pointer +} + +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 +{ + if (! quantity) + throw_(amount_error, "Cannot write out an uninitialized amount"); + + 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 strs; + std::list strs; char buf[4]; for (int powers = 0; true; powers += 3) { @@ -867,11 +1140,11 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) bool printed = false; - for (std::list::reverse_iterator i = strs.rbegin(); + for (std::list::reverse_iterator i = strs.rbegin(); i != strs.rend(); i++) { if (printed) { - out << (comm.flags() & COMMODITY_STYLE_EUROPEAN ? '.' : ','); + out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? '.' : ','); out.width(3); out.fill('0'); } @@ -881,7 +1154,7 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) } } - if (precision) { + if (quantity && precision) { std::ostringstream final; final.width(precision); final.fill('0'); @@ -889,32 +1162,34 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) final << p; std::free(p); - const std::string& str(final.str()); + 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; - std::string ender; + string ender; if (i == len) ender = str; else if (i < comm.precision()) - ender = std::string(str, 0, comm.precision()); + ender = string(str, 0, comm.precision()); else - ender = std::string(str, 0, i); + ender = string(str, 0, i); if (! ender.empty()) { - out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); + if (omit_commodity) + out << '.'; + else + out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); out << ender; } } - if (comm.flags() & COMMODITY_STYLE_SUFFIXED) { - if (comm.flags() & COMMODITY_STYLE_SEPARATED) + if (! omit_commodity && comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << " "; - - comm.write(out); + comm.print(out); } mpz_clear(quotient); @@ -924,9 +1199,9 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) // If there are any annotations associated with this commodity, // output them now. - if (comm.annotated) { + if (! omit_commodity && comm.annotated) { annotated_commodity_t& ann(static_cast(comm)); - assert(&ann.price != &amt); + assert(&*ann.details.price != this); ann.write_annotations(out); } @@ -935,328 +1210,93 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) // entire amount string, and not just the first part. _out << out.str(); - - return _out; -} - -void parse_quantity(std::istream& in, std::string& value) -{ - char buf[256]; - char c = peek_next_nonws(in); - READ_INTO(in, buf, 255, c, - std::isdigit(c) || c == '-' || c == '.' || c == ','); - - int len = std::strlen(buf); - while (len > 0 && ! std::isdigit(buf[len - 1])) { - buf[--len] = '\0'; - in.unget(); - } - - value = buf; -} - -// Invalid commodity characters: -// SPACE, TAB, NEWLINE, RETURN -// 0-9 . , ; - + * / ^ ? : & | ! = -// < > { } [ ] ( ) @ - -int invalid_chars[256] = { - /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ -/* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, -/* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, -/* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -/* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, -/* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, -/* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -/* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -}; - -void parse_commodity(std::istream& in, std::string& symbol) -{ - char buf[256]; - char c = peek_next_nonws(in); - if (c == '"') { - in.get(c); - READ_INTO(in, buf, 255, c, c != '"'); - if (c == '"') - in.get(c); - else - throw new amount_error("Quoted commodity symbol lacks closing quote"); - } else { - READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]); - } - symbol = buf; } -void parse_annotations(std::istream& in, amount_t& price, - datetime_t& date, std::string& tag) -{ - do { - char buf[256]; - char c = peek_next_nonws(in); - if (c == '{') { - if (price) - throw new 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 new amount_error("Commodity price lacks closing brace"); - - price.parse(buf, AMOUNT_PARSE_NO_MIGRATE); - price.reduce(); - - // Since this price will maintain its own precision, make sure - // it is at least as large as the base commodity, since the user - // may have only specified {$1} or something similar. - if (price.quantity->prec < price.commodity().precision()) - price = price.round(); // no need to retain individual precision - } - else if (c == '[') { - if (date) - throw new 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 new amount_error("Commodity date lacks closing bracket"); - - date = buf; - } - else if (c == '(') { - if (! tag.empty()) - throw new 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 new amount_error("Commodity tag lacks closing parenthesis"); - - tag = buf; - } - else { - break; - } - } while (true); - - DEBUG_PRINT("amounts.commodities", - "Parsed commodity annotations: " - << " price " << price << " " - << " date " << date << " " - << " tag " << tag); +namespace { + char * bigints; + char * bigints_next; + uint_fast32_t bigints_index; + uint_fast32_t bigints_count; + char buf[4096]; } -void amount_t::parse(std::istream& in, unsigned char flags) +void amount_t::read(std::istream& in) { - // The possible syntax for an amount is: - // - // [-]NUM[ ]SYM [@ AMOUNT] - // SYM[ ][-]NUM [@ AMOUNT] - - std::string symbol; - std::string quant; - amount_t price; - datetime_t date; - std::string tag; - unsigned int comm_flags = COMMODITY_STYLE_DEFAULTS; - bool negative = false; - - char c = peek_next_nonws(in); - if (c == '-') { - negative = true; - in.get(c); - c = peek_next_nonws(in); - } + using namespace ledger::binary; - char n; - if (std::isdigit(c)) { - parse_quantity(in, quant); + // Read in the commodity for this amount - if (! in.eof() && ((n = in.peek()) != '\n')) { - if (std::isspace(n)) - comm_flags |= COMMODITY_STYLE_SEPARATED; - - parse_commodity(in, symbol); - - if (! symbol.empty()) - comm_flags |= COMMODITY_STYLE_SUFFIXED; - - if (! in.eof() && ((n = in.peek()) != '\n')) - parse_annotations(in, price, date, tag); - } - } else { - parse_commodity(in, symbol); - - if (! in.eof() && ((n = in.peek()) != '\n')) { - if (std::isspace(in.peek())) - comm_flags |= COMMODITY_STYLE_SEPARATED; - - parse_quantity(in, quant); - - if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n')) - parse_annotations(in, price, date, tag); - } - } - - if (quant.empty()) - throw new amount_error("No quantity specified for amount"); - - _init(); - - // Create the commodity if has not already been seen, and update the - // precision if something greater was used for the quantity. - - bool newly_created = false; - - if (symbol.empty()) { - commodity_ = commodity_t::null_commodity; - } else { - commodity_ = commodity_t::find(symbol); - if (! commodity_) { - commodity_ = commodity_t::create(symbol); - newly_created = true; - } - assert(commodity_); - - if (! price.realzero() || date || ! tag.empty()) - commodity_ = - annotated_commodity_t::find_or_create(*commodity_, price, date, tag); - } - - // Determine the precision of the amount, based on the usage of - // comma or period. - - std::string::size_type last_comma = quant.rfind(','); - std::string::size_type last_period = quant.rfind('.'); - - if (last_comma != std::string::npos && last_period != std::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 != std::string::npos && - (! commodity_t::default_commodity || - commodity_t::default_commodity->flags() & COMMODITY_STYLE_EUROPEAN)) { - comm_flags |= COMMODITY_STYLE_EUROPEAN; - quantity->prec = quant.length() - last_comma - 1; - } - else if (last_period != std::string::npos && - ! (commodity().flags() & COMMODITY_STYLE_EUROPEAN)) { - quantity->prec = quant.length() - last_period - 1; - } + commodity_t::ident_t ident; + read_long(in, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = current_pool->null_commodity; else { - quantity->prec = 0; + commodity_ = current_pool->find(ident); + assert(commodity_); } - // Set the commodity's flags and precision accordingly + // Read in the quantity - if (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE)) { - commodity().add_flags(comm_flags); - if (quantity->prec > commodity().precision()) - commodity().set_precision(quantity->prec); - } + char byte; + in.read(&byte, sizeof(byte)); - if (flags & AMOUNT_PARSE_NO_MIGRATE) - quantity->flags |= BIGINT_KEEP_PREC; + if (byte < 3) { + quantity = new bigint_t; - // Now we have the final number. Remove commas and periods, if - // necessary. + unsigned short len; + in.read((char *)&len, sizeof(len)); + assert(len < 4096); + in.read(buf, len); + mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), + 0, 0, buf); - if (last_comma != std::string::npos || last_period != std::string::npos) { - int len = quant.length(); - char * buf = new char[len + 1]; - const char * p = quant.c_str(); - char * t = buf; + char negative; + in.read(&negative, sizeof(negative)); + if (negative) + mpz_neg(MPZ(quantity), MPZ(quantity)); - while (*p) { - if (*p == ',' || *p == '.') - p++; - *t++ = *p++; - } - *t = '\0'; + in.read((char *)&quantity->prec, sizeof(quantity->prec)); - mpz_set_str(MPZ(quantity), buf, 10); - delete[] buf; - } else { - mpz_set_str(MPZ(quantity), quant.c_str(), 10); + bigint_t::flags_t tflags; + in.read((char *)&tflags, sizeof(tflags)); + quantity->set_flags(tflags); } - - if (negative) - negate(); - - if (! (flags & AMOUNT_PARSE_NO_REDUCE)) - reduce(); -} - -void amount_t::reduce() -{ - while (commodity_ && commodity().smaller()) { - *this *= *commodity().smaller(); - commodity_ = commodity().smaller()->commodity_; + else { + assert(false); } } -void amount_t::parse(const std::string& str, unsigned char flags) +void amount_t::read(const char *& data) { - std::istringstream stream(str); - parse(stream, flags); -} + using namespace ledger::binary; -void parse_conversion(const std::string& larger_str, - const std::string& smaller_str) -{ - amount_t larger, smaller; - - larger.parse(larger_str.c_str(), AMOUNT_PARSE_NO_REDUCE); - smaller.parse(smaller_str.c_str(), AMOUNT_PARSE_NO_REDUCE); - - larger *= smaller; + // Read in the commodity for this amount - if (larger.commodity()) { - larger.commodity().set_smaller(smaller); - larger.commodity().add_flags(smaller.commodity().flags() | - COMMODITY_STYLE_NOMARKET); + 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_); } - if (smaller.commodity()) - smaller.commodity().set_larger(larger); -} + // Read in the quantity -char * bigints; -char * bigints_next; -unsigned int bigints_index; -unsigned int bigints_count; - -void amount_t::read_quantity(char *& data) -{ char byte = *data++;; - if (byte == 0) { - quantity = NULL; - } - else if (byte == 1) { - quantity = new((bigint_t *)bigints_next) bigint_t; - bigints_next += sizeof(bigint_t); + if (byte < 3) { + if (byte == 2) { + quantity = new((bigint_t *)bigints_next) bigint_t; + bigints_next += sizeof(bigint_t); + } else { + quantity = new bigint_t; + } unsigned short len = *((unsigned short *) data); data += sizeof(unsigned short); @@ -1268,70 +1308,50 @@ void amount_t::read_quantity(char *& data) if (negative) mpz_neg(MPZ(quantity), MPZ(quantity)); - quantity->prec = *((unsigned char *) data); - data += sizeof(unsigned char); - quantity->flags = *((unsigned char *) data); - data += sizeof(unsigned char); - quantity->flags |= BIGINT_BULK_ALLOC; + quantity->prec = *((precision_t *) data); + data += sizeof(precision_t); + quantity->set_flags(*((flags_t *) data)); + data += sizeof(flags_t); + + if (byte == 2) + quantity->add_flags(BIGINT_BULK_ALLOC); } else { - unsigned int index = *((unsigned int *) data); - data += sizeof(unsigned int); + uint_fast32_t index = *((uint_fast32_t *) data); + data += sizeof(uint_fast32_t); quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); - DEBUG_PRINT("amounts.refs", - quantity << " ref++, now " << (quantity->ref + 1)); + DEBUG("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); quantity->ref++; } } -static char buf[4096]; - -void amount_t::read_quantity(std::istream& in) +void amount_t::write(std::ostream& out, bool optimized) const { - char byte; - in.read(&byte, sizeof(byte)); + using namespace ledger::binary; - if (byte == 0) { - quantity = NULL; - } - else if (byte == 1) { - quantity = new bigint_t; + // Write out the commodity for this amount - unsigned short len; - in.read((char *)&len, sizeof(len)); - assert(len < 4096); - in.read(buf, len); - mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), - 0, 0, buf); + if (! quantity) + throw_(amount_error, "Cannot serialize an uninitialized amount"); - char negative; - in.read(&negative, sizeof(negative)); - if (negative) - mpz_neg(MPZ(quantity), MPZ(quantity)); + if (commodity_) + write_long(out, commodity_->ident); + else + write_long(out, 0xffffffff); - in.read((char *)&quantity->prec, sizeof(quantity->prec)); - in.read((char *)&quantity->flags, sizeof(quantity->flags)); - } - else { - assert(0); - } -} + // Write out the quantity -void amount_t::write_quantity(std::ostream& out) const -{ char byte; - if (! quantity) { - byte = 0; - out.write(&byte, sizeof(byte)); - return; - } - - if (quantity->index == 0) { - quantity->index = ++bigints_index; - bigints_count++; - - byte = 1; + if (! optimized || quantity->index == 0) { + if (optimized) { + quantity->index = ++bigints_index; // if !optimized, this is garbage + bigints_count++; + byte = 2; + } else { + byte = 1; + } out.write(&byte, sizeof(byte)); std::size_t size; @@ -1347,669 +1367,34 @@ void amount_t::write_quantity(std::ostream& out) const out.write(&byte, sizeof(byte)); out.write((char *)&quantity->prec, sizeof(quantity->prec)); - unsigned char flags = quantity->flags & ~BIGINT_BULK_ALLOC; - assert(sizeof(flags) == sizeof(quantity->flags)); - out.write((char *)&flags, sizeof(flags)); + bigint_t::flags_t tflags = quantity->flags() & ~BIGINT_BULK_ALLOC; + assert(sizeof(tflags) == sizeof(bigint_t::flags_t)); + out.write((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 = 2; + byte = 3; out.write(&byte, sizeof(byte)); out.write((char *)&quantity->index, sizeof(quantity->index)); } } + bool amount_t::valid() const { if (quantity) { if (quantity->ref == 0) { - DEBUG_PRINT("ledger.validate", "amount_t: quantity->ref == 0"); + DEBUG("ledger.validate", "amount_t: quantity->ref == 0"); return false; } } else if (commodity_) { - DEBUG_PRINT("ledger.validate", "amount_t: commodity_ != NULL"); - return false; - } - return true; -} - -void amount_t::annotate_commodity(const amount_t& price, - const datetime_t& date, - const std::string& tag) -{ - const commodity_t * this_base; - annotated_commodity_t * this_ann = NULL; - - if (commodity().annotated) { - this_ann = &static_cast(commodity()); - this_base = this_ann->ptr; - } else { - this_base = &commodity(); - } - assert(this_base); - - DEBUG_PRINT("amounts.commodities", "Annotating commodity for amount " - << *this << std::endl - << " price " << price << " " - << " date " << date << " " - << " tag " << tag); - - commodity_t * ann_comm = - annotated_commodity_t::find_or_create - (*this_base, ! price && this_ann ? this_ann->price : price, - ! date && this_ann ? this_ann->date : date, - tag.empty() && this_ann ? this_ann->tag : tag); - if (ann_comm) - set_commodity(*ann_comm); - - DEBUG_PRINT("amounts.commodities", " Annotated amount is " << *this); -} - -amount_t amount_t::strip_annotations(const bool _keep_price, - const bool _keep_date, - const bool _keep_tag) const -{ - if (! commodity().annotated || - (_keep_price && _keep_date && _keep_tag)) - return *this; - - DEBUG_PRINT("amounts.commodities", "Reducing commodity for amount " - << *this << std::endl - << " keep price " << _keep_price << " " - << " keep date " << _keep_date << " " - << " keep tag " << _keep_tag); - - annotated_commodity_t& - ann_comm(static_cast(commodity())); - assert(ann_comm.base); - - commodity_t * new_comm; - - if ((_keep_price && ann_comm.price) || - (_keep_date && ann_comm.date) || - (_keep_tag && ! ann_comm.tag.empty())) - { - new_comm = annotated_commodity_t::find_or_create - (*ann_comm.ptr, _keep_price ? ann_comm.price : amount_t(), - _keep_date ? ann_comm.date : datetime_t(), - _keep_tag ? ann_comm.tag : ""); - } else { - new_comm = commodity_t::find_or_create(ann_comm.base_symbol()); - } - assert(new_comm); - - amount_t temp(*this); - temp.set_commodity(*new_comm); - - DEBUG_PRINT("amounts.commodities", " Reduced amount is " << temp); - - return temp; -} - -amount_t amount_t::price() const -{ - if (commodity_ && commodity_->annotated) { - amount_t temp(((annotated_commodity_t *)commodity_)->price); - temp *= *this; - DEBUG_PRINT("amounts.commodities", - "Returning price of " << *this << " = " << temp); - return temp; - } - return *this; -} - -datetime_t amount_t::date() const -{ - if (commodity_ && commodity_->annotated) { - DEBUG_PRINT("amounts.commodities", - "Returning date of " << *this << " = " - << ((annotated_commodity_t *)commodity_)->date); - return ((annotated_commodity_t *)commodity_)->date; - } - return 0L; -} - - -void commodity_base_t::add_price(const datetime_t& date, - const amount_t& price) -{ - if (! history) - history = new history_t; - - history_map::iterator i = history->prices.find(date); - if (i != history->prices.end()) { - (*i).second = price; - } else { - std::pair result - = history->prices.insert(history_pair(date, price)); - assert(result.second); - } -} - -bool commodity_base_t::remove_price(const datetime_t& date) -{ - if (history) { - history_map::size_type n = history->prices.erase(date); - if (n > 0) { - if (history->prices.empty()) - history = NULL; - return true; - } - } - return false; -} - -commodity_base_t * commodity_base_t::create(const std::string& symbol) -{ - commodity_base_t * commodity = new commodity_base_t(symbol); - - DEBUG_PRINT("amounts.commodities", "Creating base commodity " << symbol); - - std::pair result - = commodities.insert(base_commodities_pair(symbol, commodity)); - assert(result.second); - - return commodity; -} - -bool commodity_t::needs_quotes(const std::string& symbol) -{ - for (const char * p = symbol.c_str(); *p; p++) - if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.') - return true; - - return false; -} - -bool commodity_t::valid() const -{ - if (symbol().empty() && this != null_commodity) { - DEBUG_PRINT("ledger.validate", - "commodity_t: symbol().empty() && this != null_commodity"); - return false; - } - - if (annotated && ! base) { - DEBUG_PRINT("ledger.validate", "commodity_t: annotated && ! base"); - return false; - } - - if (precision() > 16) { - DEBUG_PRINT("ledger.validate", "commodity_t: precision() > 16"); + DEBUG("ledger.validate", "amount_t: commodity_ != NULL"); return false; } - return true; } -commodity_t * commodity_t::create(const std::string& symbol) -{ - std::auto_ptr commodity(new commodity_t); - - commodity->base = commodity_base_t::create(symbol); - - if (needs_quotes(symbol)) { - commodity->qualified_symbol = "\""; - commodity->qualified_symbol += symbol; - commodity->qualified_symbol += "\""; - } else { - commodity->qualified_symbol = symbol; - } - - DEBUG_PRINT("amounts.commodities", - "Creating commodity " << commodity->qualified_symbol); - - std::pair result - = commodities.insert(commodities_pair(symbol, commodity.get())); - if (! result.second) - return NULL; - - // Start out the new commodity with the default commodity's flags - // and precision, if one has been defined. - if (default_commodity) - commodity->drop_flags(COMMODITY_STYLE_THOUSANDS | - COMMODITY_STYLE_NOMARKET); - - return commodity.release(); -} - -commodity_t * commodity_t::find_or_create(const std::string& symbol) -{ - DEBUG_PRINT("amounts.commodities", "Find-or-create commodity " << symbol); - - commodity_t * commodity = find(symbol); - if (commodity) - return commodity; - return create(symbol); -} - -commodity_t * commodity_t::find(const std::string& symbol) -{ - DEBUG_PRINT("amounts.commodities", "Find commodity " << symbol); - - commodities_map::const_iterator i = commodities.find(symbol); - if (i != commodities.end()) - return (*i).second; - return NULL; -} - -amount_t commodity_base_t::value(const datetime_t& moment) -{ - datetime_t age; - amount_t price; - - if (history) { - assert(history->prices.size() > 0); - - if (! moment) { - history_map::reverse_iterator r = history->prices.rbegin(); - age = (*r).first; - price = (*r).second; - } else { - history_map::iterator i = history->prices.lower_bound(moment); - if (i == history->prices.end()) { - history_map::reverse_iterator r = history->prices.rbegin(); - age = (*r).first; - price = (*r).second; - } else { - age = (*i).first; - if (moment != age) { - if (i != history->prices.begin()) { - --i; - age = (*i).first; - price = (*i).second; - } else { - age = 0; - } - } else { - price = (*i).second; - } - } - } - } - - if (updater && ! (flags & COMMODITY_STYLE_NOMARKET)) - (*updater)(*this, moment, age, - (history && history->prices.size() > 0 ? - (*history->prices.rbegin()).first : datetime_t()), price); - - return price; -} - -bool annotated_commodity_t::operator==(const commodity_t& comm) const -{ - // If the base commodities don't match, the game's up. - if (base != comm.base) - return false; - - if (price && - (! comm.annotated || - price != static_cast(comm).price)) - return false; - - if (date && - (! comm.annotated || - date != static_cast(comm).date)) - return false; - - if (! tag.empty() && - (! comm.annotated || - tag != static_cast(comm).tag)) - return false; - - return true; -} - -void -annotated_commodity_t::write_annotations(std::ostream& out, - const amount_t& price, - const datetime_t& date, - const std::string& tag) -{ - if (price) - out << " {" << price << '}'; - - if (date) - out << " [" << date << ']'; - - if (! tag.empty()) - out << " (" << tag << ')'; -} - -commodity_t * -annotated_commodity_t::create(const commodity_t& comm, - const amount_t& price, - const datetime_t& date, - const std::string& tag, - const std::string& mapping_key) -{ - std::auto_ptr commodity(new annotated_commodity_t); - - // Set the annotated bits - commodity->price = price; - commodity->date = date; - commodity->tag = tag; - - commodity->ptr = &comm; - assert(commodity->ptr); - commodity->base = comm.base; - assert(commodity->base); - - commodity->qualified_symbol = comm.symbol(); - - DEBUG_PRINT("amounts.commodities", "Creating annotated commodity " - << "symbol " << commodity->symbol() - << " key " << mapping_key << std::endl - << " price " << price << " " - << " date " << date << " " - << " tag " << tag); - - // Add the fully annotated name to the map, so that this symbol may - // quickly be found again. - std::pair result - = commodities.insert(commodities_pair(mapping_key, commodity.get())); - if (! result.second) - return NULL; - - return commodity.release(); -} - -namespace { - std::string make_qualified_name(const commodity_t& comm, - const amount_t& price, - const datetime_t& date, - const std::string& tag) - { - if (price < 0) - throw new amount_error("A commodity's price may not be negative"); - - std::ostringstream name; - - comm.write(name); - annotated_commodity_t::write_annotations(name, price, date, tag); - - DEBUG_PRINT("amounts.commodities", "make_qualified_name for " - << comm.qualified_symbol << std::endl - << " price " << price << " " - << " date " << date << " " - << " tag " << tag); - - DEBUG_PRINT("amounts.commodities", "qualified_name is " << name.str()); - - return name.str(); - } -} - -commodity_t * -annotated_commodity_t::find_or_create(const commodity_t& comm, - const amount_t& price, - const datetime_t& date, - const std::string& tag) -{ - std::string name = make_qualified_name(comm, price, date, tag); - - commodity_t * ann_comm = commodity_t::find(name); - if (ann_comm) { - assert(ann_comm->annotated); - return ann_comm; - } - return create(comm, price, date, tag, name); -} - -bool compare_amount_commodities::operator()(const amount_t * left, - const amount_t * right) const -{ - commodity_t& leftcomm(left->commodity()); - commodity_t& rightcomm(right->commodity()); - - int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol()); - if (cmp != 0) - return cmp < 0; - - if (! leftcomm.annotated) { - assert(rightcomm.annotated); - return true; - } - else if (! rightcomm.annotated) { - assert(leftcomm.annotated); - return false; - } - else { - annotated_commodity_t& aleftcomm(static_cast(leftcomm)); - annotated_commodity_t& arightcomm(static_cast(rightcomm)); - - if (! aleftcomm.price && arightcomm.price) - return true; - if (aleftcomm.price && ! arightcomm.price) - return false; - - if (aleftcomm.price && arightcomm.price) { - amount_t leftprice(aleftcomm.price); - leftprice.reduce(); - amount_t rightprice(arightcomm.price); - rightprice.reduce(); - - if (leftprice.commodity() == rightprice.commodity()) { - int diff = leftprice.compare(rightprice); - if (diff) - return diff; - } 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(); - - int diff = leftprice.compare(rightprice); - if (diff) - return diff; - } - } - - if (! aleftcomm.date && arightcomm.date) - return true; - if (aleftcomm.date && ! arightcomm.date) - return false; - - if (aleftcomm.date && arightcomm.date) { - int diff = aleftcomm.date - arightcomm.date; - if (diff) - return diff < 0; - } - - if (aleftcomm.tag.empty() && ! arightcomm.tag.empty()) - return true; - if (! aleftcomm.tag.empty() && arightcomm.tag.empty()) - return false; - - if (! aleftcomm.tag.empty() && ! arightcomm.tag.empty()) - return aleftcomm.tag < arightcomm.tag; - - // The two annotated commodities don't differ enough to matter. This - // should make this identical. - assert(0); - return true; - } -} - } // namespace ledger - -#ifdef USE_BOOST_PYTHON - -#include -#include - -using namespace boost::python; -using namespace ledger; - -int py_amount_quantity(amount_t& amount) -{ - std::string quant = amount.quantity_string(); - return std::atol(quant.c_str()); -} - -void py_parse_1(amount_t& amount, const std::string& str, - unsigned char flags) { - amount.parse(str, flags); -} -void py_parse_2(amount_t& amount, const std::string& str) { - amount.parse(str); -} - -struct commodity_updater_wrap : public commodity_base_t::updater_t -{ - PyObject * self; - commodity_updater_wrap(PyObject * self_) : self(self_) {} - - virtual void operator()(commodity_base_t& commodity, - const datetime_t& moment, - const datetime_t& date, - const datetime_t& last, - amount_t& price) { - call_method(self, "__call__", commodity, moment, date, last, price); - } -}; - -commodity_t * py_find_commodity(const std::string& symbol) -{ - return commodity_t::find(symbol); -} - -#define EXC_TRANSLATOR(type) \ - void exc_translate_ ## type(const type& err) { \ - PyErr_SetString(PyExc_RuntimeError, err.what()); \ - } - -EXC_TRANSLATOR(amount_error) - -void export_amount() -{ - scope().attr("AMOUNT_PARSE_NO_MIGRATE") = AMOUNT_PARSE_NO_MIGRATE; - scope().attr("AMOUNT_PARSE_NO_REDUCE") = AMOUNT_PARSE_NO_REDUCE; - - class_< amount_t > ("Amount") - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - - .def(self += self) - .def(self += long()) - .def(self + self) - .def(self + long()) - .def(self -= self) - .def(self -= long()) - .def(self - self) - .def(self - long()) - .def(self *= self) - .def(self *= long()) - .def(self * self) - .def(self * long()) - .def(self /= self) - .def(self /= long()) - .def(self / self) - .def(self / long()) - .def(- self) - - .def(self < self) - .def(self < long()) - .def(self <= self) - .def(self <= long()) - .def(self > self) - .def(self > long()) - .def(self >= self) - .def(self >= long()) - .def(self == self) - .def(self == long()) - .def(self != self) - .def(self != long()) - .def(! self) - - .def(self_ns::int_(self)) - .def(self_ns::float_(self)) - .def(self_ns::str(self)) - .def(abs(self)) - - .add_property("commodity", - make_function(&amount_t::commodity, - return_value_policy()), - make_function(&amount_t::set_commodity, - with_custodian_and_ward<1, 2>())) - - .def("strip_annotations", &amount_t::strip_annotations) - - .def("negate", &amount_t::negate) - .def("negated", &amount_t::negated) - .def("parse", py_parse_1) - .def("parse", py_parse_2) - .def("reduce", &amount_t::reduce) - - .def("valid", &amount_t::valid) - ; - - class_< commodity_base_t::updater_t, commodity_updater_wrap, - boost::noncopyable > - ("Updater") - ; - - scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; - scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; - scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; - scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN; - scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; - scope().attr("COMMODITY_STYLE_NOMARKET") = COMMODITY_STYLE_NOMARKET; - scope().attr("COMMODITY_STYLE_BUILTIN") = COMMODITY_STYLE_BUILTIN; - - class_< commodity_t > ("Commodity") - .add_property("symbol", &commodity_t::symbol) - -#if 0 - .add_property("name", &commodity_t::name, &commodity_t::set_name) - .add_property("note", &commodity_t::note, &commodity_t::set_note) - .add_property("precision", &commodity_t::precision, - &commodity_t::set_precision) - .add_property("flags", &commodity_t::flags, &commodity_t::set_flags) - .add_property("add_flags", &commodity_t::add_flags) - .add_property("drop_flags", &commodity_t::drop_flags) -#if 0 - .add_property("updater", &commodity_t::updater) -#endif - - .add_property("smaller", - make_getter(&commodity_t::smaller, - return_value_policy()), - make_setter(&commodity_t::smaller, - return_value_policy())) - .add_property("larger", - make_getter(&commodity_t::larger, - return_value_policy()), - make_setter(&commodity_t::larger, - return_value_policy())) - - .def(self_ns::str(self)) - - .def("find", py_find_commodity, - return_value_policy()) - .staticmethod("find") -#endif - - .def("add_price", &commodity_t::add_price) - .def("remove_price", &commodity_t::remove_price) - .def("value", &commodity_t::value) - - .def("valid", &commodity_t::valid) - ; - -#define EXC_TRANSLATE(type) \ - register_exception_translator(&exc_translate_ ## type); - - EXC_TRANSLATE(amount_error); -} - -#endif // USE_BOOST_PYTHON diff --git a/amount.h b/amount.h index dfe8d2df..cd011675 100644 --- a/amount.h +++ b/amount.h @@ -1,628 +1,712 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file 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 -#include -#include -#include -#include -#include -#include -#include - -#include "datetime.h" -#include "debug.h" -#include "error.h" +#include "utils.h" namespace ledger { -extern bool do_cleanup; - class commodity_t; - +class annotation_t; +class commodity_pool_t; + +DECLARE_EXCEPTION(error, amount_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 > > > { - public: - class bigint_t; + // 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; - static bool keep_base; - protected: - void _init(); + /** + * 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; + +protected: void _copy(const amount_t& amt); - void _release(); void _dup(); - void _resize(unsigned int prec); + void _resize(precision_t prec); + void _clear(); + void _release(); - void _clear() { - if (quantity) { - assert(commodity_); - _release(); - quantity = NULL; - commodity_ = NULL; - } else { - assert(! commodity_); - } - } + struct bigint_t; bigint_t * quantity; commodity_t * commodity_; - public: - // constructors - amount_t() : quantity(NULL), commodity_(NULL) {} +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, ""); + } + amount_t(const double val); + 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 *"); + 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(const std::string& value) : quantity(NULL) { - parse(value); - } - amount_t(const char * value) : quantity(NULL) { - parse(value); - } - amount_t(const bool value); - amount_t(const long value); - amount_t(const unsigned long value); - amount_t(const double value); + amount_t& operator=(const amount_t& amt); - // destructor - ~amount_t() { - if (quantity) - _release(); - } + amount_t& operator=(const string& str) { + return *this = amount_t(str); + } + amount_t& operator=(const char * 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; - commodity_t& commodity() const; - void set_commodity(commodity_t& comm) { - commodity_ = &comm; + bool operator==(const amount_t& amt) const; + + template + bool operator==(const T& val) const { + return compare(val) == 0; } - void annotate_commodity(const amount_t& price, - const datetime_t& date = datetime_t(), - const std::string& tag = ""); - amount_t strip_annotations(const bool _keep_price = keep_price, - const bool _keep_date = keep_date, - const bool _keep_tag = keep_tag) const; - void clear_commodity() { - commodity_ = NULL; + template + bool operator<(const T& amt) const { + return compare(amt) < 0; } - amount_t price() const; - datetime_t date() const; - - bool null() const { - return ! quantity && ! commodity_; + template + bool operator>(const T& amt) const { + return compare(amt) > 0; } - std::string quantity_string() const; - - // assignment operator - amount_t& operator=(const amount_t& amt); - amount_t& operator=(const std::string& value); - amount_t& operator=(const char * value); - amount_t& operator=(const bool value); - amount_t& operator=(const long value); - amount_t& operator=(const unsigned long value); - amount_t& operator=(const double value); - - // general methods - amount_t round(unsigned int prec) const; - amount_t round() const; - amount_t unround() const; - - // in-place arithmetic + /** + * 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); - template - amount_t& operator+=(T value) { - return *this += amount_t(value); - } - template - amount_t& operator-=(T value) { - return *this -= amount_t(value); - } - template - amount_t& operator*=(T value) { - return *this *= amount_t(value); - } - template - amount_t& operator/=(T value) { - return *this /= amount_t(value); - } - - // simple arithmetic - amount_t operator+(const amount_t& amt) const { - amount_t temp = *this; - temp += amt; - return temp; - } - amount_t operator-(const amount_t& amt) const { - amount_t temp = *this; - temp -= amt; - return temp; - } - amount_t operator*(const amount_t& amt) const { - amount_t temp = *this; - temp *= amt; - return temp; - } - amount_t operator/(const amount_t& amt) const { - amount_t temp = *this; - temp /= amt; + /** + * 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) 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(); - template - amount_t operator+(T value) const { - amount_t temp = *this; - temp += value; - return temp; - } - template - amount_t operator-(T value) const { - amount_t temp = *this; - temp -= value; - return temp; + amount_t operator-() const { + return negate(); } - template - amount_t operator*(T value) const { - amount_t temp = *this; - temp *= value; - return temp; + + amount_t abs() const { + if (sign() < 0) + return negate(); + return *this; } - template - amount_t operator/(T value) const { - amount_t temp = *this; - temp /= value; + + 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(); - // unary negation - void negate(); - amount_t negated() const { - amount_t temp = *this; - temp.negate(); + amount_t unreduce() const { + amount_t temp(*this); + temp.in_place_unreduce(); return temp; } - amount_t operator-() const { - return negated(); - } - - // test for non-zero (use ! for zero) - operator bool() const; - operator long() const; - operator double() const; - - bool realzero() const; - - // comparisons between amounts - int compare(const amount_t& amt) const; + amount_t& in_place_unreduce(); + + optional value(const optional& 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; - bool operator<(const amount_t& amt) const { - return compare(amt) < 0; - } - bool operator<=(const amount_t& amt) const { - return compare(amt) <= 0; - } - bool operator>(const amount_t& amt) const { - return compare(amt) > 0; + operator bool() const { + return is_nonzero(); } - bool operator>=(const amount_t& amt) const { - return compare(amt) >= 0; + bool is_nonzero() const { + return ! is_zero(); } - bool operator==(const amount_t& amt) const; - bool operator!=(const amount_t& amt) const; - template - void parse_num(T num) { - std::ostringstream temp; - temp << num; - std::istringstream in(temp.str()); - parse(in); + bool is_zero() const; + bool is_realzero() const { + return sign() == 0; } - int sign() const; + 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. + */ + double to_double(bool no_check = false) const; + long to_long(bool no_check = false) const; + string to_string() const; + string to_fullstring() const; + string quantity_string() const; + + bool fits_in_double() const; + 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; - // POD comparisons -#define AMOUNT_CMP_INT(OP) \ - template \ - bool operator OP (T num) const { \ - if (num == 0) { \ - return sign() OP 0; \ - } else { \ - amount_t amt; \ - amt.parse_num(num); \ - return *this OP amt; \ - } \ + bool has_commodity() const; + void set_commodity(commodity_t& comm) { + if (! quantity) + *this = 0L; + commodity_ = &comm; } - - AMOUNT_CMP_INT(<) - AMOUNT_CMP_INT(<=) - AMOUNT_CMP_INT(>) - AMOUNT_CMP_INT(>=) - AMOUNT_CMP_INT(==) - - template - bool operator!=(T num) const { - return ! (*this == num); + void clear_commodity() { + commodity_ = NULL; } - amount_t value(const datetime_t& moment) const; + amount_t number() const { + if (! has_commodity()) + return *this; - void abs() { - if (*this < 0) - negate(); + 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, [moment_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_commodity(const annotation_t& details); + bool commodity_annotated() const; + annotation_t annotation_details() const; + 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 - void parse(std::istream& in, unsigned char flags = 0); - void parse(const std::string& str, unsigned char flags = 0); - void reduce(); - - amount_t reduced() const { - amount_t temp(*this); - temp.reduce(); - return temp; + typedef uint_least8_t flags_t; + + void parse(std::istream& in, flags_t flags = 0); + void parse(const string& str, flags_t flags = 0) { + std::istringstream stream(str); + parse(stream, flags); + assert(stream.eof()); + } + + 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 << ")"; } - void read_quantity(char *& data); - void read_quantity(std::istream& in); - void write_quantity(std::ostream& out) const; - bool valid() const; - - // Classes that are friends, and help to implement this class - - friend std::ostream& operator<<(std::ostream& out, const amount_t& amt); - friend std::istream& operator>>(std::istream& in, amount_t& amt); - - friend unsigned int sizeof_bigint_t(); - - friend void read_binary_amount(char *& data, amount_t& amt); - friend void write_binary_amount(std::ostream& out, const amount_t& amt); - - // This function is special, and exists only to support a custom - // optimization in binary.cc (which offers a significant enough gain - // to be worth the trouble). - - friend void clean_commodity_history(char * item_pool, - char * item_pool_end); - - friend void parse_annotations(std::istream& in, amount_t& price, - datetime_t& date, std::string& tag); }; -unsigned int sizeof_bigint_t(); - -void parse_quantity(std::istream& in, std::string& value); -void parse_commodity(std::istream& in, std::string& symbol); -void parse_annotations(std::istream& in, const std::string& symbol, - std::string& name, std::string& price, - std::string& date, std::string& tag); -void parse_conversion(const std::string& larger, - const std::string& smaller); - -inline bool is_quote_or_paren(char * p) { - return *p == '"' || *p == '{' || *p == '[' || *p == '('; +inline amount_t amount_t::exact(const string& value) { + amount_t temp; + temp.parse(value, AMOUNT_PARSE_NO_MIGRATE); + return temp; } -inline char * scan_past_quotes_and_parens(char * expr) -{ - std::stack paren_stack; - - char * p; - for (p = expr; *p; p++) { - if (*p == '"' || - ((*p == '(' || ((*p == '{' || *p == '[') && - paren_stack.top() != '(')) && - paren_stack.top() != '"')) { - paren_stack.push(*p); - } - else if ((*p == ')' && paren_stack.top() == '(') || - (*p == '}' && paren_stack.top() == '{') || - (*p == ']' && paren_stack.top() == '[') || - (*p == '"' && paren_stack.top() == '"')) { - paren_stack.pop(); - if (paren_stack.size() == 0) - break; - } - } - return p; +inline string amount_t::to_string() const { + std::ostringstream bufstream; + print(bufstream); + return bufstream.str(); } -inline amount_t abs(const amount_t& amt) { - return amt < 0 ? amt.negated() : amt; +inline string amount_t::to_fullstring() const { + std::ostringstream bufstream; + print(bufstream, false, true); + return bufstream.str(); } -std::ostream& operator<<(std::ostream& out, const amount_t& amt); +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 -#define COMMODITY_STYLE_DEFAULTS 0x0000 -#define COMMODITY_STYLE_SUFFIXED 0x0001 -#define COMMODITY_STYLE_SEPARATED 0x0002 -#define COMMODITY_STYLE_EUROPEAN 0x0004 -#define COMMODITY_STYLE_THOUSANDS 0x0008 -#define COMMODITY_STYLE_NOMARKET 0x0010 -#define COMMODITY_STYLE_BUILTIN 0x0020 - -typedef std::map history_map; -typedef std::pair history_pair; - -class commodity_base_t; - -typedef std::map base_commodities_map; -typedef std::pair base_commodities_pair; - -class commodity_base_t -{ - public: - friend class commodity_t; - friend class annotated_commodity_t; - - typedef unsigned long ident_t; - - ident_t ident; - std::string name; - std::string note; - unsigned char precision; - unsigned char flags; - amount_t * smaller; - amount_t * larger; - - commodity_base_t() - : precision(0), flags(COMMODITY_STYLE_DEFAULTS), - smaller(NULL), larger(NULL), history(NULL) {} - - commodity_base_t(const std::string& _symbol, - unsigned int _precision = 0, - unsigned int _flags = COMMODITY_STYLE_DEFAULTS) - : precision(_precision), flags(_flags), - smaller(NULL), larger(NULL), symbol(_symbol), history(NULL) {} - - ~commodity_base_t() { - if (history) delete history; - if (smaller) delete smaller; - if (larger) delete larger; - } - - static base_commodities_map commodities; - static commodity_base_t * create(const std::string& symbol); - - std::string symbol; - - struct history_t { - history_map prices; - datetime_t last_lookup; - datetime_t bogus_time; - history_t() : last_lookup(0), bogus_time(0) {} - }; - history_t * history; - - void add_price(const datetime_t& date, const amount_t& price); - bool remove_price(const datetime_t& date); - amount_t value(const datetime_t& moment = datetime_t::now); - - class updater_t { - public: - virtual ~updater_t() {} - virtual void operator()(commodity_base_t& commodity, - const datetime_t& moment, - const datetime_t& date, - const datetime_t& last, - amount_t& price) = 0; - }; - friend class updater_t; - - static updater_t * updater; -}; - -typedef std::map commodities_map; -typedef std::pair commodities_pair; - -class commodity_t -{ - friend class annotated_commodity_t; - - public: - // This map remembers all commodities that have been defined. - - static commodities_map commodities; - static bool commodities_sorted; - static commodity_t * null_commodity; - static commodity_t * default_commodity; - - static commodity_t * create(const std::string& symbol); - static commodity_t * find(const std::string& name); - static commodity_t * find_or_create(const std::string& symbol); - - static bool needs_quotes(const std::string& symbol); - - static void make_alias(const std::string& symbol, - commodity_t * commodity); - - // These are specific to each commodity reference - - typedef unsigned long ident_t; - - ident_t ident; - commodity_base_t * base; - std::string qualified_symbol; - bool annotated; - - public: - explicit commodity_t() : base(NULL), annotated(false) {} - virtual ~commodity_t() {} - - operator bool() const { - return this != null_commodity; - } - virtual bool operator==(const commodity_t& comm) const { - if (comm.annotated) - return comm == *this; - return base == comm.base; - } - bool operator!=(const commodity_t& comm) const { - return ! (*this == comm); - } - - std::string base_symbol() const { - return base->symbol; - } - std::string symbol() const { - return qualified_symbol; - } - - void write(std::ostream& out) const { - out << symbol(); - } - - std::string name() const { - return base->name; - } - void set_name(const std::string& arg) { - base->name = arg; - } - - std::string note() const { - return base->note; - } - void set_note(const std::string& arg) { - base->note = arg; - } - - unsigned char precision() const { - return base->precision; - } - void set_precision(unsigned char arg) { - base->precision = arg; - } - - unsigned char flags() const { - return base->flags; - } - void set_flags(unsigned char arg) { - base->flags = arg; - } - void add_flags(unsigned char arg) { - base->flags |= arg; - } - void drop_flags(unsigned char arg) { - base->flags &= ~arg; - } - - amount_t * smaller() const { - return base->smaller; - } - void set_smaller(const amount_t& arg) { - if (base->smaller) - delete base->smaller; - base->smaller = new amount_t(arg); - } - - amount_t * larger() const { - return base->larger; - } - void set_larger(const amount_t& arg) { - if (base->larger) - delete base->larger; - base->larger = new amount_t(arg); - } - - commodity_base_t::history_t * history() const { - return base->history; - } - - void add_price(const datetime_t& date, const amount_t& price) { - return base->add_price(date, price); - } - bool remove_price(const datetime_t& date) { - return base->remove_price(date); - } - amount_t value(const datetime_t& moment = datetime_t::now) const { - return base->value(moment); - } - - bool valid() const; -}; - -class annotated_commodity_t : public commodity_t -{ - public: - const commodity_t * ptr; - - amount_t price; - datetime_t date; - std::string tag; - - explicit annotated_commodity_t() { - annotated = true; - } - - virtual bool operator==(const commodity_t& comm) const; - - void write_annotations(std::ostream& out) const { - annotated_commodity_t::write_annotations(out, price, date, tag); - } - - static void write_annotations(std::ostream& out, - const amount_t& price, - const datetime_t& date, - const std::string& tag); - - private: - static commodity_t * create(const commodity_t& comm, - const amount_t& price, - const datetime_t& date, - const std::string& tag, - const std::string& mapping_key); - - static commodity_t * find_or_create(const commodity_t& comm, - const amount_t& price, - const datetime_t& date, - const std::string& tag); - - friend class amount_t; -}; +#include "commodity.h" -inline std::ostream& operator<<(std::ostream& out, - const commodity_t& comm) { - out << comm.symbol(); - return out; -} +namespace ledger { -inline amount_t amount_t::round() const { - return round(commodity().precision()); +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 { - if (! commodity_) - return *commodity_t::null_commodity; - else - return *commodity_; + return has_commodity() ? *commodity_ : *current_pool->null_commodity; } -class amount_error : public error { - public: - amount_error(const std::string& reason) throw() : error(reason) {} - virtual ~amount_error() throw() {} -}; - -struct compare_amount_commodities { - bool operator()(const amount_t * left, const amount_t * right) const; -}; +inline bool amount_t::has_commodity() const { + return commodity_ && commodity_ != commodity_->parent().null_commodity; +} } // namespace ledger diff --git a/balance.cc b/balance.cc index 9e516736..80637221 100644 --- a/balance.cc +++ b/balance.cc @@ -1,529 +1,272 @@ -#include "balance.h" -#include "util.h" +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ -#include -#include +#include "balance.h" namespace ledger { -amount_t balance_t::amount(const commodity_t& commodity) const -{ - if (! commodity) { - if (amounts.size() == 1) { - amounts_map::const_iterator i = amounts.begin(); - return (*i).second; - } - else if (amounts.size() > 1) { - // Try stripping annotations before giving an error. - balance_t temp(strip_annotations()); - if (temp.amounts.size() == 1) - return temp.amount(commodity); - - std::ostringstream errmsg; - errmsg << "Requested amount of a balance with multiple commodities: " - << temp; - throw new amount_error(errmsg.str()); - } - } - else if (amounts.size() > 0) { - amounts_map::const_iterator i = amounts.find(&commodity); - if (i != amounts.end()) - return (*i).second; - } - return amount_t(); -} - -balance_t balance_t::value(const datetime_t& moment) const +balance_t& balance_t::operator+=(const balance_t& bal) { - balance_t temp; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); i++) - temp += (*i).second.value(moment); - - return temp; + *this += i->second; + return *this; } -balance_t balance_t::price() const +balance_t& balance_t::operator+=(const amount_t& amt) { - balance_t temp; + if (amt.is_null()) + throw_(balance_error, + "Cannot add an uninitialized amount to a balance"); - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - temp += (*i).second.price(); + if (amt.is_realzero()) + return *this; - return temp; -} + 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)); -datetime_t balance_t::date() const -{ - datetime_t temp; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) { - datetime_t date = (*i).second.date(); - if (! temp && date) - temp = date; - else if (temp != date) - return datetime_t(); - } - - return temp; + return *this; } -balance_t balance_t::strip_annotations(const bool keep_price, - const bool keep_date, - const bool keep_tag) const +balance_t& balance_t::operator-=(const balance_t& bal) { - balance_t temp; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); i++) - temp += (*i).second.strip_annotations(keep_price, keep_date, keep_tag); - - return temp; + *this -= i->second; + return *this; } -void balance_t::write(std::ostream& out, - const int first_width, - const int latter_width) const +balance_t& balance_t::operator-=(const amount_t& amt) { - bool first = true; - int lwidth = latter_width; - - if (lwidth == -1) - lwidth = first_width; - - if (commodity_t::commodities_sorted) { - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) { - int width; - if (! first) { - out << std::endl; - width = lwidth; - } else { - first = false; - width = first_width; - } - - out.width(width); - out.fill(' '); - out << std::right << (*i).second; - } + 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 { - typedef std::deque amounts_deque; - amounts_deque sorted; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if ((*i).second) - sorted.push_back(&(*i).second); - - std::stable_sort(sorted.begin(), sorted.end(), compare_amount_commodities()); - - for (amounts_deque::const_iterator i = sorted.begin(); - i != sorted.end(); - i++) { - int width; - if (! first) { - out << std::endl; - width = lwidth; - } else { - first = false; - width = first_width; - } - - out.width(width); - out.fill(' '); - out << std::right << **i; - } - } - - if (first) { - out.width(first_width); - out.fill(' '); - out << std::right << "0"; - } -} - -balance_t& balance_t::operator*=(const balance_t& bal) -{ - if (realzero() || bal.realzero()) { - return *this = 0L; - } - else if (bal.amounts.size() == 1) { - return *this *= (*bal.amounts.begin()).second; - } - else if (amounts.size() == 1) { - return *this = bal * *this; - } - else { - // Since we would fail with an error at this point otherwise, try - // stripping annotations to see if we can come up with a - // reasonable result. The user will not notice any annotations - // missing (since they are viewing a stripped report anyway), only - // that some of their value expression may not see any pricing or - // date data because of this operation. - - balance_t temp(bal.strip_annotations()); - if (temp.amounts.size() == 1) - return *this *= temp; - temp = strip_annotations(); - if (temp.amounts.size() == 1) - return *this = bal * temp; - - std::ostringstream errmsg; - errmsg << "Cannot multiply two balances: " << temp << " * " << bal; - throw new amount_error(errmsg.str()); + amounts.insert(amounts_map::value_type(&amt.commodity(), amt.negate())); } + return *this; } balance_t& balance_t::operator*=(const amount_t& amt) { - if (realzero() || amt.realzero()) { - return *this = 0L; + 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 the null commodity causes all amounts to be - // increased by the same factor. + // Multiplying by an amount with no commodity causes all the + // component amounts to be increased by the same factor. for (amounts_map::iterator i = amounts.begin(); i != amounts.end(); i++) - (*i).second *= amt; + i->second *= amt; } else if (amounts.size() == 1) { - *this = (*amounts.begin()).second * amt; + // 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 { - amounts_map::iterator i = amounts.find(&amt.commodity()); - if (i != amounts.end()) { - (*i).second *= amt; - } else { - // Try stripping annotations before giving an error. - balance_t temp(strip_annotations()); - if (temp.amounts.size() == 1) { - return *this = (*temp.amounts.begin()).second * amt; - } else { - i = temp.amounts.find(&amt.commodity()); - if (i != temp.amounts.end()) - return *this = temp * amt; - } - - std::ostringstream errmsg; - errmsg << "Attempt to multiply balance by a commodity" - << " not found in that balance: " - << temp << " * " << amt; - throw new amount_error(errmsg.str()); - } + 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 balance_t& bal) -{ - if (bal.realzero()) { - std::ostringstream errmsg; - errmsg << "Attempt to divide by zero: " << *this << " / " << bal; - throw new amount_error(errmsg.str()); - } - else if (realzero()) { - return *this = 0L; - } - else if (bal.amounts.size() == 1) { - return *this /= (*bal.amounts.begin()).second; - } - else if (*this == bal) { - return *this = 1L; - } - else { - // Try stripping annotations before giving an error. - balance_t temp(bal.strip_annotations()); - if (temp.amounts.size() == 1) - return *this /= temp; - - std::ostringstream errmsg; - errmsg << "Cannot divide between two balances: " << temp << " / " << bal; - throw new amount_error(errmsg.str()); - } -} - balance_t& balance_t::operator/=(const amount_t& amt) { - if (amt.realzero()) { - std::ostringstream errmsg; - errmsg << "Attempt to divide by zero: " << *this << " / " << amt; - throw new amount_error(errmsg.str()); + if (amt.is_null()) + throw_(balance_error, + "Cannot divide a balance by an uninitialized amount"); + + if (is_realzero()) { + ; } - else if (realzero()) { - return *this = 0L; + else if (amt.is_realzero()) { + throw_(balance_error, "Divide by zero"); } else if (! amt.commodity()) { - // Dividing by the null commodity causes all amounts to be - // decreased by the same factor. + // Dividing by an amount with no commodity causes all the + // component amounts to be divided by the same factor. for (amounts_map::iterator i = amounts.begin(); i != amounts.end(); i++) - (*i).second /= amt; + i->second /= amt; } - else if (amounts.size() == 1 && - (*amounts.begin()).first == &amt.commodity()) { - (*amounts.begin()).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 { - amounts_map::iterator i = amounts.find(&amt.commodity()); - if (i != amounts.end()) { - (*i).second /= amt; - } else { - // Try stripping annotations before giving an error. - balance_t temp(strip_annotations()); - if (temp.amounts.size() == 1 && - (*temp.amounts.begin()).first == &amt.commodity()) - return *this = temp / amt; - - std::ostringstream errmsg; - errmsg << "Attempt to divide balance by a commodity" - << " not found in that balance: " - << temp << " * " << amt; - throw new amount_error(errmsg.str()); - } + assert(amounts.size() > 1); + throw_(balance_error, + "Cannot divide a multi-commodity balance by a commoditized amount"); } return *this; } -balance_t::operator amount_t() const +optional +balance_t::value(const optional& moment) const { - if (amounts.size() == 1) { - return (*amounts.begin()).second; - } - else if (amounts.size() == 0) { - return amount_t(); - } - else { - // Try stripping annotations before giving an error. - balance_t temp(strip_annotations()); - if (temp.amounts.size() == 1) - return (*temp.amounts.begin()).second; - - std::ostringstream errmsg; - errmsg << "Cannot convert a balance with " - << "multiple commodities to an amount: " << temp; - throw new amount_error(errmsg.str()); - } -} + optional temp; -} // namespace ledger + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (optional val = i->second.value(moment)) { + if (! temp) + temp = balance_t(); + *temp += *val; + } -#ifdef USE_BOOST_PYTHON + return temp; +} -#include +optional +balance_t::commodity_amount(const optional& 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); -using namespace boost::python; -using namespace ledger; + 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; +} -unsigned int balance_len(balance_t& bal) +balance_t balance_t::strip_annotations(const bool keep_price, + const bool keep_date, + const bool keep_tag) const { - return bal.amounts.size(); + balance_t temp; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + temp += i->second.strip_annotations(keep_price, keep_date, keep_tag); + + return temp; } -amount_t balance_getitem(balance_t& bal, int i) +void balance_t::print(std::ostream& out, + const int first_width, + const int latter_width) const { - std::size_t len = bal.amounts.size(); + bool first = true; + int lwidth = latter_width; - if (abs(i) >= len) { - PyErr_SetString(PyExc_IndexError, "Index out of range"); - throw_error_already_set(); - } + if (lwidth == -1) + lwidth = first_width; - int x = i < 0 ? len + i : i; - amounts_map::iterator elem = bal.amounts.begin(); - while (--x >= 0) - elem++; + typedef std::vector amounts_array; + amounts_array sorted; - return (*elem).second; -} + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (i->second) + sorted.push_back(&i->second); -unsigned int balance_pair_len(balance_pair_t& bal_pair) -{ - return balance_len(bal_pair.quantity); -} + std::stable_sort(sorted.begin(), sorted.end(), + compare_amount_commodities()); -amount_t balance_pair_getitem(balance_pair_t& bal_pair, int i) -{ - return balance_getitem(bal_pair.quantity, i); -} + for (amounts_array::const_iterator i = sorted.begin(); + i != sorted.end(); + i++) { + int width; + if (! first) { + out << std::endl; + width = lwidth; + } else { + first = false; + width = first_width; + } -void export_balance() -{ - class_< balance_t > ("Balance") - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - - .def(self += self) - .def(self += other()) - .def(self += long()) - .def(self + self) - .def(self + other()) - .def(self + long()) - .def(self -= self) - .def(self -= other()) - .def(self -= long()) - .def(self - self) - .def(self - other()) - .def(self - long()) - .def(self *= self) - .def(self *= other()) - .def(self *= long()) - .def(self * self) - .def(self * other()) - .def(self * long()) - .def(self /= self) - .def(self /= other()) - .def(self /= long()) - .def(self / self) - .def(self / other()) - .def(self / long()) - .def(- self) - - .def(self < self) - .def(self < other()) - .def(self < long()) - .def(self <= self) - .def(self <= other()) - .def(self <= long()) - .def(self > self) - .def(self > other()) - .def(self > long()) - .def(self >= self) - .def(self >= other()) - .def(self >= long()) - .def(self == self) - .def(self == other()) - .def(self == long()) - .def(self != self) - .def(self != other()) - .def(self != long()) - .def(! self) - - .def(abs(self)) - .def(self_ns::str(self)) - - .def("__len__", balance_len) - .def("__getitem__", balance_getitem) - - .def("valid", &balance_t::valid) - - .def("realzero", &balance_t::realzero) - .def("amount", &balance_t::amount) - .def("value", &balance_t::value) - .def("price", &balance_t::price) - .def("date", &balance_t::date) - .def("strip_annotations", &balance_t::strip_annotations) - .def("write", &balance_t::write) - .def("round", &balance_t::round) - .def("negate", &balance_t::negate) - .def("negated", &balance_t::negated) - ; + out.width(width); + out.fill(' '); + out << std::right << **i; + } - class_< balance_pair_t > ("BalancePair") - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - - .def(self += self) - .def(self += other()) - .def(self += other()) - .def(self += long()) - .def(self + self) - .def(self + other()) - .def(self + other()) - .def(self + long()) - .def(self -= self) - .def(self -= other()) - .def(self -= other()) - .def(self -= long()) - .def(self - self) - .def(self - other()) - .def(self - other()) - .def(self - long()) - .def(self *= self) - .def(self *= other()) - .def(self *= other()) - .def(self *= long()) - .def(self * self) - .def(self * other()) - .def(self * other()) - .def(self * long()) - .def(self /= self) - .def(self /= other()) - .def(self /= other()) - .def(self /= long()) - .def(self / self) - .def(self / other()) - .def(self / other()) - .def(self / long()) - .def(- self) - - .def(self < self) - .def(self < other()) - .def(self < other()) - .def(self < long()) - .def(self <= self) - .def(self <= other()) - .def(self <= other()) - .def(self <= long()) - .def(self > self) - .def(self > other()) - .def(self > other()) - .def(self > long()) - .def(self >= self) - .def(self >= other()) - .def(self >= other()) - .def(self >= long()) - .def(self == self) - .def(self == other()) - .def(self == other()) - .def(self == long()) - .def(self != self) - .def(self != other()) - .def(self != other()) - .def(self != long()) - .def(! self) - - .def(abs(self)) - .def(self_ns::str(self)) - - .def("__len__", balance_pair_len) - .def("__getitem__", balance_pair_getitem) - - .def("valid", &balance_pair_t::valid) - - .def("realzero", &balance_pair_t::realzero) - .def("amount", &balance_pair_t::amount) - .def("value", &balance_pair_t::value) - .def("price", &balance_pair_t::price) - .def("date", &balance_pair_t::date) - .def("strip_annotations", &balance_pair_t::strip_annotations) - .def("write", &balance_pair_t::write) - .def("round", &balance_pair_t::round) - .def("negate", &balance_pair_t::negate) - .def("negated", &balance_pair_t::negated) - - .add_property("cost", - make_getter(&balance_pair_t::cost, - return_value_policy())) - ; + if (first) { + out.width(first_width); + out.fill(' '); + out << std::right << "0"; + } } -#endif // USE_BOOST_PYTHON +} // namespace ledger diff --git a/balance.h b/balance.h index ba76d459..aeee506f 100644 --- a/balance.h +++ b/balance.h @@ -1,942 +1,516 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file 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" -#include -#include - namespace ledger { -typedef std::map amounts_map; -typedef std::pair amounts_pair; +DECLARE_EXCEPTION(error, balance_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 > > > > > > > > > > > > > { - public: - amounts_map amounts; +public: + typedef std::map amounts_map; - bool valid() const { - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (! (*i).second.valid()) - return false; - return true; - } + amounts_map amounts; - // constructors - balance_t() {} - balance_t(const balance_t& bal) { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - *this += (*i).second; + // 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) { - if (! amt.realzero()) - amounts.insert(amounts_pair(&amt.commodity(), 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)); } - template - balance_t(T value) { - amount_t amt(value); - if (! amt.realzero()) - amounts.insert(amounts_pair(&amt.commodity(), amt)); - } - - // assignment operator - balance_t& operator=(const balance_t& bal) { - if (this != &bal) { - amounts.clear(); - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - *this += (*i).second; - } - return *this; + balance_t(const double val) { + TRACE_CTOR(balance_t, "const double"); + amounts.insert + (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); } - balance_t& operator=(const amount_t& amt) { - amounts.clear(); - *this += amt; - return *this; + 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)); } - template - balance_t& operator=(T value) { - amounts.clear(); - *this += value; - return *this; + balance_t(const long val) { + TRACE_CTOR(balance_t, "const long"); + amounts.insert + (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); } - // in-place arithmetic - balance_t& operator+=(const balance_t& bal) { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - *this += (*i).second; - return *this; + 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)); } - balance_t& operator+=(const amount_t& amt) { - amounts_map::iterator i = amounts.find(&amt.commodity()); - if (i != amounts.end()) - (*i).second += amt; - else if (! amt.realzero()) - amounts.insert(amounts_pair(&amt.commodity(), amt)); - return *this; + 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)); } - template - balance_t& operator+=(T val) { - return *this += amount_t(val); + + /** + * Destructor. Destroys all of the accumulated amounts in the + * balance. + */ + virtual ~balance_t() { + TRACE_DTOR(balance_t); } - balance_t& operator-=(const balance_t& bal) { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - *this -= (*i).second; - return *this; + + /** + * 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 amount_t& amt) { - amounts_map::iterator i = amounts.find(&amt.commodity()); - if (i != amounts.end()) { - (*i).second -= amt; - if ((*i).second.realzero()) - amounts.erase(i); - } - else if (! amt.realzero()) { - amounts.insert(amounts_pair(&amt.commodity(), - amt)); - } + + balance_t& operator=(const balance_t& bal) { + if (this != &bal) + amounts = bal.amounts; return *this; } - template - balance_t& operator-=(T val) { - return *this -= amount_t(val); - } + balance_t& operator=(const amount_t& amt) { + if (amt.is_null()) + throw_(balance_error, + "Cannot assign an uninitialized amount to a balance"); - // simple arithmetic - balance_t operator+(const balance_t& bal) const { - balance_t temp = *this; - temp += bal; - return temp; - } - balance_t operator+(const amount_t& amt) const { - balance_t temp = *this; - temp += amt; - return temp; - } - template - balance_t operator+(T val) const { - balance_t temp = *this; - temp += val; - return temp; - } - balance_t operator-(const balance_t& bal) const { - balance_t temp = *this; - temp -= bal; - return temp; - } - balance_t operator-(const amount_t& amt) const { - balance_t temp = *this; - temp -= amt; - return temp; - } - template - balance_t operator-(T val) const { - balance_t temp = *this; - temp -= val; - return temp; - } + amounts.clear(); + if (! amt.is_realzero()) + amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); - // multiplication and divide - balance_t& operator*=(const balance_t& bal); - balance_t& operator*=(const amount_t& amt); - template - balance_t& operator*=(T val) { - return *this *= amount_t(val); + return *this; } - balance_t& operator/=(const balance_t& bal); - balance_t& operator/=(const amount_t& amt); - template - balance_t& operator/=(T val) { - return *this /= amount_t(val); + balance_t& operator=(const 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"); - // multiplication and divide - balance_t operator*(const balance_t& bal) const { - balance_t temp = *this; - temp *= bal; - return temp; - } - balance_t operator*(const amount_t& amt) const { - balance_t temp = *this; - temp *= amt; - return temp; - } - template - balance_t operator*(T val) const { - balance_t temp = *this; - temp *= val; - return temp; - } - balance_t operator/(const balance_t& bal) const { - balance_t temp = *this; - temp /= bal; - return temp; - } - balance_t operator/(const amount_t& amt) const { - balance_t temp = *this; - temp /= amt; - return temp; + if (amt.is_realzero()) + return amounts.empty(); + else + return amounts.size() == 1 && amounts.begin()->second == amt; } + template - balance_t operator/(T val) const { - balance_t temp = *this; - temp /= val; - return temp; + bool operator==(const T& val) const { + return *this == balance_t(val); } - // comparison - bool operator<(const balance_t& bal) const { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - if (! (amount(*(*i).first) < (*i).second)) - return false; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (! ((*i).second < bal.amount(*(*i).first))) - return false; + /** + * 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); - if (bal.amounts.size() == 0 && amounts.size() == 0) - return false; + virtual balance_t& operator*=(const amount_t& amt); - return true; + balance_t& operator*=(const double val) { + return *this *= amount_t(val); } - bool operator<(const amount_t& amt) const { - if (amt.commodity()) - return amount(amt.commodity()) < amt; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if ((*i).second < amt) - return true; - return false; + balance_t& operator*=(const unsigned long val) { + return *this *= amount_t(val); } - template - bool operator<(T val) const { - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if ((*i).second < val) - return true; - return false; + balance_t& operator*=(const long val) { + return *this *= amount_t(val); } - bool operator<=(const balance_t& bal) const { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - if (! (amount(*(*i).first) <= (*i).second)) - return false; + virtual balance_t& operator/=(const amount_t& amt); - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (! ((*i).second <= bal.amount(*(*i).first))) - return false; - - return true; + balance_t& operator/=(const double val) { + return *this /= amount_t(val); } - bool operator<=(const amount_t& amt) const { - if (amt.commodity()) - return amount(amt.commodity()) <= amt; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if ((*i).second <= amt) - return true; - return false; + balance_t& operator/=(const unsigned long val) { + return *this /= amount_t(val); } - template - bool operator<=(T val) const { - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if ((*i).second <= val) - return true; - return false; + balance_t& operator/=(const long val) { + return *this /= amount_t(val); } - bool operator>(const balance_t& bal) const { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - if (! (amount(*(*i).first) > (*i).second)) - return false; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if (! ((*i).second > bal.amount(*(*i).first))) - return false; - - if (bal.amounts.size() == 0 && amounts.size() == 0) - return false; - - return true; + /** + * 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) 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; } - bool operator>(const amount_t& amt) const { - if (amt.commodity()) - return amount(amt.commodity()) > amt; - - for (amounts_map::const_iterator i = amounts.begin(); + virtual balance_t& in_place_negate() { + for (amounts_map::iterator i = amounts.begin(); i != amounts.end(); i++) - if ((*i).second > amt) - return true; - return false; + i->second.in_place_negate(); + return *this; } - template - bool operator>(T val) const { - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if ((*i).second > val) - return true; - return false; + balance_t operator-() const { + return negate(); } - bool operator>=(const balance_t& bal) const { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - if (! (amount(*(*i).first) >= (*i).second)) - return false; - + balance_t abs() const { + balance_t temp; for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - if (! ((*i).second >= bal.amount(*(*i).first))) - return false; - - return true; + temp += i->second.abs(); + return temp; } - bool operator>=(const amount_t& amt) const { - if (amt.commodity()) - return amount(amt.commodity()) >= amt; - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) - if ((*i).second >= amt) - return true; - return false; + balance_t reduce() const { + balance_t temp(*this); + temp.in_place_reduce(); + return temp; } - template - bool operator>=(T val) const { + 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; for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - if ((*i).second >= val) - return true; - return false; + temp += i->second.reduce(); + return *this = temp; } - 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(); + balance_t unreduce() const { + balance_t temp(*this); + temp.in_place_unreduce(); + return temp; } - bool operator==(const amount_t& amt) const { - if (amt.commodity()) - return amounts.size() == 1 && (*amounts.begin()).second == amt; - + 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; for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - if ((*i).second == amt) - return true; - return false; - } - template - bool operator==(T val) const { + temp += i->second.unreduce(); + return *this = temp; + } + + optional value(const optional& 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 { for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - if ((*i).second == val) + if (i->second.is_nonzero()) return true; return false; } - bool operator!=(const balance_t& bal) const { - return ! (*this == bal); - } - bool operator!=(const amount_t& amt) const { - return ! (*this == amt); - } - template - bool operator!=(T val) const { - return ! (*this == val); - } - - // unary negation - void negate() { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second.negate(); - } - balance_t negated() const { - balance_t temp = *this; - temp.negate(); - return temp; - } - balance_t operator-() const { - return negated(); - } + bool is_zero() const { + if (is_empty()) + return true; - // conversion operators - operator amount_t() const; - operator bool() const { for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - if ((*i).second) - return true; - return false; + if (! i->second.is_zero()) + return false; + return true; } - bool realzero() const { - if (amounts.size() == 0) + bool is_realzero() const { + if (is_empty()) return true; + for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - if (! (*i).second.realzero()) + if (! i->second.is_realzero()) return false; return true; } - amount_t amount(const commodity_t& commodity = - *commodity_t::null_commodity) const; - balance_t value(const datetime_t& moment = datetime_t::now) const; - balance_t price() const; - datetime_t date() const; - + 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) 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 + commodity_amount(const optional& 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; - void write(std::ostream& out, const int first_width, + /** + * 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; - void abs() { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second.abs(); - } - - void reduce() { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second.reduce(); - } - - balance_t reduced() const { - balance_t temp(*this); - temp.reduce(); - return temp; - } - - void round() { - for (amounts_map::iterator i = amounts.begin(); + /** + * 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; + for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); - i++) - if ((*i).second.commodity()) - (*i).second = (*i).second.round(); + i++) { + if (first) + first = false; + else + out << ", "; + i->second.print(out); + } + out << ")"; } - balance_t unround() const { - balance_t temp; + virtual bool valid() const { for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - if ((*i).second.commodity()) - temp += (*i).second.unround(); - return temp; + if (! i->second.valid()) + return false; + return true; } }; -inline balance_t abs(const balance_t& bal) { - balance_t temp = bal; - temp.abs(); - return temp; -} - inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) { - bal.write(out, 12); - return out; -} - -class balance_pair_t -{ - public: - balance_t quantity; - balance_t * cost; - - // constructors - balance_pair_t() : cost(NULL) {} - balance_pair_t(const balance_pair_t& bal_pair) - : quantity(bal_pair.quantity), cost(NULL) { - if (bal_pair.cost) - cost = new balance_t(*bal_pair.cost); - } - balance_pair_t(const balance_t& _quantity) - : quantity(_quantity), cost(NULL) {} - balance_pair_t(const amount_t& _quantity) - : quantity(_quantity), cost(NULL) {} - template - balance_pair_t(T value) : quantity(value), cost(NULL) {} - - // destructor - ~balance_pair_t() { - if (cost) delete cost; - } - - // assignment operator - balance_pair_t& operator=(const balance_pair_t& bal_pair) { - if (this != &bal_pair) { - if (cost) { - delete cost; - cost = NULL; - } - quantity = bal_pair.quantity; - if (bal_pair.cost) - cost = new balance_t(*bal_pair.cost); - } - return *this; - } - balance_pair_t& operator=(const balance_t& bal) { - if (cost) { - delete cost; - cost = NULL; - } - quantity = bal; - return *this; - } - balance_pair_t& operator=(const amount_t& amt) { - if (cost) { - delete cost; - cost = NULL; - } - quantity = amt; - return *this; - } - template - balance_pair_t& operator=(T value) { - if (cost) { - delete cost; - cost = NULL; - } - quantity = value; - return *this; - } - - // in-place arithmetic - balance_pair_t& operator+=(const balance_pair_t& bal_pair) { - if (bal_pair.cost && ! cost) - cost = new balance_t(quantity); - quantity += bal_pair.quantity; - if (cost) - *cost += bal_pair.cost ? *bal_pair.cost : bal_pair.quantity; - return *this; - } - balance_pair_t& operator+=(const balance_t& bal) { - quantity += bal; - if (cost) - *cost += bal; - return *this; - } - balance_pair_t& operator+=(const amount_t& amt) { - quantity += amt; - if (cost) - *cost += amt; - return *this; - } - template - balance_pair_t& operator+=(T val) { - return *this += amount_t(val); - } - - balance_pair_t& operator-=(const balance_pair_t& bal_pair) { - if (bal_pair.cost && ! cost) - cost = new balance_t(quantity); - quantity -= bal_pair.quantity; - if (cost) - *cost -= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity; - return *this; - } - balance_pair_t& operator-=(const balance_t& bal) { - quantity -= bal; - if (cost) - *cost -= bal; - return *this; - } - balance_pair_t& operator-=(const amount_t& amt) { - quantity -= amt; - if (cost) - *cost -= amt; - return *this; - } - template - balance_pair_t& operator-=(T val) { - return *this -= amount_t(val); - } - - // simple arithmetic - balance_pair_t operator+(const balance_pair_t& bal_pair) const { - balance_pair_t temp = *this; - temp += bal_pair; - return temp; - } - balance_pair_t operator+(const balance_t& bal) const { - balance_pair_t temp = *this; - temp += bal; - return temp; - } - balance_pair_t operator+(const amount_t& amt) const { - balance_pair_t temp = *this; - temp += amt; - return temp; - } - template - balance_pair_t operator+(T val) const { - balance_pair_t temp = *this; - temp += val; - return temp; - } - - balance_pair_t operator-(const balance_pair_t& bal_pair) const { - balance_pair_t temp = *this; - temp -= bal_pair; - return temp; - } - balance_pair_t operator-(const balance_t& bal) const { - balance_pair_t temp = *this; - temp -= bal; - return temp; - } - balance_pair_t operator-(const amount_t& amt) const { - balance_pair_t temp = *this; - temp -= amt; - return temp; - } - template - balance_pair_t operator-(T val) const { - balance_pair_t temp = *this; - temp -= val; - return temp; - } - - // multiplication and division - balance_pair_t& operator*=(const balance_pair_t& bal_pair) { - if (bal_pair.cost && ! cost) - cost = new balance_t(quantity); - quantity *= bal_pair.quantity; - if (cost) - *cost *= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity; - return *this; - } - balance_pair_t& operator*=(const balance_t& bal) { - quantity *= bal; - if (cost) - *cost *= bal; - return *this; - } - balance_pair_t& operator*=(const amount_t& amt) { - quantity *= amt; - if (cost) - *cost *= amt; - return *this; - } - template - balance_pair_t& operator*=(T val) { - return *this *= amount_t(val); - } - - balance_pair_t& operator/=(const balance_pair_t& bal_pair) { - if (bal_pair.cost && ! cost) - cost = new balance_t(quantity); - quantity /= bal_pair.quantity; - if (cost) - *cost /= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity; - return *this; - } - balance_pair_t& operator/=(const balance_t& bal) { - quantity /= bal; - if (cost) - *cost /= bal; - return *this; - } - balance_pair_t& operator/=(const amount_t& amt) { - quantity /= amt; - if (cost) - *cost /= amt; - return *this; - } - template - balance_pair_t& operator/=(T val) { - return *this /= amount_t(val); - } - - balance_pair_t operator*(const balance_pair_t& bal_pair) const { - balance_pair_t temp = *this; - temp *= bal_pair; - return temp; - } - balance_pair_t operator*(const balance_t& bal) const { - balance_pair_t temp = *this; - temp *= bal; - return temp; - } - balance_pair_t operator*(const amount_t& amt) const { - balance_pair_t temp = *this; - temp *= amt; - return temp; - } - template - balance_pair_t operator*(T val) const { - balance_pair_t temp = *this; - temp *= val; - return temp; - } - - balance_pair_t operator/(const balance_pair_t& bal_pair) const { - balance_pair_t temp = *this; - temp /= bal_pair; - return temp; - } - balance_pair_t operator/(const balance_t& bal) const { - balance_pair_t temp = *this; - temp /= bal; - return temp; - } - balance_pair_t operator/(const amount_t& amt) const { - balance_pair_t temp = *this; - temp /= amt; - return temp; - } - template - balance_pair_t operator/(T val) const { - balance_pair_t temp = *this; - temp /= val; - return temp; - } - - // comparison - bool operator<(const balance_pair_t& bal_pair) const { - return quantity < bal_pair.quantity; - } - bool operator<(const balance_t& bal) const { - return quantity < bal; - } - bool operator<(const amount_t& amt) const { - return quantity < amt; - } - template - bool operator<(T val) const { - return quantity < val; - } - - bool operator<=(const balance_pair_t& bal_pair) const { - return quantity <= bal_pair.quantity; - } - bool operator<=(const balance_t& bal) const { - return quantity <= bal; - } - bool operator<=(const amount_t& amt) const { - return quantity <= amt; - } - template - bool operator<=(T val) const { - return quantity <= val; - } - - bool operator>(const balance_pair_t& bal_pair) const { - return quantity > bal_pair.quantity; - } - bool operator>(const balance_t& bal) const { - return quantity > bal; - } - bool operator>(const amount_t& amt) const { - return quantity > amt; - } - template - bool operator>(T val) const { - return quantity > val; - } - - bool operator>=(const balance_pair_t& bal_pair) const { - return quantity >= bal_pair.quantity; - } - bool operator>=(const balance_t& bal) const { - return quantity >= bal; - } - bool operator>=(const amount_t& amt) const { - return quantity >= amt; - } - template - bool operator>=(T val) const { - return quantity >= val; - } - - bool operator==(const balance_pair_t& bal_pair) const { - return quantity == bal_pair.quantity; - } - bool operator==(const balance_t& bal) const { - return quantity == bal; - } - bool operator==(const amount_t& amt) const { - return quantity == amt; - } - template - bool operator==(T val) const { - return quantity == val; - } - - bool operator!=(const balance_pair_t& bal_pair) const { - return ! (*this == bal_pair); - } - bool operator!=(const balance_t& bal) const { - return ! (*this == bal); - } - bool operator!=(const amount_t& amt) const { - return ! (*this == amt); - } - template - bool operator!=(T val) const { - return ! (*this == val); - } - - // unary negation - void negate() { - quantity.negate(); - if (cost) cost->negate(); - } - balance_pair_t negated() const { - balance_pair_t temp = *this; - temp.negate(); - return temp; - } - balance_pair_t operator-() const { - return negated(); - } - - // test for non-zero (use ! for zero) - operator balance_t() const { - return quantity; - } - operator amount_t() const { - return quantity; - } - operator bool() const { - return quantity; - } - - bool realzero() const { - return ((! cost || cost->realzero()) && quantity.realzero()); - } - - void abs() { - quantity.abs(); - if (cost) cost->abs(); - } - - amount_t amount(const commodity_t& commodity = - *commodity_t::null_commodity) const { - return quantity.amount(commodity); - } - balance_t value(const datetime_t& moment = datetime_t::now) const { - return quantity.value(moment); - } - balance_t price() const { - return quantity.price(); - } - datetime_t date() const { - return quantity.date(); - } - - balance_t - strip_annotations(const bool keep_price = amount_t::keep_price, - const bool keep_date = amount_t::keep_date, - const bool keep_tag = amount_t::keep_tag) const { - return quantity.strip_annotations(keep_price, keep_date, keep_tag); - } - - void write(std::ostream& out, const int first_width, - const int latter_width = -1) const { - quantity.write(out, first_width, latter_width); - } - - balance_pair_t& add(const amount_t& amount, - const amount_t * a_cost = NULL) { - if (a_cost && ! cost) - cost = new balance_t(quantity); - quantity += amount; - if (cost) - *cost += a_cost ? *a_cost : amount; - return *this; - } - - bool valid() { - return quantity.valid() && (! cost || cost->valid()); - } - - void reduce() { - quantity.reduce(); - if (cost) cost->reduce(); - } - - balance_pair_t reduced() const { - balance_pair_t temp(*this); - temp.reduce(); - return temp; - } - - void round() { - quantity.round(); - if (cost) cost->round(); - } - - balance_pair_t unround() { - balance_pair_t temp(quantity.unround()); - if (cost) - temp.cost = new balance_t(cost->unround()); - return temp; - } -}; - -inline balance_pair_t abs(const balance_pair_t& bal_pair) { - balance_pair_t temp; - temp.abs(); - return temp; -} - -inline std::ostream& operator<<(std::ostream& out, - const balance_pair_t& bal_pair) { - bal_pair.quantity.write(out, 12); + bal.print(out, 12); return out; } diff --git a/balpair.h b/balpair.h new file mode 100644 index 00000000..96ccf42a --- /dev/null +++ b/balpair.h @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file 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 > > > > > > > > > > > > > > > > +{ + /** + * 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 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&"); + } + balance_pair_t(const double val) : balance_t(val) { + TRACE_CTOR(balance_pair_t, "const double"); + } + 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 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& 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_pair). + */ + balance_pair_t abs() const { + balance_t temp; + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + temp += i->second.abs(); + + if (cost) { + balance_t cost_temp; + for (amounts_map::const_iterator i = cost->amounts.begin(); + i != cost->amounts.end(); + i++) + cost_temp += i->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; + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + temp += i->second.reduce(); + + if (cost) { + balance_t cost_temp; + for (amounts_map::const_iterator i = cost->amounts.begin(); + i != cost->amounts.end(); + i++) + cost_temp += i->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; + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + temp += i->second.unreduce(); + + if (cost) { + balance_t cost_temp; + for (amounts_map::const_iterator i = cost->amounts.begin(); + i != cost->amounts.end(); + i++) + cost_temp += i->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 + 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. + */ + virtual bool valid() { + if (! balance_t::valid()) + return false; + + if (cost && ! cost->valid()) + return false; + + return true; + } +}; + +} // namespace ledger + +#endif // _BALPAIR_H diff --git a/binary.cc b/binary.cc index 9185f766..52d5f196 100644 --- a/binary.cc +++ b/binary.cc @@ -1,151 +1,71 @@ -#include "journal.h" -#include "valexpr.h" -#include "binary.h" - -#include -#include - -#define TIMELOG_SUPPORT 1 +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils.h" namespace ledger { +namespace binary { -static unsigned long binary_magic_number = 0xFFEED765; -#ifdef DEBUG_ENABLED -static unsigned long format_version = 0x0002060b; -#else -static unsigned long format_version = 0x0002060a; -#endif - -static account_t ** accounts; -static account_t ** accounts_next; -static unsigned int account_index; - -static commodity_base_t ** base_commodities; -static commodity_base_t ** base_commodities_next; -static unsigned int base_commodity_index; - -static commodity_t ** commodities; -static commodity_t ** commodities_next; -static unsigned int commodity_index; - -extern char * bigints; -extern char * bigints_next; -extern unsigned int bigints_index; -extern unsigned int bigints_count; - -template -inline void read_binary_number_nocheck(std::istream& in, T& num) { - in.read((char *)&num, sizeof(num)); -} - -template -inline T read_binary_number_nocheck(std::istream& in) { - T num; - read_binary_number_nocheck(in, num); - return num; -} - -template -inline void read_binary_number_nocheck(char *& data, T& num) { - num = *((T *) data); - data += sizeof(T); -} - -template -inline T read_binary_number_nocheck(char *& data) { - T num; - read_binary_number_nocheck(data, num); - return num; -} - -#if DEBUG_LEVEL >= ALPHA -static void assert_failed() { - assert(0); -} -#define read_binary_guard(in, id) \ - if (read_binary_number_nocheck(in) != id) \ - assert_failed(); -#else -#define read_binary_guard(in, id) -#endif - -template -inline void read_binary_number(std::istream& in, T& num) { - read_binary_guard(in, 0x2003); - in.read((char *)&num, sizeof(num)); - read_binary_guard(in, 0x2004); -} - -inline void read_binary_bool(std::istream& in, bool& num) { - read_binary_guard(in, 0x2005); +void read_bool(std::istream& in, bool& num) +{ + read_guard(in, 0x2005); unsigned char val; - in.read((char *)&val, sizeof(val)); + in.read(reinterpret_cast(&val), sizeof(val)); num = val == 1; - read_binary_guard(in, 0x2006); -} - -template -inline void read_binary_long(std::istream& in, T& num) { - read_binary_guard(in, 0x2001); - - unsigned char len; - read_binary_number_nocheck(in, len); - - num = 0; - unsigned char temp; - if (len > 3) { - read_binary_number_nocheck(in, temp); - num |= ((unsigned long)temp) << 24; - } - if (len > 2) { - read_binary_number_nocheck(in, temp); - num |= ((unsigned long)temp) << 16; - } - if (len > 1) { - read_binary_number_nocheck(in, temp); - num |= ((unsigned long)temp) << 8; - } - - read_binary_number_nocheck(in, temp); - num |= ((unsigned long)temp); - - read_binary_guard(in, 0x2002); -} - -template -inline T read_binary_number(std::istream& in) { - T num; - read_binary_number(in, num); - return num; + read_guard(in, 0x2006); } -inline bool read_binary_bool(std::istream& in) { - bool num; - read_binary_bool(in, num); - return num; -} - -template -inline T read_binary_long(std::istream& in) { - T num; - read_binary_long(in, num); - return num; +void read_bool(const char *& data, bool& num) +{ + read_guard(data, 0x2005); + const unsigned char val = *reinterpret_cast(data); + data += sizeof(unsigned char); + num = val == 1; + read_guard(data, 0x2006); } -inline void read_binary_string(std::istream& in, std::string& str) +void read_string(std::istream& in, string& str) { - read_binary_guard(in, 0x3001); + read_guard(in, 0x3001); unsigned char len; - read_binary_number_nocheck(in, len); + read_number_nocheck(in, len); if (len == 0xff) { unsigned short slen; - read_binary_number_nocheck(in, slen); + read_number_nocheck(in, slen); char * buf = new char[slen + 1]; in.read(buf, slen); buf[slen] = '\0'; str = buf; - delete[] buf; + checked_array_delete(buf); } else if (len) { char buf[256]; @@ -156,1183 +76,82 @@ inline void read_binary_string(std::istream& in, std::string& str) str = ""; } - read_binary_guard(in, 0x3002); -} - -inline std::string read_binary_string(std::istream& in) { - std::string temp; - read_binary_string(in, temp); - return temp; -} - -template -inline void read_binary_number(char *& data, T& num) { - read_binary_guard(data, 0x2003); - num = *((T *) data); - data += sizeof(T); - read_binary_guard(data, 0x2004); + read_guard(in, 0x3002); } -inline void read_binary_bool(char *& data, bool& num) { - read_binary_guard(data, 0x2005); - unsigned char val = *((unsigned char *) data); - data += sizeof(unsigned char); - num = val == 1; - read_binary_guard(data, 0x2006); -} - -template -inline void read_binary_long(char *& data, T& num) { - read_binary_guard(data, 0x2001); - - unsigned char len; - read_binary_number_nocheck(data, len); - - num = 0; - unsigned char temp; - if (len > 3) { - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp) << 24; - } - if (len > 2) { - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp) << 16; - } - if (len > 1) { - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp) << 8; - } - - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp); - - read_binary_guard(data, 0x2002); -} - -template -inline T read_binary_number(char *& data) { - T num; - read_binary_number(data, num); - return num; -} - -inline bool read_binary_bool(char *& data) { - bool num; - read_binary_bool(data, num); - return num; -} - -template -inline T read_binary_long(char *& data) { - T num; - read_binary_long(data, num); - return num; -} - -inline void read_binary_string(char *& data, std::string& str) +void read_string(const char *& data, string& str) { - read_binary_guard(data, 0x3001); + read_guard(data, 0x3001); unsigned char len; - read_binary_number_nocheck(data, len); + read_number_nocheck(data, len); if (len == 0xff) { unsigned short slen; - read_binary_number_nocheck(data, slen); - str = std::string(data, slen); + read_number_nocheck(data, slen); + str = string(data, slen); data += slen; } else if (len) { - str = std::string(data, len); + str = string(data, len); data += len; } else { str = ""; } - read_binary_guard(data, 0x3002); -} - -inline std::string read_binary_string(char *& data) -{ - std::string temp; - read_binary_string(data, temp); - return temp; + read_guard(data, 0x3002); } -inline void read_binary_string(char *& data, std::string * str) +void read_string(const char *& data, string * str) { - read_binary_guard(data, 0x3001); + read_guard(data, 0x3001); unsigned char len; - read_binary_number_nocheck(data, len); + read_number_nocheck(data, len); if (len == 0xff) { unsigned short slen; - read_binary_number_nocheck(data, slen); - new(str) std::string(data, slen); + read_number_nocheck(data, slen); + new(str) string(data, slen); data += slen; } else if (len) { - new(str) std::string(data, len); + new(str) string(data, len); data += len; } else { - new(str) std::string(""); + new(str) string(""); } - read_binary_guard(data, 0x3002); + read_guard(data, 0x3002); } -inline void read_binary_amount(char *& data, amount_t& amt) -{ - commodity_t::ident_t ident; - read_binary_long(data, ident); - if (ident == 0xffffffff) - amt.commodity_ = NULL; - else if (ident == 0) - amt.commodity_ = commodity_t::null_commodity; - else - amt.commodity_ = commodities[ident - 1]; - - amt.read_quantity(data); -} -inline void read_binary_value(char *& data, value_t& val) +void write_bool(std::ostream& out, bool num) { - val.type = static_cast(read_binary_long(data)); - - switch (val.type) { - case value_t::BOOLEAN: - read_binary_bool(data, *((bool *) val.data)); - break; - case value_t::INTEGER: - read_binary_long(data, *((long *) val.data)); - break; - case value_t::DATETIME: - read_binary_number(data, *((datetime_t *) val.data)); - break; - case value_t::AMOUNT: - read_binary_amount(data, *((amount_t *) val.data)); - break; - - case value_t::BALANCE: - case value_t::BALANCE_PAIR: - assert(0); - break; - } -} - -inline void read_binary_mask(char *& data, mask_t *& mask) -{ - bool exclude; - read_binary_number(data, exclude); - std::string pattern; - read_binary_string(data, pattern); - - mask = new mask_t(pattern); - mask->exclude = exclude; -} - -inline void read_binary_value_expr(char *& data, value_expr_t *& expr) -{ - if (! read_binary_bool(data)) { - expr = NULL; - return; - } - - value_expr_t::kind_t kind; - read_binary_number(data, kind); - - expr = new value_expr_t(kind); - - if (kind > value_expr_t::TERMINALS) { - read_binary_value_expr(data, expr->left); - if (expr->left) expr->left->acquire(); - } - - switch (expr->kind) { - case value_expr_t::O_ARG: - case value_expr_t::INDEX: - read_binary_long(data, expr->arg_index); - break; - case value_expr_t::CONSTANT: - expr->value = new value_t; - read_binary_value(data, *expr->value); - break; - - case value_expr_t::F_CODE_MASK: - case value_expr_t::F_PAYEE_MASK: - case value_expr_t::F_NOTE_MASK: - case value_expr_t::F_ACCOUNT_MASK: - case value_expr_t::F_SHORT_ACCOUNT_MASK: - case value_expr_t::F_COMMODITY_MASK: - if (read_binary_bool(data)) - read_binary_mask(data, expr->mask); - break; - - default: - if (kind > value_expr_t::TERMINALS) { - read_binary_value_expr(data, expr->right); - if (expr->right) expr->right->acquire(); - } - break; - } -} - - -inline void read_binary_transaction(char *& data, transaction_t * xact) -{ - read_binary_number(data, xact->_date); - read_binary_number(data, xact->_date_eff); - xact->account = accounts[read_binary_long(data) - 1]; - - unsigned char flag = read_binary_number(data); - if (flag == 0) { - read_binary_amount(data, xact->amount); - } - else if (flag == 1) { - read_binary_amount(data, xact->amount); - read_binary_string(data, xact->amount_expr.expr); - } - else { - value_expr_t * ptr = NULL; - read_binary_value_expr(data, ptr); - assert(ptr); - xact->amount_expr.reset(ptr); - read_binary_string(data, xact->amount_expr.expr); - } - - if (read_binary_bool(data)) { - xact->cost = new amount_t; - read_binary_amount(data, *xact->cost); - read_binary_string(data, xact->cost_expr); - } else { - xact->cost = NULL; - } - - read_binary_number(data, xact->state); - read_binary_number(data, xact->flags); - xact->flags |= TRANSACTION_BULK_ALLOC; - read_binary_string(data, &xact->note); - - xact->beg_pos = read_binary_long(data); - read_binary_long(data, xact->beg_line); - xact->end_pos = read_binary_long(data); - read_binary_long(data, xact->end_line); - - xact->data = NULL; - - if (xact->amount_expr) - compute_amount(xact->amount_expr, xact->amount, xact); -} - -inline void read_binary_entry_base(char *& data, entry_base_t * entry, - transaction_t *& xact_pool, bool& finalize) -{ - read_binary_long(data, entry->src_idx); - entry->beg_pos = read_binary_long(data); - read_binary_long(data, entry->beg_line); - entry->end_pos = read_binary_long(data); - read_binary_long(data, entry->end_line); - - bool ignore_calculated = read_binary_bool(data); - - for (unsigned long i = 0, count = read_binary_long(data); - i < count; - i++) { - new(xact_pool) transaction_t; - read_binary_transaction(data, xact_pool); - if (ignore_calculated && xact_pool->flags & TRANSACTION_CALCULATED) - finalize = true; - entry->add_transaction(xact_pool++); - } -} - -inline void read_binary_entry(char *& data, entry_t * entry, - transaction_t *& xact_pool, bool& finalize) -{ - read_binary_entry_base(data, entry, xact_pool, finalize); - read_binary_number(data, entry->_date); - read_binary_number(data, entry->_date_eff); - read_binary_string(data, &entry->code); - read_binary_string(data, &entry->payee); -} - -inline void read_binary_auto_entry(char *& data, auto_entry_t * entry, - transaction_t *& xact_pool) -{ - bool ignore; - read_binary_entry_base(data, entry, xact_pool, ignore); - value_expr_t * expr; - read_binary_value_expr(data, expr); - // the item_predicate constructor will acquire the reference - entry->predicate = new item_predicate(expr); -} - -inline void read_binary_period_entry(char *& data, period_entry_t * entry, - transaction_t *& xact_pool, bool& finalize) -{ - read_binary_entry_base(data, entry, xact_pool, finalize); - read_binary_string(data, &entry->period_string); - std::istringstream stream(entry->period_string); - entry->period.parse(stream); -} - -inline commodity_base_t * read_binary_commodity_base(char *& data) -{ - commodity_base_t * commodity = new commodity_base_t; - *base_commodities_next++ = commodity; - - read_binary_string(data, commodity->symbol); - read_binary_string(data, commodity->name); - read_binary_string(data, commodity->note); - read_binary_number(data, commodity->precision); - read_binary_number(data, commodity->flags); - - return commodity; -} - -inline void read_binary_commodity_base_extra(char *& data, - commodity_t::ident_t ident) -{ - commodity_base_t * commodity = base_commodities[ident]; - - bool read_history = false; - for (unsigned long i = 0, count = read_binary_long(data); - i < count; - i++) { - datetime_t when; - read_binary_number(data, when); - amount_t amt; - read_binary_amount(data, amt); - - // Upon insertion, amt will be copied, which will cause the amount - // to be duplicated (and thus not lost when the journal's - // item_pool is deleted). - if (! commodity->history) - commodity->history = new commodity_base_t::history_t; - commodity->history->prices.insert(history_pair(when, amt)); - - read_history = true; - } - if (read_history) - read_binary_number(data, commodity->history->last_lookup); - - if (read_binary_bool(data)) { - amount_t amt; - read_binary_amount(data, amt); - commodity->smaller = new amount_t(amt); - } - - if (read_binary_bool(data)) { - amount_t amt; - read_binary_amount(data, amt); - commodity->larger = new amount_t(amt); - } -} - -inline commodity_t * read_binary_commodity(char *& data) -{ - commodity_t * commodity = new commodity_t; - *commodities_next++ = commodity; - - commodity->base = - base_commodities[read_binary_long(data) - 1]; - - read_binary_string(data, commodity->qualified_symbol); - commodity->annotated = false; - - return commodity; -} - -inline commodity_t * read_binary_commodity_annotated(char *& data) -{ - annotated_commodity_t * commodity = new annotated_commodity_t; - *commodities_next++ = commodity; - - commodity->base = - base_commodities[read_binary_long(data) - 1]; - - read_binary_string(data, commodity->qualified_symbol); - commodity->annotated = true; - - commodity->ptr = - commodities[read_binary_long(data) - 1]; - - // This read-and-then-assign causes a new amount to be allocated - // which does not live within the bulk allocation pool, since that - // pool will be deleted *before* the commodities are destroyed. - amount_t amt; - read_binary_amount(data, amt); - commodity->price = amt; - - read_binary_number(data, commodity->date); - read_binary_string(data, commodity->tag); - - return commodity; -} - -inline -account_t * read_binary_account(char *& data, journal_t * journal, - account_t * master = NULL) -{ - account_t * acct = new account_t(NULL); - *accounts_next++ = acct; - - acct->journal = journal; - - account_t::ident_t id; - read_binary_long(data, id); // parent id - if (id == 0xffffffff) - acct->parent = NULL; - else - acct->parent = accounts[id - 1]; - - read_binary_string(data, acct->name); - read_binary_string(data, acct->note); - read_binary_number(data, acct->depth); - - // If all of the subaccounts will be added to a different master - // account, throw away what we've learned about the recorded - // journal's own master account. - - if (master && acct != master) { - delete acct; - acct = master; - } - - for (account_t::ident_t i = 0, - count = read_binary_long(data); - i < count; - i++) { - account_t * child = read_binary_account(data, journal); - child->parent = acct; - assert(acct != child); - acct->add_account(child); - } - - return acct; -} - -unsigned int read_binary_journal(std::istream& in, - const std::string& file, - journal_t * journal, - account_t * master) -{ - account_index = - base_commodity_index = - commodity_index = 0; - - // Read in the files that participated in this journal, so that they - // can be checked for changes on reading. - - if (! file.empty()) { - for (unsigned short i = 0, - count = read_binary_number(in); - i < count; - i++) { - std::string path = read_binary_string(in); - std::time_t old_mtime; - read_binary_number(in, old_mtime); - struct stat info; - stat(path.c_str(), &info); - if (std::difftime(info.st_mtime, old_mtime) > 0) - return 0; - - journal->sources.push_back(path); - } - - // Make sure that the cache uses the same price database, - // otherwise it means that LEDGER_PRICE_DB has been changed, and - // we should ignore this cache file. - if (read_binary_string(in) != journal->price_db) - return 0; - } - - // Read all of the data in at once, so that we're just dealing with - // a big data buffer. - - unsigned long data_size = read_binary_number(in); - - char * data_pool = new char[data_size]; - char * data = data_pool; - in.read(data, data_size); - - // Read in the accounts - - account_t::ident_t a_count = read_binary_long(data); - accounts = accounts_next = new account_t *[a_count]; - - assert(journal->master); - delete journal->master; - journal->master = read_binary_account(data, journal, master); - - if (read_binary_bool(data)) - journal->basket = accounts[read_binary_long(data) - 1]; - - // Allocate the memory needed for the entries and transactions in - // one large block, which is then chopped up and custom constructed - // as necessary. - - unsigned long count = read_binary_long(data); - unsigned long auto_count = read_binary_long(data); - unsigned long period_count = read_binary_long(data); - unsigned long xact_count = read_binary_number(data); - unsigned long bigint_count = read_binary_number(data); - - std::size_t pool_size = (sizeof(entry_t) * count + - sizeof(transaction_t) * xact_count + - sizeof_bigint_t() * bigint_count); - - char * item_pool = new char[pool_size]; - - journal->item_pool = item_pool; - journal->item_pool_end = item_pool + pool_size; - - entry_t * entry_pool = (entry_t *) item_pool; - transaction_t * xact_pool = (transaction_t *) (item_pool + - sizeof(entry_t) * count); - bigints_index = 0; - bigints = bigints_next = (item_pool + sizeof(entry_t) * count + - sizeof(transaction_t) * xact_count); - - // Read in the base commodities and then derived commodities - - commodity_base_t::ident_t bc_count = - read_binary_long(data); - base_commodities = base_commodities_next = new commodity_base_t *[bc_count]; - - for (commodity_base_t::ident_t i = 0; i < bc_count; i++) { - commodity_base_t * commodity = read_binary_commodity_base(data); - - std::pair result = - commodity_base_t::commodities.insert - (base_commodities_pair(commodity->symbol, commodity)); - if (! result.second) { - base_commodities_map::iterator c = - commodity_base_t::commodities.find(commodity->symbol); - - // It's possible the user might have used a commodity in a value - // expression passed to an option, we'll just override the - // flags, but keep the commodity pointer intact. - if (c == commodity_base_t::commodities.end()) - throw new error(std::string("Failed to read base commodity from cache: ") + - commodity->symbol); - - (*c).second->name = commodity->name; - (*c).second->note = commodity->note; - (*c).second->precision = commodity->precision; - (*c).second->flags = commodity->flags; - if ((*c).second->smaller) - delete (*c).second->smaller; - (*c).second->smaller = commodity->smaller; - if ((*c).second->larger) - delete (*c).second->larger; - (*c).second->larger = commodity->larger; - - *(base_commodities_next - 1) = (*c).second; - delete commodity; - } - } - - commodity_t::ident_t c_count = read_binary_long(data); - commodities = commodities_next = new commodity_t *[c_count]; - - for (commodity_t::ident_t i = 0; i < c_count; i++) { - commodity_t * commodity; - std::string mapping_key; - - if (! read_binary_bool(data)) { - commodity = read_binary_commodity(data); - mapping_key = commodity->base->symbol; - } else { - read_binary_string(data, mapping_key); - commodity = read_binary_commodity_annotated(data); - } - - std::pair result = - commodity_t::commodities.insert(commodities_pair - (mapping_key, commodity)); - if (! result.second) { - commodities_map::iterator c = - commodity_t::commodities.find(mapping_key); - if (c == commodity_t::commodities.end()) - throw new error(std::string("Failed to read commodity from cache: ") + - commodity->symbol()); - - *(commodities_next - 1) = (*c).second; - delete commodity; - } - } - - for (commodity_base_t::ident_t i = 0; i < bc_count; i++) - read_binary_commodity_base_extra(data, i); - - commodity_t::ident_t ident; - read_binary_long(data, ident); - if (ident == 0xffffffff || ident == 0) - commodity_t::default_commodity = NULL; - else - commodity_t::default_commodity = commodities[ident - 1]; - - // Read in the entries and transactions - - for (unsigned long i = 0; i < count; i++) { - new(entry_pool) entry_t; - bool finalize = false; - read_binary_entry(data, entry_pool, xact_pool, finalize); - entry_pool->journal = journal; - if (finalize && ! entry_pool->finalize()) - continue; - journal->entries.push_back(entry_pool++); - } - - for (unsigned long i = 0; i < auto_count; i++) { - auto_entry_t * auto_entry = new auto_entry_t; - read_binary_auto_entry(data, auto_entry, xact_pool); - auto_entry->journal = journal; - journal->auto_entries.push_back(auto_entry); - } - - for (unsigned long i = 0; i < period_count; i++) { - period_entry_t * period_entry = new period_entry_t; - bool finalize = false; - read_binary_period_entry(data, period_entry, xact_pool, finalize); - period_entry->journal = journal; - if (finalize && ! period_entry->finalize()) - continue; - journal->period_entries.push_back(period_entry); - } - - // Clean up and return the number of entries read - - delete[] accounts; - delete[] commodities; - delete[] data_pool; - - VALIDATE(journal->valid()); - - return count; -} - -bool binary_parser_t::test(std::istream& in) const -{ - if (read_binary_number_nocheck(in) == binary_magic_number && - read_binary_number_nocheck(in) == format_version) - return true; - - in.clear(); - in.seekg(0, std::ios::beg); - return false; -} - -unsigned int binary_parser_t::parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) -{ - return read_binary_journal(in, original_file ? *original_file : "", - journal, master); -} - -template -inline void write_binary_number_nocheck(std::ostream& out, T num) { - out.write((char *)&num, sizeof(num)); -} - -#if DEBUG_LEVEL >= ALPHA -#define write_binary_guard(out, id) \ - write_binary_number_nocheck(out, id) -#else -#define write_binary_guard(in, id) -#endif - -template -inline void write_binary_number(std::ostream& out, T num) { - write_binary_guard(out, 0x2003); - out.write((char *)&num, sizeof(num)); - write_binary_guard(out, 0x2004); -} - -inline void write_binary_bool(std::ostream& out, bool num) { - write_binary_guard(out, 0x2005); + write_guard(out, 0x2005); unsigned char val = num ? 1 : 0; - out.write((char *)&val, sizeof(val)); - write_binary_guard(out, 0x2006); -} - -template -inline void write_binary_long(std::ostream& out, T num) { - write_binary_guard(out, 0x2001); - - unsigned char len = 4; - if (((unsigned long)num) < 0x00000100UL) - len = 1; - else if (((unsigned long)num) < 0x00010000UL) - len = 2; - else if (((unsigned long)num) < 0x01000000UL) - len = 3; - write_binary_number_nocheck(out, len); - - unsigned char temp; - if (len > 3) { - temp = (((unsigned long)num) & 0xFF000000UL) >> 24; - write_binary_number_nocheck(out, temp); - } - if (len > 2) { - temp = (((unsigned long)num) & 0x00FF0000UL) >> 16; - write_binary_number_nocheck(out, temp); - } - if (len > 1) { - temp = (((unsigned long)num) & 0x0000FF00UL) >> 8; - write_binary_number_nocheck(out, temp); - } - - temp = (((unsigned long)num) & 0x000000FFUL); - write_binary_number_nocheck(out, temp); - - write_binary_guard(out, 0x2002); + out.write(reinterpret_cast(&val), sizeof(val)); + write_guard(out, 0x2006); } -inline void write_binary_string(std::ostream& out, const std::string& str) +void write_string(std::ostream& out, const string& str) { - write_binary_guard(out, 0x3001); + write_guard(out, 0x3001); unsigned long len = str.length(); if (len > 255) { assert(len < 65536); - write_binary_number_nocheck(out, 0xff); - write_binary_number_nocheck(out, len); + write_number_nocheck(out, 0xff); + write_number_nocheck(out, len); } else { - write_binary_number_nocheck(out, len); + write_number_nocheck(out, len); } if (len) out.write(str.c_str(), len); - write_binary_guard(out, 0x3002); -} - -void write_binary_amount(std::ostream& out, const amount_t& amt) -{ - if (amt.commodity_) - write_binary_long(out, amt.commodity_->ident); - else - write_binary_long(out, 0xffffffff); - - amt.write_quantity(out); -} - -void write_binary_value(std::ostream& out, const value_t& val) -{ - write_binary_long(out, (int)val.type); - - switch (val.type) { - case value_t::BOOLEAN: - write_binary_bool(out, *((bool *) val.data)); - break; - case value_t::INTEGER: - write_binary_long(out, *((long *) val.data)); - break; - case value_t::DATETIME: - write_binary_number(out, *((datetime_t *) val.data)); - break; - case value_t::AMOUNT: - write_binary_amount(out, *((amount_t *) val.data)); - break; - - case value_t::BALANCE: - case value_t::BALANCE_PAIR: - throw new error("Cannot write a balance to the binary cache"); - } -} - -void write_binary_mask(std::ostream& out, mask_t * mask) -{ - write_binary_number(out, mask->exclude); - write_binary_string(out, mask->pattern); -} - -void write_binary_value_expr(std::ostream& out, const value_expr_t * expr) -{ - if (! expr) { - write_binary_bool(out, false); - return; - } - write_binary_bool(out, true); - write_binary_number(out, expr->kind); - - if (expr->kind > value_expr_t::TERMINALS) - write_binary_value_expr(out, expr->left); - - switch (expr->kind) { - case value_expr_t::O_ARG: - case value_expr_t::INDEX: - write_binary_long(out, expr->arg_index); - break; - case value_expr_t::CONSTANT: - write_binary_value(out, *expr->value); - break; - - case value_expr_t::F_CODE_MASK: - case value_expr_t::F_PAYEE_MASK: - case value_expr_t::F_NOTE_MASK: - case value_expr_t::F_ACCOUNT_MASK: - case value_expr_t::F_SHORT_ACCOUNT_MASK: - case value_expr_t::F_COMMODITY_MASK: - if (expr->mask) { - write_binary_bool(out, true); - write_binary_mask(out, expr->mask); - } else { - write_binary_bool(out, false); - } - break; - - default: - if (expr->kind > value_expr_t::TERMINALS) - write_binary_value_expr(out, expr->right); - break; - } - -} - -void write_binary_transaction(std::ostream& out, transaction_t * xact, - bool ignore_calculated) -{ - write_binary_number(out, xact->_date); - write_binary_number(out, xact->_date_eff); - write_binary_long(out, xact->account->ident); - - if (ignore_calculated && xact->flags & TRANSACTION_CALCULATED) { - write_binary_number(out, 0); - write_binary_amount(out, amount_t()); - } - else if (xact->amount_expr) { - write_binary_number(out, 2); - write_binary_value_expr(out, xact->amount_expr.get()); - write_binary_string(out, xact->amount_expr.expr); - } - else if (! xact->amount_expr.expr.empty()) { - write_binary_number(out, 1); - write_binary_amount(out, xact->amount); - write_binary_string(out, xact->amount_expr.expr); - } - else { - write_binary_number(out, 0); - write_binary_amount(out, xact->amount); - } - - if (xact->cost && - (! (ignore_calculated && xact->flags & TRANSACTION_CALCULATED))) { - write_binary_bool(out, true); - write_binary_amount(out, *xact->cost); - write_binary_string(out, xact->cost_expr); - } else { - write_binary_bool(out, false); - } - - write_binary_number(out, xact->state); - write_binary_number(out, xact->flags); - write_binary_string(out, xact->note); - - write_binary_long(out, xact->beg_pos); - write_binary_long(out, xact->beg_line); - write_binary_long(out, xact->end_pos); - write_binary_long(out, xact->end_line); -} - -void write_binary_entry_base(std::ostream& out, entry_base_t * entry) -{ - write_binary_long(out, entry->src_idx); - write_binary_long(out, entry->beg_pos); - write_binary_long(out, entry->beg_line); - write_binary_long(out, entry->end_pos); - write_binary_long(out, entry->end_line); - - bool ignore_calculated = false; - for (transactions_list::const_iterator i = entry->transactions.begin(); - i != entry->transactions.end(); - i++) - if ((*i)->amount_expr) { - ignore_calculated = true; - break; - } - - write_binary_bool(out, ignore_calculated); - - write_binary_long(out, entry->transactions.size()); - for (transactions_list::const_iterator i = entry->transactions.begin(); - i != entry->transactions.end(); - i++) - write_binary_transaction(out, *i, ignore_calculated); -} - -void write_binary_entry(std::ostream& out, entry_t * entry) -{ - write_binary_entry_base(out, entry); - write_binary_number(out, entry->_date); - write_binary_number(out, entry->_date_eff); - write_binary_string(out, entry->code); - write_binary_string(out, entry->payee); -} - -void write_binary_auto_entry(std::ostream& out, auto_entry_t * entry) -{ - write_binary_entry_base(out, entry); - write_binary_value_expr(out, entry->predicate->predicate); -} - -void write_binary_period_entry(std::ostream& out, period_entry_t * entry) -{ - write_binary_entry_base(out, entry); - write_binary_string(out, entry->period_string); -} - -void write_binary_commodity_base(std::ostream& out, commodity_base_t * commodity) -{ - commodity->ident = ++base_commodity_index; - - write_binary_string(out, commodity->symbol); - write_binary_string(out, commodity->name); - write_binary_string(out, commodity->note); - write_binary_number(out, commodity->precision); - write_binary_number(out, commodity->flags); -} - -void write_binary_commodity_base_extra(std::ostream& out, - commodity_base_t * commodity) -{ - if (commodity->history && commodity->history->bogus_time) - commodity->remove_price(commodity->history->bogus_time); - - if (! commodity->history) { - write_binary_long(out, 0); - } else { - write_binary_long(out, commodity->history->prices.size()); - for (history_map::const_iterator i = commodity->history->prices.begin(); - i != commodity->history->prices.end(); - i++) { - write_binary_number(out, (*i).first); - write_binary_amount(out, (*i).second); - } - write_binary_number(out, commodity->history->last_lookup); - } - - if (commodity->smaller) { - write_binary_bool(out, true); - write_binary_amount(out, *commodity->smaller); - } else { - write_binary_bool(out, false); - } - - if (commodity->larger) { - write_binary_bool(out, true); - write_binary_amount(out, *commodity->larger); - } else { - write_binary_bool(out, false); - } -} - -void write_binary_commodity(std::ostream& out, commodity_t * commodity) -{ - commodity->ident = ++commodity_index; - - write_binary_long(out, commodity->base->ident); - write_binary_string(out, commodity->qualified_symbol); -} - -void write_binary_commodity_annotated(std::ostream& out, - commodity_t * commodity) -{ - commodity->ident = ++commodity_index; - - write_binary_long(out, commodity->base->ident); - write_binary_string(out, commodity->qualified_symbol); - - annotated_commodity_t * ann_comm = - static_cast(commodity); - - write_binary_long(out, ann_comm->base->ident); - write_binary_amount(out, ann_comm->price); - write_binary_number(out, ann_comm->date); - write_binary_string(out, ann_comm->tag); -} - -static inline account_t::ident_t count_accounts(account_t * account) -{ - account_t::ident_t count = 1; - - for (accounts_map::iterator i = account->accounts.begin(); - i != account->accounts.end(); - i++) - count += count_accounts((*i).second); - - return count; -} - -void write_binary_account(std::ostream& out, account_t * account) -{ - account->ident = ++account_index; - - if (account->parent) - write_binary_long(out, account->parent->ident); - else - write_binary_long(out, 0xffffffff); - - write_binary_string(out, account->name); - write_binary_string(out, account->note); - write_binary_number(out, account->depth); - - write_binary_long(out, account->accounts.size()); - for (accounts_map::iterator i = account->accounts.begin(); - i != account->accounts.end(); - i++) - write_binary_account(out, (*i).second); -} - -void write_binary_journal(std::ostream& out, journal_t * journal) -{ - account_index = - base_commodity_index = - commodity_index = 0; - - write_binary_number_nocheck(out, binary_magic_number); - write_binary_number_nocheck(out, format_version); - - // Write out the files that participated in this journal, so that - // they can be checked for changes on reading. - - if (journal->sources.empty()) { - write_binary_number(out, 0); - } else { - write_binary_number(out, journal->sources.size()); - for (strings_list::const_iterator i = journal->sources.begin(); - i != journal->sources.end(); - i++) { - write_binary_string(out, *i); - struct stat info; - stat((*i).c_str(), &info); - write_binary_number(out, std::time_t(info.st_mtime)); - } - - // Write out the price database that relates to this data file, so - // that if it ever changes the cache can be invalidated. - write_binary_string(out, journal->price_db); - } - - ostream_pos_type data_val = out.tellp(); - write_binary_number(out, 0); - - // Write out the accounts - - write_binary_long(out, count_accounts(journal->master)); - write_binary_account(out, journal->master); - - if (journal->basket) { - write_binary_bool(out, true); - write_binary_long(out, journal->basket->ident); - } else { - write_binary_bool(out, false); - } - - // Write out the number of entries, transactions, and amounts - - write_binary_long(out, journal->entries.size()); - write_binary_long(out, journal->auto_entries.size()); - write_binary_long(out, journal->period_entries.size()); - - ostream_pos_type xacts_val = out.tellp(); - write_binary_number(out, 0); - - ostream_pos_type bigints_val = out.tellp(); - write_binary_number(out, 0); - - bigints_count = 0; - - // Write out the commodities - - write_binary_long - (out, commodity_base_t::commodities.size()); - - for (base_commodities_map::const_iterator i = - commodity_base_t::commodities.begin(); - i != commodity_base_t::commodities.end(); - i++) - write_binary_commodity_base(out, (*i).second); - - write_binary_long - (out, commodity_t::commodities.size()); - - for (commodities_map::const_iterator i = commodity_t::commodities.begin(); - i != commodity_t::commodities.end(); - i++) { - if (! (*i).second->annotated) { - write_binary_bool(out, false); - write_binary_commodity(out, (*i).second); - } - } - - for (commodities_map::const_iterator i = commodity_t::commodities.begin(); - i != commodity_t::commodities.end(); - i++) { - if ((*i).second->annotated) { - write_binary_bool(out, true); - write_binary_string(out, (*i).first); // the mapping key - write_binary_commodity_annotated(out, (*i).second); - } - } - - // Write out the history and smaller/larger convertible links after - // both the base and the main commodities have been written, since - // the amounts in both will refer to the mains. - - for (base_commodities_map::const_iterator i = - commodity_base_t::commodities.begin(); - i != commodity_base_t::commodities.end(); - i++) - write_binary_commodity_base_extra(out, (*i).second); - - if (commodity_t::default_commodity) - write_binary_long(out, commodity_t::default_commodity->ident); - else - write_binary_long(out, 0xffffffff); - - // Write out the entries and transactions - - unsigned long xact_count = 0; - - for (entries_list::const_iterator i = journal->entries.begin(); - i != journal->entries.end(); - i++) { - write_binary_entry(out, *i); - xact_count += (*i)->transactions.size(); - } - - for (auto_entries_list::const_iterator i = journal->auto_entries.begin(); - i != journal->auto_entries.end(); - i++) { - write_binary_auto_entry(out, *i); - xact_count += (*i)->transactions.size(); - } - - for (period_entries_list::const_iterator i = journal->period_entries.begin(); - i != journal->period_entries.end(); - i++) { - write_binary_period_entry(out, *i); - xact_count += (*i)->transactions.size(); - } - - // Back-patch the count for amounts - - unsigned long data_size = (((unsigned long) out.tellp()) - - ((unsigned long) data_val) - - sizeof(unsigned long)); - out.seekp(data_val); - write_binary_number(out, data_size); - out.seekp(xacts_val); - write_binary_number(out, xact_count); - out.seekp(bigints_val); - write_binary_number(out, bigints_count); + write_guard(out, 0x3002); } +} // namespace binary } // namespace ledger diff --git a/binary.h b/binary.h index ca3254c9..864c6ea2 100644 --- a/binary.h +++ b/binary.h @@ -1,26 +1,269 @@ -#ifndef _BINARY_H -#define _BINARY_H +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ -#include "journal.h" -#include "parser.h" +#ifndef BINARY_H +#define BINARY_H namespace ledger { +namespace binary { -class binary_parser_t : public parser_t +template +inline void read_number_nocheck(std::istream& in, T& num) { + in.read(reinterpret_cast(&num), sizeof(num)); +} + +template +inline void read_number_nocheck(const char *& data, T& num) { + num = *reinterpret_cast(data); + data += sizeof(T); +} + +template +inline T read_number_nocheck(std::istream& in) { + T num; + read_number_nocheck(in, num); + return num; +} + +template +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(in) != id) \ + assert(false); +#else +#define read_guard(in, id) +#endif + +template +inline void read_number(std::istream& in, T& num) { + read_guard(in, 0x2003); + in.read(reinterpret_cast(&num), sizeof(num)); + read_guard(in, 0x2004); +} + +template +inline void read_number(const char *& data, T& num) { + read_guard(data, 0x2003); + num = *reinterpret_cast(data); + data += sizeof(T); + read_guard(data, 0x2004); +} + +template +inline T read_number(std::istream& in) { + T num; + read_number(in, num); + return num; +} + +template +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 +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(temp) << 24; + } + if (len > 2) { + read_number_nocheck(in, temp); + num |= static_cast(temp) << 16; + } + if (len > 1) { + read_number_nocheck(in, temp); + num |= static_cast(temp) << 8; + } + + read_number_nocheck(in, temp); + num |= static_cast(temp); + + read_guard(in, 0x2002); +} + +template +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(temp) << 24; + } + if (len > 2) { + read_number_nocheck(data, temp); + num |= static_cast(temp) << 16; + } + if (len > 1) { + read_number_nocheck(data, temp); + num |= static_cast(temp) << 8; + } + + read_number_nocheck(data, temp); + num |= static_cast(temp); + + read_guard(data, 0x2002); +} + +template +inline T read_long(std::istream& in) { + T num; + read_long(in, num); + return num; +} + +template +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; +} + + +template +inline void write_number_nocheck(std::ostream& out, T num) { + out.write(reinterpret_cast(&num), sizeof(num)); +} + +#if DEBUG_LEVEL >= ALPHA +#define write_guard(out, id) \ + write_number_nocheck(out, id) +#else +#define write_guard(in, id) +#endif + +template +inline void write_number(std::ostream& out, T num) { + write_guard(out, 0x2003); + out.write(reinterpret_cast(&num), sizeof(num)); + write_guard(out, 0x2004); +} + +void write_bool(std::ostream& out, bool num); + +template +void write_long(std::ostream& out, T num) { - public: - virtual bool test(std::istream& in) const; + write_guard(out, 0x2001); + + unsigned char len = 4; + if (static_cast(num) < 0x00000100UL) + len = 1; + else if (static_cast(num) < 0x00010000UL) + len = 2; + else if (static_cast(num) < 0x01000000UL) + len = 3; + write_number_nocheck(out, len); + + unsigned char temp; + if (len > 3) { + temp = (static_cast(num) & 0xFF000000UL) >> 24; + write_number_nocheck(out, temp); + } + if (len > 2) { + temp = (static_cast(num) & 0x00FF0000UL) >> 16; + write_number_nocheck(out, temp); + } + if (len > 1) { + temp = (static_cast(num) & 0x0000FF00UL) >> 8; + write_number_nocheck(out, temp); + } + + temp = (static_cast(num) & 0x000000FFUL); + write_number_nocheck(out, temp); + + write_guard(out, 0x2002); +} - virtual unsigned int parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); -}; +void write_string(std::ostream& out, const string& str); -void write_binary_journal(std::ostream& out, - journal_t * journal); +template +inline void write_object(std::ostream& out, const T& journal) { + assert(false); +} +} // namespace binary } // namespace ledger -#endif // _BINARY_H +#endif // BINARY_H diff --git a/commodity.cc b/commodity.cc new file mode 100644 index 00000000..76614f92 --- /dev/null +++ b/commodity.cc @@ -0,0 +1,598 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file commodity.cc + * @author John Wiegley + * @date Thu Apr 26 15:19:46 2007 + * + * @brief Types for dealing with commodities. + * + * This file defines member functions for flavors of commodity_t. + */ + +#include "amount.h" +#include "parser.h" // for parsing utility functions + +namespace ledger { + +void commodity_t::add_price(const moment_t& date, + const amount_t& price) +{ + if (! base->history) + base->history = history_t(); + + history_map::iterator i = base->history->prices.find(date); + if (i != base->history->prices.end()) { + (*i).second = price; + } else { + std::pair result + = base->history->prices.insert(history_map::value_type(date, price)); + assert(result.second); + } +} + +bool commodity_t::remove_price(const moment_t& date) +{ + if (base->history) { + history_map::size_type n = base->history->prices.erase(date); + if (n > 0) { + if (base->history->prices.empty()) + base->history.reset(); + return true; + } + } + return false; +} + +optional commodity_t::value(const optional& moment) +{ + optional age; + optional price; + + if (base->history) { + assert(base->history->prices.size() > 0); + + if (! moment) { + history_map::reverse_iterator r = base->history->prices.rbegin(); + age = (*r).first; + price = (*r).second; + } else { + history_map::iterator i = base->history->prices.lower_bound(*moment); + if (i == base->history->prices.end()) { + history_map::reverse_iterator r = base->history->prices.rbegin(); + age = (*r).first; + price = (*r).second; + } else { + age = (*i).first; + if (*moment != *age) { + if (i != base->history->prices.begin()) { + --i; + age = (*i).first; + price = (*i).second; + } else { + age = none; + } + } else { + price = (*i).second; + } + } + } + } + + if (! has_flags(COMMODITY_STYLE_NOMARKET) && parent().get_quote) { + if (optional quote = parent().get_quote + (*this, age, moment, + (base->history && base->history->prices.size() > 0 ? + (*base->history->prices.rbegin()).first : optional()))) + return *quote; + } + return price; +} + +commodity_t::operator bool() const +{ + return this != parent().null_commodity; +} + +bool commodity_t::symbol_needs_quotes(const string& symbol) +{ + for (const char * p = symbol.c_str(); *p; p++) + if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.') + return true; + + return false; +} + +void commodity_t::parse_symbol(std::istream& in, string& symbol) +{ + // Invalid commodity characters: + // SPACE, TAB, NEWLINE, RETURN + // 0-9 . , ; - + * / ^ ? : & | ! = + // < > { } [ ] ( ) @ + + static int invalid_chars[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + /* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, + /* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, + /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + char buf[256]; + char c = peek_next_nonws(in); + if (c == '"') { + in.get(c); + READ_INTO(in, buf, 255, c, c != '"'); + if (c == '"') + in.get(c); + else + throw_(amount_error, "Quoted commodity symbol lacks closing quote"); + } else { + READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]); + } + symbol = buf; +} + +void commodity_t::parse_symbol(char *& p, string& symbol) +{ + if (*p == '"') { + char * q = std::strchr(p + 1, '"'); + if (! q) + throw_(parse_error, "Quoted commodity symbol lacks closing quote"); + symbol = string(p + 1, 0, q - p - 1); + p = q + 2; + } else { + char * q = next_element(p); + symbol = p; + if (q) + p = q; + else + p += symbol.length(); + } + if (symbol.empty()) + throw_(parse_error, "Failed to parse commodity"); +} + +bool commodity_t::valid() const +{ + if (symbol().empty() && this != parent().null_commodity) { + DEBUG("ledger.validate", + "commodity_t: symbol().empty() && this != null_commodity"); + return false; + } + + if (annotated && ! base) { + DEBUG("ledger.validate", "commodity_t: annotated && ! base"); + return false; + } + + if (precision() > 16) { + DEBUG("ledger.validate", "commodity_t: precision() > 16"); + return false; + } + + return true; +} + +void annotation_t::parse(std::istream& in) +{ + do { + char buf[256]; + char c = peek_next_nonws(in); + if (c == '{') { + if (price) + throw_(amount_error, "Commodity specifies more than one price"); + + in.get(c); + READ_INTO(in, buf, 255, c, c != '}'); + if (c == '}') + in.get(c); + else + throw_(amount_error, "Commodity price lacks closing brace"); + + amount_t temp; + temp.parse(buf, AMOUNT_PARSE_NO_MIGRATE); + temp.in_place_reduce(); + + // Since this price will maintain its own precision, make sure + // it is at least as large as the base commodity, since the user + // may have only specified {$1} or something similar. + + if (temp.has_commodity() && + temp.precision() < temp.commodity().precision()) + temp = temp.round(); // no need to retain individual precision + + price = temp; + } + else if (c == '[') { + if (date) + throw_(amount_error, "Commodity specifies more than one date"); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ']'); + if (c == ']') + in.get(c); + else + throw_(amount_error, "Commodity date lacks closing bracket"); + + date = parse_datetime(buf); + } + else if (c == '(') { + if (tag) + throw_(amount_error, "Commodity specifies more than one tag"); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') + in.get(c); + else + throw_(amount_error, "Commodity tag lacks closing parenthesis"); + + tag = buf; + } + else { + break; + } + } while (true); + + DEBUG("amounts.commodities", + "Parsed commodity annotations: " << std::endl << *this); +} + +bool annotated_commodity_t::operator==(const commodity_t& comm) const +{ + // If the base commodities don't match, the game's up. + if (base != comm.base) + return false; + + assert(annotated); + if (! comm.annotated) + return false; + + if (details != as_annotated_commodity(comm).details) + return false; + + return true; +} + +commodity_t& +annotated_commodity_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) +{ + DEBUG("commodity.annotated.strip", + "Reducing commodity " << *this << std::endl + << " keep price " << _keep_price << " " + << " keep date " << _keep_date << " " + << " keep tag " << _keep_tag); + + commodity_t * new_comm; + + if ((_keep_price && details.price) || + (_keep_date && details.date) || + (_keep_tag && details.tag)) + { + new_comm = parent().find_or_create + (referent(), + annotation_t(_keep_price ? details.price : none, + _keep_date ? details.date : none, + _keep_tag ? details.tag : none)); + } else { + new_comm = parent().find_or_create(base_symbol()); + } + + assert(new_comm); + return *new_comm; +} + +void annotated_commodity_t::write_annotations(std::ostream& out, + const annotation_t& info) +{ + if (info.price) + out << " {" << *info.price << '}'; + + if (info.date) + out << " [" << *info.date << ']'; + + if (info.tag) + out << " (" << *info.tag << ')'; +} + +bool compare_amount_commodities::operator()(const amount_t * left, + const amount_t * right) const +{ + commodity_t& leftcomm(left->commodity()); + commodity_t& rightcomm(right->commodity()); + + int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol()); + if (cmp != 0) + return cmp < 0; + + if (! leftcomm.annotated) { + assert(rightcomm.annotated); + return true; + } + else if (! rightcomm.annotated) { + assert(leftcomm.annotated); + return false; + } + else { + annotated_commodity_t& aleftcomm(static_cast(leftcomm)); + annotated_commodity_t& arightcomm(static_cast(rightcomm)); + + if (! aleftcomm.details.price && arightcomm.details.price) + return true; + if (aleftcomm.details.price && ! arightcomm.details.price) + return false; + + if (aleftcomm.details.price && arightcomm.details.price) { + amount_t leftprice(*aleftcomm.details.price); + leftprice.in_place_reduce(); + amount_t rightprice(*arightcomm.details.price); + rightprice.in_place_reduce(); + + if (leftprice.commodity() == rightprice.commodity()) { + return (leftprice - rightprice).sign() < 0; + } else { + // Since we have two different amounts, there's really no way + // to establish a true sorting order; we'll just do it based + // on the numerical values. + leftprice.clear_commodity(); + rightprice.clear_commodity(); + return (leftprice - rightprice).sign() < 0; + } + } + + if (! aleftcomm.details.date && arightcomm.details.date) + return true; + if (aleftcomm.details.date && ! arightcomm.details.date) + return false; + + if (aleftcomm.details.date && arightcomm.details.date) { + duration_t diff = *aleftcomm.details.date - *arightcomm.details.date; + return diff.is_negative(); + } + + if (! aleftcomm.details.tag && arightcomm.details.tag) + return true; + if (aleftcomm.details.tag && ! arightcomm.details.tag) + return false; + + if (aleftcomm.details.tag && arightcomm.details.tag) + return *aleftcomm.details.tag < *arightcomm.details.tag; + + assert(false); + return true; + } +} + +commodity_pool_t::commodity_pool_t() : default_commodity(NULL) +{ + null_commodity = create(""); + null_commodity->add_flags(COMMODITY_STYLE_NOMARKET | + COMMODITY_STYLE_BUILTIN); +} + +commodity_t * commodity_pool_t::create(const string& symbol) +{ + shared_ptr + base_commodity(new commodity_t::base_t(symbol)); + std::auto_ptr commodity(new commodity_t(this, base_commodity)); + + DEBUG("amounts.commodities", "Creating base commodity " << symbol); + + // Create the "qualified symbol" version of this commodity's symbol + if (commodity_t::symbol_needs_quotes(symbol)) { + commodity->qualified_symbol = "\""; + *commodity->qualified_symbol += symbol; + *commodity->qualified_symbol += "\""; + } + + DEBUG("amounts.commodities", + "Creating commodity '" << commodity->symbol() << "'"); + + // Start out the new commodity with the default commodity's flags + // and precision, if one has been defined. +#if 0 + // jww (2007-05-02): This doesn't do anything currently! + if (default_commodity) + commodity->drop_flags(COMMODITY_STYLE_THOUSANDS | + COMMODITY_STYLE_NOMARKET); +#endif + + commodity->ident = commodities.size(); + + commodities.push_back(commodity.get()); + return commodity.release(); +} + +commodity_t * commodity_pool_t::find_or_create(const string& symbol) +{ + DEBUG("amounts.commodities", "Find-or-create commodity " << symbol); + + commodity_t * commodity = find(symbol); + if (commodity) + return commodity; + return create(symbol); +} + +commodity_t * commodity_pool_t::find(const string& symbol) +{ + DEBUG("amounts.commodities", "Find commodity " << symbol); + + typedef commodity_pool_t::commodities_t::nth_index<1>::type + commodities_by_name; + + commodities_by_name& name_index = commodities.get<1>(); + commodities_by_name::const_iterator i = name_index.find(symbol); + if (i != name_index.end()) + return *i; + else + return NULL; +} + +commodity_t * commodity_pool_t::find(const commodity_t::ident_t ident) +{ + DEBUG("amounts.commodities", "Find commodity by ident " << ident); + + typedef commodity_pool_t::commodities_t::nth_index<0>::type + commodities_by_ident; + + commodities_by_ident& ident_index = commodities.get<0>(); + return ident_index[ident]; +} + +commodity_t * +commodity_pool_t::create(const string& symbol, const annotation_t& details) +{ + commodity_t * new_comm = create(symbol); + if (! new_comm) + return NULL; + + if (details) + return find_or_create(*new_comm, details); + else + return new_comm; +} + +namespace { + string make_qualified_name(const commodity_t& comm, + const annotation_t& details) + { + assert(details); + + if (details.price && details.price->sign() < 0) + throw_(amount_error, "A commodity's price may not be negative"); + + std::ostringstream name; + comm.print(name); + annotated_commodity_t::write_annotations(name, details); + + DEBUG("amounts.commodities", "make_qualified_name for " + << comm.qualified_symbol << std::endl << details); + DEBUG("amounts.commodities", "qualified_name is " << name.str()); + + return name.str(); + } +} + +commodity_t * +commodity_pool_t::find(const string& symbol, const annotation_t& details) +{ + commodity_t * comm = find(symbol); + if (! comm) + return NULL; + + if (details) { + string name = make_qualified_name(*comm, details); + + if (commodity_t * ann_comm = find(name)) { + assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); + return ann_comm; + } + return NULL; + } else { + return comm; + } +} + +commodity_t * +commodity_pool_t::find_or_create(const string& symbol, + const annotation_t& details) +{ + commodity_t * comm = find(symbol); + if (! comm) + return NULL; + + if (details) + return find_or_create(*comm, details); + else + return comm; +} + +commodity_t * +commodity_pool_t::create(commodity_t& comm, + const annotation_t& details, + const string& mapping_key) +{ + assert(comm); + assert(details); + assert(! mapping_key.empty()); + + std::auto_ptr commodity + (new annotated_commodity_t(&comm, details)); + + commodity->qualified_symbol = comm.symbol(); + assert(! commodity->qualified_symbol->empty()); + + DEBUG("amounts.commodities", "Creating annotated commodity " + << "symbol " << commodity->symbol() + << " key " << mapping_key << std::endl << details); + + // Add the fully annotated name to the map, so that this symbol may + // quickly be found again. + commodity->ident = commodities.size(); + commodity->mapping_key_ = mapping_key; + + commodities.push_back(commodity.get()); + return commodity.release(); +} + +commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, + const annotation_t& details) +{ + assert(comm); + assert(details); + + string name = make_qualified_name(comm, details); + assert(! name.empty()); + + if (commodity_t * ann_comm = find(name)) { + assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); + return ann_comm; + } + return create(comm, details, name); +} + +} // namespace ledger diff --git a/commodity.h b/commodity.h new file mode 100644 index 00000000..767023e8 --- /dev/null +++ b/commodity.h @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file commodity.h + * @author John Wiegley + * @date Wed Apr 18 22:05:53 2007 + * + * @brief Types for handling commodities. + * + * This file contains one of the most basic types in Ledger: + * commodity_t, and its annotated cousin, annotated_commodity_t. + */ +#ifndef _COMMODITY_H +#define _COMMODITY_H + +namespace ledger { + +class commodity_t + : public delegates_flags<>, + public equality_comparable1 +{ + friend class commodity_pool_t; + + class base_t : public noncopyable, public supports_flags<> + { + public: + typedef std::map history_map; + + struct history_t { + history_map prices; + ptime last_lookup; + }; + +#define COMMODITY_STYLE_DEFAULTS 0x00 +#define COMMODITY_STYLE_SUFFIXED 0x01 +#define COMMODITY_STYLE_SEPARATED 0x02 +#define COMMODITY_STYLE_EUROPEAN 0x04 +#define COMMODITY_STYLE_THOUSANDS 0x08 +#define COMMODITY_STYLE_NOMARKET 0x10 +#define COMMODITY_STYLE_BUILTIN 0x20 + + string symbol; + amount_t::precision_t precision; + optional name; + optional note; + optional history; + optional smaller; + optional 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; + + commodity_pool_t * parent_; + ident_t ident; + optional qualified_symbol; + optional mapping_key_; + bool annotated; + +public: + explicit commodity_t(commodity_pool_t * _parent, + const shared_ptr& _base) + : delegates_flags<>(*_base.get()), base(_base), + parent_(_parent), annotated(false) { + TRACE_CTOR(commodity_t, ""); + } + virtual ~commodity_t() { + TRACE_DTOR(commodity_t); + } + + operator bool() const; + + virtual bool operator==(const commodity_t& comm) const { + if (comm.annotated) + return comm == *this; + return base.get() == comm.base.get(); + } + + commodity_pool_t& parent() const { + return *parent_; + } + + 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 name() const { + return base->name; + } + void set_name(const optional& arg = none) { + base->name = arg; + } + + optional note() const { + return base->note; + } + void set_note(const optional& 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 smaller() const { + return base->smaller; + } + void set_smaller(const optional& arg = none) { + base->smaller = arg; + } + + optional larger() const { + return base->larger; + } + void set_larger(const optional& arg = none) { + base->larger = arg; + } + + optional history() const { + return base->history; + } + + void add_price(const moment_t& date, const amount_t& price); + bool remove_price(const moment_t& date); + + optional value(const optional& moment = none); + + static void parse_symbol(std::istream& in, string& symbol); + static void parse_symbol(char *& p, string& symbol); + static string parse_symbol(std::istream& in) { + string temp; + parse_symbol(in, temp); + return temp; + } + + void print(std::ostream& out) const { + out << symbol(); + } + + void read(std::istream& in); + void read(char *& data); + void write(std::ostream& out) const; + + bool valid() const; +}; + +inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { + comm.print(out); + return out; +} + +struct annotation_t : public equality_comparable +{ + optional price; + optional date; + optional tag; + + explicit annotation_t + (const optional& _price = none, + const optional& _date = none, + const optional& _tag = none) + : price(_price), date(_date), tag(_tag) {} + + operator bool() const { + return price || date || tag; + } + + bool operator==(const annotation_t& rhs) const { + return (price == rhs.price && + date == rhs.date && + tag == rhs.tag); + } + + void parse(std::istream& in); + void print(std::ostream& out) const { + out << "price " << (price ? price->to_string() : "NONE") << " " + << "date " << (date ? *date : moment_t()) << " " + << "tag " << (tag ? *tag : "NONE"); + } + + bool valid() const { + assert(*this); + return true; + } +}; + +inline std::ostream& operator<<(std::ostream& out, const annotation_t& details) { + details.print(out); + return out; +} + +class annotated_commodity_t + : public commodity_t, + public equality_comparable > +{ +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(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(commodity); +} +inline const annotated_commodity_t& +as_annotated_commodity(const commodity_t& commodity) { + return downcast(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 > + > + > commodities_t; + + commodities_t commodities; + +public: + commodity_t * null_commodity; + commodity_t * default_commodity; + +private: + template + struct first_initialized + { + typedef T result_type; + + template + T operator()(InputIterator first, InputIterator last) const + { + for (; first != last; first++) + if (*first) + return *first; + return T(); + } + }; + +public: + boost::function + (commodity_t& commodity, + const optional& date, + const optional& moment, + const optional& last)> get_quote; + + explicit commodity_pool_t(); + + ~commodity_pool_t() { + typedef commodity_pool_t::commodities_t::nth_index<0>::type + commodities_by_ident; + + commodities_by_ident& ident_index = commodities.get<0>(); + for (commodities_by_ident::iterator i = ident_index.begin(); + i != ident_index.end(); + i++) + checked_delete(*i); + } + + commodity_t * create(const string& symbol); + commodity_t * find(const string& name); + commodity_t * find(const commodity_t::ident_t ident); + commodity_t * find_or_create(const string& symbol); + + commodity_t * create(const string& symbol, const annotation_t& details); + commodity_t * find(const string& symbol, const annotation_t& details); + commodity_t * find_or_create(const string& symbol, + const annotation_t& details); + + commodity_t * create(commodity_t& comm, + const annotation_t& details, + const string& mapping_key); + + commodity_t * find_or_create(commodity_t& comm, + const annotation_t& details); +}; + +} // namespace ledger + +#endif // _COMMODITY_H diff --git a/configure.in b/configure.in index 038e80aa..c42a8c0b 100644 --- a/configure.in +++ b/configure.in @@ -1,9 +1,12 @@ -# -*- Autoconf -*- +# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. -AC_PREREQ(2.59) -AC_INIT(ledger, 2.6.0.90, johnw@newartisans.com) -AM_INIT_AUTOMAKE(ledger, 2.6.0.90) +AC_PREREQ(2.61) + +AC_INIT([ledger],[2.7],[johnw@newartisans.com]) +AC_CONFIG_SRCDIR(ledger) +AM_INIT_AUTOMAKE([dist-bzip2]) + AC_CONFIG_SRCDIR([main.cc]) AC_CONFIG_HEADER([acconf.h]) @@ -16,39 +19,68 @@ AM_PROG_LIBTOOL # Checks for emacs lisp path AM_PATH_LISPDIR +# Check for options +AC_ARG_ENABLE(debug, + [ --enable-debug Turn on debugging], + [case "${enableval}" in + yes) debug=true ;; + no) debug=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;; + esac],[debug=false]) + +AM_CONDITIONAL(DEBUG, test x$debug = xtrue) + +AC_ARG_ENABLE(pch, + [ --enable-pch Use GCC 4.x pre-compiled headers], + [case "${enableval}" in + yes) pch=true ;; + no) pch=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-pch) ;; + esac],[pch=false]) + +AM_CONDITIONAL(USE_PCH, test x$pch = xtrue) + +AC_ARG_WITH(boost-suffix, + [ --with-boost-suffix=X Append X to the Boost library names], + [BOOST_SUFFIX="-${withval}"], + [BOOST_SUFFIX=""]) + +AC_SUBST([BOOST_SUFFIX], $BOOST_SUFFIX) + # check if UNIX pipes are available AC_CACHE_CHECK( [if pipes can be used], [pipes_avail_cv_], [AC_LANG_PUSH(C++) - AC_TRY_LINK( - [#include - #include - #include - #include - #include - #include ], - [int status, pfd[2]; - status = pipe(pfd); - status = fork(); - if (status < 0) { - ; - } else if (status == 0) { - char *arg0; - - status = dup2(pfd[0], STDIN_FILENO); - - close(pfd[1]); - close(pfd[0]); - - execlp("", arg0, (char *)0); - perror("execl"); - exit(1); - } else { - close(pfd[0]); - }], - [pipes_avail_cv_=true], - [pipes_avail_cv_=false]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include + #include + #include + #include + #include + #include ]], + [[int status, pfd[2]; + status = pipe(pfd); + status = fork(); + if (status < 0) { + ; + } else if (status == 0) { + char *arg0; + + status = dup2(pfd[0], STDIN_FILENO); + + close(pfd[1]); + close(pfd[0]); + + execlp("", arg0, (char *)0); + perror("execl"); + exit(1); + } else { + close(pfd[0]); + }]])], + [pipes_avail=true], + [pipes_avail=false]) AC_LANG_POP]) if [test x$pipes_avail_cv_ = xtrue ]; then @@ -62,18 +94,13 @@ AC_CACHE_CHECK( [libgmp_save_libs=$LIBS LIBS="-lgmp $LIBS" AC_LANG_PUSH(C++) - AC_TRY_LINK( - [#include ], - [mpz_t bar; + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[mpz_t bar; mpz_init(bar); - mpz_clear(bar);], - [libgmp_avail_cv_=true], - [libgmp_avail_cv_=false]) + mpz_clear(bar);]])],[libgmp_avail=true],[libgmp_avail=false]) AC_LANG_POP LIBS=$libgmp_save_libs]) -if [test x$libgmp_avail_cv_ = xtrue ]; then - AM_CONDITIONAL(HAVE_GMP, true) +if [test x$libgmp_avail = xtrue ]; then LIBS="-lgmp $LIBS" else AC_MSG_FAILURE("Could not find gmp library (set CPPFLAGS and LDFLAGS?)") @@ -173,6 +200,112 @@ else AM_CONDITIONAL(HAVE_XMLPARSE, false) fi +# check for boost_regex +AC_CACHE_CHECK( + [if boost_regex is available], + [boost_regex_avail], + [boost_regex_save_libs=$LIBS + LIBS="-lboost_regex$BOOST_SUFFIX $LIBS" + AC_LANG_PUSH(C++) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include ]], + [[boost::regex foo_regexp("Hello, world!");]])], + [boost_regex_avail=true], + [boost_regex_avail=false]) + AC_LANG_POP + LIBS=$boost_regex_save_libs]) + +if [test x$boost_regex_avail = xtrue ]; then + LIBS="-lboost_regex$BOOST_SUFFIX $LIBS" +else + AC_MSG_FAILURE("Could not find boost_regex library (set CPPFLAGS and LDFLAGS?)") +fi + +# check for boost_date_time +AC_CACHE_CHECK( + [if boost_date_time is available], + [boost_date_time_cpplib_avail], + [boost_date_time_save_libs=$LIBS + LIBS="-lboost_date_time$BOOST_SUFFIX $LIBS" + AC_LANG_PUSH(C++) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include + #include + #include + #include + + using namespace boost::posix_time; + using namespace boost::date_time; + + #include + + inline ptime time_to_system_local(const ptime& when) { + struct std::tm tm_gmt = to_tm(when); + return from_time_t(mktime(&tm_gmt)); + }]], + [[ptime t10 = ptime(boost::gregorian::from_string("2007-01-15"), + ptime::time_duration_type()); + + ptime t12 = time_to_system_local(t10); + + return t10 != t12;]])], + [boost_date_time_cpplib_avail=true], + [boost_date_time_cpplib_avail=false]) + AC_LANG_POP + LIBS=$boost_date_time_save_libs]) + +if [test x$boost_date_time_cpplib_avail = xtrue ]; then + LIBS="-lboost_date_time$BOOST_SUFFIX $LIBS" +else + AC_MSG_FAILURE("Could not find boost_date_time library (set CPPFLAGS and LDFLAGS?)") +fi + +# check for boost_filesystem +AC_CACHE_CHECK( + [if boost_filesystem is available], + [boost_filesystem_cpplib_avail], + [boost_filesystem_save_libs=$LIBS + LIBS="-lboost_filesystem$BOOST_SUFFIX -lboost_system$BOOST_SUFFIX $LIBS" + AC_LANG_PUSH(C++) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include ]], + [[boost::filesystem::path this_path("Hello");]])], + [boost_filesystem_cpplib_avail=true], + [boost_filesystem_cpplib_avail=false]) + AC_LANG_POP + LIBS=$boost_filesystem_save_libs]) + +if [test x$boost_filesystem_cpplib_avail = xtrue ]; then + LIBS="-lboost_filesystem$BOOST_SUFFIX -lboost_system$BOOST_SUFFIX $LIBS" +else + AC_MSG_FAILURE("Could not find boost_filesystem library (set CPPFLAGS and LDFLAGS?)") +fi + +## check for boost_signals +#AC_CACHE_CHECK( +# [if boost_signals is available], +# [boost_signals_cpplib_avail], +# [boost_signals_save_libs=$LIBS +# LIBS="-lboost_signals$BOOST_SUFFIX $LIBS" +# AC_LANG_PUSH(C++) +# AC_LINK_IFELSE( +# [AC_LANG_PROGRAM( +# [[#include ]], +# [[boost::signal this_signal;]])], +# [boost_signals_cpplib_avail=true], +# [boost_signals_cpplib_avail=false]) +# AC_LANG_POP +# LIBS=$boost_signals_save_libs]) +# +#if [test x$boost_signals_cpplib_avail = xtrue ]; then +# LIBS="-lboost_signals$BOOST_SUFFIX $LIBS" +#else +# AC_MSG_FAILURE("Could not find boost_signals library (set CPPFLAGS and LDFLAGS?)") +#fi + # check for libofx AC_ARG_ENABLE(ofx, [ --enable-ofx Turn on support for OFX/OCF parsing], @@ -181,6 +314,7 @@ AC_ARG_ENABLE(ofx, no) ofx=false ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-ofx) ;; esac],[ofx=true]) + AM_CONDITIONAL(USE_OFX, test x$ofx = xtrue) if [test x$ofx = xtrue ]; then @@ -190,11 +324,12 @@ if [test x$ofx = xtrue ]; then [libofx_save_libs=$LIBS LIBS="-lofx $LIBS" AC_LANG_PUSH(C++) - AC_TRY_LINK( - [#include ], - [ LibofxContextPtr libofx_context = libofx_get_new_context();], - [libofx_avail_cv_=true], - [libofx_avail_cv_=false]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include ]], + [[LibofxContextPtr libofx_context = libofx_get_new_context();]])], + [libofx_avail=true], + [libofx_avail=false]) AC_LANG_POP LIBS=$libofx_save_libs]) @@ -216,6 +351,7 @@ AC_ARG_ENABLE(python, no) python=false ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-python) ;; esac],[python=false]) + AM_CONDITIONAL(USE_PYTHON, test x$python = xtrue) if [test x$python = xtrue ]; then @@ -225,23 +361,25 @@ if [test x$python = xtrue ]; then [if boost_python is available], [boost_python_cpplib_avail_cv_], [boost_python_save_libs=$LIBS - LIBS="-lboost_python -lpython$PYTHON_VERSION $LIBS" + LIBS="-lboost_python$BOOST_SUFFIX -lpython$PYTHON_VERSION $LIBS" AC_LANG_PUSH(C++) - AC_TRY_LINK( - [#include - using namespace boost::python; - class foo {}; - BOOST_PYTHON_MODULE(samp) { - class_< foo > ("foo") ; - }], - [return 0], - [boost_python_cpplib_avail_cv_=true], - [boost_python_cpplib_avail_cv_=false]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include + using namespace boost::python; + class foo {}; + BOOST_PYTHON_MODULE(samp) { + class_< foo > ("foo") ; + }]], + [[return 0]])], + [boost_python_cpplib_avail=true], + [boost_python_cpplib_avail=false]) AC_LANG_POP LIBS=$boost_python_save_libs]) - if [test x$boost_python_cpplib_avail_cv_ = xtrue ]; then + + if [test x$boost_python_cpplib_avail = xtrue ]; then AM_CONDITIONAL(HAVE_BOOST_PYTHON, true) - LIBS="-lboost_python -lpython$PYTHON_VERSION $LIBS" + LIBS="-lboost_python$BOOST_SUFFIX -lpython$PYTHON_VERSION $LIBS" else AM_CONDITIONAL(HAVE_BOOST_PYTHON, false) fi @@ -252,19 +390,39 @@ else AM_CONDITIONAL(HAVE_BOOST_PYTHON, false) fi -# Check for options -AC_ARG_ENABLE(debug, - [ --enable-debug Turn on debugging], - [case "${enableval}" in - yes) debug=true ;; - no) debug=false ;; - *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;; - esac],[debug=false]) -AM_CONDITIONAL(DEBUG, test x$debug = xtrue) +# check for CppUnit +AC_CACHE_CHECK( + [if cppunit is available], + [cppunit_avail], + [cppunit_save_libs=$LIBS + LIBS="-lcppunit $LIBS" + AC_LANG_PUSH(C++) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include + #include + #include + #include + #include + #include + #include + #include ]], + [[CPPUNIT_NS::TestResult controller; + CPPUNIT_NS::TestResultCollector result;]])], + [cppunit_avail=true], + [cppunit_avail=false]) + AC_LANG_POP + LIBS=$cppunit_save_libs]) + +if [test x$cppunit_avail = xtrue ]; then + AM_CONDITIONAL(HAVE_CPPUNIT, true) +else + AM_CONDITIONAL(HAVE_CPPUNIT, false) +fi # Checks for header files. -AC_STDC_HEADERS -AC_HAVE_HEADERS(sys/stat.h) +AC_HEADER_STDC +AC_CHECK_HEADERS([sys/stat.h langinfo.h]) # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL @@ -272,9 +430,8 @@ AC_TYPE_SIZE_T AC_STRUCT_TM # Checks for library functions. -#AC_FUNC_ERROR_AT_LINE AC_HEADER_STDC -AC_CHECK_FUNCS([access mktime realpath stat strftime strptime getpwuid getpwnam]) +AC_CHECK_FUNCS([access mktime realpath getpwuid getpwnam nl_langinfo]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff --git a/datetime.cc b/datetime.cc deleted file mode 100644 index 040c3046..00000000 --- a/datetime.cc +++ /dev/null @@ -1,363 +0,0 @@ -#if defined(__GNUG__) && __GNUG__ < 3 -#define _XOPEN_SOURCE -#endif - -#include "debug.h" -#include "datetime.h" - -#include -#include - -date_t date_t::now(std::time(NULL)); -int date_t::current_year = date_t::now.year(); -std::string date_t::input_format; -std::string date_t::output_format = "%Y/%m/%d"; - -const char * date_t::formats[] = { - "%Y/%m/%d", - "%m/%d", - "%Y.%m.%d", - "%m.%d", - "%Y-%m-%d", - "%m-%d", - "%a", - "%A", - "%b", - "%B", - "%Y", - NULL -}; - -datetime_t datetime_t::now(std::time(NULL)); - -namespace { - static std::time_t base = -1; - static int base_year = -1; - - static const int month_days[12] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 - }; - - bool parse_date_mask(const char * date_str, struct std::tm * result); - bool parse_date(const char * date_str, std::time_t * result, - const int year = -1); - bool quick_parse_date(const char * date_str, std::time_t * result); -} - -date_t::date_t(const std::string& _when) -{ - if (! quick_parse_date(_when.c_str(), &when)) - throw new date_error - (std::string("Invalid date string: ") + _when); -} - -datetime_t::datetime_t(const std::string& _when) -{ - if (const char * p = std::strchr(_when.c_str(), ' ')) { - date_t date(std::string(_when, 0, p - _when.c_str())); - - struct std::tm moment = *std::localtime(&date.when); - if (! strptime(++p, "%H:%M:%S", &moment)) - throw new datetime_error - (std::string("Invalid date/time string: ") + _when); - - when = std::mktime(&moment); - } else { - when = date_t(_when).when; - } -} - -datetime_t interval_t::first(const datetime_t& moment) const -{ - datetime_t quant(begin); - - 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. - - struct std::tm * desc = std::localtime(&moment.when); - - if (years) - desc->tm_mon = 0; - desc->tm_mday = 1; - - desc->tm_hour = 0; - desc->tm_min = 0; - desc->tm_sec = 0; - desc->tm_isdst = -1; - - quant = std::mktime(desc); - - datetime_t temp; - while (moment >= (temp = increment(quant))) { - if (quant == temp) - break; - quant = temp; - } - } - - return quant; -} - -datetime_t interval_t::increment(const datetime_t& moment) const -{ - struct std::tm * desc = std::localtime(&moment.when); - - if (years) - desc->tm_year += years; - if (months) - desc->tm_mon += months; - if (days) - desc->tm_mday += days; - - desc->tm_hour += hours; - desc->tm_min += minutes; - desc->tm_sec += seconds; - - desc->tm_isdst = -1; - - return std::mktime(desc); -} - -namespace { - void parse_inclusion_specifier(const std::string& word, - datetime_t * begin, datetime_t * end) - { - struct std::tm when; - - if (! parse_date_mask(word.c_str(), &when)) - throw new datetime_error(std::string("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 = date_t::current_year; - 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 = std::mktime(&when); - if (end) - *end = interval_t(saw_day ? 86400 : 0, saw_mon ? 1 : 0, - saw_year ? 1 : 0).increment(*begin); - } - else if (end) { - *end = std::mktime(&when); - } - } - - inline void read_lower_word(std::istream& in, std::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, std::string& word, - datetime_t * begin, datetime_t * end) - { - std::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") { - std::strftime(buf, 31, "%B", datetime_t::now.localtime()); - word = buf; - mon_spec = true; - } - else if (word == "year") { - std::strftime(buf, 31, "%Y", datetime_t::now.localtime()); - 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) -{ - std::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 == "hours") - hours = quantity; - else if (word == "minutes") - minutes = quantity; - else if (word == "seconds") - seconds = 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 == "hour") - hours = 1; - else if (word == "minute") - minutes = 1; - else if (word == "second") - seconds = 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 == "hourly") - hours = 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 { - bool parse_date_mask(const char * date_str, struct std::tm * result) - { - if (! date_t::input_format.empty()) { - std::memset(result, INT_MAX, sizeof(struct std::tm)); - if (strptime(date_str, date_t::input_format.c_str(), result)) - return true; - } - for (const char ** f = date_t::formats; *f; f++) { - std::memset(result, INT_MAX, sizeof(struct std::tm)); - if (strptime(date_str, *f, result)) - return true; - } - return false; - } - - bool parse_date(const char * date_str, std::time_t * result, const int year) - { - struct std::tm when; - - if (! parse_date_mask(date_str, &when)) - return false; - - when.tm_hour = 0; - when.tm_min = 0; - when.tm_sec = 0; - - if (when.tm_year == -1) - when.tm_year = ((year == -1) ? date_t::current_year : (year - 1900)); - - if (when.tm_mon == -1) - when.tm_mon = 0; - - if (when.tm_mday == -1) - when.tm_mday = 1; - - *result = std::mktime(&when); - - return true; - } - - bool quick_parse_date(const char * date_str, std::time_t * result) - { - return parse_date(date_str, result, date_t::current_year + 1900); - } -} diff --git a/datetime.h b/datetime.h deleted file mode 100644 index 9f85d3f2..00000000 --- a/datetime.h +++ /dev/null @@ -1,314 +0,0 @@ -#ifndef _DATETIME_H -#define _DATETIME_H - -#include -#include - -#include "error.h" - -class date_error : public error { - public: - date_error(const std::string& reason) throw() : error(reason) {} - virtual ~date_error() throw() {} -}; - -struct interval_t; -class datetime_t; - -class date_t -{ - date_t(const datetime_t& _when); - - protected: - std::time_t when; - - public: - static date_t now; - static const char * formats[]; - static int current_year; - static std::string input_format; - static std::string output_format; - - date_t() : when(0) {} - date_t(const date_t& _when) : when(_when.when) {} - - date_t(const std::time_t _when) : when(_when) { -#if 0 - struct std::tm * moment = std::localtime(&_when); - moment->tm_hour = 0; - moment->tm_min = 0; - moment->tm_sec = 0; - when = std::mktime(moment); -#endif - } - date_t(const interval_t& period); - date_t(const std::string& _when); - - virtual ~date_t() {} - - date_t& operator=(const date_t& _when) { - when = _when.when; - return *this; - } - date_t& operator=(const std::time_t _when) { - return *this = date_t(_when); - } - date_t& operator=(const datetime_t& _when) { - return *this = date_t(_when); - } - date_t& operator=(const interval_t& period) { - return *this = date_t(period); - } - date_t& operator=(const std::string& _when) { - return *this = date_t(_when); - } - - date_t& operator+=(const interval_t& period); - - long operator-=(const date_t& date) { - return (when - date.when) / 86400; - } - - virtual date_t& operator+=(const long days) { - // jww (2006-03-26): This is not accurate enough when DST is in effect! - assert(0); - when += days * 86400; - return *this; - } - virtual date_t& operator-=(const long days) { - assert(0); - when -= days * 86400; - return *this; - } - -#define DEF_DATE_OP(OP) \ - bool operator OP(const date_t& other) const { \ - return when OP other.when; \ - } - - DEF_DATE_OP(<) - DEF_DATE_OP(<=) - DEF_DATE_OP(>) - DEF_DATE_OP(>=) - DEF_DATE_OP(==) - DEF_DATE_OP(!=) - - operator bool() const { - return when != 0; - } - operator std::time_t() { - return when; - } - operator std::string() const { - return to_string(); - } - - std::string to_string(const std::string& format = output_format) const { - char buf[64]; - std::strftime(buf, 63, format.c_str(), localtime()); - return buf; - } - - int year() const { - return localtime()->tm_year + 1900; - } - int month() const { - return localtime()->tm_mon + 1; - } - int day() const { - return localtime()->tm_mday; - } - int wday() const { - return localtime()->tm_wday; - } - - std::tm * localtime() const { - return std::localtime(&when); - } - - void write(std::ostream& out, - const std::string& format = output_format) const { - out << to_string(format); - } - - friend class datetime_t; - friend struct interval_t; -}; - -inline long operator-(const date_t& left, const date_t& right) { - date_t temp(left); - temp -= right; - return (long)temp; -} - -inline date_t operator+(const date_t& left, const long days) { - date_t temp(left); - temp += days; - return temp; -} - -inline date_t operator-(const date_t& left, const long days) { - date_t temp(left); - temp -= days; - return temp; -} - -inline std::ostream& operator<<(std::ostream& out, const date_t& moment) { - moment.write(out); - return out; -} - -class datetime_error : public error { - public: - datetime_error(const std::string& reason) throw() : error(reason) {} - virtual ~datetime_error() throw() {} -}; - -class datetime_t : public date_t -{ - public: - static datetime_t now; - - datetime_t() : date_t() {} - datetime_t(const datetime_t& _when) : date_t(_when.when) {} - datetime_t(const date_t& _when) : date_t(_when) {} - - datetime_t(const std::time_t _when) : date_t(_when) {} - datetime_t(const std::string& _when); - - datetime_t& operator=(const datetime_t& _when) { - when = _when.when; - return *this; - } - datetime_t& operator=(const date_t& _when) { - when = _when.when; - return *this; - } - datetime_t& operator=(const std::time_t _when) { - return *this = datetime_t(_when); - } - datetime_t& operator=(const std::string& _when) { - return *this = datetime_t(_when); - } - - long operator-=(const datetime_t& date) { - return when - date.when; - } - - virtual datetime_t& operator+=(const long secs) { - when += secs; - return *this; - } - virtual datetime_t& operator-=(const long secs) { - when -= secs; - return *this; - } - -#define DEF_DATETIME_OP(OP) \ - bool operator OP(const datetime_t& other) const { \ - return when OP other.when; \ - } - - DEF_DATETIME_OP(<) - DEF_DATETIME_OP(<=) - DEF_DATETIME_OP(>) - DEF_DATETIME_OP(>=) - DEF_DATETIME_OP(==) - DEF_DATETIME_OP(!=) - - int hour() const { - return localtime()->tm_hour; - } - int min() const { - return localtime()->tm_min; - } - int sec() const { - return localtime()->tm_sec; - } -}; - -inline long operator-(const datetime_t& left, const datetime_t& right) { - std::time_t left_time(left); - std::time_t right_time(right); - return left_time - right_time; -} - -inline datetime_t operator+(const datetime_t& left, const long seconds) { - datetime_t temp(left); - temp += seconds; - return temp; -} - -inline datetime_t operator-(const datetime_t& left, const long seconds) { - datetime_t temp(left); - temp -= seconds; - return temp; -} - -inline std::ostream& operator<<(std::ostream& out, - const datetime_t& moment) { - char buf[64]; - std::strftime(buf, 63, (date_t::output_format + " %H:%M:%S").c_str(), - moment.localtime()); - out << buf; - return out; -} - -struct interval_t -{ - unsigned short years; - unsigned short months; - unsigned short days; - unsigned short hours; - unsigned short minutes; - unsigned short seconds; - - datetime_t begin; - datetime_t end; - - 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), - hours(0), minutes(0), seconds(0), - begin(_begin), end(_end) {} - - interval_t(const std::string& desc) - : years(0), months(0), days(0), - hours(0), minutes(0), seconds(0) { - std::istringstream stream(desc); - parse(stream); - } - - operator bool() const { - return (years > 0 || months > 0 || days > 0 || - hours > 0 || minutes > 0 || seconds > 0); - } - - void start(const datetime_t& moment) { - begin = first(moment); - } - datetime_t first(const datetime_t& moment = datetime_t()) const; - datetime_t increment(const datetime_t&) const; - - void parse(std::istream& in); -}; - -inline date_t::date_t(const interval_t& period) { - when = period.first().when; -} - -inline date_t& date_t::operator+=(const interval_t& period) { - return *this = period.increment(*this); -} - -inline date_t::date_t(const datetime_t& _when) { - assert(0); - struct std::tm * moment = _when.localtime(); - moment->tm_hour = 0; - moment->tm_min = 0; - moment->tm_sec = 0; - when = std::mktime(moment); -} - -#endif // _DATETIME_H diff --git a/debug.cc b/debug.cc deleted file mode 100644 index b3b140bc..00000000 --- a/debug.cc +++ /dev/null @@ -1,125 +0,0 @@ -#include "debug.h" - -#ifdef DEBUG_ENABLED - -#include -#include - -#include // for the `write' method - -int offset = 0; - -std::map ptrs; - -#define PRINT_INC(x) { \ - char buf[128]; \ - std::sprintf(buf, "%d: %p: %s", ++offset, ptr, x); \ - write(1, buf, std::strlen(buf)); \ -} - -#define PRINT_DEC(x) { \ - char buf[128]; \ - std::sprintf(buf, "%d: %p: %s", --offset, ptr, x); \ - write(1, buf, std::strlen(buf)); \ -} - -void * operator new(std::size_t size) throw (std::bad_alloc) { - void * ptr = std::malloc(size); - if (DEBUG("debug.alloc")) { - PRINT_INC("void * operator new(std::size_t size) throw (std::bad_alloc)\n"); - } - return ptr; -} -void * operator new[](std::size_t size) throw (std::bad_alloc) { - void * ptr = std::malloc(size); - if (DEBUG("debug.alloc")) { - PRINT_INC("void * operator new[](std::size_t) throw (std::bad_alloc)\n"); - } - return ptr; -} -void * operator new(std::size_t size, const std::nothrow_t&) throw() { - void * ptr = std::malloc(size); - if (DEBUG("debug.alloc")) { - PRINT_INC("void * operator new(std::size_t size, const std::nothrow_t&) throw()\n"); - } - return ptr; -} -void * operator new[](std::size_t size, const std::nothrow_t&) throw() { - void * ptr = std::malloc(size); - if (DEBUG("debug.alloc")) { - PRINT_INC("void * operator new[](std::size_t size, const std::nothrow_t&) throw()\n"); - } - return ptr; -} -void operator delete(void * ptr) throw() { - if (DEBUG("debug.alloc")) { - PRINT_DEC("void operator delete(void * ptr) throw()\n"); - } - std::free(ptr); -} -void operator delete[](void * ptr) throw() { - if (DEBUG("debug.alloc")) { - PRINT_DEC("void operator delete[](void * ptr) throw()\n"); - } - std::free(ptr); -} -void operator delete(void * ptr, const std::nothrow_t&) throw() { - if (DEBUG("debug.alloc")) { - PRINT_DEC("void operator delete(void * ptr, const std::nothrow_t&) throw()\n"); - } - std::free(ptr); -} -void operator delete[](void * ptr, const std::nothrow_t&) throw() { - if (DEBUG("debug.alloc")) { - PRINT_DEC("void operator delete[](void * ptr, const std::nothrow_t&) throw()\n"); - } - std::free(ptr); -} - -std::ostream * _debug_stream = &std::cerr; -bool _free_debug_stream = false; - -bool _debug_active(const char * const cls) { - if (char * debug = std::getenv("DEBUG_CLASS")) { - static const char * error; - static int erroffset; - static int ovec[30]; - static pcre * class_regexp = pcre_compile(debug, PCRE_CASELESS, - &error, &erroffset, NULL); - return pcre_exec(class_regexp, NULL, cls, std::strlen(cls), - 0, 0, ovec, 30) >= 0; - } - return false; -} - -static struct init_streams { - init_streams() { - // If debugging is enabled and DEBUG_FILE is set, all debugging - // output goes to that file. - if (const char * p = std::getenv("DEBUG_FILE")) { - _debug_stream = new std::ofstream(p); - _free_debug_stream = true; - } - } - ~init_streams() { - if (_free_debug_stream && _debug_stream) { - delete _debug_stream; - _debug_stream = NULL; - } - } -} _debug_init; - -#endif // DEBUG_ENABLED - -#if DEBUG_LEVEL >= BETA - -#include - -void debug_assert(const std::string& reason, - const std::string& file, - unsigned long line) -{ - throw new fatal_assert(reason, new file_context(file, line)); -} - -#endif diff --git a/debug.h b/debug.h deleted file mode 100644 index 81083ad3..00000000 --- a/debug.h +++ /dev/null @@ -1,146 +0,0 @@ -#ifndef _DEBUG_H -#define _DEBUG_H - -#define DEVELOPER 4 -#define ALPHA 3 -#define BETA 2 -#define RELEASE 1 -#define NO_SEATBELT 0 - -#ifndef DEBUG_LEVEL -#define DEBUG_LEVEL NO_SEATBELT -#endif - -#if DEBUG_LEVEL >= RELEASE -#include "error.h" - -#ifdef assert -#undef assert -#endif -#if DEBUG_LEVEL >= BETA -void debug_assert(const std::string& reason, - const std::string& file, - unsigned long line); -#define assert(x) \ - if (! (x)) \ - debug_assert(#x, __FILE__, __LINE__) -#else -#define assert(x) \ - if (! (x)) \ - throw new fatal_assert(#x, new file_context(__FILE__, __LINE__)) -#endif -#else -#ifdef assert -#undef assert -#endif -#define assert(x) -#endif - -////////////////////////////////////////////////////////////////////// -// -// General debugging facilities -// -// - In developer level, all checking and debugging facilities are -// active. -// -// - Alpha level does not include performance degrading -// VALIDATE calls. -// -// - Beta level is like Alpha, but does not include debugging -// facilities. -// -// - Release level does not include CONFIRM checks, but does include -// assert calls. -// -// - Running with no seatbelt disables all checking except for normal -// syntax and semantic error checking. - -#if DEBUG_LEVEL >= ALPHA - -#include -#include -#include -#include -#include - -#include "datetime.h" - -#define DEBUG_ENABLED - -extern std::ostream * _debug_stream; -extern bool _free_debug_stream; - -bool _debug_active(const char * const cls); - -#define DEBUG_CLASS(cls) static const char * const _debug_cls = (cls) - -#define DEBUG(cls) (_debug_active(cls)) -#define DEBUG_() DEBUG(_debug_cls) - -#define DEBUG_IF(cls) if (_debug_active(cls)) -#define DEBUG_IF_() if (_debug_active(_debug_cls)) - -#define DEBUG_PRINT(cls, x) \ - if (_debug_stream && _debug_active(cls)) { \ - *_debug_stream << x << std::endl; \ - } -#define DEBUG_PRINT_(x) DEBUG_PRINT(_debug_cls, x) - -#define DEBUG_PRINT_TIME(cls, x) { \ - DEBUG_PRINT(cls, #x << " is " << x); \ -} - -#define DEBUG_PRINT_TIME_(x) DEBUG_PRINT_TIME(_debug_cls, x) - -#define CONFIRM(x) assert(x) - -#if DEBUG_LEVEL == DEVELOPER -#define VALIDATE(x) assert(x) -#else -#define VALIDATE(x) -#endif - -void * operator new(std::size_t) throw (std::bad_alloc); -void * operator new[](std::size_t) throw (std::bad_alloc); -void operator delete(void*) throw(); -void operator delete[](void*) throw(); -void * operator new(std::size_t, const std::nothrow_t&) throw(); -void * operator new[](std::size_t, const std::nothrow_t&) throw(); -void operator delete(void*, const std::nothrow_t&) throw(); -void operator delete[](void*, const std::nothrow_t&) throw(); - -#else // DEBUG_LEVEL - -#define DEBUG_CLASS(cls) -#define DEBUG(cls) 0 -#define DEBUG_() 0 -#define DEBUG_IF(cls) -#define DEBUG_IF_() -#define DEBUG_PRINT(cls, x) -#define DEBUG_PRINT_(x) -#define DEBUG_PRINT_TIME(cls, x) -#define DEBUG_PRINT_TIME_(x) - -#define VALIDATE(x) - -#if DEBUG_LEVEL == NO_SEATBELT - -#ifdef assert -#undef assert -#endif -#define assert(x) -#define CONFIRM(x) - -#elif DEBUG_LEVEL >= RELEASE - -#define CONFIRM(x) - -#elif DEBUG_LEVEL >= BETA - -#define CONFIRM(x) assert(x) - -#endif - -#endif // DEBUG_LEVEL - -#endif // _DEBUG_H diff --git a/error.h b/error.h index 7f77db0f..771e9f10 100644 --- a/error.h +++ b/error.h @@ -7,6 +7,8 @@ #include #include +namespace ledger { + class error_context { public: @@ -63,15 +65,13 @@ class line_context : public error_context { ////////////////////////////////////////////////////////////////////// -class str_exception : public std::exception { - protected: - std::string reason; +class str_exception : public std::logic_error { public: std::list context; - str_exception(const std::string& _reason, + str_exception(const std::string& why, error_context * ctxt = NULL) throw() - : reason(_reason) { + : std::logic_error(why) { if (ctxt) context.push_back(ctxt); } @@ -95,31 +95,36 @@ class str_exception : public std::exception { (*i)->describe(out); } } +}; - virtual const char* what() const throw() { - return reason.c_str(); +#define DECLARE_EXCEPTION(kind, name) \ + class name : public kind { \ + public: \ + name(const std::string& why, error_context * ctxt = NULL) throw() \ + : kind(why, ctxt) {} \ } -}; class error : public str_exception { public: - error(const std::string& reason, error_context * ctxt = NULL) throw() - : str_exception(reason, ctxt) {} + error(const std::string& why, error_context * ctxt = NULL) throw() + : str_exception(why, ctxt) {} virtual ~error() throw() {} }; class fatal : public str_exception { public: - fatal(const std::string& reason, error_context * ctxt = NULL) throw() - : str_exception(reason, ctxt) {} + fatal(const std::string& why, error_context * ctxt = NULL) throw() + : str_exception(why, ctxt) {} virtual ~fatal() throw() {} }; class fatal_assert : public fatal { public: - fatal_assert(const std::string& reason, error_context * ctxt = NULL) throw() - : fatal(std::string("assertion failed '") + reason + "'", ctxt) {} + fatal_assert(const std::string& why, error_context * ctxt = NULL) throw() + : fatal(std::string("assertion failed '") + why + "'", ctxt) {} virtual ~fatal_assert() throw() {} }; +} // namespace ledger + #endif // _ERROR_H diff --git a/fdstream.hpp b/fdstream.hpp index a74a5781..ffcf5709 100644 --- a/fdstream.hpp +++ b/fdstream.hpp @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /* The following code declares classes to read from and write to * file descriptore or file handles. * diff --git a/flags.h b/flags.h new file mode 100644 index 00000000..5ae8b60f --- /dev/null +++ b/flags.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FLAGS_H +#define _FLAGS_H + +template +class supports_flags +{ +public: + typedef T flags_t; + +protected: + flags_t flags_; + +public: + supports_flags() : flags_(0) {} + supports_flags(const flags_t arg) : flags_(arg) {} + + 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 +class delegates_flags : public boost::noncopyable +{ +public: + typedef T flags_t; + +protected: + supports_flags& flags_; + +public: + delegates_flags() : flags_() {} + delegates_flags(supports_flags& arg) : flags_(arg) {} + + 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/journal.h b/journal.h index a459160c..c2b8d030 100644 --- a/journal.h +++ b/journal.h @@ -7,12 +7,9 @@ #include #include "amount.h" -#include "datetime.h" #include "value.h" #include "valexpr.h" -#include "error.h" -#include "debug.h" -#include "util.h" +#include "utils.h" namespace ledger { diff --git a/main.py b/main.py new file mode 100644 index 00000000..57ba84c5 --- /dev/null +++ b/main.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python + +# Ledger, the command-line accounting tool +# +# Copyright (c) 2003-2004, New Artisans LLC. All rights reserved. +# +# This program is made available under the terms of the BSD Public +# License. See the LICENSE file included with the distribution for +# details and disclaimer. +# +# This script provides a Python front-end to the ledger library, and +# replicates the functionality of the C++ front-end, main.cc. It is +# provided as an example, and as a starting point for creating custom +# front-ends based on the Ledger module. See the documentation for an +# API reference, and how to use this module. + +import os +import sys +import string +import time + +true, false = 1, 0 + +from ledger import * + +# Create the main journal object, into which all entries will be +# recorded. Once done, the 'journal' may be iterated to yield those +# entries, in the same order as which they appeared in the journal +# file. + +journal = Journal () + +# This call registers all of the default command-line options that +# Ledger supports into the option handling mechanism. Skip this call +# if you wish to do all of your own processing -- in which case simply +# modify the 'config' object however you like. + +add_config_option_handlers () + +averages = {} +compute_monthly_avg = false + +def get_index (xact): + return time.strftime ("%Y/%m", time.localtime (xact.entry.date)) + +class ComputeMonthlyAvg (TransactionHandler): + def __call__ (self, xact): + global averages + index = get_index (xact) + if not averages.has_key(index): + averages[index] = [Value (), 0] + add_transaction_to (xact, averages[index][0]) + averages[index][1] += 1 + TransactionHandler.__call__ (self, xact) + +def monthly_avg (details): + index = get_index (xact) + return averages[index][0] / averages[index][1] + +def show_monthly_averages (arg): + global compute_monthly_avg + compute_monthly_avg = true + config.report_period = "monthly"; + config.total_expr = "@monthly_avg()" + +add_option_handler ("monthly-avg", "", show_monthly_averages) + +# Process the command-line arguments, test whether caching should be +# enabled, and then process any option settings from the execution +# environment. Some historical environment variable names are also +# supported. + +args = process_arguments (sys.argv[1:]) +config.use_cache = not config.data_file +process_environment (os.environ, "LEDGER_") + +if os.environ.has_key ("LEDGER"): + process_option ("file", os.getenv ("LEDGER")) +if os.environ.has_key ("PRICE_HIST"): + process_option ("price-db", os.getenv ("PRICE_HIST")) +if os.environ.has_key ("PRICE_EXP"): + process_option ("price-exp", os.getenv ("PRICE_EXP")) + +# If no argument remain, then no command word was given. Report the +# default help text and exit. + +if len (args) == 0: + option_help () + sys.exit (0) + +# The command word is in the first argument. Canonicalize it to a +# unique, simple form that the remaining code can use to find out +# which command was specified. + +command = args.pop (0); + +if command == "balance" or command == "bal" or command == "b": + command = "b" +elif command == "register" or command == "reg" or command == "r": + command = "r" +elif command == "print" or command == "p": + command = "p" +elif command == "output": + command = "w" +elif command == "emacs": + command = "x" +elif command == "xml": + command = "X" +elif command == "entry": + command = "e" +elif command == "equity": + command = "E" +elif command == "prices": + command = "P" +elif command == "pricesdb": + command = "D"; +else: + print "Unrecognized command:", command + sys.exit (1) + +# Create all the parser objects to be used. They are all registered, +# so that Ledger will try each one in turn whenever it is presented +# with a data file. They are attempted in reverse order to their +# registry. Note that Gnucash parsing is only available if the Ledger +# module was built with such support (which requires the expat C +# library). + +bin_parser = BinaryParser () +gnucash_parser = None +xml_parser = None +try: xml_parser = GnucashParser () +except: pass +try: gnucash_parser = GnucashParser () +except: pass +try: ofx_parser = OfxParser () +except: pass +qif_parser = QifParser () +text_parser = TextualParser () + +register_parser (bin_parser) +if xml_parser: + register_parser (xml_parser) +if gnucash_parser: + register_parser (gnucash_parser) +if ofx_parser: + register_parser (ofx_parser) +register_parser (qif_parser) +register_parser (text_parser) + +# Parse all entries from the user specified locations (found in +# 'config') into the journal object we created. The two parsers given +# as explicit arguments indicate: the parser to be used for standard +# input, and the parser to be used for cache files. + +parse_ledger_data (journal, bin_parser) + +# Now that everything has been correctly parsed (parse_ledger_data +# would have thrown an exception if not), we can take time to further +# process the configuration options. This changes the configuration a +# bit based on previous option settings, the command word, and the +# remaining arguments. + +config.process_options (command, args); + +# If the command is "e", use the method journal.derive_entry to create +# a brand new entry based on the arguments given. + +new_entry = None +if command == "e": + new_entry = derive_new_entry (journal, args) + if new_entry is None: + sys.exit (1) + +# Determine the format string to used, based on the command. + +if config.format_string: + format = config.format_string +elif command == "b": + format = config.balance_format +elif command == "r": + format = config.register_format +elif command == "E": + format = config.equity_format +elif command == "P": + min_val = 0 + def vmin(d, val): + global min_val + if not min_val or val < min_val: + min_val = val + return val + return min_val + + max_val = 0 + def vmax(d, val): + global max_val + if not max_val or val > max_val: + max_val = val + return val + return max_val + + format = config.prices_format +elif command == "D": + format = config.pricesdb_format +elif command == "w": + format = config.write_xact_format +else: + format = config.print_format + +# Configure the output file + +if config.output_file: + out = open (config.output_file, "w") +else: + out = sys.stdout + +# Set the final transaction handler: for balances and equity reports, +# it will simply add the value of the transaction to the account's +# xdata, which is used a bit later to report those totals. For all +# other reports, the transaction data is sent to the configured output +# location (default is sys.stdout). + +if command == "b" or command == "E": + handler = SetAccountValue () +elif command == "p" or command == "e": + handler = FormatEntries (out, format) +elif command == "x": + handler = FormatEmacsTransactions (out) +elif command == "X": + handler = FormatXmlEntries (out, config.show_totals) +else: + handler = FormatTransactions (out, format) + +if command == "w": + write_textual_journal(journal, args, handler, out); +else: + # Chain transaction filters on top of the base handler. Most of these + # filters customize the output for reporting. None of this is done + # for balance or equity reports, which don't need it. + + if not (command == "b" or command == "E"): + if config.head_entries or config.tail_entries: + handler = TruncateEntries (handler, config.head_entries, + config.tail_entries) + + if config.display_predicate: + handler = FilterTransactions (handler, config.display_predicate) + + handler = CalcTransactions (handler) + + if config.reconcile_balance: + reconcilable = False + if config.reconcile_balance == "": + reconcilable = True + else: + target_balance = Value (config.reconcile_balance) + + cutoff = time.time () + if config.reconcile_date: + cutoff = parse_date (config.reconcile_date) + + handler = ReconcileTransactions (handler, target_balance, + cutoff, reconcilable) + + if config.sort_string: + handler = SortTransactions (handler, config.sort_string) + + if config.show_revalued: + handler = ChangedValueTransactions (handler, + config.show_revalued_only) + + if config.show_collapsed: + handler = CollapseTransactions (handler); + + if config.show_subtotal and not (command == "b" or command == "E"): + handler = SubtotalTransactions (handler) + + if config.days_of_the_week: + handler = DowTransactions (handler) + elif config.by_payee: + handler = ByPayeeTransactions (handler) + + if config.report_period: + handler = IntervalTransactions (handler, config.report_period, + config.report_period_sort) + handler = SortTransactions (handler, "d") + + if compute_monthly_avg: + handler = ComputeMonthlyAvg (handler) + + # The next set of transaction filters are used by all reports. + + if config.show_inverted: + handler = InvertTransactions (handler) + + if config.show_related: + handler = RelatedTransactions (handler, config.show_all_related) + + if config.predicate: + handler = FilterTransactions (handler, config.predicate) + + if config.budget_flags: + handler = BudgetTransactions (handler, config.budget_flags) + handler.add_period_entries (journal) + elif config.forecast_limit: + handler = ForecastTransactions (handler, config.forecast_limit) + handler.add_period_entries (journal) + + if config.comm_as_payee: + handler = SetCommAsPayee (handler) + + # Walk the journal's entries, and pass each entry's transaction to the + # handler chain established above. And although a journal's entries + # can be walked using Python, it is significantly faster to do this + # simple walk in C++, using `walk_entries'. + # + # if command == "e": + # for xact in new_entry: + # handler (xact) + # else: + # for entry in journal: + # for xact in entry: + # handler (xact) + + if command == "e": + walk_transactions (new_entry, handler) + elif command == "P" or command == "D": + walk_commodities (handler) + else: + walk_entries (journal, handler) + + # Flush the handlers, causing them to output whatever data is still + # pending. + + if command != "P" and command != "D": + handler.flush () + +# For the balance and equity reports, the account totals now need to +# be displayed. This is different from outputting transactions, in +# that we are now outputting account totals to display a summary of +# the transactions that were just walked. + +if command == "b": + acct_formatter = FormatAccount (out, format, config.display_predicate) + sum_accounts (journal.master) + walk_accounts (journal.master, acct_formatter, config.sort_string) + acct_formatter.final (journal.master) + acct_formatter.flush () + + if account_has_xdata (journal.master): + xdata = account_xdata (journal.master) + if not config.show_collapsed and xdata.total: + out.write("--------------------\n") + xdata.value = xdata.total + # jww (2005-02-15): yet to convert + #acct_formatter.format.format (out, details_t (journal.master)) + +elif command == "E": + acct_formatter = FormatEquity (out, format, config.display_predicate) + sum_accounts (journal.master) + walk_accounts (journal.master, acct_formatter, config.sort_string) + acct_formatter.flush () + +# If it were important to clean things up, we would have to clear out +# the accumulated xdata at this point: + +#clear_all_xdata () + +# If the cache is being used, and is dirty, update it now. + +if config.use_cache and config.cache_dirty and config.cache_file: + write_binary_journal (config.cache_file, journal); + +# We're done! diff --git a/mask.cc b/mask.cc index 972b24ce..959df8ea 100644 --- a/mask.cc +++ b/mask.cc @@ -1,14 +1,42 @@ -#include "mask.h" -#include "debug.h" -#include "util.h" +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ -#include +#include "mask.h" -#include +namespace ledger { -mask_t::mask_t(const std::string& pat) : exclude(false) +mask_t::mask_t(const string& pat) : exclude(false) { const char * p = pat.c_str(); + if (*p == '-') { exclude = true; p++; @@ -20,34 +48,8 @@ mask_t::mask_t(const std::string& pat) : exclude(false) while (std::isspace(*p)) p++; } - pattern = p; - - const char *error; - int erroffset; - regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS, - &error, &erroffset, NULL); - if (! regexp) - throw new mask_error(std::string("Failed to compile regexp '") + - pattern + "'"); -} -mask_t::mask_t(const mask_t& m) : exclude(m.exclude), pattern(m.pattern) -{ - const char *error; - int erroffset; - regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS, - &error, &erroffset, NULL); - assert(regexp); -} - -mask_t::~mask_t() { - pcre_free((pcre *)regexp); + expr.assign(p); } -bool mask_t::match(const std::string& str) const -{ - static int ovec[30]; - int result = pcre_exec((pcre *)regexp, NULL, - str.c_str(), str.length(), 0, 0, ovec, 30); - return result >= 0 && ! exclude; -} +} // namespace ledger diff --git a/mask.h b/mask.h index 6d4790a3..daae014f 100644 --- a/mask.h +++ b/mask.h @@ -1,29 +1,55 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + #ifndef _MASK_H #define _MASK_H -#include -#include +#include "utils.h" -#include "error.h" +namespace ledger { class mask_t { public: - bool exclude; - std::string pattern; - void * regexp; + bool exclude; + boost::regex expr; - explicit mask_t(const std::string& pattern); - mask_t(const mask_t&); - ~mask_t(); + explicit mask_t(const string& pattern); + mask_t(const mask_t& m) : exclude(m.exclude), expr(m.expr) {} - bool match(const std::string& str) const; + bool match(const string& str) const { + return boost::regex_match(str, expr) && ! exclude; + } }; -class mask_error : public error { - public: - mask_error(const std::string& reason) throw() : error(reason) {} - virtual ~mask_error() throw() {} -}; +} // namespace ledger #endif // _MASK_H diff --git a/parser.h b/parser.h index 6178d293..cea67e41 100644 --- a/parser.h +++ b/parser.h @@ -57,6 +57,85 @@ class parse_error : public error { virtual ~parse_error() throw() {} }; +/************************************************************************ + * + * General utility parsing functions + */ + +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 // _PARSER_H diff --git a/pushvar.h b/pushvar.h new file mode 100644 index 00000000..98944481 --- /dev/null +++ b/pushvar.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file 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 +class push_variable : public boost::noncopyable +{ + T& var; + T prev; + bool enabled; + +public: + explicit push_variable(T& _var) + : var(_var), prev(var), enabled(true) {} + explicit push_variable(T& _var, const T& value) + : var(_var), prev(var), enabled(true) { + var = value; + } + ~push_variable() { + if (enabled) + var = prev; + } + + void clear() { + enabled = false; + } +}; + +#endif // _PUSHVAR_H diff --git a/py_amount.cc b/py_amount.cc new file mode 100644 index 00000000..07be1512 --- /dev/null +++ b/py_amount.cc @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pyinterp.h" +#include "pyutils.h" +#include "amount.h" + +#include +#include +#include + +namespace ledger { + +using namespace boost::python; + +amount_t py_round_0(const amount_t& amount) { + return amount.round(); +} +amount_t py_round_1(const amount_t& amount, amount_t::precision_t prec) { + return amount.round(prec); +} + +double py_to_double_0(amount_t& amount) { + return amount.to_double(); +} +double py_to_double_1(amount_t& amount, bool no_check) { + return amount.to_double(no_check); +} + +long py_to_long_0(amount_t& amount) { + return amount.to_long(); +} +long py_to_long_1(amount_t& amount, bool no_check) { + return amount.to_long(no_check); +} + +boost::optional py_value_0(const amount_t& amount) { + return amount.value(); +} +boost::optional py_value_1(const amount_t& amount, + const boost::optional& moment) { + return amount.value(moment); +} + +void py_parse_2(amount_t& amount, object in, unsigned char flags) { + if (PyFile_Check(in.ptr())) { + pyifstream instr(reinterpret_cast(in.ptr())); + amount.parse(instr, flags); + } else { + PyErr_SetString(PyExc_IOError, + "Argument to amount.parse(file) is not a file object"); + } +} +void py_parse_1(amount_t& amount, object in) { + py_parse_2(amount, in, 0); +} + +void py_parse_str_1(amount_t& amount, const string& str) { + amount.parse(str); +} +void py_parse_str_2(amount_t& amount, const string& str, unsigned char flags) { + amount.parse(str, flags); +} + +void py_read_1(amount_t& amount, object in) { + if (PyFile_Check(in.ptr())) { + pyifstream instr(reinterpret_cast(in.ptr())); + amount.read(instr); + } else { + PyErr_SetString(PyExc_IOError, + "Argument to amount.parse(file) is not a file object"); + } +} +void py_read_2(amount_t& amount, const std::string& str) { + const char * p = str.c_str(); + amount.read(p); +} + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_ArithmeticError, err.what()); \ + } + +EXC_TRANSLATOR(amount_error) + +void export_amount() +{ + scope().attr("AMOUNT_PARSE_NO_MIGRATE") = AMOUNT_PARSE_NO_MIGRATE; + scope().attr("AMOUNT_PARSE_NO_REDUCE") = AMOUNT_PARSE_NO_REDUCE; + + class_< amount_t > ("amount") +#if 0 + .def("initialize", &amount_t::initialize) + .staticmethod("initialize") + .def("shutdown", &amount_t::shutdown) + .staticmethod("shutdown") +#endif + + .add_static_property("current_pool", + make_getter(&amount_t::current_pool, + return_value_policy())) + + .add_static_property("keep_base", &amount_t::keep_base) + + .add_static_property("keep_price", &amount_t::keep_price) + .add_static_property("keep_date", &amount_t::keep_date) + .add_static_property("keep_tag", &amount_t::keep_tag) + + .add_static_property("stream_fullstrings", &amount_t::stream_fullstrings) + + .def(init()) + .def(init()) + .def(init()) + + .def("exact", &amount_t::exact, args("value"), + "Construct an amount object whose display precision is always equal to its\n\ +internal precision.") + .staticmethod("exact") + + .def(init()) + + .def("compare", &amount_t::compare) + + .def(self == self) + .def(self == long()) + .def(long() == self) + .def(self == double()) + .def(double() == self) + + .def(self != self) + .def(self != long()) + .def(long() != self) + .def(self != double()) + .def(double() != self) + + .def(! self) + + .def(self < self) + .def(self < long()) + .def(long() < self) + .def(self < double()) + .def(double() < self) + + .def(self <= self) + .def(self <= long()) + .def(long() <= self) + .def(self <= double()) + .def(double() <= self) + + .def(self > self) + .def(self > long()) + .def(long() > self) + .def(self > double()) + .def(double() > self) + + .def(self >= self) + .def(self >= long()) + .def(long() >= self) + .def(self >= double()) + .def(double() >= self) + + .def(self += self) + .def(self += long()) + .def(self += double()) + + .def(self + self) + .def(self + long()) + .def(long() + self) + .def(self + double()) + .def(double() + self) + + .def(self -= self) + .def(self -= long()) + .def(self -= double()) + + .def(self - self) + .def(self - long()) + .def(long() - self) + .def(self - double()) + .def(double() - self) + + .def(self *= self) + .def(self *= long()) + .def(self *= double()) + + .def(self * self) + .def(self * long()) + .def(long() * self) + .def(self * double()) + .def(double() * self) + + .def(self /= self) + .def(self /= long()) + .def(self /= double()) + + .def(self / self) + .def(self / long()) + .def(long() / self) + .def(self / double()) + .def(double() / self) + + .add_property("precision", &amount_t::precision) + + .def("negate", &amount_t::negate) + .def("in_place_negate", &amount_t::in_place_negate, + return_value_policy()) + .def(- self) + + .def("abs", &amount_t::abs) + .def("__abs__", &amount_t::abs) + + .def("round", py_round_0) + .def("round", py_round_1) + .def("unround", &amount_t::unround) + + .def("reduce", &amount_t::reduce) + .def("in_place_reduce", &amount_t::in_place_reduce, + return_value_policy()) + + .def("unreduce", &amount_t::unreduce) + .def("in_place_unreduce", &amount_t::in_place_unreduce, + return_value_policy()) + + .def("value", py_value_0) + .def("value", py_value_1) + + .def("sign", &amount_t::sign) + .def("__nonzero__", &amount_t::is_nonzero) + .def("is_nonzero", &amount_t::is_nonzero) + .def("is_zero", &amount_t::is_zero) + .def("is_realzero", &amount_t::is_realzero) + .def("is_null", &amount_t::is_null) + + .def("to_double", py_to_double_0) + .def("to_double", py_to_double_1) + .def("__float__", py_to_double_0) + .def("to_long", py_to_long_0) + .def("to_long", py_to_long_1) + .def("__int__", py_to_long_0) + .def("to_string", &amount_t::to_string) + .def("__str__", &amount_t::to_string) + .def("to_fullstring", &amount_t::to_fullstring) + .def("__repr__", &amount_t::to_fullstring) + + .def("fits_in_double", &amount_t::fits_in_double) + .def("fits_in_long", &amount_t::fits_in_long) + + .add_property("quantity_string", &amount_t::quantity_string) + + .add_property("commodity", + make_function(&amount_t::commodity, + return_value_policy()), + make_function(&amount_t::set_commodity, + with_custodian_and_ward<1, 2>())) + + .def("has_commodity", &amount_t::has_commodity) + .def("clear_commodity", &amount_t::clear_commodity) + .add_property("number", &amount_t::number) + + .def("annotate_commodity", &amount_t::annotate_commodity) + .def("commodity_annotated", &amount_t::commodity_annotated) + .add_property("annotation_details", &amount_t::annotation_details) + .def("strip_annotations", &amount_t::strip_annotations) + + .def("parse", py_parse_1) + .def("parse", py_parse_2) + .def("parse", py_parse_str_1) + .def("parse", py_parse_str_2) + + .def("parse_conversion", &amount_t::parse_conversion) + .staticmethod("parse_conversion") + + .def("read", py_read_1) + .def("read", py_read_2) + .def("write", &amount_t::write) + + .def("valid", &amount_t::valid) + ; + + register_optional_to_python(); + + implicitly_convertible(); + implicitly_convertible(); + implicitly_convertible(); + +#define EXC_TRANSLATE(type) \ + register_exception_translator(&exc_translate_ ## type); + + EXC_TRANSLATE(amount_error); +} + +} // namespace ledger diff --git a/py_commodity.cc b/py_commodity.cc new file mode 100644 index 00000000..0dab3cd3 --- /dev/null +++ b/py_commodity.cc @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pyinterp.h" +#include "pyutils.h" +#include "amount.h" + +#include +#include + +namespace ledger { + +using namespace boost::python; + +void export_commodity() +{ + scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; + scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; + scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; + scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN; + scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; + scope().attr("COMMODITY_STYLE_NOMARKET") = COMMODITY_STYLE_NOMARKET; + scope().attr("COMMODITY_STYLE_BUILTIN") = COMMODITY_STYLE_BUILTIN; + + class_< commodity_t, bases<>, + commodity_t, boost::noncopyable > ("commodity", no_init) + .def(self == self) + + .def("drop_flags", &commodity_t::drop_flags) + + .add_property("precision", &commodity_t::precision) + ; +} + +} // namespace ledger diff --git a/py_times.cc b/py_times.cc new file mode 100644 index 00000000..173f21fa --- /dev/null +++ b/py_times.cc @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pyinterp.h" +#include "pyutils.h" + +#include +#include +#include +#include + +#include +#include + +// jww (2007-05-04): Convert time duration objects to PyDelta + +namespace ledger { + +using namespace boost::python; + +typedef boost::gregorian::date date; + +struct date_to_python +{ + static PyObject* convert(const date& dte) + { + PyDateTime_IMPORT; + return PyDate_FromDate(dte.year(), dte.month(), dte.day()); + } +}; + +struct date_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + PyDateTime_IMPORT; + if (PyDate_Check(obj_ptr)) return obj_ptr; + return 0; + } + + static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data) + { + PyDateTime_IMPORT; + int y = PyDateTime_GET_YEAR(obj_ptr); + int m = PyDateTime_GET_MONTH(obj_ptr); + int d = PyDateTime_GET_DAY(obj_ptr); + date* dte = new date(y,m,d); + data->convertible = (void*)dte; + } +}; + +typedef register_python_conversion + date_python_conversion; + + +struct datetime_to_python +{ + static PyObject* convert(const moment_t& moment) + { + PyDateTime_IMPORT; + date dte = moment.date(); + moment_t::time_duration_type tod = moment.time_of_day(); + return PyDateTime_FromDateAndTime(dte.year(), dte.month(), dte.day(), + tod.hours(), tod.minutes(), tod.seconds(), + tod.total_microseconds() % 1000000); + } +}; + +struct datetime_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + PyDateTime_IMPORT; + if(PyDateTime_Check(obj_ptr)) return obj_ptr; + return 0; + } + + static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data) + { + PyDateTime_IMPORT; + int y = PyDateTime_GET_YEAR(obj_ptr); + int m = PyDateTime_GET_MONTH(obj_ptr); + int d = PyDateTime_GET_DAY(obj_ptr); + int h = PyDateTime_DATE_GET_HOUR(obj_ptr); + int min = PyDateTime_DATE_GET_MINUTE(obj_ptr); + int s = PyDateTime_DATE_GET_SECOND(obj_ptr); + moment_t* moment = new moment_t(date(y,m,d), + moment_t::time_duration_type(h, min, s)); + data->convertible = (void*)moment; + } +}; + +typedef register_python_conversion + datetime_python_conversion; + +void export_times() +{ + date_python_conversion(); + datetime_python_conversion(); + + register_optional_to_python(); +} + +} // namespace ledger diff --git a/py_utils.cc b/py_utils.cc new file mode 100644 index 00000000..50ce9712 --- /dev/null +++ b/py_utils.cc @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pyinterp.h" +#include "pyutils.h" + +#include +#include +#include + +namespace ledger { + +using namespace boost::python; + +struct bool_to_python +{ + static PyObject * convert(const bool truth) + { + if (truth) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; + } +}; + +struct bool_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + if (!PyBool_Check(obj_ptr)) return 0; + return obj_ptr; + } + + static void construct(PyObject* obj_ptr, + converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*) data)->storage.bytes; + if (obj_ptr == Py_True) + new (storage) bool(true); + else + new (storage) bool(false); + data->convertible = storage; + } +}; + +typedef register_python_conversion + bool_python_conversion; + + +struct string_to_python +{ + static PyObject* convert(const string& str) + { + return incref(object(*boost::polymorphic_downcast(&str)).ptr()); + } +}; + +struct string_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + if (!PyString_Check(obj_ptr)) return 0; + return obj_ptr; + } + + static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data) + { + const char* value = PyString_AsString(obj_ptr); + if (value == 0) throw_error_already_set(); + void* storage = ((converter::rvalue_from_python_storage*) data)->storage.bytes; + new (storage) string(value); + data->convertible = storage; + } +}; + +typedef register_python_conversion + string_python_conversion; + + +struct istream_to_python +{ + static PyObject* convert(const std::istream& str) + { + return incref(boost::python::detail::none()); + } +}; + +struct istream_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + if (!PyFile_Check(obj_ptr)) return 0; + return obj_ptr; + } + + static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*) data)->storage.bytes; + new (storage) pyifstream(reinterpret_cast(obj_ptr)); + data->convertible = storage; + } +}; + +typedef register_python_conversion + istream_python_conversion; + + +struct ostream_to_python +{ + static PyObject* convert(const std::ostream& str) + { + return incref(boost::python::detail::none()); + } +}; + +struct ostream_from_python +{ + static void* convertible(PyObject* obj_ptr) + { + if (!PyFile_Check(obj_ptr)) return 0; + return obj_ptr; + } + + static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*) data)->storage.bytes; + new (storage) pyofstream(reinterpret_cast(obj_ptr)); + data->convertible = storage; + } +}; + +typedef register_python_conversion + ostream_python_conversion; + + +void export_utils() +{ + bool_python_conversion(); + string_python_conversion(); + istream_python_conversion(); + ostream_python_conversion(); +} + +} // namespace ledger diff --git a/pyfstream.h b/pyfstream.h new file mode 100644 index 00000000..f4650e0a --- /dev/null +++ b/pyfstream.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PYFSTREAM_H +#define _PYFSTREAM_H + +// pyofstream +// - a stream that writes on a Python file object + +class pyoutbuf : public std::streambuf { + protected: + PyFileObject * fo; // Python file object + public: + // constructor + pyoutbuf (PyFileObject * _fo) : fo(_fo) {} + + protected: + // write one character + virtual int_type overflow (int_type c) { + if (c != EOF) { + char z[2]; + z[0] = c; + z[1] = '\0'; + if (PyFile_WriteString(z, (PyObject *)fo) < 0) { + return EOF; + } + } + return c; + } + + // write multiple characters + virtual std::streamsize xsputn (const char* s, std::streamsize num) { + char * buf = new char[num + 1]; + std::strncpy(buf, s, num); + buf[num] = '\0'; + if (PyFile_WriteString(buf, (PyObject *)fo) < 0) + num = 0; + delete[] buf; + return num; + } +}; + +class pyofstream : public std::ostream { + protected: + pyoutbuf buf; + public: + pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) { + rdbuf(&buf); + } +}; + +// pyifstream +// - a stream that reads on a file descriptor + +class pyinbuf : public std::streambuf { + protected: + PyFileObject * fo; // Python file object + protected: + /* data buffer: + * - at most, pbSize characters in putback area plus + * - at most, bufSize characters in ordinary read buffer + */ + static const int pbSize = 4; // size of putback area + static const int bufSize = 1024; // size of the data buffer + char buffer[bufSize + pbSize]; // data buffer + + public: + /* constructor + * - initialize file descriptor + * - initialize empty data buffer + * - no putback area + * => force underflow() + */ + pyinbuf (PyFileObject * _fo) : fo(_fo) { + setg (buffer+pbSize, // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize); // end position + } + + protected: + // insert new characters into the buffer + virtual int_type underflow () { +#ifndef _MSC_VER + using std::memmove; +#endif + + // is read position before end of buffer? + if (gptr() < egptr()) { + return traits_type::to_int_type(*gptr()); + } + + /* process size of putback area + * - use number of characters read + * - but at most size of putback area + */ + int numPutback; + numPutback = gptr() - eback(); + if (numPutback > pbSize) { + numPutback = pbSize; + } + + /* copy up to pbSize characters previously read into + * the putback area + */ + memmove (buffer+(pbSize-numPutback), gptr()-numPutback, + numPutback); + + // read at most bufSize new characters + int num; + PyObject *line = PyFile_GetLine((PyObject *)fo, bufSize); + if (! line || ! PyString_Check(line)) { + // ERROR or EOF + return EOF; + } + + num = PyString_Size(line); + if (num == 0) + return EOF; + + memmove (buffer+pbSize, PyString_AsString(line), num); + + // reset buffer pointers + setg (buffer+(pbSize-numPutback), // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize+num); // end of buffer + + // return next character + return traits_type::to_int_type(*gptr()); + } +}; + +class pyifstream : public std::istream { + protected: + pyinbuf buf; + public: + pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) { + rdbuf(&buf); + } +}; + +#endif // _PYFSTREAM_H diff --git a/pyinterp.cc b/pyinterp.cc new file mode 100644 index 00000000..d521a0ee --- /dev/null +++ b/pyinterp.cc @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pyinterp.h" + +#include + +namespace ledger { + +using namespace boost::python; + +void export_utils(); +void export_times(); +void export_amount(); +void export_commodity(); +#if 0 +void export_balance(); +void export_value(); +void export_journal(); +void export_parser(); +void export_option(); +void export_walk(); +void export_report(); +void export_format(); +void export_valexpr(); +#endif + +void initialize_for_python() +{ + export_utils(); + export_times(); + export_amount(); + export_commodity(); +#if 0 + export_balance(); + export_value(); + export_journal(); + export_parser(); + export_option(); + export_walk(); + export_format(); + export_report(); + export_valexpr(); +#endif +} + +struct python_run +{ + object result; + + python_run(python_interpreter_t * intepreter, + const string& str, int input_mode) + : result(handle<>(borrowed(PyRun_String(str.c_str(), input_mode, + intepreter->nspace.ptr(), + intepreter->nspace.ptr())))) {} + operator object() { + return result; + } +}; + +python_interpreter_t::python_interpreter_t(xml::xpath_t::scope_t& parent) + : xml::xpath_t::symbol_scope_t(parent), + mmodule(borrowed(PyImport_AddModule("__main__"))), + nspace(handle<>(borrowed(PyModule_GetDict(mmodule.get())))) +{ + Py_Initialize(); + boost::python::detail::init_module("ledger", &initialize_for_python); +} + +object python_interpreter_t::import(const string& str) +{ + assert(Py_IsInitialized()); + + try { + PyObject * mod = PyImport_Import(PyString_FromString(str.c_str())); + if (! mod) + throw_(std::logic_error, "Failed to import Python module " << str); + + object newmod(handle<>(borrowed(mod))); + +#if 1 + // Import all top-level entries directly into the main namespace + dict m_nspace(handle<>(borrowed(PyModule_GetDict(mod)))); + nspace.update(m_nspace); +#else + nspace[string(PyModule_GetName(mod))] = newmod; +#endif + return newmod; + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::logic_error, "Importing Python module " << str); + } + return object(); +} + +object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode) +{ + bool first = true; + string buffer; + buffer.reserve(4096); + + while (! in.eof()) { + char buf[256]; + in.getline(buf, 255); + if (buf[0] == '!') + break; + if (first) + first = false; + else + buffer += "\n"; + buffer += buf; + } + + try { + int input_mode; + switch (mode) { + case PY_EVAL_EXPR: input_mode = Py_eval_input; break; + case PY_EVAL_STMT: input_mode = Py_single_input; break; + case PY_EVAL_MULTI: input_mode = Py_file_input; break; + } + assert(Py_IsInitialized()); + return python_run(this, buffer, input_mode); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::logic_error, "Evaluating Python code"); + } + return object(); +} + +object python_interpreter_t::eval(const string& str, py_eval_mode_t mode) +{ + try { + int input_mode; + switch (mode) { + case PY_EVAL_EXPR: input_mode = Py_eval_input; break; + case PY_EVAL_STMT: input_mode = Py_single_input; break; + case PY_EVAL_MULTI: input_mode = Py_file_input; break; + } + assert(Py_IsInitialized()); + return python_run(this, str, input_mode); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::logic_error, "Evaluating Python code"); + } + return object(); +} + +value_t python_interpreter_t::functor_t::operator() + (xml::xpath_t::call_scope_t& args) +{ + try { + if (! PyCallable_Check(func.ptr())) { + return extract(func.ptr()); + } else { + if (args.size() > 0) { + list arglist; + if (args.value().is_sequence()) + foreach (const value_t& value, args.value().as_sequence()) + arglist.append(value); + else + arglist.append(args.value()); + + if (PyObject * val = + PyObject_CallObject(func.ptr(), + boost::python::tuple(arglist).ptr())) { + value_t result = extract(val)(); + Py_DECREF(val); + return result; + } + else if (PyObject * err = PyErr_Occurred()) { + PyErr_Print(); + throw_(xml::xpath_t::calc_error, + "While calling Python function '" /*<< name() <<*/ "': " << err); + } else { + assert(false); + } + } else { + return call(func.ptr()); + } + } + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(xml::xpath_t::calc_error, + "While calling Python function '" /*<< name() <<*/ "'"); + } + return NULL_VALUE; +} + +value_t python_interpreter_t::lambda_t::operator() + (xml::xpath_t::call_scope_t& args) +{ + try { + assert(args.size() == 1); + value_t item = args[0]; + assert(item.is_xml_node()); + return call(func.ptr(), item.as_xml_node()); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(xml::xpath_t::calc_error, + "While evaluating Python lambda expression"); + } + return NULL_VALUE; +} + +} // namespace ledger diff --git a/pyinterp.h b/pyinterp.h new file mode 100644 index 00000000..3d69d972 --- /dev/null +++ b/pyinterp.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PYINTERP_H +#define _PYINTERP_H + +#include "xpath.h" + +#include +#include + +namespace ledger { + +class python_interpreter_t : public xml::xpath_t::symbol_scope_t +{ + boost::python::handle<> mmodule; + + public: + boost::python::dict nspace; + + python_interpreter_t(xml::xpath_t::scope_t& parent); + + virtual ~python_interpreter_t() { + Py_Finalize(); + } + + boost::python::object import(const string& name); + + enum py_eval_mode_t { + PY_EVAL_EXPR, + PY_EVAL_STMT, + PY_EVAL_MULTI + }; + + boost::python::object eval(std::istream& in, + py_eval_mode_t mode = PY_EVAL_EXPR); + boost::python::object eval(const string& str, + py_eval_mode_t mode = PY_EVAL_EXPR); + boost::python::object eval(const char * c_str, + py_eval_mode_t mode = PY_EVAL_EXPR) { + string str(c_str); + return eval(str, mode); + } + + class functor_t { + protected: + boost::python::object func; + public: + functor_t(const string& name, boost::python::object _func) : func(_func) {} + virtual ~functor_t() {} + virtual value_t operator()(xml::xpath_t::call_scope_t& args); + }; + + virtual xml::xpath_t::ptr_op_t lookup(const string& name) { + if (boost::python::object func = eval(name)) + return WRAP_FUNCTOR(functor_t(name, func)); + else + return xml::xpath_t::symbol_scope_t::lookup(name); + } + + class lambda_t : public functor_t { + public: + lambda_t(boost::python::object code) : functor_t("", code) {} + virtual value_t operator()(xml::xpath_t::call_scope_t& args); + }; +}; + +} // namespace ledger + +#endif // _PYINTERP_H diff --git a/pyledger.cc b/pyledger.cc new file mode 100644 index 00000000..ebbdc82e --- /dev/null +++ b/pyledger.cc @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +using namespace boost::python; + +ledger::session_t python_session; + +namespace ledger { + extern void initialize_for_python(); +} + +BOOST_PYTHON_MODULE(ledger) +{ + ledger::set_session_context(&python_session); + ledger::initialize_for_python(); +} diff --git a/pyledger.h b/pyledger.h new file mode 100644 index 00000000..3ab82558 --- /dev/null +++ b/pyledger.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PYLEDGER_H +#define _PYLEDGER_H + +////////////////////////////////////////////////////////////////////// +// +// Ledger Accounting Tool (with Python support via Boost.Python) +// +// A command-line tool for general double-entry accounting. +// +// Copyright (c) 2003,2004 John Wiegley +// + +#include +#include + +#endif // _PYLEDGER_H diff --git a/pyutils.h b/pyutils.h new file mode 100644 index 00000000..41bbbfde --- /dev/null +++ b/pyutils.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PY_UTILS_H +#define _PY_UTILS_H + +#include "pyfstream.h" + +template +struct object_from_python +{ + object_from_python() { + boost::python::converter::registry::push_back + (&TfromPy::convertible, &TfromPy::construct, + boost::python::type_id()); + } +}; + +template +struct register_python_conversion +{ + register_python_conversion() { + boost::python::to_python_converter(); + object_from_python(); + } +}; + +template +struct register_optional_to_python : public boost::noncopyable +{ + struct optional_to_python + { + static PyObject * convert(const boost::optional& value) + { + return boost::python::incref + (value ? boost::python::to_python_value()(*value) : + boost::python::detail::none()); + } + }; + + struct optional_from_python + { + static void * convertible(PyObject * source) + { + using namespace boost::python::converter; + + if (source == Py_None) + return source; + + const registration& converters(registered::converters); + + if (implicit_rvalue_convertible_from_python(source, converters)) { + rvalue_from_python_stage1_data data = + rvalue_from_python_stage1(source, converters); + return rvalue_from_python_stage2(source, data, converters); + } + return NULL; + } + + static void construct(PyObject * source, + boost::python::converter::rvalue_from_python_stage1_data * data) + { + using namespace boost::python::converter; + + void * const storage = ((rvalue_from_python_storage *) data)->storage.bytes; + + if (data->convertible == source) // == None + new (storage) boost::optional(); // A Boost uninitialized value + else + new (storage) boost::optional(*static_cast(data->convertible)); + + data->convertible = storage; + } + }; + + explicit register_optional_to_python() { + register_python_conversion, + optional_to_python, optional_from_python>(); + } +}; + +//boost::python::register_ptr_to_python< boost::shared_ptr >(); + +#endif // _PY_UTILS_H diff --git a/setup.py b/setup.py index 9e2cbbd5..b8137e4e 100755 --- a/setup.py +++ b/setup.py @@ -4,15 +4,17 @@ from distutils.core import setup, Extension import os -libs = ["amounts", "boost_python", "gmp"] +defines = [('PYTHON_MODULE', 1)] +libs = os.environ["PYLIBS"].split() -setup(name = "Amounts", - version = "2.6.0.90", - description = "Amounts and Commodities Library", +setup(name = "Ledger", + version = "2.7", + description = "Ledger Accounting Library", author = "John Wiegley", author_email = "johnw@newartisans.com", - url = "http://www.newartisans.com/johnw/", + url = "http://www.newartisans.com/software/ledger.html", ext_modules = [ - Extension("amounts", ["amounts.cc"], - define_macros = [('PYTHON_MODULE', 1)], - libraries = libs)]) + Extension("ledger", + [os.path.join(os.environ['SRCDIR'], + "src", "python", "pyledger.cc")], + define_macros = defines, libraries = libs)]) diff --git a/system.hh b/system.hh new file mode 100644 index 00000000..1621ac85 --- /dev/null +++ b/system.hh @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SYSTEM_HH +#define _SYSTEM_HH + +/** + * @file system.hh + * @author John Wiegley + * @date Mon Apr 23 03:43:05 2007 + * + * @brief All system headers needed by Ledger. + * + * These are collected here so that a pre-compiled header can be made. + * None of these header files (with the exception of acconf.h, when + * configure is re-run) are expected to change. + */ + +#include "acconf.h" + +#if defined(__GNUG__) && __GNUG__ < 3 +#define _XOPEN_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__GNUG__) && __GNUG__ < 3 +namespace std { + inline ostream & right (ostream & i) { + i.setf(i.right, i.adjustfield); + return i; + } + inline ostream & left (ostream & i) { + i.setf(i.left, i.adjustfield); + return i; + } +} +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if defined __FreeBSD__ && __FreeBSD__ <= 4 +// FreeBSD has a broken isspace macro, so don't use it +#undef isspace(c) +#endif + +#include + +#ifdef WIN32 +#include +#else +#include +#endif + +#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM) +#include +#endif + +#if defined(HAVE_NL_LANGINFO) +#include +#endif + +#include + +extern "C" { +#if defined(HAVE_EXPAT) +#include // expat XML parser +#elif defined(HAVE_XMLPARSE) +#include // expat XML parser +#endif +} + +#if defined(HAVE_LIBOFX) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // _SYSTEM_HH diff --git a/times.cc b/times.cc new file mode 100644 index 00000000..fc6f2f1b --- /dev/null +++ b/times.cc @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils.h" + +namespace ledger { + +#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK +const ptime time_now = boost::posix_time::microsec_clock::universal_time(); +#else +const ptime time_now = boost::posix_time::second_clock::universal_time(); +#endif +const date date_now = boost::gregorian::day_clock::universal_day(); + +#ifdef SUPPORT_DATE_AND_TIME +const moment_t& now(time_now); +#else +const moment_t& now(date_now); +#endif + +bool day_before_month = false; +static bool day_before_month_initialized = false; + +moment_t parse_datetime(const char * str) +{ + if (! day_before_month_initialized) { +#ifdef HAVE_NL_LANGINFO + const char * d_fmt = nl_langinfo(D_FMT); + if (d_fmt && std::strlen(d_fmt) > 1 && d_fmt[1] == 'd') + day_before_month = true; + day_before_month_initialized = true; +#endif + } +#if 0 + return parse_abs_datetime(in); +#else + int year = ((str[0] - '0') * 1000 + + (str[1] - '0') * 100 + + (str[2] - '0') * 10 + + (str[3] - '0')); + + int mon = ((str[5] - '0') * 10 + + (str[6] - '0')); + + int day = ((str[8] - '0') * 10 + + (str[9] - '0')); + + return moment_t(boost::gregorian::date(year, mon, day)); +#endif +} + +} // namespace ledger diff --git a/times.h b/times.h new file mode 100644 index 00000000..de1dd26a --- /dev/null +++ b/times.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TIMES_H +#define _TIMES_H + +namespace ledger { + +#define SUPPORT_DATE_AND_TIME 1 +#ifdef SUPPORT_DATE_AND_TIME + +typedef boost::posix_time::ptime moment_t; +typedef moment_t::time_duration_type duration_t; + +inline bool is_valid_moment(const moment_t& moment) { + return ! moment.is_not_a_date_time(); +} + +#else // SUPPORT_DATE_AND_TIME + +typedef boost::gregorian::date moment_t; +typedef boost::gregorian::date_duration duration_t; + +inline bool is_valid_moment(const moment_t& moment) { + return ! moment.is_not_a_date(); +} + +#endif // SUPPORT_DATE_AND_TIME + +extern const moment_t& now; + +DECLARE_EXCEPTION(error, datetime_error); + +class interval_t +{ +public: + interval_t() {} + interval_t(const string&) {} + + operator bool() const { + return false; + } + + void start(const moment_t&) {} + moment_t next() const { return moment_t(); } + + void parse(std::istream&) {} +}; + +#if 0 +inline moment_t ptime_local_to_utc(const moment_t& when) { + struct std::tm tm_gmt = to_tm(when); + return boost::posix_time::from_time_t(std::mktime(&tm_gmt)); +} + +// jww (2007-04-18): I need to make a general parsing function +// instead, and then make these into private methods. +inline moment_t ptime_from_local_date_string(const string& date_string) { + return ptime_local_to_utc(moment_t(boost::gregorian::from_string(date_string), + time_duration())); +} + +inline moment_t ptime_from_local_time_string(const string& time_string) { + return ptime_local_to_utc(boost::posix_time::time_from_string(time_string)); +} +#endif + +moment_t parse_datetime(const char * str); + +inline moment_t parse_datetime(const string& str) { + return parse_datetime(str.c_str()); +} + +extern const ptime time_now; +extern const date date_now; +extern bool day_before_month; + +#if 0 +struct intorchar +{ + int ival; + string sval; + + intorchar() : ival(-1) {} + intorchar(int val) : ival(val) {} + intorchar(const string& val) : ival(-1), sval(val) {} + intorchar(const intorchar& o) : ival(o.ival), sval(o.sval) {} +}; + +ledger::moment_t parse_abs_datetime(std::istream& input); +#endif + +} // namespace ledger + +#endif // _TIMES_H diff --git a/timing.h b/timing.h deleted file mode 100644 index 7e1029ea..00000000 --- a/timing.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef _TIMING_H -#define _TIMING_H - -#include "debug.h" - -#include - -namespace ledger { - -class timing_t -{ - public: - std::clock_t begin; - std::clock_t cumulative; - std::string file; - unsigned long line; - std::string symbol; - std::string category; - - timing_t(const std::string& _symbol, const std::string& _category) - : begin(0), cumulative(0), symbol(_symbol), category(_category) {} - - timing_t(const std::string& _symbol) - : begin(0), cumulative(0), symbol(_symbol) {} - - ~timing_t() { - std::string cls = "timing.results."; - cls += symbol; - DEBUG_PRINT(cls.c_str(), file << ":" << line << ": " - << category << " = " - << (double(cumulative) / double(CLOCKS_PER_SEC)) << "s"); - } - - void start(const std::string& _file, unsigned long _line) { - file = _file; - line = _line; - begin = std::clock(); - } - void start() { - begin = std::clock(); - } - - void stop() { - cumulative += std::clock() - begin; - } -}; - -#ifdef DEBUG_ENABLED -#define TIMER_DEF(sym, cat) static timing_t sym(#sym, cat) -#define TIMER_DEF_(sym) static timing_t sym(#sym, #sym) -#define TIMER_START(sym) sym.start(__FILE__, __LINE__) -#define TIMER_STOP(sym) sym.stop() -#else -#define TIMER_DEF(sym, cat) -#define TIMER_DEF_(sym) -#define TIMER_START(sym) -#define TIMER_STOP(sym) -#endif - -} // namespace ledger - -#endif // _TIMING_H diff --git a/tuples.hpp b/tuples.hpp new file mode 100644 index 00000000..523846a7 --- /dev/null +++ b/tuples.hpp @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Copyright 2004-2007 Roman Yakovenko. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef TUPLES_HPP_16_JAN_2007 +#define TUPLES_HPP_16_JAN_2007 + +#include "boost/python.hpp" +#include "boost/tuple/tuple.hpp" +#include "boost/python/object.hpp" //len function +#include +#include + +/** + * Converts boost::tuples::tuple<...> to\from Python tuple + * + * The conversion is done "on-the-fly", you should only register the conversion + * with your tuple classes. + * For example: + * + * typedef boost::tuples::tuple< int, double, std::string > triplet; + * boost::python::register_tuple< triplet >(); + * + * That's all. After this point conversion to\from next types will be handled + * by Boost.Python library: + * + * triplet + * triplet& ( return type only ) + * const triplet + * const triplet& + * + * Implementation description. + * The conversion uses Boost.Python custom r-value converters. r-value converters + * is very powerful and undocumented feature of the library. The only documentation + * we have is http://boost.org/libs/python/doc/v2/faq.html#custom_string . + * + * The conversion consists from two parts: "to" and "from". + * + * "To" conversion + * The "to" part is pretty easy and well documented ( http://docs.python.org/api/api.html ). + * You should use Python C API to create an instance of a class and than you + * initialize the relevant members of the instance. + * + * "From" conversion + * Lets start from analyzing one of the use case Boost.Python library have to + * deal with: + * + * void do_smth( const triplet& arg ){...} + * + * In order to allow calling this function from Python, the library should keep + * parameter "arg" alive until the function returns. In other words, the library + * should provide instances life-time management. The provided interface is not + * ideal and could be improved. You have to implement two functions: + * + * void* convertible( PyObject* obj ) + * Checks whether the "obj" could be converted to an instance of the desired + * class. If true, the function should return "obj", otherwise NULL + * + * void construct( PyObject* obj, converter::rvalue_from_python_stage1_data* data) + * Constructs the instance of the desired class. This function will be called + * if and only if "convertible" function returned true. The first argument + * is Python object, which was passed as parameter to "convertible" function. + * The second object is some kind of memory allocator for one object. Basically + * it keeps a memory chunk. You will use the memory for object allocation. + * + * For some unclear for me reason, the library implements "C style Inheritance" + * ( http://www.embedded.com/97/fe29712.htm ). So, in order to create new + * object in the storage you have to cast to the "right" class: + * + * typedef converter::rvalue_from_python_storage storage_t; + * storage_t* the_storage = reinterpret_cast( data ); + * void* memory_chunk = the_storage->storage.bytes; + * + * "memory_chunk" points to the memory, where the instance will be allocated. + * + * In order to create object at specific location, you should use placement new + * operator: + * + * your_type_t* instance = new (memory_chunk) your_type_t(); + * + * Now, you can continue to initialize the instance. + * + * instance->set_xyz = read xyz from obj + * + * If "your_type_t" constructor requires some arguments, "read" the Python + * object before you call the constructor: + * + * xyz_type xyz = read xyz from obj + * your_type_t* instance = new (memory_chunk) your_type_t(xyz); + * + * Hint: + * In most case you don't really need\have to work with C Python API. Let + * Boost.Python library to do some work for you! + * + **/ + +namespace boost{ namespace python{ + +namespace details{ + +//Small helper function, introduced to allow short syntax for index incrementing +template< int index> +typename mpl::next< mpl::int_< index > >::type increment_index(){ + typedef typename mpl::next< mpl::int_< index > >::type next_index_type; + return next_index_type(); +} + +} + +template< class TTuple > +struct to_py_tuple{ + + typedef mpl::int_< tuples::length< TTuple >::value > length_type; + + static PyObject* convert(const TTuple& c_tuple){ + list values; + //add all c_tuple items to "values" list + convert_impl( c_tuple, values, mpl::int_< 0 >(), length_type() ); + //create Python tuple from the list + return incref( python::tuple( values ).ptr() ); + } + +private: + + template< int index, int length > + static void + convert_impl( const TTuple &c_tuple, list& values, mpl::int_< index >, mpl::int_< length > ) { + values.append( c_tuple.template get< index >() ); + convert_impl( c_tuple, values, details::increment_index(), length_type() ); + } + + template< int length > + static void + convert_impl( const TTuple&, list& values, mpl::int_< length >, mpl::int_< length >) + {} + +}; + + +template< class TTuple> +struct from_py_sequence{ + + typedef TTuple tuple_type; + + typedef mpl::int_< tuples::length< TTuple >::value > length_type; + + static void* + convertible(PyObject* py_obj){ + + if( !PySequence_Check( py_obj ) ){ + return 0; + } + + if( !PyObject_HasAttrString( py_obj, "__len__" ) ){ + return 0; + } + + python::object py_sequence( handle<>( borrowed( py_obj ) ) ); + + if( tuples::length< TTuple >::value != len( py_sequence ) ){ + return 0; + } + + if( convertible_impl( py_sequence, mpl::int_< 0 >(), length_type() ) ){ + return py_obj; + } + else{ + return 0; + } + } + + static void + construct( PyObject* py_obj, converter::rvalue_from_python_stage1_data* data){ + typedef converter::rvalue_from_python_storage storage_t; + storage_t* the_storage = reinterpret_cast( data ); + void* memory_chunk = the_storage->storage.bytes; + TTuple* c_tuple = new (memory_chunk) TTuple(); + data->convertible = memory_chunk; + + python::object py_sequence( handle<>( borrowed( py_obj ) ) ); + construct_impl( py_sequence, *c_tuple, mpl::int_< 0 >(), length_type() ); + } + + static TTuple to_c_tuple( PyObject* py_obj ){ + if( !convertible( py_obj ) ){ + throw std::runtime_error( "Unable to construct boost::tuples::tuple from Python object!" ); + } + TTuple c_tuple; + python::object py_sequence( handle<>( borrowed( py_obj ) ) ); + construct_impl( py_sequence, c_tuple, mpl::int_< 0 >(), length_type() ); + return c_tuple; + } + +private: + + template< int index, int length > + static bool + convertible_impl( const python::object& py_sequence, mpl::int_< index >, mpl::int_< length > ){ + + typedef typename tuples::element< index, TTuple>::type element_type; + + object element = py_sequence[index]; + extract type_checker( element ); + if( !type_checker.check() ){ + return false; + } + else{ + return convertible_impl( py_sequence, details::increment_index(), length_type() ); + } + } + + template< int length > + static bool + convertible_impl( const python::object& py_sequence, mpl::int_< length >, mpl::int_< length > ){ + return true; + } + + template< int index, int length > + static void + construct_impl( const python::object& py_sequence, TTuple& c_tuple, mpl::int_< index >, mpl::int_< length > ){ + + typedef typename tuples::element< index, TTuple>::type element_type; + + object element = py_sequence[index]; + c_tuple.template get< index >() = extract( element ); + + construct_impl( py_sequence, c_tuple, details::increment_index(), length_type() ); + } + + template< int length > + static void + construct_impl( const python::object& py_sequence, TTuple& c_tuple, mpl::int_< length >, mpl::int_< length > ) + {} + +}; + +template< class TTuple> +void register_tuple(){ + + to_python_converter< TTuple, to_py_tuple >(); + + converter::registry::push_back( &from_py_sequence::convertible + , &from_py_sequence::construct + , type_id() ); +}; + +} } //boost::python + +#endif//TUPLES_HPP_16_JAN_2007 diff --git a/util.h b/util.h deleted file mode 100644 index 21008a22..00000000 --- a/util.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef _UTIL_H -#define _UTIL_H - -#include - -#if defined __FreeBSD__ && __FreeBSD__ <=4 -// FreeBSD has a broken isspace macro, so dont use it -#undef isspace(c) -#endif - -#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 -typedef std::istream::pos_type istream_pos_type; -typedef std::ostream::pos_type ostream_pos_type; -#endif - -inline char * skip_ws(char * ptr) { - while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') - ptr++; - return ptr; -} - -inline char peek_next_nonws(std::istream& in) { - char c = in.peek(); - while (! in.eof() && std::isspace(c)) { - in.get(c); - c = in.peek(); - } - return c; -} - -#define READ_INTO(str, targ, size, var, cond) { \ - char * _p = targ; \ - var = str.peek(); \ - while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \ - str.get(var); \ - if (str.eof()) \ - break; \ - if (var == '\\') { \ - str.get(var); \ - if (in.eof()) \ - break; \ - } \ - *_p++ = var; \ - var = str.peek(); \ - } \ - *_p = '\0'; \ -} - -#endif // _UTIL_H diff --git a/utils.cc b/utils.cc new file mode 100644 index 00000000..ad6f8a96 --- /dev/null +++ b/utils.cc @@ -0,0 +1,720 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils.h" + +/********************************************************************** + * + * Assertions + */ + +#if defined(ASSERTS_ON) + +namespace ledger { + +DECLARE_EXCEPTION(fatal, assertion_failed); + +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 allocation_pair; +typedef std::map live_memory_map; +typedef std::multimap live_objects_map; + +typedef std::pair count_size_pair; +typedef std::map 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 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; + + for (object_count_map::const_iterator i = live_memory_count->begin(); + i != live_memory_count->end(); + i++) + memory_size += (*i).second.second; + + return memory_size; +} + +static void trace_new_func(void * ptr, const char * which, std::size_t size) +{ + memory_tracing_active = false; + + if (! live_memory) return; + + live_memory->insert + (live_memory_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) +{ + for (object_count_map::iterator i = the_map.begin(); + i != the_map.end(); + i++) + out << " " << std::right << std::setw(12) << (*i).second.first + << " " << std::right << std::setw(12) << (*i).second.second + << " " << std::left << (*i).first + << std::endl; +} + +std::size_t current_objects_size() +{ + std::size_t objects_size = 0; + + for (object_count_map::const_iterator i = live_object_count->begin(); + i != live_object_count->end(); + i++) + objects_size += (*i).second.second; + + return objects_size; +} + +void trace_ctor_func(void * ptr, const char * cls_name, const char * args, + std::size_t cls_size) +{ + memory_tracing_active = false; + + if (! live_objects) return; + + static char name[1024]; + std::strcpy(name, cls_name); + std::strcat(name, "("); + std::strcat(name, args); + std::strcat(name, ")"); + + DEBUG("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); + VERIFY(i != live_objects->end()); + + int ptr_count = live_objects->count(ptr); + for (int x = 0; x < ptr_count; x++, i++) { + if ((*i).second.first == cls_name) { + live_objects->erase(i); + break; + } + } + + object_count_map::iterator k = live_object_count->find(cls_name); + VERIFY(k != live_object_count->end()); + + (*k).second.second -= cls_size; + if (--(*k).second.first == 0) + live_object_count->erase(k); + + memory_tracing_active = true; +} + +void report_memory(std::ostream& out, bool report_all) +{ + if (! live_memory) return; + + if (live_memory_count->size() > 0) { + out << "NOTE: There may be memory held by Boost " + << "and libstdc++ after ledger::shutdown()" << std::endl; + out << "Live memory count:" << std::endl; + report_count_map(out, *live_memory_count); + } + + if (live_memory->size() > 0) { + out << "Live memory:" << std::endl; + + for (live_memory_map::const_iterator i = live_memory->begin(); + i != live_memory->end(); + i++) + out << " " << std::right << std::setw(7) << (*i).first + << " " << std::right << std::setw(7) << (*i).second.second + << " " << std::left << (*i).second.first + << std::endl; + } + + if (report_all && total_memory_count->size() > 0) { + out << "Total memory counts:" << std::endl; + report_count_map(out, *total_memory_count); + } + + if (live_object_count->size() > 0) { + out << "Live object count:" << std::endl; + report_count_map(out, *live_object_count); + } + + if (live_objects->size() > 0) { + out << "Live objects:" << std::endl; + + for (live_objects_map::const_iterator i = live_objects->begin(); + i != live_objects->end(); + i++) + out << " " << std::right << std::setw(7) << (*i).first + << " " << std::right << std::setw(7) << (*i).second.second + << " " << std::left << (*i).second.first + << std::endl; + } + + if (report_all) { + if (total_object_count->size() > 0) { + out << "Total object counts:" << std::endl; + report_count_map(out, *total_object_count); + } + + if (total_ctor_count->size() > 0) { + out << "Total constructor counts:" << std::endl; + report_count_map(out, *total_ctor_count); + } + } +} + + +string::string() : std::string() { + TRACE_CTOR(string, ""); +} +string::string(const string& str) : std::string(str) { + TRACE_CTOR(string, "const string&"); +} +string::string(const std::string& str) : std::string(str) { + TRACE_CTOR(string, "const std::string&"); +} +string::string(const int len, char x) : std::string(len, x) { + TRACE_CTOR(string, "const int, char"); +} +string::string(const char * str) : std::string(str) { + TRACE_CTOR(string, "const char *"); +} +string::string(const char * str, const char * end) : std::string(str, end) { + TRACE_CTOR(string, "const char *, const char *"); +} +string::string(const string& str, int x) : std::string(str, x) { + TRACE_CTOR(string, "const string&, int"); +} +string::string(const string& str, int x, int y) : std::string(str, x, y) { + TRACE_CTOR(string, "const string&, int, int"); +} +string::string(const char * str, int x) : std::string(str, x) { + TRACE_CTOR(string, "const char *, int"); +} +string::string(const char * str, int x, int y) : std::string(str, x, y) { + TRACE_CTOR(string, "const char *, int, int"); +} +string::~string() { + TRACE_DTOR(string); +} + +} // namespace ledger + +#endif // VERIFY_ON + +/********************************************************************** + * + * 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 - now).total_milliseconds(); + } + + *_log_stream << std::right << std::setw(5) + << (CURRENT_TIME() - logger_start).total_milliseconds(); + + IF_VERIFY() { + *_log_stream << std::right << std::setw(6) << std::setprecision(3); + stream_memory_size(*_log_stream, current_objects_size()); + *_log_stream << std::right << std::setw(6) << std::setprecision(3); + stream_memory_size(*_log_stream, current_memory_size()); + } + + *_log_stream << " " << std::left << std::setw(7); + + switch (level) { + case LOG_CRIT: *_log_stream << "[CRIT]"; break; + case LOG_FATAL: *_log_stream << "[FATAL]"; break; + case LOG_ASSERT: *_log_stream << "[ASSRT]"; break; + case LOG_ERROR: *_log_stream << "[ERROR]"; break; + case LOG_VERIFY: *_log_stream << "[VERFY]"; break; + case LOG_WARN: *_log_stream << "[WARN]"; break; + case LOG_INFO: *_log_stream << "[INFO]"; break; + case LOG_EXCEPT: *_log_stream << "[EXCPT]"; break; + case LOG_DEBUG: *_log_stream << "[DEBUG]"; break; + case LOG_TRACE: *_log_stream << "[TRACE]"; break; + + case LOG_OFF: + case LOG_ALL: + assert(false); + break; + } + + *_log_stream << ' ' << _log_buffer.str(); + + if (appender) + *_log_stream << " (" << appender << "ms startup)"; + + *_log_stream << std::endl; + + _log_buffer.str(""); + + return true; +} + +} // namespace ledger + +#if defined(DEBUG_ON) + +namespace ledger { + +optional _log_category; + +} // namespace ledger + +#endif // DEBUG_ON +#endif // LOGGING_ON + +/********************************************************************** + * + * Timers (allows log entries to specify cumulative time spent) + */ + +#if defined(LOGGING_ON) && defined(TIMERS_ON) + +namespace ledger { + +struct timer_t { + log_level_t level; + ptime begin; + time_duration spent; + std::string description; + bool active; + + timer_t(log_level_t _level, std::string _description) + : level(_level), begin(CURRENT_TIME()), + spent(time_duration(0, 0, 0, 0)), + description(_description), active(true) {} +}; + +typedef std::map timer_map; + +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 _exc_buffer; +/*ptr_list context_stack;*/ + +} // namespace ledger + +/********************************************************************** + * + * General utility functions + */ + +namespace ledger { + +path expand_path(const path& pathname) +{ + if (pathname.empty()) + return pathname; + +#if 1 + return pathname; +#else + // 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; +#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/utils.h b/utils.h new file mode 100644 index 00000000..47f36d89 --- /dev/null +++ b/utils.h @@ -0,0 +1,540 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file 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 + +/********************************************************************** + * + * 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 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() ? trace_ctor_func(this, #cls, args, sizeof(cls)) : ((void)0)) +#define TRACE_DTOR(cls) \ + (DO_VERIFY() ? trace_dtor_func(this, #cls, sizeof(cls)) : ((void)0)) + +void report_memory(std::ostream& out, bool report_all = false); + +/** + * 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(); +}; + +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 + +#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 _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 + */ + +#include "error.h" + +namespace ledger { + +extern std::ostringstream _exc_buffer; + +template +inline void throw_func(const std::string& message) { + _exc_buffer.str(""); + throw T(message); +} + +#define throw_(cls, msg) \ + ((_exc_buffer << msg), throw_func(_exc_buffer.str())) + +#if 0 +inline void throw_unexpected_error(char c, char wanted) { + if (c == -1) { + if (wanted) + throw new error(string("Missing '") + wanted + "'"); + else + throw new error("Unexpected end of input"); + } else { + if (wanted) + throw new error(string("Invalid char '") + c + + "' (wanted '" + wanted + "')"); + else + throw new error(string("Invalid char '") + c + "'"); + } +} +#else +inline void throw_unexpected_error(char, char) { +} +#endif + +} // namespace ledger + +/********************************************************************** + * + * Date/time support classes + * General support for objects with "flags" + * Support for object serialization (binary read/write) + * Support for scoped execution and variable restoration + */ + +#include "times.h" +#include "flags.h" +#include "binary.h" +#include "pushvar.h" + +/********************************************************************** + * + * General utility functions + */ + +#define foreach BOOST_FOREACH + +namespace ledger { + +template +inline T& downcast(U& object) { + return *polymorphic_downcast(&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; +} + +} // namespace ledger + +#endif // _UTILS_H diff --git a/valgrind.sh b/valgrind.sh new file mode 100755 index 00000000..fe292f44 --- /dev/null +++ b/valgrind.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +VALGRIND=$(which valgrind 2>&1) + +if [ -x "$VALGRIND" ]; then + exec "$VALGRIND" --leak-check=full --show-reachable=yes "$@" +else + exec "$@" +fi diff --git a/value.cc b/value.cc index 641beb49..7a7249da 100644 --- a/value.cc +++ b/value.cc @@ -1,1248 +1,1286 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + #include "value.h" -#include "debug.h" -#include "error.h" namespace ledger { -void value_t::destroy() +intrusive_ptr value_t::true_value; +intrusive_ptr value_t::false_value; + +void value_t::storage_t::destroy() { switch (type) { case AMOUNT: - ((amount_t *)data)->~amount_t(); + reinterpret_cast(data)->~amount_t(); break; case BALANCE: - ((balance_t *)data)->~balance_t(); + checked_delete(*reinterpret_cast(data)); break; case BALANCE_PAIR: - ((balance_pair_t *)data)->~balance_pair_t(); + checked_delete(*reinterpret_cast(data)); + break; + case STRING: + reinterpret_cast(data)->~string(); + break; + case SEQUENCE: + checked_delete(*reinterpret_cast(data)); break; + case POINTER: + reinterpret_cast(data)->~any(); + break; + default: break; } + type = VOID; } -void value_t::simplify() +void value_t::initialize() { - if (realzero()) { - DEBUG_PRINT("amounts.values.simplify", "Zeroing type " << type); - *this = 0L; - return; +#if 0 + LOGGER("value.initialize"); +#endif + + true_value = new storage_t; + true_value->type = BOOLEAN; + *reinterpret_cast(true_value->data) = true; + + false_value = new storage_t; + false_value->type = BOOLEAN; + *reinterpret_cast(false_value->data) = false; + + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(bool)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(moment_t)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(long)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(amount_t)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_t *)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_pair_t *)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(string)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(sequence_t *)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(boost::any)); + +#if 0 + DEBUG_(std::setw(3) << std::right << sizeof(bool) + << " sizeof(bool)"); + DEBUG_(std::setw(3) << std::right << sizeof(moment_t) + << " sizeof(moment_t)"); + DEBUG_(std::setw(3) << std::right << sizeof(long) + << " sizeof(long)"); + DEBUG_(std::setw(3) << std::right << sizeof(amount_t) + << " sizeof(amount_t)"); + DEBUG_(std::setw(3) << std::right << sizeof(balance_t *) + << " sizeof(balance_t *)"); + DEBUG_(std::setw(3) << std::right << sizeof(balance_pair_t *) + << " sizeof(balance_pair_t *)"); + DEBUG_(std::setw(3) << std::right << sizeof(string) + << " sizeof(string)"); + DEBUG_(std::setw(3) << std::right << sizeof(sequence_t *) + << " sizeof(sequence_t *)"); + DEBUG_(std::setw(3) << std::right << sizeof(boost::any) + << " sizeof(boost::any)"); +#endif +} + +void value_t::shutdown() +{ + true_value = intrusive_ptr(); + false_value = intrusive_ptr(); +} + +void value_t::_dup() +{ + assert(storage); + if (storage->refc > 1) { + storage = new storage_t(*storage.get()); + + // If the data referenced by storage is an allocated pointer, we + // need to create a new object in order to achieve duplication. + switch (storage->type) { + case BALANCE: + *(balance_t **) storage->data = + new balance_t(**(balance_t **) storage->data); + break; + case BALANCE_PAIR: + *(balance_pair_t **) storage->data = + new balance_pair_t(**(balance_pair_t **) storage->data); + break; + case SEQUENCE: + *(sequence_t **) storage->data = + new sequence_t(**(sequence_t **) storage->data); + break; + default: + break; // everything else has been duplicated + } } +} - if (type == BALANCE_PAIR && - (! ((balance_pair_t *) data)->cost || - ((balance_pair_t *) data)->cost->realzero())) { - DEBUG_PRINT("amounts.values.simplify", "Reducing balance pair to balance"); - cast(BALANCE); +value_t::operator bool() const +{ + switch (type()) { + case BOOLEAN: + return as_boolean(); + case INTEGER: + return as_long(); + case DATETIME: + return is_valid_moment(as_datetime()); + case AMOUNT: + return as_amount(); + case BALANCE: + return as_balance(); + case BALANCE_PAIR: + return as_balance_pair(); + case STRING: + return ! as_string().empty(); + case SEQUENCE: + return ! as_sequence().empty(); + case POINTER: + return ! as_any_pointer().empty(); + default: + assert(false); + break; } + assert(false); + return 0; +} - if (type == BALANCE && - ((balance_t *) data)->amounts.size() == 1) { - DEBUG_PRINT("amounts.values.simplify", "Reducing balance to amount"); - cast(AMOUNT); +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(); } +} - if (type == AMOUNT && - ! ((amount_t *) data)->commodity()) { - DEBUG_PRINT("amounts.values.simplify", "Reducing amount to integer"); - cast(INTEGER); +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(); } } -value_t& value_t::operator=(const value_t& value) +moment_t value_t::to_datetime() const { - if (this == &value) - return *this; + if (is_datetime()) { + return as_datetime(); + } else { + value_t temp(*this); + temp.in_place_cast(DATETIME); + return temp.as_datetime(); + } +} - destroy(); +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(); + } +} - switch (value.type) { - case BOOLEAN: - *((bool *) data) = *((bool *) value.data); - break; +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(); + } +} - case INTEGER: - *((long *) data) = *((long *) value.data); - break; +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(); + } +} - case DATETIME: - *((datetime_t *) data) = *((datetime_t *) value.data); - break; +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(); + } +} - case AMOUNT: - new((amount_t *)data) amount_t(*((amount_t *) value.data)); - break; +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(); + } +} - case BALANCE: - new((balance_t *)data) balance_t(*((balance_t *) value.data)); - break; - case BALANCE_PAIR: - new((balance_pair_t *)data) balance_pair_t(*((balance_pair_t *) value.data)); - break; +void value_t::in_place_simplify() +{ + LOGGER("amounts.values.simplify"); - default: - assert(0); - break; + if (is_realzero()) { + DEBUG_("Zeroing type " << type()); + set_long(0L); + return; } - type = value.type; + if (is_balance_pair() && + (! as_balance_pair().cost || as_balance_pair().cost->is_realzero())) { + DEBUG_("Reducing balance pair to balance"); + in_place_cast(BALANCE); + } - return *this; + if (is_balance() && as_balance().amounts.size() == 1) { + DEBUG_("Reducing balance to amount"); + in_place_cast(AMOUNT); + } + +#if 0 + if (is_amount() && ! as_amount().has_commodity() && + as_amount().fits_in_long()) { + DEBUG_("Reducing amount to integer"); + in_place_cast(INTEGER); + } +#endif } -value_t& value_t::operator+=(const value_t& value) +value_t& value_t::operator+=(const value_t& val) { - if (value.type == BOOLEAN) - throw new value_error("Cannot add a boolean to a value"); - else if (value.type == DATETIME) - throw new value_error("Cannot add a date/time to a value"); - - switch (type) { - case BOOLEAN: - throw new value_error("Cannot add a value to a boolean"); + 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; + } - case INTEGER: - switch (value.type) { + switch (type()) { + case DATETIME: + switch (val.type()) { case INTEGER: - *((long *) data) += *((long *) value.data); - break; + as_datetime_lval() += date_duration(val.as_long()); + return *this; case AMOUNT: - cast(AMOUNT); - *((amount_t *) data) += *((amount_t *) value.data); - break; - case BALANCE: - cast(BALANCE); - *((balance_t *) data) += *((balance_t *) value.data); - break; - case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) += *((balance_pair_t *) value.data); - break; + as_datetime_lval() += date_duration(val.as_amount().to_long()); + return *this; default: - assert(0); break; } break; - case DATETIME: - switch (value.type) { + case INTEGER: + switch (val.type()) { case INTEGER: - *((datetime_t *) data) += *((long *) value.data); - break; + as_long_lval() += val.as_long(); + return *this; case AMOUNT: - *((datetime_t *) data) += long(*((amount_t *) value.data)); - break; + in_place_cast(AMOUNT); + as_amount_lval() += val.as_amount(); + return *this; case BALANCE: - *((datetime_t *) data) += long(*((balance_t *) value.data)); - break; + in_place_cast(BALANCE); + as_balance_lval() += val.as_balance(); + return *this; case BALANCE_PAIR: - *((datetime_t *) data) += long(*((balance_pair_t *) value.data)); - break; + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() += val.as_balance_pair(); + return *this; default: - assert(0); break; } break; case AMOUNT: - switch (value.type) { + switch (val.type()) { case INTEGER: - if (*((long *) value.data) && - ((amount_t *) data)->commodity()) { - cast(BALANCE); - return *this += value; + if (as_amount().has_commodity()) { + in_place_cast(BALANCE); + return *this += val; + } else { + as_amount_lval() += val.as_long(); + return *this; } - *((amount_t *) data) += *((long *) value.data); break; case AMOUNT: - if (((amount_t *) data)->commodity() != - ((amount_t *) value.data)->commodity()) { - cast(BALANCE); - return *this += value; + if (as_amount().commodity() != val.as_amount().commodity()) { + in_place_cast(BALANCE); + return *this += val; + } else { + as_amount_lval() += val.as_amount(); + return *this; } - *((amount_t *) data) += *((amount_t *) value.data); break; case BALANCE: - cast(BALANCE); - *((balance_t *) data) += *((balance_t *) value.data); - break; + in_place_cast(BALANCE); + as_balance_lval() += val.as_balance(); + return *this; case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) += *((balance_pair_t *) value.data); - break; - + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() += val.as_balance_pair(); + return *this; default: - assert(0); break; } break; case BALANCE: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((balance_t *) data) += *((long *) value.data); - break; + as_balance_lval() += val.to_amount(); + return *this; case AMOUNT: - *((balance_t *) data) += *((amount_t *) value.data); - break; + as_balance_lval() += val.as_amount(); + return *this; case BALANCE: - *((balance_t *) data) += *((balance_t *) value.data); - break; + as_balance_lval() += val.as_balance(); + return *this; case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) += *((balance_pair_t *) value.data); - break; + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() += val.as_balance_pair(); + return *this; default: - assert(0); break; } break; case BALANCE_PAIR: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((balance_pair_t *) data) += *((long *) value.data); - break; + as_balance_pair_lval() += val.to_amount(); + return *this; case AMOUNT: - *((balance_pair_t *) data) += *((amount_t *) value.data); - break; + as_balance_pair_lval() += val.as_amount(); + return *this; case BALANCE: - *((balance_pair_t *) data) += *((balance_t *) value.data); - break; + as_balance_pair_lval() += val.as_balance(); + return *this; case BALANCE_PAIR: - *((balance_pair_t *) data) += *((balance_pair_t *) value.data); - break; + as_balance_pair_lval() += val.as_balance_pair(); + return *this; default: - assert(0); break; } break; default: - assert(0); break; } + + throw_(value_error, "Cannot add " << label() << " to " << val.label()); + return *this; } -value_t& value_t::operator-=(const value_t& value) +value_t& value_t::operator-=(const value_t& val) { - if (value.type == BOOLEAN) - throw new value_error("Cannot subtract a boolean from a value"); - else if (value.type == DATETIME && type != DATETIME) - throw new value_error("Cannot subtract a date/time from a value"); - - switch (type) { - case BOOLEAN: - throw new value_error("Cannot subtract a value from a boolean"); + if (is_sequence()) { + sequence_t& seq(as_sequence_lval()); + + if (val.is_sequence()) { + for (sequence_t::const_iterator i = val.as_sequence().begin(); + i != val.as_sequence().end(); + i++) { + sequence_t::iterator j = std::find(seq.begin(), seq.end(), *i); + if (j != seq.end()) + seq.erase(j); + } + } else { + sequence_t::iterator i = std::find(seq.begin(), seq.end(), val); + if (i != seq.end()) + seq.erase(i); + } + return *this; + } - case INTEGER: - switch (value.type) { + switch (type()) { + case DATETIME: + switch (val.type()) { case INTEGER: - *((long *) data) -= *((long *) value.data); - break; + as_datetime_lval() -= date_duration(val.as_long()); + return *this; case AMOUNT: - cast(AMOUNT); - *((amount_t *) data) -= *((amount_t *) value.data); - break; - case BALANCE: - cast(BALANCE); - *((balance_t *) data) -= *((balance_t *) value.data); - break; - case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) -= *((balance_pair_t *) value.data); - break; + as_datetime_lval() -= date_duration(val.as_amount().to_long()); + return *this; default: - assert(0); break; } break; - case DATETIME: - switch (value.type) { + case INTEGER: + switch (val.type()) { case INTEGER: - *((datetime_t *) data) -= *((long *) value.data); - break; - case DATETIME: { - long val = *((datetime_t *) data) - *((datetime_t *) value.data); - cast(INTEGER); - *((long *) data) = val; - break; - } + as_long_lval() -= val.as_long(); + return *this; case AMOUNT: - *((datetime_t *) data) -= long(*((amount_t *) value.data)); - break; + in_place_cast(AMOUNT); + as_amount_lval() -= val.as_amount(); + in_place_simplify(); + return *this; case BALANCE: - *((datetime_t *) data) -= long(*((balance_t *) value.data)); - break; + in_place_cast(BALANCE); + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; case BALANCE_PAIR: - *((datetime_t *) data) -= long(*((balance_pair_t *) value.data)); - break; + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; default: - assert(0); break; } break; case AMOUNT: - switch (value.type) { + switch (val.type()) { case INTEGER: - if (*((long *) value.data) && - ((amount_t *) data)->commodity()) { - cast(BALANCE); - return *this -= value; + 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; } - *((amount_t *) data) -= *((long *) value.data); break; case AMOUNT: - if (((amount_t *) data)->commodity() != - ((amount_t *) value.data)->commodity()) { - cast(BALANCE); - return *this -= value; + 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; } - *((amount_t *) data) -= *((amount_t *) value.data); break; case BALANCE: - cast(BALANCE); - *((balance_t *) data) -= *((balance_t *) value.data); - break; + in_place_cast(BALANCE); + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) -= *((balance_pair_t *) value.data); - break; - + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; default: - assert(0); break; } break; case BALANCE: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((balance_t *) data) -= *((long *) value.data); - break; + as_balance_lval() -= val.to_amount(); + in_place_simplify(); + return *this; case AMOUNT: - *((balance_t *) data) -= *((amount_t *) value.data); - break; + as_balance_lval() -= val.as_amount(); + in_place_simplify(); + return *this; case BALANCE: - *((balance_t *) data) -= *((balance_t *) value.data); - break; + as_balance_lval() -= val.as_balance(); + in_place_simplify(); + return *this; case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) -= *((balance_pair_t *) value.data); - break; + in_place_cast(BALANCE_PAIR); + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; default: - assert(0); break; } break; case BALANCE_PAIR: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((balance_pair_t *) data) -= *((long *) value.data); - break; + as_balance_pair_lval() -= val.to_amount(); + in_place_simplify(); + return *this; case AMOUNT: - *((balance_pair_t *) data) -= *((amount_t *) value.data); - break; + as_balance_pair_lval() -= val.as_amount(); + in_place_simplify(); + return *this; case BALANCE: - *((balance_pair_t *) data) -= *((balance_t *) value.data); - break; + as_balance_pair_lval() -= val.as_balance(); + in_place_simplify(); + return *this; case BALANCE_PAIR: - *((balance_pair_t *) data) -= *((balance_pair_t *) value.data); - break; + as_balance_pair_lval() -= val.as_balance_pair(); + in_place_simplify(); + return *this; default: - assert(0); break; } break; default: - assert(0); break; } - simplify(); + throw_(value_error, "Cannot subtract " << label() << " from " << val.label()); return *this; } -value_t& value_t::operator*=(const value_t& value) +value_t& value_t::operator*=(const value_t& val) { - if (value.type == BOOLEAN) - throw new value_error("Cannot multiply a boolean by a value"); - else if (value.type == DATETIME) - throw new value_error("Cannot multiply a date/time by a value"); - - if (value.realzero()) { - *this = 0L; + 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 BOOLEAN: - throw new value_error("Cannot multiply a value by a boolean"); - + switch (type()) { case INTEGER: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((long *) data) *= *((long *) value.data); - break; + as_long_lval() *= val.as_long(); + return *this; case AMOUNT: - cast(AMOUNT); - *((amount_t *) data) *= *((amount_t *) value.data); - break; - case BALANCE: - cast(BALANCE); - *((balance_t *) data) *= *((balance_t *) value.data); - break; - case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) *= *((balance_pair_t *) value.data); - break; + set_amount(val.as_amount() * as_long()); + return *this; default: - assert(0); break; } break; case AMOUNT: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((amount_t *) data) *= *((long *) value.data); - break; + as_amount_lval() *= val.as_long(); + return *this; case AMOUNT: - *((amount_t *) data) *= *((amount_t *) value.data); - break; - case BALANCE: - cast(BALANCE); - *((balance_t *) data) *= *((balance_t *) value.data); - break; - case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) *= *((balance_pair_t *) value.data); + if (as_amount().commodity() == val.as_amount().commodity() || + ! val.as_amount().has_commodity()) { + as_amount_lval() *= val.as_amount(); + return *this; + } break; default: - assert(0); break; } break; case BALANCE: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((balance_t *) data) *= *((long *) value.data); - break; + as_balance_lval() *= val.as_long(); + return *this; case AMOUNT: - *((balance_t *) data) *= *((amount_t *) value.data); - break; - case BALANCE: - *((balance_t *) data) *= *((balance_t *) value.data); - break; - case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) *= *((balance_pair_t *) value.data); + if (! val.as_amount().has_commodity()) { + as_balance_lval() *= val.as_amount(); + return *this; + } break; default: - assert(0); break; } break; case BALANCE_PAIR: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((balance_pair_t *) data) *= *((long *) value.data); - break; + as_balance_pair_lval() *= val.as_long(); + return *this; case AMOUNT: - *((balance_pair_t *) data) *= *((amount_t *) value.data); - break; - case BALANCE: - *((balance_pair_t *) data) *= *((balance_t *) value.data); - break; - case BALANCE_PAIR: - *((balance_pair_t *) data) *= *((balance_pair_t *) value.data); + if (! val.as_amount().has_commodity()) { + as_balance_pair_lval() *= val.as_amount(); + return *this; + } break; default: - assert(0); break; } break; default: - assert(0); break; } + + throw_(value_error, "Cannot multiply " << label() << " with " << val.label()); + return *this; } -value_t& value_t::operator/=(const value_t& value) +value_t& value_t::operator/=(const value_t& val) { - if (value.type == BOOLEAN) - throw new value_error("Cannot divide a boolean by a value"); - else if (value.type == DATETIME) - throw new value_error("Cannot divide a date/time by a value"); - - switch (type) { - case BOOLEAN: - throw new value_error("Cannot divide a value by a boolean"); - + switch (type()) { case INTEGER: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((long *) data) /= *((long *) value.data); - break; + as_long_lval() /= val.as_long(); + return *this; case AMOUNT: - cast(AMOUNT); - *((amount_t *) data) /= *((amount_t *) value.data); - break; - case BALANCE: - cast(BALANCE); - *((balance_t *) data) /= *((balance_t *) value.data); - break; - case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); - break; + set_amount(val.as_amount() / as_long()); + return *this; default: - assert(0); break; } break; case AMOUNT: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((amount_t *) data) /= *((long *) value.data); - break; + as_amount_lval() /= val.as_long(); + return *this; + case AMOUNT: - *((amount_t *) data) /= *((amount_t *) value.data); - break; - case BALANCE: - cast(BALANCE); - *((balance_t *) data) /= *((balance_t *) value.data); - break; - case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); + if (as_amount().commodity() == val.as_amount().commodity() || + ! val.as_amount().has_commodity()) { + as_amount_lval() /= val.as_amount(); + return *this; + } break; default: - assert(0); break; } break; case BALANCE: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((balance_t *) data) /= *((long *) value.data); - break; + as_balance_lval() /= val.as_long(); + return *this; case AMOUNT: - *((balance_t *) data) /= *((amount_t *) value.data); - break; - case BALANCE: - *((balance_t *) data) /= *((balance_t *) value.data); - break; - case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); + if (! val.as_amount().has_commodity()) { + as_balance_lval() /= val.as_amount(); + return *this; + } break; default: - assert(0); break; } break; case BALANCE_PAIR: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((balance_pair_t *) data) /= *((long *) value.data); - break; + as_balance_pair_lval() /= val.as_long(); + return *this; case AMOUNT: - *((balance_pair_t *) data) /= *((amount_t *) value.data); - break; - case BALANCE: - *((balance_pair_t *) data) /= *((balance_t *) value.data); - break; - case BALANCE_PAIR: - *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); + if (! val.as_amount().has_commodity()) { + as_balance_pair_lval() /= val.as_amount(); + return *this; + } break; default: - assert(0); break; } break; default: - assert(0); break; } - return *this; -} -#define DEF_VALUE_CMP_OP(OP) \ -bool value_t::operator OP(const value_t& value) \ -{ \ - switch (type) { \ - case BOOLEAN: \ - switch (value.type) { \ - case BOOLEAN: \ - return *((bool *) data) OP *((bool *) value.data); \ - \ - case INTEGER: \ - return *((bool *) data) OP bool(*((long *) value.data)); \ - \ - case DATETIME: \ - return *((bool *) data) OP bool(*((datetime_t *) value.data)); \ - \ - case AMOUNT: \ - return *((bool *) data) OP bool(*((amount_t *) value.data)); \ - \ - case BALANCE: \ - return *((bool *) data) OP bool(*((balance_t *) value.data)); \ - \ - case BALANCE_PAIR: \ - return *((bool *) data) OP bool(*((balance_pair_t *) value.data)); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case INTEGER: \ - switch (value.type) { \ - case BOOLEAN: \ - return (*((long *) data) OP \ - ((long) *((bool *) value.data))); \ - \ - case INTEGER: \ - return (*((long *) data) OP *((long *) value.data)); \ - \ - case DATETIME: \ - return (*((long *) data) OP \ - ((long) *((datetime_t *) value.data))); \ - \ - case AMOUNT: \ - return (amount_t(*((long *) data)) OP \ - *((amount_t *) value.data)); \ - \ - case BALANCE: \ - return (balance_t(*((long *) data)) OP \ - *((balance_t *) value.data)); \ - \ - case BALANCE_PAIR: \ - return (balance_pair_t(*((long *) data)) OP \ - *((balance_pair_t *) value.data)); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case DATETIME: \ - switch (value.type) { \ - case BOOLEAN: \ - throw new value_error("Cannot compare a date/time to a boolean"); \ - \ - case INTEGER: \ - return (*((datetime_t *) data) OP \ - datetime_t(*((long *) value.data))); \ - \ - case DATETIME: \ - return (*((datetime_t *) data) OP \ - *((datetime_t *) value.data)); \ - \ - case AMOUNT: \ - throw new value_error("Cannot compare a date/time to an amount"); \ - \ - case BALANCE: \ - throw new value_error("Cannot compare a date/time to a balance"); \ - \ - case BALANCE_PAIR: \ - throw new value_error("Cannot compare a date/time to a balance pair"); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case AMOUNT: \ - switch (value.type) { \ - case BOOLEAN: \ - throw new value_error("Cannot compare an amount to a boolean"); \ - \ - case INTEGER: \ - return (*((amount_t *) data) OP \ - amount_t(*((long *) value.data))); \ - \ - case DATETIME: \ - throw new value_error("Cannot compare an amount to a date/time"); \ - \ - case AMOUNT: \ - return *((amount_t *) data) OP *((amount_t *) value.data); \ - \ - case BALANCE: \ - return (balance_t(*((amount_t *) data)) OP \ - *((balance_t *) value.data)); \ - \ - case BALANCE_PAIR: \ - return (balance_t(*((amount_t *) data)) OP \ - *((balance_pair_t *) value.data)); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case BALANCE: \ - switch (value.type) { \ - case BOOLEAN: \ - throw new value_error("Cannot compare a balance to a boolean"); \ - \ - case INTEGER: \ - return *((balance_t *) data) OP *((long *) value.data); \ - \ - case DATETIME: \ - throw new value_error("Cannot compare a balance to a date/time"); \ - \ - case AMOUNT: \ - return *((balance_t *) data) OP *((amount_t *) value.data); \ - \ - case BALANCE: \ - return *((balance_t *) data) OP *((balance_t *) value.data); \ - \ - case BALANCE_PAIR: \ - return (*((balance_t *) data) OP \ - ((balance_pair_t *) value.data)->quantity); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case BALANCE_PAIR: \ - switch (value.type) { \ - case BOOLEAN: \ - throw new value_error("Cannot compare a balance pair to a boolean"); \ - \ - case INTEGER: \ - return (((balance_pair_t *) data)->quantity OP \ - *((long *) value.data)); \ - \ - case DATETIME: \ - throw new value_error("Cannot compare a balance pair to a date/time"); \ - \ - case AMOUNT: \ - return (((balance_pair_t *) data)->quantity OP \ - *((amount_t *) value.data)); \ - \ - case BALANCE: \ - return (((balance_pair_t *) data)->quantity OP \ - *((balance_t *) value.data)); \ - \ - case BALANCE_PAIR: \ - return (*((balance_pair_t *) data) OP \ - *((balance_pair_t *) value.data)); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - default: \ - assert(0); \ - break; \ - } \ - return *this; \ + throw_(value_error, "Cannot divide " << label() << " by " << val.label()); + + return *this; } -DEF_VALUE_CMP_OP(==) -DEF_VALUE_CMP_OP(<) -DEF_VALUE_CMP_OP(<=) -DEF_VALUE_CMP_OP(>) -DEF_VALUE_CMP_OP(>=) -template <> -value_t::operator long() const +bool value_t::operator==(const value_t& val) const { - switch (type) { + switch (type()) { case BOOLEAN: - throw new value_error("Cannot convert a boolean to an integer"); - case INTEGER: - return *((long *) data); - case DATETIME: - return *((datetime_t *) data); - case AMOUNT: - return *((amount_t *) data); - case BALANCE: - throw new value_error("Cannot convert a balance to an integer"); - case BALANCE_PAIR: - throw new value_error("Cannot convert a balance pair to an integer"); - - default: - assert(0); + if (val.is_boolean()) + return as_boolean() == val.as_boolean(); break; - } - assert(0); - return 0; -} -template <> -value_t::operator datetime_t() const -{ - switch (type) { - case BOOLEAN: - throw new value_error("Cannot convert a boolean to a date/time"); - case INTEGER: - return *((long *) data); case DATETIME: - return *((datetime_t *) data); - case AMOUNT: - throw new value_error("Cannot convert an amount to a date/time"); - case BALANCE: - throw new value_error("Cannot convert a balance to a date/time"); - case BALANCE_PAIR: - throw new value_error("Cannot convert a balance pair to a date/time"); - - default: - assert(0); + if (val.is_datetime()) + return as_datetime() == val.as_datetime(); break; - } - assert(0); - return 0; -} -template <> -value_t::operator double() const -{ - switch (type) { - case BOOLEAN: - throw new value_error("Cannot convert a boolean to a double"); case INTEGER: - return *((long *) data); - case DATETIME: - throw new value_error("Cannot convert a date/time to a double"); - case AMOUNT: - return *((amount_t *) data); - case BALANCE: - throw new value_error("Cannot convert a balance to a double"); - case BALANCE_PAIR: - throw new value_error("Cannot convert a balance pair to a double"); - - default: - assert(0); - break; - } - assert(0); - return 0; -} - -void value_t::cast(type_t cast_type) -{ - switch (type) { - case BOOLEAN: - switch (cast_type) { - case BOOLEAN: - break; + switch (val.type()) { case INTEGER: - throw new value_error("Cannot convert a boolean to an integer"); - case DATETIME: - throw new value_error("Cannot convert a boolean to a date/time"); + return as_long() == val.as_long(); case AMOUNT: - throw new value_error("Cannot convert a boolean to an amount"); + return val.as_amount() == to_amount(); case BALANCE: - throw new value_error("Cannot convert a boolean to a balance"); + return val.as_balance() == to_amount(); case BALANCE_PAIR: - throw new value_error("Cannot convert a boolean to a balance pair"); - + return val.as_balance_pair() == to_amount(); default: - assert(0); break; } break; - case INTEGER: - switch (cast_type) { - case BOOLEAN: - *((bool *) data) = *((long *) data); - break; + case AMOUNT: + switch (val.type()) { case INTEGER: - break; - case DATETIME: - *((datetime_t *) data) = datetime_t(*((long *) data)); - break; + return as_amount() == val.as_long(); case AMOUNT: - new((amount_t *)data) amount_t(*((long *) data)); - break; + return as_amount() == val.as_amount(); case BALANCE: - new((balance_t *)data) balance_t(amount_t(*((long *) data))); - break; + return val.as_balance() == as_amount(); case BALANCE_PAIR: - new((balance_pair_t *)data) balance_pair_t(amount_t(*((long *) data))); + 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: - assert(0); break; } break; - case DATETIME: - switch (cast_type) { - case BOOLEAN: - *((bool *) data) = *((datetime_t *) data); - break; + case BALANCE_PAIR: + switch (val.type()) { case INTEGER: - *((long *) data) = *((datetime_t *) data); - break; - case DATETIME: - break; + return as_balance_pair() == val.to_amount(); case AMOUNT: - throw new value_error("Cannot convert a date/time to an amount"); + return as_balance_pair() == val.as_amount(); case BALANCE: - throw new value_error("Cannot convert a date/time to a balance"); + return as_balance_pair() == val.as_balance(); case BALANCE_PAIR: - throw new value_error("Cannot convert a date/time to a 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 INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() < val.as_long(); + case AMOUNT: + return val.as_amount() < as_long(); default: - assert(0); break; } break; case AMOUNT: - switch (cast_type) { - case BOOLEAN: { - bool temp = *((amount_t *) data); - destroy(); - *((bool *)data) = temp; + switch (val.type()) { + case INTEGER: + return as_amount() < val.as_long(); + case AMOUNT: + return as_amount() < val.as_amount(); + default: break; } - case INTEGER: { - long temp = *((amount_t *) data); - destroy(); - *((long *)data) = temp; + 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 INTEGER: + switch (val.type()) { + case INTEGER: + return as_long() > val.as_long(); + case AMOUNT: + return val.as_amount() > as_long(); + default: break; } - case DATETIME: - throw new value_error("Cannot convert an amount to a date/time"); + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + return as_amount() > val.as_long(); case AMOUNT: - break; - case BALANCE: { - amount_t temp = *((amount_t *) data); - destroy(); - new((balance_t *)data) balance_t(temp); + return as_amount() > val.as_amount(); + default: break; } - case BALANCE_PAIR: { - amount_t temp = *((amount_t *) data); - destroy(); - new((balance_pair_t *)data) balance_pair_t(temp); + 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(as_long())); + return; default: - assert(0); break; } break; - case BALANCE: + case AMOUNT: switch (cast_type) { - case BOOLEAN: { - bool temp = *((balance_t *) data); - destroy(); - *((bool *)data) = temp; + case INTEGER: + set_long(as_amount().to_long()); + return; + case BALANCE: + set_balance(as_amount()); + return; + case BALANCE_PAIR: + set_balance_pair(as_amount()); + return; + case STRING: + set_string(as_amount().to_string()); + return; + default: break; } - case INTEGER: - throw new value_error("Cannot convert a balance to an integer"); - case DATETIME: - throw new value_error("Cannot convert a balance to a date/time"); + break; + case BALANCE: + switch (cast_type) { case AMOUNT: { - balance_t * temp = (balance_t *) data; - if (temp->amounts.size() == 1) { - amount_t amt = (*temp->amounts.begin()).second; - destroy(); - new((amount_t *)data) amount_t(amt); + const balance_t& temp(as_balance()); + if (temp.amounts.size() == 1) { + set_amount((*temp.amounts.begin()).second); + return; } - else if (temp->amounts.size() == 0) { - new((amount_t *)data) amount_t(); + else if (temp.amounts.size() == 0) { + set_amount(0L); + return; } else { - throw new value_error("Cannot convert a balance with " - "multiple commodities to an amount"); + throw_(value_error, "Cannot convert " << label() << + " with multiple commodities to " << label(cast_type)); } break; } - case BALANCE: - break; - case BALANCE_PAIR: { - balance_t temp = *((balance_t *) data); - destroy(); - new((balance_pair_t *)data) balance_pair_t(temp); - break; - } - + case BALANCE_PAIR: + set_balance_pair(as_balance()); + return; default: - assert(0); break; } break; case BALANCE_PAIR: switch (cast_type) { - case BOOLEAN: { - bool temp = *((balance_pair_t *) data); - destroy(); - *((bool *)data) = temp; - break; - } - case INTEGER: - throw new value_error("Cannot convert a balance pair to an integer"); - case DATETIME: - throw new value_error("Cannot convert a balance pair to a date/time"); - case AMOUNT: { - balance_t * temp = &((balance_pair_t *) data)->quantity; - if (temp->amounts.size() == 1) { - amount_t amt = (*temp->amounts.begin()).second; - destroy(); - new((amount_t *)data) amount_t(amt); + const balance_t& temp(as_balance_pair().quantity()); + if (temp.amounts.size() == 1) { + set_amount((*temp.amounts.begin()).second); + return; } - else if (temp->amounts.size() == 0) { - new((amount_t *)data) amount_t(); + else if (temp.amounts.size() == 0) { + set_amount(0L); + return; } else { - throw new value_error("Cannot convert a balance pair with " - "multiple commodities to an amount"); + throw_(value_error, "Cannot convert " << label() << + " with multiple commodities to " << label(cast_type)); } break; } - case BALANCE: { - balance_t temp = ((balance_pair_t *) data)->quantity; - destroy(); - new((balance_t *)data) balance_t(temp); + case BALANCE: + set_balance(as_balance_pair().quantity()); + return; + default: break; } - case BALANCE_PAIR: - break; + break; + case STRING: + switch (cast_type) { + case INTEGER: { + if (all(as_string(), is_digit())) { + set_long(lexical_cast(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: - assert(0); break; } break; default: - assert(0); break; } - type = cast_type; + + throw_(value_error, + "Cannot convert " << label() << " to " << label(cast_type)); } -void value_t::negate() +void value_t::in_place_negate() { - switch (type) { + switch (type()) { case BOOLEAN: - *((bool *) data) = ! *((bool *) data); - break; + set_boolean(! as_boolean()); + return; case INTEGER: - *((long *) data) = - *((long *) data); - break; - case DATETIME: - throw new value_error("Cannot negate a date/time"); + set_long(- as_long()); + return; case AMOUNT: - ((amount_t *) data)->negate(); - break; + as_amount_lval().in_place_negate(); + return; case BALANCE: - ((balance_t *) data)->negate(); - break; + as_balance_lval().in_place_negate(); + return; case BALANCE_PAIR: - ((balance_pair_t *) data)->negate(); - break; - + as_balance_pair_lval().in_place_negate(); + return; default: - assert(0); break; } + + throw_(value_error, "Cannot negate " << label()); } -void value_t::abs() +bool value_t::is_realzero() const { - switch (type) { + switch (type()) { case BOOLEAN: - break; + return ! as_boolean(); case INTEGER: - if (*((long *) data) < 0) - *((long *) data) = - *((long *) data); - break; + return as_long() == 0; case DATETIME: - break; + return ! is_valid_moment(as_datetime()); case AMOUNT: - ((amount_t *) data)->abs(); - break; + return as_amount().is_realzero(); case BALANCE: - ((balance_t *) data)->abs(); - break; + return as_balance().is_realzero(); case BALANCE_PAIR: - ((balance_pair_t *) data)->abs(); - break; + 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(0); + assert(false); break; } + assert(false); + return true; } -value_t value_t::value(const datetime_t& moment) const +value_t value_t::value(const optional& moment) const { - switch (type) { - case BOOLEAN: - throw new value_error("Cannot find the value of a boolean"); - case DATETIME: - throw new value_error("Cannot find the value of a date/time"); + switch (type()) { case INTEGER: return *this; - case AMOUNT: - return ((amount_t *) data)->value(moment); - case BALANCE: - return ((balance_t *) data)->value(moment); - case BALANCE_PAIR: - return ((balance_pair_t *) data)->quantity.value(moment); + + case AMOUNT: { + if (optional val = as_amount().value(moment)) + return *val; + return false; + } + case BALANCE: { + if (optional bal = as_balance().value(moment)) + return *bal; + return false; } + case BALANCE_PAIR: { + if (optional 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 value_t(); } -void value_t::reduce() +void value_t::in_place_reduce() { - switch (type) { - case BOOLEAN: - case DATETIME: + switch (type()) { case INTEGER: - break; + return; case AMOUNT: - ((amount_t *) data)->reduce(); - break; + as_amount_lval().in_place_reduce(); + return; case BALANCE: - ((balance_t *) data)->reduce(); - break; + as_balance_lval().in_place_reduce(); + return; case BALANCE_PAIR: - ((balance_pair_t *) data)->reduce(); + as_balance_pair_lval().in_place_reduce(); + return; + default: break; } + + throw_(value_error, "Cannot reduce " << label()); } -void value_t::round() +value_t value_t::round() const { - switch (type) { - case BOOLEAN: - throw new value_error("Cannot round a boolean"); - case DATETIME: - throw new value_error("Cannot round a date/time"); + switch (type()) { case INTEGER: - break; + return *this; case AMOUNT: - *((amount_t *) data) = ((amount_t *) data)->round(); - break; - case BALANCE: - ((balance_t *) data)->round(); - break; - case BALANCE_PAIR: - ((balance_pair_t *) data)->round(); + return as_amount().round(); + default: break; } + + throw_(value_error, "Cannot round " << label()); + return value_t(); } value_t value_t::unround() const { - value_t temp; - switch (type) { - case BOOLEAN: - throw new value_error("Cannot un-round a boolean"); - case DATETIME: - throw new value_error("Cannot un-round a date/time"); + switch (type()) { case INTEGER: - break; + return *this; case AMOUNT: - temp = ((amount_t *) data)->unround(); - break; - case BALANCE: - temp = ((balance_t *) data)->unround(); - break; - case BALANCE_PAIR: - temp = ((balance_pair_t *) data)->unround(); + return as_amount().unround(); + default: break; } - return temp; + + throw_(value_error, "Cannot unround " << label()); + return value_t(); } -value_t value_t::price() const +value_t value_t::annotated_price() const { - switch (type) { - case BOOLEAN: - throw new value_error("Cannot find the price of a boolean"); - case INTEGER: - return *this; - case DATETIME: - throw new value_error("Cannot find the price of a date/time"); + switch (type()) { + case AMOUNT: { + optional temp = as_amount().annotation_details().price; + if (! temp) + return false; + return *temp; + } - case AMOUNT: - return ((amount_t *) data)->price(); + default: + break; + } - case BALANCE: - return ((balance_t *) data)->price(); + throw_(value_error, "Cannot find the annotated price of " << label()); + return value_t(); +} - case BALANCE_PAIR: - return ((balance_pair_t *) data)->quantity.price(); +value_t value_t::annotated_date() const +{ + switch (type()) { + case DATETIME: + return *this; + + case AMOUNT: { + optional temp = as_amount().annotation_details().date; + if (! temp) + return false; + return *temp; + } default: - assert(0); break; } - assert(0); + + throw_(value_error, "Cannot find the annotated date of " << label()); return value_t(); } -value_t value_t::date() const +value_t value_t::annotated_tag() const { - switch (type) { - case BOOLEAN: - throw new value_error("Cannot find the date of a boolean"); - case INTEGER: - return datetime_t(); + switch (type()) { case DATETIME: return *this; - case AMOUNT: - return datetime_t(((amount_t *) data)->date()); - - case BALANCE: - return datetime_t(((balance_t *) data)->date()); - - case BALANCE_PAIR: - return datetime_t(((balance_pair_t *) data)->quantity.date()); + case AMOUNT: { + optional temp = as_amount().annotation_details().tag; + if (! temp) + return false; + return value_t(*temp, true); + } default: - assert(0); break; } - assert(0); + + throw_(value_error, "Cannot find the annotated tag of " << label()); return value_t(); } @@ -1250,445 +1288,147 @@ value_t value_t::strip_annotations(const bool keep_price, const bool keep_date, const bool keep_tag) const { - switch (type) { + switch (type()) { + case VOID: case BOOLEAN: case INTEGER: case DATETIME: + 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 ((amount_t *) data)->strip_annotations - (keep_price, keep_date, keep_tag); + return as_amount().strip_annotations(keep_price, keep_date, keep_tag); case BALANCE: - return ((balance_t *) data)->strip_annotations - (keep_price, keep_date, keep_tag); + return as_balance().strip_annotations(keep_price, keep_date, keep_tag); case BALANCE_PAIR: - return ((balance_pair_t *) data)->quantity.strip_annotations - (keep_price, keep_date, keep_tag); + return as_balance_pair().quantity().strip_annotations(keep_price, keep_date, + keep_tag); default: - assert(0); + assert(false); break; } - assert(0); + assert(false); return value_t(); } value_t value_t::cost() const { - switch (type) { - case BOOLEAN: - throw new value_error("Cannot find the cost of a boolean"); + switch (type()) { case INTEGER: case AMOUNT: case BALANCE: return *this; - case DATETIME: - throw new value_error("Cannot find the cost of a date/time"); case BALANCE_PAIR: - assert(((balance_pair_t *) data)->cost); - if (((balance_pair_t *) data)->cost) - return *(((balance_pair_t *) data)->cost); + assert(as_balance_pair().cost); + if (as_balance_pair().cost) + return *(as_balance_pair().cost); else - return ((balance_pair_t *) data)->quantity; + return as_balance_pair().quantity(); default: - assert(0); break; } - assert(0); + + throw_(value_error, "Cannot find the cost of " << label()); return value_t(); } -value_t& value_t::add(const amount_t& amount, const amount_t * cost) +value_t& value_t::add(const amount_t& amount, const optional& tcost) { - switch (type) { - case BOOLEAN: - throw new value_error("Cannot add an amount to a boolean"); - case DATETIME: - throw new value_error("Cannot add an amount to a date/time"); + switch (type()) { case INTEGER: case AMOUNT: - if (cost) { - cast(BALANCE_PAIR); - return add(amount, cost); + if (tcost) { + in_place_cast(BALANCE_PAIR); + return add(amount, tcost); } - else if ((type == AMOUNT && - ((amount_t *) data)->commodity() != amount.commodity()) || - (type != AMOUNT && amount.commodity())) { - cast(BALANCE); - return add(amount, cost); + else if ((is_amount() && + as_amount().commodity() != amount.commodity()) || + (! is_amount() && amount.commodity())) { + in_place_cast(BALANCE); + return add(amount, tcost); } - else if (type != AMOUNT) { - cast(AMOUNT); + else if (! is_amount()) { + in_place_cast(AMOUNT); } - *((amount_t *) data) += amount; + *this += amount; break; case BALANCE: - if (cost) { - cast(BALANCE_PAIR); - return add(amount, cost); + if (tcost) { + in_place_cast(BALANCE_PAIR); + return add(amount, tcost); } - *((balance_t *) data) += amount; + *this += amount; break; case BALANCE_PAIR: - ((balance_pair_t *) data)->add(amount, cost); + as_balance_pair_lval().add(amount, tcost); break; default: - assert(0); break; } + throw_(value_error, "Cannot add an amount to " << label()); return *this; } -value_context::value_context(const value_t& _bal, - const std::string& desc) throw() - : bal(new value_t(_bal)), error_context(desc) {} - -value_context::~value_context() throw() -{ - delete bal; -} - -void value_context::describe(std::ostream& out) const throw() +void value_t::print(std::ostream& out, const int first_width, + const int latter_width) const { - if (! desc.empty()) - out << desc << std::endl; - - ledger::balance_t * ptr = NULL; - - out << std::right; - out.width(20); - - switch (bal->type) { - case ledger::value_t::BOOLEAN: - out << (*((bool *) bal->data) ? "true" : "false"); - break; - case ledger::value_t::INTEGER: - out << *((long *) bal->data); + switch (type()) { + case VOID: + out << "NULL"; break; - case ledger::value_t::DATETIME: - out << *((datetime_t *) bal->data); - break; - case ledger::value_t::AMOUNT: - out << *((ledger::amount_t *) bal->data); - break; - case ledger::value_t::BALANCE: - ptr = (ledger::balance_t *) bal->data; - // fall through... - - case ledger::value_t::BALANCE_PAIR: - if (! ptr) - ptr = &((ledger::balance_pair_t *) bal->data)->quantity; - ptr->write(out, 20); - break; - default: - assert(0); + case BOOLEAN: + case DATETIME: + case INTEGER: + case AMOUNT: + case STRING: + case POINTER: + // jww (2007-05-14): I need a version of this print just for XPath + // expression, since amounts and strings need to be output with + // special syntax. + out << *this; + break; + + case SEQUENCE: { + out << '('; + bool first = true; + foreach (const value_t& value, as_sequence()) { + if (first) + first = false; + else + out << ", "; + + value.print(out, first_width, latter_width); + } + out << ')'; break; } - out << std::endl; -} - -} // namespace ledger - -#ifdef USE_BOOST_PYTHON - -#include - -using namespace boost::python; -using namespace ledger; - -long balance_len(balance_t& bal); -amount_t balance_getitem(balance_t& bal, int i); -long balance_pair_len(balance_pair_t& bal_pair); -amount_t balance_pair_getitem(balance_pair_t& bal_pair, int i); - -long value_len(value_t& value) -{ - switch (value.type) { - case value_t::BOOLEAN: - case value_t::INTEGER: - case value_t::DATETIME: - case value_t::AMOUNT: - return 1; - case value_t::BALANCE: - return balance_len(*((balance_t *) value.data)); - - case value_t::BALANCE_PAIR: - return balance_pair_len(*((balance_pair_t *) value.data)); - - default: - assert(0); + case BALANCE: + as_balance().print(out, first_width, latter_width); + break; + case BALANCE_PAIR: + as_balance_pair().print(out, first_width, latter_width); break; - } - assert(0); - return 0; -} - -amount_t value_getitem(value_t& value, int i) -{ - std::size_t len = value_len(value); - - if (abs(i) >= len) { - PyErr_SetString(PyExc_IndexError, "Index out of range"); - throw_error_already_set(); - } - - switch (value.type) { - case value_t::BOOLEAN: - throw new value_error("Cannot cast a boolean to an amount"); - - case value_t::INTEGER: - return long(value); - - case value_t::DATETIME: - throw new value_error("Cannot cast a date/time to an amount"); - - case value_t::AMOUNT: - return *((amount_t *) value.data); - - case value_t::BALANCE: - return balance_getitem(*((balance_t *) value.data), i); - - case value_t::BALANCE_PAIR: - return balance_pair_getitem(*((balance_pair_t *) value.data), i); - default: - assert(0); + assert(false); break; } - assert(0); - return 0L; -} - -double py_to_float(value_t& value) -{ - return double(value); } -void export_value() -{ - scope in_value = class_< value_t > ("Value") - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - - .def(self + self) - .def(self + other()) - .def(self + other()) - .def(self + other()) - .def(self + long()) - .def(self + double()) - - .def(other() + self) - .def(other() + self) - .def(other() + self) - .def(long() + self) - .def(double() + self) - - .def(self - self) - .def(self - other()) - .def(self - other()) - .def(self - other()) - .def(self - long()) - .def(self - double()) - - .def(other() - self) - .def(other() - self) - .def(other() - self) - .def(long() - self) - .def(double() - self) - - .def(self * self) - .def(self * other()) - .def(self * other()) - .def(self * other()) - .def(self * long()) - .def(self * double()) - - .def(other() * self) - .def(other() * self) - .def(other() * self) - .def(long() * self) - .def(double() * self) - - .def(self / self) - .def(self / other()) - .def(self / other()) - .def(self / other()) - .def(self / long()) - .def(self / double()) - - .def(other() / self) - .def(other() / self) - .def(other() / self) - .def(long() / self) - .def(double() / self) - - .def(- self) - - .def(self += self) - .def(self += other()) - .def(self += other()) - .def(self += other()) - .def(self += long()) - .def(self += double()) - - .def(self -= self) - .def(self -= other()) - .def(self -= other()) - .def(self -= other()) - .def(self -= long()) - .def(self -= double()) - - .def(self *= self) - .def(self *= other()) - .def(self *= other()) - .def(self *= other()) - .def(self *= long()) - .def(self *= double()) - - .def(self /= self) - .def(self /= other()) - .def(self /= other()) - .def(self /= other()) - .def(self /= long()) - .def(self /= double()) - - .def(self < self) - .def(self < other()) - .def(self < other()) - .def(self < other()) - .def(self < long()) - .def(self < other()) - .def(self < double()) - - .def(other() < self) - .def(other() < self) - .def(other() < self) - .def(long() < self) - .def(other() < self) - .def(double() < self) - - .def(self <= self) - .def(self <= other()) - .def(self <= other()) - .def(self <= other()) - .def(self <= long()) - .def(self <= other()) - .def(self <= double()) - - .def(other() <= self) - .def(other() <= self) - .def(other() <= self) - .def(long() <= self) - .def(other() <= self) - .def(double() <= self) - - .def(self > self) - .def(self > other()) - .def(self > other()) - .def(self > other()) - .def(self > long()) - .def(self > other()) - .def(self > double()) - - .def(other() > self) - .def(other() > self) - .def(other() > self) - .def(long() > self) - .def(other() > self) - .def(double() > self) - - .def(self >= self) - .def(self >= other()) - .def(self >= other()) - .def(self >= other()) - .def(self >= long()) - .def(self >= other()) - .def(self >= double()) - - .def(other() >= self) - .def(other() >= self) - .def(other() >= self) - .def(long() >= self) - .def(other() >= self) - .def(double() >= self) - - .def(self == self) - .def(self == other()) - .def(self == other()) - .def(self == other()) - .def(self == long()) - .def(self == other()) - .def(self == double()) - - .def(other() == self) - .def(other() == self) - .def(other() == self) - .def(long() == self) - .def(other() == self) - .def(double() == self) - - .def(self != self) - .def(self != other()) - .def(self != other()) - .def(self != other()) - .def(self != long()) - .def(self != other()) - .def(self != double()) - - .def(other() != self) - .def(other() != self) - .def(other() != self) - .def(long() != self) - .def(other() != self) - .def(double() != self) - - .def(! self) - - .def(self_ns::int_(self)) - .def(self_ns::float_(self)) - .def(self_ns::str(self)) - .def(abs(self)) - - .def_readonly("type", &value_t::type) - - .def("__len__", value_len) - .def("__getitem__", value_getitem) - - .def("cast", &value_t::cast) - .def("cost", &value_t::cost) - .def("price", &value_t::price) - .def("date", &value_t::date) - .def("strip_annotations", &value_t::strip_annotations) - .def("add", &value_t::add, return_internal_reference<>()) - .def("value", &value_t::value) - .def("round", &value_t::round) - .def("negate", &value_t::negate) - .def("negated", &value_t::negated) - ; - - enum_< value_t::type_t > ("ValueType") - .value("BOOLEAN", value_t::BOOLEAN) - .value("INTEGER", value_t::INTEGER) - .value("DATETIME", value_t::DATETIME) - .value("AMOUNT", value_t::AMOUNT) - .value("BALANCE", value_t::BALANCE) - .value("BALANCE_PAIR", value_t::BALANCE_PAIR) - ; -} - -#endif // USE_BOOST_PYTHON +} // namespace ledger diff --git a/value.h b/value.h index fe01786b..5618eabb 100644 --- a/value.h +++ b/value.h @@ -1,443 +1,832 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file 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 "amount.h" -#include "balance.h" -#include "error.h" - -#include +#include "balpair.h" // pulls in balance.h and amount.h namespace ledger { -// 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 + * + * @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 > > > > > > > > > > { - public: - char data[sizeof(balance_pair_t)]; - +public: + /** + * The sequence_t member type abstracts the type used to represent a + * resizable "array" of value_t objects. + */ + typedef std::vector 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 { - BOOLEAN, - INTEGER, - DATETIME, - AMOUNT, - BALANCE, - BALANCE_PAIR - } type; + VOID, // a null value (i.e., uninitialized) + BOOLEAN, // a boolean + DATETIME, // a date and time (Boost posix_time) + 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, ""); + } - value_t() { - *((long *) data) = 0; - type = INTEGER; - } + 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(); + } - value_t(const value_t& value) : type(INTEGER) { - *this = value; - } - value_t(const bool value) { - *((bool *) data) = value; - type = BOOLEAN; - } - value_t(const long value) { - *((long *) data) = value; - type = INTEGER; - } - value_t(const datetime_t value) { - *((datetime_t *) data) = value; - type = DATETIME; + 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, ""); + std::memcpy(data, rhs.data, sizeof(data)); + } + storage_t& operator=(const storage_t& rhs) { + type = rhs.type; + std::memcpy(data, rhs.data, sizeof(data)); + return *this; + } + + /** + * 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; + + /** + * _dup() makes a private copy of the current value so that it can + * subsequently be modified. + * + * _clear() removes our pointer to the current value and initializes + * a new value for things to be stored in. + * + * _reset() makes the current object appear as if it had been + * default initialized. + */ + void _dup(); + void _clear() { + if (! storage || storage->refc > 1) + storage = new storage_t; + else + storage->destroy(); + } + void _reset() { + if (storage) + storage = intrusive_ptr(); + } + + /** + * 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 true_value; + static intrusive_ptr 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 long val) { + TRACE_CTOR(value_t, "const long"); + set_long(val); + } + value_t(const moment_t val) { + TRACE_CTOR(value_t, "const moment_t"); + set_datetime(val); + } + value_t(const double val) { + TRACE_CTOR(value_t, "const double"); + set_amount(val); + } + value_t(const unsigned long val) { + TRACE_CTOR(value_t, "const unsigned long"); + set_amount(val); + } + explicit value_t(const string& val, bool literal = false) { + TRACE_CTOR(value_t, "const string&, bool"); + if (literal) + set_string(val); + else + set_amount(amount_t(val)); + } + explicit value_t(const char * val, bool literal = false) { + TRACE_CTOR(value_t, "const char *"); + if (literal) + set_string(val); + else + set_amount(amount_t(val)); + } + value_t(const amount_t& val) { + TRACE_CTOR(value_t, "const amount_t&"); + set_amount(val); + } + value_t(const balance_t& val) { + TRACE_CTOR(value_t, "const balance_t&"); + set_balance(val); + } + value_t(const balance_pair_t& val) { + TRACE_CTOR(value_t, "const balance_pair_t&"); + set_balance_pair(val); + } + value_t(const sequence_t& val) { + TRACE_CTOR(value_t, "const sequence_t&"); + set_sequence(val); } - value_t(const unsigned long value) { - new((amount_t *) data) amount_t(value); - type = AMOUNT; + template + explicit value_t(T * item) { + TRACE_CTOR(value_t, "T *"); + set_pointer(item); } - value_t(const double value) { - new((amount_t *) data) amount_t(value); - type = AMOUNT; + + /** + * 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); } - value_t(const std::string& value) { - new((amount_t *) data) amount_t(value); - type = AMOUNT; + + /** + * 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(const char * value) { - new((amount_t *) data) amount_t(value); - type = AMOUNT; + value_t& operator=(const value_t& val) { + if (! (this == &val || storage == val.storage)) + storage = val.storage; + return *this; } - value_t(const amount_t& value) { - new((amount_t *)data) amount_t(value); - type = AMOUNT; + + /** + * Comparison operators. Values can be compared to other values + */ + bool operator==(const value_t& val) const; + bool operator<(const value_t& val) const; + + template + bool operator==(const T& amt) const { + return *this == value_t(amt); } - value_t(const balance_t& value) : type(INTEGER) { - *this = value; + template + bool operator<(const T& amt) const { + return *this < value_t(amt); + } + + /** + * Binary arithmetic operators. + * + * add(amount_t, optional) 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); + + value_t& add(const amount_t& amount, + const optional& cost = none); + + /** + * Unary arithmetic operators. + */ + value_t negate() const { + value_t temp = *this; + temp.in_place_negate(); + return temp; } - value_t(const balance_pair_t& value) : type(INTEGER) { - *this = value; + void in_place_negate(); + + value_t operator-() const { + return negate(); } - ~value_t() { - destroy(); + 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(); - void destroy(); - void simplify(); + value_t value(const optional& moment = none) const; - value_t& operator=(const value_t& value); - value_t& operator=(const bool value) { - if ((bool *) data != &value) { - destroy(); - *((bool *) data) = value; - type = BOOLEAN; + /** + * Truth tests. + */ + operator bool() const; + + bool is_realzero() const; + bool is_null() const { + if (! storage) { + return true; + } else { + assert(! is_type(VOID)); + return false; } - return *this; } - value_t& operator=(const long value) { - if ((long *) data != &value) { - destroy(); - *((long *) data) = value; - type = INTEGER; - } - return *this; + + type_t type() const { + type_t result = storage ? storage->type : VOID; + assert(result >= VOID && result <= POINTER); + return result; } - value_t& operator=(const datetime_t value) { - if ((datetime_t *) data != &value) { - destroy(); - *((datetime_t *) data) = value; - type = DATETIME; + + 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)); } - return *this; } - value_t& operator=(const unsigned long value) { - return *this = amount_t(value); + +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_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_integer() + * 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 *(bool *) storage->data; + } + const bool& as_boolean() const { + assert(is_boolean()); + return *(bool *) storage->data; + } + void set_boolean(const bool val) { + set_type(BOOLEAN); + storage = val ? true_value : false_value; + } + + bool is_long() const { + return is_type(INTEGER); + } + long& as_long_lval() { + assert(is_long()); + _dup(); + return *(long *) storage->data; + } + const long& as_long() const { + assert(is_long()); + return *(long *) storage->data; + } + void set_long(const long val) { + set_type(INTEGER); + *(long *) storage->data = val; + } + + bool is_datetime() const { + return is_type(DATETIME); } - value_t& operator=(const double value) { - return *this = amount_t(value); + moment_t& as_datetime_lval() { + assert(is_datetime()); + _dup(); + return *(moment_t *) storage->data; } - value_t& operator=(const std::string& value) { - return *this = amount_t(value); + const moment_t& as_datetime() const { + assert(is_datetime()); + return *(moment_t *) storage->data; } - value_t& operator=(const char * value) { - return *this = amount_t(value); + void set_datetime(const moment_t& val) { + set_type(DATETIME); + new((moment_t *) storage->data) moment_t(val); } - value_t& operator=(const amount_t& value) { - if (type == AMOUNT && - (amount_t *) data == &value) - return *this; - - if (value.realzero()) { - return *this = 0L; - } else { - destroy(); - new((amount_t *)data) amount_t(value); - type = AMOUNT; - } - return *this; + + bool is_amount() const { + return is_type(AMOUNT); } - value_t& operator=(const balance_t& value) { - if (type == BALANCE && - (balance_t *) data == &value) - return *this; - - if (value.realzero()) { - return *this = 0L; - } - else if (value.amounts.size() == 1) { - return *this = (*value.amounts.begin()).second; - } - else { - destroy(); - new((balance_t *)data) balance_t(value); - type = BALANCE; - return *this; - } + amount_t& as_amount_lval() { + assert(is_amount()); + _dup(); + return *(amount_t *) storage->data; } - value_t& operator=(const balance_pair_t& value) { - if (type == BALANCE_PAIR && - (balance_pair_t *) data == &value) - return *this; - - if (value.realzero()) { - return *this = 0L; - } - else if (! value.cost) { - return *this = value.quantity; - } - else { - destroy(); - new((balance_pair_t *)data) balance_pair_t(value); - type = BALANCE_PAIR; - return *this; - } + const amount_t& as_amount() const { + assert(is_amount()); + return *(amount_t *) storage->data; + } + void set_amount(const amount_t& val) { + set_type(AMOUNT); + new((amount_t *) storage->data) amount_t(val); } - value_t& operator+=(const value_t& value); - value_t& operator-=(const value_t& value); - value_t& operator*=(const value_t& value); - value_t& operator/=(const value_t& value); + bool is_balance() const { + return is_type(BALANCE); + } + balance_t& as_balance_lval() { + assert(is_balance()); + _dup(); + return **(balance_t **) storage->data; + } + const balance_t& as_balance() const { + assert(is_balance()); + return **(balance_t **) storage->data; + } + void set_balance(const balance_t& val) { + set_type(BALANCE); + *(balance_t **) storage->data = new balance_t(val); + } - template - value_t& operator+=(const T& value) { - return *this += value_t(value); + bool is_balance_pair() const { + return is_type(BALANCE_PAIR); } - template - value_t& operator-=(const T& value) { - return *this -= value_t(value); + balance_pair_t& as_balance_pair_lval() { + assert(is_balance_pair()); + _dup(); + return **(balance_pair_t **) storage->data; } - template - value_t& operator*=(const T& value) { - return *this *= value_t(value); + const balance_pair_t& as_balance_pair() const { + assert(is_balance_pair()); + return **(balance_pair_t **) storage->data; } - template - value_t& operator/=(const T& value) { - return *this /= value_t(value); + void set_balance_pair(const balance_pair_t& val) { + set_type(BALANCE_PAIR); + *(balance_pair_t **) storage->data = new balance_pair_t(val); } - value_t operator+(const value_t& value) { - value_t temp(*this); - temp += value; - return temp; + bool is_string() const { + return is_type(STRING); } - value_t operator-(const value_t& value) { - value_t temp(*this); - temp -= value; - return temp; + string& as_string_lval() { + assert(is_string()); + _dup(); + return *(string *) storage->data; } - value_t operator*(const value_t& value) { - value_t temp(*this); - temp *= value; - return temp; + const string& as_string() const { + assert(is_string()); + return *(string *) storage->data; } - value_t operator/(const value_t& value) { - value_t temp(*this); - temp /= value; - return temp; + void set_string(const string& val = "") { + set_type(STRING); + new((string *) storage->data) string(val); } - template - value_t operator+(const T& value) { - return *this + value_t(value); + bool is_sequence() const { + return is_type(SEQUENCE); } - template - value_t operator-(const T& value) { - return *this - value_t(value); + sequence_t& as_sequence_lval() { + assert(is_sequence()); + _dup(); + return **(sequence_t **) storage->data; } - template - value_t operator*(const T& value) { - return *this * value_t(value); + const sequence_t& as_sequence() const { + assert(is_sequence()); + return **(sequence_t **) storage->data; } - template - value_t operator/(const T& value) { - return *this / value_t(value); + void set_sequence(const sequence_t& val) { + set_type(SEQUENCE); + *(sequence_t **) storage->data = new sequence_t(val); } - bool operator<(const value_t& value); - bool operator<=(const value_t& value); - bool operator>(const value_t& value); - bool operator>=(const value_t& value); - bool operator==(const value_t& value); - bool operator!=(const value_t& value) { - return ! (*this == value); + bool is_pointer() const { + return is_type(POINTER); } - - template - bool operator<(const T& value) { - return *this < value_t(value); + boost::any& as_any_pointer_lval() { + assert(is_pointer()); + _dup(); + return *(boost::any *) storage->data; } template - bool operator<=(const T& value) { - return *this <= value_t(value); + T *& as_pointer_lval() { + assert(is_pointer()); + _dup(); + return any_cast(*(boost::any *) storage->data); } template - bool operator>(const T& value) { - return *this > value_t(value); + T& as_ref_lval() { + assert(is_pointer()); + _dup(); + return *any_cast(*(boost::any *) storage->data); } - template - bool operator>=(const T& value) { - return *this >= value_t(value); + boost::any as_any_pointer() const { + assert(is_pointer()); + return *(boost::any *) storage->data; } template - bool operator==(const T& value) { - return *this == value_t(value); + T * as_pointer() const { + assert(is_pointer()); + return any_cast(*(boost::any *) storage->data); } template - bool operator!=(const T& value) { - return ! (*this == value); + T& as_ref() const { + assert(is_pointer()); + return *any_cast(*(boost::any *) storage->data); + } + void set_any_pointer(const boost::any& val) { + set_type(POINTER); + new((boost::any *) storage->data) boost::any(val); } - template - operator T() const; + void set_pointer(T * val) { + set_type(POINTER); + new((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; + moment_t to_datetime() const; + amount_t to_amount() const; + balance_t to_balance() const; + balance_pair_t to_balance_pair() const; + string to_string() const; + sequence_t to_sequence() const; + + /** + * 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); - void negate(); - value_t negated() const { + value_t simplify() const { value_t temp = *this; - temp.negate(); + temp.in_place_simplify(); return temp; } - value_t operator-() const { - return negated(); + void in_place_simplify(); + + /** + * Annotated commodity methods. + */ + value_t annotated_price() const; + value_t annotated_date() const; + value_t annotated_tag() const; + + 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 + */ + value_t& operator[](const int index) { + assert(! is_null()); + if (is_sequence()) + return as_sequence_lval()[index]; + else if (index == 0) + return *this; + + assert(false); + static value_t null; + return null; } + const value_t& operator[](const int index) const { + assert(! is_null()); + if (is_sequence()) + return as_sequence()[index]; + else if (index == 0) + return *this; + + assert(false); + static value_t null; + return null; + } + + void push_back(const value_t& val) { + if (! val.is_null()) { + if (is_null()) { + *this = val; + } else { + if (! is_sequence()) + in_place_cast(SEQUENCE); + + value_t::sequence_t& seq(as_sequence_lval()); + if (! val.is_sequence()) { + if (! val.is_null()) + seq.push_back(val); + } else { + const value_t::sequence_t& val_seq(val.as_sequence()); + std::copy(val_seq.begin(), val_seq.end(), back_inserter(seq)); + } + } + } + } + + void pop_back() { + assert(! is_null()); - bool realzero() const { - switch (type) { + if (! is_sequence()) { + _reset(); + } else { + as_sequence_lval().pop_back(); + + std::size_t new_size = as_sequence().size(); + if (new_size == 0) + _reset(); + else if (new_size == 1) + *this = as_sequence().front(); + } + } + + const std::size_t size() const { + if (is_null()) + return 0; + else if (is_sequence()) + return as_sequence().size(); + else + return 1; + } + + /** + * Informational methods. + */ + string label(optional the_type = none) const { + switch (the_type ? *the_type : type()) { + case VOID: + return "an uninitialized value"; case BOOLEAN: - return ! *((bool *) data); + return "a boolean"; case INTEGER: - return *((long *) data) == 0; + return "an integer"; case DATETIME: - return ! *((datetime_t *) data); + return "a date/time"; case AMOUNT: - return ((amount_t *) data)->realzero(); + return "an amount"; case BALANCE: - return ((balance_t *) data)->realzero(); + return "a balance"; case BALANCE_PAIR: - return ((balance_pair_t *) data)->realzero(); - + return "a balance pair"; + case STRING: + return "a string"; + case SEQUENCE: + return "a sequence"; + case POINTER: + return "a pointer"; default: - assert(0); + assert(false); break; } - assert(0); - return 0; + assert(false); + return ""; } - void abs(); - void cast(type_t cast_type); value_t cost() const; - value_t price() const; - value_t date() const; - 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; + /** + * Printing methods. + */ + void print(std::ostream& out, const int first_width, + const int latter_width = -1) const; - value_t& add(const amount_t& amount, const amount_t * cost = NULL); - value_t value(const datetime_t& moment) const; - void reduce(); - - value_t reduced() const { - value_t temp(*this); - temp.reduce(); - return temp; - } - - void round(); - value_t unround() const; + /** + * Debugging methods. + */ }; -#define DEF_VALUE_AUX_OP(OP) \ - inline value_t operator OP(const balance_pair_t& value, \ - const value_t& obj) { \ - return value_t(value) OP obj; \ - } \ - inline value_t operator OP(const balance_t& value, \ - const value_t& obj) { \ - return value_t(value) OP obj; \ - } \ - inline value_t operator OP(const amount_t& value, \ - const value_t& obj) { \ - return value_t(value) OP obj; \ - } \ - template \ - inline value_t operator OP(T value, const value_t& obj) { \ - return value_t(value) OP obj; \ - } - -DEF_VALUE_AUX_OP(+) -DEF_VALUE_AUX_OP(-) -DEF_VALUE_AUX_OP(*) -DEF_VALUE_AUX_OP(/) - -DEF_VALUE_AUX_OP(<) -DEF_VALUE_AUX_OP(<=) -DEF_VALUE_AUX_OP(>) -DEF_VALUE_AUX_OP(>=) -DEF_VALUE_AUX_OP(==) -DEF_VALUE_AUX_OP(!=) - -template -value_t::operator T() const -{ - switch (type) { - case BOOLEAN: - return *((bool *) data); - case INTEGER: - return *((long *) data); - case DATETIME: - return *((datetime_t *) data); - case AMOUNT: - return *((amount_t *) data); - case BALANCE: - return *((balance_t *) data); - case BALANCE_PAIR: - return *((balance_pair_t *) data); - - default: - assert(0); - break; - } - assert(0); - return 0; -} - -template <> value_t::operator long() const; -template <> value_t::operator datetime_t() const; -template <> value_t::operator double() const; +#define NULL_VALUE (value_t()) -inline value_t abs(const value_t& value) { - value_t temp(value); - temp.abs(); - return temp; +inline value_t string_value(const string& str) { + return value_t(str, true); } -inline std::ostream& operator<<(std::ostream& out, const value_t& value) { - switch (value.type) { - case value_t::BOOLEAN: - out << (*((bool *) value.data) ? "true" : "false"); - break; - case value_t::INTEGER: - out << *((long *) value.data); - break; - case value_t::DATETIME: - out << *((datetime_t *) value.data); - break; - case value_t::AMOUNT: - out << *((amount_t *) value.data); - break; - case value_t::BALANCE: - out << *((balance_t *) value.data); - break; - case value_t::BALANCE_PAIR: - out << *((balance_pair_t *) value.data); - break; - - default: - assert(0); - break; - } +inline std::ostream& operator<<(std::ostream& out, const value_t& val) { + val.print(out, 12); return out; } -class value_context : public error_context -{ - value_t * bal; - public: - value_context(const value_t& _bal, - const std::string& desc = "") throw(); - virtual ~value_context() throw(); - - virtual void describe(std::ostream& out) const throw(); -}; - -class value_error : public error { - public: - value_error(const std::string& reason, error_context * ctxt = NULL) throw() - : error(reason, ctxt) {} - virtual ~value_error() throw() {} -}; +DECLARE_EXCEPTION(error, value_error); } // namespace ledger diff --git a/version b/version new file mode 100755 index 00000000..b481c6f4 --- /dev/null +++ b/version @@ -0,0 +1,5 @@ +#!/bin/sh + +cat configure.in | \ + grep "^AC_INIT" | \ + sed 's/AC_INIT(\[ledger\],\[\([^]]*\)\],.*/\1/' -- cgit v1.2.3