diff options
141 files changed, 15628 insertions, 9543 deletions
@@ -25,6 +25,7 @@ /config.status /config.sub /configure +/configure.in /depcomp /elc-stamp /elisp-comp @@ -42,6 +43,7 @@ AUTHORS INSTALL Makefile Makefile.in +TAGS acconf.h acconf.h.in aclocal.m4 diff --git a/Makefile.am b/Makefile.am index bb483b26..a15edf50 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,20 +1,42 @@ +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 \ + commodity.cc \ balance.cc \ - datetime.cc \ - value.cc + value.cc \ + times.cc \ + utils.cc +if HAVE_EXPAT +libamounts_la_CPPFLAGS += -DHAVE_EXPAT=1 +endif +if HAVE_XMLPARSE +libamounts_la_CPPFLAGS += -DHAVE_XMLPARSE=1 +endif +if HAVE_LIBOFX +libamounts_la_CPPFLAGS += -DHAVE_LIBOFX=1 +endif 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 endif -libledger_la_CXXFLAGS = +libledger_la_CPPFLAGS = $(libamounts_la_CPPFLAGS) libledger_la_SOURCES = \ binary.cc \ config.cc \ @@ -27,7 +49,6 @@ libledger_la_SOURCES = \ option.cc \ parser.cc \ qif.cc \ - quotes.cc \ reconcile.cc \ report.cc \ startup.cc \ @@ -35,32 +56,29 @@ libledger_la_SOURCES = \ valexpr.cc \ walk.cc \ xml.cc +# quotes.cc this is currently not being included if HAVE_EXPAT -libledger_la_CXXFLAGS += -DHAVE_EXPAT=1 libledger_la_SOURCES += gnucash.cc endif if HAVE_XMLPARSE -libledger_la_CXXFLAGS += -DHAVE_XMLPARSE=1 libledger_la_SOURCES += gnucash.cc endif if HAVE_LIBOFX -libledger_la_CXXFLAGS += -DHAVE_LIBOFX=1 libledger_la_SOURCES += ofx.cc endif -if DEBUG -libledger_la_CXXFLAGS += -DDEBUG_LEVEL=4 -endif -libledger_la_LDFLAGS = -release 2.6.1 + +libledger_la_LDFLAGS = -release 2.7.0 pkginclude_HEADERS = \ acconf.h \ \ amount.h \ + commodity.h \ balance.h \ - datetime.h \ + balpair.h \ value.h \ - debug.h \ - util.h \ + times.h \ + utils.h \ \ binary.h \ config.h \ @@ -80,37 +98,38 @@ pkginclude_HEADERS = \ reconcile.h \ report.h \ textual.h \ - timing.h \ valexpr.h \ walk.h \ xml.h -###################################################################### +if USE_PCH +nodist_libledger_la_SOURCES = system.hh.gch -bin_PROGRAMS = ledger -ledger_CXXFLAGS = -ledger_SOURCES = main.cc -ledger_LDADD = $(LIBOBJS) libamounts.la libledger.la -if HAVE_EXPAT -ledger_CXXFLAGS += -DHAVE_EXPAT=1 -endif -if HAVE_XMLPARSE -ledger_CXXFLAGS += -DHAVE_XMLPARSE=1 -endif -if HAVE_LIBOFX -ledger_CXXFLAGS += -DHAVE_LIBOFX=1 -endif -if DEBUG -ledger_CXXFLAGS += -DDEBUG_LEVEL=4 -endif -ledger_LDFLAGS = -static # for the sake of command-line speed +BUILT_SOURCES += system.hh.gch +CLEANFILES += system.hh.gch system.hh -info_TEXINFOS = ledger.texi +$(top_builddir)/system.hh.gch: $(srcdir)/system.hh $(top_builddir)/acconf.h + echo "#include \"$(srcdir)/system.hh\"" > $(top_builddir)/system.hh + $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(libledger_la_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) \ + -g -o $@ $(srcdir)/system.hh +endif ###################################################################### -lisp_LISP = ledger.el timeclock.el -dist_lisp_LISP = ledger.el timeclock.el +bin_PROGRAMS = ledger + +ledger_CPPFLAGS = $(libledger_la_CPPFLAGS) +ledger_SOURCES = main.cc +ledger_LDADD = $(LIBOBJS) libamounts.la libledger.la +ledger_LDFLAGS = -static # for the sake of command-line speed + +info_TEXINFOS = ledger.texi + +dist_lisp_LISP = ledger.el timeclock.el + +DISTCLEANFILES = ledger.elc timeclock.elc ###################################################################### @@ -118,59 +137,153 @@ 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) $(DEFAULT_INCLUDES) $(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) $(DEFAULT_INCLUDES) $(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 = test/UnitTests.cc \ + \ + test/utility/t_utils.cc \ + test/utility/t_times.cc \ + test/numerics/t_commodity.cc \ + test/numerics/t_amount.cc \ + test/numerics/t_balance.cc + +UnitTests_CPPFLAGS = -I$(srcdir)/test $(libledger_la_CPPFLAGS) +UnitTests_LDFLAGS = $(LIBADD_DL) +UnitTests_LDADD = $(lib_LTLIBRARIES) -lcppunit + +nodist_PyUnitTests_SOURCES = test/python/PyUnitTests.py + +# jww (2007-05-10): This rule will not be triggered on systems that +# define an EXEEXT. +PyUnitTests: $(srcdir)/test/python/PyUnitTests.py + cat $(srcdir)/test/python/PyUnitTests.py \ + | sed "s/%srcdir%/$(ESC_srcdir)/g" \ + | sed "s/%builddir%/$(ESC_builddir)/g" > $@ + chmod 755 $@ + +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.cc: $(TESTSUITES) - test -f $(TESTGEN) && python $(TESTGEN) -o $@ --error-printer $(TESTSUITES) +############################################################################### -alltests: alltests.cc ledger - $(CXXCOMPILE) -I$(CXXTEST_DIR) -lexpat -lgmp -lpcre -o $@ \ - alltests.cc -L. -L.libs -lamounts -lledger +DISTCLEANFILES += Doxyfile.gen -runtests: alltests - LD_LIBRARY_PATH=.libs ./alltests && tests/regress && tests/regtest +alldocs: ledger.info ledger.pdf doxygen-docs -verify: runtests - python tests/runtests.py +$(top_builddir)/Doxyfile.gen: $(srcdir)/Doxyfile + cat $(srcdir)/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 @@ -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 @@ -9,25 +18,20 @@ cmd=$(which glibtoolize 2>&1) if [ -x "$cmd" ]; then export LIBTOOLIZE="$cmd" fi + +cat configure.tmpl | sed "s/%VERSION%/$(git describe)/" > configure.in + 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="/usr" -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 +47,97 @@ 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 -Winvalid-pch" +#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" + SWITCHES="$SWITCHES --disable-shared" + CPPFLAGS="$CPPFLAGS -DBOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING=1" + CPPFLAGS="$CPPFLAGS -DBOOST_MULTI_INDEX_ENABLE_SAFE_MODE=1" + ;; + + --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)" + projdir="$projdir$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" CXX="$CXX" \ + CPPFLAGS="$CPPFLAGS" CXXFLAGS="$CXXFLAGS $local_cxxflags" \ + LDFLAGS="$LDFLAGS" LIBS="$LIBS" $SWITCHES "$@" @@ -1,287 +1,318 @@ -#include "amount.h" -#include "util.h" - -#include <list> -#include <sstream> -#include <cstdlib> +/* + * 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 <gmp.h> +#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; -#define BIGINT_BULK_ALLOC 0x0001 -#define BIGINT_KEEP_PREC 0x0002 +bool amount_t::stream_fullstrings = false; -class amount_t::bigint_t { - public: - mpz_t val; - unsigned char prec; - unsigned char flags; - unsigned int ref; - unsigned int index; +#ifndef THREADSAFE +/** + * These global temporaries are pre-initialized for the sake of + * efficiency, and reused over and over again. + */ +static mpz_t temp; +static mpz_t divisor; +#endif + +struct amount_t::bigint_t : public supports_flags<> +{ +#define BIGINT_BULK_ALLOC 0x01 +#define BIGINT_KEEP_PREC 0x02 - bigint_t() : prec(0), flags(0), ref(1), index(0) { + mpz_t val; + precision_t prec; + uint_least16_t ref; + uint_fast32_t index; + +#define MPZ(bigint) ((bigint)->val) + + bigint_t() : prec(0), ref(1), index(0) { + TRACE_CTOR(bigint_t, ""); mpz_init(val); } - bigint_t(mpz_t _val) : prec(0), 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); +uint_fast32_t amount_t::sizeof_bigint_t() +{ + return sizeof(bigint_t); } -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 +324,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 +386,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 +396,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 +422,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 +518,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_; - 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 this amount has a commodity, and we're not dealing with plain + // numbers, or internal numbers (which keep full precision at all + // times), then round the number to within the commodity's precision + // plus six places. + + if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) { + precision_t comm_prec = commodity().precision(); + if (quantity->prec > comm_prec + 6U) { + mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); + quantity->prec = comm_prec + 6U; + } } return *this; } -// 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"); - if (quantity->prec <= commodity().precision()) { - return mpz_sgn(MPZ(quantity)) != 0; + while (commodity_ && commodity().larger()) { + *this /= commodity().larger()->number(); + commodity_ = commodity().larger()->commodity_; + if (abs() < amount_t(1.0)) + break; + } + return *this; +} + +optional<amount_t> amount_t::value(const optional<datetime_t>& moment) const +{ + if (quantity) { + optional<amount_t> amt(commodity().value(moment)); + if (amt) + return (*amt * number()).round(); } else { - 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,486 +725,151 @@ amount_t::operator double() const mpz_clear(remainder); - return std::atof(num.str().c_str()); -} + double value = lexical_cast<double>(num.str()); -bool amount_t::realzero() const -{ - if (! quantity) - return true; - return mpz_sgn(MPZ(quantity)) == 0; -} + if (! no_check && *this != value) + throw_(amount_error, "Conversion of amount to_double loses precision"); -amount_t amount_t::value(const datetime_t& moment) const -{ - if (quantity) { - amount_t amt(commodity().value(moment)); - if (! amt.realzero()) - return (amt * *this).round(); - } - return *this; + return value; } -amount_t amount_t::round(unsigned int prec) const +long amount_t::to_long(bool no_check) const { - amount_t temp = *this; - - if (! quantity || quantity->prec <= prec) { - if (quantity && quantity->flags & BIGINT_KEEP_PREC) { - temp._dup(); - temp.quantity->flags &= ~BIGINT_KEEP_PREC; - } - return temp; - } + if (! quantity) + throw_(amount_error, "Cannot convert an uninitialized amount to a long"); - temp._dup(); + mpz_set(temp, MPZ(quantity)); + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_q(temp, temp, divisor); - mpz_round(MPZ(temp.quantity), MPZ(temp.quantity), temp.quantity->prec, prec); + long value = mpz_get_si(temp); - temp.quantity->prec = prec; - temp.quantity->flags &= ~BIGINT_KEEP_PREC; + if (! no_check && *this != value) + throw_(amount_error, "Conversion of amount to_long loses precision"); - return temp; + return value; } -amount_t amount_t::unround() const +bool amount_t::fits_in_double() 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; - } - - amount_t temp = *this; - temp._dup(); - temp.quantity->flags |= BIGINT_KEEP_PREC; - - return temp; + double value = to_double(true); + return *this == amount_t(value); } -std::string amount_t::quantity_string() const +bool amount_t::fits_in_long() const { - if (! quantity) - return "0"; - - 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(commodity()); - unsigned char precision; - - if (! comm || quantity->flags & BIGINT_KEEP_PREC) { - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); - precision = quantity->prec; - } - else if (comm.precision() < quantity->prec) { - mpz_round(rquotient, MPZ(quantity), quantity->prec, comm.precision()); - mpz_ui_pow_ui(divisor, 10, comm.precision()); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision(); - } - else if (comm.precision() > quantity->prec) { - mpz_ui_pow_ui(divisor, 10, comm.precision() - quantity->prec); - mpz_mul(rquotient, MPZ(quantity), divisor); - mpz_ui_pow_ui(divisor, 10, comm.precision()); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision(); - } - else if (quantity->prec) { - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); - precision = quantity->prec; - } - else { - mpz_set(quotient, MPZ(quantity)); - mpz_set_ui(remainder, 0); - precision = 0; - } - - if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { - negative = true; + long value = to_long(true); + return *this == amount_t(value); +} - mpz_abs(quotient, quotient); - mpz_abs(remainder, remainder); - } - mpz_set(rquotient, remainder); - if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) - return "0"; +void amount_t::annotate_commodity(const annotation_t& details) +{ + commodity_t * this_base; + annotated_commodity_t * this_ann = NULL; - if (negative) - out << "-"; + 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 (mpz_sgn(quotient) == 0) { - out << '0'; + if (commodity().annotated) { + this_ann = &as_annotated_commodity(commodity()); + this_base = &this_ann->referent(); } else { - char * p = mpz_get_str(NULL, 10, quotient); - out << p; - std::free(p); + this_base = &commodity(); } + assert(this_base); - if (precision) { - out << '.'; - - out.width(precision); - out.fill('0'); + DEBUG("amounts.commodities", "Annotating commodity for amount " + << *this << std::endl << details); - char * p = mpz_get_str(NULL, 10, rquotient); - out << p; - std::free(p); - } - - mpz_clear(quotient); - mpz_clear(rquotient); - mpz_clear(remainder); + 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 - return out.str(); + DEBUG("amounts.commodities", " Annotated amount is " << *this); } -std::ostream& operator<<(std::ostream& _out, const amount_t& amt) +bool amount_t::commodity_annotated() const { - 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::ostringstream out; - - mpz_t quotient; - mpz_t rquotient; - mpz_t remainder; - - mpz_init(quotient); - mpz_init(rquotient); - mpz_init(remainder); - - bool negative = false; - - // Ensure the value is rounded to the commodity's precision before - // outputting it. NOTE: `rquotient' is used here as a temp variable! - - commodity_t& comm(base.commodity()); - unsigned char precision; - - 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; - } - 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 (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) { - _out << "0"; - return _out; - } - - if (! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) { - comm.write(out); - - if (comm.flags() & COMMODITY_STYLE_SEPARATED) - out << " "; - } - - if (negative) - out << "-"; - - 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); - } - else { - std::list<std::string> strs; - char buf[4]; - - for (int powers = 0; true; powers += 3) { - if (powers > 0) { - mpz_ui_pow_ui(divisor, 10, powers); - mpz_tdiv_q(temp, quotient, divisor); - if (mpz_sgn(temp) == 0) - break; - mpz_tdiv_r_ui(temp, temp, 1000); - } else { - mpz_tdiv_r_ui(temp, quotient, 1000); - } - mpz_get_str(buf, 10, temp); - strs.push_back(buf); - } - - bool printed = false; - - for (std::list<std::string>::reverse_iterator i = strs.rbegin(); - i != strs.rend(); - i++) { - if (printed) { - out << (comm.flags() & COMMODITY_STYLE_EUROPEAN ? '.' : ','); - out.width(3); - out.fill('0'); - } - out << *i; - - printed = true; - } - } - - if (precision) { - std::ostringstream final; - final.width(precision); - final.fill('0'); - char * p = mpz_get_str(NULL, 10, rquotient); - final << p; - std::free(p); - - const std::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; - if (i == len) - ender = str; - else if (i < comm.precision()) - ender = std::string(str, 0, comm.precision()); - else - ender = std::string(str, 0, i); - - if (! ender.empty()) { - out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); - out << ender; - } - } - - if (comm.flags() & COMMODITY_STYLE_SUFFIXED) { - if (comm.flags() & COMMODITY_STYLE_SEPARATED) - out << " "; + if (! quantity) + throw_(amount_error, + "Cannot determine if an uninitialized amount's commodity is annotated"); - comm.write(out); - } + assert(! commodity().annotated || as_annotated_commodity(commodity()).details); + return commodity().annotated; +} - mpz_clear(quotient); - mpz_clear(rquotient); - mpz_clear(remainder); +annotation_t amount_t::annotation_details() const +{ + if (! quantity) + throw_(amount_error, + "Cannot return commodity annotation details of an uninitialized amount"); - // If there are any annotations associated with this commodity, - // output them now. + assert(! commodity().annotated || as_annotated_commodity(commodity()).details); - if (comm.annotated) { - annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm)); - assert(&ann.price != &amt); - ann.write_annotations(out); + if (commodity().annotated) { + annotated_commodity_t& ann_comm(as_annotated_commodity(commodity())); + return ann_comm.details; } - - // Things are output to a string first, so that if anyone has - // specified a width or fill for _out, it will be applied to the - // entire amount string, and not just the first part. - - _out << out.str(); - - return _out; + return annotation_t(); } -void parse_quantity(std::istream& in, std::string& value) +amount_t amount_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) const { - char buf[256]; - char c = peek_next_nonws(in); - READ_INTO(in, buf, 255, c, - std::isdigit(c) || c == '-' || c == '.' || c == ','); + if (! quantity) + throw_(amount_error, + "Cannot strip commodity annotations from an uninitialized amount"); - int len = std::strlen(buf); - while (len > 0 && ! std::isdigit(buf[len - 1])) { - buf[--len] = '\0'; - in.unget(); - } + if (! commodity().annotated || + (_keep_price && _keep_date && _keep_tag)) + return *this; - value = buf; + amount_t t(*this); + t.set_commodity(as_annotated_commodity(commodity()). + strip_annotations(_keep_price, _keep_date, _keep_tag)); + return t; } -// 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 { +namespace { + void parse_quantity(std::istream& in, string& value) + { 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(); + READ_INTO(in, buf, 255, c, + std::isdigit(c) || c == '-' || c == '.' || c == ','); - // 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 + int len = std::strlen(buf); + while (len > 0 && ! std::isdigit(buf[len - 1])) { + buf[--len] = '\0'; + in.unget(); } - 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); + value = buf; + } } -void amount_t::parse(std::istream& in, unsigned char flags) +void amount_t::parse(std::istream& in, flags_t flags) { // 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; + 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 == '-') { @@ -1092,16 +886,16 @@ void amount_t::parse(std::istream& in, unsigned char flags) if (std::isspace(n)) comm_flags |= COMMODITY_STYLE_SEPARATED; - parse_commodity(in, symbol); + commodity_t::parse_symbol(in, symbol); if (! symbol.empty()) comm_flags |= COMMODITY_STYLE_SUFFIXED; if (! in.eof() && ((n = in.peek()) != '\n')) - parse_annotations(in, price, date, tag); + details.parse(in); } } else { - parse_commodity(in, symbol); + commodity_t::parse_symbol(in, symbol); if (! in.eof() && ((n = in.peek()) != '\n')) { if (std::isspace(in.peek())) @@ -1110,14 +904,30 @@ void amount_t::parse(std::istream& in, unsigned char flags) parse_quantity(in, quant); if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n')) - parse_annotations(in, price, date, tag); + details.parse(in); } } if (quant.empty()) - throw new amount_error("No quantity specified for amount"); + throw_(amount_error, "No quantity specified for amount"); + + // Allocate memory for the amount's quantity value. We have to + // monitor the allocation in an auto_ptr because this function gets + // called sometimes from amount_t's constructor; and if there is an + // exeception thrown by any of the function calls after this point, + // the destructor will never be called and the memory never freed. + + std::auto_ptr<bigint_t> safe_holder; - _init(); + if (! quantity) { + quantity = new bigint_t; + safe_holder.reset(quantity); + } + else if (quantity->ref > 1) { + _release(); + quantity = new bigint_t; + safe_holder.reset(quantity); + } // Create the commodity if has not already been seen, and update the // precision if something greater was used for the quantity. @@ -1125,27 +935,26 @@ void amount_t::parse(std::istream& in, unsigned char flags) bool newly_created = false; if (symbol.empty()) { - commodity_ = commodity_t::null_commodity; + commodity_ = NULL; } else { - commodity_ = commodity_t::find(symbol); + commodity_ = current_pool->find(symbol); if (! commodity_) { - commodity_ = commodity_t::create(symbol); + commodity_ = current_pool->create(symbol); newly_created = true; } assert(commodity_); - if (! price.realzero() || date || ! tag.empty()) - commodity_ = - annotated_commodity_t::find_or_create(*commodity_, price, date, tag); + if (details) + commodity_ = current_pool->find_or_create(*commodity_, details); } // 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('.'); + string::size_type last_comma = quant.rfind(','); + string::size_type last_period = quant.rfind('.'); - if (last_comma != std::string::npos && last_period != std::string::npos) { + if (last_comma != string::npos && last_period != string::npos) { comm_flags |= COMMODITY_STYLE_THOUSANDS; if (last_comma > last_period) { comm_flags |= COMMODITY_STYLE_EUROPEAN; @@ -1154,14 +963,12 @@ void amount_t::parse(std::istream& in, unsigned char flags) 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; + else if (last_comma != string::npos && + commodity().has_flags(COMMODITY_STYLE_EUROPEAN)) { quantity->prec = quant.length() - last_comma - 1; } - else if (last_period != std::string::npos && - ! (commodity().flags() & COMMODITY_STYLE_EUROPEAN)) { + else if (last_period != string::npos && + ! (commodity().has_flags(COMMODITY_STYLE_EUROPEAN))) { quantity->prec = quant.length() - last_period - 1; } else { @@ -1170,18 +977,22 @@ void amount_t::parse(std::istream& in, unsigned char flags) // Set the commodity's flags and precision accordingly - if (! (flags & AMOUNT_PARSE_NO_MIGRATE)) { + if (commodity_ && ! (flags & AMOUNT_PARSE_NO_MIGRATE)) { commodity().add_flags(comm_flags); + if (quantity->prec > commodity().precision()) commodity().set_precision(quantity->prec); - } else { - quantity->flags |= BIGINT_KEEP_PREC; } + // Setup the amount's own flags + + if (flags & AMOUNT_PARSE_NO_MIGRATE) + quantity->add_flags(BIGINT_KEEP_PREC); + // Now we have the final number. Remove commas and periods, if // necessary. - if (last_comma != std::string::npos || last_period != std::string::npos) { + 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(); @@ -1195,41 +1006,29 @@ void amount_t::parse(std::istream& in, unsigned char flags) *t = '\0'; mpz_set_str(MPZ(quantity), buf, 10); - delete[] buf; + checked_array_delete(buf); } else { mpz_set_str(MPZ(quantity), quant.c_str(), 10); } if (negative) - negate(); + in_place_negate(); if (! (flags & AMOUNT_PARSE_NO_REDUCE)) - reduce(); -} + in_place_reduce(); -void amount_t::reduce() -{ - while (commodity_ && commodity().smaller()) { - *this *= *commodity().smaller(); - commodity_ = commodity().smaller()->commodity_; - } + safe_holder.release(); // `this->quantity' owns the pointer } -void amount_t::parse(const std::string& str, unsigned char flags) -{ - std::istringstream stream(str); - parse(stream, flags); -} - -void parse_conversion(const std::string& larger_str, - const std::string& smaller_str) +void amount_t::parse_conversion(const string& larger_str, + const string& smaller_str) { amount_t larger, smaller; - larger.parse(larger_str.c_str(), AMOUNT_PARSE_NO_REDUCE); - smaller.parse(smaller_str.c_str(), AMOUNT_PARSE_NO_REDUCE); + larger.parse(larger_str, AMOUNT_PARSE_NO_REDUCE); + smaller.parse(smaller_str, AMOUNT_PARSE_NO_REDUCE); - larger *= smaller; + larger *= smaller.number(); if (larger.commodity()) { larger.commodity().set_smaller(smaller); @@ -1241,773 +1040,371 @@ void parse_conversion(const std::string& larger_str, } -char * bigints; -char * bigints_next; -unsigned int bigints_index; -unsigned int bigints_count; - -void amount_t::read_quantity(char *& data) +void amount_t::print(std::ostream& _out, bool omit_commodity, + bool full_precision) const { - 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 (! quantity) + throw_(amount_error, "Cannot write out an uninitialized amount"); - unsigned short len = *((unsigned short *) data); - data += sizeof(unsigned short); - mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), - 0, 0, data); - data += len; + amount_t base(*this); + if (! amount_t::keep_base) + base.in_place_unreduce(); - char negative = *data++; - if (negative) - mpz_neg(MPZ(quantity), MPZ(quantity)); + std::ostringstream out; - quantity->prec = *((unsigned char *) data); - data += sizeof(unsigned char); - quantity->flags = *((unsigned char *) data); - data += sizeof(unsigned char); - quantity->flags |= BIGINT_BULK_ALLOC; - } else { - unsigned int index = *((unsigned int *) data); - data += sizeof(unsigned int); + mpz_t quotient; + mpz_t rquotient; + mpz_t remainder; - quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); - DEBUG_PRINT("amounts.refs", - quantity << " ref++, now " << (quantity->ref + 1)); - quantity->ref++; - } -} + mpz_init(quotient); + mpz_init(rquotient); + mpz_init(remainder); -static char buf[4096]; + bool negative = false; -void amount_t::read_quantity(std::istream& in) -{ - char byte; - in.read(&byte, sizeof(byte)); + // Ensure the value is rounded to the commodity's precision before + // outputting it. NOTE: `rquotient' is used here as a temp variable! - if (byte == 0) { - quantity = NULL; - } - else if (byte == 1) { - quantity = new bigint_t; + commodity_t& comm(base.commodity()); + precision_t precision = 0; - 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) { + 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; + } - char negative; - in.read(&negative, sizeof(negative)); - if (negative) - mpz_neg(MPZ(quantity), MPZ(quantity)); + if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { + negative = true; - in.read((char *)&quantity->prec, sizeof(quantity->prec)); - in.read((char *)&quantity->flags, sizeof(quantity->flags)); - } - else { - assert(0); + mpz_abs(quotient, quotient); + mpz_abs(remainder, remainder); + } + mpz_set(rquotient, remainder); } -} -void amount_t::write_quantity(std::ostream& out) const -{ - char byte; - - if (! quantity) { - byte = 0; - out.write(&byte, sizeof(byte)); - return; + if (! omit_commodity && ! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + comm.print(out); + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; } - if (quantity->index == 0) { - quantity->index = ++bigints_index; - bigints_count++; + if (negative) + out << "-"; - byte = 1; - out.write(&byte, sizeof(byte)); + if (! quantity || mpz_sgn(quotient) == 0) { + out << '0'; + } + else if (omit_commodity || ! comm.has_flags(COMMODITY_STYLE_THOUSANDS)) { + char * p = mpz_get_str(NULL, 10, quotient); + out << p; + std::free(p); + } + else { + std::list<string> strs; + char buf[4]; - std::size_t size; - mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity)); - unsigned short len = size * sizeof(short); - out.write((char *)&len, sizeof(len)); - if (len) { - assert(len < 4096); - out.write(buf, len); + for (int powers = 0; true; powers += 3) { + if (powers > 0) { + mpz_ui_pow_ui(divisor, 10, powers); + mpz_tdiv_q(temp, quotient, divisor); + if (mpz_sgn(temp) == 0) + break; + mpz_tdiv_r_ui(temp, temp, 1000); + } else { + mpz_tdiv_r_ui(temp, quotient, 1000); + } + mpz_get_str(buf, 10, temp); + strs.push_back(buf); } - byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0; - out.write(&byte, sizeof(byte)); - - out.write((char *)&quantity->prec, sizeof(quantity->prec)); - unsigned char flags = quantity->flags & ~BIGINT_BULK_ALLOC; - assert(sizeof(flags) == sizeof(quantity->flags)); - out.write((char *)&flags, sizeof(flags)); - } else { - assert(quantity->ref > 1); + bool printed = false; - // Since this value has already been written, we simply write - // out a reference to which one it was. - byte = 2; - out.write(&byte, sizeof(byte)); - out.write((char *)&quantity->index, sizeof(quantity->index)); - } -} + for (std::list<string>::reverse_iterator i = strs.rbegin(); + i != strs.rend(); + i++) { + if (printed) { + out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? '.' : ','); + out.width(3); + out.fill('0'); + } + out << *i; -bool amount_t::valid() const -{ - if (quantity) { - if (quantity->ref == 0) { - DEBUG_PRINT("ledger.validate", "amount_t: quantity->ref == 0"); - return false; + printed = true; } } - 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<annotated_commodity_t&>(commodity()); - this_base = this_ann->ptr; - } else { - this_base = &commodity(); - } - assert(this_base); + if (quantity && precision) { + std::ostringstream final; + final.width(precision); + final.fill('0'); + char * p = mpz_get_str(NULL, 10, rquotient); + final << p; + std::free(p); - 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); + 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; - DEBUG_PRINT("amounts.commodities", " Annotated amount is " << *this); -} + string ender; + if (i == len) + ender = str; + else if (i < comm.precision()) + ender = string(str, 0, comm.precision()); + else + ender = string(str, 0, i); -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; + if (! ender.empty()) { + if (omit_commodity) + out << '.'; + else + out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); + out << ender; + } + } - DEBUG_PRINT("amounts.commodities", "Reducing commodity for amount " - << *this << std::endl - << " keep price " << _keep_price << " " - << " keep date " << _keep_date << " " - << " keep tag " << _keep_tag); + if (! omit_commodity && comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; + comm.print(out); + } - annotated_commodity_t& - ann_comm(static_cast<annotated_commodity_t&>(commodity())); - assert(ann_comm.base); + mpz_clear(quotient); + mpz_clear(rquotient); + mpz_clear(remainder); - commodity_t * new_comm; + // If there are any annotations associated with this commodity, + // output them now. - 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()); + if (! omit_commodity && comm.annotated) { + annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm)); + assert(&*ann.details.price != this); + ann.write_annotations(out); } - assert(new_comm); - - amount_t temp(*this); - temp.set_commodity(*new_comm); - DEBUG_PRINT("amounts.commodities", " Reduced amount is " << temp); + // Things are output to a string first, so that if anyone has + // specified a width or fill for _out, it will be applied to the + // entire amount string, and not just the first part. - return temp; + _out << out.str(); } -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; +#if 0 +// jww (2008-05-08): Should these be global? +namespace { +#endif + char * bigints; + char * bigints_next; + uint_fast32_t bigints_index; + uint_fast32_t bigints_count; + char buf[4096]; +#if 0 } +#endif - -void commodity_base_t::add_price(const datetime_t& date, - const amount_t& price) +void amount_t::read(std::istream& in) { - if (! history) - history = new history_t; + using namespace ledger::binary; - history_map::iterator i = history->prices.find(date); - if (i != history->prices.end()) { - (*i).second = price; - } else { - std::pair<history_map::iterator, bool> result - = history->prices.insert(history_pair(date, price)); - assert(result.second); - } -} + // Read in the commodity for this amount -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; - } + commodity_t::ident_t ident; + read_long(in, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = current_pool->null_commodity; + else { + commodity_ = current_pool->find(ident); + assert(commodity_); } - 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); + // Read in the quantity - std::pair<base_commodities_map::iterator, bool> result - = commodities.insert(base_commodities_pair(symbol, commodity)); - assert(result.second); + char byte; + in.read(&byte, sizeof(byte)); - return commodity; -} + if (byte < 3) { + quantity = new bigint_t; -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; + 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); - return false; -} + char negative; + in.read(&negative, sizeof(negative)); + if (negative) + mpz_neg(MPZ(quantity), MPZ(quantity)); -bool commodity_t::valid() const -{ - if (symbol().empty() && this != null_commodity) { - DEBUG_PRINT("ledger.validate", - "commodity_t: symbol().empty() && this != null_commodity"); - return false; - } + in.read((char *)&quantity->prec, sizeof(quantity->prec)); - if (annotated && ! base) { - DEBUG_PRINT("ledger.validate", "commodity_t: annotated && ! base"); - return false; + bigint_t::flags_t tflags; + in.read((char *)&tflags, sizeof(tflags)); + quantity->set_flags(tflags); } - - if (precision() > 16) { - DEBUG_PRINT("ledger.validate", "commodity_t: precision() > 16"); - return false; + else { + assert(false); } - - return true; } -commodity_t * commodity_t::create(const std::string& symbol) +void amount_t::read(const char *& data) { - std::auto_ptr<commodity_t> commodity(new commodity_t); + using namespace ledger::binary; - commodity->base = commodity_base_t::create(symbol); + // Read in the commodity for this amount - if (needs_quotes(symbol)) { - commodity->qualified_symbol = "\""; - commodity->qualified_symbol += symbol; - commodity->qualified_symbol += "\""; - } else { - commodity->qualified_symbol = symbol; + 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_); } - DEBUG_PRINT("amounts.commodities", - "Creating commodity " << commodity->qualified_symbol); - - std::pair<commodities_map::iterator, bool> 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; + // Read in the quantity - if (history) { - assert(history->prices.size() > 0); + char byte = *data++;; - if (! moment) { - history_map::reverse_iterator r = history->prices.rbegin(); - age = (*r).first; - price = (*r).second; + if (byte < 3) { + if (byte == 2) { + quantity = new((bigint_t *)bigints_next) bigint_t; + bigints_next += sizeof(bigint_t); } 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; - } - } + quantity = new bigint_t; } - } - - 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<const annotated_commodity_t&>(comm).price)) - return false; - - if (date && - (! comm.annotated || - date != static_cast<const annotated_commodity_t&>(comm).date)) - return false; - - if (! tag.empty() && - (! comm.annotated || - tag != static_cast<const annotated_commodity_t&>(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<annotated_commodity_t> 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<commodities_map::iterator, bool> 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; + unsigned short len = *((unsigned short *) data); + data += sizeof(unsigned short); + mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), + 0, 0, data); + data += len; - comm.write(name); - annotated_commodity_t::write_annotations(name, price, date, tag); + char negative = *data++; + if (negative) + mpz_neg(MPZ(quantity), MPZ(quantity)); - DEBUG_PRINT("amounts.commodities", "make_qualified_name for " - << comm.qualified_symbol << std::endl - << " price " << price << " " - << " date " << date << " " - << " tag " << tag); + quantity->prec = *((precision_t *) data); + data += sizeof(precision_t); + quantity->set_flags(*((flags_t *) data)); + data += sizeof(flags_t); - DEBUG_PRINT("amounts.commodities", "qualified_name is " << name.str()); + if (byte == 2) + quantity->add_flags(BIGINT_BULK_ALLOC); + } else { + uint_fast32_t index = *((uint_fast32_t *) data); + data += sizeof(uint_fast32_t); - return name.str(); + quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); + DEBUG("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; } } -commodity_t * -annotated_commodity_t::find_or_create(const commodity_t& comm, - const amount_t& price, - const datetime_t& date, - const std::string& tag) +void amount_t::write(std::ostream& out, bool optimized) const { - std::string name = make_qualified_name(comm, price, date, tag); + using namespace ledger::binary; - 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()); + // Write out the commodity for this amount - int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol()); - if (cmp != 0) - return cmp < 0; + if (! quantity) + throw_(amount_error, "Cannot serialize an uninitialized amount"); - if (! leftcomm.annotated) { - assert(rightcomm.annotated); - return true; - } - else if (! rightcomm.annotated) { - assert(leftcomm.annotated); - return false; - } - else { - annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm)); - annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm)); + if (commodity_) + write_long(out, commodity_->ident); + else + write_long<commodity_t::ident_t>(out, 0xffffffff); - if (! aleftcomm.price && arightcomm.price) - return true; - if (aleftcomm.price && ! arightcomm.price) - return false; + // Write out the quantity - if (aleftcomm.price && arightcomm.price) { - amount_t leftprice(aleftcomm.price); - leftprice.reduce(); - amount_t rightprice(arightcomm.price); - rightprice.reduce(); + char byte; - 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 (! 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)); - 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; + std::size_t size; + mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity)); + unsigned short len = size * sizeof(short); + out.write((char *)&len, sizeof(len)); + if (len) { + assert(len < 4096); + out.write(buf, len); } - if (aleftcomm.tag.empty() && ! arightcomm.tag.empty()) - return true; - if (! aleftcomm.tag.empty() && arightcomm.tag.empty()) - return false; + byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0; + out.write(&byte, sizeof(byte)); - if (! aleftcomm.tag.empty() && ! arightcomm.tag.empty()) - return aleftcomm.tag < arightcomm.tag; + out.write((char *)&quantity->prec, sizeof(quantity->prec)); + bigint_t::flags_t tflags = quantity->flags() & ~BIGINT_BULK_ALLOC; + assert(sizeof(tflags) == sizeof(bigint_t::flags_t)); + out.write((char *)&tflags, sizeof(tflags)); + } else { + assert(quantity->ref > 1); - // The two annotated commodities don't differ enough to matter. This - // should make this identical. - return true; + // Since this value has already been written, we simply write + // out a reference to which one it was. + byte = 3; + out.write(&byte, sizeof(byte)); + out.write((char *)&quantity->index, sizeof(quantity->index)); } } -} // namespace ledger - -#ifdef USE_BOOST_PYTHON - -#include <boost/python.hpp> -#include <Python.h> - -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 +bool amount_t::valid() const { - 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<void>(self, "__call__", commodity, moment, date, last, price); + if (quantity) { + if (quantity->ref == 0) { + DEBUG("ledger.validate", "amount_t: quantity->ref == 0"); + return false; + } } -}; - -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()); \ + else if (commodity_) { + DEBUG("ledger.validate", "amount_t: commodity_ != NULL"); + return false; } - -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<amount_t>()) - .def(init<std::string>()) - .def(init<char *>()) - .def(init<bool>()) - .def(init<long>()) - .def(init<unsigned long>()) - .def(init<double>()) - - .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<reference_existing_object>()), - 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<reference_existing_object>()), - make_setter(&commodity_t::smaller, - return_value_policy<reference_existing_object>())) - .add_property("larger", - make_getter(&commodity_t::larger, - return_value_policy<reference_existing_object>()), - make_setter(&commodity_t::larger, - return_value_policy<reference_existing_object>())) - - .def(self_ns::str(self)) - - .def("find", py_find_commodity, - return_value_policy<reference_existing_object>()) - .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<type>(&exc_translate_ ## type); - - EXC_TRANSLATE(amount_error); + return true; } -#endif // USE_BOOST_PYTHON +} // namespace ledger @@ -1,629 +1,715 @@ +/* + * 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 <map> -#include <stack> -#include <string> -#include <memory> -#include <cctype> -#include <iostream> -#include <sstream> -#include <cassert> -#include <exception> - -#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<amount_t, + ordered_field_operators<amount_t, double, + ordered_field_operators<amount_t, unsigned long, + ordered_field_operators<amount_t, long> > > > { - 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; + + static uint_fast32_t sizeof_bigint_t(); + +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; +public: // needed by binary.cc 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 <typename T> + 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 <typename T> + 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 <typename T> + 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 <typename T> - amount_t& operator+=(T value) { - return *this += amount_t(value); - } - template <typename T> - amount_t& operator-=(T value) { - return *this -= amount_t(value); - } - template <typename T> - amount_t& operator*=(T value) { - return *this *= amount_t(value); - } - template <typename T> - 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<datetime_t>) returns the historical value for an + * amount -- the default moment returns the most recently known + * price -- based on the price history of its commodity. For + * example, if the amount were 10 AAPL, and on Apr 10, 2000 each + * share of AAPL was worth $10, then call value() for that moment in + * time would yield the amount $100.00. + * + * Further, for the sake of efficiency and avoiding temporary + * objects, the following methods support "in-place" variants that + * act on the amount itself and return a reference to the result + * (`*this'): + * + * in_place_negate() + * in_place_reduce() + * in_place_unreduce() + */ + precision_t precision() const; + + amount_t negate() const { + amount_t temp(*this); + temp.in_place_negate(); return temp; } + amount_t& in_place_negate(); - template <typename T> - amount_t operator+(T value) const { - amount_t temp = *this; - temp += value; - return temp; - } - template <typename T> - amount_t operator-(T value) const { - amount_t temp = *this; - temp -= value; - return temp; + amount_t operator-() const { + return negate(); } - template <typename T> - 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 <typename T> - 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<amount_t> value(const optional<datetime_t>& moment = none) const; + + /** + * Truth tests. An amount may be truth test in several ways: + * + * sign() returns an integer less than, greater than, or equal to + * zero depending on whether the amount is negative, zero, or + * greater than zero. Note that this function tests the actual + * value of the amount -- using its internal precision -- and not + * the display value. To test its display value, use: + * `round().sign()'. + * + * is_nonzero(), or operator bool, returns true if an amount's + * display value is not zero. + * + * is_zero() returns true if an amount's display value is zero. + * Thus, $0.0001 is considered zero if the current display precision + * for dollars is two decimal places. + * + * is_realzero() returns true if an amount's actual value is zero. + * Thus, $0.0001 is never considered realzero. + * + * is_null() returns true if an amount has no value and no + * commodity. This only occurs if an uninitialized amount has never + * been assigned a value. + */ + int sign() const; - 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 <typename T> - 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 <typename T> \ - 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 <typename T> - 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, [datetime_t date, string tag]) + * sets the annotations for the current amount's commodity. Only + * the price argument is required, although it can be passed as + * `none' if no price is desired. + * + * commodity_annotated() returns true if an amount's commodity has + * any annotation details associated with it. + * + * annotation_details() returns all of the details of an annotated + * commodity's annotations. The structure returns will evaluate as + * boolean false if there are no details. + * + * strip_annotations([keep_price, keep_date, keep_tag]) returns an + * amount whose commodity's annotations have been stripped. The + * three `keep_' arguments determine which annotation detailed are + * kept, meaning that the default is to follow whatever + * amount_t::keep_price, amount_t::keep_date and amount_t::keep_tag + * have been set to (which all default to false). + */ + void annotate_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<char> 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<const datetime_t, amount_t> history_map; -typedef std::pair<const datetime_t, amount_t> history_pair; - -class commodity_base_t; - -typedef std::map<const std::string, commodity_base_t *> base_commodities_map; -typedef std::pair<const std::string, commodity_base_t *> 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<const std::string, commodity_t *> commodities_map; -typedef std::pair<const std::string, commodity_t *> 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 @@ -1,529 +1,271 @@ -#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 <deque> -#include <algorithm> +#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<const amount_t *> 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> +balance_t::value(const optional<datetime_t>& 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<balance_t> temp; -} // namespace ledger + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (optional<amount_t> val = i->second.value(moment)) { + if (! temp) + temp = balance_t(); + *temp += *val; + } -#ifdef USE_BOOST_PYTHON + return temp; +} -#include <boost/python.hpp> +optional<amount_t> +balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const +{ + // jww (2007-05-20): Needs work + if (! commodity) { + if (amounts.size() == 1) { + amounts_map::const_iterator i = amounts.begin(); + return i->second; + } + else if (amounts.size() > 1) { + // Try stripping annotations before giving an error. + balance_t temp(strip_annotations()); + if (temp.amounts.size() == 1) + return temp.commodity_amount(commodity); -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<const amount_t *> 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<balance_t>()) - .def(init<amount_t>()) - .def(init<long>()) - .def(init<unsigned long>()) - .def(init<double>()) - - .def(self += self) - .def(self += other<amount_t>()) - .def(self += long()) - .def(self + self) - .def(self + other<amount_t>()) - .def(self + long()) - .def(self -= self) - .def(self -= other<amount_t>()) - .def(self -= long()) - .def(self - self) - .def(self - other<amount_t>()) - .def(self - long()) - .def(self *= self) - .def(self *= other<amount_t>()) - .def(self *= long()) - .def(self * self) - .def(self * other<amount_t>()) - .def(self * long()) - .def(self /= self) - .def(self /= other<amount_t>()) - .def(self /= long()) - .def(self / self) - .def(self / other<amount_t>()) - .def(self / long()) - .def(- self) - - .def(self < self) - .def(self < other<amount_t>()) - .def(self < long()) - .def(self <= self) - .def(self <= other<amount_t>()) - .def(self <= long()) - .def(self > self) - .def(self > other<amount_t>()) - .def(self > long()) - .def(self >= self) - .def(self >= other<amount_t>()) - .def(self >= long()) - .def(self == self) - .def(self == other<amount_t>()) - .def(self == long()) - .def(self != self) - .def(self != other<amount_t>()) - .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<balance_pair_t>()) - .def(init<balance_t>()) - .def(init<amount_t>()) - .def(init<long>()) - .def(init<unsigned long>()) - .def(init<double>()) - - .def(self += self) - .def(self += other<balance_t>()) - .def(self += other<amount_t>()) - .def(self += long()) - .def(self + self) - .def(self + other<balance_t>()) - .def(self + other<amount_t>()) - .def(self + long()) - .def(self -= self) - .def(self -= other<balance_t>()) - .def(self -= other<amount_t>()) - .def(self -= long()) - .def(self - self) - .def(self - other<balance_t>()) - .def(self - other<amount_t>()) - .def(self - long()) - .def(self *= self) - .def(self *= other<balance_t>()) - .def(self *= other<amount_t>()) - .def(self *= long()) - .def(self * self) - .def(self * other<balance_t>()) - .def(self * other<amount_t>()) - .def(self * long()) - .def(self /= self) - .def(self /= other<balance_t>()) - .def(self /= other<amount_t>()) - .def(self /= long()) - .def(self / self) - .def(self / other<balance_t>()) - .def(self / other<amount_t>()) - .def(self / long()) - .def(- self) - - .def(self < self) - .def(self < other<balance_t>()) - .def(self < other<amount_t>()) - .def(self < long()) - .def(self <= self) - .def(self <= other<balance_t>()) - .def(self <= other<amount_t>()) - .def(self <= long()) - .def(self > self) - .def(self > other<balance_t>()) - .def(self > other<amount_t>()) - .def(self > long()) - .def(self >= self) - .def(self >= other<balance_t>()) - .def(self >= other<amount_t>()) - .def(self >= long()) - .def(self == self) - .def(self == other<balance_t>()) - .def(self == other<amount_t>()) - .def(self == long()) - .def(self != self) - .def(self != other<balance_t>()) - .def(self != other<amount_t>()) - .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<reference_existing_object>())) - ; + if (first) { + out.width(first_width); + out.fill(' '); + out << std::right << "0"; + } } -#endif // USE_BOOST_PYTHON +} // namespace ledger @@ -1,958 +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 <map> -#include <iostream> - namespace ledger { -typedef std::map<const commodity_t *, amount_t> amounts_map; -typedef std::pair<const commodity_t *, amount_t> 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<balance_t, + equality_comparable<balance_t, amount_t, + equality_comparable<balance_t, double, + equality_comparable<balance_t, unsigned long, + equality_comparable<balance_t, long, + additive<balance_t, + additive<balance_t, amount_t, + additive<balance_t, double, + additive<balance_t, unsigned long, + additive<balance_t, long, + multiplicative<balance_t, amount_t, + multiplicative<balance_t, double, + multiplicative<balance_t, unsigned long, + multiplicative<balance_t, long> > > > > > > > > > > > > > { - public: - amounts_map amounts; +public: + typedef std::map<const commodity_t *, amount_t> 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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; + 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 <typename T> - 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<datetime_t>) returns the total historical value for + * a balance -- the default moment returns a value based on the most + * recently known price -- based on the price history of its + * component commodities. See amount_t::value for an example. + * + * Further, for the sake of efficiency and avoiding temporary + * objects, the following methods support "in-place" variants act on + * the balance itself and return a reference to the result + * (`*this'): + * + * in_place_negate() + * in_place_reduce() + * in_place_unreduce() + */ + balance_t negate() const { + balance_t temp(*this); + temp.in_place_negate(); + return temp; } - 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 <typename T> - 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 <typename T> - 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 <typename T> - bool operator==(T val) const { + temp += i->second.unreduce(); + return *this = temp; + } + + optional<balance_t> value(const optional<datetime_t>& moment = none) const; + + /** + * Truth tests. An balance may be truth test in two ways: + * + * is_nonzero(), or operator bool, returns true if a balance's + * display value is not zero. + * + * is_zero() returns true if an balance's display value is zero. + * Thus, a balance containing $0.0001 is considered zero if the + * current display precision for dollars is two decimal places. + * + * is_realzero() returns true if an balance's actual value is zero. + * Thus, a balance containing $0.0001 is never considered realzero. + * + * is_empty() returns true if a balance has no amounts within it. + * This can occur after a balance has been default initialized, or + * if the exact amount it contains is subsequently subtracted from + * it. + */ + operator bool() const { 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 <typename T> - 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<commodity_t>) returns an (optional) + * amount for the given commodity within the balance; if no + * commodity is specified, it returns the (optional) uncommoditized + * component of the balance. If no matching element can be found, + * boost::none is returned. + */ + std::size_t commodity_count() const { + return amounts.size(); + } + + optional<amount_t> + commodity_amount(const optional<const commodity_t&>& commodity = none) const; + + /** + * Annotated commodity methods. The amounts contained by a balance + * may use annotated commodities. The `strip_annotations' method + * will return a balance all of whose component amount have had + * their commodity annotations likewise stripped. See + * amount_t::strip_annotations for more details. + */ balance_t strip_annotations(const bool keep_price = amount_t::keep_price, const bool keep_date = amount_t::keep_date, const bool keep_tag = amount_t::keep_tag) const; - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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; - } - - friend inline bool operator<(const balance_t& bal, const balance_pair_t& bal_pair) { - return bal_pair >= bal; - } - friend inline bool operator<=(const balance_t& bal, const balance_pair_t& bal_pair) { - return bal_pair > bal; - } - friend inline bool operator>(const balance_t& bal, const balance_pair_t& bal_pair) { - return bal_pair <= bal; - } - friend inline bool operator>=(const balance_t& bal, const balance_pair_t& bal_pair) { - return bal_pair < bal; - } - friend inline bool operator==(const balance_t& bal, const balance_pair_t& bal_pair) { - return bal_pair == bal; - } -}; - -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<balance_pair_t, + equality_comparable<balance_pair_t, balance_t, + equality_comparable<balance_pair_t, amount_t, + equality_comparable<balance_pair_t, double, + equality_comparable<balance_pair_t, unsigned long, + equality_comparable<balance_pair_t, long, + additive<balance_pair_t, + additive<balance_pair_t, balance_t, + additive<balance_pair_t, amount_t, + additive<balance_pair_t, double, + additive<balance_pair_t, unsigned long, + additive<balance_pair_t, long, + multiplicative<balance_pair_t, amount_t, + multiplicative<balance_pair_t, balance_t, + multiplicative<balance_pair_t, double, + multiplicative<balance_pair_t, unsigned long, + multiplicative<balance_pair_t, long> > > > > > > > > > > > > > > > > +{ + /** + * The `cost' member of a balance pair tracks the cost associated + * with each transaction amount that is added. This member is + * optional, and if not cost-bearing transactions are added, it will + * remain uninitialized. + */ + optional<balance_t> cost; + + friend class value_t; + friend class entry_base_t; + +public: + /** + * Constructors. balance_pair_t supports identical forms of construction + * to balance_t. See balance_t for more information. + */ + balance_pair_t() { + TRACE_CTOR(balance_pair_t, ""); + } + balance_pair_t(const balance_t& bal) : balance_t(bal) { + TRACE_CTOR(balance_pair_t, "const balance_t&"); + } + balance_pair_t(const balance_t& bal, + const balance_t& cost_bal) + : balance_t(bal), cost(cost_bal) { + TRACE_CTOR(balance_pair_t, "const balance_t&, const balance_t&"); + } + balance_pair_t(const amount_t& amt) : balance_t(amt) { + TRACE_CTOR(balance_pair_t, "const amount_t&"); + } + balance_pair_t(const amount_t& amt, const amount_t& cost_amt) + : balance_t(amt), cost(cost_amt) { + TRACE_CTOR(balance_pair_t, "const amount_t&, const amount_t&"); + } + 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<amount_t> cost) + */ + balance_pair_t& operator+=(const balance_pair_t& bal_pair) { + balance_t::operator+=(bal_pair); + if (bal_pair.cost) { + if (! cost) + cost = quantity(); + *cost += *bal_pair.cost; + } + return *this; + } + balance_pair_t& operator-=(const balance_pair_t& bal_pair) { + balance_t::operator+=(bal_pair); + if (bal_pair.cost) { + if (! cost) + cost = quantity(); + *cost += *bal_pair.cost; + } + return *this; + } + + virtual balance_pair_t& operator*=(const amount_t& amt) { + balance_t::operator*=(amt); + if (cost) + *cost *= amt; + return *this; + } + + virtual balance_pair_t& operator/=(const amount_t& amt) { + balance_t::operator/=(amt); + if (cost) + *cost /= amt; + return *this; + } + + balance_pair_t& add(const amount_t& amt, + const optional<amount_t>& a_cost = none) { + if (a_cost && ! cost) + cost = quantity(); + + *this += amt; + + if (cost) + *cost += a_cost ? *a_cost : amt; + + return *this; + } + + /** + * Unary arithmetic operators. There are only a few unary methods + * supported for balance pairs (otherwise, the operators inherited + * from balance_t are used): + * + * abs() returns the absolute value of both the quantity and the + * cost of a balance pair. + * + * in_place_negate() negates all the amounts in both the quantity + * and the cost. + * + * in_place_reduce() reduces all the amounts in both the quantity + * and the cost. + * + * in_place_unreduce() unreduces all the amounts in both the + * quantity and the cost. + * + * quantity() returns the balance part of a balance. It is the same + * as doing a downcast<balance_t>(balance_pair). + */ + balance_pair_t abs() const { + balance_t temp; + 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 <typename T> + bool operator==(const T& val) const { + return balance_t::operator==(val); + } + + /** + * Debugging methods. There is only one method specifically for + * balance pairs to help with debugging: + * + * valid() returns true if the balances within the balance pair are + * valid. + */ + virtual bool valid() { + if (! balance_t::valid()) + return false; + + if (cost && ! cost->valid()) + return false; + + return true; + } +}; + +} // namespace ledger + +#endif // _BALPAIR_H @@ -1,11 +1,36 @@ -#include "journal.h" -#include "valexpr.h" -#include "binary.h" - -#include <fstream> -#include <sys/stat.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. + */ -#define TIMELOG_SUPPORT 1 +#include "binary.h" +#include "journal.h" namespace ledger { @@ -16,136 +41,85 @@ static unsigned long format_version = 0x0002060b; static unsigned long format_version = 0x0002060a; #endif -static account_t ** accounts; -static account_t ** accounts_next; -static unsigned int account_index; +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::base_t ** base_commodities; +static commodity_t::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; +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; +extern char * bigints; +extern char * bigints_next; +extern unsigned int bigints_index; +extern unsigned int bigints_count; -template <typename T> -inline void read_binary_number_nocheck(std::istream& in, T& num) { - in.read((char *)&num, sizeof(num)); -} - -template <typename T> -inline T read_binary_number_nocheck(std::istream& in) { - T num; - read_binary_number_nocheck(in, num); - return num; -} +bool binary_parser_t::test(std::istream& in) const +{ + if (binary::read_number_nocheck<unsigned long>(in) == binary_magic_number && + binary::read_number_nocheck<unsigned long>(in) == format_version) + return true; -template <typename T> -inline void read_binary_number_nocheck(char *& data, T& num) { - num = *((T *) data); - data += sizeof(T); + in.clear(); + in.seekg(0, std::ios::beg); + return false; } -template <typename T> -inline T read_binary_number_nocheck(char *& data) { - T num; - read_binary_number_nocheck(data, num); - return num; +namespace binary { + unsigned int read_journal(std::istream& in, + const path& file, + journal_t * journal, + account_t * master); } -#if DEBUG_LEVEL >= ALPHA -static void assert_failed() { - assert(0); +unsigned int binary_parser_t::parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master, + const path * original_file) +{ + return binary::read_journal(in, original_file ? *original_file : "", + journal, master); } -#define read_binary_guard(in, id) \ - if (read_binary_number_nocheck<unsigned short>(in) != id) \ - assert_failed(); -#else -#define read_binary_guard(in, id) -#endif -template <typename T> -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); -} +namespace binary { -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<char *>(&val), sizeof(val)); num = val == 1; - read_binary_guard(in, 0x2006); + read_guard(in, 0x2006); } -template <typename T> -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 <typename T> -inline T read_binary_number(std::istream& in) { - T num; - read_binary_number(in, num); - return num; -} - -inline bool read_binary_bool(std::istream& in) { - bool num; - read_binary_bool(in, num); - return num; -} - -template <typename T> -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<const unsigned char *>(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,207 +130,161 @@ 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 <typename T> -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); -} - -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); + read_guard(in, 0x3002); } -template <typename T> -inline void read_binary_long(char *& data, T& num) { - read_binary_guard(data, 0x2001); +void read_string(const char *& data, string& str) +{ + read_guard(data, 0x3001); 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; + read_number_nocheck(data, len); + if (len == 0xff) { + unsigned short slen; + read_number_nocheck(data, slen); + str = string(data, slen); + data += slen; } - if (len > 2) { - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp) << 16; + else if (len) { + str = string(data, len); + data += len; } - if (len > 1) { - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp) << 8; + else { + str = ""; } - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp); - - read_binary_guard(data, 0x2002); -} - -template <typename T> -inline T read_binary_number(char *& data) { - T num; - read_binary_number(data, num); - return num; + read_guard(data, 0x3002); } -inline bool read_binary_bool(char *& data) { - bool num; - read_binary_bool(data, num); - return num; -} - -template <typename T> -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); + new(str) string(data, slen); data += slen; } else if (len) { - str = std::string(data, len); + new(str) string(data, len); data += len; } else { - str = ""; + new(str) string(""); } - read_binary_guard(data, 0x3002); + read_guard(data, 0x3002); } -inline std::string read_binary_string(char *& data) + +void write_bool(std::ostream& out, bool num) { - std::string temp; - read_binary_string(data, temp); - return temp; + write_guard(out, 0x2005); + unsigned char val = num ? 1 : 0; + out.write(reinterpret_cast<char *>(&val), sizeof(val)); + write_guard(out, 0x2006); } -inline void read_binary_string(char *& data, std::string * str) +void write_string(std::ostream& out, const string& str) { - read_binary_guard(data, 0x3001); + write_guard(out, 0x3001); - unsigned char len; - read_binary_number_nocheck(data, len); - if (len == 0xff) { - unsigned short slen; - read_binary_number_nocheck(data, slen); - new(str) std::string(data, slen); - data += slen; - } - else if (len) { - new(str) std::string(data, len); - data += len; - } - else { - new(str) std::string(""); + unsigned long len = str.length(); + if (len > 255) { + assert(len < 65536); + write_number_nocheck<unsigned char>(out, 0xff); + write_number_nocheck<unsigned short>(out, len); + } else { + write_number_nocheck<unsigned char>(out, len); } - read_binary_guard(data, 0x3002); + if (len) + out.write(str.c_str(), len); + + write_guard(out, 0x3002); } -inline void read_binary_amount(char *& data, amount_t& amt) +inline void read_amount(const char *& data, amount_t& amt) { commodity_t::ident_t ident; - read_binary_long(data, ident); + read_long(data, ident); if (ident == 0xffffffff) amt.commodity_ = NULL; else if (ident == 0) - amt.commodity_ = commodity_t::null_commodity; + amt.commodity_ = amount_t::current_pool->null_commodity; else amt.commodity_ = commodities[ident - 1]; - amt.read_quantity(data); + amt.read(data); } -inline void read_binary_value(char *& data, value_t& val) +inline void read_value(const char *& data, value_t& val) { - val.type = static_cast<value_t::type_t>(read_binary_long<int>(data)); - - switch (val.type) { + switch (static_cast<value_t::type_t>(read_long<int>(data))) { case value_t::BOOLEAN: - read_binary_bool(data, *((bool *) val.data)); + val.set_boolean(read_bool(data)); break; case value_t::INTEGER: - read_binary_long(data, *((long *) val.data)); + val.set_long(read_number<unsigned long>(data)); break; case value_t::DATETIME: - read_binary_number(data, *((datetime_t *) val.data)); + // jww (2008-04-22): I need to record and read a datetime_t directly + //val.set_datetime(read_long<unsigned long>(data)); break; - case value_t::AMOUNT: - read_binary_amount(data, *((amount_t *) val.data)); + case value_t::AMOUNT: { + amount_t temp; + read_amount(data, temp); + val.set_amount(temp); break; + } - case value_t::BALANCE: - case value_t::BALANCE_PAIR: - assert(0); + //case value_t::BALANCE: + //case value_t::BALANCE_PAIR: + default: + assert(false); break; } } -inline void read_binary_mask(char *& data, mask_t *& mask) +inline void read_mask(const char *& data, mask_t *& mask) { bool exclude; - read_binary_number(data, exclude); - std::string pattern; - read_binary_string(data, pattern); + read_number(data, exclude); + string pattern; + read_string(data, pattern); mask = new mask_t(pattern); mask->exclude = exclude; } -inline void read_binary_value_expr(char *& data, value_expr_t *& expr) +inline void read_value_expr(const char *& data, value_expr_t *& expr) { - if (! read_binary_bool(data)) { + if (! read_bool(data)) { expr = NULL; return; } value_expr_t::kind_t kind; - read_binary_number(data, kind); + read_number(data, kind); expr = new value_expr_t(kind); if (kind > value_expr_t::TERMINALS) { - read_binary_value_expr(data, expr->left); + read_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); + read_long(data, expr->arg_index); break; case value_expr_t::CONSTANT: expr->value = new value_t; - read_binary_value(data, *expr->value); + read_value(data, *expr->value); break; case value_expr_t::F_CODE_MASK: @@ -365,13 +293,13 @@ inline void read_binary_value_expr(char *& data, value_expr_t *& expr) 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); + if (read_bool(data)) + read_mask(data, expr->mask); break; default: if (kind > value_expr_t::TERMINALS) { - read_binary_value_expr(data, expr->right); + read_value_expr(data, expr->right); if (expr->right) expr->right->acquire(); } break; @@ -379,45 +307,45 @@ inline void read_binary_value_expr(char *& data, value_expr_t *& expr) } -inline void read_binary_transaction(char *& data, transaction_t * xact) +inline void read_transaction(const char *& data, transaction_t * xact) { - read_binary_number(data, xact->_date); - read_binary_number(data, xact->_date_eff); - xact->account = accounts[read_binary_long<account_t::ident_t>(data) - 1]; + read_number(data, xact->_date); + read_number(data, xact->_date_eff); + xact->account = accounts[read_long<account_t::ident_t>(data) - 1]; - unsigned char flag = read_binary_number<unsigned char>(data); + unsigned char flag = read_number<unsigned char>(data); if (flag == 0) { - read_binary_amount(data, xact->amount); + read_amount(data, xact->amount); } else if (flag == 1) { - read_binary_amount(data, xact->amount); - read_binary_string(data, xact->amount_expr.expr); + read_amount(data, xact->amount); + read_string(data, xact->amount_expr.expr); } else { value_expr_t * ptr = NULL; - read_binary_value_expr(data, ptr); + read_value_expr(data, ptr); assert(ptr); xact->amount_expr.reset(ptr); - read_binary_string(data, xact->amount_expr.expr); + read_string(data, xact->amount_expr.expr); } - if (read_binary_bool(data)) { + if (read_bool(data)) { xact->cost = new amount_t; - read_binary_amount(data, *xact->cost); - read_binary_string(data, xact->cost_expr); + read_amount(data, *xact->cost); + read_string(data, xact->cost_expr); } else { xact->cost = NULL; } - read_binary_number(data, xact->state); - read_binary_number(data, xact->flags); + read_number(data, xact->state); + read_number(data, xact->flags); xact->flags |= TRANSACTION_BULK_ALLOC; - read_binary_string(data, &xact->note); + read_string(data, &xact->note); - xact->beg_pos = read_binary_long<unsigned long>(data); - read_binary_long(data, xact->beg_line); - xact->end_pos = read_binary_long<unsigned long>(data); - read_binary_long(data, xact->end_line); + xact->beg_pos = read_long<unsigned long>(data); + read_long(data, xact->beg_line); + xact->end_pos = read_long<unsigned long>(data); + read_long(data, xact->end_line); xact->data = NULL; @@ -425,155 +353,178 @@ inline void read_binary_transaction(char *& data, transaction_t * xact) 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) +inline void read_entry_base(const 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<unsigned long>(data); - read_binary_long(data, entry->beg_line); - entry->end_pos = read_binary_long<unsigned long>(data); - read_binary_long(data, entry->end_line); + read_long(data, entry->src_idx); + entry->beg_pos = read_long<unsigned long>(data); + read_long(data, entry->beg_line); + entry->end_pos = read_long<unsigned long>(data); + read_long(data, entry->end_line); - bool ignore_calculated = read_binary_bool(data); + bool ignore_calculated = read_bool(data); - for (unsigned long i = 0, count = read_binary_long<unsigned long>(data); + for (unsigned long i = 0, count = read_long<unsigned long>(data); i < count; i++) { new(xact_pool) transaction_t; - read_binary_transaction(data, xact_pool); + read_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) +inline void read_entry(const 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); + read_entry_base(data, entry, xact_pool, finalize); + read_number(data, entry->_date); + read_number(data, entry->_date_eff); + read_string(data, &entry->code); + read_string(data, &entry->payee); } -inline void read_binary_auto_entry(char *& data, auto_entry_t * entry, - transaction_t *& xact_pool) +inline void read_auto_entry(const char *& data, auto_entry_t * entry, + transaction_t *& xact_pool) { bool ignore; - read_binary_entry_base(data, entry, xact_pool, ignore); + read_entry_base(data, entry, xact_pool, ignore); value_expr_t * expr; - read_binary_value_expr(data, expr); + read_value_expr(data, expr); // the item_predicate constructor will acquire the reference entry->predicate = new item_predicate<transaction_t>(expr); } -inline void read_binary_period_entry(char *& data, period_entry_t * entry, - transaction_t *& xact_pool, bool& finalize) +inline void read_period_entry(const 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); + read_entry_base(data, entry, xact_pool, finalize); + read_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) +inline commodity_t::base_t * read_commodity_base(const char *& data) { - commodity_base_t * commodity = new commodity_base_t; + string str; + + read_string(data, str); + + commodity_t::base_t * commodity = new commodity_t::base_t(str); *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); + read_string(data, str); + if (! str.empty()) + commodity->name = str; + + read_string(data, str); + if (! str.empty()) + commodity->note = str; + + read_number(data, commodity->precision); + unsigned long flags; + read_number(data, flags); + commodity->set_flags(flags); return commodity; } -inline void read_binary_commodity_base_extra(char *& data, - commodity_t::ident_t ident) +inline void read_commodity_base_extra(const char *& data, + commodity_t::ident_t ident) { - commodity_base_t * commodity = base_commodities[ident]; + commodity_t::base_t * commodity = base_commodities[ident]; bool read_history = false; - for (unsigned long i = 0, count = read_binary_long<unsigned long>(data); + for (unsigned long i = 0, count = read_long<unsigned long>(data); i < count; i++) { datetime_t when; - read_binary_number(data, when); + read_number(data, when); amount_t amt; - read_binary_amount(data, amt); + read_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)); + commodity->history = commodity_t::history_t(); + commodity->history->prices.insert(commodity_t::base_t::history_pair(when, amt)); read_history = true; } if (read_history) - read_binary_number(data, commodity->history->last_lookup); + read_number(data, commodity->history->last_lookup); - if (read_binary_bool(data)) { + if (read_bool(data)) { amount_t amt; - read_binary_amount(data, amt); - commodity->smaller = new amount_t(amt); + read_amount(data, amt); + commodity->smaller = amount_t(amt); } - if (read_binary_bool(data)) { + if (read_bool(data)) { amount_t amt; - read_binary_amount(data, amt); - commodity->larger = new amount_t(amt); + read_amount(data, amt); + commodity->larger = amount_t(amt); } } -inline commodity_t * read_binary_commodity(char *& data) +inline commodity_t * read_commodity(const char *& data) { - commodity_t * commodity = new commodity_t; - *commodities_next++ = commodity; + commodity_t::base_t * base = + base_commodities[read_long<commodity_t::ident_t>(data) - 1]; - commodity->base = - base_commodities[read_binary_long<commodity_base_t::ident_t>(data) - 1]; + commodity_t * commodity = + new commodity_t(amount_t::current_pool, + shared_ptr<commodity_t::base_t>(base)); - read_binary_string(data, commodity->qualified_symbol); + *commodities_next++ = commodity; + + string str; + read_string(data, str); + if (! str.empty()) + commodity->qualified_symbol = str; commodity->annotated = false; return commodity; } -inline commodity_t * read_binary_commodity_annotated(char *& data) +inline commodity_t * read_commodity_annotated(const char *& data) { - annotated_commodity_t * commodity = new annotated_commodity_t; - *commodities_next++ = commodity; - - commodity->base = - base_commodities[read_binary_long<commodity_base_t::ident_t>(data) - 1]; + commodity_t * commodity = + commodities[read_long<commodity_t::ident_t>(data) - 1]; - read_binary_string(data, commodity->qualified_symbol); - commodity->annotated = true; + annotation_t details; - commodity->ptr = - commodities[read_binary_long<commodity_t::ident_t>(data) - 1]; + string str; + read_string(data, str); - // This read-and-then-assign causes a new amount to be allocated - // which does not live within the bulk allocation pool, since that - // pool will be deleted *before* the commodities are destroyed. + // 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_amount(data, amt); + details.price = amt; - read_binary_number(data, commodity->date); - read_binary_string(data, commodity->tag); +#if 0 + // jww (2008-04-22): These are optional members! + read_number(data, details.date); + read_string(data, details.tag); +#endif - return commodity; + annotated_commodity_t * ann_comm = + new annotated_commodity_t(commodity, details); + *commodities_next++ = ann_comm; + + if (! str.empty()) + ann_comm->qualified_symbol = str; + + return ann_comm; } inline -account_t * read_binary_account(char *& data, journal_t * journal, - account_t * master = NULL) +account_t * read_account(const char *& data, journal_t * journal, + account_t * master = NULL) { account_t * acct = new account_t(NULL); *accounts_next++ = acct; @@ -581,15 +532,15 @@ account_t * read_binary_account(char *& data, journal_t * journal, acct->journal = journal; account_t::ident_t id; - read_binary_long(data, id); // parent id + read_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); + read_string(data, acct->name); + read_string(data, acct->note); + read_number(data, acct->depth); // If all of the subaccounts will be added to a different master // account, throw away what we've learned about the recorded @@ -601,10 +552,10 @@ account_t * read_binary_account(char *& data, journal_t * journal, } for (account_t::ident_t i = 0, - count = read_binary_long<account_t::ident_t>(data); + count = read_long<account_t::ident_t>(data); i < count; i++) { - account_t * child = read_binary_account(data, journal); + account_t * child = read_account(data, journal); child->parent = acct; assert(acct != child); acct->add_account(child); @@ -613,10 +564,10 @@ account_t * read_binary_account(char *& data, journal_t * journal, return acct; } -unsigned int read_binary_journal(std::istream& in, - const std::string& file, - journal_t * journal, - account_t * master) +unsigned int read_journal(std::istream& in, + const path& file, + journal_t * journal, + account_t * master) { account_index = base_commodity_index = @@ -627,61 +578,63 @@ unsigned int read_binary_journal(std::istream& in, if (! file.empty()) { for (unsigned short i = 0, - count = read_binary_number<unsigned short>(in); + count = read_number<unsigned short>(in); i < count; i++) { - std::string path = read_binary_string(in); + path pathname = read_string(in); std::time_t old_mtime; - read_binary_number(in, old_mtime); + read_number(in, old_mtime); struct stat info; - stat(path.c_str(), &info); + // jww (2008-04-22): can this be done differently now? + stat(pathname.string().c_str(), &info); if (std::difftime(info.st_mtime, old_mtime) > 0) return 0; - journal->sources.push_back(path); + journal->sources.push_back(pathname); } // Make sure that the cache uses the same price database, // otherwise it means that LEDGER_PRICE_DB has been changed, and // we should ignore this cache file. - if (read_binary_string(in) != journal->price_db) + if (read_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<unsigned long>(in); + unsigned long data_size = read_number<unsigned long>(in); char * data_pool = new char[data_size]; - char * data = data_pool; - in.read(data, data_size); + in.read(data_pool, data_size); // Read in the accounts - account_t::ident_t a_count = read_binary_long<account_t::ident_t>(data); + const char * data = data_pool; + + account_t::ident_t a_count = read_long<account_t::ident_t>(data); accounts = accounts_next = new account_t *[a_count]; assert(journal->master); delete journal->master; - journal->master = read_binary_account(data, journal, master); + journal->master = read_account(data, journal, master); - if (read_binary_bool(data)) - journal->basket = accounts[read_binary_long<account_t::ident_t>(data) - 1]; + if (read_bool(data)) + journal->basket = accounts[read_long<account_t::ident_t>(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<unsigned long>(data); - unsigned long auto_count = read_binary_long<unsigned long>(data); - unsigned long period_count = read_binary_long<unsigned long>(data); - unsigned long xact_count = read_binary_number<unsigned long>(data); - unsigned long bigint_count = read_binary_number<unsigned long>(data); + unsigned long count = read_long<unsigned long>(data); + unsigned long auto_count = read_long<unsigned long>(data); + unsigned long period_count = read_long<unsigned long>(data); + unsigned long xact_count = read_number<unsigned long>(data); + unsigned long bigint_count = read_number<unsigned long>(data); std::size_t pool_size = (sizeof(entry_t) * count + sizeof(transaction_t) * xact_count + - sizeof_bigint_t() * bigint_count); + amount_t::sizeof_bigint_t() * bigint_count); char * item_pool = new char[pool_size]; @@ -697,25 +650,26 @@ unsigned int read_binary_journal(std::istream& in, // Read in the base commodities and then derived commodities - commodity_base_t::ident_t bc_count = - read_binary_long<commodity_base_t::ident_t>(data); - base_commodities = base_commodities_next = new commodity_base_t *[bc_count]; + commodity_t::ident_t bc_count = read_long<commodity_t::ident_t>(data); + base_commodities = base_commodities_next = new commodity_t::base_t *[bc_count]; + + for (commodity_t::ident_t i = 0; i < bc_count; i++) { +#if 0 + commodity_t::base_t * base = read_commodity_base(data); - for (commodity_base_t::ident_t i = 0; i < bc_count; i++) { - commodity_base_t * commodity = read_binary_commodity_base(data); + // jww (2008-04-22): How does the pool get created here? + amount_t::current_pool->commodities.push_back(commodity); - std::pair<base_commodities_map::iterator, bool> result = - commodity_base_t::commodities.insert - (base_commodities_pair(commodity->symbol, commodity)); + // jww (2008-04-22): What about this logic here? if (! result.second) { base_commodities_map::iterator c = - commodity_base_t::commodities.find(commodity->symbol); + commodity_t::base_t::commodities.find(commodity->symbol); // It's possible the user might have used a commodity in a value // expression passed to an option, we'll just override the // flags, but keep the commodity pointer intact. - if (c == commodity_base_t::commodities.end()) - throw new error(std::string("Failed to read base commodity from cache: ") + + if (c == commodity_t::base_t::commodities.end()) + throw new error(string("Failed to read base commodity from cache: ") + commodity->symbol); (*c).second->name = commodity->name; @@ -732,54 +686,57 @@ unsigned int read_binary_journal(std::istream& in, *(base_commodities_next - 1) = (*c).second; delete commodity; } +#endif } - commodity_t::ident_t c_count = read_binary_long<commodity_t::ident_t>(data); + commodity_t::ident_t c_count = read_long<commodity_t::ident_t>(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; + string mapping_key; - if (! read_binary_bool(data)) { - commodity = read_binary_commodity(data); + if (! read_bool(data)) { + commodity = read_commodity(data); mapping_key = commodity->base->symbol; } else { - read_binary_string(data, mapping_key); - commodity = read_binary_commodity_annotated(data); + read_string(data, mapping_key); + commodity = read_commodity_annotated(data); } - std::pair<commodities_map::iterator, bool> result = - commodity_t::commodities.insert(commodities_pair - (mapping_key, commodity)); + // jww (2008-04-22): What do I do with mapping_key here? + amount_t::current_pool->commodities.push_back(commodity); +#if 0 + // jww (2008-04-22): What about the error case? 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: ") + + throw new error(string("Failed to read commodity from cache: ") + commodity->symbol()); *(commodities_next - 1) = (*c).second; delete commodity; } +#endif } - for (commodity_base_t::ident_t i = 0; i < bc_count; i++) - read_binary_commodity_base_extra(data, i); + for (commodity_t::ident_t i = 0; i < bc_count; i++) + read_commodity_base_extra(data, i); commodity_t::ident_t ident; - read_binary_long(data, ident); + read_long(data, ident); if (ident == 0xffffffff || ident == 0) - commodity_t::default_commodity = NULL; + amount_t::current_pool->default_commodity = NULL; else - commodity_t::default_commodity = commodities[ident - 1]; + amount_t::current_pool->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); + read_entry(data, entry_pool, xact_pool, finalize); entry_pool->journal = journal; if (finalize && ! entry_pool->finalize()) continue; @@ -788,7 +745,7 @@ unsigned int read_binary_journal(std::istream& in, 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); + read_auto_entry(data, auto_entry, xact_pool); auto_entry->journal = journal; journal->auto_entries.push_back(auto_entry); } @@ -796,7 +753,7 @@ unsigned int read_binary_journal(std::istream& in, 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); + read_period_entry(data, period_entry, xact_pool, finalize); period_entry->journal = journal; if (finalize && ! period_entry->finalize()) continue; @@ -809,169 +766,71 @@ unsigned int read_binary_journal(std::istream& in, delete[] commodities; delete[] data_pool; - VALIDATE(journal->valid()); + VERIFY(journal->valid()); return count; } -bool binary_parser_t::test(std::istream& in) const -{ - if (read_binary_number_nocheck<unsigned long>(in) == binary_magic_number && - read_binary_number_nocheck<unsigned long>(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 <typename T> -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<unsigned short>(out, id) -#else -#define write_binary_guard(in, id) -#endif - -template <typename T> -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); - unsigned char val = num ? 1 : 0; - out.write((char *)&val, sizeof(val)); - write_binary_guard(out, 0x2006); -} - -template <typename T> -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<unsigned char>(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); -} - -inline void write_binary_string(std::ostream& out, const std::string& str) -{ - write_binary_guard(out, 0x3001); - - unsigned long len = str.length(); - if (len > 255) { - assert(len < 65536); - write_binary_number_nocheck<unsigned char>(out, 0xff); - write_binary_number_nocheck<unsigned short>(out, len); - } else { - write_binary_number_nocheck<unsigned char>(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) +void write_amount(std::ostream& out, const amount_t& amt) { if (amt.commodity_) - write_binary_long(out, amt.commodity_->ident); + write_long(out, amt.commodity_->ident); else - write_binary_long<commodity_t::ident_t>(out, 0xffffffff); + write_long<commodity_t::ident_t>(out, 0xffffffff); - amt.write_quantity(out); + amt.write(out); } -void write_binary_value(std::ostream& out, const value_t& val) +void write_value(std::ostream& out, const value_t& val) { - write_binary_long(out, (int)val.type); + write_long(out, (int)val.type()); - switch (val.type) { + switch (val.type()) { case value_t::BOOLEAN: - write_binary_bool(out, *((bool *) val.data)); + write_bool(out, const_cast<value_t&>(val).as_boolean_lval()); break; case value_t::INTEGER: - write_binary_long(out, *((long *) val.data)); + write_long(out, const_cast<value_t&>(val).as_long_lval()); break; case value_t::DATETIME: - write_binary_number(out, *((datetime_t *) val.data)); + write_number(out, const_cast<value_t&>(val).as_datetime_lval()); break; case value_t::AMOUNT: - write_binary_amount(out, *((amount_t *) val.data)); + write_amount(out, const_cast<value_t&>(val).as_amount_lval()); break; - case value_t::BALANCE: - case value_t::BALANCE_PAIR: + //case value_t::BALANCE: + //case value_t::BALANCE_PAIR: + default: throw new error("Cannot write a balance to the binary cache"); } } -void write_binary_mask(std::ostream& out, mask_t * mask) +void write_mask(std::ostream& out, mask_t * mask) { - write_binary_number(out, mask->exclude); - write_binary_string(out, mask->pattern); + write_number(out, mask->exclude); + write_string(out, mask->expr.str()); } -void write_binary_value_expr(std::ostream& out, const value_expr_t * expr) +void write_value_expr(std::ostream& out, const value_expr_t * expr) { if (! expr) { - write_binary_bool(out, false); + write_bool(out, false); return; } - write_binary_bool(out, true); - write_binary_number(out, expr->kind); + write_bool(out, true); + write_number(out, expr->kind); if (expr->kind > value_expr_t::TERMINALS) - write_binary_value_expr(out, expr->left); + write_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); + write_long(out, expr->arg_index); break; case value_expr_t::CONSTANT: - write_binary_value(out, *expr->value); + write_value(out, *expr->value); break; case value_expr_t::F_CODE_MASK: @@ -981,73 +840,73 @@ void write_binary_value_expr(std::ostream& out, const value_expr_t * expr) 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); + write_bool(out, true); + write_mask(out, expr->mask); } else { - write_binary_bool(out, false); + write_bool(out, false); } break; default: if (expr->kind > value_expr_t::TERMINALS) - write_binary_value_expr(out, expr->right); + write_value_expr(out, expr->right); break; } } -void write_binary_transaction(std::ostream& out, transaction_t * xact, +void write_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); + write_number(out, xact->_date); + write_number(out, xact->_date_eff); + write_long(out, xact->account->ident); if (ignore_calculated && xact->flags & TRANSACTION_CALCULATED) { - write_binary_number<unsigned char>(out, 0); - write_binary_amount(out, amount_t()); + write_number<unsigned char>(out, 0); + write_amount(out, amount_t()); } else if (xact->amount_expr) { - write_binary_number<unsigned char>(out, 2); - write_binary_value_expr(out, xact->amount_expr.get()); - write_binary_string(out, xact->amount_expr.expr); + write_number<unsigned char>(out, 2); + write_value_expr(out, xact->amount_expr.get()); + write_string(out, xact->amount_expr.expr); } else if (! xact->amount_expr.expr.empty()) { - write_binary_number<unsigned char>(out, 1); - write_binary_amount(out, xact->amount); - write_binary_string(out, xact->amount_expr.expr); + write_number<unsigned char>(out, 1); + write_amount(out, xact->amount); + write_string(out, xact->amount_expr.expr); } else { - write_binary_number<unsigned char>(out, 0); - write_binary_amount(out, xact->amount); + write_number<unsigned char>(out, 0); + write_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); + write_bool(out, true); + write_amount(out, *xact->cost); + write_string(out, xact->cost_expr); } else { - write_binary_bool(out, false); + write_bool(out, false); } - write_binary_number(out, xact->state); - write_binary_number(out, xact->flags); - write_binary_string(out, xact->note); + write_number(out, xact->state); + write_number(out, xact->flags); + write_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); + write_long(out, xact->beg_pos); + write_long(out, xact->beg_line); + write_long(out, xact->end_pos); + write_long(out, xact->end_line); } -void write_binary_entry_base(std::ostream& out, entry_base_t * entry) +void write_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); + write_long(out, entry->src_idx); + write_long(out, entry->beg_pos); + write_long(out, entry->beg_line); + write_long(out, entry->end_pos); + write_long(out, entry->end_line); bool ignore_calculated = false; for (transactions_list::const_iterator i = entry->transactions.begin(); @@ -1058,104 +917,116 @@ void write_binary_entry_base(std::ostream& out, entry_base_t * entry) break; } - write_binary_bool(out, ignore_calculated); + write_bool(out, ignore_calculated); - write_binary_long(out, entry->transactions.size()); + write_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); + write_transaction(out, *i, ignore_calculated); } -void write_binary_entry(std::ostream& out, entry_t * entry) +void write_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); + write_entry_base(out, entry); + write_number(out, entry->_date); + write_number(out, entry->_date_eff); + write_string(out, entry->code); + write_string(out, entry->payee); } -void write_binary_auto_entry(std::ostream& out, auto_entry_t * entry) +void write_auto_entry(std::ostream& out, auto_entry_t * entry) { - write_binary_entry_base(out, entry); - write_binary_value_expr(out, entry->predicate->predicate); + write_entry_base(out, entry); + write_value_expr(out, entry->predicate->predicate); } -void write_binary_period_entry(std::ostream& out, period_entry_t * entry) +void write_period_entry(std::ostream& out, period_entry_t * entry) { - write_binary_entry_base(out, entry); - write_binary_string(out, entry->period_string); + write_entry_base(out, entry); + write_string(out, entry->period_string); } -void write_binary_commodity_base(std::ostream& out, commodity_base_t * commodity) +void write_commodity_base(std::ostream& out, commodity_t::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); + // jww (2008-04-22): Not using this anymore? + //commodity->ident = ++base_commodity_index; + + write_string(out, commodity->symbol); + // jww (2008-04-22): What to do with optional members? + write_string(out, *commodity->name); + write_string(out, *commodity->note); + write_number(out, commodity->precision); + write_number(out, commodity->flags()); } -void write_binary_commodity_base_extra(std::ostream& out, - commodity_base_t * commodity) +void write_commodity_base_extra(std::ostream& out, + commodity_t::base_t * commodity) { +#if 0 + // jww (2008-04-22): What did bogus_time used to do? if (commodity->history && commodity->history->bogus_time) commodity->remove_price(commodity->history->bogus_time); +#endif if (! commodity->history) { - write_binary_long<unsigned long>(out, 0); + write_long<unsigned long>(out, 0); } else { - write_binary_long<unsigned long>(out, commodity->history->prices.size()); - for (history_map::const_iterator i = commodity->history->prices.begin(); + write_long<unsigned long>(out, commodity->history->prices.size()); + for (commodity_t::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_number(out, (*i).first); + write_amount(out, (*i).second); } - write_binary_number(out, commodity->history->last_lookup); + write_number(out, commodity->history->last_lookup); } if (commodity->smaller) { - write_binary_bool(out, true); - write_binary_amount(out, *commodity->smaller); + write_bool(out, true); + write_amount(out, *commodity->smaller); } else { - write_binary_bool(out, false); + write_bool(out, false); } if (commodity->larger) { - write_binary_bool(out, true); - write_binary_amount(out, *commodity->larger); + write_bool(out, true); + write_amount(out, *commodity->larger); } else { - write_binary_bool(out, false); + write_bool(out, false); } } -void write_binary_commodity(std::ostream& out, commodity_t * commodity) +void write_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); + // jww (2008-04-22): Is this used anymore? + //write_long(out, commodity->base->ident); + // jww (2008-04-22): Optional! + write_string(out, *commodity->qualified_symbol); } -void write_binary_commodity_annotated(std::ostream& out, +void write_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); + // jww (2008-04-22): No longer needed? + //write_long(out, commodity->base->ident); + // jww (2008-04-22): Optional! + write_string(out, *commodity->qualified_symbol); annotated_commodity_t * ann_comm = static_cast<annotated_commodity_t *>(commodity); - 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); + // jww (2008-04-22): No longer needed? + //write_long(out, ann_comm->base->ident); + // jww (2008-04-22): Make a write_annotation_details function; and optional! + write_amount(out, *ann_comm->details.price); + write_number(out, *ann_comm->details.date); + write_string(out, *ann_comm->details.tag); } static inline account_t::ident_t count_accounts(account_t * account) @@ -1170,105 +1041,106 @@ static inline account_t::ident_t count_accounts(account_t * account) return count; } -void write_binary_account(std::ostream& out, account_t * account) +void write_account(std::ostream& out, account_t * account) { account->ident = ++account_index; if (account->parent) - write_binary_long(out, account->parent->ident); + write_long(out, account->parent->ident); else - write_binary_long<account_t::ident_t>(out, 0xffffffff); + write_long<account_t::ident_t>(out, 0xffffffff); - write_binary_string(out, account->name); - write_binary_string(out, account->note); - write_binary_number(out, account->depth); + write_string(out, account->name); + write_string(out, account->note); + write_number(out, account->depth); - write_binary_long<account_t::ident_t>(out, account->accounts.size()); + write_long<account_t::ident_t>(out, account->accounts.size()); for (accounts_map::iterator i = account->accounts.begin(); i != account->accounts.end(); i++) - write_binary_account(out, (*i).second); + write_account(out, (*i).second); } -void write_binary_journal(std::ostream& out, journal_t * journal) +void write_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_number_nocheck(out, binary_magic_number); + write_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<unsigned short>(out, 0); + write_number<unsigned short>(out, 0); } else { - write_binary_number<unsigned short>(out, journal->sources.size()); - for (strings_list::const_iterator i = journal->sources.begin(); + write_number<unsigned short>(out, journal->sources.size()); + for (paths_list::const_iterator i = journal->sources.begin(); i != journal->sources.end(); i++) { - write_binary_string(out, *i); + write_string(out, (*i).string()); struct stat info; - stat((*i).c_str(), &info); - write_binary_number(out, std::time_t(info.st_mtime)); + stat((*i).string().c_str(), &info); + write_number(out, std::time_t(info.st_mtime)); } // Write out the price database that relates to this data file, so // that if it ever changes the cache can be invalidated. - write_binary_string(out, journal->price_db); + write_string(out, journal->price_db.string()); } ostream_pos_type data_val = out.tellp(); - write_binary_number<unsigned long>(out, 0); + write_number<unsigned long>(out, 0); // Write out the accounts - write_binary_long<account_t::ident_t>(out, count_accounts(journal->master)); - write_binary_account(out, journal->master); + write_long<account_t::ident_t>(out, count_accounts(journal->master)); + write_account(out, journal->master); if (journal->basket) { - write_binary_bool(out, true); - write_binary_long(out, journal->basket->ident); + write_bool(out, true); + write_long(out, journal->basket->ident); } else { - write_binary_bool(out, false); + write_bool(out, false); } // Write out the number of entries, transactions, and amounts - write_binary_long<unsigned long>(out, journal->entries.size()); - write_binary_long<unsigned long>(out, journal->auto_entries.size()); - write_binary_long<unsigned long>(out, journal->period_entries.size()); + write_long<unsigned long>(out, journal->entries.size()); + write_long<unsigned long>(out, journal->auto_entries.size()); + write_long<unsigned long>(out, journal->period_entries.size()); ostream_pos_type xacts_val = out.tellp(); - write_binary_number<unsigned long>(out, 0); + write_number<unsigned long>(out, 0); ostream_pos_type bigints_val = out.tellp(); - write_binary_number<unsigned long>(out, 0); + write_number<unsigned long>(out, 0); bigints_count = 0; // Write out the commodities + // jww (2008-04-22): This whole section needs to be reworked - write_binary_long<commodity_t::ident_t> - (out, commodity_base_t::commodities.size()); +#if 0 + write_long<commodity_t::ident_t>(out, amount_t::current_pool->commodities.size()); for (base_commodities_map::const_iterator i = - commodity_base_t::commodities.begin(); - i != commodity_base_t::commodities.end(); + commodity_t::base_t::commodities.begin(); + i != commodity_t::base_t::commodities.end(); i++) - write_binary_commodity_base(out, (*i).second); + write_commodity_base(out, (*i).second); - write_binary_long<commodity_t::ident_t> + write_long<commodity_t::ident_t> (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); + write_bool(out, false); + write_commodity(out, (*i).second); } } @@ -1276,9 +1148,9 @@ void write_binary_journal(std::ostream& out, journal_t * journal) 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_bool(out, true); + write_string(out, (*i).first); // the mapping key + write_commodity_annotated(out, (*i).second); } } @@ -1287,15 +1159,16 @@ void write_binary_journal(std::ostream& out, journal_t * journal) // 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(); + commodity_t::base_t::commodities.begin(); + i != commodity_t::base_t::commodities.end(); i++) - write_binary_commodity_base_extra(out, (*i).second); + write_commodity_base_extra(out, (*i).second); if (commodity_t::default_commodity) - write_binary_long(out, commodity_t::default_commodity->ident); + write_long(out, commodity_t::default_commodity->ident); else - write_binary_long<commodity_t::ident_t>(out, 0xffffffff); + write_long<commodity_t::ident_t>(out, 0xffffffff); +#endif // Write out the entries and transactions @@ -1304,21 +1177,21 @@ void write_binary_journal(std::ostream& out, journal_t * journal) for (entries_list::const_iterator i = journal->entries.begin(); i != journal->entries.end(); i++) { - write_binary_entry(out, *i); + write_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); + write_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); + write_period_entry(out, *i); xact_count += (*i)->transactions.size(); } @@ -1328,11 +1201,12 @@ void write_binary_journal(std::ostream& out, journal_t * journal) ((unsigned long) data_val) - sizeof(unsigned long)); out.seekp(data_val); - write_binary_number<unsigned long>(out, data_size); + write_number<unsigned long>(out, data_size); out.seekp(xacts_val); - write_binary_number<unsigned long>(out, xact_count); + write_number<unsigned long>(out, xact_count); out.seekp(bigints_val); - write_binary_number<unsigned long>(out, bigints_count); + write_number<unsigned long>(out, bigints_count); } +} // namespace binary } // namespace ledger @@ -1,26 +1,276 @@ -#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. + */ + +#ifndef BINARY_H +#define BINARY_H -#include "journal.h" #include "parser.h" namespace ledger { -class binary_parser_t : public parser_t +class journal_t; + +namespace binary { + +template <typename T> +inline void read_number_nocheck(std::istream& in, T& num) { + in.read(reinterpret_cast<char *>(&num), sizeof(num)); +} + +template <typename T> +inline void read_number_nocheck(const char *& data, T& num) { + num = *reinterpret_cast<const T *>(data); + data += sizeof(T); +} + +template <typename T> +inline T read_number_nocheck(std::istream& in) { + T num; + read_number_nocheck(in, num); + return num; +} + +template <typename T> +inline T read_number_nocheck(const char *& data) { + T num; + read_number_nocheck(data, num); + return num; +} + +#if DEBUG_LEVEL >= ALPHA +#define read_guard(in, id) \ + if (read_number_nocheck<unsigned short>(in) != id) \ + assert(false); +#else +#define read_guard(in, id) +#endif + +template <typename T> +inline void read_number(std::istream& in, T& num) { + read_guard(in, 0x2003); + in.read(reinterpret_cast<char *>(&num), sizeof(num)); + read_guard(in, 0x2004); +} + +template <typename T> +inline void read_number(const char *& data, T& num) { + read_guard(data, 0x2003); + num = *reinterpret_cast<const T *>(data); + data += sizeof(T); + read_guard(data, 0x2004); +} + +template <typename T> +inline T read_number(std::istream& in) { + T num; + read_number(in, num); + return num; +} + +template <typename T> +inline T read_number(const char *& data) { + T num; + read_number(data, num); + return num; +} + +void read_bool(std::istream& in, bool& num); +void read_bool(const char *& data, bool& num); + +inline bool read_bool(std::istream& in) { + bool num; + read_bool(in, num); + return num; +} + +inline bool read_bool(const char *& data) { + bool num; + read_bool(data, num); + return num; +} + +template <typename T> +void read_long(std::istream& in, T& num) +{ + read_guard(in, 0x2001); + + unsigned char len; + read_number_nocheck(in, len); + + num = 0; + unsigned char temp; + if (len > 3) { + read_number_nocheck(in, temp); + num |= static_cast<unsigned long>(temp) << 24; + } + if (len > 2) { + read_number_nocheck(in, temp); + num |= static_cast<unsigned long>(temp) << 16; + } + if (len > 1) { + read_number_nocheck(in, temp); + num |= static_cast<unsigned long>(temp) << 8; + } + + read_number_nocheck(in, temp); + num |= static_cast<unsigned long>(temp); + + read_guard(in, 0x2002); +} + +template <typename T> +void read_long(const char *& data, T& num) { - public: - virtual bool test(std::istream& in) const; + read_guard(data, 0x2001); + + unsigned char len; + read_number_nocheck(data, len); + + num = 0; + unsigned char temp; + if (len > 3) { + read_number_nocheck(data, temp); + num |= static_cast<unsigned long>(temp) << 24; + } + if (len > 2) { + read_number_nocheck(data, temp); + num |= static_cast<unsigned long>(temp) << 16; + } + if (len > 1) { + read_number_nocheck(data, temp); + num |= static_cast<unsigned long>(temp) << 8; + } + + read_number_nocheck(data, temp); + num |= static_cast<unsigned long>(temp); + + read_guard(data, 0x2002); +} + +template <typename T> +inline T read_long(std::istream& in) { + T num; + read_long(in, num); + return num; +} + +template <typename T> +inline T read_long(const char *& data) { + T num; + read_long(data, num); + return num; +} + +void read_string(std::istream& in, string& str); +void read_string(const char *& data, string& str); +void read_string(const char *& data, string * str); + +inline string read_string(std::istream& in) { + string temp; + read_string(in, temp); + return temp; +} + +inline string read_string(const char *& data) { + string temp; + read_string(data, temp); + return temp; +} + + +template <typename T> +inline void write_number_nocheck(std::ostream& out, T num) { + out.write(reinterpret_cast<char *>(&num), sizeof(num)); +} + +#if DEBUG_LEVEL >= ALPHA +#define write_guard(out, id) \ + write_number_nocheck<unsigned short>(out, id) +#else +#define write_guard(in, id) +#endif + +template <typename T> +inline void write_number(std::ostream& out, T num) { + write_guard(out, 0x2003); + out.write(reinterpret_cast<char *>(&num), sizeof(num)); + write_guard(out, 0x2004); +} + +void write_bool(std::ostream& out, bool num); + +template <typename T> +void write_long(std::ostream& out, T num) +{ + write_guard(out, 0x2001); + + unsigned char len = 4; + if (static_cast<unsigned long>(num) < 0x00000100UL) + len = 1; + else if (static_cast<unsigned long>(num) < 0x00010000UL) + len = 2; + else if (static_cast<unsigned long>(num) < 0x01000000UL) + len = 3; + write_number_nocheck<unsigned char>(out, len); + + unsigned char temp; + if (len > 3) { + temp = (static_cast<unsigned long>(num) & 0xFF000000UL) >> 24; + write_number_nocheck(out, temp); + } + if (len > 2) { + temp = (static_cast<unsigned long>(num) & 0x00FF0000UL) >> 16; + write_number_nocheck(out, temp); + } + if (len > 1) { + temp = (static_cast<unsigned long>(num) & 0x0000FF00UL) >> 8; + write_number_nocheck(out, temp); + } + + temp = (static_cast<unsigned long>(num) & 0x000000FFUL); + write_number_nocheck(out, temp); + + write_guard(out, 0x2002); +} + +void write_string(std::ostream& out, const string& str); - virtual unsigned int parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); -}; +template <typename T> +inline void write_object(std::ostream& out, const T& journal) { + assert(false); +} -void write_binary_journal(std::ostream& out, - journal_t * journal); +void write_journal(std::ostream& out, journal_t * journal); +} // namespace binary } // namespace ledger -#endif // _BINARY_H +#endif // BINARY_H diff --git a/commodity.cc b/commodity.cc new file mode 100644 index 00000000..3bb3ec72 --- /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 datetime_t& date, + const amount_t& price) +{ + if (! base->history) + base->history = history_t(); + + history_map::iterator i = base->history->prices.find(date); + if (i != base->history->prices.end()) { + (*i).second = price; + } else { + std::pair<history_map::iterator, bool> result + = base->history->prices.insert(history_map::value_type(date, price)); + assert(result.second); + } +} + +bool commodity_t::remove_price(const datetime_t& date) +{ + if (base->history) { + history_map::size_type n = base->history->prices.erase(date); + if (n > 0) { + if (base->history->prices.empty()) + base->history.reset(); + return true; + } + } + return false; +} + +optional<amount_t> commodity_t::value(const optional<datetime_t>& moment) +{ + optional<datetime_t> age; + optional<amount_t> price; + + if (base->history) { + assert(base->history->prices.size() > 0); + + if (! moment) { + history_map::reverse_iterator r = base->history->prices.rbegin(); + age = (*r).first; + price = (*r).second; + } else { + history_map::iterator i = base->history->prices.lower_bound(*moment); + if (i == base->history->prices.end()) { + history_map::reverse_iterator r = base->history->prices.rbegin(); + age = (*r).first; + price = (*r).second; + } else { + age = (*i).first; + if (*moment != *age) { + if (i != base->history->prices.begin()) { + --i; + age = (*i).first; + price = (*i).second; + } else { + age = none; + } + } else { + price = (*i).second; + } + } + } + } + + if (! has_flags(COMMODITY_STYLE_NOMARKET) && parent().get_quote) { + if (optional<amount_t> quote = parent().get_quote + (*this, age, moment, + (base->history && base->history->prices.size() > 0 ? + (*base->history->prices.rbegin()).first : optional<datetime_t>()))) + return *quote; + } + return price; +} + +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<annotated_commodity_t&>(leftcomm)); + annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm)); + + if (! aleftcomm.details.price && arightcomm.details.price) + return true; + if (aleftcomm.details.price && ! arightcomm.details.price) + return false; + + if (aleftcomm.details.price && arightcomm.details.price) { + amount_t leftprice(*aleftcomm.details.price); + leftprice.in_place_reduce(); + amount_t rightprice(*arightcomm.details.price); + rightprice.in_place_reduce(); + + if (leftprice.commodity() == rightprice.commodity()) { + return (leftprice - rightprice).sign() < 0; + } else { + // Since we have two different amounts, there's really no way + // to establish a true sorting order; we'll just do it based + // on the numerical values. + leftprice.clear_commodity(); + rightprice.clear_commodity(); + return (leftprice - rightprice).sign() < 0; + } + } + + if (! aleftcomm.details.date && arightcomm.details.date) + return true; + if (aleftcomm.details.date && ! arightcomm.details.date) + return false; + + if (aleftcomm.details.date && arightcomm.details.date) { + 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<commodity_t::base_t> + base_commodity(new commodity_t::base_t(symbol)); + std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity)); + + DEBUG("amounts.commodities", "Creating base commodity " << symbol); + + // Create the "qualified symbol" version of this commodity's symbol + if (commodity_t::symbol_needs_quotes(symbol)) { + commodity->qualified_symbol = "\""; + *commodity->qualified_symbol += symbol; + *commodity->qualified_symbol += "\""; + } + + DEBUG("amounts.commodities", + "Creating commodity '" << commodity->symbol() << "'"); + + // Start out the new commodity with the default commodity's flags + // and precision, if one has been defined. +#if 0 + // jww (2007-05-02): This doesn't do anything currently! + if (default_commodity) + commodity->drop_flags(COMMODITY_STYLE_THOUSANDS | + COMMODITY_STYLE_NOMARKET); +#endif + + commodity->ident = commodities.size(); + + commodities.push_back(commodity.get()); + return commodity.release(); +} + +commodity_t * commodity_pool_t::find_or_create(const string& symbol) +{ + DEBUG("amounts.commodities", "Find-or-create commodity " << symbol); + + commodity_t * commodity = find(symbol); + if (commodity) + return commodity; + return create(symbol); +} + +commodity_t * commodity_pool_t::find(const string& symbol) +{ + DEBUG("amounts.commodities", "Find commodity " << symbol); + + typedef commodity_pool_t::commodities_t::nth_index<1>::type + commodities_by_name; + + commodities_by_name& name_index = commodities.get<1>(); + commodities_by_name::const_iterator i = name_index.find(symbol); + if (i != name_index.end()) + return *i; + else + return NULL; +} + +commodity_t * commodity_pool_t::find(const commodity_t::ident_t ident) +{ + DEBUG("amounts.commodities", "Find commodity by ident " << ident); + + typedef commodity_pool_t::commodities_t::nth_index<0>::type + commodities_by_ident; + + commodities_by_ident& ident_index = commodities.get<0>(); + return ident_index[ident]; +} + +commodity_t * +commodity_pool_t::create(const string& symbol, const annotation_t& details) +{ + commodity_t * new_comm = create(symbol); + if (! new_comm) + return NULL; + + if (details) + return find_or_create(*new_comm, details); + else + return new_comm; +} + +namespace { + string make_qualified_name(const commodity_t& comm, + const annotation_t& details) + { + assert(details); + + if (details.price && details.price->sign() < 0) + throw_(amount_error, "A commodity's price may not be negative"); + + std::ostringstream name; + comm.print(name); + annotated_commodity_t::write_annotations(name, details); + + DEBUG("amounts.commodities", "make_qualified_name for " + << comm.qualified_symbol << std::endl << details); + DEBUG("amounts.commodities", "qualified_name is " << name.str()); + + return name.str(); + } +} + +commodity_t * +commodity_pool_t::find(const string& symbol, const annotation_t& details) +{ + commodity_t * comm = find(symbol); + if (! comm) + return NULL; + + if (details) { + string name = make_qualified_name(*comm, details); + + if (commodity_t * ann_comm = find(name)) { + assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); + return ann_comm; + } + return NULL; + } else { + return comm; + } +} + +commodity_t * +commodity_pool_t::find_or_create(const string& symbol, + const annotation_t& details) +{ + commodity_t * comm = find(symbol); + if (! comm) + return NULL; + + if (details) + return find_or_create(*comm, details); + else + return comm; +} + +commodity_t * +commodity_pool_t::create(commodity_t& comm, + const annotation_t& details, + const string& mapping_key) +{ + assert(comm); + assert(details); + assert(! mapping_key.empty()); + + std::auto_ptr<commodity_t> commodity + (new annotated_commodity_t(&comm, details)); + + commodity->qualified_symbol = comm.symbol(); + assert(! commodity->qualified_symbol->empty()); + + DEBUG("amounts.commodities", "Creating annotated commodity " + << "symbol " << commodity->symbol() + << " key " << mapping_key << std::endl << details); + + // Add the fully annotated name to the map, so that this symbol may + // quickly be found again. + commodity->ident = commodities.size(); + commodity->mapping_key_ = mapping_key; + + commodities.push_back(commodity.get()); + return commodity.release(); +} + +commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, + const annotation_t& details) +{ + assert(comm); + assert(details); + + string name = make_qualified_name(comm, details); + assert(! name.empty()); + + if (commodity_t * ann_comm = find(name)) { + assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); + return ann_comm; + } + return create(comm, details, name); +} + +} // namespace ledger diff --git a/commodity.h b/commodity.h new file mode 100644 index 00000000..5a8df20b --- /dev/null +++ b/commodity.h @@ -0,0 +1,396 @@ +/* + * 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<commodity_t, noncopyable> +{ + friend class commodity_pool_t; + +public: + class base_t : public noncopyable, public supports_flags<> + { + public: + typedef std::map<const datetime_t, amount_t> history_map; + typedef std::pair<const datetime_t, amount_t> history_pair; + + struct history_t { + history_map prices; + ptime last_lookup; + }; + +#define COMMODITY_STYLE_DEFAULTS 0x00 +#define COMMODITY_STYLE_SUFFIXED 0x01 +#define COMMODITY_STYLE_SEPARATED 0x02 +#define COMMODITY_STYLE_EUROPEAN 0x04 +#define COMMODITY_STYLE_THOUSANDS 0x08 +#define COMMODITY_STYLE_NOMARKET 0x10 +#define COMMODITY_STYLE_BUILTIN 0x20 + + string symbol; + amount_t::precision_t precision; + optional<string> name; + optional<string> note; + optional<history_t> history; + optional<amount_t> smaller; + optional<amount_t> larger; + + public: + explicit base_t(const string& _symbol) + : supports_flags<>(COMMODITY_STYLE_DEFAULTS), + symbol(_symbol), precision(0) { + TRACE_CTOR(base_t, "const string&"); + } + ~base_t() { + TRACE_DTOR(base_t); + } + }; + +public: + static bool symbol_needs_quotes(const string& symbol); + + typedef base_t::history_t history_t; + typedef base_t::history_map history_map; + typedef uint_least32_t ident_t; + + shared_ptr<base_t> base; + + commodity_pool_t * parent_; + ident_t ident; + optional<string> qualified_symbol; + optional<string> mapping_key_; + bool annotated; + +public: + explicit commodity_t(commodity_pool_t * _parent, + const shared_ptr<base_t>& _base) + : delegates_flags<>(*_base.get()), base(_base), + parent_(_parent), annotated(false) { + TRACE_CTOR(commodity_t, ""); + } + virtual ~commodity_t() { + TRACE_DTOR(commodity_t); + } + + operator bool() const; + + virtual bool operator==(const commodity_t& comm) const { + if (comm.annotated) + return comm == *this; + return base.get() == comm.base.get(); + } + + commodity_pool_t& parent() const { + return *parent_; + } + + string base_symbol() const { + return base->symbol; + } + string symbol() const { + return qualified_symbol ? *qualified_symbol : base_symbol(); + } + + string mapping_key() const { + if (mapping_key_) + return *mapping_key_; + else + return base_symbol(); + } + + optional<string> name() const { + return base->name; + } + void set_name(const optional<string>& arg = none) { + base->name = arg; + } + + optional<string> note() const { + return base->note; + } + void set_note(const optional<string>& arg = none) { + base->note = arg; + } + + amount_t::precision_t precision() const { + return base->precision; + } + void set_precision(amount_t::precision_t arg) { + base->precision = arg; + } + + optional<amount_t> smaller() const { + return base->smaller; + } + void set_smaller(const optional<amount_t>& arg = none) { + base->smaller = arg; + } + + optional<amount_t> larger() const { + return base->larger; + } + void set_larger(const optional<amount_t>& arg = none) { + base->larger = arg; + } + + optional<history_t> history() const { + return base->history; + } + + void add_price(const datetime_t& date, const amount_t& price); + bool remove_price(const datetime_t& date); + + optional<amount_t> value(const optional<datetime_t>& moment = none); + + static void parse_symbol(std::istream& in, string& symbol); + static void parse_symbol(char *& p, string& symbol); + static string parse_symbol(std::istream& in) { + string temp; + parse_symbol(in, temp); + return temp; + } + + void print(std::ostream& out) const { + out << symbol(); + } + + void read(std::istream& in); + void read(char *& data); + void write(std::ostream& out) const; + + bool valid() const; +}; + +inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { + comm.print(out); + return out; +} + +struct annotation_t : public equality_comparable<annotation_t> +{ + optional<amount_t> price; + optional<datetime_t> date; + optional<string> tag; + + explicit annotation_t + (const optional<amount_t>& _price = none, + const optional<datetime_t>& _date = none, + const optional<string>& _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 : datetime_t()) << " " + << "tag " << (tag ? *tag : "NONE"); + } + + bool valid() const { + assert(*this); + return true; + } +}; + +inline std::ostream& operator<<(std::ostream& out, const annotation_t& details) { + details.print(out); + return out; +} + +class annotated_commodity_t + : public commodity_t, + public equality_comparable<annotated_commodity_t, + equality_comparable2<annotated_commodity_t, commodity_t, + noncopyable> > +{ +public: + commodity_t * ptr; + annotation_t details; + + explicit annotated_commodity_t(commodity_t * _ptr, + const annotation_t& _details) + : commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) { + TRACE_CTOR(annotated_commodity_t, ""); + annotated = true; + } + virtual ~annotated_commodity_t() { + TRACE_DTOR(annotated_commodity_t); + } + + virtual bool operator==(const commodity_t& comm) const; + virtual bool operator==(const annotated_commodity_t& comm) const { + return *this == static_cast<const commodity_t&>(comm); + } + + commodity_t& referent() { + return *ptr; + } + const commodity_t& referent() const { + return *ptr; + } + + commodity_t& strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag); + + void write_annotations(std::ostream& out) const { + annotated_commodity_t::write_annotations(out, details); + } + + static void write_annotations(std::ostream& out, + const annotation_t& info); +}; + +inline annotated_commodity_t& +as_annotated_commodity(commodity_t& commodity) { + return downcast<annotated_commodity_t>(commodity); +} +inline const annotated_commodity_t& +as_annotated_commodity(const commodity_t& commodity) { + return downcast<const annotated_commodity_t>(commodity); +} + + +struct compare_amount_commodities { + bool operator()(const amount_t * left, const amount_t * right) const; +}; + +class commodity_pool_t : public noncopyable +{ + /** + * The commodities collection in commodity_pool_t maintains pointers + * to all the commodities which have ever been created by the user, + * whether explicitly by calling the create methods of + * commodity_pool_t, or implicitly by parsing a commoditized amount. + * + * The `commodities' member variable represents a collection which + * is indexed by two vertices: first, and ordered sequence of unique + * integer which identify commodities by a numerical identifier; and + * second, by a hashed set of symbolic names which reflect how the + * commodity was referred to by the user. + */ + typedef multi_index_container< + commodity_t *, + multi_index::indexed_by< + multi_index::random_access<>, + multi_index::hashed_unique< + multi_index::const_mem_fun<commodity_t, + string, &commodity_t::mapping_key> > + > + > commodities_t; + +public: + typedef commodity_pool_t::commodities_t::nth_index<0>::type + commodities_by_ident; + + commodities_t commodities; + + commodity_t * null_commodity; + commodity_t * default_commodity; + +private: + template<typename T> + struct first_initialized + { + typedef T result_type; + + template<typename InputIterator> + T operator()(InputIterator first, InputIterator last) const + { + for (; first != last; first++) + if (*first) + return *first; + return T(); + } + }; + +public: + boost::function<optional<amount_t> + (commodity_t& commodity, + const optional<datetime_t>& date, + const optional<datetime_t>& moment, + const optional<datetime_t>& last)> get_quote; + + explicit commodity_pool_t(); + + ~commodity_pool_t() { + 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 @@ -1,38 +1,21 @@ #include "config.h" #include "acconf.h" #include "option.h" -#include "datetime.h" -#include "quotes.h" +//#include "quotes.h" #include "valexpr.h" #include "walk.h" -#include <fstream> -#include <cstdlib> -#ifdef WIN32 -#include <io.h> -#else -#include <unistd.h> -#endif - -#ifdef HAVE_REALPATH -extern "C" char *realpath(const char *, char resolved_path[]); -#endif - -#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM) -#include <pwd.h> -#endif - namespace ledger { -std::string expand_path(const std::string& path) +string expand_path(const string& pathname) { - if (path.length() == 0 || path[0] != '~') - return path; + if (pathname.length() == 0 || pathname[0] != '~') + return pathname; const char * pfx = NULL; - std::string::size_type pos = path.find_first_of('/'); + string::size_type pos = pathname.find_first_of('/'); - if (path.length() == 1 || pos == 1) { + if (pathname.length() == 1 || pos == 1) { pfx = std::getenv("HOME"); #ifdef HAVE_GETPWUID if (! pfx) { @@ -45,37 +28,38 @@ std::string expand_path(const std::string& path) } #ifdef HAVE_GETPWNAM else { - std::string user(path, 1, pos == std::string::npos ? - std::string::npos : pos - 1); + 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 we failed to find an expansion, return the pathname unchanged. if (! pfx) - return path; + return pathname; - std::string result(pfx); + string result(pfx); - if (pos == std::string::npos) + if (pos == string::npos) return result; if (result.length() == 0 || result[result.length() - 1] != '/') result += '/'; - result += path.substr(pos + 1); + result += pathname.substr(pos + 1); return result; } -std::string resolve_path(const std::string& path) +// jww (2008-04-22): This needs to be changed to use boost::filesystem +string resolve_path(const string& pathname) { - if (path[0] == '~') - return expand_path(path); - return path; + if (pathname[0] == '~') + return expand_path(pathname); + return pathname; } config_t::config_t() @@ -104,29 +88,4 @@ config_t::config_t() trace_mode = false; } -////////////////////////////////////////////////////////////////////// - -void trace(const std::string& cat, const std::string& str) -{ - char buf[32]; - std::strftime(buf, 31, "%H:%M:%S", datetime_t::now.localtime()); - std::cerr << buf << " " << cat << ": " << str << std::endl; -} - -void trace_push(const std::string& cat, const std::string& str, - timing_t& timer) -{ - timer.start(); - trace(cat, str); -} - -void trace_pop(const std::string& cat, const std::string& str, - timing_t& timer) -{ - timer.stop(); - std::ostringstream out; - out << str << ": " << (double(timer.cumulative) / double(CLOCKS_PER_SEC)) << "s"; - trace(cat, out.str()); -} - } // namespace ledger @@ -2,38 +2,33 @@ #define _CONFIG_H #include "ledger.h" -#include "timing.h" - -#include <iostream> -#include <memory> -#include <list> namespace ledger { class config_t { public: - std::string init_file; - std::string data_file; - std::string cache_file; - std::string price_db; - - std::string balance_format; - std::string register_format; - std::string wide_register_format; - std::string plot_amount_format; - std::string plot_total_format; - std::string print_format; - std::string write_hdr_format; - std::string write_xact_format; - std::string equity_format; - std::string prices_format; - std::string pricesdb_format; - - std::string date_input_format; - - std::string account; - std::string pager; + path init_file; + path data_file; + path cache_file; + path price_db; + + string balance_format; + string register_format; + string wide_register_format; + string plot_amount_format; + string plot_total_format; + string print_format; + string write_hdr_format; + string write_xact_format; + string equity_format; + string prices_format; + string pricesdb_format; + + string date_input_format; + + string account; + string pager; unsigned long pricing_leeway; @@ -47,33 +42,6 @@ class config_t config_t(); }; -////////////////////////////////////////////////////////////////////// - -std::string resolve_path(const std::string& path); - -////////////////////////////////////////////////////////////////////// - -void trace(const std::string& cat, const std::string& str); -void trace_push(const std::string& cat, const std::string& str, - timing_t& timer); -void trace_pop(const std::string& cat, const std::string& str, - timing_t& timer); - -#define TRACE(cat, msg) if (config.trace_mode) trace(#cat, msg) -#define TRACE_(cat, msg) if (trace_mode) trace(#cat, msg) - -#define TRACE_PUSH(cat, msg) \ - timing_t timer_ ## cat(#cat); \ - if (config.trace_mode) trace_push(#cat, msg, timer_ ## cat) -#define TRACE_PUSH_(cat, msg) \ - timing_t timer_ ## cat(#cat); \ - if (trace_mode) trace_push(#cat, msg, timer_ ## cat) - -#define TRACE_POP(cat, msg) \ - if (config.trace_mode) trace_pop(#cat, msg, timer_ ## cat) -#define TRACE_POP_(cat, msg) \ - if (trace_mode) trace_pop(#cat, msg, timer_ ## cat) - } // namespace ledger #endif // _CONFIG_H diff --git a/configure.in b/configure.in deleted file mode 100644 index 8ef800f8..00000000 --- a/configure.in +++ /dev/null @@ -1,280 +0,0 @@ -# -*- 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.1) -AC_CONFIG_SRCDIR([main.cc]) -AC_CONFIG_HEADER([acconf.h]) - -# Checks for programs. -AC_PROG_CXX -AC_PROG_MAKE_SET -AC_PROG_LIBTOOL -AM_PROG_LIBTOOL - -# Checks for emacs lisp path -AM_PATH_LISPDIR - -# 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 <sys/types.h> - #include <sys/wait.h> - #include <unistd.h> - #include <stdlib.h> - #include <string.h> - #include <stdio.h>], - [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_LANG_POP]) - -if [test x$pipes_avail_cv_ = xtrue ]; then - AC_DEFINE([HAVE_UNIX_PIPES], [1], [Whether UNIX pipes are available]) -fi - -# check for gmp -AC_CACHE_CHECK( - [if libgmp is available], - [libgmp_avail_cv_], - [libgmp_save_libs=$LIBS - LIBS="-lgmp $LIBS" - AC_LANG_PUSH(C++) - AC_TRY_LINK( - [#include <gmp.h>], - [mpz_t bar; - mpz_init(bar); - mpz_clear(bar);], - [libgmp_avail_cv_=true], - [libgmp_avail_cv_=false]) - AC_LANG_POP - LIBS=$libgmp_save_libs]) - -if [test x$libgmp_avail_cv_ = xtrue ]; then - AM_CONDITIONAL(HAVE_GMP, true) - LIBS="-lgmp $LIBS" -else - AC_MSG_FAILURE("Could not find gmp library (set CPPFLAGS and LDFLAGS?)") -fi - -# check for pcre -AC_CACHE_CHECK( - [if libpcre is available], - [libpcre_avail_cv_], - [libpcre_save_libs=$LIBS - LIBS="-lpcre $LIBS" - AC_LANG_PUSH(C++) - AC_TRY_LINK( - [#include <pcre.h>], - [pcre_free((pcre *)NULL);], - [libpcre_avail_cv_=true], - [libpcre_avail_cv_=false]) - AC_LANG_POP - LIBS=$libpcre_save_libs]) - -if [test x$libpcre_avail_cv_ = xtrue ]; then - AM_CONDITIONAL(HAVE_PCRE, true) - LIBS="-lpcre $LIBS" -else - AC_MSG_FAILURE("Could not find pcre library (set CPPFLAGS and LDFLAGS?)") -fi - -# check for expat or xmlparse -AC_ARG_ENABLE(xml, - [ --enable-xml Turn on support for XML parsing], - [case "${enableval}" in - yes) xml=true ;; - no) xml=false ;; - *) AC_MSG_ERROR(bad value ${enableval} for --enable-xml) ;; - esac],[xml=true]) -AM_CONDITIONAL(USE_XML, test x$xml = xtrue) - -if [test x$xml = xtrue ]; then - AC_CACHE_CHECK( - [if libexpat is available], - [libexpat_avail_cv_], - [libexpat_save_libs=$LIBS - LIBS="-lexpat $LIBS" - AC_LANG_PUSH(C++) - AC_TRY_LINK( - [#include <stdio.h> - extern "C" { - #include <expat.h> // expat XML parser - }], - [XML_Parser parser = XML_ParserCreate(NULL); - return parser != NULL;], - [libexpat_avail_cv_=true], - [libexpat_avail_cv_=false]) - AC_LANG_POP - LIBS=$libexpat_save_libs]) - - if [test x$libexpat_avail_cv_ = xtrue ]; then - AM_CONDITIONAL(HAVE_EXPAT, true) - LIBS="-lexpat $LIBS" - else - AM_CONDITIONAL(HAVE_EXPAT, false) - fi -else - AM_CONDITIONAL(HAVE_EXPAT, false) -fi - -if [test x$xml = xtrue ]; then - if [test x$libexpat_avail_cv_ = xfalse ]; then - AC_CACHE_CHECK( - [if libxmlparse is available], - [libxmlparse_avail_cv_], - [libxmlparse_save_libs=$LIBS - LIBS="-lxmlparse -lxmltok $LIBS" - AC_LANG_PUSH(C++) - AC_TRY_LINK( - [#include <stdio.h> - extern "C" { - #include <xmlparse.h> // expat XML parser - }], - [XML_Parser parser = XML_ParserCreate(NULL); - return parser != NULL;], - [libxmlparse_avail_cv_=true], - [libxmlparse_avail_cv_=false]) - AC_LANG_POP - LIBS=$libxmlparse_save_libs]) - - if [test x$libxmlparse_avail_cv_ = xtrue ]; then - AM_CONDITIONAL(HAVE_XMLPARSE, true) - LIBS="-lxmlparse -lxmltok $LIBS" - else - AM_CONDITIONAL(HAVE_XMLPARSE, false) - fi - else - AM_CONDITIONAL(HAVE_XMLPARSE, false) - fi -else - AM_CONDITIONAL(HAVE_XMLPARSE, false) -fi - -# check for libofx -AC_ARG_ENABLE(ofx, - [ --enable-ofx Turn on support for OFX/OCF parsing], - [case "${enableval}" in - yes) ofx=true ;; - 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 - AC_CACHE_CHECK( - [if libofx is available], - [libofx_avail_cv_], - [libofx_save_libs=$LIBS - LIBS="-lofx $LIBS" - AC_LANG_PUSH(C++) - AC_TRY_LINK( - [#include <libofx.h>], - [ LibofxContextPtr libofx_context = libofx_get_new_context();], - [libofx_avail_cv_=true], - [libofx_avail_cv_=false]) - AC_LANG_POP - LIBS=$libofx_save_libs]) - - if [test x$libofx_avail_cv_ = xtrue ]; then - AM_CONDITIONAL(HAVE_LIBOFX, true) - LIBS="-lofx $LIBS" - else - AM_CONDITIONAL(HAVE_LIBOFX, false) - fi -else - AM_CONDITIONAL(HAVE_LIBOFX, false) -fi - -# check for Python -AC_ARG_ENABLE(python, - [ --enable-python Build the amounts library as a Python module], - [case "${enableval}" in - yes) python=true ;; - 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 - AM_PATH_PYTHON(2.2,, :) - if [test "$PYTHON" != :]; then - AC_CACHE_CHECK( - [if boost_python is available], - [boost_python_cpplib_avail_cv_], - [boost_python_save_libs=$LIBS - LIBS="-lboost_python -lpython$PYTHON_VERSION $LIBS" - AC_LANG_PUSH(C++) - AC_TRY_LINK( - [#include <boost/python.hpp> - 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_LANG_POP - LIBS=$boost_python_save_libs]) - if [test x$boost_python_cpplib_avail_cv_ = xtrue ]; then - AM_CONDITIONAL(HAVE_BOOST_PYTHON, true) - LIBS="-lboost_python -lpython$PYTHON_VERSION $LIBS" - else - AM_CONDITIONAL(HAVE_BOOST_PYTHON, false) - fi - else - AM_CONDITIONAL(HAVE_BOOST_PYTHON, false) - fi -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) - -# Checks for header files. -AC_STDC_HEADERS -AC_HAVE_HEADERS(sys/stat.h) - -# Checks for typedefs, structures, and compiler characteristics. -AC_HEADER_STDBOOL -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_CONFIG_FILES([Makefile]) -AC_OUTPUT diff --git a/configure.tmpl b/configure.tmpl new file mode 100644 index 00000000..d88cfe03 --- /dev/null +++ b/configure.tmpl @@ -0,0 +1,437 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.61) + +AC_INIT([ledger],[%VERSION%],[johnw@newartisans.com]) +AC_CONFIG_SRCDIR(ledger) +AM_INIT_AUTOMAKE([dist-bzip2]) + +AC_CONFIG_SRCDIR([main.cc]) +AC_CONFIG_HEADER([acconf.h]) + +# Checks for programs. +AC_PROG_CXX +AC_PROG_MAKE_SET +AC_PROG_LIBTOOL +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_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include <sys/types.h> + #include <sys/wait.h> + #include <unistd.h> + #include <stdlib.h> + #include <string.h> + #include <stdio.h>]], + [[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_LANG_POP]) + +if [test x$pipes_avail_cv_ = xtrue ]; then + AC_DEFINE([HAVE_UNIX_PIPES], [1], [Whether UNIX pipes are available]) +fi + +# check for gmp +AC_CACHE_CHECK( + [if libgmp is available], + [libgmp_avail_cv_], + [libgmp_save_libs=$LIBS + LIBS="-lgmp $LIBS" + AC_LANG_PUSH(C++) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <gmp.h>]], [[mpz_t bar; + mpz_init(bar); + mpz_clear(bar);]])],[libgmp_avail_cv_=true],[libgmp_avail_cv_=false]) + AC_LANG_POP + LIBS=$libgmp_save_libs]) + +if [test x$libgmp_avail_cv_ = xtrue ]; then + LIBS="-lgmp $LIBS" +else + AC_MSG_FAILURE("Could not find gmp library (set CPPFLAGS and LDFLAGS?)") +fi + +# check for pcre +AC_CACHE_CHECK( + [if libpcre is available], + [libpcre_avail_cv_], + [libpcre_save_libs=$LIBS + LIBS="-lpcre $LIBS" + AC_LANG_PUSH(C++) + AC_TRY_LINK( + [#include <pcre.h>], + [pcre_free((pcre *)NULL);], + [libpcre_avail_cv_=true], + [libpcre_avail_cv_=false]) + AC_LANG_POP + LIBS=$libpcre_save_libs]) + +if [test x$libpcre_avail_cv_ = xtrue ]; then + AM_CONDITIONAL(HAVE_PCRE, true) + LIBS="-lpcre $LIBS" +else + AC_MSG_FAILURE("Could not find pcre library (set CPPFLAGS and LDFLAGS?)") +fi + +# check for expat or xmlparse +AC_ARG_ENABLE(xml, + [ --enable-xml Turn on support for XML parsing], + [case "${enableval}" in + yes) xml=true ;; + no) xml=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-xml) ;; + esac],[xml=true]) +AM_CONDITIONAL(USE_XML, test x$xml = xtrue) + +if [test x$xml = xtrue ]; then + AC_CACHE_CHECK( + [if libexpat is available], + [libexpat_avail_cv_], + [libexpat_save_libs=$LIBS + LIBS="-lexpat $LIBS" + AC_LANG_PUSH(C++) + AC_TRY_LINK( + [#include <stdio.h> + extern "C" { + #include <expat.h> // expat XML parser + }], + [XML_Parser parser = XML_ParserCreate(NULL); + return parser != NULL;], + [libexpat_avail_cv_=true], + [libexpat_avail_cv_=false]) + AC_LANG_POP + LIBS=$libexpat_save_libs]) + + if [test x$libexpat_avail_cv_ = xtrue ]; then + AM_CONDITIONAL(HAVE_EXPAT, true) + LIBS="-lexpat $LIBS" + else + AM_CONDITIONAL(HAVE_EXPAT, false) + fi +else + AM_CONDITIONAL(HAVE_EXPAT, false) +fi + +if [test x$xml = xtrue ]; then + if [test x$libexpat_avail_cv_ = xfalse ]; then + AC_CACHE_CHECK( + [if libxmlparse is available], + [libxmlparse_avail_cv_], + [libxmlparse_save_libs=$LIBS + LIBS="-lxmlparse -lxmltok $LIBS" + AC_LANG_PUSH(C++) + AC_TRY_LINK( + [#include <stdio.h> + extern "C" { + #include <xmlparse.h> // expat XML parser + }], + [XML_Parser parser = XML_ParserCreate(NULL); + return parser != NULL;], + [libxmlparse_avail_cv_=true], + [libxmlparse_avail_cv_=false]) + AC_LANG_POP + LIBS=$libxmlparse_save_libs]) + + if [test x$libxmlparse_avail_cv_ = xtrue ]; then + AM_CONDITIONAL(HAVE_XMLPARSE, true) + LIBS="-lxmlparse -lxmltok $LIBS" + else + AM_CONDITIONAL(HAVE_XMLPARSE, false) + fi + else + AM_CONDITIONAL(HAVE_XMLPARSE, false) + fi +else + AM_CONDITIONAL(HAVE_XMLPARSE, false) +fi + +# check for boost_regex +AC_CACHE_CHECK( + [if boost_regex is available], + [boost_regex_avail_cv_], + [boost_regex_save_libs=$LIBS + LIBS="-lboost_regex$BOOST_SUFFIX $LIBS" + AC_LANG_PUSH(C++) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include <boost/regex.hpp>]], + [[boost::regex foo_regexp("Hello, world!");]])], + [boost_regex_avail_cv_=true], + [boost_regex_avail_cv_=false]) + AC_LANG_POP + LIBS=$boost_regex_save_libs]) + +if [test x$boost_regex_avail_cv_ = 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_cv_], + [boost_date_time_save_libs=$LIBS + LIBS="-lboost_date_time$BOOST_SUFFIX $LIBS" + AC_LANG_PUSH(C++) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include <boost/date_time/posix_time/posix_time.hpp> + #include <boost/date_time/gregorian/gregorian.hpp> + #include <boost/date_time/local_time_adjustor.hpp> + #include <boost/date_time/time_duration.hpp> + + using namespace boost::posix_time; + using namespace boost::date_time; + + #include <ctime> + + 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_cv_=true], + [boost_date_time_cpplib_avail_cv_=false]) + AC_LANG_POP + LIBS=$boost_date_time_save_libs]) + +if [test x$boost_date_time_cpplib_avail_cv_ = 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_cv_], + [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.hpp>]], + [[boost::filesystem::path this_path("Hello");]])], + [boost_filesystem_cpplib_avail_cv_=true], + [boost_filesystem_cpplib_avail_cv_=false]) + AC_LANG_POP + LIBS=$boost_filesystem_save_libs]) + +if [test x$boost_filesystem_cpplib_avail_cv_ = 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_cv_], +# [boost_signals_save_libs=$LIBS +# LIBS="-lboost_signals$BOOST_SUFFIX $LIBS" +# AC_LANG_PUSH(C++) +# AC_LINK_IFELSE( +# [AC_LANG_PROGRAM( +# [[#include <boost/signal.hpp>]], +# [[boost::signal<void (void)> this_signal;]])], +# [boost_signals_cpplib_avail_cv_=true], +# [boost_signals_cpplib_avail_cv_=false]) +# AC_LANG_POP +# LIBS=$boost_signals_save_libs]) +# +#if [test x$boost_signals_cpplib_avail_cv_ = 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], + [case "${enableval}" in + yes) ofx=true ;; + 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 + AC_CACHE_CHECK( + [if libofx is available], + [libofx_avail_cv_], + [libofx_save_libs=$LIBS + LIBS="-lofx $LIBS" + AC_LANG_PUSH(C++) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include <libofx.h>]], + [[LibofxContextPtr libofx_context = libofx_get_new_context();]])], + [libofx_avail_cv_=true], + [libofx_avail_cv_=false]) + AC_LANG_POP + LIBS=$libofx_save_libs]) + + if [test x$libofx_avail_cv_ = xtrue ]; then + AM_CONDITIONAL(HAVE_LIBOFX, true) + LIBS="-lofx $LIBS" + else + AM_CONDITIONAL(HAVE_LIBOFX, false) + fi +else + AM_CONDITIONAL(HAVE_LIBOFX, false) +fi + +# check for Python +AC_ARG_ENABLE(python, + [ --enable-python Build the amounts library as a Python module], + [case "${enableval}" in + yes) python=true ;; + 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 + AM_PATH_PYTHON(2.2,, :) + if [test "$PYTHON" != :]; then + AC_CACHE_CHECK( + [if boost_python is available], + [boost_python_cpplib_avail_cv_], + [boost_python_save_libs=$LIBS + LIBS="-lboost_python$BOOST_SUFFIX -lpython$PYTHON_VERSION $LIBS" + AC_LANG_PUSH(C++) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include <boost/python.hpp> + 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_LANG_POP + LIBS=$boost_python_save_libs]) + + if [test x$boost_python_cpplib_avail_cv_ = xtrue ]; then + AM_CONDITIONAL(HAVE_BOOST_PYTHON, true) + LIBS="-lboost_python$BOOST_SUFFIX -lpython$PYTHON_VERSION $LIBS" + else + AM_CONDITIONAL(HAVE_BOOST_PYTHON, false) + fi + else + AM_CONDITIONAL(HAVE_BOOST_PYTHON, false) + fi +else + AM_CONDITIONAL(HAVE_BOOST_PYTHON, false) +fi + +# check for CppUnit +AC_CACHE_CHECK( + [if cppunit is available], + [cppunit_avail_cv_], + [cppunit_save_libs=$LIBS + LIBS="-lcppunit $LIBS" + AC_LANG_PUSH(C++) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include <cppunit/CompilerOutputter.h> + #include <cppunit/TestResult.h> + #include <cppunit/TestResultCollector.h> + #include <cppunit/TestRunner.h> + #include <cppunit/TextTestProgressListener.h> + #include <cppunit/BriefTestProgressListener.h> + #include <cppunit/XmlOutputter.h> + #include <cppunit/extensions/TestFactoryRegistry.h>]], + [[CPPUNIT_NS::TestResult controller; + CPPUNIT_NS::TestResultCollector result;]])], + [cppunit_avail_cv_=true], + [cppunit_avail_cv_=false]) + AC_LANG_POP + LIBS=$cppunit_save_libs]) + +if [test x$cppunit_avail_cv_ = xtrue ]; then + AM_CONDITIONAL(HAVE_CPPUNIT, true) +else + AM_CONDITIONAL(HAVE_CPPUNIT, false) +fi + +# Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS([sys/stat.h langinfo.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_HEADER_STDBOOL +AC_TYPE_SIZE_T +AC_STRUCT_TM + +# Checks for library functions. +AC_HEADER_STDC +AC_CHECK_FUNCS([access mktime realpath getpwuid getpwnam nl_langinfo]) + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT @@ -3,10 +3,10 @@ namespace ledger { namespace { - inline void write_escaped_string(std::ostream& out, const std::string& xact) + inline void write_escaped_string(std::ostream& out, const string& xact) { out << "\""; - for (std::string::const_iterator i = xact.begin(); i != xact.end(); i++) + for (string::const_iterator i = xact.begin(); i != xact.end(); i++) if (*i == '"') { out << "\\"; out << "\""; diff --git a/datetime.h b/datetime.h deleted file mode 100644 index c057c76b..00000000 --- a/datetime.h +++ /dev/null @@ -1,310 +0,0 @@ -#ifndef _DATETIME_H -#define _DATETIME_H - -#include <ctime> -#include <sstream> - -#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); - - public: - static date_t now; - static const char * formats[]; - static int current_year; - static std::string input_format; - static std::string output_format; - - std::time_t when; - - 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::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; - } - - friend inline long operator-(const datetime_t& left, const datetime_t& right) { - std::time_t left_time = left.when; - std::time_t right_time = right.when; - return long(left_time) - long(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 -{ - int years; - int months; - int days; - int hours; - int minutes; - int 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 <map> -#include <fstream> - -#include <unistd.h> // for the `write' method - -int offset = 0; - -std::map<void *, int> 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 <string> - -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 <pcre.h> -#include <cstring> -#include <new> -#include <iostream> -#include <cstdlib> - -#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 @@ -1,11 +1,8 @@ #include "derive.h" -#include "datetime.h" -#include "error.h" +#include "utils.h" #include "mask.h" #include "walk.h" -#include <memory> - namespace ledger { entry_t * derive_new_entry(journal_t& journal, @@ -16,7 +13,9 @@ entry_t * derive_new_entry(journal_t& journal, entry_t * matching = NULL; - added->_date = *i++; + // jww (2008-04-20): Need to parse the string here + //added->_date = *i++; + added->_date = boost::posix_time::time_from_string(*i++); if (i == end) throw new error("Too few arguments to 'entry'"); @@ -31,7 +30,7 @@ entry_t * derive_new_entry(journal_t& journal, break; } - added->payee = matching ? matching->payee : regexp.pattern; + added->payee = matching ? matching->payee : regexp.expr.str(); if (! matching) { account_t * acct; @@ -67,8 +66,8 @@ entry_t * derive_new_entry(journal_t& journal, sum_accounts(*journal.master); value_t total = account_xdata(*acct).total; - if (total.type == value_t::AMOUNT) - xact->amount.set_commodity(((amount_t *) total.data)->commodity()); + if (total.is_type(value_t::AMOUNT)) + xact->amount.set_commodity(total.as_amount_lval().commodity()); } } @@ -116,9 +115,9 @@ entry_t * derive_new_entry(journal_t& journal, account_t * draw_acct = NULL; while (i != end) { - std::string& re_pat(*i++); - account_t * acct = NULL; - amount_t * amt = NULL; + string& re_pat(*i++); + account_t * acct = NULL; + amount_t * amt = NULL; mask_t acct_regex(re_pat); @@ -164,8 +163,8 @@ entry_t * derive_new_entry(journal_t& journal, if (! xact->amount.commodity()) { if (amt) xact->amount.set_commodity(amt->commodity()); - else if (commodity_t::default_commodity) - xact->amount.set_commodity(*commodity_t::default_commodity); + else if (amount_t::current_pool->default_commodity) + xact->amount.set_commodity(*amount_t::current_pool->default_commodity); } } added->add_transaction(xact); @@ -179,7 +178,6 @@ entry_t * derive_new_entry(journal_t& journal, added->add_transaction(new transaction_t(draw_acct)); } - done: if (! run_hooks(journal.entry_finalize_hooks, *added, false) || ! added->finalize() || ! run_hooks(journal.entry_finalize_hooks, *added, true)) @@ -5,7 +5,7 @@ namespace ledger { void format_emacs_transactions::write_entry(entry_t& entry) { int idx = entry.src_idx; - for (strings_list::iterator i = entry.journal->sources.begin(); + for (paths_list::const_iterator i = entry.journal->sources.begin(); i != entry.journal->sources.end(); i++) if (! idx--) { @@ -15,7 +15,9 @@ void format_emacs_transactions::write_entry(entry_t& entry) out << (((unsigned long)entry.beg_line) + 1) << " "; - std::time_t date = entry.date().when; + tm when = boost::posix_time::to_tm(entry.date()); + std::time_t date = std::mktime(&when); // jww (2008-04-20): Is this GMT or local? + out << "(" << (date / 65536) << " " << (date % 65536) << " 0) "; if (entry.code.empty()) @@ -2,17 +2,20 @@ #define _ERROR_H #include <exception> +#include <stdexcept> #include <string> #include <cstring> #include <sstream> #include <list> +namespace ledger { + class error_context { public: - std::string desc; + string desc; - error_context(const std::string& _desc) throw() : desc(_desc) {} + error_context(const string& _desc) throw() : desc(_desc) {} virtual ~error_context() throw() {} virtual void describe(std::ostream& out) const throw() { if (! desc.empty()) @@ -23,11 +26,11 @@ class error_context class file_context : public error_context { protected: - std::string file; + path file; unsigned long line; public: - file_context(const std::string& _file, unsigned long _line, - const std::string& desc = "") throw() + file_context(const path& _file, unsigned long _line, + const string& desc = "") throw() : error_context(desc), file(_file), line(_line) {} virtual ~file_context() throw() {} @@ -39,13 +42,14 @@ class file_context : public error_context } }; -class line_context : public error_context { - public: - std::string line; +class line_context : public error_context +{ +public: + string line; long pos; - line_context(const std::string& _line, long _pos, - const std::string& desc = "") throw() + line_context(const string& _line, long _pos, + const string& desc = "") throw() : error_context(desc), line(_line), pos(_pos) {} virtual ~line_context() throw() {} @@ -63,15 +67,14 @@ class line_context : public error_context { ////////////////////////////////////////////////////////////////////// -class str_exception : public std::exception { - protected: - std::string reason; - public: +class str_exception : public std::logic_error +{ +public: std::list<error_context *> context; - str_exception(const std::string& _reason, + str_exception(const string& why, error_context * ctxt = NULL) throw() - : reason(_reason) { + : std::logic_error(why) { if (ctxt) context.push_back(ctxt); } @@ -84,7 +87,7 @@ class str_exception : public std::exception { } virtual void reveal_context(std::ostream& out, - const std::string& kind) const throw() { + const string& kind) const throw() { for (std::list<error_context *>::const_reverse_iterator i = context.rbegin(); i != context.rend(); @@ -95,31 +98,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 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 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 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 string& why, error_context * ctxt = NULL) throw() + : fatal(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 <typename T = boost::uint_least8_t> +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 <typename T = boost::uint_least8_t> +class delegates_flags : public boost::noncopyable +{ +public: + typedef T flags_t; + +protected: + supports_flags<T>& flags_; + +public: + delegates_flags() : flags_() {} + delegates_flags(supports_flags<T>& 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 @@ -12,10 +12,10 @@ int format_t::abbrev_length = 2; bool format_t::ansi_codes = false; bool format_t::ansi_invert = false; -std::string format_t::truncate(const std::string& str, unsigned int width, +string format_t::truncate(const string& str, unsigned int width, const bool is_account) { - const int len = str.length(); + const unsigned int len = str.length(); if (len <= width) return str; @@ -43,28 +43,28 @@ std::string format_t::truncate(const std::string& str, unsigned int width, case ABBREVIATE: if (is_account) { - std::list<std::string> parts; - std::string::size_type beg = 0; - for (std::string::size_type pos = str.find(':'); - pos != std::string::npos; + std::list<string> parts; + string::size_type beg = 0; + for (string::size_type pos = str.find(':'); + pos != string::npos; beg = pos + 1, pos = str.find(':', beg)) - parts.push_back(std::string(str, beg, pos - beg)); - parts.push_back(std::string(str, beg)); + parts.push_back(string(str, beg, pos - beg)); + parts.push_back(string(str, beg)); - std::string result; - int newlen = len; - for (std::list<std::string>::iterator i = parts.begin(); + string result; + unsigned int newlen = len; + for (std::list<string>::iterator i = parts.begin(); i != parts.end(); i++) { // Don't contract the last element - std::list<std::string>::iterator x = i; + std::list<string>::iterator x = i; if (++x == parts.end()) { result += *i; break; } if (newlen > width) { - result += std::string(*i, 0, abbrev_length); + result += string(*i, 0, abbrev_length); result += ":"; newlen -= (*i).length() - abbrev_length; } else { @@ -98,9 +98,9 @@ std::string format_t::truncate(const std::string& str, unsigned int width, return buf; } -std::string partial_account_name(const account_t& account) +string partial_account_name(const account_t& account) { - std::string name; + string name; for (const account_t * acct = &account; acct && acct->parent; @@ -118,7 +118,7 @@ std::string partial_account_name(const account_t& account) return name; } -element_t * format_t::parse_elements(const std::string& fmt) +element_t * format_t::parse_elements(const string& fmt) { std::auto_ptr<element_t> result; @@ -143,7 +143,7 @@ element_t * format_t::parse_elements(const std::string& fmt) if (q != buf) { current->type = element_t::STRING; - current->chars = std::string(buf, q); + current->chars = string(buf, q); q = buf; current->next = new element_t; @@ -219,7 +219,7 @@ element_t * format_t::parse_elements(const std::string& fmt) current->type = element_t::VALUE_EXPR; assert(! current->val_expr); - current->val_expr = std::string(b, p); + current->val_expr = string(b, p); break; } @@ -238,7 +238,7 @@ element_t * format_t::parse_elements(const std::string& fmt) throw new format_error("Missing ']'"); current->type = element_t::DATE_STRING; - current->chars = std::string(b, p); + current->chars = string(b, p); break; } @@ -255,11 +255,11 @@ element_t * format_t::parse_elements(const std::string& fmt) case 'd': current->type = element_t::COMPLETE_DATE_STRING; - current->chars = datetime_t::output_format; + current->chars = output_time_format; break; case 'D': current->type = element_t::DATE_STRING; - current->chars = datetime_t::output_format; + current->chars = output_time_format; break; case 'S': current->type = element_t::SOURCE; break; @@ -294,7 +294,7 @@ element_t * format_t::parse_elements(const std::string& fmt) current = current->next; } current->type = element_t::STRING; - current->chars = std::string(buf, q); + current->chars = string(buf, q); } return result.release(); @@ -324,7 +324,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const { for (const element_t * elem = elements; elem; elem = elem->next) { std::ostringstream out; - std::string name; + string name; bool ignore_max_width = false; if (elem->flags & ELEMENT_ALIGN_LEFT) @@ -349,7 +349,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const case element_t::TOTAL: calc = total_expr; break; case element_t::VALUE_EXPR: calc = elem->val_expr; break; default: - assert(0); + assert(false); break; } if (! calc) @@ -363,7 +363,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const if (! amount_t::keep_price || ! amount_t::keep_date || ! amount_t::keep_tag) { - switch (value.type) { + switch (value.type()) { case value_t::AMOUNT: case value_t::BALANCE: case value_t::BALANCE_PAIR: @@ -376,56 +376,56 @@ void format_t::format(std::ostream& out_str, const details_t& details) const bool highlighted = false; - switch (value.type) { + switch (value.type()) { case value_t::BOOLEAN: - out << (*((bool *) value.data) ? "true" : "false"); + out << (value.as_boolean_lval() ? "true" : "false"); break; case value_t::INTEGER: if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) { if (ansi_invert) { - if (*((long *) value.data) > 0) { + if (value.as_long_lval() > 0) { mark_red(out, elem); highlighted = true; } } else { - if (*((long *) value.data) < 0) { + if (value.as_long_lval() < 0) { mark_red(out, elem); highlighted = true; } } } - out << *((long *) value.data); + out << value.as_long_lval(); break; case value_t::DATETIME: - out << *((datetime_t *) value.data); + out << value.as_datetime_lval(); break; case value_t::AMOUNT: if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) { if (ansi_invert) { - if (*((amount_t *) value.data) > 0) { + if (value.as_amount_lval().sign() > 0) { mark_red(out, elem); highlighted = true; } } else { - if (*((amount_t *) value.data) < 0) { + if (value.as_amount_lval().sign() < 0) { mark_red(out, elem); highlighted = true; } } } - out << *((amount_t *) value.data); + out << value.as_amount_lval(); break; case value_t::BALANCE: - bal = (balance_t *) value.data; + bal = &(value.as_balance_lval()); // fall through... case value_t::BALANCE_PAIR: if (! bal) - bal = &((balance_pair_t *) value.data)->quantity; + bal = &(value.as_balance_pair_lval().quantity()); if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) { if (ansi_invert) { @@ -440,14 +440,14 @@ void format_t::format(std::ostream& out_str, const details_t& details) const } } } - bal->write(out, elem->min_width, + bal->print(out, elem->min_width, (elem->max_width > 0 ? elem->max_width : elem->min_width)); ignore_max_width = true; break; default: - assert(0); + assert(false); break; } @@ -458,7 +458,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const case element_t::OPT_AMOUNT: if (details.xact) { - std::string disp; + string disp; bool use_disp = false; if (details.xact->cost && details.xact->amount) { @@ -511,7 +511,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const case element_t::SOURCE: if (details.entry && details.entry->journal) { int idx = details.entry->src_idx; - for (strings_list::iterator i = details.entry->journal->sources.begin(); + for (paths_list::const_iterator i = details.entry->journal->sources.begin(); i != details.entry->journal->sources.end(); i++) if (! idx--) { @@ -568,9 +568,12 @@ void format_t::format(std::ostream& out_str, const details_t& details) const else if (details.entry) date = details.entry->date(); +#if 0 + // jww (2008-04-20): This needs to be rewritten char buf[256]; std::strftime(buf, 255, elem->chars.c_str(), date.localtime()); out << (elem->max_width == 0 ? buf : truncate(buf, elem->max_width)); +#endif break; } @@ -587,13 +590,23 @@ void format_t::format(std::ostream& out_str, const details_t& details) const } char abuf[256]; +#if 0 + // jww (2008-04-20): This needs to be rewritten std::strftime(abuf, 255, elem->chars.c_str(), actual_date.localtime()); +#else + abuf[0] = '\0'; +#endif - if (effective_date && effective_date != actual_date) { + if (is_valid(effective_date) && effective_date != actual_date) { char buf[512]; char ebuf[256]; +#if 0 + // jww (2008-04-20): This needs to be rewritten std::strftime(ebuf, 255, elem->chars.c_str(), effective_date.localtime()); +#else + ebuf[0] = '\0'; +#endif std::strcpy(buf, abuf); std::strcat(buf, "="); @@ -615,6 +628,8 @@ void format_t::format(std::ostream& out_str, const details_t& details) const case transaction_t::PENDING: out << "! "; break; + case transaction_t::UNCLEARED: + break; } } break; @@ -630,12 +645,14 @@ void format_t::format(std::ostream& out_str, const details_t& details) const case transaction_t::PENDING: out << "! "; break; + case transaction_t::UNCLEARED: + break; } } break; case element_t::CODE: { - std::string temp; + string temp; if (details.entry && ! details.entry->code.empty()) { temp += "("; temp += details.entry->code; @@ -675,6 +692,8 @@ void format_t::format(std::ostream& out_str, const details_t& details) const case transaction_t::PENDING: name = "! "; break; + case transaction_t::UNCLEARED: + break; } } // fall through... @@ -691,9 +710,9 @@ void format_t::format(std::ostream& out_str, const details_t& details) const name = truncate(name, elem->max_width - 2, true); if (details.xact->flags & TRANSACTION_BALANCE) - name = "[" + name + "]"; + name = string("[") + name + "]"; else - name = "(" + name + ")"; + name = string("(") + name + ")"; } else if (elem->max_width > 0) name = truncate(name, elem->max_width, true); @@ -722,11 +741,11 @@ void format_t::format(std::ostream& out_str, const details_t& details) const break; default: - assert(0); + assert(false); break; } - std::string temp = out.str(); + string temp = out.str(); if (! ignore_max_width && elem->max_width > 0 && elem->max_width < temp.length()) temp.erase(elem->max_width); @@ -735,13 +754,13 @@ void format_t::format(std::ostream& out_str, const details_t& details) const } format_transactions::format_transactions(std::ostream& _output_stream, - const std::string& format) + const string& format) : output_stream(_output_stream), last_entry(NULL), last_xact(NULL) { const char * f = format.c_str(); if (const char * p = std::strstr(f, "%/")) { - first_line_format.reset(std::string(f, 0, p - f)); - next_lines_format.reset(std::string(p + 2)); + first_line_format.reset(string(f, 0, p - f)); + next_lines_format.reset(string(p + 2)); } else { first_line_format.reset(format); next_lines_format.reset(format); @@ -798,11 +817,11 @@ void format_entries::operator()(transaction_t& xact) } void print_entry(std::ostream& out, const entry_base_t& entry_base, - const std::string& prefix) + const string& prefix) { - std::string print_format; + string print_format; - if (const entry_t * entry = dynamic_cast<const entry_t *>(&entry_base)) { + if (dynamic_cast<const entry_t *>(&entry_base)) { print_format = (prefix + "%D %X%C%P\n" + prefix + " %-34A %12o\n%/" + prefix + " %-34A %12o\n"); @@ -818,7 +837,7 @@ void print_entry(std::ostream& out, const entry_base_t& entry_base, print_format = prefix + " %-34A %12o\n"; } else { - assert(0); + assert(false); } format_entries formatter(out, print_format); @@ -901,15 +920,15 @@ void format_account::operator()(account_t& account) } } -format_equity::format_equity(std::ostream& _output_stream, - const std::string& _format, - const std::string& display_predicate) +format_equity::format_equity(std::ostream& _output_stream, + const string& _format, + const string& display_predicate) : output_stream(_output_stream), disp_pred(display_predicate) { const char * f = _format.c_str(); if (const char * p = std::strstr(f, "%/")) { - first_line_format.reset(std::string(f, 0, p - f)); - next_lines_format.reset(std::string(p + 2)); + first_line_format.reset(string(f, 0, p - f)); + next_lines_format.reset(string(p + 2)); } else { first_line_format.reset(_format); next_lines_format.reset(_format); @@ -917,7 +936,7 @@ format_equity::format_equity(std::ostream& _output_stream, entry_t header_entry; header_entry.payee = "Opening Balances"; - header_entry._date = datetime_t::now; + header_entry._date = current_moment; first_line_format.format(output_stream, details_t(header_entry)); } @@ -929,16 +948,16 @@ void format_equity::flush() account_t summary(NULL, "Equity:Opening Balances"); summary.data = &xdata; - if (total.type >= value_t::BALANCE) { + if (total.type() >= value_t::BALANCE) { balance_t * bal; - if (total.type == value_t::BALANCE) - bal = (balance_t *) total.data; - else if (total.type == value_t::BALANCE_PAIR) - bal = &((balance_pair_t *) total.data)->quantity; + if (total.is_type(value_t::BALANCE)) + bal = &(total.as_balance_lval()); + else if (total.is_type(value_t::BALANCE_PAIR)) + bal = &(total.as_balance_pair_lval().quantity()); else - assert(0); + assert(false); - for (amounts_map::const_iterator i = bal->amounts.begin(); + for (balance_t::amounts_map::const_iterator i = bal->amounts.begin(); i != bal->amounts.end(); i++) { xdata.value = (*i).second; @@ -957,16 +976,16 @@ void format_equity::operator()(account_t& account) if (account_has_xdata(account)) { value_t val = account_xdata_(account).value; - if (val.type >= value_t::BALANCE) { + if (val.type() >= value_t::BALANCE) { balance_t * bal; - if (val.type == value_t::BALANCE) - bal = (balance_t *) val.data; - else if (val.type == value_t::BALANCE_PAIR) - bal = &((balance_pair_t *) val.data)->quantity; + if (val.is_type(value_t::BALANCE)) + bal = &(val.as_balance_lval()); + else if (val.is_type(value_t::BALANCE_PAIR)) + bal = &(val.as_balance_pair_lval().quantity()); else - assert(0); + assert(false); - for (amounts_map::const_iterator i = bal->amounts.begin(); + for (balance_t::amounts_map::const_iterator i = bal->amounts.begin(); i != bal->amounts.end(); i++) { account_xdata_(account).value = (*i).second; @@ -7,10 +7,10 @@ namespace ledger { -std::string truncated(const std::string& str, unsigned int width, +string truncated(const string& str, unsigned int width, const int style = 2); -std::string partial_account_name(const account_t& account, +string partial_account_name(const account_t& account, const unsigned int start_depth); #define ELEMENT_ALIGN_LEFT 0x01 @@ -50,7 +50,7 @@ struct element_t kind_t type; unsigned char flags; - std::string chars; + string chars; unsigned char min_width; unsigned char max_width; value_expr val_expr; @@ -59,18 +59,18 @@ struct element_t element_t() : type(STRING), flags(false), min_width(0), max_width(0), next(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor element_t"); + DEBUG("ledger.memory.ctors", "ctor element_t"); } ~element_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor element_t"); + DEBUG("ledger.memory.dtors", "dtor element_t"); if (next) delete next; // recursive, but not too deep } }; struct format_t { - std::string format_string; + string format_string; element_t * elements; enum elision_style_t { @@ -87,27 +87,27 @@ struct format_t static bool ansi_invert; format_t() : elements(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor format_t"); + DEBUG("ledger.memory.ctors", "ctor format_t"); } - format_t(const std::string& _format) : elements(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor format_t"); + format_t(const string& _format) : elements(NULL) { + DEBUG("ledger.memory.ctors", "ctor format_t"); reset(_format); } ~format_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor format_t"); + DEBUG("ledger.memory.dtors", "dtor format_t"); if (elements) delete elements; } - void reset(const std::string& _format) { + void reset(const string& _format) { if (elements) delete elements; elements = parse_elements(_format); format_string = _format; } - static element_t * parse_elements(const std::string& fmt); + static element_t * parse_elements(const string& fmt); - static std::string truncate(const std::string& str, unsigned int width, + static string truncate(const string& str, unsigned int width, const bool is_account = false); void format(std::ostream& out, const details_t& details) const; @@ -124,7 +124,7 @@ class format_transactions : public item_handler<transaction_t> public: format_transactions(std::ostream& _output_stream, - const std::string& format); + const string& format); virtual void flush() { output_stream.flush(); @@ -135,7 +135,7 @@ class format_transactions : public item_handler<transaction_t> class format_entries : public format_transactions { public: - format_entries(std::ostream& output_stream, const std::string& format) + format_entries(std::ostream& output_stream, const string& format) : format_transactions(output_stream, format) {} virtual void format_last_entry(); @@ -151,7 +151,7 @@ class format_entries : public format_transactions }; void print_entry(std::ostream& out, const entry_base_t& entry, - const std::string& prefix = ""); + const string& prefix = ""); bool disp_subaccounts_p(const account_t& account, const item_predicate<account_t>& disp_pred, @@ -175,8 +175,8 @@ class format_account : public item_handler<account_t> format_t format; format_account(std::ostream& _output_stream, - const std::string& _format, - const std::string& display_predicate = NULL) + const string& _format, + const string& display_predicate = NULL) : output_stream(_output_stream), disp_pred(display_predicate), format(_format) {} @@ -199,8 +199,8 @@ class format_equity : public item_handler<account_t> public: format_equity(std::ostream& _output_stream, - const std::string& _format, - const std::string& display_predicate); + const string& _format, + const string& display_predicate); virtual void flush(); virtual void operator()(account_t& account); @@ -208,7 +208,7 @@ class format_equity : public item_handler<account_t> class format_error : public error { public: - format_error(const std::string& reason, error_context * ctxt = NULL) throw() + format_error(const string& reason, error_context * ctxt = NULL) throw() : error(reason, ctxt) {} virtual ~format_error() throw() {} }; @@ -20,8 +20,8 @@ extern "C" { namespace ledger { -typedef std::map<const std::string, account_t *> accounts_map; -typedef std::pair<const std::string, account_t *> accounts_pair; +typedef std::map<const string, account_t *> accounts_map; +typedef std::pair<const string, account_t *> accounts_pair; typedef std::map<account_t *, commodity_t *> account_comm_map; typedef std::pair<account_t *, commodity_t *> account_comm_pair; @@ -29,7 +29,7 @@ typedef std::pair<account_t *, commodity_t *> account_comm_pair; static journal_t * curr_journal; static account_t * master_account; static account_t * curr_account; -static std::string curr_account_id; +static string curr_account_id; static entry_t * curr_entry; static commodity_t * entry_comm; static commodity_t * curr_comm; @@ -39,12 +39,12 @@ static XML_Parser current_parser; static accounts_map accounts_by_id; static account_comm_map account_comms; static unsigned int count; -static std::string have_error; +static string have_error; static std::istream * instreamp; static unsigned int offset; static XML_Parser parser; -static std::string path; +static path pathname; static unsigned int src_idx; static istream_pos_type beg_pos; static unsigned long beg_line; @@ -196,14 +196,14 @@ static void endElement(void *userData, const char *name) } -static amount_t convert_number(const std::string& number, +static amount_t convert_number(const string& number, int * precision = NULL) { const char * num = number.c_str(); if (char * p = std::strchr(num, '/')) { - std::string numer_str(num, p - num); - std::string denom_str(p + 1); + string numer_str(num, p - num); + string denom_str(p + 1); amount_t amt(numer_str); amount_t den(denom_str); @@ -226,15 +226,15 @@ static void dataHandler(void *userData, const char *s, int len) { switch (action) { case ACCOUNT_NAME: - curr_account->name = std::string(s, len); + curr_account->name = string(s, len); break; case ACCOUNT_ID: - curr_account_id = std::string(s, len); + curr_account_id = string(s, len); break; case ACCOUNT_PARENT: { - accounts_map::iterator i = accounts_by_id.find(std::string(s, len)); + accounts_map::iterator i = accounts_by_id.find(string(s, len)); assert(i != accounts_by_id.end()); curr_account->parent = (*i).second; curr_account->depth = curr_account->parent->depth + 1; @@ -243,10 +243,10 @@ static void dataHandler(void *userData, const char *s, int len) } case COMM_SYM: { - std::string symbol(s, len); + string symbol(s, len); if (symbol == "USD") symbol = "$"; - curr_comm = commodity_t::find_or_create(symbol); + curr_comm = amount_t::current_pool->find_or_create(symbol); assert(curr_comm); if (symbol != "$") @@ -260,7 +260,7 @@ static void dataHandler(void *userData, const char *s, int len) } case COMM_NAME: - curr_comm->set_name(std::string(s, len)); + curr_comm->set_name(string(s, len)); break; case COMM_PREC: @@ -268,15 +268,15 @@ static void dataHandler(void *userData, const char *s, int len) break; case ENTRY_NUM: - curr_entry->code = std::string(s, len); + curr_entry->code = string(s, len); break; case ENTRY_DATE: - curr_entry->_date = std::string(s, len); + curr_entry->_date = parse_datetime(string(s, len)); break; case ENTRY_DESC: - curr_entry->payee = std::string(s, len); + curr_entry->payee = string(s, len); break; case XACT_STATE: @@ -291,7 +291,7 @@ static void dataHandler(void *userData, const char *s, int len) case XACT_VALUE: { int precision; assert(entry_comm); - curr_value = convert_number(std::string(s, len), &precision); + curr_value = convert_number(string(s, len), &precision); curr_value.set_commodity(*entry_comm); if (precision > entry_comm->precision()) @@ -300,26 +300,26 @@ static void dataHandler(void *userData, const char *s, int len) } case XACT_QUANTITY: - curr_quant = convert_number(std::string(s, len)); + curr_quant = convert_number(string(s, len)); break; case XACT_ACCOUNT: { transaction_t * xact = curr_entry->transactions.back(); - accounts_map::iterator i = accounts_by_id.find(std::string(s, len)); + accounts_map::iterator i = accounts_by_id.find(string(s, len)); if (i != accounts_by_id.end()) { xact->account = (*i).second; } else { xact->account = curr_journal->find_account("<Unknown>"); - have_error = (std::string("Could not find account ") + - std::string(s, len)); + have_error = (string("Could not find account ") + + string(s, len)); } break; } case XACT_NOTE: - curr_entry->transactions.back()->note = std::string(s, len); + curr_entry->transactions.back()->note = string(s, len); break; case NO_ACTION: @@ -328,7 +328,7 @@ static void dataHandler(void *userData, const char *s, int len) break; default: - assert(0); + assert(false); break; } } @@ -343,17 +343,20 @@ bool gnucash_parser_t::test(std::istream& in) const return std::strncmp(buf, "<?xml", 5) == 0; } -unsigned int gnucash_parser_t::parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) +unsigned int gnucash_parser_t::parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master, + const path * original_file) { char buf[BUFSIZ]; +#if 0 + // jww (2008-05-08): Replace this // This is the date format used by Gnucash, so override whatever the // user specified. date_t::input_format = "%Y-%m-%d %H:%M:%S %z"; +#endif count = 0; action = NO_ACTION; @@ -366,12 +369,12 @@ unsigned int gnucash_parser_t::parse(std::istream& in, curr_state = transaction_t::UNCLEARED; instreamp = ∈ - path = original_file ? *original_file : "<gnucash>"; + pathname = original_file ? *original_file : "<gnucash>"; src_idx = journal->sources.size() - 1; // GnuCash uses the USD commodity without defining it, which really // means $. - commodity_t * usd = commodity_t::find_or_create("$"); + commodity_t * usd = amount_t::current_pool->find_or_create("$"); usd->set_precision(2); usd->add_flags(COMMODITY_STYLE_THOUSANDS); @@ -388,14 +391,14 @@ unsigned int gnucash_parser_t::parse(std::istream& in, in.getline(buf, BUFSIZ - 1); std::strcat(buf, "\n"); if (! XML_Parse(parser, buf, std::strlen(buf), in.eof())) { - unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; const char * msg = XML_ErrorString(XML_GetErrorCode(parser)); XML_ParserFree(parser); throw new parse_error(msg); } if (! have_error.empty()) { - unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; parse_error err(have_error); std::cerr << "Error: " << err.what() << std::endl; have_error = ""; @@ -10,11 +10,11 @@ class gnucash_parser_t : public parser_t public: virtual bool test(std::istream& in) const; - virtual unsigned int parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); + virtual unsigned int parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master = NULL, + const path * original_file = NULL); }; } // namespace ledger @@ -1,34 +1,32 @@ #include "journal.h" -#include "datetime.h" +#include "utils.h" #include "valexpr.h" #include "mask.h" #include "format.h" #include "acconf.h" -#include <fstream> - namespace ledger { -const std::string version = PACKAGE_VERSION; +const string version = PACKAGE_VERSION; bool transaction_t::use_effective_date = false; transaction_t::~transaction_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor transaction_t"); + DEBUG("ledger.memory.dtors", "dtor transaction_t"); if (cost) delete cost; } datetime_t transaction_t::actual_date() const { - if (! _date && entry) + if (! is_valid(_date) && entry) return entry->actual_date(); return _date; } datetime_t transaction_t::effective_date() const { - if (! _date_eff && entry) + if (! is_valid(_date_eff) && entry) return entry->effective_date(); return _date_eff; } @@ -36,12 +34,12 @@ datetime_t transaction_t::effective_date() const bool transaction_t::valid() const { if (! entry) { - DEBUG_PRINT("ledger.validate", "transaction_t: ! entry"); + DEBUG("ledger.validate", "transaction_t: ! entry"); return false; } if (state != UNCLEARED && state != CLEARED && state != PENDING) { - DEBUG_PRINT("ledger.validate", "transaction_t: state is bad"); + DEBUG("ledger.validate", "transaction_t: state is bad"); return false; } @@ -54,27 +52,27 @@ bool transaction_t::valid() const break; } if (! found) { - DEBUG_PRINT("ledger.validate", "transaction_t: ! found"); + DEBUG("ledger.validate", "transaction_t: ! found"); return false; } if (! account) { - DEBUG_PRINT("ledger.validate", "transaction_t: ! account"); + DEBUG("ledger.validate", "transaction_t: ! account"); return false; } if (! amount.valid()) { - DEBUG_PRINT("ledger.validate", "transaction_t: ! amount.valid()"); + DEBUG("ledger.validate", "transaction_t: ! amount.valid()"); return false; } if (cost && ! cost->valid()) { - DEBUG_PRINT("ledger.validate", "transaction_t: cost && ! cost->valid()"); + DEBUG("ledger.validate", "transaction_t: cost && ! cost->valid()"); return false; } if (flags & ~0x003f) { - DEBUG_PRINT("ledger.validate", "transaction_t: flags are bad"); + DEBUG("ledger.validate", "transaction_t: flags are bad"); return false; } @@ -92,6 +90,8 @@ bool entry_base_t::remove_transaction(transaction_t * xact) return true; } +// jww (2008-04-20): Migrate the Common Lisp version here! + bool entry_base_t::finalize() { // Scan through and compute the total balance for the entry. This @@ -120,8 +120,9 @@ bool entry_base_t::finalize() annotated_commodity_t& ann_comm(static_cast<annotated_commodity_t&> ((*x)->amount.commodity())); - if (ann_comm.price) - balance += ann_comm.price * (*x)->amount - *((*x)->cost); + if (ann_comm.details.price) + balance += ((*ann_comm.details.price) * (*x)->amount - + *((*x)->cost)); } } else { saw_null = true; @@ -136,7 +137,7 @@ bool entry_base_t::finalize() // account if one has been set. if (journal && journal->basket && transactions.size() == 1) { - assert(balance.type < value_t::BALANCE); + assert(balance.type() < value_t::BALANCE); transaction_t * nxact = new transaction_t(journal->basket); // The amount doesn't need to be set because the code below will // balance this transaction against the other. @@ -149,21 +150,23 @@ bool entry_base_t::finalize() // determine its price by dividing the unit count into the value of // the balance. This is done for the last eligible commodity. - if (! saw_null && balance && balance.type == value_t::BALANCE && - ((balance_t *) balance.data)->amounts.size() == 2) { + if (! saw_null && balance && balance.is_type(value_t::BALANCE) && + balance.as_balance_lval().amounts.size() == 2) { transactions_list::const_iterator x = transactions.begin(); commodity_t& this_comm = (*x)->amount.commodity(); - amounts_map::const_iterator this_bal = - ((balance_t *) balance.data)->amounts.find(&this_comm); - amounts_map::const_iterator other_bal = - ((balance_t *) balance.data)->amounts.begin(); - if (this_bal == other_bal) - other_bal++; + balance_t& bal(balance.as_balance_lval()); + + balance_t::amounts_map::const_iterator this_amt = + bal.amounts.find(&this_comm); + balance_t::amounts_map::const_iterator other_amt = + bal.amounts.begin(); + if (this_amt == other_amt) + other_amt++; - if (this_bal != ((balance_t *) balance.data)->amounts.end()) { + if (this_amt != bal.amounts.end()) { amount_t per_unit_cost = - amount_t((*other_bal).second / (*this_bal).second).unround(); + amount_t((*other_amt).second / (*this_amt).second).unround(); for (; x != transactions.end(); x++) { if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL) || @@ -178,9 +181,9 @@ bool entry_base_t::finalize() if ((*x)->amount.commodity() && ! (*x)->amount.commodity().annotated) (*x)->amount.annotate_commodity - (abs(per_unit_cost), - entry ? entry->actual_date() : datetime_t(), - entry ? entry->code : ""); + (annotation_t(per_unit_cost.abs(), + entry ? optional<datetime_t>(entry->actual_date()) : none, + entry ? optional<string>(entry->code) : none)); (*x)->cost = new amount_t(- (per_unit_cost * (*x)->amount)); balance += *(*x)->cost; @@ -196,7 +199,7 @@ bool entry_base_t::finalize() for (transactions_list::const_iterator x = transactions.begin(); x != transactions.end(); x++) { - if (! (*x)->amount.null() || + if (! (*x)->amount.is_null() || (((*x)->flags & TRANSACTION_VIRTUAL) && ! ((*x)->flags & TRANSACTION_BALANCE))) continue; @@ -211,20 +214,20 @@ bool entry_base_t::finalize() // generated to balance them all. balance_t * bal = NULL; - switch (balance.type) { + switch (balance.type()) { case value_t::BALANCE_PAIR: - bal = &((balance_pair_t *) balance.data)->quantity; + bal = &(balance.as_balance_pair_lval().quantity()); // fall through... case value_t::BALANCE: if (! bal) - bal = (balance_t *) balance.data; + bal = &(balance.as_balance_lval()); if (bal->amounts.size() < 2) { balance.cast(value_t::AMOUNT); } else { bool first = true; - for (amounts_map::const_iterator i = bal->amounts.begin(); + for (balance_t::amounts_map::const_iterator i = bal->amounts.begin(); i != bal->amounts.end(); i++) { amount_t amt = (*i).second; @@ -247,7 +250,7 @@ bool entry_base_t::finalize() // fall through... case value_t::AMOUNT: - (*x)->amount = *((amount_t *) balance.data); + (*x)->amount = balance.as_amount_lval(); (*x)->amount.negate(); (*x)->flags |= TRANSACTION_CALCULATED; @@ -263,8 +266,7 @@ bool entry_base_t::finalize() error * err = new balance_error("Entry does not balance", new entry_context(*this, "While balancing entry:")); - DEBUG_PRINT("ledger.journal.unbalanced_remainder", - "balance = " << balance); + DEBUG("ledger.journal.unbalanced_remainder", "balance = " << balance); balance.round(); err->context.push_front (new value_context(balance, "Unbalanced remainder is:")); @@ -278,7 +280,7 @@ entry_t::entry_t(const entry_t& e) : entry_base_t(e), _date(e._date), _date_eff(e._date_eff), code(e.code), payee(e.payee) { - DEBUG_PRINT("ledger.memory.ctors", "ctor entry_t"); + DEBUG("ledger.memory.ctors", "ctor entry_t"); for (transactions_list::const_iterator i = transactions.begin(); i != transactions.end(); @@ -315,8 +317,8 @@ void entry_t::add_transaction(transaction_t * xact) bool entry_t::valid() const { - if (! _date || ! journal) { - DEBUG_PRINT("ledger.validate", "entry_t: ! _date || ! journal"); + if (! is_valid(_date) || ! journal) { + DEBUG("ledger.validate", "entry_t: ! _date || ! journal"); return false; } @@ -324,23 +326,23 @@ bool entry_t::valid() const i != transactions.end(); i++) if ((*i)->entry != this || ! (*i)->valid()) { - DEBUG_PRINT("ledger.validate", "entry_t: transaction not valid"); + DEBUG("ledger.validate", "entry_t: transaction not valid"); return false; } return true; } -auto_entry_t::auto_entry_t(const std::string& _predicate) +auto_entry_t::auto_entry_t(const string& _predicate) : predicate_string(_predicate) { - DEBUG_PRINT("ledger.memory.ctors", "ctor auto_entry_t"); + DEBUG("ledger.memory.ctors", "ctor auto_entry_t"); predicate = new item_predicate<transaction_t>(predicate_string); } auto_entry_t::~auto_entry_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor auto_entry_t"); + DEBUG("ledger.memory.dtors", "dtor auto_entry_t"); if (predicate) delete predicate; } @@ -369,7 +371,7 @@ void auto_entry_t::extend_entry(entry_base_t& entry, bool post) } account_t * account = (*t)->account; - std::string fullname = account->fullname(); + string fullname = account->fullname(); assert(! fullname.empty()); if (fullname == "$account" || fullname == "@account") account = (*i)->account; @@ -396,7 +398,7 @@ void auto_entry_t::extend_entry(entry_base_t& entry, bool post) account_t::~account_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor account_t " << this); + DEBUG("ledger.memory.dtors", "dtor account_t " << this); //assert(! data); for (accounts_map::iterator i = accounts.begin(); @@ -405,8 +407,8 @@ account_t::~account_t() delete (*i).second; } -account_t * account_t::find_account(const std::string& name, - const bool auto_create) +account_t * account_t::find_account(const string& name, + const bool auto_create) { accounts_map::const_iterator i = accounts.find(name); if (i != accounts.end()) @@ -414,11 +416,11 @@ account_t * account_t::find_account(const std::string& name, char buf[256]; - std::string::size_type sep = name.find(':'); - assert(sep < 256|| sep == std::string::npos); + string::size_type sep = name.find(':'); + assert(sep < 256|| sep == string::npos); const char * first, * rest; - if (sep == std::string::npos) { + if (sep == string::npos) { first = name.c_str(); rest = NULL; } else { @@ -467,18 +469,18 @@ account_t * find_account_re_(account_t * account, const mask_t& regexp) return NULL; } -account_t * journal_t::find_account_re(const std::string& regexp) +account_t * journal_t::find_account_re(const string& regexp) { return find_account_re_(master, mask_t(regexp)); } -std::string account_t::fullname() const +string account_t::fullname() const { if (! _fullname.empty()) { return _fullname; } else { - const account_t * first = this; - std::string fullname = name; + const account_t * first = this; + string fullname = name; while (first->parent) { first = first->parent; @@ -501,7 +503,7 @@ std::ostream& operator<<(std::ostream& out, const account_t& account) bool account_t::valid() const { if (depth > 256 || ! journal) { - DEBUG_PRINT("ledger.validate", "account_t: depth > 256 || ! journal"); + DEBUG("ledger.validate", "account_t: depth > 256 || ! journal"); return false; } @@ -509,12 +511,12 @@ bool account_t::valid() const i != accounts.end(); i++) { if (this == (*i).second) { - DEBUG_PRINT("ledger.validate", "account_t: parent refers to itself!"); + DEBUG("ledger.validate", "account_t: parent refers to itself!"); return false; } if (! (*i).second->valid()) { - DEBUG_PRINT("ledger.validate", "account_t: child not valid"); + DEBUG("ledger.validate", "account_t: child not valid"); return false; } } @@ -524,7 +526,7 @@ bool account_t::valid() const journal_t::~journal_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor journal_t"); + DEBUG("ledger.memory.dtors", "dtor journal_t"); assert(master); delete master; @@ -607,7 +609,7 @@ bool journal_t::remove_entry(entry_t * entry) bool journal_t::valid() const { if (! master->valid()) { - DEBUG_PRINT("ledger.validate", "journal_t: master not valid"); + DEBUG("ledger.validate", "journal_t: master not valid"); return false; } @@ -615,15 +617,16 @@ bool journal_t::valid() const i != entries.end(); i++) if (! (*i)->valid()) { - DEBUG_PRINT("ledger.validate", "journal_t: entry not valid"); + DEBUG("ledger.validate", "journal_t: entry not valid"); return false; } - for (commodities_map::const_iterator i = commodity_t::commodities.begin(); - i != commodity_t::commodities.end(); + for (commodity_pool_t::commodities_by_ident::const_iterator + i = amount_t::current_pool->commodities.begin(); + i != amount_t::current_pool->commodities.end(); i++) - if (! (*i).second->valid()) { - DEBUG_PRINT("ledger.validate", "journal_t: commodity not valid"); + if (! (*i)->valid()) { + DEBUG("ledger.validate", "journal_t: commodity not valid"); return false; } @@ -639,12 +642,12 @@ void entry_context::describe(std::ostream& out) const throw() } xact_context::xact_context(const ledger::transaction_t& _xact, - const std::string& desc) throw() - : xact(_xact), file_context("", 0, desc) + const string& desc) throw() + : file_context("", 0, desc), xact(_xact) { - const ledger::strings_list& sources(xact.entry->journal->sources); - int x = 0; - for (ledger::strings_list::const_iterator i = sources.begin(); + const ledger::paths_list& sources(xact.entry->journal->sources); + unsigned int x = 0; + for (ledger::paths_list::const_iterator i = sources.begin(); i != sources.end(); i++, x++) if (x == xact.entry->src_idx) { @@ -1,18 +1,10 @@ #ifndef _JOURNAL_H #define _JOURNAL_H -#include <map> -#include <list> -#include <string> -#include <iostream> - #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 { @@ -39,10 +31,10 @@ class transaction_t amount_t amount; value_expr amount_expr; amount_t * cost; - std::string cost_expr; + string cost_expr; state_t state; unsigned short flags; - std::string note; + string note; istream_pos_type beg_pos; unsigned long beg_line; istream_pos_type end_pos; @@ -55,24 +47,24 @@ class transaction_t : entry(NULL), account(_account), cost(NULL), state(UNCLEARED), flags(TRANSACTION_NORMAL), beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); + DEBUG("ledger.memory.ctors", "ctor transaction_t"); } - transaction_t(account_t * _account, - const amount_t& _amount, - unsigned int _flags = TRANSACTION_NORMAL, - const std::string& _note = "") + transaction_t(account_t * _account, + const amount_t& _amount, + unsigned int _flags = TRANSACTION_NORMAL, + const string& _note = "") : entry(NULL), account(_account), amount(_amount), cost(NULL), state(UNCLEARED), flags(_flags), note(_note), beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); + DEBUG("ledger.memory.ctors", "ctor transaction_t"); } transaction_t(const transaction_t& xact) : entry(xact.entry), account(xact.account), amount(xact.amount), cost(xact.cost ? new amount_t(*xact.cost) : NULL), state(xact.state), flags(xact.flags), note(xact.note), beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); + DEBUG("ledger.memory.ctors", "ctor transaction_t"); } ~transaction_t(); @@ -100,7 +92,7 @@ class xact_context : public file_context { const transaction_t& xact; xact_context(const transaction_t& _xact, - const std::string& desc = "") throw(); + const string& desc = "") throw(); virtual ~xact_context() throw() {} }; @@ -112,7 +104,7 @@ class entry_base_t { public: journal_t * journal; - std::string note; + string note; unsigned long src_idx; istream_pos_type beg_pos; unsigned long beg_line; @@ -123,19 +115,19 @@ class entry_base_t entry_base_t() : journal(NULL), beg_pos(0), beg_line(0), end_pos(0), end_line(0) { - DEBUG_PRINT("ledger.memory.ctors", "ctor entry_base_t"); + DEBUG("ledger.memory.ctors", "ctor entry_base_t"); } entry_base_t(const entry_base_t& e) : journal(NULL), beg_pos(0), beg_line(0), end_pos(0), end_line(0) { - DEBUG_PRINT("ledger.memory.ctors", "ctor entry_base_t"); + DEBUG("ledger.memory.ctors", "ctor entry_base_t"); for (transactions_list::const_iterator i = e.transactions.begin(); i != e.transactions.end(); i++) transactions.push_back(new transaction_t(**i)); } virtual ~entry_base_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor entry_base_t"); + DEBUG("ledger.memory.dtors", "dtor entry_base_t"); for (transactions_list::iterator i = transactions.begin(); i != transactions.end(); i++) @@ -162,25 +154,25 @@ class entry_base_t class entry_t : public entry_base_t { public: - datetime_t _date; - datetime_t _date_eff; - std::string code; - std::string payee; + datetime_t _date; + datetime_t _date_eff; + string code; + string payee; entry_t() { - DEBUG_PRINT("ledger.memory.ctors", "ctor entry_t"); + DEBUG("ledger.memory.ctors", "ctor entry_t"); } entry_t(const entry_t& e); virtual ~entry_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor entry_t"); + DEBUG("ledger.memory.dtors", "dtor entry_t"); } datetime_t actual_date() const { return _date; } datetime_t effective_date() const { - if (! _date_eff) + if (! is_valid(_date_eff)) return _date; return _date_eff; } @@ -208,20 +200,13 @@ class entry_context : public error_context { const entry_base_t& entry; entry_context(const entry_base_t& _entry, - const std::string& desc = "") throw() + const string& desc = "") throw() : error_context(desc), entry(_entry) {} virtual ~entry_context() throw() {} virtual void describe(std::ostream& out) const throw(); }; -class balance_error : public error { - public: - balance_error(const std::string& reason, error_context * ctxt = NULL) throw() - : error(reason, ctxt) {} - virtual ~balance_error() throw() {} -}; - template <typename T> class item_predicate; @@ -230,12 +215,12 @@ class auto_entry_t : public entry_base_t { public: item_predicate<transaction_t> * predicate; - std::string predicate_string; + string predicate_string; auto_entry_t() : predicate(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor auto_entry_t"); + DEBUG("ledger.memory.ctors", "ctor auto_entry_t"); } - auto_entry_t(const std::string& _predicate); + auto_entry_t(const string& _predicate); virtual ~auto_entry_t(); @@ -256,23 +241,23 @@ struct auto_entry_finalizer_t : public entry_finalizer_t { class period_entry_t : public entry_base_t { public: - interval_t period; - std::string period_string; + interval_t period; + string period_string; period_entry_t() { - DEBUG_PRINT("ledger.memory.ctors", "ctor period_entry_t"); + DEBUG("ledger.memory.ctors", "ctor period_entry_t"); } - period_entry_t(const std::string& _period) + period_entry_t(const string& _period) : period(_period), period_string(_period) { - DEBUG_PRINT("ledger.memory.ctors", "ctor period_entry_t"); + DEBUG("ledger.memory.ctors", "ctor period_entry_t"); } period_entry_t(const period_entry_t& e) : entry_base_t(e), period(e.period), period_string(e.period_string) { - DEBUG_PRINT("ledger.memory.ctors", "ctor period_entry_t"); + DEBUG("ledger.memory.ctors", "ctor period_entry_t"); } virtual ~period_entry_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor period_entry_t"); + DEBUG("ledger.memory.dtors", "dtor period_entry_t"); } virtual bool valid() const { @@ -281,8 +266,8 @@ class period_entry_t : public entry_base_t }; -typedef std::map<const std::string, account_t *> accounts_map; -typedef std::pair<const std::string, account_t *> accounts_pair; +typedef std::map<const string, account_t *> accounts_map; +typedef std::pair<const string, account_t *> accounts_pair; class account_t { @@ -291,21 +276,21 @@ class account_t journal_t * journal; account_t * parent; - std::string name; - std::string note; + string name; + string note; unsigned short depth; accounts_map accounts; mutable void * data; mutable ident_t ident; - mutable std::string _fullname; + mutable string _fullname; - account_t(account_t * _parent = NULL, - const std::string& _name = "", - const std::string& _note = "") + account_t(account_t * _parent = NULL, + const string& _name = "", + const string& _note = "") : parent(_parent), name(_name), note(_note), depth(parent ? parent->depth + 1 : 0), data(NULL), ident(0) { - DEBUG_PRINT("ledger.memory.ctors", "ctor account_t " << this); + DEBUG("ledger.memory.ctors", "ctor account_t " << this); } ~account_t(); @@ -316,7 +301,7 @@ class account_t return ! (*this == account); } - std::string fullname() const; + string fullname() const; void add_account(account_t * acct) { accounts.insert(accounts_pair(acct->name, acct)); @@ -328,9 +313,9 @@ class account_t return n > 0; } - account_t * find_account(const std::string& name, bool auto_create = true); + account_t * find_account(const string& name, bool auto_create = true); - operator std::string() const { + operator string() const { return fullname(); } @@ -379,7 +364,8 @@ bool run_hooks(std::list<T>& list, Data& item, bool post) { typedef std::list<entry_t *> entries_list; typedef std::list<auto_entry_t *> auto_entries_list; typedef std::list<period_entry_t *> period_entries_list; -typedef std::list<std::string> strings_list; +typedef std::list<string> strings_list; +typedef std::list<path> paths_list; class journal_t { @@ -387,8 +373,8 @@ class journal_t account_t * master; account_t * basket; entries_list entries; - strings_list sources; - std::string price_db; + paths_list sources; + path price_db; char * item_pool; char * item_pool_end; @@ -399,7 +385,7 @@ class journal_t std::list<entry_finalizer_t *> entry_finalize_hooks; journal_t() : basket(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor journal_t"); + DEBUG("ledger.memory.ctors", "ctor journal_t"); master = new account_t(NULL, ""); master->journal = this; item_pool = item_pool_end = NULL; @@ -422,7 +408,7 @@ class journal_t acct->journal = NULL; } - account_t * find_account(const std::string& name, bool auto_create = true) { + account_t * find_account(const string& name, bool auto_create = true) { accounts_map::iterator c = accounts_cache.find(name); if (c != accounts_cache.end()) return (*c).second; @@ -432,7 +418,7 @@ class journal_t account->journal = this; return account; } - account_t * find_account_re(const std::string& regexp); + account_t * find_account_re(const string& regexp); bool add_entry(entry_t * entry); bool remove_entry(entry_t * entry); @@ -460,7 +446,7 @@ inline bool auto_entry_finalizer_t::operator()(entry_t& entry, bool post) { return true; } -extern const std::string version; +extern const string version; } // namespace ledger @@ -1,10 +1,10 @@ ;;; ledger.el --- Helper code for use with the "ledger" command-line tool -;; Copyright (C) 2007 John Wiegley (johnw AT gnu DOT org) +;; Copyright (C) 2008 John Wiegley (johnw AT gnu DOT org) ;; Emacs Lisp Archive Entry ;; Filename: ledger.el -;; Version: 2.6.1 +;; Version: 2.7 ;; Date: Thu 18-Jul-2008 ;; Keywords: data ;; Author: John Wiegley (johnw AT gnu DOT org) @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + #ifndef _LEDGER_H #define _LEDGER_H @@ -7,7 +38,7 @@ // // A command-line tool for general double-entry accounting. // -// Copyright (c) 2003,2004 John Wiegley <johnw@newartisans.com> +// Copyright (c) 2003-2008, John Wiegley <johnw@newartisans.com> // #include <amount.h> @@ -16,11 +47,10 @@ #include <journal.h> -#include <datetime.h> #include <format.h> #include <emacs.h> #include <csv.h> -#include <quotes.h> +//#include <quotes.h> #include <valexpr.h> #include <walk.h> #include <derive.h> @@ -27,11 +27,11 @@ int parse_and_report(config_t& config, std::auto_ptr<journal_t>& journal, { // Configure the terminus for value expressions - ledger::terminus = datetime_t::now; + ledger::terminus = current_moment; // Parse command-line arguments, and those set in the environment - std::list<std::string> args; + std::list<string> args; process_arguments(ledger::config_options, argc - 1, argv + 1, false, args); if (args.empty()) { @@ -44,9 +44,11 @@ int parse_and_report(config_t& config, std::auto_ptr<journal_t>& journal, config.use_cache = false; else config.use_cache = config.data_file.empty() && config.price_db.empty(); - DEBUG_PRINT("ledger.config.cache", "1. use_cache = " << config.use_cache); + DEBUG("ledger.config.cache", "1. use_cache = " << config.use_cache); +#if 0 TRACE(main, "Processing options and environment variables"); +#endif process_environment(ledger::config_options, const_cast<const char **>(envp), "LEDGER_"); @@ -65,7 +67,7 @@ int parse_and_report(config_t& config, std::auto_ptr<journal_t>& journal, #endif const char * p = std::getenv("HOME"); - std::string home = p ? p : ""; + string home = p ? p : ""; if (config.init_file.empty()) config.init_file = home + "/.ledgerrc"; @@ -77,21 +79,23 @@ int parse_and_report(config_t& config, std::auto_ptr<journal_t>& journal, if (config.data_file == config.cache_file) config.use_cache = false; - DEBUG_PRINT("ledger.config.cache", "2. use_cache = " << config.use_cache); + DEBUG("ledger.config.cache", "2. use_cache = " << config.use_cache); - TRACE(main, std::string("Initialization file is ") + config.init_file); - TRACE(main, std::string("Price database is ") + config.price_db); - TRACE(main, std::string("Binary cache is ") + config.cache_file); - TRACE(main, std::string("Main journal is ") + config.data_file); +#if 0 + TRACE(main, string("Initialization file is ") + config.init_file); + TRACE(main, string("Price database is ") + config.price_db); + TRACE(main, string("Binary cache is ") + config.cache_file); + TRACE(main, string("Main journal is ") + config.data_file); - TRACE(main, std::string("Based on option settings, binary cache ") + + TRACE(main, string("Based on option settings, binary cache ") + (config.use_cache ? "WILL " : "will NOT ") + "be used"); +#endif // Read the command word, canonicalize it to its one letter form, // then configure the system based on the kind of report to be // generated - std::string command = *arg++; + string command = *arg++; if (command == "balance" || command == "bal" || command == "b") command = "b"; @@ -125,7 +129,7 @@ int parse_and_report(config_t& config, std::auto_ptr<journal_t>& journal, ledger::dump_value_expr(std::cout, expr.get()); std::cout << std::endl; std::cout << "Value expression parsed was:" << std::endl; - ledger::write_value_expr(std::cout, expr.get()); + ledger::print_value_expr(std::cout, expr.get()); std::cout << std::endl << std::endl; std::cout << "Result of computation: "; } @@ -139,24 +143,28 @@ int parse_and_report(config_t& config, std::auto_ptr<journal_t>& journal, // this gets done below... } else { - throw new error(std::string("Unrecognized command '") + command + "'"); + throw new error(string("Unrecognized command '") + command + "'"); } // Parse initialization files, ledger data, price database, etc. journal.reset(new journal_t); +#if 0 { TRACE_PUSH(parser, "Parsing journal file"); +#endif if (parse_ledger_data(config, journal.get()) == 0) throw new error("Please specify ledger file using -f" " or LEDGER_FILE environment variable."); +#if 0 TRACE_POP(parser, "Finished parsing"); } +#endif // process the command word and its following arguments - std::string first_arg; + string first_arg; if (command == "w") { if (arg != args.end()) first_arg = *arg++; @@ -166,17 +174,22 @@ int parse_and_report(config_t& config, std::auto_ptr<journal_t>& journal, throw new error("The 'dump' command requires use of the --output option"); } - TRACE(options, std::string("Post-processing options ") + +#if 0 + TRACE(options, string("Post-processing options ") + "for command \"" + command + "\""); +#endif report.process_options(command, arg, args.end()); +#if 0 + // jww (2008-05-08): Is this disabled now? // If downloading is to be supported, configure the updater if (! commodity_base_t::updater && config.download_quotes) commodity_base_t::updater = new quotes_by_script(config.price_db, config.pricing_leeway, config.cache_dirty); +#endif std::auto_ptr<entry_t> new_entry; if (command == "e") { @@ -223,7 +236,7 @@ appending the output of this command to your Ledger file if you so choose." std::ostream * out = &std::cout; if (! report.output_file.empty()) { - out = new std::ofstream(report.output_file.c_str()); + out = new ofstream(report.output_file); } #ifdef HAVE_UNIX_PIPES else if (! config.pager.empty()) { @@ -279,7 +292,7 @@ appending the output of this command to your Ledger file if you so choose." ledger::dump_value_expr(std::cout, expr.get()); std::cout << std::endl; std::cout << "Value expression parsed was:" << std::endl; - ledger::write_value_expr(std::cout, expr.get()); + ledger::print_value_expr(std::cout, expr.get()); std::cout << std::endl << std::endl; std::cout << "Result of computation: "; } @@ -292,7 +305,7 @@ appending the output of this command to your Ledger file if you so choose." // Compile the format strings - const std::string * format; + const string * format; if (! report.format_string.empty()) format = &report.format_string; @@ -330,39 +343,53 @@ appending the output of this command to your Ledger file if you so choose." formatter = new format_transactions(*out, *format); if (command == "w") { +#if 0 TRACE_PUSH(text_writer, "Writing journal file"); +#endif write_textual_journal(*journal, first_arg, *formatter, config.write_hdr_format, *out); +#if 0 TRACE_POP(text_writer, "Finished writing"); +#endif } else if (command == "W") { +#if 0 TRACE_PUSH(binary_writer, "Writing binary file"); - std::ofstream stream(report.output_file.c_str()); - write_binary_journal(stream, journal.get()); +#endif + ofstream stream(report.output_file); + binary::write_journal(stream, journal.get()); +#if 0 TRACE_POP(binary_writer, "Finished writing"); +#endif } else { +#if 0 TRACE_PUSH(main, "Walking journal entries"); +#endif formatter = report.chain_xact_handlers(command, formatter, journal.get(), journal->master, formatter_ptrs); if (command == "e") walk_transactions(new_entry->transactions, *formatter); else if (command == "P" || command == "D") - walk_commodities(commodity_t::commodities, *formatter); + walk_commodities(amount_t::current_pool->commodities, *formatter); else walk_entries(journal->entries, *formatter); if (command != "P" && command != "D") formatter->flush(); +#if 0 TRACE_POP(main, "Finished entry walk"); +#endif } // For the balance and equity reports, output the sum totals. if (command == "b") { +#if 0 TRACE_PUSH(main, "Walking journal accounts"); +#endif format_account acct_formatter(*out, *format, report.display_predicate); sum_accounts(*journal->master); @@ -377,21 +404,29 @@ appending the output of this command to your Ledger file if you so choose." acct_formatter.format.format(*out, details_t(*journal->master)); } } +#if 0 TRACE_POP(main, "Finished account walk"); +#endif } else if (command == "E") { +#if 0 TRACE_PUSH(main, "Walking journal accounts"); +#endif format_equity acct_formatter(*out, *format, report.display_predicate); sum_accounts(*journal->master); walk_accounts(*journal->master, acct_formatter, report.sort_string); acct_formatter.flush(); +#if 0 TRACE_POP(main, "Finished account walk"); +#endif } #if DEBUG_LEVEL >= BETA +#if 0 { TRACE_PUSH(cleanup, "Cleaning up allocated memory"); +#endif clear_transaction_xdata xact_cleaner; walk_entries(journal->entries, xact_cleaner); @@ -408,19 +443,25 @@ appending the output of this command to your Ledger file if you so choose." i++) delete *i; +#if 0 TRACE_POP(cleanup, "Finished cleaning"); } #endif +#endif // Write out the binary cache, if need be if (config.use_cache && config.cache_dirty && ! config.cache_file.empty()) { +#if 0 TRACE_PUSH(binary_cache, "Writing journal file"); +#endif - std::ofstream stream(config.cache_file.c_str()); - write_binary_journal(stream, journal.get()); + ofstream stream(config.cache_file); + binary::write_journal(stream, journal.get()); +#if 0 TRACE_POP(binary_cache, "Finished writing"); +#endif } #ifdef HAVE_UNIX_PIPES @@ -454,9 +495,13 @@ int main(int argc, char * argv[], char * envp[]) report_t report; ledger::config = &config; ledger::report = &report; +#if 0 TRACE_PUSH(main, "Ledger starting"); +#endif int status = parse_and_report(config, journal, report, argc, argv, envp); +#if 0 TRACE_POP(main, "Ledger done"); +#endif return status; } catch (error * err) { 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 == "<all>": + 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! @@ -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 <cstdlib> +#include "mask.h" -#include <pcre.h> +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 @@ -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 <string> -#include <exception> +#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 @@ -10,11 +10,11 @@ namespace ledger { -typedef std::map<const std::string, account_t *> accounts_map; -typedef std::pair<const std::string, account_t *> accounts_pair; +typedef std::map<const string, account_t *> accounts_map; +typedef std::pair<const string, account_t *> accounts_pair; -typedef std::map<const std::string, commodity_t *> commodities_map; -typedef std::pair<const std::string, commodity_t *> commodities_pair; +typedef std::map<const string, commodity_t *> commodities_map; +typedef std::pair<const string, commodity_t *> commodities_pair; journal_t * curr_journal; accounts_map ofx_accounts; @@ -31,7 +31,7 @@ int ofx_proc_account_cb(struct OfxAccountData data, void * account_data) if (! data.account_id_valid) return -1; - DEBUG_PRINT("ledger.ofx.parse", "account " << data.account_name); + DEBUG("ledger.ofx.parse", "account " << data.account_name); account_t * account = new account_t(master_account, data.account_name); curr_journal->add_account(account); ofx_accounts.insert(accounts_pair(data.account_id, account)); @@ -88,7 +88,7 @@ int ofx_proc_transaction_cb(struct OfxTransactionData data, xact->cost = new amount_t(stream.str() + " " + default_commodity->base_symbol()); } - DEBUG_PRINT("ledger.ofx.parse", "xact " << xact->amount + DEBUG("ledger.ofx.parse", "xact " << xact->amount << " from " << *xact->account); if (data.date_initiated_valid) @@ -129,7 +129,7 @@ int ofx_proc_security_cb(struct OfxSecurityData data, void * security_data) if (! data.unique_id_valid) return -1; - std::string symbol; + string symbol; if (data.ticker_valid) symbol = data.ticker; else if (data.currency_valid) @@ -148,13 +148,13 @@ int ofx_proc_security_cb(struct OfxSecurityData data, void * security_data) commodities_map::iterator i = ofx_securities.find(data.unique_id); if (i == ofx_securities.end()) { - DEBUG_PRINT("ledger.ofx.parse", "security " << symbol); + DEBUG("ledger.ofx.parse", "security " << symbol); ofx_securities.insert(commodities_pair(data.unique_id, commodity)); } // jww (2005-02-09): What is the commodity for data.unitprice? if (data.date_unitprice_valid && data.unitprice_valid) { - DEBUG_PRINT("ledger.ofx.parse", " price " << data.unitprice); + DEBUG("ledger.ofx.parse", " price " << data.unitprice); commodity->add_price(data.date_unitprice, amount_t(data.unitprice)); } @@ -194,11 +194,11 @@ bool ofx_parser_t::test(std::istream& in) const return true; } -unsigned int ofx_parser_t::parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) +unsigned int ofx_parser_t::parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master, + const path * original_file) { if (! original_file) return 0; @@ -10,11 +10,11 @@ class ofx_parser_t : public parser_t public: virtual bool test(std::istream& in) const; - virtual unsigned int parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); + virtual unsigned int parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master = NULL, + const path * original_file = NULL); }; } // namespace ledger @@ -1,15 +1,9 @@ #include "option.h" #include "config.h" #include "report.h" -#include "debug.h" -#include "error.h" +#include "utils.h" -#include <iostream> -#include <cstdarg> -#include <cstdlib> -#include <unistd.h> - -#include "util.h" +namespace ledger { namespace { inline void process_option(option_t * opt, const char * arg = NULL) { @@ -20,9 +14,9 @@ namespace { catch (error * err) { err->context.push_back (new error_context - (std::string("While parsing option '--") + opt->long_opt + + (string("While parsing option '--") + opt->long_opt + "'" + (opt->short_opt != '\0' ? - (std::string(" (-") + opt->short_opt + "):") : ":"))); + (string(" (-") + opt->short_opt + "):") : ":"))); throw err; } opt->handled = true; @@ -59,7 +53,7 @@ namespace { } } -bool process_option(option_t * options, const std::string& name, +bool process_option(option_t * options, const string& name, const char * arg) { option_t * opt = search_options(options, name.c_str()); @@ -71,9 +65,8 @@ bool process_option(option_t * options, const std::string& name, } void process_arguments(option_t * options, int argc, char ** argv, - const bool anywhere, std::list<std::string>& args) + const bool anywhere, std::list<string>& args) { - int index = 0; for (char ** i = argv; *i; i++) { if ((*i)[0] != '-') { if (anywhere) { @@ -87,7 +80,6 @@ void process_arguments(option_t * options, int argc, char ** argv, } // --long-option or -s - again: if ((*i)[1] == '-') { if ((*i)[2] == '\0') break; @@ -101,18 +93,18 @@ void process_arguments(option_t * options, int argc, char ** argv, option_t * opt = search_options(options, name); if (! opt) - throw new option_error(std::string("illegal option --") + name); + throw new option_error(string("illegal option --") + name); if (opt->wants_arg && value == NULL) { value = *++i; if (value == NULL) - throw new option_error(std::string("missing option argument for --") + + throw new option_error(string("missing option argument for --") + name); } process_option(opt, value); } else if ((*i)[1] == '\0') { - throw new option_error(std::string("illegal option -")); + throw new option_error(string("illegal option -")); } else { std::list<option_t *> opt_queue; @@ -121,7 +113,7 @@ void process_arguments(option_t * options, int argc, char ** argv, for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) { option_t * opt = search_options(options, c); if (! opt) - throw new option_error(std::string("illegal option -") + c); + throw new option_error(string("illegal option -") + c); opt_queue.push_back(opt); } @@ -131,20 +123,17 @@ void process_arguments(option_t * options, int argc, char ** argv, if ((*o)->wants_arg) { value = *++i; if (value == NULL) - throw new option_error(std::string("missing option argument for -") + + throw new option_error(string("missing option argument for -") + (*o)->short_opt); } process_option(*o, value); } } - - next: - ; } } void process_environment(option_t * options, const char ** envp, - const std::string& tag) + const string& tag) { const char * tag_p = tag.c_str(); unsigned int tag_len = tag.length(); @@ -171,7 +160,7 @@ void process_environment(option_t * options, const char ** envp, err->context.pop_back(); err->context.push_back (new error_context - (std::string("While parsing environment variable option '") + + (string("While parsing environment variable option '") + *p + "':")); throw err; } @@ -181,8 +170,6 @@ void process_environment(option_t * options, const char ** envp, ////////////////////////////////////////////////////////////////////// -namespace ledger { - config_t * config = NULL; report_t * report = NULL; @@ -406,23 +393,23 @@ OPT_BEGIN(version, "v") { } OPT_END(version); OPT_BEGIN(init_file, "i:") { - std::string path = resolve_path(optarg); - if (access(path.c_str(), R_OK) != -1) - config->init_file = path; + path pathname = resolve_path(optarg); + if (boost::filesystem::exists(pathname)) + config->init_file = pathname; else - throw new error(std::string("The init file '") + path + + throw new error(string("The init file '") + string(pathname.string()) + "' does not exist or is not readable"); } OPT_END(init_file); OPT_BEGIN(file, "f:") { - if (std::string(optarg) == "-") { + if (string(optarg) == "-") { config->data_file = optarg; } else { - std::string path = resolve_path(optarg); - if (access(path.c_str(), R_OK) != -1) - config->data_file = path; + path pathname = resolve_path(optarg); + if (boost::filesystem::exists(pathname)) + config->data_file = pathname; else - throw new error(std::string("The ledger file '") + path + + throw new error(string("The ledger file '") + string(pathname.string()) + "' does not exist or is not readable"); } } OPT_END(file); @@ -436,10 +423,8 @@ OPT_BEGIN(no_cache, "") { } OPT_END(no_cache); OPT_BEGIN(output, "o:") { - if (std::string(optarg) != "-") { - std::string path = resolve_path(optarg); - report->output_file = path; - } + if (string(optarg) != "-") + report->output_file = resolve_path(optarg); } OPT_END(output); OPT_BEGIN(account, "a:") { @@ -468,30 +453,27 @@ OPT_BEGIN(effective, "") { } OPT_END(effective); OPT_BEGIN(begin, "b:") { - char buf[128]; interval_t interval(optarg); - if (! interval.begin) - throw new error(std::string("Could not determine beginning of period '") + + if (! is_valid(interval.begin)) + throw new error(string("Could not determine beginning of period '") + optarg + "'"); if (! report->predicate.empty()) report->predicate += "&"; report->predicate += "d>=["; - report->predicate += interval.begin.to_string(); + report->predicate += format_datetime(interval.begin); report->predicate += "]"; } OPT_END(begin); OPT_BEGIN(end, "e:") { - char buf[128]; interval_t interval(optarg); - if (! interval.begin) - throw new error(std::string("Could not determine end of period '") + - optarg + "'"); + if (! is_valid(interval.begin)) + throw new error(string("Could not determine end of period '") + optarg + "'"); if (! report->predicate.empty()) report->predicate += "&"; report->predicate += "d<["; - report->predicate += interval.begin.to_string(); + report->predicate += format_datetime(interval.begin); report->predicate += "]"; terminus = interval.begin; @@ -618,7 +600,7 @@ OPT_BEGIN(pager, ":") { } OPT_END(pager); OPT_BEGIN(truncate, ":") { - std::string style(optarg); + string style(optarg); if (style == "leading") format_t::elision_style = format_t::TRUNCATE_LEADING; else if (style == "middle") @@ -674,16 +656,16 @@ OPT_BEGIN(related, "r") { } OPT_END(related); OPT_BEGIN(descend, "") { - std::string arg(optarg); - std::string::size_type beg = 0; + string arg(optarg); + string::size_type beg = 0; report->descend_expr = ""; - for (std::string::size_type pos = arg.find(';'); - pos != std::string::npos; + for (string::size_type pos = arg.find(';'); + pos != string::npos; beg = pos + 1, pos = arg.find(';', beg)) - report->descend_expr += (std::string("t=={") + - std::string(arg, beg, pos - beg) + "};"); - report->descend_expr += (std::string("t=={") + - std::string(arg, beg) + "}"); + report->descend_expr += (string("t=={") + + string(arg, beg, pos - beg) + "};"); + report->descend_expr += (string("t=={") + + string(arg, beg) + "}"); } OPT_END(descend); OPT_BEGIN(descend_if, "") { @@ -704,19 +686,19 @@ OPT_BEGIN(period, "p:") { interval_t interval(report->report_period); - if (interval.begin) { + if (is_valid(interval.begin)) { if (! report->predicate.empty()) report->predicate += "&"; report->predicate += "d>=["; - report->predicate += interval.begin.to_string(); + report->predicate += format_datetime(interval.begin); report->predicate += "]"; } - if (interval.end) { + if (is_valid(interval.end)) { if (! report->predicate.empty()) report->predicate += "&"; report->predicate += "d<["; - report->predicate += interval.end.to_string(); + report->predicate += format_datetime(interval.end); report->predicate += "]"; terminus = interval.end; @@ -727,35 +709,35 @@ OPT_BEGIN(daily, "") { if (report->report_period.empty()) report->report_period = "daily"; else - report->report_period = std::string("daily ") + report->report_period; + report->report_period = string("daily ") + report->report_period; } OPT_END(daily); OPT_BEGIN(weekly, "W") { if (report->report_period.empty()) report->report_period = "weekly"; else - report->report_period = std::string("weekly ") + report->report_period; + report->report_period = string("weekly ") + report->report_period; } OPT_END(weekly); OPT_BEGIN(monthly, "M") { if (report->report_period.empty()) report->report_period = "monthly"; else - report->report_period = std::string("monthly ") + report->report_period; + report->report_period = string("monthly ") + report->report_period; } OPT_END(monthly); OPT_BEGIN(quarterly, "") { if (report->report_period.empty()) report->report_period = "quarterly"; else - report->report_period = std::string("quarterly ") + report->report_period; + report->report_period = string("quarterly ") + report->report_period; } OPT_END(quarterly); OPT_BEGIN(yearly, "Y") { if (report->report_period.empty()) report->report_period = "yearly"; else - report->report_period = std::string("yearly ") + report->report_period; + report->report_period = string("yearly ") + report->report_period; } OPT_END(yearly); OPT_BEGIN(dow, "") { @@ -902,24 +884,28 @@ namespace { while (equals > optarg && std::isspace(*(equals - 1))) equals--; - std::string symbol(optarg, 0, equals - optarg); + string symbol(optarg, 0, equals - optarg); amount_t price(equals + 1); - if (commodity_t * commodity = commodity_t::find_or_create(symbol)) { - commodity->add_price(datetime_t::now, price); - commodity->history()->bogus_time = datetime_t::now; + if (commodity_t * commodity = + amount_t::current_pool->find_or_create(symbol)) { + commodity->add_price(current_moment, price); +#if 0 + // jww (2008-04-20): what was this? + commodity->history()->bogus_time = current_moment; +#endif } } } OPT_BEGIN(set_price, ":") { - std::string arg(optarg); - std::string::size_type beg = 0; - for (std::string::size_type pos = arg.find(';'); - pos != std::string::npos; + string arg(optarg); + string::size_type beg = 0; + for (string::size_type pos = arg.find(';'); + pos != string::npos; beg = pos + 1, pos = arg.find(';', beg)) - parse_price_setting(std::string(arg, beg, pos - beg).c_str()); - parse_price_setting(std::string(arg, beg).c_str()); + parse_price_setting(string(arg, beg, pos - beg).c_str()); + parse_price_setting(string(arg, beg).c_str()); } OPT_END(set_price); OPT_BEGIN(performance, "g") { @@ -935,15 +921,15 @@ OPT_BEGIN(gain, "G") { ledger::total_expr = "@G"; } OPT_END(gain); -static std::string expand_value_expr(const std::string& tmpl, - const std::string& expr) +static string expand_value_expr(const string& tmpl, + const string& expr) { - std::string xp = tmpl; - for (std::string::size_type i = xp.find('#'); - i != std::string::npos; + string xp = tmpl; + for (string::size_type i = xp.find('#'); + i != string::npos; i = xp.find('#')) - xp = (std::string(xp, 0, i) + "(" + expr + ")" + - std::string(xp, i + 1)); + xp = (string(xp, 0, i) + "(" + expr + ")" + + string(xp, i + 1)); return xp; } @@ -1,11 +1,9 @@ #ifndef _OPTION_H #define _OPTION_H -#include <list> -#include <string> -#include <exception> +#include "utils.h" -#include "error.h" +namespace ledger { typedef void (*handler_t)(const char * arg); @@ -17,20 +15,14 @@ struct option_t { bool handled; }; -class option_error : public error { - public: - option_error(const std::string& reason) throw() : error(reason) {} - virtual ~option_error() throw() {} -}; +DECLARE_EXCEPTION(error, option_error); -bool process_option(option_t * options, const std::string& opt, +bool process_option(option_t * options, const string& opt, const char * arg = NULL); void process_arguments(option_t * options, int argc, char ** argv, - const bool anywhere, std::list<std::string>& args); + const bool anywhere, std::list<string>& args); void process_environment(option_t * options, const char ** envp, - const std::string& tag); - -namespace ledger { + const string& tag); class config_t; class report_t; @@ -2,13 +2,6 @@ #include "journal.h" #include "config.h" -#include <fstream> -#ifdef WIN32 -#include <io.h> -#else -#include <unistd.h> -#endif - namespace ledger { typedef std::list<parser_t *> parsers_list; @@ -56,11 +49,11 @@ bool unregister_parser(parser_t * parser) return true; } -unsigned int parse_journal(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) +unsigned int parse_journal(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master, + const path * original_file) { if (! master) master = journal->master; @@ -74,21 +67,22 @@ unsigned int parse_journal(std::istream& in, return 0; } -unsigned int parse_journal_file(const std::string& path, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) +unsigned int parse_journal_file(const path& pathname, + config_t& config, + journal_t * journal, + account_t * master, + const path * original_file) { - journal->sources.push_back(path); + journal->sources.push_back(pathname); - if (access(path.c_str(), R_OK) == -1) - throw new error(std::string("Cannot read file '") + path + "'"); + if (! boost::filesystem::exists(pathname)) + throw new error(string("Cannot read file '") + + string(pathname.string()) + "'"); if (! original_file) - original_file = &path; + original_file = &pathname; - std::ifstream stream(path.c_str()); + boost::filesystem::ifstream stream(pathname); return parse_journal(stream, config, journal, master, original_file); } @@ -107,33 +101,33 @@ unsigned int parse_ledger_data(config_t& config, if (! cache_parser) cache_parser = binary_parser_ptr; if (! xml_parser) - xml_parser = xml_parser_ptr; + xml_parser = xml_parser_ptr; if (! stdin_parser) stdin_parser = textual_parser_ptr; - DEBUG_PRINT("ledger.config.cache", + DEBUG("ledger.config.cache", "3. use_cache = " << config.use_cache); if (! config.init_file.empty() && - access(config.init_file.c_str(), R_OK) != -1) { - if (parse_journal_file(config.init_file, config, journal) || + boost::filesystem::exists(config.init_file)) { + if (parse_journal_file(config.init_file.string(), config, journal) || journal->auto_entries.size() > 0 || journal->period_entries.size() > 0) - throw new error(std::string("Entries found in initialization file '") + - config.init_file + "'"); + throw new error(string("Entries found in initialization file '") + + string(config.init_file.string()) + "'"); journal->sources.pop_front(); // remove init file } if (config.use_cache && ! config.cache_file.empty() && ! config.data_file.empty()) { - DEBUG_PRINT("ledger.config.cache", + DEBUG("ledger.config.cache", "using_cache " << config.cache_file); config.cache_dirty = true; - if (access(config.cache_file.c_str(), R_OK) != -1) { - std::ifstream stream(config.cache_file.c_str()); + if (boost::filesystem::exists(config.cache_file)) { + boost::filesystem::ifstream stream(config.cache_file); if (cache_parser && cache_parser->test(stream)) { - std::string price_db_orig = journal->price_db; + path price_db_orig = journal->price_db; journal->price_db = config.price_db; entry_count += cache_parser->parse(stream, config, journal, NULL, &config.data_file); @@ -152,17 +146,17 @@ unsigned int parse_ledger_data(config_t& config, journal->price_db = config.price_db; if (! journal->price_db.empty() && - access(journal->price_db.c_str(), R_OK) != -1) { + boost::filesystem::exists(journal->price_db)) { if (parse_journal_file(journal->price_db, config, journal)) { throw new error("Entries not allowed in price history file"); } else { - DEBUG_PRINT("ledger.config.cache", + DEBUG("ledger.config.cache", "read price database " << journal->price_db); journal->sources.pop_back(); } } - DEBUG_PRINT("ledger.config.cache", + DEBUG("ledger.config.cache", "rejected cache, parsing " << config.data_file); if (config.data_file == "-") { config.use_cache = false; @@ -170,22 +164,20 @@ unsigned int parse_ledger_data(config_t& config, #if 0 // jww (2006-03-23): Why doesn't XML work on stdin? if (xml_parser && std::cin.peek() == '<') - entry_count += xml_parser->parse(std::cin, config, journal, - acct); + entry_count += xml_parser->parse(std::cin, config, journal, acct); else if (stdin_parser) #endif - entry_count += stdin_parser->parse(std::cin, config, - journal, acct); + entry_count += stdin_parser->parse(std::cin, config, journal, acct); } - else if (access(config.data_file.c_str(), R_OK) != -1) { - entry_count += parse_journal_file(config.data_file, config, - journal, acct); + else if (boost::filesystem::exists(config.data_file)) { + entry_count += parse_journal_file(config.data_file, config, journal, + acct); if (! journal->price_db.empty()) journal->sources.push_back(journal->price_db); } } - VALIDATE(journal->valid()); + VERIFY(journal->valid()); return entry_count; } @@ -1,10 +1,7 @@ #ifndef _PARSER_H #define _PARSER_H -#include <iostream> -#include <string> - -#include "error.h" +#include "utils.h" namespace ledger { @@ -19,27 +16,39 @@ class parser_t virtual bool test(std::istream& in) const = 0; - virtual unsigned int parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL) = 0; + virtual unsigned int parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master = NULL, + const path * original_file = NULL) = 0; +}; + +class binary_parser_t : public parser_t +{ +public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master = NULL, + const path * original_file = NULL); }; bool register_parser(parser_t * parser); bool unregister_parser(parser_t * parser); -unsigned int parse_journal(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); +unsigned int parse_journal(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master = NULL, + const path * original_file = NULL); -unsigned int parse_journal_file(const std::string& path, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); +unsigned int parse_journal_file(const path& path, + config_t& config, + journal_t * journal, + account_t * master = NULL, + const path * original_file = NULL); unsigned int parse_ledger_data(config_t& config, journal_t * journal, @@ -52,11 +61,90 @@ void shutdown_parser_support(); class parse_error : public error { public: - parse_error(const std::string& reason, error_context * ctxt = NULL) throw() + parse_error(const string& reason, error_context * ctxt = NULL) throw() : error(reason, ctxt) {} 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 <typename T> +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 <boost/python/exception_translator.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/args.hpp> + +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<amount_t> py_value_0(const amount_t& amount) { + return amount.value(); +} +boost::optional<amount_t> py_value_1(const amount_t& amount, + const boost::optional<moment_t>& 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<PyFileObject *>(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<PyFileObject *>(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<reference_existing_object>())) + + .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<double>()) + .def(init<long>()) + .def(init<std::string>()) + + .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<amount_t>()) + + .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<reference_existing_object>()) + .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<reference_existing_object>()) + + .def("unreduce", &amount_t::unreduce) + .def("in_place_unreduce", &amount_t::in_place_unreduce, + return_value_policy<reference_existing_object>()) + + .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<reference_existing_object>()), + 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<amount_t>(); + + implicitly_convertible<double, amount_t>(); + implicitly_convertible<long, amount_t>(); + implicitly_convertible<string, amount_t>(); + +#define EXC_TRANSLATE(type) \ + register_exception_translator<type>(&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 <boost/python/exception_translator.hpp> +#include <boost/python/implicit.hpp> + +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 <boost/cast.hpp> +#include <boost/python/module.hpp> +#include <boost/python/def.hpp> +#include <boost/python/to_python_converter.hpp> + +#include <Python.h> +#include <datetime.h> + +// 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, date_to_python, date_from_python> + 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<moment_t, datetime_to_python, datetime_from_python> + datetime_python_conversion; + +void export_times() +{ + date_python_conversion(); + datetime_python_conversion(); + + register_optional_to_python<moment_t>(); +} + +} // 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 <boost/python/module.hpp> +#include <boost/python/def.hpp> +#include <boost/python/to_python_converter.hpp> + +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<bool>*) 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, bool_to_python, bool_from_python> + bool_python_conversion; + + +struct string_to_python +{ + static PyObject* convert(const string& str) + { + return incref(object(*boost::polymorphic_downcast<const std::string *>(&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<string>*) data)->storage.bytes; + new (storage) string(value); + data->convertible = storage; + } +}; + +typedef register_python_conversion<string, string_to_python, string_from_python> + 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<pyifstream>*) data)->storage.bytes; + new (storage) pyifstream(reinterpret_cast<PyFileObject *>(obj_ptr)); + data->convertible = storage; + } +}; + +typedef register_python_conversion<std::istream, istream_to_python, istream_from_python> + 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<pyofstream>*) data)->storage.bytes; + new (storage) pyofstream(reinterpret_cast<PyFileObject *>(obj_ptr)); + data->convertible = storage; + } +}; + +typedef register_python_conversion<std::ostream, ostream_to_python, ostream_from_python> + 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 <boost/python/module_init.hpp> + +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<value_t>(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<value_t>(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<value_t>(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<value_t>(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..897cf1d0 --- /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 "valexpr.h" + +#include <boost/python.hpp> +#include <Python.h> + +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("<lambda>", 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 <pyledger.h> + +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 <johnw@newartisans.com> +// + +#include <ledger.h> +#include <pyinterp.h> + +#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 <typename T, typename TfromPy> +struct object_from_python +{ + object_from_python() { + boost::python::converter::registry::push_back + (&TfromPy::convertible, &TfromPy::construct, + boost::python::type_id<T>()); + } +}; + +template <typename T, typename TtoPy, typename TfromPy> +struct register_python_conversion +{ + register_python_conversion() { + boost::python::to_python_converter<T, TtoPy>(); + object_from_python<T, TfromPy>(); + } +}; + +template <typename T> +struct register_optional_to_python : public boost::noncopyable +{ + struct optional_to_python + { + static PyObject * convert(const boost::optional<T>& value) + { + return boost::python::incref + (value ? boost::python::to_python_value<T>()(*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<T>::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<T> *) data)->storage.bytes; + + if (data->convertible == source) // == None + new (storage) boost::optional<T>(); // A Boost uninitialized value + else + new (storage) boost::optional<T>(*static_cast<T *>(data->convertible)); + + data->convertible = storage; + } + }; + + explicit register_optional_to_python() { + register_python_conversion<boost::optional<T>, + optional_to_python, optional_from_python>(); + } +}; + +//boost::python::register_ptr_to_python< boost::shared_ptr<Base> >(); + +#endif // _PY_UTILS_H @@ -1,18 +1,13 @@ #include "journal.h" #include "qif.h" -#include "datetime.h" -#include "error.h" -#include "util.h" - -#include <cstring> -#include <memory> +#include "utils.h" namespace ledger { #define MAX_LINE 1024 static char line[MAX_LINE + 1]; -static std::string path; +static path pathname; static unsigned int src_idx; static unsigned int linenum; @@ -38,11 +33,11 @@ bool qif_parser_t::test(std::istream& in) const std::strcmp(magic, "\r\n!T") == 0); } -unsigned int qif_parser_t::parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) +unsigned int qif_parser_t::parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master, + const path * original_file) { std::auto_ptr<entry_t> entry; std::auto_ptr<amount_t> amount; @@ -59,9 +54,9 @@ unsigned int qif_parser_t::parse(std::istream& in, xact = new transaction_t(master); entry->add_transaction(xact); - path = journal->sources.back(); - src_idx = journal->sources.size() - 1; - linenum = 1; + pathname = journal->sources.back(); + src_idx = journal->sources.size() - 1; + linenum = 1; istream_pos_type beg_pos = 0; unsigned long beg_line = 0; @@ -97,14 +92,14 @@ unsigned int qif_parser_t::parse(std::istream& in, std::strcmp(line, "Type:Cat") == 0 || std::strcmp(line, "Type:Class") == 0 || std::strcmp(line, "Type:Memorized") == 0) - throw new parse_error(std::string("QIF files of type ") + line + + throw new parse_error(string("QIF files of type ") + line + " are not supported."); break; case 'D': SET_BEG_POS_AND_LINE(); get_line(in); - entry->_date = line; + entry->_date = parse_datetime(line); break; case 'T': @@ -117,7 +112,7 @@ unsigned int qif_parser_t::parse(std::istream& in, unsigned char prec = xact->amount.commodity().precision(); if (! def_commodity) { - def_commodity = commodity_t::find_or_create("$"); + def_commodity = amount_t::current_pool->find_or_create("$"); assert(def_commodity); } xact->amount.set_commodity(*def_commodity); @@ -10,11 +10,11 @@ class qif_parser_t : public parser_t public: virtual bool test(std::istream& in) const; - virtual unsigned int parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); + virtual unsigned int parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master = NULL, + const path * original_file = NULL); }; } // namespace ledger @@ -17,24 +17,24 @@ void quotes_by_script::operator()(commodity_base_t& commodity, { DEBUG_CLASS("ledger.quotes.download"); - DEBUG_PRINT_("commodity: " << commodity.symbol); - DEBUG_PRINT_TIME_(datetime_t::now); - DEBUG_PRINT_TIME_(moment); - DEBUG_PRINT_TIME_(date); - DEBUG_PRINT_TIME_(last); + DEBUG_("commodity: " << commodity.symbol); + DEBUG_TIME_(current_moment); + DEBUG_TIME_(moment); + DEBUG_TIME_(date); + DEBUG_TIME_(last); if (commodity.history) - DEBUG_PRINT_TIME_(commodity.history->last_lookup); - DEBUG_PRINT_("pricing_leeway is " << pricing_leeway); + DEBUG_TIME_(commodity.history->last_lookup); + DEBUG_("pricing_leeway is " << pricing_leeway); if ((commodity.history && - (datetime_t::now - commodity.history->last_lookup) < pricing_leeway) || - (datetime_t::now - last) < pricing_leeway || + (current_moment - commodity.history->last_lookup) < pricing_leeway) || + (current_moment - last) < pricing_leeway || (price && moment > date && (moment - date) <= pricing_leeway)) return; using namespace std; - DEBUG_PRINT_("downloading quote for symbol " << commodity.symbol); + DEBUG_("downloading quote for symbol " << commodity.symbol); char buf[256]; buf[0] = '\0'; @@ -55,12 +55,12 @@ void quotes_by_script::operator()(commodity_base_t& commodity, char * p = strchr(buf, '\n'); if (p) *p = '\0'; - DEBUG_PRINT_("downloaded quote: " << buf); + DEBUG_("downloaded quote: " << buf); price.parse(buf); - commodity.add_price(datetime_t::now, price); + commodity.add_price(current_moment, price); - commodity.history->last_lookup = datetime_t::now; + commodity.history->last_lookup = current_moment; cache_dirty = true; if (price && ! price_db.empty()) { @@ -69,11 +69,11 @@ void quotes_by_script::operator()(commodity_base_t& commodity, #else ofstream database(price_db.c_str(), ios_base::out | ios_base::app); #endif - database << "P " << datetime_t::now.to_string("%Y/%m/%d %H:%M:%S") + database << "P " << current_moment.to_string("%Y/%m/%d %H:%M:%S") << " " << commodity.symbol << " " << price << endl; } } else { - throw new error(std::string("Failed to download price for '") + + throw new error(string("Failed to download price for '") + commodity.symbol + "' (command: \"getquote " + commodity.symbol + "\")"); } @@ -5,14 +5,14 @@ namespace ledger { -class quotes_by_script : public commodity_base_t::updater_t +class quotes_by_script : public commodity_t::base_t::updater_t { - std::string price_db; + string price_db; unsigned long pricing_leeway; bool& cache_dirty; public: - quotes_by_script(std::string _price_db, + quotes_by_script(path _price_db, unsigned long _pricing_leeway, bool& _cache_dirty) : price_db(_price_db), pricing_leeway(_pricing_leeway), diff --git a/reconcile.cc b/reconcile.cc index 5b6dba24..b92ff9f1 100644 --- a/reconcile.cc +++ b/reconcile.cc @@ -40,11 +40,10 @@ void reconcile_transactions::flush() transaction_t * first = NULL; transaction_t ** last_ptr = &first; - bool found_pending = false; for (transactions_list::iterator x = xacts.begin(); x != xacts.end(); x++) { - if (! cutoff || (*x)->date() < cutoff) { + if (! is_valid(cutoff) || (*x)->date() < cutoff) { switch ((*x)->state) { case transaction_t::CLEARED: cleared_balance += (*x)->amount; @@ -59,25 +58,25 @@ void reconcile_transactions::flush() } } - if (cleared_balance.type >= value_t::BALANCE) + if (cleared_balance.type() >= value_t::BALANCE) throw new error("Cannot reconcile accounts with multiple commodities"); cleared_balance.cast(value_t::AMOUNT); balance.cast(value_t::AMOUNT); - commodity_t& cb_comm = ((amount_t *) cleared_balance.data)->commodity(); - commodity_t& b_comm = ((amount_t *) balance.data)->commodity(); + commodity_t& cb_comm = cleared_balance.as_amount_lval().commodity(); + commodity_t& b_comm = balance.as_amount_lval().commodity(); balance -= cleared_balance; - if (balance.type >= value_t::BALANCE) - throw new error(std::string("Reconcile balance is not of the same commodity ('") + - b_comm.symbol() + "' != '" + cb_comm.symbol() + "')"); + if (balance.type() >= value_t::BALANCE) + throw new error(string("Reconcile balance is not of the same commodity ('") + + b_comm.symbol() + string("' != '") + cb_comm.symbol() + "')"); // If the amount to reconcile is the same as the pending balance, // then assume an exact match and return the results right away. - amount_t to_reconcile = *((amount_t *) balance.data); + amount_t& to_reconcile(balance.as_amount_lval()); pending_balance.cast(value_t::AMOUNT); - if (to_reconcile == *((amount_t *) pending_balance.data) || + if (to_reconcile == pending_balance.as_amount_lval() || search_for_balance(to_reconcile, &first, first)) { push_to_handler(first); } else { @@ -38,21 +38,21 @@ report_t::report_t() } void -report_t::regexps_to_predicate(const std::string& command, - std::list<std::string>::const_iterator begin, - std::list<std::string>::const_iterator end, +report_t::regexps_to_predicate(const string& command, + std::list<string>::const_iterator begin, + std::list<string>::const_iterator end, const bool account_regexp, const bool add_account_short_masks, const bool logical_and) { - std::string regexps[2]; + string regexps[2]; assert(begin != end); // Treat the remaining command-line arguments as regular // expressions, used for refining report results. - for (std::list<std::string>::const_iterator i = begin; + for (std::list<string>::const_iterator i = begin; i != end; i++) if ((*i)[0] == '-') { @@ -83,12 +83,12 @@ report_t::regexps_to_predicate(const std::string& command, predicate += "!"; } else if (add_account_short_masks) { - if (regexps[i].find(':') != std::string::npos || - regexps[i].find('.') != std::string::npos || - regexps[i].find('*') != std::string::npos || - regexps[i].find('+') != std::string::npos || - regexps[i].find('[') != std::string::npos || - regexps[i].find('(') != std::string::npos) { + if (regexps[i].find(':') != string::npos || + regexps[i].find('.') != string::npos || + regexps[i].find('*') != string::npos || + regexps[i].find('+') != string::npos || + regexps[i].find('[') != string::npos || + regexps[i].find('(') != string::npos) { show_subtotal = true; add_predicate = 1; } else { @@ -127,7 +127,7 @@ report_t::regexps_to_predicate(const std::string& command, } } -void report_t::process_options(const std::string& command, +void report_t::process_options(const string& command, strings_list::iterator arg, strings_list::iterator args_end) { @@ -158,7 +158,7 @@ void report_t::process_options(const std::string& command, // Treat the remaining command-line arguments as regular // expressions, used for refining report results. - std::list<std::string>::iterator i = arg; + std::list<string>::iterator i = arg; for (; i != args_end; i++) if (*i == "--") break; @@ -191,8 +191,8 @@ void report_t::process_options(const std::string& command, } } - DEBUG_PRINT("ledger.config.predicates", "Predicate: " << predicate); - DEBUG_PRINT("ledger.config.predicates", "Display P: " << display_predicate); + DEBUG("ledger.config.predicates", "Predicate: " << predicate); + DEBUG("ledger.config.predicates", "Display P: " << display_predicate); // Setup the values of %t and %T, used in format strings @@ -204,7 +204,7 @@ void report_t::process_options(const std::string& command, // Now setup the various formatting strings if (! date_output_format.empty()) - date_t::output_format = date_output_format; + output_time_format = date_output_format; amount_t::keep_price = keep_price; amount_t::keep_date = keep_date; @@ -215,7 +215,7 @@ void report_t::process_options(const std::string& command, } item_handler<transaction_t> * -report_t::chain_xact_handlers(const std::string& command, +report_t::chain_xact_handlers(const string& command, item_handler<transaction_t> * base_formatter, journal_t * journal, account_t * master, @@ -254,16 +254,16 @@ report_t::chain_xact_handlers(const std::string& command, // transactions which made up the total for that reported // transaction. if (! descend_expr.empty()) { - std::list<std::string> descend_exprs; + std::list<string> descend_exprs; - std::string::size_type beg = 0; - for (std::string::size_type pos = descend_expr.find(';'); - pos != std::string::npos; + string::size_type beg = 0; + for (string::size_type pos = descend_expr.find(';'); + pos != string::npos; beg = pos + 1, pos = descend_expr.find(';', beg)) - descend_exprs.push_back(std::string(descend_expr, beg, pos - beg)); - descend_exprs.push_back(std::string(descend_expr, beg)); + descend_exprs.push_back(string(descend_expr, beg, pos - beg)); + descend_exprs.push_back(string(descend_expr, beg)); - for (std::list<std::string>::reverse_iterator i = + for (std::list<string>::reverse_iterator i = descend_exprs.rbegin(); i != descend_exprs.rend(); i++) @@ -277,9 +277,9 @@ report_t::chain_xact_handlers(const std::string& command, // transactions which can be reconciled to a given balance // (calculated against the transactions which it receives). if (! reconcile_balance.empty()) { - datetime_t cutoff = datetime_t::now; + datetime_t cutoff = current_moment; if (! reconcile_date.empty()) - cutoff = reconcile_date; + cutoff = parse_datetime(reconcile_date); ptrs.push_back(formatter = new reconcile_transactions (formatter, value_t(reconcile_balance), cutoff)); @@ -2,7 +2,6 @@ #define _REPORT_H #include "ledger.h" -#include "timing.h" #include <iostream> #include <memory> @@ -13,21 +12,22 @@ namespace ledger { class report_t { public: - std::string output_file; - std::string predicate; - std::string secondary_predicate; - std::string display_predicate; - std::string report_period; - std::string report_period_sort; - std::string format_string; - std::string sort_string; - std::string amount_expr; - std::string total_expr; - std::string descend_expr; - std::string forecast_limit; - std::string reconcile_balance; - std::string reconcile_date; - std::string date_output_format; + path output_file; + + string predicate; + string secondary_predicate; + string display_predicate; + string report_period; + string report_period_sort; + string format_string; + string sort_string; + string amount_expr; + string total_expr; + string descend_expr; + string forecast_limit; + string reconcile_balance; + string reconcile_date; + string date_output_format; unsigned long budget_flags; @@ -55,19 +55,19 @@ class report_t report_t(); - void regexps_to_predicate(const std::string& command, - std::list<std::string>::const_iterator begin, - std::list<std::string>::const_iterator end, + void regexps_to_predicate(const string& command, + std::list<string>::const_iterator begin, + std::list<string>::const_iterator end, const bool account_regexp = false, const bool add_account_short_masks = false, const bool logical_and = true); - void process_options(const std::string& command, + void process_options(const string& command, strings_list::iterator arg, strings_list::iterator args_end); item_handler<transaction_t> * - chain_xact_handlers(const std::string& command, + chain_xact_handlers(const string& command, item_handler<transaction_t> * base_formatter, journal_t * journal, account_t * master, @@ -4,15 +4,16 @@ 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.1", - 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'], "pyledger.cc")], + define_macros = defines, libraries = libs)]) @@ -1,4 +1,8 @@ #include "ledger.h" +#include "xml.h" +#include "gnucash.h" +#include "qif.h" +#include "ofx.h" using namespace ledger; @@ -49,8 +53,11 @@ namespace { startup::~startup() { + // jww (2008-04-22): What about this? +#if 0 if (! ledger::do_cleanup) return; +#endif shutdown_parser_support(); } } diff --git a/system.hh b/system.hh new file mode 100644 index 00000000..388cf53d --- /dev/null +++ b/system.hh @@ -0,0 +1,166 @@ +/* + * 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 <algorithm> +#include <exception> +#include <stdexcept> +#include <iostream> +#include <streambuf> +#include <iomanip> +#include <fstream> +#include <sstream> +#include <iterator> +#include <list> +#include <map> +#include <memory> +#include <new> +#include <stack> +#include <string> +#include <vector> + +#if defined(__GNUG__) && __GNUG__ < 3 + +namespace std { + inline ostream & right (ostream & i) { + i.setf(i.right, i.adjustfield); + return i; + } + inline ostream & left (ostream & i) { + i.setf(i.left, i.adjustfield); + return i; + } +} + +typedef unsigned long istream_pos_type; +typedef unsigned long ostream_pos_type; + +#else // ! (defined(__GNUG__) && __GNUG__ < 3) + +typedef std::istream::pos_type istream_pos_type; +typedef std::ostream::pos_type ostream_pos_type; + +#endif + +#include <cassert> +#include <cctype> +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> + +#if defined __FreeBSD__ && __FreeBSD__ <= 4 +// FreeBSD has a broken isspace macro, so don't use it +#undef isspace(c) +#endif + +#include <sys/stat.h> + +#ifdef WIN32 +#include <io.h> +#else +#include <unistd.h> +#endif + +#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM) +#include <pwd.h> +#endif + +#if defined(HAVE_NL_LANGINFO) +#include <langinfo.h> +#endif + +#include <gmp.h> + +extern "C" { +#if defined(HAVE_EXPAT) +#include <expat.h> // expat XML parser +#elif defined(HAVE_XMLPARSE) +#include <xmlparse.h> // expat XML parser +#endif +} + +#if defined(HAVE_LIBOFX) +#include <libofx.h> +#endif + +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/any.hpp> +#include <boost/cast.hpp> +#include <boost/current_function.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/filesystem/convenience.hpp> +#include <boost/filesystem/exception.hpp> +#include <boost/filesystem/fstream.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/path.hpp> +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> +#include <boost/lambda/bind.hpp> +#include <boost/lambda/lambda.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/multi_index/hashed_index.hpp> +#include <boost/multi_index/key_extractors.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include <boost/multi_index/random_access_index.hpp> +#include <boost/multi_index/sequenced_index.hpp> +#include <boost/multi_index_container.hpp> +#include <boost/operators.hpp> +#include <boost/optional.hpp> +#include <boost/ptr_container/ptr_list.hpp> +#include <boost/ptr_container/ptr_vector.hpp> +#include <boost/regex.hpp> +#include <boost/variant.hpp> + +#endif // _SYSTEM_HH diff --git a/test/UnitTests.cc b/test/UnitTests.cc new file mode 100644 index 00000000..7f5d1333 --- /dev/null +++ b/test/UnitTests.cc @@ -0,0 +1,115 @@ +#include <cppunit/CompilerOutputter.h> +#include <cppunit/TestResult.h> +#include <cppunit/TestResultCollector.h> +#include <cppunit/TestRunner.h> +#include <cppunit/TextTestProgressListener.h> +#include <cppunit/BriefTestProgressListener.h> +#include <cppunit/XmlOutputter.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <stdexcept> +#include <fstream> + +#include "UnitTests.h" + + +// Create the CppUnit registry + +CPPUNIT_REGISTRY_ADD_TO_DEFAULT("Framework"); + +CPPUNIT_REGISTRY_ADD_TO_DEFAULT("numerics"); + +// Create a sample test, which acts both as a template, and a +// verification that the basic framework is functioning. + +class UnitTests : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE( UnitTests ); + CPPUNIT_TEST( testInitialization ); + CPPUNIT_TEST_SUITE_END(); + +public: + UnitTests() {} + virtual ~UnitTests() {} + + virtual void setUp() {} + virtual void tearDown() {} + + void testInitialization() { + assertEqual(std::string("Hello, world!"), + std::string("Hello, world!")); + } + +private: + UnitTests( const UnitTests © ); + void operator =( const UnitTests © ); +}; + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(UnitTests, "framework"); + + +// Create the various runners and commence running the tests! + +int main(int argc, char* argv[]) +{ + int index = 1; + + if (argc > index && std::string(argv[index]) == "--verify") { + ledger::verify_enabled = true; + index++; + } + + // Retreive test path from command line first argument. Default to + // "" which resolves to the top level suite. + std::string testPath = ((argc > index) ? std::string(argv[index]) : + std::string("")); + + // Create the event manager and test controller + CPPUNIT_NS::TestResult controller; + + // Add a listener that collects test results + CPPUNIT_NS::TestResultCollector result; + controller.addListener(&result); + + // Add a listener that print dots as test run. +#if 1 + CPPUNIT_NS::TextTestProgressListener progress; +#else + CPPUNIT_NS::BriefTestProgressListener progress; +#endif + controller.addListener(&progress); + + // Add the top suite to the test runner + CPPUNIT_NS::TestRunner runner; + runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest()); + try { + IF_VERIFY() + initialize_memory_tracing(); + + runner.run(controller, testPath); + + IF_VERIFY() + shutdown_memory_tracing(); + + // Print test in a compiler compatible format. + CPPUNIT_NS::CompilerOutputter outputter(&result, CPPUNIT_NS::stdCOut()); + outputter.write(); + +#if 0 + // Uncomment this for XML output + std::ofstream file("tests.xml"); + CPPUNIT_NS::XmlOutputter xml(&result, file); + xml.setStyleSheet("report.xsl"); + xml.write(); + file.close(); +#endif + } + catch (std::invalid_argument &e) { // Test path not resolved + CPPUNIT_NS::stdCOut() << "\n" + << "ERROR: " << e.what() + << "\n"; + return 0; + } + + return result.wasSuccessful() ? 0 : 1; +} diff --git a/test/UnitTests.h b/test/UnitTests.h new file mode 100644 index 00000000..e7027cf4 --- /dev/null +++ b/test/UnitTests.h @@ -0,0 +1,24 @@ +#ifndef _UNITTESTS_H +#define _UNITTESTS_H + +#include "ledger.h" + +using namespace ledger; + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/Exception.h> +#include <cppunit/Portability.h> + +#define assertDoublesEqual(x,y,z,w) CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(x,y,z,w) +#define assertEqual(x,y) CPPUNIT_ASSERT_EQUAL(x,y) +#define assertNotEqual(x,y) CPPUNIT_ASSERT((x) != (y)) +#define assertTrue(x) CPPUNIT_ASSERT(x) +#define assertFalse(x) CPPUNIT_ASSERT(! (x)) +#define assertValid(x) CPPUNIT_ASSERT((x).valid()) +#define assertEqualMessage(x,y,z) CPPUNIT_ASSERT_EQUAL_MESSAGE(x,y,z) +#define assertMessage(x,y) CPPUNIT_ASSERT_MESSAGE(x,y) +#define assertThrow(x,y) CPPUNIT_ASSERT_THROW(x,y) + +#define internalAmount(x) amount_t::exact(x) + +#endif /* _UNITTESTS_H */ diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/__init__.py diff --git a/test/numerics/t_amount.cc b/test/numerics/t_amount.cc new file mode 100644 index 00000000..a96c7e2f --- /dev/null +++ b/test/numerics/t_amount.cc @@ -0,0 +1,1558 @@ +#include "t_amount.h" + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AmountTestCase, "numerics"); + +void AmountTestCase::setUp() +{ + //ledger::set_session_context(&session); + + // Cause the display precision for dollars to be initialized to 2. + amount_t x1("$1.00"); + assertTrue(x1); + + amount_t::stream_fullstrings = true; // make reports from UnitTests accurate +} + +void AmountTestCase::tearDown() +{ + amount_t::stream_fullstrings = false; + + //ledger::set_session_context(); +} + +void AmountTestCase::testParser() +{ + amount_t x0; + amount_t x1; + amount_t x2; + amount_t x3; + amount_t x4(123.456); + amount_t x5(x4); + amount_t x6(x4); + amount_t x7(x4); + amount_t x8("$123.45"); + amount_t x9(x8); + amount_t x10(x8); + amount_t x11(x8); + amount_t x12("$100"); + + assertEqual(amount_t::precision_t(2), x12.commodity().precision()); + + string buf("$100..."); + std::istringstream input(buf); + amount_t x13; + x13.parse(input); + assertEqual(x12, x13); + + amount_t x14; + assertThrow(x14.parse("DM"), amount_error); + + amount_t x15("$1.000.000,00"); // parsing this switches us to European + + amount_t x16("$2000"); + assertEqual(string("$2.000,00"), x16.to_string()); + x16.parse("$2000,00"); + assertEqual(string("$2.000,00"), x16.to_string()); + + // Since European-ness is an additive quality, we must switch back + // to American-ness manually + x15.commodity().drop_flags(COMMODITY_STYLE_EUROPEAN); + + amount_t x17("$1,000,000.00"); // parsing this switches back to American + + amount_t x18("$2000"); + assertEqual(string("$2,000.00"), x18.to_string()); + x18.parse("$2,000"); + assertEqual(string("$2,000.00"), x18.to_string()); + + assertEqual(x15, x17); + + amount_t x19("EUR 1000"); + amount_t x20("EUR 1000"); + + assertEqual(string("EUR 1000"), x19.to_string()); + assertEqual(string("EUR 1000"), x20.to_string()); + + x1.parse("$100.0000", AMOUNT_PARSE_NO_MIGRATE); + assertEqual(amount_t::precision_t(2), x12.commodity().precision()); + assertEqual(x1.commodity(), x12.commodity()); + assertEqual(x1, x12); + + x0.parse("$100.0000"); + assertEqual(amount_t::precision_t(4), x12.commodity().precision()); + assertEqual(x0.commodity(), x12.commodity()); + assertEqual(x0, x12); + + x2.parse("$100.00", AMOUNT_PARSE_NO_REDUCE); + assertEqual(x2, x12); + x3.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE); + assertEqual(x3, x12); + + x4.parse("$100.00"); + assertEqual(x4, x12); + x5.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE); + assertEqual(x5, x12); + x6.parse("$100.00", AMOUNT_PARSE_NO_REDUCE); + assertEqual(x6, x12); + x7.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE); + assertEqual(x7, x12); + + x8.parse("$100.00"); + assertEqual(x8, x12); + x9.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE); + assertEqual(x9, x12); + x10.parse("$100.00", AMOUNT_PARSE_NO_REDUCE); + assertEqual(x10, x12); + x11.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE); + assertEqual(x11, x12); + + assertValid(x0); + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x5); + assertValid(x6); + assertValid(x7); + assertValid(x8); + assertValid(x9); + assertValid(x10); + assertValid(x11); + assertValid(x12); +} + +void AmountTestCase::testConstructors() +{ + amount_t x0; + amount_t x1(123456L); + amount_t x2(123456UL); + amount_t x3(123.456); + amount_t x5("123456"); + amount_t x6("123.456"); + amount_t x7(string("123456")); + amount_t x8(string("123.456")); + amount_t x9(x3); + amount_t x10(x6); + amount_t x11(x8); + + assertThrow(amount_t(0L) == x0, amount_error); + assertThrow(amount_t() == x0, amount_error); + assertThrow(amount_t("0") == x0, amount_error); + assertThrow(amount_t("0.0") == x0, amount_error); + assertEqual(x2, x1); + assertEqual(x5, x1); + assertEqual(x7, x1); + assertEqual(x6, x3); + assertEqual(x8, x3); + assertEqual(x10, x3); + assertEqual(x10, x9); + + assertValid(x0); + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x5); + assertValid(x6); + assertValid(x7); + assertValid(x8); + assertValid(x9); + assertValid(x10); + assertValid(x11); +} + +void AmountTestCase::testCommodityConstructors() +{ + amount_t x1("$123.45"); + amount_t x2("-$123.45"); + amount_t x3("$-123.45"); + amount_t x4("DM 123.45"); + amount_t x5("-DM 123.45"); + amount_t x6("DM -123.45"); + amount_t x7("123.45 euro"); + amount_t x8("-123.45 euro"); + amount_t x9("123.45€"); + amount_t x10("-123.45€"); + + assertEqual(amount_t("$123.45"), x1); + assertEqual(amount_t("-$123.45"), x2); + assertEqual(amount_t("$-123.45"), x3); + assertEqual(amount_t("DM 123.45"), x4); + assertEqual(amount_t("-DM 123.45"), x5); + assertEqual(amount_t("DM -123.45"), x6); + assertEqual(amount_t("123.45 euro"), x7); + assertEqual(amount_t("-123.45 euro"), x8); + assertEqual(amount_t("123.45€"), x9); + assertEqual(amount_t("-123.45€"), x10); + + assertEqual(string("$123.45"), x1.to_string()); + assertEqual(string("$-123.45"), x2.to_string()); + assertEqual(string("$-123.45"), x3.to_string()); + assertEqual(string("DM 123.45"), x4.to_string()); + assertEqual(string("DM -123.45"), x5.to_string()); + assertEqual(string("DM -123.45"), x6.to_string()); + assertEqual(string("123.45 euro"), x7.to_string()); + assertEqual(string("-123.45 euro"), x8.to_string()); + assertEqual(string("123.45€"), x9.to_string()); + assertEqual(string("-123.45€"), x10.to_string()); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); + assertValid(x7); + assertValid(x8); + assertValid(x9); + assertValid(x10); +} + +void AmountTestCase::testAssignment() +{ + amount_t x0; + amount_t x1; + amount_t x2; + amount_t x3; + amount_t x5; + amount_t x6; + amount_t x7; + amount_t x8; + amount_t x9; + amount_t x10; + + x1 = 123456L; + x2 = 123456UL; + x3 = 123.456; + x5 = "123456"; + x6 = "123.456"; + x7 = string("123456"); + x8 = string("123.456"); + x9 = x3; + x10 = amount_t(x6); + + assertEqual(x2, x1); + assertEqual(x5, x1); + assertEqual(x7, x1); + assertEqual(x6, x3); + assertEqual(x8, x3); + assertEqual(x10, x3); + assertEqual(x10, x9); + + assertFalse(x1.is_null()); + x1 = x0; // sets x1 back to uninitialized state + assertTrue(x0.is_null()); + assertTrue(x1.is_null()); + + assertValid(x0); + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x5); + assertValid(x6); + assertValid(x7); + assertValid(x8); + assertValid(x9); + assertValid(x10); +} + +void AmountTestCase::testCommodityAssignment() +{ + amount_t x1; + amount_t x2; + amount_t x3; + amount_t x4; + amount_t x5; + amount_t x6; + amount_t x7; + amount_t x8; + amount_t x9; + amount_t x10; + + x1 = "$123.45"; + x2 = "-$123.45"; + x3 = "$-123.45"; + x4 = "DM 123.45"; + x5 = "-DM 123.45"; + x6 = "DM -123.45"; + x7 = "123.45 euro"; + x8 = "-123.45 euro"; + x9 = "123.45€"; + x10 = "-123.45€"; + + assertEqual(amount_t("$123.45"), x1); + assertEqual(amount_t("-$123.45"), x2); + assertEqual(amount_t("$-123.45"), x3); + assertEqual(amount_t("DM 123.45"), x4); + assertEqual(amount_t("-DM 123.45"), x5); + assertEqual(amount_t("DM -123.45"), x6); + assertEqual(amount_t("123.45 euro"), x7); + assertEqual(amount_t("-123.45 euro"), x8); + assertEqual(amount_t("123.45€"), x9); + assertEqual(amount_t("-123.45€"), x10); + + assertEqual(string("$123.45"), x1.to_string()); + assertEqual(string("$-123.45"), x2.to_string()); + assertEqual(string("$-123.45"), x3.to_string()); + assertEqual(string("DM 123.45"), x4.to_string()); + assertEqual(string("DM -123.45"), x5.to_string()); + assertEqual(string("DM -123.45"), x6.to_string()); + assertEqual(string("123.45 euro"), x7.to_string()); + assertEqual(string("-123.45 euro"), x8.to_string()); + assertEqual(string("123.45€"), x9.to_string()); + assertEqual(string("-123.45€"), x10.to_string()); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); + assertValid(x7); + assertValid(x8); + assertValid(x9); + assertValid(x10); +} + +void AmountTestCase::testEquality() +{ + amount_t x1(123456L); + amount_t x2(456789L); + amount_t x3(333333L); + amount_t x4(123456.0); + amount_t x5("123456.0"); + amount_t x6(123456.0F); + + assertTrue(x1 == 123456L); + assertTrue(x1 != x2); + assertTrue(x1 == (x2 - x3)); + assertTrue(x1 == x4); + assertTrue(x4 == x5); + assertTrue(x4 == x6); + + assertTrue(x1 == 123456L); + assertTrue(123456L == x1); + assertTrue(x1 == 123456UL); + assertTrue(123456UL == x1); + assertTrue(x1 == 123456.0); + assertTrue(123456.0 == x1); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); +} + +void AmountTestCase::testCommodityEquality() +{ + amount_t x0; + amount_t x1; + amount_t x2; + amount_t x3; + amount_t x4; + amount_t x5; + amount_t x6; + amount_t x7; + amount_t x8; + amount_t x9; + amount_t x10; + + x1 = "$123.45"; + x2 = "-$123.45"; + x3 = "$-123.45"; + x4 = "DM 123.45"; + x5 = "-DM 123.45"; + x6 = "DM -123.45"; + x7 = "123.45 euro"; + x8 = "-123.45 euro"; + x9 = "123.45€"; + x10 = "-123.45€"; + + assertTrue(x0.is_null()); + assertThrow(x0.is_zero(), amount_error); + assertThrow(x0.is_realzero(), amount_error); + assertThrow(assert(x0.sign() == 0), amount_error); + assertThrow(assert(x0.compare(x1) < 0), amount_error); + assertThrow(assert(x0.compare(x2) > 0), amount_error); + assertThrow(assert(x0.compare(x0) == 0), amount_error); + + assertTrue(x1 != x2); + assertTrue(x1 != x4); + assertTrue(x1 != x7); + assertTrue(x1 != x9); + assertTrue(x2 == x3); + assertTrue(x4 != x5); + assertTrue(x5 == x6); + assertTrue(x7 == - x8); + assertTrue(x9 == - x10); + + assertValid(x0); + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); + assertValid(x7); + assertValid(x8); + assertValid(x9); + assertValid(x10); +} + +void AmountTestCase::testComparisons() +{ + amount_t x0; + amount_t x1(-123L); + amount_t x2(123L); + amount_t x3(-123.45); + amount_t x4(123.45); + amount_t x5("-123.45"); + amount_t x6("123.45"); + + assertThrow(x0 > x1, amount_error); + assertThrow(x0 < x2, amount_error); + assertThrow(x0 > x3, amount_error); + assertThrow(x0 < x4, amount_error); + assertThrow(x0 > x5, amount_error); + assertThrow(x0 < x6, amount_error); + + assertTrue(x1 > x3); + assertTrue(x3 <= x5); + assertTrue(x3 >= x5); + assertTrue(x3 < x1); + assertTrue(x3 < x4); + + assertTrue(x1 < 100L); + assertTrue(100L > x1); + assertTrue(x1 < 100UL); + assertTrue(100UL > x1); + assertTrue(x1 < 100.0); + assertTrue(100.0 > x1); + + assertValid(x0); + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); +} + +void AmountTestCase::testCommodityComparisons() +{ + amount_t x1("$-123"); + amount_t x2("$123.00"); + amount_t x3(internalAmount("$-123.4544")); + amount_t x4(internalAmount("$123.4544")); + amount_t x5("$-123.45"); + amount_t x6("$123.45"); + amount_t x7("DM 123.45"); + + assertTrue(x1 > x3); + assertTrue(x3 <= x5); + assertTrue(x3 < x5); + assertTrue(x3 <= x5); + assertFalse(x3 == x5); + assertTrue(x3 < x1); + assertTrue(x3 < x4); + assertFalse(x6 == x7); + assertThrow(x6 < x7, amount_error); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); +} + +void AmountTestCase::testIntegerAddition() +{ + amount_t x0; + amount_t x1(123L); + amount_t y1(456L); + + assertEqual(amount_t(579L), x1 + y1); + assertEqual(amount_t(579L), x1 + 456L); + assertEqual(amount_t(579L), 456L + x1); + + x1 += amount_t(456L); + assertEqual(amount_t(579L), x1); + x1 += 456L; + assertEqual(amount_t(1035L), x1); + + amount_t x4("123456789123456789123456789"); + + assertEqual(amount_t("246913578246913578246913578"), x4 + x4); + + assertValid(x0); + assertValid(x1); + assertValid(y1); + assertValid(x4); +} + +void AmountTestCase::testFractionalAddition() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertEqual(amount_t(579.579), x1 + y1); + assertEqual(amount_t(579.579), x1 + 456.456); + assertEqual(amount_t(579.579), 456.456 + x1); + + x1 += amount_t(456.456); + assertEqual(amount_t(579.579), x1); + x1 += 456.456; + assertEqual(amount_t(1036.035), x1); + x1 += 456L; + assertEqual(amount_t(1492.035), x1); + + amount_t x2("123456789123456789.123456789123456789"); + + assertEqual(amount_t("246913578246913578.246913578246913578"), x2 + x2); + + assertValid(x1); + assertValid(y1); + assertValid(x2); +} + +void AmountTestCase::testCommodityAddition() +{ + amount_t x0; + amount_t x1("$123.45"); + amount_t x2(internalAmount("$123.456789")); + amount_t x3("DM 123.45"); + amount_t x4("123.45 euro"); + amount_t x5("123.45€"); + amount_t x6("123.45"); + + assertEqual(amount_t("$246.90"), x1 + x1); + assertNotEqual(amount_t("$246.91"), x1 + x2); + assertEqual(internalAmount("$246.906789"), x1 + x2); + + // Converting to string drops internal precision + assertEqual(string("$246.90"), (x1 + x1).to_string()); + assertEqual(string("$246.91"), (x1 + x2).to_string()); + + assertThrow(x1 + x0, amount_error); + assertThrow(x0 + x1, amount_error); + assertThrow(x0 + x0, amount_error); + assertThrow(x1 + x3, amount_error); + assertThrow(x1 + x4, amount_error); + assertThrow(x1 + x5, amount_error); + assertThrow(x1 + x6, amount_error); + assertThrow(x1 + 123.45, amount_error); + assertThrow(x1 + 123L, amount_error); + + assertEqual(amount_t("DM 246.90"), x3 + x3); + assertEqual(amount_t("246.90 euro"), x4 + x4); + assertEqual(amount_t("246.90€"), x5 + x5); + + assertEqual(string("DM 246.90"), (x3 + x3).to_string()); + assertEqual(string("246.90 euro"), (x4 + x4).to_string()); + assertEqual(string("246.90€"), (x5 + x5).to_string()); + + x1 += amount_t("$456.45"); + assertEqual(amount_t("$579.90"), x1); + x1 += amount_t("$456.45"); + assertEqual(amount_t("$1036.35"), x1); + x1 += amount_t("$456"); + assertEqual(amount_t("$1492.35"), x1); + + amount_t x7(internalAmount("$123456789123456789.123456789123456789")); + + assertEqual(internalAmount("$246913578246913578.246913578246913578"), x7 + x7); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); + assertValid(x7); +} + +void AmountTestCase::testIntegerSubtraction() +{ + amount_t x1(123L); + amount_t y1(456L); + + assertEqual(amount_t(333L), y1 - x1); + assertEqual(amount_t(-333L), x1 - y1); + assertEqual(amount_t(23L), x1 - 100L); + assertEqual(amount_t(-23L), 100L - x1); + + x1 -= amount_t(456L); + assertEqual(amount_t(-333L), x1); + x1 -= 456L; + assertEqual(amount_t(-789L), x1); + + amount_t x4("123456789123456789123456789"); + amount_t y4("8238725986235986"); + + assertEqual(amount_t("123456789115218063137220803"), x4 - y4); + assertEqual(amount_t("-123456789115218063137220803"), y4 - x4); + + assertValid(x1); + assertValid(y1); + assertValid(x4); + assertValid(y4); +} + +void AmountTestCase::testFractionalSubtraction() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertEqual(amount_t(-333.333), x1 - y1); + assertEqual(amount_t(333.333), y1 - x1); + + x1 -= amount_t(456.456); + assertEqual(amount_t(-333.333), x1); + x1 -= 456.456; + assertEqual(amount_t(-789.789), x1); + x1 -= 456L; + assertEqual(amount_t(-1245.789), x1); + + amount_t x2("123456789123456789.123456789123456789"); + amount_t y2("9872345982459.248974239578"); + + assertEqual(amount_t("123446916777474329.874482549545456789"), x2 - y2); + assertEqual(amount_t("-123446916777474329.874482549545456789"), y2 - x2); + + assertValid(x1); + assertValid(y1); + assertValid(x2); + assertValid(y2); +} + +void AmountTestCase::testCommoditySubtraction() +{ + amount_t x0; + amount_t x1("$123.45"); + amount_t x2(internalAmount("$123.456789")); + amount_t x3("DM 123.45"); + amount_t x4("123.45 euro"); + amount_t x5("123.45€"); + amount_t x6("123.45"); + + assertNotEqual(amount_t(), x1 - x1); + assertEqual(amount_t("$0"), x1 - x1); + assertEqual(amount_t("$23.45"), x1 - amount_t("$100.00")); + assertEqual(amount_t("$-23.45"), amount_t("$100.00") - x1); + assertNotEqual(amount_t("$-0.01"), x1 - x2); + assertEqual(internalAmount("$-0.006789"), x1 - x2); + + // Converting to string drops internal precision. If an amount is + // zero, it drops the commodity as well. + assertEqual(string("$0.00"), (x1 - x1).to_string()); + assertEqual(string("$-0.01"), (x1 - x2).to_string()); + + assertThrow(x1 - x0, amount_error); + assertThrow(x0 - x1, amount_error); + assertThrow(x0 - x0, amount_error); + assertThrow(x1 - x3, amount_error); + assertThrow(x1 - x4, amount_error); + assertThrow(x1 - x5, amount_error); + assertThrow(x1 - x6, amount_error); + assertThrow(x1 - 123.45, amount_error); + assertThrow(x1 - 123L, amount_error); + + assertEqual(amount_t("DM 0.00"), x3 - x3); + assertEqual(amount_t("DM 23.45"), x3 - amount_t("DM 100.00")); + assertEqual(amount_t("DM -23.45"), amount_t("DM 100.00") - x3); + assertEqual(amount_t("0.00 euro"), x4 - x4); + assertEqual(amount_t("23.45 euro"), x4 - amount_t("100.00 euro")); + assertEqual(amount_t("-23.45 euro"), amount_t("100.00 euro") - x4); + assertEqual(amount_t("0.00€"), x5 - x5); + assertEqual(amount_t("23.45€"), x5 - amount_t("100.00€")); + assertEqual(amount_t("-23.45€"), amount_t("100.00€") - x5); + + assertEqual(string("DM 0.00"), (x3 - x3).to_string()); + assertEqual(string("DM 23.45"), (x3 - amount_t("DM 100.00")).to_string()); + assertEqual(string("DM -23.45"), (amount_t("DM 100.00") - x3).to_string()); + assertEqual(string("0.00 euro"), (x4 - x4).to_string()); + assertEqual(string("23.45 euro"), (x4 - amount_t("100.00 euro")).to_string()); + assertEqual(string("-23.45 euro"), (amount_t("100.00 euro") - x4).to_string()); + assertEqual(string("0.00€"), (x5 - x5).to_string()); + assertEqual(string("23.45€"), (x5 - amount_t("100.00€")).to_string()); + assertEqual(string("-23.45€"), (amount_t("100.00€") - x5).to_string()); + + x1 -= amount_t("$456.45"); + assertEqual(amount_t("$-333.00"), x1); + x1 -= amount_t("$456.45"); + assertEqual(amount_t("$-789.45"), x1); + x1 -= amount_t("$456"); + assertEqual(amount_t("$-1245.45"), x1); + + amount_t x7(internalAmount("$123456789123456789.123456789123456789")); + amount_t x8(internalAmount("$2354974984698.98459845984598")); + + assertEqual(internalAmount("$123454434148472090.138858329277476789"), x7 - x8); + assertEqual(string("$123454434148472090.138858329277476789"), (x7 - x8).to_string()); + assertEqual(string("$123454434148472090.14"), + (amount_t("$1.00") * (x7 - x8)).to_string()); + assertEqual(internalAmount("$-123454434148472090.138858329277476789"), x8 - x7); + assertEqual(string("$-123454434148472090.138858329277476789"), (x8 - x7).to_string()); + assertEqual(string("$-123454434148472090.14"), + (amount_t("$1.00") * (x8 - x7)).to_string()); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); + assertValid(x7); + assertValid(x8); +} + +void AmountTestCase::testIntegerMultiplication() +{ + amount_t x1(123L); + amount_t y1(456L); + + assertEqual(amount_t(0L), x1 * 0L); + assertEqual(amount_t(0L), amount_t(0L) * x1); + assertEqual(amount_t(0L), 0L * x1); + assertEqual(x1, x1 * 1L); + assertEqual(x1, amount_t(1L) * x1); + assertEqual(x1, 1L * x1); + assertEqual(- x1, x1 * -1L); + assertEqual(- x1, amount_t(-1L) * x1); + assertEqual(- x1, -1L * x1); + assertEqual(amount_t(56088L), x1 * y1); + assertEqual(amount_t(56088L), y1 * x1); + assertEqual(amount_t(56088L), x1 * 456L); + assertEqual(amount_t(56088L), amount_t(456L) * x1); + assertEqual(amount_t(56088L), 456L * x1); + + x1 *= amount_t(123L); + assertEqual(amount_t(15129L), x1); + x1 *= 123L; + assertEqual(amount_t(1860867L), x1); + + amount_t x4("123456789123456789123456789"); + + assertEqual(amount_t("15241578780673678546105778281054720515622620750190521"), + x4 * x4); + + assertValid(x1); + assertValid(y1); + assertValid(x4); +} + +void AmountTestCase::testFractionalMultiplication() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertEqual(amount_t(0L), x1 * 0L); + assertEqual(amount_t(0L), amount_t(0L) * x1); + assertEqual(amount_t(0L), 0L * x1); + assertEqual(x1, x1 * 1L); + assertEqual(x1, amount_t(1L) * x1); + assertEqual(x1, 1L * x1); + assertEqual(- x1, x1 * -1L); + assertEqual(- x1, amount_t(-1L) * x1); + assertEqual(- x1, -1L * x1); + assertEqual(amount_t("56200.232088"), x1 * y1); + assertEqual(amount_t("56200.232088"), y1 * x1); + assertEqual(amount_t("56200.232088"), x1 * 456.456); + assertEqual(amount_t("56200.232088"), amount_t(456.456) * x1); + assertEqual(amount_t("56200.232088"), 456.456 * x1); + + x1 *= amount_t(123.123); + assertEqual(amount_t("15159.273129"), x1); + x1 *= 123.123; + assertEqual(amount_t("1866455.185461867"), x1); + x1 *= 123L; + assertEqual(amount_t("229573987.811809641"), x1); + + amount_t x2("123456789123456789.123456789123456789"); + + assertEqual(amount_t("15241578780673678546105778311537878.046486820281054720515622620750190521"), + x2 * x2); + + assertValid(x1); + assertValid(y1); + assertValid(x2); +} + +void AmountTestCase::testCommodityMultiplication() +{ + amount_t x0; + amount_t x1("$123.12"); + amount_t y1("$456.45"); + amount_t x2(internalAmount("$123.456789")); + amount_t x3("DM 123.45"); + amount_t x4("123.45 euro"); + amount_t x5("123.45€"); + + assertEqual(amount_t("$0.00"), x1 * 0L); + assertEqual(amount_t("$0.00"), 0L * x1); + assertEqual(x1, x1 * 1L); + assertEqual(x1, 1L * x1); + assertEqual(- x1, x1 * -1L); + assertEqual(- x1, -1L * x1); + assertEqual(internalAmount("$56198.124"), x1 * y1); + assertEqual(string("$56198.12"), (x1 * y1).to_string()); + assertEqual(internalAmount("$56198.124"), y1 * x1); + assertEqual(string("$56198.12"), (y1 * x1).to_string()); + + // Internal amounts retain their precision, even when being + // converted to strings + assertEqual(internalAmount("$15199.99986168"), x1 * x2); + assertEqual(internalAmount("$15199.99986168"), x2 * x1); + assertEqual(string("$15200.00"), (x1 * x2).to_string()); + assertEqual(string("$15199.99986168"), (x2 * x1).to_string()); + + assertThrow(x1 * x0, amount_error); + assertThrow(x0 * x1, amount_error); + assertThrow(x0 * x0, amount_error); + assertThrow(x1 * x3, amount_error); + assertThrow(x1 * x4, amount_error); + assertThrow(x1 * x5, amount_error); + + x1 *= amount_t("123.12"); + assertEqual(internalAmount("$15158.5344"), x1); + assertEqual(string("$15158.53"), x1.to_string()); + x1 *= 123.12; + assertEqual(internalAmount("$1866318.755328"), x1); + assertEqual(string("$1866318.76"), x1.to_string()); + x1 *= 123L; + assertEqual(internalAmount("$229557206.905344"), x1); + assertEqual(string("$229557206.91"), x1.to_string()); + + amount_t x7(internalAmount("$123456789123456789.123456789123456789")); + + assertEqual(internalAmount("$15241578780673678546105778311537878.046486820281054720515622620750190521"), + x7 * x7); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x7); +} + +void AmountTestCase::testIntegerDivision() +{ + amount_t x1(123L); + amount_t y1(456L); + + assertThrow(x1 / 0L, amount_error); + assertEqual(amount_t(0L), amount_t(0L) / x1); + assertEqual(amount_t(0L), 0L / x1); + assertEqual(x1, x1 / 1L); + assertEqual(amount_t("0.008130"), amount_t(1L) / x1); + assertEqual(amount_t("0.008130"), 1L / x1); + assertEqual(- x1, x1 / -1L); + assertEqual(- amount_t("0.008130"), amount_t(-1L) / x1); + assertEqual(- amount_t("0.008130"), -1L / x1); + assertEqual(amount_t("0.269737"), x1 / y1); + assertEqual(amount_t("3.707317"), y1 / x1); + assertEqual(amount_t("0.269737"), x1 / 456L); + assertEqual(amount_t("3.707317"), amount_t(456L) / x1); + assertEqual(amount_t("3.707317"), 456L / x1); + + x1 /= amount_t(456L); + assertEqual(amount_t("0.269737"), x1); + x1 /= 456L; + assertEqual(amount_t("0.00059152850877193"), x1); + + amount_t x4("123456789123456789123456789"); + amount_t y4("56"); + + assertEqual(amount_t(1L), x4 / x4); + assertEqual(amount_t("2204585520061728377204585.517857"), x4 / y4); + + assertValid(x1); + assertValid(y1); + assertValid(x4); + assertValid(y4); +} + +void AmountTestCase::testFractionalDivision() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertThrow(x1 / 0L, amount_error); + assertEqual(amount_t("0.008121959"), amount_t(1.0) / x1); + assertEqual(amount_t("0.008121959"), 1.0 / x1); + assertEqual(x1, x1 / 1.0); + assertEqual(amount_t("0.008121959"), amount_t(1.0) / x1); + assertEqual(amount_t("0.008121959"), 1.0 / x1); + assertEqual(- x1, x1 / -1.0); + assertEqual(- amount_t("0.008121959"), amount_t(-1.0) / x1); + assertEqual(- amount_t("0.008121959"), -1.0 / x1); + assertEqual(amount_t("0.269736842105263"), x1 / y1); + assertEqual(amount_t("3.707317073170732"), y1 / x1); + assertEqual(amount_t("0.269736842105263"), x1 / 456.456); + assertEqual(amount_t("3.707317073170732"), amount_t(456.456) / x1); + assertEqual(amount_t("3.707317073170732"), 456.456 / x1); + + x1 /= amount_t(456.456); + assertEqual(amount_t("0.269736842105263"), x1); + x1 /= 456.456; + assertEqual(amount_t("0.000590937225286255411255411255411255411"), x1); + x1 /= 456L; + assertEqual(amount_t("0.000001295914967733016252753094858358016252192982456140350877192982456140350877192982"), x1); + + amount_t x4("1234567891234567.89123456789"); + amount_t y4("56.789"); + + assertEqual(amount_t(1.0), x4 / x4); + assertEqual(amount_t("21739560323910.7554497273748437197344556164046"), x4 / y4); + + assertValid(x1); + assertValid(y1); + assertValid(x4); + assertValid(y4); +} + +void AmountTestCase::testCommodityDivision() +{ + amount_t x0; + amount_t x1("$123.12"); + amount_t y1("$456.45"); + amount_t x2(internalAmount("$123.456789")); + amount_t x3("DM 123.45"); + amount_t x4("123.45 euro"); + amount_t x5("123.45€"); + + assertThrow(x1 / 0L, amount_error); + assertEqual(amount_t("$0.00"), 0L / x1); + assertEqual(x1, x1 / 1L); + assertEqual(internalAmount("$0.00812216"), 1L / x1); + assertEqual(- x1, x1 / -1L); + assertEqual(internalAmount("$-0.00812216"), -1L / x1); + assertEqual(internalAmount("$0.26973382"), x1 / y1); + assertEqual(string("$0.27"), (x1 / y1).to_string()); + assertEqual(internalAmount("$3.70735867"), y1 / x1); + assertEqual(string("$3.71"), (y1 / x1).to_string()); + + // Internal amounts retain their precision, even when being + // converted to strings + assertEqual(internalAmount("$0.99727201"), x1 / x2); + assertEqual(internalAmount("$1.00273545321637426901"), x2 / x1); + assertEqual(string("$1.00"), (x1 / x2).to_string()); + assertEqual(string("$1.00273545321637426901"), (x2 / x1).to_string()); + + assertThrow(x1 / x0, amount_error); + assertThrow(x0 / x1, amount_error); + assertThrow(x0 / x0, amount_error); + assertThrow(x1 / x3, amount_error); + assertThrow(x1 / x4, amount_error); + assertThrow(x1 / x5, amount_error); + + x1 /= amount_t("123.12"); + assertEqual(internalAmount("$1.00"), x1); + assertEqual(string("$1.00"), x1.to_string()); + x1 /= 123.12; + assertEqual(internalAmount("$0.00812216"), x1); + assertEqual(string("$0.01"), x1.to_string()); + x1 /= 123L; + assertEqual(internalAmount("$0.00006603"), x1); + assertEqual(string("$0.00"), x1.to_string()); + + amount_t x6(internalAmount("$237235987235987.98723987235978")); + amount_t x7(internalAmount("$123456789123456789.123456789123456789")); + + assertEqual(amount_t("$1"), x7 / x7); + assertEqual(internalAmount("$0.0019216115121765559608381226612019501046413574469262"), + x6 / x7); + assertEqual(internalAmount("$520.39654928343335571379527154924040947271699678158689736256"), + x7 / x6); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); + assertValid(x7); +} + +void AmountTestCase::testNegation() +{ + amount_t x0; + amount_t x1(-123456L); + amount_t x3(-123.456); + amount_t x5("-123456"); + amount_t x6("-123.456"); + amount_t x7(string("-123456")); + amount_t x8(string("-123.456")); + amount_t x9(- x3); + + assertThrow(x0.negate(), amount_error); + assertEqual(x5, x1); + assertEqual(x7, x1); + assertEqual(x6, x3); + assertEqual(x8, x3); + assertEqual(- x6, x9); + assertEqual(x3.negate(), x9); + + amount_t x10(x9.negate()); + + assertEqual(x3, x10); + + assertValid(x1); + assertValid(x3); + assertValid(x5); + assertValid(x6); + assertValid(x7); + assertValid(x8); + assertValid(x9); + assertValid(x10); +} + +void AmountTestCase::testCommodityNegation() +{ + amount_t x1("$123.45"); + amount_t x2("-$123.45"); + amount_t x3("$-123.45"); + amount_t x4("DM 123.45"); + amount_t x5("-DM 123.45"); + amount_t x6("DM -123.45"); + amount_t x7("123.45 euro"); + amount_t x8("-123.45 euro"); + amount_t x9("123.45€"); + amount_t x10("-123.45€"); + + assertEqual(amount_t("$-123.45"), - x1); + assertEqual(amount_t("$123.45"), - x2); + assertEqual(amount_t("$123.45"), - x3); + assertEqual(amount_t("DM -123.45"), - x4); + assertEqual(amount_t("DM 123.45"), - x5); + assertEqual(amount_t("DM 123.45"), - x6); + assertEqual(amount_t("-123.45 euro"), - x7); + assertEqual(amount_t("123.45 euro"), - x8); + assertEqual(amount_t("-123.45€"), - x9); + assertEqual(amount_t("123.45€"), - x10); + + assertEqual(amount_t("$-123.45"), x1.negate()); + assertEqual(amount_t("$123.45"), x2.negate()); + assertEqual(amount_t("$123.45"), x3.negate()); + + assertEqual(string("$-123.45"), (- x1).to_string()); + assertEqual(string("$123.45"), (- x2).to_string()); + assertEqual(string("$123.45"), (- x3).to_string()); + assertEqual(string("DM -123.45"), (- x4).to_string()); + assertEqual(string("DM 123.45"), (- x5).to_string()); + assertEqual(string("DM 123.45"), (- x6).to_string()); + assertEqual(string("-123.45 euro"), (- x7).to_string()); + assertEqual(string("123.45 euro"), (- x8).to_string()); + assertEqual(string("-123.45€"), (- x9).to_string()); + assertEqual(string("123.45€"), (- x10).to_string()); + + assertEqual(amount_t("$-123.45"), x1.negate()); + assertEqual(amount_t("$123.45"), x2.negate()); + assertEqual(amount_t("$123.45"), x3.negate()); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); + assertValid(x7); + assertValid(x8); + assertValid(x9); + assertValid(x10); +} + +void AmountTestCase::testAbs() +{ + amount_t x0; + amount_t x1(-1234L); + amount_t x2(1234L); + + assertThrow(x0.abs(), amount_error); + assertEqual(amount_t(1234L), x1.abs()); + assertEqual(amount_t(1234L), x2.abs()); + + assertValid(x0); + assertValid(x1); + assertValid(x2); +} + +void AmountTestCase::testCommodityAbs() +{ + amount_t x1("$-1234.56"); + amount_t x2("$1234.56"); + + assertEqual(amount_t("$1234.56"), x1.abs()); + assertEqual(amount_t("$1234.56"), x2.abs()); + + assertValid(x1); + assertValid(x2); +} + +void AmountTestCase::testFractionalRound() +{ + amount_t x0; + amount_t x1("1234.567890"); + + assertThrow(x0.precision(), amount_error); + assertThrow(x0.round(), amount_error); + assertThrow(x0.round(2), amount_error); + assertThrow(x0.unround(), amount_error); + assertEqual(amount_t::precision_t(6), x1.precision()); + + amount_t x1b(x1.unround()); + + assertEqual(x1b.precision(), x1b.unround().precision()); + + amount_t y7(x1.round(7)); + amount_t y6(x1.round(6)); + amount_t y5(x1.round(5)); + amount_t y4(x1.round(4)); + amount_t y3(x1.round(3)); + amount_t y2(x1.round(2)); + amount_t y1(x1.round(1)); + amount_t y0(x1.round(0)); + + assertEqual(amount_t::precision_t(6), y7.precision()); + assertEqual(amount_t::precision_t(6), y6.precision()); + assertEqual(amount_t::precision_t(5), y5.precision()); + assertEqual(amount_t::precision_t(4), y4.precision()); + assertEqual(amount_t::precision_t(3), y3.precision()); + assertEqual(amount_t::precision_t(2), y2.precision()); + assertEqual(amount_t::precision_t(1), y1.precision()); + assertEqual(amount_t::precision_t(0), y0.precision()); + + assertEqual(amount_t("1234.56789"), y7); + assertEqual(amount_t("1234.56789"), y6); + assertEqual(amount_t("1234.56789"), y5); + assertEqual(amount_t("1234.5679"), y4); + assertEqual(amount_t("1234.568"), y3); + assertEqual(amount_t("1234.57"), y2); + assertEqual(amount_t("1234.6"), y1); + assertEqual(amount_t("1235"), y0); + + amount_t x2("9876.543210"); + + assertEqual(amount_t("9876.543210"), x2.round(6)); + assertEqual(amount_t("9876.54321"), x2.round(5)); + assertEqual(amount_t("9876.5432"), x2.round(4)); + assertEqual(amount_t("9876.543"), x2.round(3)); + assertEqual(amount_t("9876.54"), x2.round(2)); + assertEqual(amount_t("9876.5"), x2.round(1)); + assertEqual(amount_t("9877"), x2.round(0)); + + amount_t x3("-1234.567890"); + + assertEqual(amount_t("-1234.56789"), x3.round(6)); + assertEqual(amount_t("-1234.56789"), x3.round(5)); + assertEqual(amount_t("-1234.5679"), x3.round(4)); + assertEqual(amount_t("-1234.568"), x3.round(3)); + assertEqual(amount_t("-1234.57"), x3.round(2)); + assertEqual(amount_t("-1234.6"), x3.round(1)); + assertEqual(amount_t("-1235"), x3.round(0)); + + amount_t x4("-9876.543210"); + + assertEqual(amount_t("-9876.543210"), x4.round(6)); + assertEqual(amount_t("-9876.54321"), x4.round(5)); + assertEqual(amount_t("-9876.5432"), x4.round(4)); + assertEqual(amount_t("-9876.543"), x4.round(3)); + assertEqual(amount_t("-9876.54"), x4.round(2)); + assertEqual(amount_t("-9876.5"), x4.round(1)); + assertEqual(amount_t("-9877"), x4.round(0)); + + amount_t x5("0.0000000000000000000000000000000000001"); + + assertEqual(amount_t("0.0000000000000000000000000000000000001"), + x5.round(37)); + assertEqual(amount_t(0L), x5.round(36)); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); +} + +void AmountTestCase::testCommodityRound() +{ + amount_t x1(internalAmount("$1234.567890")); + + assertEqual(internalAmount("$1234.56789"), x1.round(6)); + assertEqual(internalAmount("$1234.56789"), x1.round(5)); + assertEqual(internalAmount("$1234.5679"), x1.round(4)); + assertEqual(internalAmount("$1234.568"), x1.round(3)); + assertEqual(amount_t("$1234.57"), x1.round(2)); + assertEqual(amount_t("$1234.6"), x1.round(1)); + assertEqual(amount_t("$1235"), x1.round(0)); + + amount_t x2(internalAmount("$9876.543210")); + + assertEqual(internalAmount("$9876.543210"), x2.round(6)); + assertEqual(internalAmount("$9876.54321"), x2.round(5)); + assertEqual(internalAmount("$9876.5432"), x2.round(4)); + assertEqual(internalAmount("$9876.543"), x2.round(3)); + assertEqual(amount_t("$9876.54"), x2.round(2)); + assertEqual(amount_t("$9876.5"), x2.round(1)); + assertEqual(amount_t("$9877"), x2.round(0)); + + amount_t x3(internalAmount("$-1234.567890")); + + assertEqual(internalAmount("$-1234.56789"), x3.round(6)); + assertEqual(internalAmount("$-1234.56789"), x3.round(5)); + assertEqual(internalAmount("$-1234.5679"), x3.round(4)); + assertEqual(internalAmount("$-1234.568"), x3.round(3)); + assertEqual(amount_t("$-1234.57"), x3.round(2)); + assertEqual(amount_t("$-1234.6"), x3.round(1)); + assertEqual(amount_t("$-1235"), x3.round(0)); + + amount_t x4(internalAmount("$-9876.543210")); + + assertEqual(internalAmount("$-9876.543210"), x4.round(6)); + assertEqual(internalAmount("$-9876.54321"), x4.round(5)); + assertEqual(internalAmount("$-9876.5432"), x4.round(4)); + assertEqual(internalAmount("$-9876.543"), x4.round(3)); + assertEqual(amount_t("$-9876.54"), x4.round(2)); + assertEqual(amount_t("$-9876.5"), x4.round(1)); + assertEqual(amount_t("$-9877"), x4.round(0)); + + amount_t x5("$123.45"); + + x5 *= 100.12; + + assertEqual(internalAmount("$12359.814"), x5); + assertEqual(string("$12359.81"), x5.to_string()); + assertEqual(string("$12359.814"), x5.to_fullstring()); + assertEqual(string("$12359.814"), x5.unround().to_string()); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); +} + +void AmountTestCase::testCommodityDisplayRound() +{ + amount_t x1("$0.85"); + amount_t x2("$0.1"); + + x1 *= 0.19; + + assertNotEqual(amount_t("$0.16"), x1); + assertEqual(internalAmount("$0.1615"), x1); + assertEqual(string("$0.16"), x1.to_string()); + + assertEqual(amount_t("$0.10"), x2); + assertNotEqual(internalAmount("$0.101"), x2); + assertEqual(string("$0.10"), x2.to_string()); + + x1 *= 7L; + + assertNotEqual(amount_t("$1.13"), x1); + assertEqual(internalAmount("$1.1305"), x1); + assertEqual(string("$1.13"), x1.to_string()); +} + +void AmountTestCase::testReduction() +{ + amount_t x0; + amount_t x1("60s"); + amount_t x2("600s"); + amount_t x3("6000s"); + amount_t x4("360000s"); + amount_t x5("10m"); // 600s + amount_t x6("100m"); // 6000s + amount_t x7("1000m"); // 60000s + amount_t x8("10000m"); // 600000s + amount_t x9("10h"); // 36000s + amount_t x10("100h"); // 360000s + amount_t x11("1000h"); // 3600000s + amount_t x12("10000h"); // 36000000s + + assertThrow(x0.reduce(), amount_error); + assertThrow(x0.unreduce(), amount_error); + assertEqual(x2, x5); + assertEqual(x3, x6); + assertEqual(x4, x10); + assertEqual(string("100.0h"), x4.unreduce().to_string()); +} + +void AmountTestCase::testSign() +{ + amount_t x0; + amount_t x1("0.0000000000000000000000000000000000001"); + amount_t x2("-0.0000000000000000000000000000000000001"); + amount_t x3("1"); + amount_t x4("-1"); + + assertThrow(x0.sign(), amount_error); + assertTrue(x1.sign() > 0); + assertTrue(x2.sign() < 0); + assertTrue(x3.sign() > 0); + assertTrue(x4.sign() < 0); + + assertValid(x0); + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); +} + +void AmountTestCase::testCommoditySign() +{ + amount_t x1(internalAmount("$0.0000000000000000000000000000000000001")); + amount_t x2(internalAmount("$-0.0000000000000000000000000000000000001")); + amount_t x3("$1"); + amount_t x4("$-1"); + + assertTrue(x1.sign() != 0); + assertTrue(x2.sign() != 0); + assertTrue(x3.sign() > 0); + assertTrue(x4.sign() < 0); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); +} + +void AmountTestCase::testTruth() +{ + amount_t x0; + amount_t x1("1234"); + amount_t x2("1234.56"); + + assertThrow(assert(x0 ? 1 : 0), amount_error); + + assertTrue(x1); + assertTrue(x2); + + assertValid(x0); + assertValid(x1); + assertValid(x2); +} + +void AmountTestCase::testCommodityTruth() +{ + amount_t x1("$1234"); + amount_t x2("$1234.56"); + + if (x1) + assertTrue(true); + else + assertTrue(false); + + if (x2) + assertTrue(true); + else + assertTrue(false); + + assertValid(x1); + assertValid(x2); +} + +void AmountTestCase::testForZero() +{ + amount_t x0; + amount_t x1("0.000000000000000000001"); + + assertTrue(x1); + assertThrow(x0.is_zero(), amount_error); + assertThrow(x0.is_realzero(), amount_error); + assertFalse(x1.is_zero()); + assertFalse(x1.is_realzero()); + + assertValid(x0); + assertValid(x1); +} + +void AmountTestCase::testCommodityForZero() +{ + amount_t x1(internalAmount("$0.000000000000000000001")); + + assertFalse(x1); + assertTrue(x1.is_zero()); + assertFalse(x1.is_realzero()); + + assertValid(x1); +} + +void AmountTestCase::testIntegerConversion() +{ + amount_t x0; + amount_t x1(123456L); + amount_t x2("12345682348723487324"); + + assertThrow(x0.to_long(), amount_error); + assertThrow(x0.to_double(), amount_error); + assertFalse(x2.fits_in_long()); + assertEqual(123456L, x1.to_long()); + assertEqual(123456.0, x1.to_double()); + assertEqual(string("123456"), x1.to_string()); + assertEqual(string("123456"), x1.quantity_string()); + + assertValid(x1); +} + +void AmountTestCase::testFractionalConversion() +{ + amount_t x1(1234.56); + amount_t x2("1234.5683787634678348734"); + + assertThrow(x1.to_long(), amount_error); // loses precision + assertThrow(x2.to_double(), amount_error); // loses precision + assertFalse(x2.fits_in_double()); + assertEqual(1234L, x1.to_long(true)); + assertEqual(1234.56, x1.to_double()); + assertEqual(string("1234.56"), x1.to_string()); + assertEqual(string("1234.56"), x1.quantity_string()); + + assertValid(x1); +} + +void AmountTestCase::testCommodityConversion() +{ + amount_t x1("$1234.56"); + + assertThrow(x1.to_long(), amount_error); // loses precision + assertEqual(1234L, x1.to_long(true)); + assertEqual(1234.56, x1.to_double()); + assertEqual(string("$1234.56"), x1.to_string()); + assertEqual(string("1234.56"), x1.quantity_string()); + + assertValid(x1); +} + +void AmountTestCase::testPrinting() +{ + amount_t x0; + amount_t x1("982340823.380238098235098235098235098"); + + { + std::ostringstream bufstr; + assertThrow(bufstr << x0, amount_error); + } + + { + std::ostringstream bufstr; + bufstr << x1; + + assertEqual(std::string("982340823.380238098235098235098235098"), + bufstr.str()); + } + + assertValid(x0); + assertValid(x1); +} + +void AmountTestCase::testCommodityPrinting() +{ + amount_t x1(internalAmount("$982340823.386238098235098235098235098")); + amount_t x2("$982340823.38"); + + { + std::ostringstream bufstr; + bufstr << x1; + + assertEqual(std::string("$982340823.386238098235098235098235098"), + bufstr.str()); + } + + { + std::ostringstream bufstr; + bufstr << (x1 * x2).to_string(); + + assertEqual(std::string("$964993493285024293.18099172508158508135413499124"), + bufstr.str()); + } + + { + std::ostringstream bufstr; + bufstr << (x2 * x1).to_string(); + + assertEqual(std::string("$964993493285024293.18"), bufstr.str()); + } + + assertValid(x1); + assertValid(x2); +} + +void AmountTestCase::testSerialization() +{ + amount_t x0; + amount_t x1("$8,192.34"); + amount_t x2("8192.34"); + amount_t x3("8192.34"); + amount_t x4("-8192.34"); + amount_t x5(x4); + + // Force x3's pointer to actually be set to null_commodity + x3.set_commodity(*x3.current_pool->null_commodity); + + std::string buf; + { + std::ostringstream storage; + assertThrow(x0.write(storage), amount_error); + x1.write(storage); + x2.write(storage); + x3.write(storage); + x4.write(storage); + x5.write(storage); + buf = storage.str(); + } + + amount_t x1b; + amount_t x2b; + amount_t x3b; + amount_t x4b; + amount_t x5b; + { + std::istringstream storage(buf); + x1b.read(storage); + x2b.read(storage); + x3b.read(storage); + x4b.read(storage); + x5b.read(storage); + } + + assertEqual(x1, x1b); + assertEqual(x2, x2b); + assertEqual(x3, x3b); + assertEqual(x4, x4b); + + const char * ptr = buf.c_str(); + + amount_t x1c; + amount_t x2c; + amount_t x3c; + amount_t x4c; + amount_t x5c; + { + x1c.read(ptr); + x2c.read(ptr); + x3c.read(ptr); + x4c.read(ptr); + x5c.read(ptr); + } + + assertEqual(x1, x1b); + assertEqual(x2, x2b); + assertEqual(x3, x3b); + assertEqual(x4, x4b); + + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x1b); + assertValid(x2b); + assertValid(x3b); + assertValid(x4b); + assertValid(x1c); + assertValid(x2c); + assertValid(x3c); + assertValid(x4c); +} diff --git a/test/numerics/t_amount.h b/test/numerics/t_amount.h new file mode 100644 index 00000000..86487cd2 --- /dev/null +++ b/test/numerics/t_amount.h @@ -0,0 +1,110 @@ +#ifndef _T_AMOUNT_H +#define _T_AMOUNT_H + +#include "UnitTests.h" + +class AmountTestCase : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE(AmountTestCase); + + CPPUNIT_TEST(testConstructors); + CPPUNIT_TEST(testCommodityConstructors); + CPPUNIT_TEST(testParser); + CPPUNIT_TEST(testAssignment); + CPPUNIT_TEST(testCommodityAssignment); + CPPUNIT_TEST(testEquality); + CPPUNIT_TEST(testCommodityEquality); + CPPUNIT_TEST(testComparisons); + CPPUNIT_TEST(testCommodityComparisons); + CPPUNIT_TEST(testIntegerAddition); + CPPUNIT_TEST(testFractionalAddition); + CPPUNIT_TEST(testCommodityAddition); + CPPUNIT_TEST(testIntegerSubtraction); + CPPUNIT_TEST(testFractionalSubtraction); + CPPUNIT_TEST(testCommoditySubtraction); + CPPUNIT_TEST(testIntegerMultiplication); + CPPUNIT_TEST(testFractionalMultiplication); + CPPUNIT_TEST(testCommodityMultiplication); + CPPUNIT_TEST(testIntegerDivision); + CPPUNIT_TEST(testFractionalDivision); + CPPUNIT_TEST(testCommodityDivision); + CPPUNIT_TEST(testNegation); + CPPUNIT_TEST(testCommodityNegation); + CPPUNIT_TEST(testAbs); + CPPUNIT_TEST(testCommodityAbs); + CPPUNIT_TEST(testFractionalRound); + CPPUNIT_TEST(testCommodityRound); + CPPUNIT_TEST(testCommodityDisplayRound); + CPPUNIT_TEST(testReduction); + CPPUNIT_TEST(testSign); + CPPUNIT_TEST(testCommoditySign); + CPPUNIT_TEST(testTruth); + CPPUNIT_TEST(testCommodityTruth); + CPPUNIT_TEST(testForZero); + CPPUNIT_TEST(testCommodityForZero); + CPPUNIT_TEST(testIntegerConversion); + CPPUNIT_TEST(testFractionalConversion); + CPPUNIT_TEST(testCommodityConversion); + CPPUNIT_TEST(testPrinting); + CPPUNIT_TEST(testCommodityPrinting); + CPPUNIT_TEST(testSerialization); + + CPPUNIT_TEST_SUITE_END(); + +public: + //ledger::session_t session; + + AmountTestCase() {} + virtual ~AmountTestCase() {} + + virtual void setUp(); + virtual void tearDown(); + + void testConstructors(); + void testCommodityConstructors(); + void testParser(); + void testAssignment(); + void testCommodityAssignment(); + void testEquality(); + void testCommodityEquality(); + void testComparisons(); + void testCommodityComparisons(); + void testIntegerAddition(); + void testFractionalAddition(); + void testCommodityAddition(); + void testIntegerSubtraction(); + void testFractionalSubtraction(); + void testCommoditySubtraction(); + void testIntegerMultiplication(); + void testFractionalMultiplication(); + void testCommodityMultiplication(); + void testIntegerDivision(); + void testFractionalDivision(); + void testCommodityDivision(); + void testNegation(); + void testCommodityNegation(); + void testAbs(); + void testCommodityAbs(); + void testFractionalRound(); + void testCommodityRound(); + void testCommodityDisplayRound(); + void testReduction(); + void testSign(); + void testCommoditySign(); + void testTruth(); + void testCommodityTruth(); + void testForZero(); + void testCommodityForZero(); + void testIntegerConversion(); + void testFractionalConversion(); + void testCommodityConversion(); + void testPrinting(); + void testCommodityPrinting(); + void testSerialization(); + +private: + AmountTestCase(const AmountTestCase ©); + void operator=(const AmountTestCase ©); +}; + +#endif // _T_AMOUNT_H diff --git a/test/numerics/t_balance.cc b/test/numerics/t_balance.cc new file mode 100644 index 00000000..2183a081 --- /dev/null +++ b/test/numerics/t_balance.cc @@ -0,0 +1,25 @@ +#include "t_balance.h" + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(BalanceTestCase, "numerics"); + +void BalanceTestCase::setUp() +{ + //ledger::set_session_context(&session); + + // Cause the display precision for dollars to be initialized to 2. + amount_t x1("$1.00"); + assertTrue(x1); + + amount_t::stream_fullstrings = true; // make reports from UnitTests accurate +} + +void BalanceTestCase::tearDown() +{ + amount_t::stream_fullstrings = false; + + //ledger::set_session_context(); +} + +void BalanceTestCase::testConstructors() +{ +} diff --git a/test/numerics/t_balance.h b/test/numerics/t_balance.h new file mode 100644 index 00000000..788a1c11 --- /dev/null +++ b/test/numerics/t_balance.h @@ -0,0 +1,30 @@ +#ifndef _T_BALANCE_H +#define _T_BALANCE_H + +#include "UnitTests.h" + +class BalanceTestCase : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE(BalanceTestCase); + + CPPUNIT_TEST(testConstructors); + + CPPUNIT_TEST_SUITE_END(); + +public: + //ledger::session_t session; + + BalanceTestCase() {} + virtual ~BalanceTestCase() {} + + virtual void setUp(); + virtual void tearDown(); + + void testConstructors(); + +private: + BalanceTestCase(const BalanceTestCase ©); + void operator=(const BalanceTestCase ©); +}; + +#endif // _T_BALANCE_H diff --git a/test/numerics/t_commodity.cc b/test/numerics/t_commodity.cc new file mode 100644 index 00000000..64cddfbb --- /dev/null +++ b/test/numerics/t_commodity.cc @@ -0,0 +1,64 @@ +#include "t_commodity.h" + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CommodityTestCase, "numerics"); + +void CommodityTestCase::setUp() { + //ledger::set_session_context(&session); +} +void CommodityTestCase::tearDown() { + //ledger::set_session_context(); +} + +void CommodityTestCase::testPriceHistory() +{ + ptime jan17_07 = parse_datetime("2007/01/17 00:00:00"); + ptime feb27_07 = parse_datetime("2007/02/27 18:00:00"); + ptime feb28_07 = parse_datetime("2007/02/28 06:00:00"); + ptime feb28_07sbm = parse_datetime("2007/02/28 11:59:59"); + ptime mar01_07 = parse_datetime("2007/03/01 00:00:00"); + ptime apr15_07 = parse_datetime("2007/04/15 13:00:00"); + + // jww (2007-04-17): tbd + amount_t x0; + amount_t x1("100.10 AAPL"); + + assertThrow(x0.value(), amount_error); + assertFalse(x1.value()); + + // Commodities cannot be constructed by themselves, since a great + // deal of their state depends on how they were seen to be used. + commodity_t& aapl(x1.commodity()); + + aapl.add_price(jan17_07, amount_t("$10.20")); + aapl.add_price(feb27_07, amount_t("$13.40")); + aapl.add_price(feb28_07, amount_t("$18.33")); + aapl.add_price(feb28_07sbm, amount_t("$18.30")); + aapl.add_price(mar01_07, amount_t("$19.50")); + aapl.add_price(apr15_07, amount_t("$21.22")); + + optional<amount_t> amt1 = x1.value(feb28_07sbm); + assertTrue(amt1); + assertEqual(amount_t("$1831.83"), *amt1); + + optional<amount_t> amt2 = x1.value(current_moment); + assertTrue(amt2); + assertEqual(amount_t("$2124.12"), *amt2); + + assertValid(x1); +} + +void CommodityTestCase::testLots() +{ + // jww (2007-04-17): tbd +} + +void CommodityTestCase::testScalingBase() +{ + // jww (2007-04-17): tbd +} + +void CommodityTestCase::testReduction() +{ + // jww (2007-04-17): tbd +} + diff --git a/test/numerics/t_commodity.h b/test/numerics/t_commodity.h new file mode 100644 index 00000000..635b2a61 --- /dev/null +++ b/test/numerics/t_commodity.h @@ -0,0 +1,36 @@ +#ifndef _T_COMMMODITY_H +#define _T_COMMMODITY_H + +#include "UnitTests.h" + +class CommodityTestCase : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE(CommodityTestCase); + + CPPUNIT_TEST(testPriceHistory); + CPPUNIT_TEST(testLots); + CPPUNIT_TEST(testScalingBase); + CPPUNIT_TEST(testReduction); + + CPPUNIT_TEST_SUITE_END(); + +public: + //ledger::session_t session; + + CommodityTestCase() {} + virtual ~CommodityTestCase() {} + + virtual void setUp(); + virtual void tearDown(); + + void testPriceHistory(); + void testLots(); + void testScalingBase(); + void testReduction(); + +private: + CommodityTestCase(const CommodityTestCase ©); + void operator=(const CommodityTestCase ©); +}; + +#endif // _T_COMMMODITY_H diff --git a/test/python/PyUnitTests.py b/test/python/PyUnitTests.py new file mode 100755 index 00000000..3c19093f --- /dev/null +++ b/test/python/PyUnitTests.py @@ -0,0 +1,5 @@ +#!/bin/sh + +PYTHONPATH="%builddir%":"%srcdir%":$PYTHONPATH \ +DYLD_LIBRARY_PATH="%builddir%/.libs":"%builddir%/gdtoa/.libs":$DYLD_LIBRARY_PATH \ + python "%srcdir%"/tests/python/UnitTests.py diff --git a/test/python/UnitTests.py b/test/python/UnitTests.py new file mode 100644 index 00000000..843e9fc1 --- /dev/null +++ b/test/python/UnitTests.py @@ -0,0 +1,9 @@ +from unittest import TextTestRunner, TestSuite + +import tests.python.numerics.t_amount as t_amount + +suites = [ + t_amount.suite(), +] + +TextTestRunner().run(TestSuite(suites)) diff --git a/test/python/__init__.py b/test/python/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/python/__init__.py diff --git a/test/python/numerics/__init__.py b/test/python/numerics/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/python/numerics/__init__.py diff --git a/test/python/numerics/t_amount.py b/test/python/numerics/t_amount.py new file mode 100644 index 00000000..f7eeee8f --- /dev/null +++ b/test/python/numerics/t_amount.py @@ -0,0 +1,1470 @@ +# -*- coding: utf-8 -*- + +import unittest +import exceptions +import operator + +from ledger import * +from StringIO import * + +internalAmount = amount.exact + +class t_amountTestCase(unittest.TestCase): + testSession = None + + def assertValid(self, amt): + self.assertTrue(amt.valid()) + + def setUp(self): + #self.testSession = session() + #set_session_context(self.testSession) + + # Cause the display precision for dollars to be initialized to 2. + x1 = amount("$1.00") + self.assertTrue(x1) + + #amount.stream_fullstrings = True # make reports from UnitTests accurate + + def tearDown(self): + pass + #amount.stream_fullstrings = False + + #set_session_context() + #self.testSession = None + + def testParser(self): + x0 = amount() + x1 = amount() + x2 = amount() + x3 = amount() + x4 = amount(123.456) + x5 = amount(x4) + x6 = amount(x4) + x7 = amount(x4) + x8 = amount("$123.45") + x9 = amount(x8) + x10 = amount(x8) + x11 = amount(x8) + x12 = amount("$100") + + self.assertEqual(2, x12.commodity.precision) + + #buf = "$100..." + #input = StringIO(buf) + #x13 = amount() + #x13.parse(input) + #self.assertEqual(x12, x13) + + x14 = amount() + self.assertRaises(exceptions.ArithmeticError, lambda: x14.parse("DM")) + + x15 = amount("$1.000.000,00") + + x16 = amount("$2000") + self.assertEqual("$2.000,00", x16.to_string()) + x16.parse("$2000,00") + self.assertEqual("$2.000,00", x16.to_string()) + + # Since European-ness is an additive quality, we must switch back + # to American-ness manually + x15.commodity.drop_flags(COMMODITY_STYLE_EUROPEAN) + + x17 = amount("$1,000,000.00") + + x18 = amount("$2000") + self.assertEqual("$2,000.00", x18.to_string()) + x18.parse("$2,000") + self.assertEqual("$2,000.00", x18.to_string()) + + self.assertEqual(x15, x17) + + x19 = amount("EUR 1000") + x20 = amount("EUR 1000") + + self.assertEqual("EUR 1000", x19.to_string()) + self.assertEqual("EUR 1000", x20.to_string()) + + x1.parse("$100.0000", AMOUNT_PARSE_NO_MIGRATE) + self.assertEqual(2, x12.commodity.precision) + self.assertEqual(x1.commodity, x12.commodity) + self.assertEqual(x1, x12) + + x0.parse("$100.0000") + self.assertEqual(4, x12.commodity.precision) + self.assertEqual(x0.commodity, x12.commodity) + self.assertEqual(x0, x12) + + x2.parse("$100.00", AMOUNT_PARSE_NO_REDUCE) + self.assertEqual(x2, x12) + x3.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE) + self.assertEqual(x3, x12) + + x4.parse("$100.00") + self.assertEqual(x4, x12) + x5.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE) + self.assertEqual(x5, x12) + x6.parse("$100.00", AMOUNT_PARSE_NO_REDUCE) + self.assertEqual(x6, x12) + x7.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE) + self.assertEqual(x7, x12) + + x8.parse("$100.00") + self.assertEqual(x8, x12) + x9.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE) + self.assertEqual(x9, x12) + x10.parse("$100.00", AMOUNT_PARSE_NO_REDUCE) + self.assertEqual(x10, x12) + x11.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE) + self.assertEqual(x11, x12) + + self.assertValid(x0) + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + self.assertValid(x8) + self.assertValid(x9) + self.assertValid(x10) + self.assertValid(x11) + self.assertValid(x12) + + def testConstructors(self): + x0 = amount() + x1 = amount(123456) + x2 = amount(123456L) + x3 = amount(123.456) + x5 = amount("123456") + x6 = amount("123.456") + x7 = amount("123456") + x8 = amount("123.456") + x9 = amount(x3) + x10 = amount(x6) + x11 = amount(x8) + + self.assertRaises(exceptions.ArithmeticError, lambda: amount(0) == x0) + self.assertRaises(exceptions.ArithmeticError, lambda: amount() == x0) + self.assertRaises(exceptions.ArithmeticError, lambda: amount("0") == x0) + self.assertRaises(exceptions.ArithmeticError, lambda: amount("0.0") == x0) + self.assertEqual(x2, x1) + self.assertEqual(x5, x1) + self.assertEqual(x7, x1) + self.assertEqual(x6, x3) + self.assertEqual(x8, x3) + self.assertEqual(x10, x3) + self.assertEqual(x10, x9) + + self.assertValid(x0) + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + self.assertValid(x8) + self.assertValid(x9) + self.assertValid(x10) + self.assertValid(x11) + + def testCommodityConstructors(self): + x1 = amount("$123.45") + x2 = amount("-$123.45") + x3 = amount("$-123.45") + x4 = amount("DM 123.45") + x5 = amount("-DM 123.45") + x6 = amount("DM -123.45") + x7 = amount("123.45 euro") + x8 = amount("-123.45 euro") + x9 = amount("123.45€") + x10 = amount("-123.45€") + + self.assertEqual(amount("$123.45"), x1) + self.assertEqual(amount("-$123.45"), x2) + self.assertEqual(amount("$-123.45"), x3) + self.assertEqual(amount("DM 123.45"), x4) + self.assertEqual(amount("-DM 123.45"), x5) + self.assertEqual(amount("DM -123.45"), x6) + self.assertEqual(amount("123.45 euro"), x7) + self.assertEqual(amount("-123.45 euro"), x8) + self.assertEqual(amount("123.45€"), x9) + self.assertEqual(amount("-123.45€"), x10) + + self.assertEqual("$123.45", x1.to_string()) + self.assertEqual("$-123.45", x2.to_string()) + self.assertEqual("$-123.45", x3.to_string()) + self.assertEqual("DM 123.45", x4.to_string()) + self.assertEqual("DM -123.45", x5.to_string()) + self.assertEqual("DM -123.45", x6.to_string()) + self.assertEqual("123.45 euro", x7.to_string()) + self.assertEqual("-123.45 euro", x8.to_string()) + self.assertEqual("123.45€", x9.to_string()) + self.assertEqual("-123.45€", x10.to_string()) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + self.assertValid(x8) + self.assertValid(x9) + self.assertValid(x10) + + def testAssignment(self): + x0 = amount() + x1 = amount(123456) + x2 = amount(123456L) + x3 = amount(123.456) + x5 = amount("123456") + x6 = amount("123.456") + x7 = "123456" + x8 = "123.456" + x9 = amount(x3) + x10 = amount(x6) + + self.assertEqual(x2, x1) + self.assertEqual(x5, x1) + self.assertEqual(x7, x1) + self.assertEqual(x6, x3) + self.assertEqual(x8, x3) + self.assertEqual(x10, x3) + self.assertEqual(x10, x9) + + x1 = amount(123456) + x2 = amount(123456L) + x3 = amount(123.456) + x5 = amount("123456") + x6 = amount("123.456") + x7 = amount("123456") + x8 = amount("123.456") + x9 = x3 + x10 = amount(x6) + + self.assertEqual(x2, x1) + self.assertEqual(x5, x1) + self.assertEqual(x7, x1) + self.assertEqual(x6, x3) + self.assertEqual(x8, x3) + self.assertEqual(x10, x3) + self.assertEqual(x10, x9) + + self.assertFalse(x1.is_null()) + x1 = x0 # sets x1 back to uninitialized state + self.assertTrue(x0.is_null()) + self.assertTrue(x1.is_null()) + + self.assertValid(x0) + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + self.assertValid(x8) + self.assertValid(x9) + self.assertValid(x10) + + def testCommodityAssignment(self): + x1 = amount("$123.45") + x2 = amount("-$123.45") + x3 = amount("$-123.45") + x4 = amount("DM 123.45") + x5 = amount("-DM 123.45") + x6 = amount("DM -123.45") + x7 = amount("123.45 euro") + x8 = amount("-123.45 euro") + x9 = amount("123.45€") + x10 = amount("-123.45€") + + self.assertEqual(amount("$123.45"), x1) + self.assertEqual(amount("-$123.45"), x2) + self.assertEqual(amount("$-123.45"), x3) + self.assertEqual(amount("DM 123.45"), x4) + self.assertEqual(amount("-DM 123.45"), x5) + self.assertEqual(amount("DM -123.45"), x6) + self.assertEqual(amount("123.45 euro"), x7) + self.assertEqual(amount("-123.45 euro"), x8) + self.assertEqual(amount("123.45€"), x9) + self.assertEqual(amount("-123.45€"), x10) + + self.assertEqual("$123.45", x1.to_string()) + self.assertEqual("$-123.45", x2.to_string()) + self.assertEqual("$-123.45", x3.to_string()) + self.assertEqual("DM 123.45", x4.to_string()) + self.assertEqual("DM -123.45", x5.to_string()) + self.assertEqual("DM -123.45", x6.to_string()) + self.assertEqual("123.45 euro", x7.to_string()) + self.assertEqual("-123.45 euro", x8.to_string()) + self.assertEqual("123.45€", x9.to_string()) + self.assertEqual("-123.45€", x10.to_string()) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + self.assertValid(x8) + self.assertValid(x9) + self.assertValid(x10) + + def testEquality(self): + x1 = amount(123456) + x2 = amount(456789) + x3 = amount(333333) + x4 = amount(123456.0) + x5 = amount("123456.0") + x6 = amount(123456.0) + + self.assertTrue(x1 == 123456) + self.assertTrue(x1 != x2) + self.assertTrue(x1 == (x2 - x3)) + self.assertTrue(x1 == x4) + self.assertTrue(x4 == x5) + self.assertTrue(x4 == x6) + + self.assertTrue(x1 == 123456) + self.assertTrue(123456 == x1) + self.assertTrue(x1 == 123456L) + self.assertTrue(123456L == x1) + self.assertTrue(x1 == 123456.0) + self.assertTrue(123456.0 == x1) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + + def testCommodityEquality(self): + x0 = amount() + x1 = amount("$123.45") + x2 = amount("-$123.45") + x3 = amount("$-123.45") + x4 = amount("DM 123.45") + x5 = amount("-DM 123.45") + x6 = amount("DM -123.45") + x7 = amount("123.45 euro") + x8 = amount("-123.45 euro") + x9 = amount("123.45€") + x10 = amount("-123.45€") + + self.assertTrue(x0.is_null()) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.is_zero()) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.is_realzero()) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.sign() == 0) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.compare(x1) < 0) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.compare(x2) > 0) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.compare(x0) == 0) + + self.assertTrue(x1 != x2) + self.assertTrue(x1 != x4) + self.assertTrue(x1 != x7) + self.assertTrue(x1 != x9) + self.assertTrue(x2 == x3) + self.assertTrue(x4 != x5) + self.assertTrue(x5 == x6) + self.assertTrue(x7 == - x8) + self.assertTrue(x9 == - x10) + + self.assertValid(x0) + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + self.assertValid(x8) + self.assertValid(x9) + self.assertValid(x10) + + def testComparisons(self): + x0 = amount() + x1 = amount(-123) + x2 = amount(123) + x3 = amount(-123.45) + x4 = amount(123.45) + x5 = amount("-123.45") + x6 = amount("123.45") + + self.assertRaises(exceptions.ArithmeticError, lambda: x0 > x1) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 < x2) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 > x3) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 < x4) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 > x5) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 < x6) + + self.assertTrue(x1 > x3) + self.assertTrue(x3 <= x5) + self.assertTrue(x3 >= x5) + self.assertTrue(x3 < x1) + self.assertTrue(x3 < x4) + + self.assertTrue(x1 < 100) + self.assertTrue(100 > x1) + self.assertTrue(x1 < 100L) + self.assertTrue(100L > x1) + self.assertTrue(x1 < 100.0) + self.assertTrue(100.0 > x1) + + self.assertValid(x0) + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + + def testCommodityComparisons(self): + x1 = amount("$-123") + x2 = amount("$123.00") + x3 = amount(internalAmount("$-123.4544")) + x4 = amount(internalAmount("$123.4544")) + x5 = amount("$-123.45") + x6 = amount("$123.45") + x7 = amount("DM 123.45") + + self.assertTrue(x1 > x3) + self.assertTrue(x3 <= x5) + self.assertTrue(x3 < x5) + self.assertTrue(x3 <= x5) + self.assertFalse(x3 == x5) + self.assertTrue(x3 < x1) + self.assertTrue(x3 < x4) + self.assertFalse(x6 == x7) + self.assertRaises(exceptions.ArithmeticError, lambda: x6 < x7) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + + def testIntegerAddition(self): + x0 = amount() + x1 = amount(123) + y1 = amount(456) + + self.assertEqual(amount(579), x1 + y1) + self.assertEqual(amount(579), x1 + 456) + self.assertEqual(amount(579), 456 + x1) + + x1 += amount(456) + self.assertEqual(amount(579), x1) + x1 += 456 + self.assertEqual(amount(1035), x1) + + x4 = amount("123456789123456789123456789") + + self.assertEqual(amount("246913578246913578246913578"), x4 + x4) + + self.assertValid(x0) + self.assertValid(x1) + self.assertValid(y1) + self.assertValid(x4) + + def testFractionalAddition(self): + x1 = amount(123.123) + y1 = amount(456.456) + + self.assertEqual(amount(579.579), x1 + y1) + self.assertEqual(amount(579.579), x1 + 456.456) + self.assertEqual(amount(579.579), 456.456 + x1) + + x1 += amount(456.456) + self.assertEqual(amount(579.579), x1) + x1 += 456.456 + self.assertEqual(amount(1036.035), x1) + x1 += 456 + self.assertEqual(amount(1492.035), x1) + + x2 = amount("123456789123456789.123456789123456789") + + self.assertEqual(amount("246913578246913578.246913578246913578"), x2 + x2) + + self.assertValid(x1) + self.assertValid(y1) + self.assertValid(x2) + + def testCommodityAddition(self): + x0 = amount() + x1 = amount("$123.45") + x2 = amount(internalAmount("$123.456789")) + x3 = amount("DM 123.45") + x4 = amount("123.45 euro") + x5 = amount("123.45€") + x6 = amount("123.45") + + self.assertEqual(amount("$246.90"), x1 + x1) + self.assertNotEqual(amount("$246.91"), x1 + x2) + self.assertEqual(internalAmount("$246.906789"), x1 + x2) + + # Converting to string drops internal precision + self.assertEqual("$246.90", (x1 + x1).to_string()) + self.assertEqual("$246.91", (x1 + x2).to_string()) + + self.assertRaises(exceptions.ArithmeticError, lambda: x1 + x0) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 + x1) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 + x0) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 + x3) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 + x4) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 + x5) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 + x6) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 + 123.45) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 + 123) + + self.assertEqual(amount("DM 246.90"), x3 + x3) + self.assertEqual(amount("246.90 euro"), x4 + x4) + self.assertEqual(amount("246.90€"), x5 + x5) + + self.assertEqual("DM 246.90", (x3 + x3).to_string()) + self.assertEqual("246.90 euro", (x4 + x4).to_string()) + self.assertEqual("246.90€", (x5 + x5).to_string()) + + x1 += amount("$456.45") + self.assertEqual(amount("$579.90"), x1) + x1 += amount("$456.45") + self.assertEqual(amount("$1036.35"), x1) + x1 += amount("$456") + self.assertEqual(amount("$1492.35"), x1) + + x7 = amount(internalAmount("$123456789123456789.123456789123456789")) + + self.assertEqual(internalAmount("$246913578246913578.246913578246913578"), x7 + x7) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + + def testIntegerSubtraction(self): + x1 = amount(123) + y1 = amount(456) + + self.assertEqual(amount(333), y1 - x1) + self.assertEqual(amount(-333), x1 - y1) + self.assertEqual(amount(23), x1 - 100) + self.assertEqual(amount(-23), 100 - x1) + + x1 -= amount(456) + self.assertEqual(amount(-333), x1) + x1 -= 456 + self.assertEqual(amount(-789), x1) + + x4 = amount("123456789123456789123456789") + y4 = amount("8238725986235986") + + self.assertEqual(amount("123456789115218063137220803"), x4 - y4) + self.assertEqual(amount("-123456789115218063137220803"), y4 - x4) + + self.assertValid(x1) + self.assertValid(y1) + self.assertValid(x4) + self.assertValid(y4) + + def testFractionalSubtraction(self): + x1 = amount(123.123) + y1 = amount(456.456) + + self.assertEqual(amount(-333.333), x1 - y1) + self.assertEqual(amount(333.333), y1 - x1) + + x1 -= amount(456.456) + self.assertEqual(amount(-333.333), x1) + x1 -= 456.456 + self.assertEqual(amount(-789.789), x1) + x1 -= 456 + self.assertEqual(amount(-1245.789), x1) + + x2 = amount("123456789123456789.123456789123456789") + y2 = amount("9872345982459.248974239578") + + self.assertEqual(amount("123446916777474329.874482549545456789"), x2 - y2) + self.assertEqual(amount("-123446916777474329.874482549545456789"), y2 - x2) + + self.assertValid(x1) + self.assertValid(y1) + self.assertValid(x2) + self.assertValid(y2) + + def testCommoditySubtraction(self): + x0 = amount() + x1 = amount("$123.45") + x2 = amount(internalAmount("$123.456789")) + x3 = amount("DM 123.45") + x4 = amount("123.45 euro") + x5 = amount("123.45€") + x6 = amount("123.45") + + self.assertNotEqual(amount(), x1 - x1) + self.assertEqual(amount("$0"), x1 - x1) + self.assertEqual(amount("$23.45"), x1 - amount("$100.00")) + self.assertEqual(amount("$-23.45"), amount("$100.00") - x1) + self.assertNotEqual(amount("$-0.01"), x1 - x2) + self.assertEqual(internalAmount("$-0.006789"), x1 - x2) + + # Converting to string drops internal precision. If an amount is + # zero, it drops the commodity as well. + self.assertEqual("$0.00", (x1 - x1).to_string()) + self.assertEqual("$-0.01", (x1 - x2).to_string()) + + self.assertRaises(exceptions.ArithmeticError, lambda: x1 - x0) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 - x1) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 - x0) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 - x3) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 - x4) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 - x5) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 - x6) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 - 123.45) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 - 123) + + self.assertEqual(amount("DM 0.00"), x3 - x3) + self.assertEqual(amount("DM 23.45"), x3 - amount("DM 100.00")) + self.assertEqual(amount("DM -23.45"), amount("DM 100.00") - x3) + self.assertEqual(amount("0.00 euro"), x4 - x4) + self.assertEqual(amount("23.45 euro"), x4 - amount("100.00 euro")) + self.assertEqual(amount("-23.45 euro"), amount("100.00 euro") - x4) + self.assertEqual(amount("0.00€"), x5 - x5) + self.assertEqual(amount("23.45€"), x5 - amount("100.00€")) + self.assertEqual(amount("-23.45€"), amount("100.00€") - x5) + + self.assertEqual("DM 0.00", (x3 - x3).to_string()) + self.assertEqual("DM 23.45", (x3 - amount("DM 100.00")).to_string()) + self.assertEqual("DM -23.45", (amount("DM 100.00") - x3).to_string()) + self.assertEqual("0.00 euro", (x4 - x4).to_string()) + self.assertEqual("23.45 euro", (x4 - amount("100.00 euro")).to_string()) + self.assertEqual("-23.45 euro", (amount("100.00 euro") - x4).to_string()) + self.assertEqual("0.00€", (x5 - x5).to_string()) + self.assertEqual("23.45€", (x5 - amount("100.00€")).to_string()) + self.assertEqual("-23.45€", (amount("100.00€") - x5).to_string()) + + x1 -= amount("$456.45") + self.assertEqual(amount("$-333.00"), x1) + x1 -= amount("$456.45") + self.assertEqual(amount("$-789.45"), x1) + x1 -= amount("$456") + self.assertEqual(amount("$-1245.45"), x1) + + x7 = amount(internalAmount("$123456789123456789.123456789123456789")) + x8 = amount(internalAmount("$2354974984698.98459845984598")) + + self.assertEqual(internalAmount("$123454434148472090.138858329277476789"), x7 - x8) + self.assertEqual("$123454434148472090.138858329277476789", (x7 - x8).to_string()) + self.assertEqual("$123454434148472090.14", + (amount("$1.00") * (x7 - x8)).to_string()) + self.assertEqual(internalAmount("$-123454434148472090.138858329277476789"), x8 - x7) + self.assertEqual("$-123454434148472090.138858329277476789", (x8 - x7).to_string()) + self.assertEqual("$-123454434148472090.14", + (amount("$1.00") * (x8 - x7)).to_string()) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + self.assertValid(x8) + + def testIntegerMultiplication(self): + x1 = amount(123) + y1 = amount(456) + + self.assertEqual(amount(0), x1 * 0) + self.assertEqual(amount(0), amount(0) * x1) + self.assertEqual(amount(0), 0 * x1) + self.assertEqual(x1, x1 * 1) + self.assertEqual(x1, amount(1) * x1) + self.assertEqual(x1, 1 * x1) + self.assertEqual(- x1, x1 * -1) + self.assertEqual(- x1, amount(-1) * x1) + self.assertEqual(- x1, -1 * x1) + self.assertEqual(amount(56088), x1 * y1) + self.assertEqual(amount(56088), y1 * x1) + self.assertEqual(amount(56088), x1 * 456) + self.assertEqual(amount(56088), amount(456) * x1) + self.assertEqual(amount(56088), 456 * x1) + + x1 *= amount(123) + self.assertEqual(amount(15129), x1) + x1 *= 123 + self.assertEqual(amount(1860867), x1) + + x4 = amount("123456789123456789123456789") + + self.assertEqual(amount("15241578780673678546105778281054720515622620750190521"), + x4 * x4) + + self.assertValid(x1) + self.assertValid(y1) + self.assertValid(x4) + + def testFractionalMultiplication(self): + x1 = amount(123.123) + y1 = amount(456.456) + + self.assertEqual(amount(0), x1 * 0) + self.assertEqual(amount(0), amount(0) * x1) + self.assertEqual(amount(0), 0 * x1) + self.assertEqual(x1, x1 * 1) + self.assertEqual(x1, amount(1) * x1) + self.assertEqual(x1, 1 * x1) + self.assertEqual(- x1, x1 * -1) + self.assertEqual(- x1, amount(-1) * x1) + self.assertEqual(- x1, -1 * x1) + self.assertEqual(amount("56200.232088"), x1 * y1) + self.assertEqual(amount("56200.232088"), y1 * x1) + self.assertEqual(amount("56200.232088"), x1 * 456.456) + self.assertEqual(amount("56200.232088"), amount(456.456) * x1) + self.assertEqual(amount("56200.232088"), 456.456 * x1) + + x1 *= amount(123.123) + self.assertEqual(amount("15159.273129"), x1) + x1 *= 123.123 + self.assertEqual(amount("1866455.185461867"), x1) + x1 *= 123 + self.assertEqual(amount("229573987.811809641"), x1) + + x2 = amount("123456789123456789.123456789123456789") + + self.assertEqual(amount("15241578780673678546105778311537878.046486820281054720515622620750190521"), + x2 * x2) + + self.assertValid(x1) + self.assertValid(y1) + self.assertValid(x2) + + def testCommodityMultiplication(self): + x0 = amount() + x1 = amount("$123.12") + y1 = amount("$456.45") + x2 = amount(internalAmount("$123.456789")) + x3 = amount("DM 123.45") + x4 = amount("123.45 euro") + x5 = amount("123.45€") + + self.assertEqual(amount("$0.00"), x1 * 0) + self.assertEqual(amount("$0.00"), 0 * x1) + self.assertEqual(x1, x1 * 1) + self.assertEqual(x1, 1 * x1) + self.assertEqual(- x1, x1 * -1) + self.assertEqual(- x1, -1 * x1) + self.assertEqual(internalAmount("$56198.124"), x1 * y1) + self.assertEqual("$56198.12", (x1 * y1).to_string()) + self.assertEqual(internalAmount("$56198.124"), y1 * x1) + self.assertEqual("$56198.12", (y1 * x1).to_string()) + + # Internal amounts retain their precision, even when being + # converted to strings + self.assertEqual(internalAmount("$15199.99986168"), x1 * x2) + self.assertEqual(internalAmount("$15199.99986168"), x2 * x1) + self.assertEqual("$15200.00", (x1 * x2).to_string()) + self.assertEqual("$15199.99986168", (x2 * x1).to_string()) + + self.assertRaises(exceptions.ArithmeticError, lambda: x1 * x0) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 * x1) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 * x0) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 * x3) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 * x4) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 * x5) + + x1 *= amount("123.12") + self.assertEqual(internalAmount("$15158.5344"), x1) + self.assertEqual("$15158.53", x1.to_string()) + x1 *= 123.12 + self.assertEqual(internalAmount("$1866318.755328"), x1) + self.assertEqual("$1866318.76", x1.to_string()) + x1 *= 123 + self.assertEqual(internalAmount("$229557206.905344"), x1) + self.assertEqual("$229557206.91", x1.to_string()) + + x7 = amount(internalAmount("$123456789123456789.123456789123456789")) + + self.assertEqual(internalAmount("$15241578780673678546105778311537878.046486820281054720515622620750190521"), + x7 * x7) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x7) + + def testIntegerDivision(self): + x1 = amount(123) + y1 = amount(456) + + self.assertRaises(exceptions.ArithmeticError, lambda: x1 / 0) + self.assertEqual(amount(0), amount(0) / x1) + self.assertEqual(amount(0), 0 / x1) + self.assertEqual(x1, x1 / 1) + self.assertEqual(amount("0.008130"), amount(1) / x1) + self.assertEqual(amount("0.008130"), 1 / x1) + self.assertEqual(- x1, x1 / -1) + self.assertEqual(- amount("0.008130"), amount(-1) / x1) + self.assertEqual(- amount("0.008130"), -1 / x1) + self.assertEqual(amount("0.269737"), x1 / y1) + self.assertEqual(amount("3.707317"), y1 / x1) + self.assertEqual(amount("0.269737"), x1 / 456) + self.assertEqual(amount("3.707317"), amount(456) / x1) + self.assertEqual(amount("3.707317"), 456 / x1) + + x1 /= amount(456) + self.assertEqual(amount("0.269737"), x1) + x1 /= 456 + self.assertEqual(amount("0.00059152850877193"), x1) + + x4 = amount("123456789123456789123456789") + y4 = amount("56") + + self.assertEqual(amount(1), x4 / x4) + self.assertEqual(amount("2204585520061728377204585.517857"), x4 / y4) + + self.assertValid(x1) + self.assertValid(y1) + self.assertValid(x4) + self.assertValid(y4) + + def testFractionalDivision(self): + x1 = amount(123.123) + y1 = amount(456.456) + + self.assertRaises(exceptions.ArithmeticError, lambda: x1 / 0) + self.assertEqual(amount("0.008121959"), amount(1.0) / x1) + self.assertEqual(amount("0.008121959"), 1.0 / x1) + self.assertEqual(x1, x1 / 1.0) + self.assertEqual(amount("0.008121959"), amount(1.0) / x1) + self.assertEqual(amount("0.008121959"), 1.0 / x1) + self.assertEqual(- x1, x1 / -1.0) + self.assertEqual(- amount("0.008121959"), amount(-1.0) / x1) + self.assertEqual(- amount("0.008121959"), -1.0 / x1) + self.assertEqual(amount("0.269736842105263"), x1 / y1) + self.assertEqual(amount("3.707317073170732"), y1 / x1) + self.assertEqual(amount("0.269736842105263"), x1 / 456.456) + self.assertEqual(amount("3.707317073170732"), amount(456.456) / x1) + self.assertEqual(amount("3.707317073170732"), 456.456 / x1) + + x1 /= amount(456.456) + self.assertEqual(amount("0.269736842105263"), x1) + x1 /= 456.456 + self.assertEqual(amount("0.000590937225286255411255411255411255411"), x1) + x1 /= 456 + self.assertEqual(amount("0.000001295914967733016252753094858358016252192982456140350877192982456140350877192982"), x1) + + x4 = amount("1234567891234567.89123456789") + y4 = amount("56.789") + + self.assertEqual(amount(1.0), x4 / x4) + self.assertEqual(amount("21739560323910.7554497273748437197344556164046"), x4 / y4) + + self.assertValid(x1) + self.assertValid(y1) + self.assertValid(x4) + self.assertValid(y4) + + def testCommodityDivision(self): + x0 = amount() + x1 = amount("$123.12") + y1 = amount("$456.45") + x2 = amount(internalAmount("$123.456789")) + x3 = amount("DM 123.45") + x4 = amount("123.45 euro") + x5 = amount("123.45€") + + self.assertRaises(exceptions.ArithmeticError, lambda: x1 / 0) + self.assertEqual(amount("$0.00"), 0 / x1) + self.assertEqual(x1, x1 / 1) + self.assertEqual(internalAmount("$0.00812216"), 1 / x1) + self.assertEqual(- x1, x1 / -1) + self.assertEqual(internalAmount("$-0.00812216"), -1 / x1) + self.assertEqual(internalAmount("$0.26973382"), x1 / y1) + self.assertEqual("$0.27", (x1 / y1).to_string()) + self.assertEqual(internalAmount("$3.70735867"), y1 / x1) + self.assertEqual("$3.71", (y1 / x1).to_string()) + + # Internal amounts retain their precision, even when being + # converted to strings + self.assertEqual(internalAmount("$0.99727201"), x1 / x2) + self.assertEqual(internalAmount("$1.00273545321637426901"), x2 / x1) + self.assertEqual("$1.00", (x1 / x2).to_string()) + self.assertEqual("$1.00273545321637426901", (x2 / x1).to_string()) + + self.assertRaises(exceptions.ArithmeticError, lambda: x1 / x0) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 / x1) + self.assertRaises(exceptions.ArithmeticError, lambda: x0 / x0) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 / x3) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 / x4) + self.assertRaises(exceptions.ArithmeticError, lambda: x1 / x5) + + x1 /= amount("123.12") + self.assertEqual(internalAmount("$1.00"), x1) + self.assertEqual("$1.00", x1.to_string()) + x1 /= 123.12 + self.assertEqual(internalAmount("$0.00812216"), x1) + self.assertEqual("$0.01", x1.to_string()) + x1 /= 123 + self.assertEqual(internalAmount("$0.00006603"), x1) + self.assertEqual("$0.00", x1.to_string()) + + x6 = amount(internalAmount("$237235987235987.98723987235978")) + x7 = amount(internalAmount("$123456789123456789.123456789123456789")) + + self.assertEqual(amount("$1"), x7 / x7) + self.assertEqual(internalAmount("$0.0019216115121765559608381226612019501046413574469262"), + x6 / x7) + self.assertEqual(internalAmount("$520.39654928343335571379527154924040947271699678158689736256"), + x7 / x6) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + + def testNegation(self): + x0 = amount() + x1 = amount(-123456) + x3 = amount(-123.456) + x5 = amount("-123456") + x6 = amount("-123.456") + x7 = amount("-123456") + x8 = amount("-123.456") + x9 = amount(- x3) + + self.assertRaises(exceptions.ArithmeticError, lambda: x0.negate()) + self.assertEqual(x5, x1) + self.assertEqual(x7, x1) + self.assertEqual(x6, x3) + self.assertEqual(x8, x3) + self.assertEqual(- x6, x9) + self.assertEqual(x3.negate(), x9) + + x10 = amount(x9.negate()) + + self.assertEqual(x3, x10) + + self.assertValid(x1) + self.assertValid(x3) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + self.assertValid(x8) + self.assertValid(x9) + self.assertValid(x10) + + def testCommodityNegation(self): + x1 = amount("$123.45") + x2 = amount("-$123.45") + x3 = amount("$-123.45") + x4 = amount("DM 123.45") + x5 = amount("-DM 123.45") + x6 = amount("DM -123.45") + x7 = amount("123.45 euro") + x8 = amount("-123.45 euro") + x9 = amount("123.45€") + x10 = amount("-123.45€") + + self.assertEqual(amount("$-123.45"), - x1) + self.assertEqual(amount("$123.45"), - x2) + self.assertEqual(amount("$123.45"), - x3) + self.assertEqual(amount("DM -123.45"), - x4) + self.assertEqual(amount("DM 123.45"), - x5) + self.assertEqual(amount("DM 123.45"), - x6) + self.assertEqual(amount("-123.45 euro"), - x7) + self.assertEqual(amount("123.45 euro"), - x8) + self.assertEqual(amount("-123.45€"), - x9) + self.assertEqual(amount("123.45€"), - x10) + + self.assertEqual(amount("$-123.45"), x1.negate()) + self.assertEqual(amount("$123.45"), x2.negate()) + self.assertEqual(amount("$123.45"), x3.negate()) + + self.assertEqual("$-123.45", (- x1).to_string()) + self.assertEqual("$123.45", (- x2).to_string()) + self.assertEqual("$123.45", (- x3).to_string()) + self.assertEqual("DM -123.45", (- x4).to_string()) + self.assertEqual("DM 123.45", (- x5).to_string()) + self.assertEqual("DM 123.45", (- x6).to_string()) + self.assertEqual("-123.45 euro", (- x7).to_string()) + self.assertEqual("123.45 euro", (- x8).to_string()) + self.assertEqual("-123.45€", (- x9).to_string()) + self.assertEqual("123.45€", (- x10).to_string()) + + self.assertEqual(amount("$-123.45"), x1.negate()) + self.assertEqual(amount("$123.45"), x2.negate()) + self.assertEqual(amount("$123.45"), x3.negate()) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + self.assertValid(x7) + self.assertValid(x8) + self.assertValid(x9) + self.assertValid(x10) + + def testAbs(self): + x0 = amount() + x1 = amount(-1234) + x2 = amount(1234) + + self.assertRaises(exceptions.ArithmeticError, lambda: x0.abs()) + self.assertEqual(amount(1234), x1.abs()) + self.assertEqual(amount(1234), x2.abs()) + + self.assertValid(x0) + self.assertValid(x1) + self.assertValid(x2) + + def testCommodityAbs(self): + x1 = amount("$-1234.56") + x2 = amount("$1234.56") + + self.assertEqual(amount("$1234.56"), x1.abs()) + self.assertEqual(amount("$1234.56"), x2.abs()) + + self.assertValid(x1) + self.assertValid(x2) + + def testFractionalRound(self): + x0 = amount() + x1 = amount("1234.567890") + + self.assertRaises(exceptions.ArithmeticError, lambda: x0.precision) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.round()) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.round(2)) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.unround()) + self.assertEqual(6, x1.precision) + + x1b = amount(x1.unround()) + + self.assertEqual(x1b.precision, x1b.unround().precision) + + y7 = amount(x1.round(7)) + y6 = amount(x1.round(6)) + y5 = amount(x1.round(5)) + y4 = amount(x1.round(4)) + y3 = amount(x1.round(3)) + y2 = amount(x1.round(2)) + y1 = amount(x1.round(1)) + y0 = amount(x1.round(0)) + + self.assertEqual(6, y7.precision) + self.assertEqual(6, y6.precision) + self.assertEqual(5, y5.precision) + self.assertEqual(4, y4.precision) + self.assertEqual(3, y3.precision) + self.assertEqual(2, y2.precision) + self.assertEqual(1, y1.precision) + self.assertEqual(0, y0.precision) + + self.assertEqual(amount("1234.56789"), y7) + self.assertEqual(amount("1234.56789"), y6) + self.assertEqual(amount("1234.56789"), y5) + self.assertEqual(amount("1234.5679"), y4) + self.assertEqual(amount("1234.568"), y3) + self.assertEqual(amount("1234.57"), y2) + self.assertEqual(amount("1234.6"), y1) + self.assertEqual(amount("1235"), y0) + + x2 = amount("9876.543210") + + self.assertEqual(amount("9876.543210"), x2.round(6)) + self.assertEqual(amount("9876.54321"), x2.round(5)) + self.assertEqual(amount("9876.5432"), x2.round(4)) + self.assertEqual(amount("9876.543"), x2.round(3)) + self.assertEqual(amount("9876.54"), x2.round(2)) + self.assertEqual(amount("9876.5"), x2.round(1)) + self.assertEqual(amount("9877"), x2.round(0)) + + x3 = amount("-1234.567890") + + self.assertEqual(amount("-1234.56789"), x3.round(6)) + self.assertEqual(amount("-1234.56789"), x3.round(5)) + self.assertEqual(amount("-1234.5679"), x3.round(4)) + self.assertEqual(amount("-1234.568"), x3.round(3)) + self.assertEqual(amount("-1234.57"), x3.round(2)) + self.assertEqual(amount("-1234.6"), x3.round(1)) + self.assertEqual(amount("-1235"), x3.round(0)) + + x4 = amount("-9876.543210") + + self.assertEqual(amount("-9876.543210"), x4.round(6)) + self.assertEqual(amount("-9876.54321"), x4.round(5)) + self.assertEqual(amount("-9876.5432"), x4.round(4)) + self.assertEqual(amount("-9876.543"), x4.round(3)) + self.assertEqual(amount("-9876.54"), x4.round(2)) + self.assertEqual(amount("-9876.5"), x4.round(1)) + self.assertEqual(amount("-9877"), x4.round(0)) + + x5 = amount("0.0000000000000000000000000000000000001") + + self.assertEqual(amount("0.0000000000000000000000000000000000001"), + x5.round(37)) + self.assertEqual(amount(0), x5.round(36)) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + + def testCommodityRound(self): + x1 = amount(internalAmount("$1234.567890")) + + self.assertEqual(internalAmount("$1234.56789"), x1.round(6)) + self.assertEqual(internalAmount("$1234.56789"), x1.round(5)) + self.assertEqual(internalAmount("$1234.5679"), x1.round(4)) + self.assertEqual(internalAmount("$1234.568"), x1.round(3)) + self.assertEqual(amount("$1234.57"), x1.round(2)) + self.assertEqual(amount("$1234.6"), x1.round(1)) + self.assertEqual(amount("$1235"), x1.round(0)) + + x2 = amount(internalAmount("$9876.543210")) + + self.assertEqual(internalAmount("$9876.543210"), x2.round(6)) + self.assertEqual(internalAmount("$9876.54321"), x2.round(5)) + self.assertEqual(internalAmount("$9876.5432"), x2.round(4)) + self.assertEqual(internalAmount("$9876.543"), x2.round(3)) + self.assertEqual(amount("$9876.54"), x2.round(2)) + self.assertEqual(amount("$9876.5"), x2.round(1)) + self.assertEqual(amount("$9877"), x2.round(0)) + + x3 = amount(internalAmount("$-1234.567890")) + + self.assertEqual(internalAmount("$-1234.56789"), x3.round(6)) + self.assertEqual(internalAmount("$-1234.56789"), x3.round(5)) + self.assertEqual(internalAmount("$-1234.5679"), x3.round(4)) + self.assertEqual(internalAmount("$-1234.568"), x3.round(3)) + self.assertEqual(amount("$-1234.57"), x3.round(2)) + self.assertEqual(amount("$-1234.6"), x3.round(1)) + self.assertEqual(amount("$-1235"), x3.round(0)) + + x4 = amount(internalAmount("$-9876.543210")) + + self.assertEqual(internalAmount("$-9876.543210"), x4.round(6)) + self.assertEqual(internalAmount("$-9876.54321"), x4.round(5)) + self.assertEqual(internalAmount("$-9876.5432"), x4.round(4)) + self.assertEqual(internalAmount("$-9876.543"), x4.round(3)) + self.assertEqual(amount("$-9876.54"), x4.round(2)) + self.assertEqual(amount("$-9876.5"), x4.round(1)) + self.assertEqual(amount("$-9877"), x4.round(0)) + + x5 = amount("$123.45") + + x5 *= 100.12 + + self.assertEqual(internalAmount("$12359.814"), x5) + self.assertEqual("$12359.81", x5.to_string()) + self.assertEqual("$12359.814", x5.to_fullstring()) + self.assertEqual("$12359.814", x5.unround().to_string()) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + + def testCommodityDisplayRound(self): + x1 = amount("$0.85") + x2 = amount("$0.1") + + x1 *= 0.19 + + self.assertNotEqual(amount("$0.16"), x1) + self.assertEqual(internalAmount("$0.1615"), x1) + self.assertEqual("$0.16", x1.to_string()) + + self.assertEqual(amount("$0.10"), x2) + self.assertNotEqual(internalAmount("$0.101"), x2) + self.assertEqual("$0.10", x2.to_string()) + + x1 *= 7 + + self.assertNotEqual(amount("$1.13"), x1) + self.assertEqual(internalAmount("$1.1305"), x1) + self.assertEqual("$1.13", x1.to_string()) + + def testReduction(self): + x0 = amount() + x1 = amount("60s") + x2 = amount("600s") + x3 = amount("6000s") + x4 = amount("360000s") + x5 = amount("10m") + x6 = amount("100m") + x7 = amount("1000m") + x8 = amount("10000m") + x9 = amount("10h") + x10 = amount("100h") + x11 = amount("1000h") + x12 = amount("10000h") + + self.assertRaises(exceptions.ArithmeticError, lambda: x0.reduce()) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.unreduce()) + self.assertEqual(x2, x5) + self.assertEqual(x3, x6) + self.assertEqual(x4, x10) + self.assertEqual("100.0h", x4.unreduce().to_string()) + + def testSign(self): + x0 = amount() + x1 = amount("0.0000000000000000000000000000000000001") + x2 = amount("-0.0000000000000000000000000000000000001") + x3 = amount("1") + x4 = amount("-1") + + self.assertRaises(exceptions.ArithmeticError, lambda: x0.sign()) + self.assertTrue(x1.sign() > 0) + self.assertTrue(x2.sign() < 0) + self.assertTrue(x3.sign() > 0) + self.assertTrue(x4.sign() < 0) + + self.assertValid(x0) + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + + def testCommoditySign(self): + x1 = amount(internalAmount("$0.0000000000000000000000000000000000001")) + x2 = amount(internalAmount("$-0.0000000000000000000000000000000000001")) + x3 = amount("$1") + x4 = amount("$-1") + + self.assertTrue(x1.sign() != 0) + self.assertTrue(x2.sign() != 0) + self.assertTrue(x3.sign() > 0) + self.assertTrue(x4.sign() < 0) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + + def testTruth(self): + x0 = amount() + x1 = amount("1234") + x2 = amount("1234.56") + + self.assertRaises(exceptions.ArithmeticError, lambda: 1 if x0 else 0) + + self.assertTrue(x1) + self.assertTrue(x2) + + self.assertValid(x0) + self.assertValid(x1) + self.assertValid(x2) + + def testCommodityTruth(self): + x1 = amount("$1234") + x2 = amount("$1234.56") + + if x1: + self.assertTrue(True) + else: + self.assertTrue(False) + + if x2: + self.assertTrue(True) + else: + self.assertTrue(False) + + self.assertValid(x1) + self.assertValid(x2) + + def testForZero(self): + x0 = amount() + x1 = amount("0.000000000000000000001") + + self.assertTrue(x1) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.is_zero()) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.is_realzero()) + self.assertFalse(x1.is_zero()) + self.assertFalse(x1.is_realzero()) + + self.assertValid(x0) + self.assertValid(x1) + + def testCommodityForZero(self): + x1 = amount(internalAmount("$0.000000000000000000001")) + + self.assertFalse(x1) + self.assertTrue(x1.is_zero()) + self.assertFalse(x1.is_realzero()) + + self.assertValid(x1) + + def testIntegerConversion(self): + x0 = amount() + x1 = amount(123456) + x2 = amount("12345682348723487324") + + self.assertRaises(exceptions.ArithmeticError, lambda: x0.to_long()) + self.assertRaises(exceptions.ArithmeticError, lambda: x0.to_double()) + self.assertFalse(x2.fits_in_long()) + self.assertEqual(123456, x1.to_long()) + self.assertEqual(123456.0, x1.to_double()) + self.assertEqual("123456", x1.to_string()) + self.assertEqual("123456", x1.quantity_string) + + self.assertValid(x1) + + def testFractionalConversion(self): + x1 = amount(1234.56) + x2 = amount("1234.5683787634678348734") + + self.assertRaises(exceptions.ArithmeticError, lambda: x1.to_long()) # loses precision + self.assertRaises(exceptions.ArithmeticError, lambda: x2.to_double()) # loses precision + self.assertFalse(x2.fits_in_double()) + self.assertEqual(1234, x1.to_long(True)) + self.assertEqual(1234.56, x1.to_double()) + self.assertEqual("1234.56", x1.to_string()) + self.assertEqual("1234.56", x1.quantity_string) + + self.assertValid(x1) + + def testCommodityConversion(self): + x1 = amount("$1234.56") + + self.assertRaises(exceptions.ArithmeticError, lambda: x1.to_long()) # loses precision + self.assertEqual(1234, x1.to_long(True)) + self.assertEqual(1234.56, x1.to_double()) + self.assertEqual("$1234.56", x1.to_string()) + self.assertEqual("1234.56", x1.quantity_string) + + self.assertValid(x1) + + def testPrinting(self): + pass + #x0 = amount() + #x1 = amount("982340823.380238098235098235098235098") + # + #bufstr = StringIO() + #self.assertRaises(exceptions.ArithmeticError, lambda: bufstr.write(x0)) + # + #bufstr = StringIO() + #bufstr.write(x1) + # + #self.assertEqual("982340823.380238098235098235098235098", + # bufstr.getvalue()) + # + #self.assertValid(x0) + #self.assertValid(x1) + + def testCommodityPrinting(self): + pass + #x1 = amount(internalAmount("$982340823.386238098235098235098235098")) + #x2 = amount("$982340823.38") + # + #bufstr = StringIO() + #bufstr.write(x1) + # + #self.assertEqual("$982340823.386238098235098235098235098", + # bufstr.getvalue()) + # + #bufstr = StringIO() + #bufstr.write((x1 * x2).to_string()) + # + #self.assertEqual("$964993493285024293.18099172508158508135413499124", + # bufstr.getvalue()) + # + #bufstr = StringIO() + #bufstr.write((x2 * x1).to_string()) + # + #self.assertEqual("$964993493285024293.18", bufstr.getvalue()) + # + #self.assertValid(x1) + #self.assertValid(x2) + + def testSerialization(self): + pass + #x0 = amount() + #x1 = amount("$8,192.34") + #x2 = amount("8192.34") + #x3 = amount("8192.34") + #x4 = amount("-8192.34") + #x5 = amount(x4) + # + ## Force x3's pointer to actually be set to null_commodity + ##x3.set_commodity(*x3.current_pool.null_commodity) + # + #buf = "" + #storage = StringIO() + #self.assertRaises(exceptions.ArithmeticError, lambda: x0.write(storage)) + #x1.write(storage) + #x2.write(storage) + #x3.write(storage) + #x4.write(storage) + #x5.write(storage) + #buf = storage.getvalue() + # + #x1b = amount() + #x2b = amount() + #x3b = amount() + #x4b = amount() + #x5b = amount() + # + #storage = StringIO(buf) + #x1b.read(storage) + #x2b.read(storage) + #x3b.read(storage) + #x4b.read(storage) + #x5b.read(storage) + # + #self.assertEqual(x1, x1b) + #self.assertEqual(x2, x2b) + #self.assertEqual(x3, x3b) + #self.assertEqual(x4, x4b) + # + #ptr = buf.c_str() + # + #x1c = amount() + #x2c = amount() + #x3c = amount() + #x4c = amount() + #x5c = amount() + # + #x1c.read(ptr) + #x2c.read(ptr) + #x3c.read(ptr) + #x4c.read(ptr) + #x5c.read(ptr) + # + #self.assertEqual(x1, x1b) + #self.assertEqual(x2, x2b) + #self.assertEqual(x3, x3b) + #self.assertEqual(x4, x4b) + # + #self.assertValid(x1) + #self.assertValid(x2) + #self.assertValid(x3) + #self.assertValid(x4) + #self.assertValid(x1b) + #self.assertValid(x2b) + #self.assertValid(x3b) + #self.assertValid(x4b) + #self.assertValid(x1c) + #self.assertValid(x2c) + #self.assertValid(x3c) + #self.assertValid(x4c) + + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(t_amountTestCase) + +if __name__ == '__main__': + unittest.main() diff --git a/test/utility/t_times.cc b/test/utility/t_times.cc new file mode 100644 index 00000000..05fd34ce --- /dev/null +++ b/test/utility/t_times.cc @@ -0,0 +1,81 @@ +#include "t_times.h" + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(DateTimeTestCase, "utility"); + +void DateTimeTestCase::setUp() {} +void DateTimeTestCase::tearDown() {} + +void DateTimeTestCase::testConstructors() +{ +#if 0 + std::time_t time_t_now = std::time(NULL); + struct tm * moment = std::localtime(&time_t_now); + + std::time_t localMoment = std::mktime(moment); + + ptime d0; + ptime d1(parse_datetime("1990/01/01")); + ptime d3(boost::posix_time::from_time_t(localMoment)); + ptime d4(parse_datetime("2006/12/25")); + //ptime d5(parse_datetime("12/25")); + ptime d6(parse_datetime("2006.12.25")); + //ptime d7(parse_datetime("12.25")); + ptime d8(parse_datetime("2006-12-25")); + //ptime d9(parse_datetime("12-25")); +#if 0 + ptime d10(parse_datetime("tue")); + ptime d11(parse_datetime("tuesday")); + ptime d12(parse_datetime("feb")); + ptime d13(parse_datetime("february")); + ptime d14(parse_datetime("2006")); +#endif + ptime d15(d3); + + assertTrue(d0.is_not_a_date_time()); + assertFalse(d1.is_not_a_date_time()); + assertFalse(d4.is_not_a_date_time()); + + assertTrue(now > d1); + //assertTrue(now <= d3); + assertTrue(now > d4); + + assertEqual(d3, d15); + assertEqual(d4, d6); + assertEqual(d4, d8); + //assertEqual(d5, d7); + //assertEqual(d5, d9); +#if 0 + assertEqual(d10, d11); + assertEqual(d12, d13); +#endif + +#if 0 + assertThrow(parse_datetime("2007/02/29"), datetime_error *); + assertThrow(parse_datetime("2007/13/01"), datetime_error *); + assertThrow(parse_datetime("2007/00/01"), datetime_error *); + assertThrow(parse_datetime("2007/01/00"), datetime_error *); + assertThrow(parse_datetime("2007/00/00"), datetime_error *); + assertThrow(parse_datetime("2007/05/32"), datetime_error *); + + assertThrow(parse_datetime("2006x/12/25"), datetime_error *); + assertThrow(parse_datetime("2006/12x/25"), datetime_error *); + //assertThrow(parse_datetime("2006/12/25x"), datetime_error *); + + assertThrow(parse_datetime("feb/12/25"), datetime_error *); + assertThrow(parse_datetime("2006/mon/25"), datetime_error *); + assertThrow(parse_datetime("2006/12/web"), datetime_error *); + + assertThrow(parse_datetime("12*25"), datetime_error *); + + assertThrow(parse_datetime("tuf"), datetime_error *); + assertThrow(parse_datetime("tufsday"), datetime_error *); + assertThrow(parse_datetime("fec"), datetime_error *); + assertThrow(parse_datetime("fecruary"), datetime_error *); + assertThrow(parse_datetime("207x"), datetime_error *); + assertThrow(parse_datetime("hello"), datetime_error *); + + interval_t i1; + interval_t i2; +#endif +#endif +} diff --git a/test/utility/t_times.h b/test/utility/t_times.h new file mode 100644 index 00000000..5bbadf21 --- /dev/null +++ b/test/utility/t_times.h @@ -0,0 +1,28 @@ +#ifndef _T_TIMES_H +#define _T_TIMES_H + +#include "UnitTests.h" + +class DateTimeTestCase : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE(DateTimeTestCase); + + CPPUNIT_TEST(testConstructors); + + CPPUNIT_TEST_SUITE_END(); + +public: + DateTimeTestCase() {} + virtual ~DateTimeTestCase() {} + + virtual void setUp(); + virtual void tearDown(); + + void testConstructors(); + +private: + DateTimeTestCase(const DateTimeTestCase ©); + void operator=(const DateTimeTestCase ©); +}; + +#endif /* _T_TIMES_H */ diff --git a/test/utility/t_utils.cc b/test/utility/t_utils.cc new file mode 100644 index 00000000..eda84a3a --- /dev/null +++ b/test/utility/t_utils.cc @@ -0,0 +1,10 @@ +#include "t_utils.h" + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(UtilitiesTestCase, "utility"); + +void UtilitiesTestCase::setUp() {} +void UtilitiesTestCase::tearDown() {} + +void UtilitiesTestCase::testConstructors() +{ +} diff --git a/test/utility/t_utils.h b/test/utility/t_utils.h new file mode 100644 index 00000000..97154bae --- /dev/null +++ b/test/utility/t_utils.h @@ -0,0 +1,28 @@ +#ifndef _T_UTILS_H +#define _T_UTILS_H + +#include "UnitTests.h" + +class UtilitiesTestCase : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE(UtilitiesTestCase); + + CPPUNIT_TEST(testConstructors); + + CPPUNIT_TEST_SUITE_END(); + +public: + UtilitiesTestCase() {} + virtual ~UtilitiesTestCase() {} + + virtual void setUp(); + virtual void tearDown(); + + void testConstructors(); + +private: + UtilitiesTestCase(const UtilitiesTestCase ©); + void operator=(const UtilitiesTestCase ©); +}; + +#endif /* _T_UTILS_H */ diff --git a/tests/amounts.h b/tests/amounts.h deleted file mode 100644 index a72e57d2..00000000 --- a/tests/amounts.h +++ /dev/null @@ -1,169 +0,0 @@ -#ifndef __TESTAMOUNT_H -#define __TESTAMOUNT_H - -#include <cxxtest/TestSuite.h> - -#include <amount.h> - -using namespace ledger; - -class TestAmount : public CxxTest::TestSuite -{ -public: - void testCreateAmountWithoutCommodityFromInteger() - { - amount_t a((long int)42); - TS_ASSERT_EQUALS("", a.commodity().symbol()); - TS_ASSERT_EQUALS("", a.commodity().base_symbol()); - TS_ASSERT_EQUALS("42", a.quantity_string()); - } - - void testCreateAmountWithoutCommodity() - { - amount_t a("42"); - TS_ASSERT_EQUALS("", a.commodity().symbol()); - TS_ASSERT_EQUALS("", a.commodity().base_symbol()); - TS_ASSERT_EQUALS("42", a.quantity_string()); - } - - void testCreateAmountWithPrefixCommodity() - { - amount_t *a; - a = new amount_t("$ 42"); - TS_ASSERT_EQUALS("$", a->commodity().symbol()); - TS_ASSERT_EQUALS("$", a->commodity().base_symbol()); - TS_ASSERT_EQUALS("42", a->quantity_string()); - } - - void testCreateAmountWithPostfixCommodity() - { - amount_t *a; - a = new amount_t("42 GLD"); - TS_ASSERT_EQUALS("GLD", a->commodity().symbol()); - TS_ASSERT_EQUALS("GLD", a->commodity().base_symbol()); - TS_ASSERT_EQUALS("42", a->quantity_string()); - } - - void testCreateAmountWithPrefixCommodityContainingSpace() - { - amount_t *a; - a = new amount_t("\"one dollar\" 42"); - TS_ASSERT_EQUALS("\"one dollar\"", a->commodity().symbol()); - TS_ASSERT_EQUALS("one dollar", a->commodity().base_symbol()); - TS_ASSERT_EQUALS("42", a->quantity_string()); - } - - void testCreateAmountWithPostfixCommodityContainingSpace() - { - amount_t *a; - a = new amount_t("42 \"swedish crowns\""); - TS_ASSERT_EQUALS("\"swedish crowns\"", a->commodity().symbol()); - TS_ASSERT_EQUALS("swedish crowns", a->commodity().base_symbol()); - TS_ASSERT_EQUALS("42", a->quantity_string()); - } - - void testCreateAmountWithDirectPrefixCommodity() - { - amount_t *a; - a = new amount_t("$42"); - TS_ASSERT_EQUALS("$", a->commodity().symbol()); - TS_ASSERT_EQUALS("$", a->commodity().base_symbol()); - TS_ASSERT_EQUALS("42", a->quantity_string()); - } - - void testCreateAmountWithDirectPostfixCommodity() - { - amount_t *a; - a = new amount_t("42GLD"); - TS_ASSERT_EQUALS("GLD", a->commodity().symbol()); - TS_ASSERT_EQUALS("GLD", a->commodity().base_symbol()); - TS_ASSERT_EQUALS("42", a->quantity_string()); - } - - void testCannotCreateAmountWithoutQuantity() - { - TS_ASSERT_THROWS(new amount_t("$"), amount_error*); - } - - void testCreateBiiigIntegerAmount() - { - amount_t a("12345678901234567890123456789012345678901234567890"); - TS_ASSERT_EQUALS("12345678901234567890123456789012345678901234567890", - a.quantity_string()); - } - - void testCreateBiiigDecimalAmount() - { - amount_t a("12345678.901234567890123456789012345678901234567890"); - TS_ASSERT_EQUALS("12345678.901234567890123456789012345678901234567890", - a.quantity_string()); - } - - void testCreateCommodityAnnotatedWithEntry() - { - amount_t a("10 AAPL (entry 6)"); - TS_ASSERT_EQUALS("10", a.quantity_string()); - commodity_t c = a.commodity(); - TS_ASSERT_EQUALS("AAPL", c.symbol()); - TS_ASSERT_EQUALS("AAPL", c.base_symbol()); - TS_ASSERT(c.annotated); - //TODO: check entry annotation - } - - void testCreateCommodityAnnotatedWithEntry2() - { - amount_t *a = new amount_t("10 AAPL (entry 6)"); - TS_ASSERT_EQUALS("10", a->quantity_string()); - commodity_t c = a->commodity(); - TS_ASSERT_EQUALS("AAPL", c.symbol()); - TS_ASSERT_EQUALS("AAPL", c.base_symbol()); - TS_ASSERT(c.annotated); - //TODO: check entry annotation - } - - void testAddTwoAmountsWithoutCommodity() - { - amount_t a("6"); - amount_t b("9"); - TS_ASSERT_EQUALS(* new amount_t((long int)15), a+b); - } - - void testAddTwoAmountsWithSameCommodity() - { - amount_t a("$ 6"); - amount_t b("$ 9"); - TS_ASSERT_EQUALS(* new amount_t("$ 15"), a+b); - } - - void testCannotAddTwoAmountsWithDifferentCommodities() - { - amount_t a("$ 6"); - amount_t b("9 GLD"); - TS_ASSERT_THROWS(a+b, amount_error*); - } - - void testCompareTwoAmountsWithSameCommodity() - { - amount_t a("6 SCOX"); - amount_t b("9 SCOX"); - TS_ASSERT(a < b); - TS_ASSERT(a <= b); - TS_ASSERT(!(a > b)); - TS_ASSERT(!(a >= b)); - TS_ASSERT(!(a == b)); - } - - void testCannotCompareTwoAmountsWithDifferentCommodities() - { - amount_t a("$ 6"); - amount_t b("9 GLD"); - - TS_ASSERT_THROWS(a < b, amount_error*); - TS_ASSERT_THROWS(a <= b, amount_error*); - TS_ASSERT_THROWS(a > b, amount_error*); - TS_ASSERT_THROWS(a >= b, amount_error*); - TS_ASSERT(!(a == b)); - } -}; - -#endif // __TESTAMOUNT_H diff --git a/tests/baseline/1001 b/tests/baseline/1001 deleted file mode 100644 index f570768a..00000000 --- a/tests/baseline/1001 +++ /dev/null @@ -1 +0,0 @@ -Error: "tests/cases/1001.dat", line 10: Only one transaction with null amount allowed per entry diff --git a/tests/baseline/1002 b/tests/baseline/1002 deleted file mode 100644 index 8ca4d8d4..00000000 --- a/tests/baseline/1002 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - xgain -20 - Liabilities:MasterCard diff --git a/tests/baseline/1003 b/tests/baseline/1003 deleted file mode 100644 index 61e03c48..00000000 --- a/tests/baseline/1003 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - xtime -20 - Liabilities:MasterCard diff --git a/tests/baseline/1004 b/tests/baseline/1004 deleted file mode 100644 index dbbfea17..00000000 --- a/tests/baseline/1004 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - xopti -20 - Liabilities:MasterCard diff --git a/tests/baseline/1005 b/tests/baseline/1005 deleted file mode 100644 index 8541a38d..00000000 --- a/tests/baseline/1005 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - xgain 0 - Liabilities:MasterCard diff --git a/tests/baseline/1006 b/tests/baseline/1006 deleted file mode 100644 index 0ea315f9..00000000 --- a/tests/baseline/1006 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - xtime 0 - Liabilities:MasterCard diff --git a/tests/baseline/1007 b/tests/baseline/1007 deleted file mode 100644 index 40f2827a..00000000 --- a/tests/baseline/1007 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - xopti 0 - Liabilities:MasterCard diff --git a/tests/baseline/1008 b/tests/baseline/1008 deleted file mode 100644 index 385151b2..00000000 --- a/tests/baseline/1008 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - xgain $-20.00 - Equity:Options diff --git a/tests/baseline/1009 b/tests/baseline/1009 deleted file mode 100644 index 127ad02a..00000000 --- a/tests/baseline/1009 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - xtime $-20.00 - Equity:Options diff --git a/tests/baseline/1010 b/tests/baseline/1010 deleted file mode 100644 index e408036b..00000000 --- a/tests/baseline/1010 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - xopti $-20.00 - Equity:Options diff --git a/tests/baseline/1011 b/tests/baseline/1011 deleted file mode 100644 index 9c70db18..00000000 --- a/tests/baseline/1011 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - xgain 0 - Equity:Options diff --git a/tests/baseline/1012 b/tests/baseline/1012 deleted file mode 100644 index 890005ab..00000000 --- a/tests/baseline/1012 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - xtime 0 - Equity:Options diff --git a/tests/baseline/1013 b/tests/baseline/1013 deleted file mode 100644 index 1b7bb71e..00000000 --- a/tests/baseline/1013 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - xopti 0 - Equity:Options diff --git a/tests/baseline/1014 b/tests/baseline/1014 deleted file mode 100644 index 384133b6..00000000 --- a/tests/baseline/1014 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - Capital Gains $-20.00 - Liabilities:MasterCard diff --git a/tests/baseline/1015 b/tests/baseline/1015 deleted file mode 100644 index 46d5f454..00000000 --- a/tests/baseline/1015 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - time -20 - Liabilities:MasterCard diff --git a/tests/baseline/1016 b/tests/baseline/1016 deleted file mode 100644 index 7fa39204..00000000 --- a/tests/baseline/1016 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - Brokerage:Options -20.00 COMP - Liabilities:MasterCard diff --git a/tests/baseline/1017 b/tests/baseline/1017 deleted file mode 100644 index 14b37f8b..00000000 --- a/tests/baseline/1017 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - Capital Gains 0 - Liabilities:MasterCard diff --git a/tests/baseline/1018 b/tests/baseline/1018 deleted file mode 100644 index 9f3761f8..00000000 --- a/tests/baseline/1018 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - time 0 - Liabilities:MasterCard diff --git a/tests/baseline/1019 b/tests/baseline/1019 deleted file mode 100644 index 10f14f4e..00000000 --- a/tests/baseline/1019 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - Brokerage:Options 0 - Liabilities:MasterCard diff --git a/tests/baseline/1020 b/tests/baseline/1020 deleted file mode 100644 index f1547ca7..00000000 --- a/tests/baseline/1020 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - Expenses -20 - Liabilities:MasterCard diff --git a/tests/baseline/1021 b/tests/baseline/1021 deleted file mode 100644 index 7e3fe64d..00000000 --- a/tests/baseline/1021 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 stock optionx - Expenses 0 - Liabilities:MasterCard diff --git a/tests/baseline/1022 b/tests/baseline/1022 deleted file mode 100644 index 953a3c02..00000000 --- a/tests/baseline/1022 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - Capital Gains $-20.00 - Equity:Options diff --git a/tests/baseline/1023 b/tests/baseline/1023 deleted file mode 100644 index 62274097..00000000 --- a/tests/baseline/1023 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - time $-20.00 - Equity:Options diff --git a/tests/baseline/1024 b/tests/baseline/1024 deleted file mode 100644 index 8272dfd9..00000000 --- a/tests/baseline/1024 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - Brokerage:Options -20.00 COMP - Equity:Options diff --git a/tests/baseline/1025 b/tests/baseline/1025 deleted file mode 100644 index 52dc8139..00000000 --- a/tests/baseline/1025 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - Capital Gains 0 - Equity:Options diff --git a/tests/baseline/1026 b/tests/baseline/1026 deleted file mode 100644 index 5071338a..00000000 --- a/tests/baseline/1026 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - time 0 - Equity:Options diff --git a/tests/baseline/1027 b/tests/baseline/1027 deleted file mode 100644 index 31d7c8ab..00000000 --- a/tests/baseline/1027 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - Brokerage:Options 45.60 COMP - Equity:Options diff --git a/tests/baseline/1028 b/tests/baseline/1028 deleted file mode 100644 index 8272dfd9..00000000 --- a/tests/baseline/1028 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - Brokerage:Options -20.00 COMP - Equity:Options diff --git a/tests/baseline/1029 b/tests/baseline/1029 deleted file mode 100644 index 31d7c8ab..00000000 --- a/tests/baseline/1029 +++ /dev/null @@ -1,4 +0,0 @@ - -2006/10/20 Stock option vesting schedule - Brokerage:Options 45.60 COMP - Equity:Options diff --git a/tests/cases/1001.dat b/tests/cases/1001.dat deleted file mode 100644 index c781fb4f..00000000 --- a/tests/cases/1001.dat +++ /dev/null @@ -1,10 +0,0 @@ -1.00s = 100c -1.00G = 100s - -D 1.00G - -2006/03/14 Opening Balances - Assets:Ebonheart 63869c - Assets:Tajer 248720c - Assets:Gruulmorg - Equity:Gold diff --git a/tests/cases/1002.dat b/tests/cases/1002.dat deleted file mode 100644 index 8719b603..00000000 --- a/tests/cases/1002.dat +++ /dev/null @@ -1,25 +0,0 @@ -D $1,000.00 - -A Liabilities:MasterCard - -@def foo(x)=x*1000 - -2006/02/14 Buy - Broker AAPL 10 @ $200.00 ; this is a little note [2006/10/10] - Broker AAPL 10 @ $300.00 - Bank ; this is a comment - -2006/02/14 Sell - Bank $2000.00 + $500.00 - Broker AAPL -5 [2005/10/20] {$200.0015} @ $500.00 - Capital Gains $-1500.00 - -2005/10/20 Stock option vesting schedule - Brokerage:Options (1.00 COMP {$10.00}*min((([2006/08/20]-[2005/10/20])/3600/24)*0.15, 5000)) - Equity:Options - -P 2006/02/27 00:00:00 COMP $15.00 - -;2005/10/20 Stock option vesting schedule -; Brokerage:Time (1+@now) -; Equity:Time diff --git a/tests/cases/1030.dat b/tests/cases/1030.dat deleted file mode 100644 index 60fcac1c..00000000 --- a/tests/cases/1030.dat +++ /dev/null @@ -1,24 +0,0 @@ -2006/03/01 Basis - Assets:Pool $1000 - (Shares) 1000 shares {$1.00} - Equity - -P 2006/03/01 shares $1.00 - -2006/03/10 Purchase - (Payable:You) -500 shares {$1} - (Shares) -500 shares - Assets:Investment $500 - Income:Investments $-500 - -2006/03/16 Growth - Assets:Pool $500 - Income:Capital Gains - -P 2006/03/16 shares $1.50 - -2006/03/16 Cash out - (Payable:You) 250 shares {$1} - (Shares) 250 shares - Expenses:Investors - Assets (- valueof({1 shares}) * 250) diff --git a/tests/cases/1032.dat b/tests/cases/1032.dat deleted file mode 100644 index 1b103f1d..00000000 --- a/tests/cases/1032.dat +++ /dev/null @@ -1,834 +0,0 @@ -i 2003/10/06 15:21:00 EDG fp_conv -o 2003/10/06 15:47:53 retrieved documents from ACM and the Web -i 2003/10/08 16:25:08 EDG fp_conv -o 2003/10/08 17:10:50 read Steele document; need to print and implement -i 2003/10/18 14:54:34 EDG fp_conv -o 2003/10/18 16:56:59 -i 2003/10/18 23:50:34 EDG fp_conv -o 2003/10/19 01:08:12 -i 2003/10/19 10:54:33 EDG fp_conv -o 2003/10/19 12:41:56 -i 2003/10/19 16:21:07 EDG fp_conv -o 2003/10/19 18:29:39 -i 2003/10/21 14:54:10 EDG fp_conv -o 2003/10/21 18:07:07 -i 2003/10/21 22:44:17 EDG fp_conv -o 2003/10/21 23:55:57 -i 2003/10/22 22:26:53 EDG fp_conv -o 2003/10/22 23:22:49 -i 2003/10/23 02:44:00 EDG fp_conv -o 2003/10/23 03:34:00 -i 2003/10/23 16:12:27 EDG fp_conv -o 2003/10/23 17:39:08 -i 2003/10/26 10:32:16 ForumJobs Meeting -o 2003/10/26 15:59:28 -i 2003/10/26 17:26:23 ForumJobs Meeting -o 2003/10/26 17:57:52 -i 2003/10/27 16:02:10 EDG fp_conv -o 2003/10/27 17:33:53 -i 2003/10/27 22:38:57 EDG fp_conv -o 2003/10/28 23:37:51 Steele is working in C, with fgmp -i 2003/10/28 12:13:46 EDG fp_conv -o 2003/10/28 12:27:31 sought documents relating to MP math -i 2003/10/28 16:14:41 EDG fp_conv -o 2003/10/28 18:32:44 still need to implement mul/div/mod -i 2003/10/29 22:18:23 EDG fp_conv -o 2003/10/29 23:38:07 -i 2003/10/30 16:39:19 EDG fp_conv -o 2003/10/30 17:18:07 -i 2003/10/30 23:33:14 EDG fp_conv -o 2003/10/31 01:21:18 -i 2003/10/31 13:16:38 EDG fp_conv -o 2003/10/31 15:01:32 -i 2003/10/31 16:49:51 EDG fp_conv -o 2003/10/31 17:26:45 -i 2003/10/31 23:15:10 EDG fp_conv -o 2003/11/01 00:48:11 -i 2003/11/01 12:50:49 EDG fp_conv -o 2003/11/01 14:01:35 -i 2003/11/01 18:53:48 EDG fp_conv -o 2003/11/01 19:16:41 -i 2003/11/02 21:50:36 EDG fp_conv -o 2003/11/03 00:05:10 -i 2003/11/03 19:30:48 EDG fp_conv -o 2003/11/03 21:44:02 -i 2003/11/04 19:55:44 EDG fp_conv -o 2003/11/04 20:54:24 -i 2003/11/05 15:36:45 EDG fp_conv -o 2003/11/05 18:35:36 -i 2003/11/06 16:23:41 EDG fp_conv -o 2003/11/06 16:37:04 -i 2003/11/06 17:23:27 EDG fp_conv -o 2003/11/06 17:37:22 -i 2003/11/07 00:02:44 EDG fp_conv -o 2003/11/07 01:44:34 -i 2003/11/07 14:52:46 EDG fp_conv -o 2003/11/07 15:07:55 -i 2003/11/07 17:16:59 EDG fp_conv -o 2003/11/07 18:01:49 -i 2003/11/08 16:47:41 EDG fp_conv -o 2003/11/08 17:25:38 -i 2003/11/09 15:25:01 EDG fp_conv -o 2003/11/09 15:49:55 -i 2003/11/10 17:54:44 EDG fp_conv -o 2003/11/10 18:43:49 -i 2003/11/11 15:02:51 EDG fp_conv -o 2003/11/11 15:23:49 -i 2003/11/13 20:37:34 EDG fp_conv -o 2003/11/13 21:24:47 -i 2003/11/14 01:23:47 EDG fp_conv -o 2003/11/14 02:58:15 -i 2003/11/16 19:43:32 EDG fp_conv -o 2003/11/16 20:13:59 -i 2003/11/16 21:37:30 EDG fp_conv -o 2003/11/16 23:32:12 -i 2003/11/16 23:53:07 EDG fp_conv -o 2003/11/17 00:10:06 -i 2003/11/19 01:14:33 EDG fp_conv -o 2003/11/19 02:29:34 -i 2003/11/19 14:54:48 EDG fp_conv -o 2003/11/19 18:27:36 -i 2003/11/19 22:49:09 EDG fp_conv -o 2003/11/19 23:42:47 -i 2003/11/19 23:53:56 EDG fp_conv -o 2003/11/20 00:22:53 -i 2003/11/23 00:27:28 EDG fp_conv -o 2003/11/23 02:36:37 -i 2003/11/23 14:24:29 EDG fp_conv -o 2003/11/23 14:46:21 -i 2003/11/23 15:23:12 EDG fp_conv -o 2003/11/23 16:24:58 -i 2003/11/24 19:04:26 EDG fp_conv -o 2003/11/24 20:40:45 -i 2003/11/24 21:13:32 EDG fp_conv -o 2003/11/24 22:50:26 -i 2003/11/25 16:04:57 EDG fp_conv -o 2003/11/25 19:16:07 -i 2003/11/25 20:01:32 EDG fp_conv -o 2003/11/25 20:20:37 -i 2003/11/25 22:03:56 EDG fp_conv -o 2003/11/25 22:32:45 -i 2003/11/30 18:45:02 EDG fp_conv -o 2003/11/30 19:45:58 -i 2003/11/30 21:49:24 EDG fp_conv -o 2003/11/30 22:36:42 -i 2003/12/03 15:48:03 EDG fp_conv -o 2003/12/03 17:07:22 -i 2003/12/04 14:46:53 EDG fp_conv -o 2003/12/04 16:05:17 -i 2003/12/07 16:00:38 ForumJobs Meeting -o 2003/12/07 19:00:27 -i 2003/12/08 00:41:29 EDG fp_conv -o 2003/12/08 01:18:18 -i 2003/12/08 14:12:19 EDG fp_conv -o 2003/12/08 14:57:03 -i 2003/12/08 16:14:25 EDG fp_conv -o 2003/12/08 17:37:53 -i 2003/12/08 22:11:16 EDG fp_conv -o 2003/12/09 02:18:03 -i 2003/12/09 16:19:26 ForumJobs -o 2003/12/09 18:41:55 -i 2003/12/09 19:46:55 ForumJobs -o 2003/12/09 20:12:32 -i 2003/12/09 21:25:34 ForumJobs -o 2003/12/09 23:23:50 -i 2003/12/09 23:48:46 EDG fp_conv -o 2003/12/10 03:17:33 -i 2003/12/10 13:50:03 EDG fp_conv -o 2003/12/10 14:30:31 -i 2003/12/10 16:50:40 EDG fp_conv -o 2003/12/11 05:12:28 -i 2003/12/11 14:31:30 EDG fp_conv -o 2003/12/11 18:17:08 -i 2003/12/12 00:56:19 EDG fp_conv -o 2003/12/12 02:38:32 -i 2003/12/12 16:42:33 EDG fp_conv -o 2003/12/12 20:13:46 -i 2003/12/12 22:09:46 EDG fp_conv -o 2003/12/13 01:26:52 -i 2003/12/14 16:00:43 ForumJobs Meeting -o 2003/12/14 18:44:38 -i 2003/12/14 20:12:59 EDG fp_conv -o 2003/12/15 02:49:53 -i 2003/12/15 15:21:19 EDG fp_conv -o 2003/12/15 17:59:45 -i 2003/12/15 19:06:10 EDG fp_conv -o 2003/12/16 00:12:14 -i 2003/12/16 00:12:15 ForumJobs -o 2003/12/16 01:16:51 -i 2003/12/16 16:43:32 EDG fp_conv -o 2003/12/16 18:44:46 -i 2003/12/16 19:54:15 EDG fp_conv -o 2003/12/17 01:08:19 -i 2003/12/17 01:34:12 ForumJobs -o 2003/12/17 01:51:34 -i 2003/12/17 16:56:39 EDG fp_conv -o 2003/12/17 19:15:58 -i 2003/12/17 22:49:59 EDG fp_conv -o 2003/12/18 00:47:10 -i 2003/12/18 16:53:31 ForumJobs -o 2003/12/18 18:35:43 -i 2003/12/19 18:12:39 ForumJobs -o 2003/12/19 20:01:01 -i 2003/12/23 17:30:29 ForumJobs -o 2003/12/23 18:10:41 -i 2004/01/13 12:32:55 ForumJobs -o 2004/01/13 12:53:55 -i 2004/01/14 14:23:47 EDG fp_conv -o 2004/01/14 19:25:42 -i 2004/01/15 17:20:01 EDG fp_conv -o 2004/01/15 18:40:34 -i 2004/01/16 12:50:26 EDG fp_conv -o 2004/01/16 18:39:45 -i 2004/01/16 19:23:09 EDG fp_conv -o 2004/01/16 20:41:14 -i 2004/01/19 12:48:57 EDG fp_conv -o 2004/01/19 15:18:38 -i 2004/01/20 16:07:50 ForumJobs -o 2004/01/20 19:18:04 -i 2004/01/21 13:18:04 EDG fp_conv -o 2004/01/21 18:18:04 -i 2004/01/22 13:39:27 EDG fp_conv -o 2004/01/22 18:31:03 -i 2004/01/22 19:12:42 EDG fp_conv -o 2004/01/22 20:48:41 -i 2004/01/23 12:58:41 EDG fp_conv -o 2004/01/23 15:27:58 -i 2004/01/23 16:00:00 ForumJobs -o 2004/01/23 19:15:00 -i 2004/01/23 21:30:26 EDG fp_conv -o 2004/01/23 23:45:56 -i 2004/01/25 17:19:42 ForumJobs -o 2004/01/25 18:22:37 -i 2004/01/26 22:34:31 ForumJobs -o 2004/01/26 23:35:23 -i 2004/01/27 12:53:42 EDG fp_conv -o 2004/01/27 13:23:15 -i 2004/01/27 19:42:50 EDG fp_conv -o 2004/01/27 19:57:28 -i 2004/01/27 22:08:53 ForumJobs -o 2004/01/27 22:44:40 -i 2004/01/27 23:14:41 EDG fp_conv -o 2004/01/27 23:48:05 -i 2004/01/28 00:00:27 ForumJobs -o 2004/01/28 00:39:21 -i 2004/01/28 17:04:22 ForumJobs -o 2004/01/28 17:34:31 -i 2004/01/29 14:07:44 ForumJobs -o 2004/01/29 16:07:04 -i 2004/01/29 17:15:58 ForumJobs -o 2004/01/29 18:55:40 -i 2004/01/29 22:11:41 ForumJobs -o 2004/01/29 23:31:41 -i 2004/01/30 15:56:02 ForumJobs -o 2004/01/30 17:54:06 -i 2004/01/30 20:43:00 ForumJobs -o 2004/01/30 21:56:30 -i 2004/01/30 22:16:09 ForumJobs -o 2004/01/30 22:28:35 -i 2004/02/03 17:19:26 ForumJobs -o 2004/02/03 19:24:08 -i 2004/02/08 14:42:44 ForumJobs -o 2004/02/08 18:07:07 -i 2004/02/10 18:05:54 EDG fp_conv -o 2004/02/10 19:10:24 -i 2004/02/10 19:10:25 ForumJobs -o 2004/02/10 19:16:23 -i 2004/02/11 18:04:55 ForumJobs -o 2004/02/11 19:25:12 -i 2004/02/11 21:42:52 ForumJobs -o 2004/02/12 01:16:28 -i 2004/02/12 16:01:50 ForumJobs -o 2004/02/12 16:45:17 -i 2004/02/17 15:36:11 ForumJobs -o 2004/02/17 17:37:50 -i 2004/02/17 22:20:06 ForumJobs -o 2004/02/17 23:20:18 -i 2004/02/20 18:00:44 EDG fp_conv -o 2004/02/20 19:21:42 -i 2004/02/23 21:16:24 EDG fp_conv -o 2004/02/24 00:01:01 -i 2004/02/24 21:29:21 EDG fp_conv -o 2004/02/24 21:53:23 -i 2004/02/25 10:55:19 EDG fp_conv -o 2004/02/25 13:30:24 -i 2004/02/27 13:57:39 EDG fp_conv -o 2004/02/27 15:49:31 -i 2004/02/27 16:18:22 EDG fp_conv -o 2004/02/27 17:47:34 -i 2004/03/02 11:45:36 ForumJobs -o 2004/03/02 12:45:38 -i 2004/03/02 12:45:38 EDG fp_conv -o 2004/03/02 18:01:03 -i 2004/03/02 21:03:44 ForumJobs -o 2004/03/02 21:39:25 -i 2004/03/03 12:05:24 EDG fp_conv -o 2004/03/03 17:47:43 -i 2004/03/03 20:34:26 EDG fp_conv -o 2004/03/03 22:04:50 -i 2004/03/04 19:38:23 EDG fp_conv -o 2004/03/04 21:08:57 -i 2004/03/07 16:53:41 ForumJobs -o 2004/03/07 17:45:30 -i 2004/03/09 14:24:33 ForumJobs -o 2004/03/09 15:07:53 -i 2004/03/09 15:07:55 EDG fp_conv -o 2004/03/09 17:53:35 -i 2004/03/22 23:21:08 ForumJobs -o 2004/03/23 00:09:32 -i 2004/03/23 15:45:28 ForumJobs -o 2004/03/23 16:38:24 -i 2004/03/30 23:30:17 ForumJobs -o 2004/03/31 01:31:43 -i 2004/04/02 17:49:46 ForumJobs -o 2004/04/02 21:02:43 -i 2004/04/03 20:33:51 ForumJobs -o 2004/04/03 22:49:54 -i 2004/04/04 18:46:00 ForumJobs -o 2004/04/04 19:03:09 -i 2004/04/05 20:21:23 ForumJobs -o 2004/04/05 20:51:24 -i 2004/04/06 15:32:37 ForumJobs -o 2004/04/06 16:40:53 -i 2004/04/08 12:54:44 ForumJobs -o 2004/04/08 13:27:18 -i 2004/04/08 14:50:39 ForumJobs -o 2004/04/08 15:39:51 -i 2004/04/08 16:05:52 ForumJobs -o 2004/04/08 17:16:33 -i 2004/04/08 23:46:34 ForumJobs -o 2004/04/09 00:57:38 -i 2004/04/10 13:00:57 ForumJobs -o 2004/04/10 14:53:01 -i 2004/04/12 16:18:02 ForumJobs -o 2004/04/12 17:34:57 -i 2004/04/12 23:39:20 ForumJobs -o 2004/04/13 00:32:45 -i 2004/04/14 12:23:48 ForumJobs -o 2004/04/14 12:50:42 -i 2004/04/15 16:48:51 ForumJobs -o 2004/04/15 17:55:10 invoice #1 -i 2004/04/26 12:34:11 ForumJobs -o 2004/04/26 13:04:54 -i 2004/05/06 14:00:28 ForumJobs -o 2004/05/06 14:38:53 -i 2004/07/27 14:11:31 Borland -o 2004/07/27 14:35:56 discussed fake symbol consolidation schemes -i 2004/07/29 17:54:42 Borland -o 2004/07/29 18:32:50 discussed hashing issues in fake ns sym lookup -i 2004/07/30 12:41:33 Borland -o 2004/07/30 12:57:16 answered namespace lookup questions -i 2005/04/04 13:03:18 3dex oss -o 2005/04/04 14:33:18 -i 2005/04/18 11:03:18 3dex oss -o 2005/04/18 12:36:18 -i 2005/04/18 14:03:18 3dex oss -o 2005/04/18 16:57:56 -i 2005/05/10 13:45:16 EDG dr -o 2005/05/10 15:30:52 10 issues checked -i 2005/05/12 14:32:16 EDG dr -o 2005/05/12 18:12:55 -i 2005/05/13 11:20:51 EDG dr -o 2005/05/13 12:41:03 -i 2005/05/13 15:50:54 EDG dr -o 2005/05/13 16:13:23 break -i 2005/05/13 16:43:59 EDG dr -o 2005/05/13 17:37:44 -i 2005/05/16 12:30:37 EDG dr -o 2005/05/16 13:02:55 -i 2005/05/16 16:02:11 EDG dr -o 2005/05/16 16:36:20 -i 2005/05/17 18:33:14 EDG dr -o 2005/05/17 18:51:02 -i 2005/05/18 14:32:08 EDG dr -o 2005/05/18 15:07:43 -i 2005/05/20 20:02:55 EDG dr -o 2005/05/20 21:21:14 -i 2005/05/20 21:42:16 EDG dr -o 2005/05/20 21:52:59 -i 2005/05/21 15:39:59 EDG dr -o 2005/05/21 16:33:27 -i 2005/05/23 20:48:20 EDG dr -o 2005/05/23 22:17:08 -i 2005/05/24 13:08:29 EDG dr -o 2005/05/24 15:25:06 -i 2005/06/28 15:08:29 3dex security -o 2005/06/28 18:44:13 -i 2005/06/28 22:38:57 3dex ledger -o 2005/06/28 23:51:31 -i 2005/06/29 16:11:41 3dex ledger -o 2005/06/29 19:46:04 -i 2005/07/01 15:03:35 3dex ledger -o 2005/07/01 20:41:08 -i 2005/07/02 14:00:35 3dex ledger -o 2005/07/02 18:00:35 -i 2005/07/03 13:30:35 3dex ledger -o 2005/07/03 17:00:35 -i 2005/07/04 14:30:35 3dex ledger -o 2005/07/04 19:00:35 -i 2005/07/04 20:44:35 3dex ledger -o 2005/07/04 22:01:35 -i 2005/07/05 17:20:29 3dex ledger -o 2005/07/05 22:58:35 -i 2005/07/06 23:43:14 3dex ledger -o 2005/07/07 03:09:01 -i 2005/07/07 19:43:14 3dex ledger -o 2005/07/07 21:09:01 -i 2005/07/09 13:55:22 3dex ledger -o 2005/07/09 14:34:29 -i 2005/07/09 15:08:05 3dex ledger -o 2005/07/09 17:46:15 -i 2005/07/12 21:24:03 3dex ledger -o 2005/07/13 02:00:03 -i 2005/07/14 21:24:03 3dex ledger -o 2005/07/14 21:59:40 -i 2005/07/16 22:03:57 3dex ledger -o 2005/07/17 00:34:07 -i 2005/07/17 10:53:38 3dex ledger -o 2005/07/17 11:56:49 -i 2005/08/10 15:45:54 3dex soap -o 2005/08/10 16:12:02 -i 2005/08/11 15:04:23 3dex soap -o 2005/08/11 16:07:35 -i 2005/08/15 15:07:18 3dex soap -o 2005/08/15 15:35:18 -i 2005/08/15 15:36:14 3dex soap -o 2005/08/15 16:18:41 -i 2005/08/17 16:21:10 3dex soap -o 2005/08/17 16:53:37 -i 2005/08/18 14:01:46 3dex soap -o 2005/08/18 18:33:12 -i 2005/08/25 13:30:44 3dex soap -o 2005/08/25 15:38:09 -i 2005/08/25 16:52:18 3dex soap -o 2005/08/25 17:54:52 -i 2005/08/25 20:19:34 3dex soap -o 2005/08/25 21:32:07 -i 2005/08/28 18:06:40 3dex soap -o 2005/08/28 18:36:55 -i 2005/08/28 19:34:56 3dex soap -o 2005/08/28 19:55:25 -i 2005/10/22 00:55:10 3dex video -o 2005/10/22 01:52:58 -i 2005/10/22 22:00:59 3dex video -o 2005/10/22 22:58:30 -i 2005/10/25 13:00:00 MOW meeting -o 2005/10/25 17:00:00 -i 2005/10/26 10:00:00 MOW meeting -o 2005/10/26 14:00:00 -i 2005/10/26 18:51:52 MOW planning -o 2005/10/26 18:57:43 -i 2005/10/27 20:34:28 MOW website -o 2005/10/27 21:40:28 -i 2005/10/28 22:04:28 MOW website -o 2005/10/28 22:50:28 -i 2005/10/29 23:04:28 MOW planning -o 2005/10/29 23:40:28 -i 2005/10/30 22:18:28 MOW website -o 2005/10/31 01:22:28 -i 2005/10/31 05:15:29 3dex video -o 2005/10/31 05:55:34 -i 2005/10/31 15:51:55 3dex video -o 2005/10/31 17:37:09 -i 2005/11/01 01:15:34 MOW website -o 2005/11/01 02:34:55 -i 2005/11/01 18:04:47 MOW website -o 2005/11/01 18:35:25 -i 2005/11/01 20:39:42 MOW website -o 2005/11/01 23:00:08 -i 2005/11/02 00:26:09 MOW website -o 2005/11/02 04:14:33 -i 2005/11/02 17:07:40 MOW website -o 2005/11/02 18:13:49 -i 2005/11/02 22:09:53 MOW website -o 2005/11/02 22:41:31 -i 2005/11/03 00:11:02 MOW website -o 2005/11/03 04:52:25 -i 2005/11/03 04:55:38 3dex video -o 2005/11/03 06:22:42 -i 2005/11/03 19:45:00 MOW website -o 2005/11/03 22:53:12 -i 2005/11/04 00:16:33 MOW website -o 2005/11/04 00:43:07 -i 2005/11/04 02:01:23 MOW website -o 2005/11/04 04:31:25 -i 2005/11/04 14:11:55 MOW website -o 2005/11/04 16:48:30 -i 2005/11/04 16:59:06 MOW website -o 2005/11/04 19:54:46 -i 2005/11/05 22:29:03 MOW website -o 2005/11/06 04:53:57 -i 2005/11/07 02:13:36 MOW website -o 2005/11/07 04:38:12 -i 2005/11/07 20:29:07 MOW website -o 2005/11/07 23:03:45 -i 2005/11/07 23:53:46 MOW website -o 2005/11/08 04:35:27 -i 2005/11/08 16:02:23 MOW website -o 2005/11/08 17:26:28 -i 2005/11/09 00:25:44 MOW website -o 2005/11/09 03:35:00 -i 2005/11/09 14:28:03 MOW website -o 2005/11/09 14:49:27 -i 2005/11/09 17:13:19 MOW website -o 2005/11/09 17:29:51 -i 2005/11/10 00:13:33 MOW website -o 2005/11/10 03:08:40 -i 2005/11/10 15:22:12 MOW website -o 2005/11/10 16:33:16 -i 2005/11/10 19:10:44 MOW website -o 2005/11/10 22:19:44 -i 2005/11/11 01:00:54 MOW website -o 2005/11/11 01:31:39 -i 2005/11/11 01:50:07 MOW website -o 2005/11/11 03:13:36 -i 2005/11/11 03:35:18 MOW website -o 2005/11/11 05:32:27 -i 2005/11/13 20:07:14 MOW website -o 2005/11/13 21:14:05 -i 2005/11/13 22:02:04 MOW website -o 2005/11/14 01:34:44 -i 2005/11/14 23:35:33 MOW website -o 2005/11/15 12:25:00 -i 2005/11/15 01:31:53 MOW website -o 2005/11/15 01:54:55 -i 2005/11/15 16:58:26 MOW website -o 2005/11/15 18:17:43 -i 2005/11/15 21:16:00 MOW website -o 2005/11/15 22:57:24 -i 2005/11/15 23:52:00 MOW website -o 2005/11/16 07:08:14 -i 2005/11/16 20:06:33 MOW website -o 2005/11/16 23:00:45 -i 2005/11/17 00:15:29 MOW website -o 2005/11/17 09:39:11 -i 2005/11/17 15:33:03 MOW website: Polishing MainIndex.aspx -o 2005/11/17 17:12:52 -i 2005/11/17 20:40:53 MOW website: Fixing the display of StoryTable.ascx -o 2005/11/18 00:14:58 -i 2005/11/18 00:15:19 MOW website: Improve Asset class -o 2005/11/18 03:42:35 -i 2005/11/19 19:04:00 MOW website: Fix MainIndex page -o 2005/11/19 22:30:14 -i 2005/11/19 22:56:38 MOW website: Finalize HTML for AuthorIndex and Profile -o 2005/11/20 07:49:00 -i 2005/11/20 19:02:48 MOW website: Resolving final issues -o 2005/11/20 23:58:01 -i 2005/11/22 00:04:47 MOW website: MainIndex - Change header display and fixed alignment -o 2005/11/22 02:54:14 -i 2005/11/22 02:54:14 MOW website: AuthorProfile - Fix alignment issues created by MainIndex fixes -o 2005/11/22 03:08:13 -i 2005/11/22 03:08:13 MOW website: Breadcrumb navigation -o 2005/11/22 04:13:01 -i 2005/11/22 04:13:01 MOW website: CreateTheme - Fixing style -o 2005/11/22 04:45:41 -i 2005/11/22 04:45:41 MOW website: Confirming deletion -o 2005/11/22 05:14:26 -i 2005/11/22 05:14:29 MOW website: Yellow message boxes -o 2005/11/22 06:17:15 -i 2005/11/22 06:47:00 MOW website: MainIndex - Testing and fixing HTML -o 2005/11/22 08:20:19 -i 2005/11/22 17:41:19 MOW website: MainIndex - Use PostedDate in Themes for sorting -o 2005/11/22 18:14:23 -i 2005/11/22 18:49:32 MOW website: MainIndex - Use PostedDate in Themes for sorting -o 2005/11/22 20:01:52 -i 2005/11/23 00:04:07 MOW website: AssetList - Finishing uploads -o 2005/11/23 01:13:28 -i 2005/11/24 16:49:19 MOW website: AssetList - Support ordering -o 2005/11/24 20:20:23 -i 2005/11/24 20:50:28 MOW website: AssetList - Finishing uploads -o 2005/11/24 22:13:13 -i 2005/11/27 19:33:05 MOW website: StoryDetails - Adding other tables -o 2005/11/27 21:15:26 -i 2005/11/28 20:27:07 MOW website: StoryDetails - Adding other tables -o 2005/11/28 21:17:52 -i 2005/11/28 21:58:54 MOW website: StoryDetails - Adding other tables -o 2005/11/29 00:49:57 -i 2005/11/29 02:16:10 MOW website: Use field validators again -o 2005/11/29 03:20:58 -i 2005/11/29 04:09:57 MOW website: Use field validators again -o 2005/11/29 04:35:41 -i 2005/11/30 00:08:28 MOW website: AddedStories - The main added story list -o 2005/11/30 01:00:32 -i 2005/11/30 20:58:15 MOW website: AddedStories - The main added story list -o 2005/11/30 22:01:42 -i 2005/11/30 22:57:15 MOW website: AddedStories - The main added story list -o 2005/12/01 03:02:46 -i 2005/12/01 20:32:47 MOW website: AddedStories - The main added story list -o 2005/12/01 21:18:13 -i 2005/12/01 21:18:27 MOW website: MainIndex - Further corrections using latest HTML -o 2005/12/01 21:55:20 -i 2005/12/01 22:55:49 MOW website: MainIndex - Further corrections using latest HTML -o 2005/12/02 03:21:50 -i 2005/12/02 12:19:06 MOW website: BookDescription -o 2005/12/02 13:27:17 -i 2005/12/02 13:27:22 MOW website: Cleanup -o 2005/12/02 17:35:32 -i 2005/12/05 17:50:37 MOW website: Cleanup -o 2005/12/05 17:52:33 -i 2005/12/05 18:44:48 MOW website: Cleanup -o 2005/12/05 22:13:06 -i 2005/12/05 22:29:40 MOW website: Cleanup -o 2005/12/06 01:00:03 -i 2005/12/06 01:38:51 MOW website: Cleanup -o 2005/12/06 02:30:04 -i 2005/12/06 18:44:24 MOW website: Cleanup -o 2005/12/06 20:00:21 -i 2005/12/06 21:00:03 MOW website: Cleanup -o 2005/12/07 00:44:28 -i 2005/12/07 00:44:46 MOW website: AuthorProfile - Support for CommLanguage and Passwords -o 2005/12/07 02:10:41 -i 2005/12/07 02:11:11 MOW website: ShowImage - Make Popup size based on Image size -o 2005/12/07 02:35:37 -i 2005/12/07 02:41:14 MOW website: ShowImage - Make Popup size based on Image size -o 2005/12/07 03:02:45 -i 2005/12/07 04:22:17 MOW website: ShowImage - Make Popup size based on Image size -o 2005/12/07 06:00:31 -i 2005/12/07 06:00:45 MOW website: AuthorIndex - Fixes for the Cleanup -o 2005/12/07 06:31:09 -i 2005/12/07 10:10:21 MOW website: AddedStories - Add support for searching by author -o 2005/12/07 11:37:51 -i 2005/12/07 12:55:55 MOW planning -o 2005/12/07 13:26:03 -i 2005/12/07 13:26:19 MOW website: Cleanup -o 2005/12/07 14:20:32 -i 2005/12/07 15:13:21 MOW website: Cleanup -o 2005/12/07 15:46:54 -i 2005/12/07 17:52:33 MOW website: Cleanup -o 2005/12/07 18:36:52 -i 2005/12/08 10:31:32 MOW website: Cleanup -o 2005/12/08 16:28:12 -i 2005/12/08 20:37:50 MOW website: Cleanup -o 2005/12/08 22:38:42 -i 2005/12/09 13:44:36 MOW website: StoryDetails - Split into multiple pages -o 2005/12/09 14:09:44 -i 2005/12/11 14:46:31 MOW website: StoryDetails - Split into multiple pages -o 2005/12/11 16:09:49 -i 2005/12/12 11:27:15 MOW website: StoryDetails - Split into multiple pages -o 2005/12/12 14:39:05 -i 2005/12/12 14:58:15 MOW website: StoryDetails - Split into multiple pages -o 2005/12/12 17:06:12 -i 2005/12/12 17:06:19 MOW website: Advertisements -o 2005/12/12 18:22:28 -i 2005/12/12 19:07:13 MOW website: Advertisements -o 2005/12/12 21:47:22 -i 2005/12/13 17:05:38 MOW website: Events -o 2005/12/13 17:54:49 -i 2005/12/13 19:08:43 MOW website: Events -o 2005/12/13 21:43:52 -i 2005/12/14 15:35:08 MOW website: Events -o 2005/12/14 16:30:54 -i 2005/12/15 13:01:22 MOW website: Events -o 2005/12/15 14:41:30 -i 2005/12/15 17:25:51 MOW website: Events -o 2005/12/15 18:07:43 -i 2005/12/16 08:19:13 MOW website: Events -o 2005/12/16 10:51:19 -i 2005/12/19 14:32:21 MOW website: Bugs -o 2005/12/19 18:17:29 -i 2005/12/19 19:52:21 MOW website: Events -o 2005/12/19 22:01:36 -i 2005/12/21 18:52:02 MOW website: TakeAction -o 2005/12/21 21:30:58 -i 2005/12/29 22:35:56 MOW website: TakeAction -o 2005/12/29 23:26:52 -i 2005/12/30 22:51:30 MOW website: Flags -o 2005/12/31 00:02:40 -i 2006/01/02 19:12:34 MOW website: Communication -o 2006/01/02 19:58:53 -i 2006/01/05 17:53:58 MOW website: Schedule -o 2006/01/05 18:30:02 -i 2006/01/06 00:08:19 MOW website: Cleanup -o 2006/01/06 01:36:11 -i 2006/01/07 17:25:54 MOW website: Cleanup -o 2006/01/07 19:30:29 -i 2006/01/07 22:05:33 MOW website: Cleanup -o 2006/01/08 00:45:18 -i 2006/01/08 15:36:25 MOW website: Cleanup -o 2006/01/08 16:41:19 -i 2006/01/08 18:04:57 MOW website: Cleanup -o 2006/01/08 20:03:15 -i 2006/01/08 20:50:43 MOW website: Cleanup -o 2006/01/08 22:12:04 -i 2006/01/08 22:12:09 sina -o 2006/01/08 23:19:16 -i 2006/01/08 23:19:17 MOW website: Cleanup -o 2006/01/09 01:01:51 -i 2006/01/09 14:46:53 MOW website: Cleanup -o 2006/01/09 17:19:53 -i 2006/01/09 18:19:54 MOW website: Cleanup -o 2006/01/09 19:35:47 -i 2006/01/12 14:50:26 MOW website: Cleanup -o 2006/01/12 17:34:41 -i 2006/01/12 19:59:47 MOW website: Cleanup -o 2006/01/12 23:33:20 -i 2006/01/13 15:32:37 MOW website: Cleanup -o 2006/01/13 16:03:19 -i 2006/01/14 18:50:12 sina -o 2006/01/14 19:22:54 -i 2006/01/15 01:14:21 sina -o 2006/01/15 01:50:04 -i 2006/01/15 02:21:13 MOW website: PressReleases -o 2006/01/15 03:11:35 -i 2006/01/16 02:25:45 sina -o 2006/01/16 02:59:01 -i 2006/01/16 18:08:28 MOW planning -o 2006/01/16 18:21:21 -i 2006/01/16 21:43:57 sina -o 2006/01/16 22:57:09 -i 2006/01/17 00:23:05 sina -o 2006/01/17 02:51:55 -i 2006/01/17 18:01:54 sina -o 2006/01/17 18:22:33 -i 2006/01/17 19:00:48 sina -o 2006/01/17 19:19:36 -i 2006/01/17 19:34:10 sina -o 2006/01/17 19:50:48 -i 2006/01/17 19:53:38 MOW website: v11 -o 2006/01/17 21:09:27 -i 2006/01/17 21:33:29 sina -o 2006/01/17 21:55:42 -i 2006/01/17 22:02:38 sina -o 2006/01/17 22:28:36 -i 2006/01/17 22:28:59 MOW website: v11 -o 2006/01/17 22:48:06 -i 2006/01/18 01:57:59 MOW website: v11 -o 2006/01/18 03:27:34 -i 2006/01/18 03:50:52 sina -o 2006/01/18 04:32:47 -i 2006/01/18 18:07:31 MOW website: TakeAction -o 2006/01/18 21:01:43 -i 2006/01/18 23:53:49 MOW website: TakeAction -o 2006/01/19 01:20:50 -i 2006/01/19 14:50:56 MOW website: TakeAction -o 2006/01/19 15:40:04 -i 2006/01/19 17:53:04 MOW website: TakeAction -o 2006/01/19 19:25:36 -i 2006/01/22 00:59:17 MOW website: Cleanup -o 2006/01/22 01:23:58 -i 2006/01/22 07:43:01 MOW website: Cleanup -o 2006/01/22 10:02:30 -i 2006/01/22 19:18:23 MOW website: Cleanup -o 2006/01/22 19:48:01 -i 2006/01/22 20:31:36 MOW website: Cleanup -o 2006/01/22 23:05:58 -i 2006/01/23 18:45:59 MOW website: SubTheme -o 2006/01/23 19:50:39 -i 2006/01/23 21:05:19 MOW website: SubTheme -o 2006/01/24 01:02:44 -i 2006/01/24 20:07:33 MOW website: Bugs -o 2006/01/24 20:39:11 -i 2006/01/25 08:38:02 MOW website: Bugs -o 2006/01/25 10:17:25 -i 2006/01/25 11:54:50 MOW website: Bugs -o 2006/01/25 14:26:52 -i 2006/01/27 03:42:03 MOW website: Bugs -o 2006/01/27 04:35:28 -i 2006/01/27 05:25:53 MOW website: Bugs -o 2006/01/27 06:14:55 -i 2006/01/27 16:27:55 MOW website: Bugs -o 2006/01/27 16:36:42 -i 2006/01/27 17:08:46 MOW website: Bugs -o 2006/01/27 17:38:40 -i 2006/01/29 15:42:37 MOW website: Bugs -o 2006/01/29 16:12:01 -i 2006/01/29 22:12:15 MOW website: Bugs -o 2006/01/30 00:30:12 -i 2006/01/30 13:24:14 MOW website: Comments -o 2006/01/30 14:54:21 -i 2006/01/30 15:38:38 MOW website: Events -o 2006/01/30 18:30:18 -i 2006/01/30 18:50:55 MOW website: AboutTheBook -o 2006/01/30 19:14:55 -i 2006/01/31 12:30:40 MOW website: AboutTheBook -o 2006/01/31 14:20:46 -i 2006/01/31 16:11:25 MOW website: Events -o 2006/01/31 18:35:55 -i 2006/02/01 03:44:57 MOW website: public -o 2006/02/01 04:21:29 -i 2006/02/01 10:40:37 MOW website: public -o 2006/02/01 15:19:13 -i 2006/02/01 16:49:14 MOW website: public -o 2006/02/01 19:37:46 -i 2006/02/02 09:55:16 MOW website: public -o 2006/02/02 10:34:26 -i 2006/02/02 15:47:38 MOW website: public -o 2006/02/02 18:39:48 -i 2006/02/03 09:14:47 MOW website: public -o 2006/02/03 10:21:18 -i 2006/02/03 16:21:20 MOW website: public -o 2006/02/03 17:26:02 -i 2006/02/05 11:49:52 MOW website: public -o 2006/02/05 22:49:53 -i 2006/02/06 16:49:23 MOW website: public -o 2006/02/06 18:10:33 -i 2006/02/07 17:01:45 MOW website: public -o 2006/02/07 18:39:24 -i 2006/02/07 21:00:15 sina -o 2006/02/08 23:27:28 -i 2006/02/08 11:47:31 MOW website: public -o 2006/02/08 12:09:20 -i 2006/02/08 12:21:06 MOW website: public -o 2006/02/08 12:54:37 -i 2006/02/10 21:20:03 MOW website: public -o 2006/02/10 23:36:40 -i 2006/02/16 21:26:56 3dex scheduler -o 2006/02/16 22:52:50 -i 2006/02/16 23:35:24 3dex scheduler -o 2006/02/17 01:11:31 -i 2006/02/17 17:57:19 MOW website: public -o 2006/02/17 19:09:45 -i 2006/02/17 21:00:15 sina -o 2006/02/17 21:30:15 -i 2006/02/18 22:00:57 3dex scheduler -o 2006/02/18 22:59:26 -i 2006/02/19 01:14:16 3dex scheduler -o 2006/02/19 02:32:59 -i 2006/02/19 17:05:01 3dex scheduler -o 2006/02/19 17:26:51 -i 2006/02/20 22:28:05 3dex scheduler -o 2006/02/20 22:48:37 -i 2006/02/21 14:50:37 MOW website: Events -o 2006/02/21 16:55:24 -i 2006/02/21 17:14:06 MOW website: Events -o 2006/02/21 18:20:14 -i 2006/02/21 19:34:15 MOW website: Events -o 2006/02/21 20:00:09 -i 2006/02/23 18:02:57 sina -o 2006/02/23 19:35:47 -i 2006/02/28 16:01:01 MOW website: Factoids -o 2006/02/28 18:32:39 -i 2006/02/28 22:13:01 MOW website: testing -o 2006/02/28 23:41:58 -i 2006/03/02 12:52:49 MOW website: Factoids -o 2006/03/02 14:13:21 -i 2006/03/02 16:01:03 MOW website: bugs -o 2006/03/02 17:45:58 -i 2006/03/02 18:10:59 MOW website: bugs -o 2006/03/02 18:59:01 -i 2006/03/03 16:23:29 MOW website: bugs -o 2006/03/03 17:23:29 -i 2006/03/10 16:45:13 MOW website: testing -o 2006/03/10 17:25:14 -i 2006/03/11 16:54:29 MOW website: testing -o 2006/03/11 18:03:01 -i 2006/03/11 21:06:39 MOW website: testing -o 2006/03/11 21:36:29 -i 2006/03/11 23:59:37 sina -o 2006/03/12 03:30:39 -i 2006/03/13 00:41:27 MOW website: testing -o 2006/03/13 02:04:44 -i 2006/03/14 03:19:06 MOW website: testing -o 2006/03/14 03:37:49 -i 2006/03/15 02:03:32 MOW website: bugs -o 2006/03/15 04:04:44 -i 2006/03/15 21:02:35 College:Setup -o 2006/03/15 22:10:44 -i 2006/03/16 01:08:22 College:Setup -o 2006/03/16 02:28:17 -i 2006/03/16 03:02:34 College:Setup -o 2006/03/16 03:34:47 -i 2006/03/16 08:08:17 College:Setup -o 2006/03/16 08:59:51 -i 2006/03/22 10:31:58 sina -o 2006/03/22 13:06:15 diff --git a/tests/confirm.py b/tests/confirm.py deleted file mode 100755 index f4947224..00000000 --- a/tests/confirm.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/python - -# This script confirms what ledger tells you. - -import sys -import os -import re - -def clean(num): - return float(re.sub("(\s+|\$|,)","", num)) - -running_total = 0.0 -index = 1 -last_line = "" -errors = 0 - -report = sys.argv[1] -for line in os.popen("./ledger -f utils/standard.dat -e 2004/4 %s reg %s" % - (report, sys.argv[2])): - match = re.match("\\s*([-$,0-9.]+)\\s+([-$,0-9.]+)", line[55:]) - if not match: - continue - value = clean(match.group(1)) - total = clean(match.group(2)) - - running_total += value - diff = abs(running_total - total) - if report == "-V" or report == "-G" and diff < 0.015: - diff = 0.0 - if diff > 0.001: - print "! discrepancy of %.2f (%.2f - %.2f) at line %d:" % \ - (running_total - total, running_total, total, index) - print line, - running_total = total - errors += 1 - - index += 1 - last_line = line - -balance_total = 0.0 - -for line in os.popen("./ledger -f utils/standard.dat -e 2004/4 %s bal %s" % - (report, sys.argv[2])): - if line[0] != '-': - balance_total = clean(line[:20]) - -diff = abs(balance_total - running_total) -if report == "-V" or report == "-G" and diff < 0.015: - diff = 0.0 -if diff > 0.001: - print - print "! discrepancy of %.2f (%.2f - %.2f) between register and balance" % \ - (balance_total - running_total, balance_total, running_total) - print last_line, - print line, - errors += 1 - -sys.exit(errors) diff --git a/tests/parser.h b/tests/parser.h deleted file mode 100644 index aa1a3a74..00000000 --- a/tests/parser.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef __TESTFILEFORMAT_H -#define __TESTFILEFORMAT_H - -#include <cxxtest/TestSuite.h> - -#include <ledger.h> - -using namespace std; -using namespace ledger; - -class TestFileFormat : public CxxTest::TestSuite -{ -public: - void testEmptyFileIsTextualFile() - { - stringstream emptyStream(stringstream::in); - textual_parser_t textualParser; - TS_ASSERT(textualParser.test(emptyStream)); - TS_ASSERT(emptyStream.good()); - TS_ASSERT_EQUALS(0, emptyStream.tellg()); - } - - void testEmptyFileIsNotXMLFile() - { -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - stringstream emptyStream(stringstream::in); - xml_parser_t xmlParser; - TS_ASSERT(!xmlParser.test(emptyStream)); - TS_ASSERT(emptyStream.good()); - TS_ASSERT_EQUALS(0, emptyStream.tellg()); -#endif - } - - void testEmptyFileIsNotGnuCashFile() - { -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - stringstream emptyStream(stringstream::in); - gnucash_parser_t gnucashParser; - TS_ASSERT(!gnucashParser.test(emptyStream)); - TS_ASSERT(emptyStream.good()); - TS_ASSERT_EQUALS(0, emptyStream.tellg()); -#endif - } - - void testEmptyFileIsNotBinaryFile() - { - stringstream emptyStream(stringstream::in); - binary_parser_t binaryParser; - TS_ASSERT(!binaryParser.test(emptyStream)); - TS_ASSERT(emptyStream.good()); - TS_ASSERT_EQUALS(0, emptyStream.tellg()); - } - - void testEmptyFileIsNotQIFFile() - { - stringstream emptyStream(stringstream::in); - qif_parser_t qifParser; - TS_ASSERT(!qifParser.test(emptyStream)); - TS_ASSERT(emptyStream.good()); - TS_ASSERT_EQUALS(0, emptyStream.tellg()); - } - -}; - -#endif // __TESTFILEFORMAT_H diff --git a/tests/regress b/tests/regress deleted file mode 100755 index 9a6c4412..00000000 --- a/tests/regress +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/sh - -TMPDIR=/tmp -TESTS=tests -UTILS=utils -CASES=$TESTS/cases - -result=0 - -generate=false -if [ "$1" = "--generate" ]; then - generate=true -fi - -runtest() { - num=$1 - shift - if [ $generate = true ]; then - echo generating $num - ./ledger "$@" > $TESTS/baseline/$num 2> $TMPDIR/errors-$$.out - cat $TMPDIR/errors-$$.out >> $TESTS/baseline/$num - rm -f $TMPDIR/*-$$.out - elif [ -r $TESTS/baseline/$num ]; then - ./ledger "$@" > $TMPDIR/test-$$.out 2> $TMPDIR/errors-$$.out - cat $TMPDIR/errors-$$.out >> $TMPDIR/test-$$.out - - diff $TESTS/baseline/$num $TMPDIR/test-$$.out \ - > $TMPDIR/result-$$.out 2>&1 - if [ -s $TMPDIR/result-$$.out ]; then - echo Error: Regression $num failed - echo ":: regression $num: ./ledger $@" >> errors.out - cat $TMPDIR/result-$$.out >> errors.out - result=`expr $result + 1` - fi - rm -f $TMPDIR/*-$$.out - fi -} - -echo Running Ledger regression tests... - -runtest 1032 -f $CASES/1032.dat -S t bal - -runtest 1031 -f $CASES/1030.dat reg -runtest 1030 -f $CASES/1030.dat bal - -runtest 1029 -f $CASES/1002.dat entry 2006/10/20 "stock option" -runtest 1028 -f $CASES/1002.dat entry 2006/10/20 "stock option" -20 -runtest 1027 -f $CASES/1002.dat entry 2006/10/20 "stock option" opti -runtest 1026 -f $CASES/1002.dat entry 2006/10/20 "stock option" time -runtest 1025 -f $CASES/1002.dat entry 2006/10/20 "stock option" gain -runtest 1024 -f $CASES/1002.dat entry 2006/10/20 "stock option" opti -20 -runtest 1023 -f $CASES/1002.dat entry 2006/10/20 "stock option" time -20 -runtest 1022 -f $CASES/1002.dat entry 2006/10/20 "stock option" gain -20 - -runtest 1021 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" -runtest 1020 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" -20 -runtest 1019 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" opti -runtest 1018 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" time -runtest 1017 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" gain -runtest 1016 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" opti -20 -runtest 1015 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" time -20 -runtest 1014 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" gain -20 - -runtest 1013 -f $CASES/1002.dat entry 2006/10/20 "stock option" xopti -runtest 1012 -f $CASES/1002.dat entry 2006/10/20 "stock option" xtime -runtest 1011 -f $CASES/1002.dat entry 2006/10/20 "stock option" xgain -runtest 1010 -f $CASES/1002.dat entry 2006/10/20 "stock option" xopti -20 -runtest 1009 -f $CASES/1002.dat entry 2006/10/20 "stock option" xtime -20 -runtest 1008 -f $CASES/1002.dat entry 2006/10/20 "stock option" xgain -20 - -runtest 1007 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xopti -runtest 1006 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xtime -runtest 1005 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xgain -runtest 1004 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xopti -20 -runtest 1003 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xtime -20 -runtest 1002 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xgain -20 - -runtest 1001 -f $CASES/1001.dat bal - -if [ -f $UTILS/standard.dat ]; then - runtest 10 -f $UTILS/standard.dat --truncate=trailing -M -r -s -n reg rent - runtest 9 -f $UTILS/standard.dat --truncate=trailing -M -r -s reg rent - runtest 8 -f $UTILS/standard.dat --truncate=trailing -M -r -n reg rent - runtest 7 -f $UTILS/standard.dat --truncate=trailing -M -r reg rent - runtest 6 -f $UTILS/standard.dat --truncate=trailing -M reg rent - runtest 5 -f $UTILS/standard.dat --truncate=trailing -r -s -n reg rent - runtest 4 -f $UTILS/standard.dat --truncate=trailing -r -s reg rent - runtest 3 -f $UTILS/standard.dat --truncate=trailing -r -n reg rent - runtest 2 -f $UTILS/standard.dat --truncate=trailing -r reg rent - runtest 1 -f $UTILS/standard.dat --truncate=trailing reg rent -fi - -echo Running Ledger regression tests...done - -exit $result diff --git a/tests/regtest b/tests/regtest deleted file mode 100755 index 57dec8e0..00000000 --- a/tests/regtest +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -errors=0 - -if [ ! -f utils/standard.dat ]; then - exit 0 -fi - -for test in \ - "-O nrl:checking" \ - "-O ^expenses" \ - "-B 401" \ - "-V 401" \ - "-G 401" \ - "-B ira" \ - "-V ira" \ - "-G ira" \ - "-B retire" \ - "-V retire" \ - "-G retire" -do - echo testing: $test - python tests/confirm.py $test - errors=`expr $errors + $?` -done - -exit $errors diff --git a/tests/runtests.py b/tests/runtests.py deleted file mode 100755 index 7649f775..00000000 --- a/tests/runtests.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python - -import random -import string -import signal -import os -import sys -import time - -true, false = 1, 0 - -options = [ - "--account=TempAccount", - "--actual", - "--add-budget", - "--amount-data", - "--amount=a", - "--ansi", - "--ansi-invert", - "--average", - #"--balance-format", - "--basis", - "--begin=2004/01", - "--budget", - "--by-payee", - #"--cache=/tmp/cache", - "--cleared", - "--collapse", - "--comm-as-payee", - #"--csv-register-format", - "--current", - "--date-format=%Y", - "--descend='$100'", - "--descend-if='t=={$100}'", - "--deviation", - "--display='a>10'", - "--dow", - "--download", - "--effective", - "--empty", - "--end=2005/01", - #"--equity-format", - #"--file=/tmp/file", - "--forecast='d<[2006]'", - "--format=%Y", - #"--full-help", - "--gain", - "--head=10", - #"--help", - #"--help-calc", - #"--help-comm", - #"--help-disp", - #"--init-file=/tmp/init", - #"--input-date-format", - "--limit='a>10'", - "--lots", - "--lot-prices", - "--lot-dates", - "--lot-tags", - "--market", - "--monthly", - "--no-cache", - #"--output=/tmp/output", - #"--pager", - #"--percentage", - "--performance", - "--period-sort=A\\(t\\)", - "--period=oct", - #"--plot-amount-format", - #"--plot-total-format", - "--price", - "--price-exp=1000", - #"--price-db=/tmp/foo", - #"--prices-format", - #"--print-format", - "--quantity", - "--real", - #"--reconcile", - #"--reconcile-date", - #"--register-format", - "--related", - "--sort=A\\(t\\)", - "--subtotal", - "--tail=5", - "--total-data", - "--total=O", - "--totals", - "--unbudgeted", - "--uncleared", - #"--version", - "--weekly", - "--wide", - #"--wide-register-format", - #"--write-hdr-format", - #"--write-xact-format", - "--yearly", -] - -commands = [ - "bal rent", - "bal ira", - "bal auto", - "reg rent", - "reg ira", - "reg expenses:food", - "print rent", - "print irc", - "xml rent", - "xml irc", - "equity rent", - "equity ira", - "prices AAPL", -] - -random.seed () - -loop = true -count = 0 -errors = 0 -if len(sys.argv) > 1: - errors = int(sys.argv[1]) -signals = 0 - -while loop: - try: - n = random.randint (0, len (options)) - opts = random.sample (options, n) - for cmd in commands: - if os.path.exists ("/tmp/out"): - os.unlink ("/tmp/out") - - cmd = "./ledger -f utils/standard.dat " + string.join(opts, " ") + " " + cmd + \ - " >> /tmp/out 2>&1" - - sys.stdout = open ("/tmp/out", "w") - print "::", cmd - sys.stdout.close () - - ret = os.system (cmd) - - sys.stdout = open ("/tmp/out", "a") - - # Convert an odd UNIX return type into the appropriate - # signal indication. - if ret and ret % 256 == 0 and ret / 256 > 127: - ret = 0x100 + (ret / 256 - 128) - - if ret and ret % 256 == 0: - print "ERROR: Return value", ret / 256 - sys.stdout.close () - os.system ("cat /tmp/out >> errors.out") - errors += 1 - elif ret: - if ret % 256 == signal.SIGINT: - loop = false - break - print "SIGNAL: Return value", ret % 256 - sys.stdout.close () - os.system ("cat /tmp/out >> signals.out") - signals += 1 - else: - sys.stdout.close () - os.system ("cat /tmp/out >> results.out") - - sys.stdout = sys.__stdout__ - count += 1 - if count < 10 or \ - (count < 100 and count % 10 == 0) or \ - (count < 1000 and count % 100 == 0) or \ - count % 1000 == 0: - if signals > 0 and errors > 0: - print "%d tests ... (%d signals, %d errors)" % \ - (count, signals, errors) - elif signals > 0: - print "%d tests ... (%d signals)" % \ - (count, signals) - elif errors > 0: - print "%d tests ... (%d errors)" % \ - (count, errors) - else: - print "%d tests ..." % count - - except KeyboardInterrupt: - loop = false diff --git a/tests/textual.h b/tests/textual.h deleted file mode 100644 index adf24c77..00000000 --- a/tests/textual.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef __TESTTEXTUALJOURNAL_H -#define __TESTTEXTUALJOURNAL_H - -#include <cxxtest/TestSuite.h> - -#include <textual.h> -#include <config.h> - -using namespace std; -using namespace ledger; - -class TestTextualJournal : public CxxTest::TestSuite -{ -public: - void testEmptyFileIsTextualFile() - { - stringstream j(stringstream::in); - - j << "2005/10/15 Something" << endl; - j << " A $ 42" << endl; - j << " B" << endl; - - textual_parser_t textualParser; - TS_ASSERT(textualParser.test(j)); - TS_ASSERT(j.good()); - TS_ASSERT_EQUALS(0, j.tellg()); - - config_t config; - std::auto_ptr<journal_t> journal(new journal_t); - textualParser.parse(j, config, journal.get()); - } -}; - -#endif // __TESTTEXTUALJOURNAL_H @@ -4,45 +4,30 @@ #include "journal.h" #include "textual.h" -#include "datetime.h" #include "valexpr.h" -#include "error.h" #include "option.h" #include "config.h" -#include "timing.h" -#include "util.h" +#include "utils.h" #include "acconf.h" -#include <fstream> -#include <sstream> -#include <cstring> -#include <cctype> -#include <cstdio> -#include <cstdlib> -#include <climits> - -#ifdef HAVE_REALPATH -extern "C" char *realpath(const char *, char resolved_path[]); -#endif - #define TIMELOG_SUPPORT 1 namespace ledger { #define MAX_LINE 1024 -static std::string path; +static path pathname; static unsigned int linenum; static unsigned int src_idx; static accounts_map account_aliases; -static std::list<std::pair<std::string, int> > include_stack; +static std::list<std::pair<path, int> > include_stack; #ifdef TIMELOG_SUPPORT struct time_entry_t { datetime_t checkin; account_t * account; - std::string desc; + string desc; time_entry_t() : account(NULL) {} time_entry_t(datetime_t _checkin, @@ -56,28 +41,6 @@ struct time_entry_t { }; #endif -inline char * next_element(char * buf, bool variable = false) -{ - for (char * p = buf; *p; p++) { - if (! (*p == ' ' || *p == '\t')) - continue; - - if (! variable) { - *p = '\0'; - return skip_ws(p + 1); - } - else if (*p == '\t') { - *p = '\0'; - return skip_ws(p + 1); - } - else if (*(p + 1) == ' ') { - *p = '\0'; - return skip_ws(p + 2); - } - } - return NULL; -} - static value_expr parse_amount_expr(std::istream& in, amount_t& amount, transaction_t * xact, unsigned short flags = 0) @@ -85,7 +48,7 @@ static value_expr parse_amount_expr(std::istream& in, amount_t& amount, value_expr expr(parse_value_expr(in, NULL, flags | PARSE_VALEXPR_RELAXED | PARSE_VALEXPR_PARTIAL)->acquire()); - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Parsed an amount expression"); #ifdef DEBUG_ENABLED @@ -113,7 +76,7 @@ static value_expr parse_amount_expr(std::istream& in, amount_t& amount, expr = NULL; #endif - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "The transaction amount is " << xact->amount); return expr; } @@ -123,7 +86,7 @@ transaction_t * parse_transaction(char * line, account_t * account, { std::istringstream in(line); - std::string err_desc; + string err_desc; try { // The account will be determined later... @@ -139,14 +102,14 @@ transaction_t * parse_transaction(char * line, account_t * account, xact->state = transaction_t::CLEARED; in.get(p); p = peek_next_nonws(in); - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Parsed the CLEARED flag"); break; case '!': xact->state = transaction_t::PENDING; in.get(p); p = peek_next_nonws(in); - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Parsed the PENDING flag"); break; } @@ -172,18 +135,18 @@ transaction_t * parse_transaction(char * line, account_t * account, if ((*b == '[' && *(e - 1) == ']') || (*b == '(' && *(e - 1) == ')')) { xact->flags |= TRANSACTION_VIRTUAL; - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Parsed a virtual account name"); if (*b == '[') { xact->flags |= TRANSACTION_BALANCE; - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Parsed a balanced virtual account name"); } b++; e--; } - std::string name(b, e - b); - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + string name(b, e - b); + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Parsed account name " << name); if (account_aliases.size() > 0) { accounts_map::const_iterator i = account_aliases.find(name); @@ -210,7 +173,7 @@ transaction_t * parse_transaction(char * line, account_t * account, PARSE_VALEXPR_NO_REDUCE); unsigned long end = (long)in.tellg(); - xact->amount_expr.expr = std::string(line, beg, end - beg); + xact->amount_expr.expr = string(line, beg, end - beg); } catch (error * err) { err_desc = "While parsing transaction amount:"; @@ -223,14 +186,14 @@ transaction_t * parse_transaction(char * line, account_t * account, if (in.good() && ! in.eof()) { p = peek_next_nonws(in); if (p == '@') { - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Found a price indicator"); bool per_unit = true; in.get(p); if (in.peek() == '@') { in.get(p); per_unit = false; - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "And it's for a total price"); } @@ -248,18 +211,18 @@ transaction_t * parse_transaction(char * line, account_t * account, unsigned long end = (long)in.tellg(); if (per_unit) - xact->cost_expr = (std::string("@") + - std::string(line, beg, end - beg)); + xact->cost_expr = (string("@") + + string(line, beg, end - beg)); else - xact->cost_expr = (std::string("@@") + - std::string(line, beg, end - beg)); + xact->cost_expr = (string("@@") + + string(line, beg, end - beg)); } catch (error * err) { err_desc = "While parsing transaction cost:"; throw err; } - if (*xact->cost < 0) + if (xact->cost->sign() < 0) throw new parse_error("A transaction's cost may not be negative"); amount_t per_unit_cost(*xact->cost); @@ -270,22 +233,24 @@ transaction_t * parse_transaction(char * line, account_t * account, if (xact->amount.commodity() && ! xact->amount.commodity().annotated) - xact->amount.annotate_commodity(per_unit_cost, - xact->entry ? xact->entry->actual_date() : datetime_t(), - xact->entry ? xact->entry->code : ""); + xact->amount.annotate_commodity + (annotation_t + (per_unit_cost, + xact->entry ? optional<datetime_t>(xact->entry->actual_date()) : none, + xact->entry ? optional<string>(xact->entry->code) : none)); - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Total cost is " << *xact->cost); - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Per-unit cost is " << per_unit_cost); - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Annotated amount is " << xact->amount); } } } xact->amount.reduce(); - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Reduced amount is " << xact->amount); // Parse the optional note @@ -297,7 +262,7 @@ transaction_t * parse_transaction(char * line, account_t * account, in.get(p); p = peek_next_nonws(in); xact->note = &line[in.tellg()]; - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << + DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Parsed a note '" << xact->note << "'"); if (char * b = std::strchr(xact->note.c_str(), '[')) @@ -306,15 +271,15 @@ transaction_t * parse_transaction(char * line, account_t * account, std::strncpy(buf, b + 1, e - b - 1); buf[e - b - 1] = '\0'; - DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << - "Parsed a transaction date " << buf); + DEBUG("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a transaction date " << buf); if (char * p = std::strchr(buf, '=')) { *p++ = '\0'; - xact->_date_eff = p; + xact->_date_eff = parse_datetime(p); } if (buf[0]) - xact->_date = buf; + xact->_date = parse_datetime(buf); } } } @@ -335,9 +300,11 @@ transaction_t * parse_transaction(char * line, account_t * account, bool parse_transactions(std::istream& in, account_t * account, entry_base_t& entry, - const std::string& kind, + const string& kind, unsigned long beg_pos) { + TRACE_START(entry_xacts, 1, "Time spent parsing transactions:"); + static char line[MAX_LINE + 1]; bool added = false; @@ -348,7 +315,7 @@ bool parse_transactions(std::istream& in, int len = std::strlen(line); if (line[len - 1] == '\r') - line[--len] == '\0'; + line[--len] = '\0'; beg_pos += len + 1; linenum++; @@ -364,39 +331,30 @@ bool parse_transactions(std::istream& in, } } - return added; -} + TRACE_STOP(entry_xacts, 1); -namespace { - TIMER_DEF(parsing_total, "total parsing time"); - TIMER_DEF(entry_xacts, "parsing transactions"); - TIMER_DEF(entry_details, "parsing entry details"); - TIMER_DEF(entry_date, "parsing entry date"); + return added; } entry_t * parse_entry(std::istream& in, char * line, account_t * master, textual_parser_t& parser, unsigned long& pos) { + TRACE_START(entry_text, 1, "Time spent preparing entry text:"); + std::auto_ptr<entry_t> curr(new entry_t); // Parse the date - TIMER_START(entry_date); - char * next = next_element(line); if (char * p = std::strchr(line, '=')) { *p++ = '\0'; - curr->_date_eff = p; + curr->_date_eff = parse_datetime(p); } - curr->_date = line; - - TIMER_STOP(entry_date); + curr->_date = parse_datetime(line); // Parse the optional cleared flag: * - TIMER_START(entry_details); - transaction_t::state_t state = transaction_t::UNCLEARED; if (next) { switch (*next) { @@ -425,11 +383,11 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master, curr->payee = next ? next : "<Unspecified payee>"; - TIMER_STOP(entry_details); + TRACE_STOP(entry_text, 1); // Parse all of the transactions associated with this entry - TIMER_START(entry_xacts); + TRACE_START(entry_details, 1, "Time spent parsing entry details:"); unsigned long end_pos; unsigned long beg_line = linenum; @@ -444,7 +402,7 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master, int len = std::strlen(line); if (line[len - 1] == '\r') - line[--len] == '\0'; + line[--len] = '\0'; end_pos = beg_pos + len + 1; linenum++; @@ -473,7 +431,7 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master, break; } - TIMER_STOP(entry_xacts); + TRACE_STOP(entry_details, 1); return curr.release(); } @@ -486,13 +444,13 @@ struct push_var { ~push_var() { var = prev; } }; -static inline void parse_symbol(char *& p, std::string& symbol) +static inline void parse_symbol(char *& p, string& symbol) { if (*p == '"') { char * q = std::strchr(p + 1, '"'); if (! q) throw new parse_error("Quoted commodity symbol lacks closing quote"); - symbol = std::string(p + 1, 0, q - p - 1); + symbol = string(p + 1, 0, q - p - 1); p = q + 2; } else { char * q = next_element(p); @@ -577,7 +535,7 @@ static void clock_out_from_timelog(std::list<time_entry_t>& time_entries, ("Timelog check-out date less than corresponding check-in"); char buf[32]; - std::sprintf(buf, "%lds", curr->_date - event.checkin); + std::sprintf(buf, "%lds", long((curr->_date - event.checkin).seconds())); amount_t amt; amt.parse(buf); @@ -592,19 +550,18 @@ static void clock_out_from_timelog(std::list<time_entry_t>& time_entries, curr.release(); } -unsigned int textual_parser_t::parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) +unsigned int textual_parser_t::parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master, + const path * original_file) { - static bool added_auto_entry_hook = false; - static char line[MAX_LINE + 1]; - char c; - unsigned int count = 0; - unsigned int errors = 0; + TRACE_START(parsing_total, 1, "Total time spent parsing text:"); - TIMER_START(parsing_total); + static bool added_auto_entry_hook = false; + static char line[MAX_LINE + 1]; + unsigned int count = 0; + unsigned int errors = 0; std::list<account_t *> account_stack; auto_entry_finalizer_t auto_entry_finalizer(journal); @@ -615,7 +572,7 @@ unsigned int textual_parser_t::parse(std::istream& in, account_stack.push_front(master); - path = journal->sources.back(); + pathname = journal->sources.back(); src_idx = journal->sources.size() - 1; linenum = 1; @@ -631,7 +588,7 @@ unsigned int textual_parser_t::parse(std::istream& in, int len = std::strlen(line); if (line[len - 1] == '\r') - line[--len] == '\0'; + line[--len] = '\0'; end_pos = beg_pos + len + 1; linenum++; @@ -651,13 +608,13 @@ unsigned int textual_parser_t::parse(std::istream& in, #ifdef TIMELOG_SUPPORT case 'i': case 'I': { - std::string date(line, 2, 19); + string date(line, 2, 19); char * p = skip_ws(line + 22); char * n = next_element(p, true); - time_entry_t event(date, account_stack.front()->find_account(p), - n ? n : ""); + time_entry_t event(parse_datetime(date), + account_stack.front()->find_account(p), n ? n : ""); if (! time_entries.empty()) for (std::list<time_entry_t>::iterator i = time_entries.begin(); @@ -676,13 +633,13 @@ unsigned int textual_parser_t::parse(std::istream& in, if (time_entries.empty()) { throw new parse_error("Timelog check-out event without a check-in"); } else { - std::string date(line, 2, 19); + string date(line, 2, 19); char * p = skip_ws(line + 22); char * n = next_element(p, true); clock_out_from_timelog - (time_entries, date, + (time_entries, parse_datetime(date), p ? account_stack.front()->find_account(p) : NULL, n, journal); count++; } @@ -691,7 +648,7 @@ unsigned int textual_parser_t::parse(std::istream& in, case 'D': { // a default commodity for "entry" amount_t amt(skip_ws(line + 1)); - commodity_t::default_commodity = &amt.commodity(); + amount_t::current_pool->default_commodity = &amt.commodity(); break; } @@ -703,7 +660,10 @@ unsigned int textual_parser_t::parse(std::istream& in, case 'C': // a set of conversions if (char * p = std::strchr(line + 1, '=')) { *p++ = '\0'; + // jww (2008-04-22): NYI! +#if 0 parse_conversion(line + 1, p); +#endif } break; @@ -711,7 +671,7 @@ unsigned int textual_parser_t::parse(std::istream& in, char * date_field_ptr = skip_ws(line + 1); char * time_field_ptr = next_element(date_field_ptr); if (! time_field_ptr) break; - std::string date_field = date_field_ptr; + string date_field = date_field_ptr; char * symbol_and_price; datetime_t datetime; @@ -719,33 +679,35 @@ unsigned int textual_parser_t::parse(std::istream& in, if (std::isdigit(time_field_ptr[0])) { symbol_and_price = next_element(time_field_ptr); if (! symbol_and_price) break; - datetime = date_field + " " + time_field_ptr; + datetime = parse_datetime(date_field + " " + time_field_ptr); } else { symbol_and_price = time_field_ptr; - datetime = date_t(date_field); + datetime = parse_datetime(date_field); } - std::string symbol; + string symbol; parse_symbol(symbol_and_price, symbol); amount_t price(symbol_and_price); - if (commodity_t * commodity = commodity_t::find_or_create(symbol)) + if (commodity_t * commodity = + amount_t::current_pool->find_or_create(symbol)) commodity->add_price(datetime, price); break; } case 'N': { // don't download prices char * p = skip_ws(line + 1); - std::string symbol; + string symbol; parse_symbol(p, symbol); - if (commodity_t * commodity = commodity_t::find_or_create(symbol)) + if (commodity_t * commodity = + amount_t::current_pool->find_or_create(symbol)) commodity->add_flags(COMMODITY_STYLE_NOMARKET); break; } case 'Y': // set the current year - date_t::current_year = std::atoi(skip_ws(line + 1)); + current_year = std::atoi(skip_ws(line + 1)); break; #ifdef TIMELOG_SUPPORT @@ -789,7 +751,7 @@ unsigned int textual_parser_t::parse(std::istream& in, case '~': { // period entry period_entry_t * pe = new period_entry_t(skip_ws(line + 1)); if (! pe->period) - throw new parse_error(std::string("Parsing time period '") + line + "'"); + throw new parse_error(string("Parsing time period '") + line + "'"); if (parse_transactions(in, account_stack.front(), *pe, "period", end_pos)) { @@ -811,30 +773,33 @@ unsigned int textual_parser_t::parse(std::istream& in, case '@': case '!': { // directive char * p = next_element(line); - std::string word(line + 1); + string word(line + 1); if (word == "include") { - push_var<std::string> save_path(path); + push_var<path> save_pathname(pathname); push_var<unsigned int> save_src_idx(src_idx); push_var<unsigned long> save_beg_pos(beg_pos); push_var<unsigned long> save_end_pos(end_pos); push_var<unsigned int> save_linenum(linenum); - path = p; - if (path[0] != '/' && path[0] != '\\' && path[0] != '~') { - std::string::size_type pos = save_path.prev.rfind('/'); - if (pos == std::string::npos) - pos = save_path.prev.rfind('\\'); - if (pos != std::string::npos) - path = std::string(save_path.prev, 0, pos + 1) + path; + // jww (2008-04-22): Fix this! + pathname = p; +#if 0 + if (pathname[0] != '/' && pathname[0] != '\\' && pathname[0] != '~') { + string::size_type pos = save_pathname.prev.rfind('/'); + if (pos == string::npos) + pos = save_pathname.prev.rfind('\\'); + if (pos != string::npos) + pathname = string(save_pathname.prev, 0, pos + 1) + pathname; } - path = resolve_path(path); + pathname = resolve_path(pathname); +#endif - DEBUG_PRINT("ledger.textual.include", "line " << linenum << ": " << - "Including path '" << path << "'"); + DEBUG("ledger.textual.include", "line " << linenum << ": " << + "Including path '" << pathname << "'"); - include_stack.push_back(std::pair<std::string, int> + include_stack.push_back(std::pair<path, int> (journal->sources.back(), linenum - 1)); - count += parse_journal_file(path, config, journal, + count += parse_journal_file(pathname, config, journal, account_stack.front()); include_stack.pop_back(); } @@ -874,8 +839,8 @@ unsigned int textual_parser_t::parse(std::istream& in, } default: { - unsigned int first_line = linenum; unsigned long pos = beg_pos; + TRACE_START(entries, 1, "Time spent handling entries:"); if (entry_t * entry = parse_entry(in, line, account_stack.front(), *this, pos)) { if (journal->add_entry(entry)) { @@ -893,18 +858,19 @@ unsigned int textual_parser_t::parse(std::istream& in, throw new parse_error("Failed to parse entry"); } end_pos = pos; + TRACE_STOP(entries, 1); break; } } } catch (error * err) { - for (std::list<std::pair<std::string, int> >::reverse_iterator i = + for (std::list<std::pair<path, int> >::reverse_iterator i = include_stack.rbegin(); i != include_stack.rend(); i++) err->context.push_back(new include_context((*i).first, (*i).second, "In file included from")); - err->context.push_front(new file_context(path, linenum - 1)); + err->context.push_front(new file_context(pathname, linenum - 1)); std::cout.flush(); if (errors > 0 && err->context.size() > 1) @@ -917,7 +883,6 @@ unsigned int textual_parser_t::parse(std::istream& in, beg_pos = end_pos; } - done: if (! time_entries.empty()) { std::list<account_t *> accounts; @@ -929,7 +894,7 @@ unsigned int textual_parser_t::parse(std::istream& in, for (std::list<account_t *>::iterator i = accounts.begin(); i != accounts.end(); i++) - clock_out_from_timelog(time_entries, datetime_t::now, *i, NULL, journal); + clock_out_from_timelog(time_entries, current_moment, *i, NULL, journal); assert(time_entries.empty()); } @@ -940,20 +905,20 @@ unsigned int textual_parser_t::parse(std::istream& in, if (errors > 0) throw (int)errors; - TIMER_STOP(parsing_total); + TRACE_STOP(parsing_total, 1); return count; } -void write_textual_journal(journal_t& journal, std::string path, +void write_textual_journal(journal_t& journal, path pathname, item_handler<transaction_t>& formatter, - const std::string& write_hdr_format, + const string& write_hdr_format, std::ostream& out) { unsigned long index = 0; - std::string found; + path found; - if (path.empty()) { + if (pathname.empty()) { if (! journal.sources.empty()) found = *journal.sources.begin(); } else { @@ -961,12 +926,12 @@ void write_textual_journal(journal_t& journal, std::string path, char buf1[PATH_MAX]; char buf2[PATH_MAX]; - ::realpath(path.c_str(), buf1); + ::realpath(pathname.string().c_str(), buf1); - for (strings_list::iterator i = journal.sources.begin(); + for (paths_list::iterator i = journal.sources.begin(); i != journal.sources.end(); i++) { - ::realpath((*i).c_str(), buf2); + ::realpath((*i).string().c_str(), buf2); if (std::strcmp(buf1, buf2) == 0) { found = *i; break; @@ -974,10 +939,10 @@ void write_textual_journal(journal_t& journal, std::string path, index++; } #else - for (strings_list::iterator i = journal.sources.begin(); + for (paths_list::iterator i = journal.sources.begin(); i != journal.sources.end(); i++) { - if (path == *i) { + if (pathname == *i) { found = *i; break; } @@ -987,8 +952,8 @@ void write_textual_journal(journal_t& journal, std::string path, } if (found.empty()) - throw new error(std::string("Journal does not refer to file '") + - path + "'"); + throw new error(string("Journal does not refer to file '") + + string(pathname.string()) + "'"); entries_list::iterator el = journal.entries.begin(); auto_entries_list::iterator al = journal.auto_entries.begin(); @@ -997,7 +962,7 @@ void write_textual_journal(journal_t& journal, std::string path, unsigned long pos = 0; format_t hdr_fmt(write_hdr_format); - std::ifstream in(found.c_str()); + boost::filesystem::ifstream in(found); while (! in.eof()) { entry_base_t * base = NULL; @@ -12,32 +12,33 @@ class textual_parser_t : public parser_t public: virtual bool test(std::istream& in) const; - virtual unsigned int parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); + virtual unsigned int parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master = NULL, + const path * original_file = NULL); }; transaction_t * parse_transaction_text(char * line, account_t * account); transaction_t * parse_transaction(std::istream& in, account_t * account); -void write_textual_journal(journal_t& journal, std::string path, +void write_textual_journal(journal_t& journal, path pathname, item_handler<transaction_t>& formatter, - const std::string& write_hdr_format, + const string& write_hdr_format, std::ostream& out); class include_context : public file_context { public: - include_context(const std::string& file, unsigned long line, - const std::string& desc = "") throw() + include_context(const path& file, unsigned long line, + const string& desc = "") throw() : file_context(file, line, desc) {} virtual ~include_context() throw() {} virtual void describe(std::ostream& out) const throw() { if (! desc.empty()) out << desc << ": "; - out << "\"" << file << "\", line " << line << ":" << std::endl; + out << "\"" << file.string() << "\", line " << line << ":" + << std::endl; } }; @@ -1,21 +1,58 @@ -#if defined(__GNUG__) && __GNUG__ < 3 -#define _XOPEN_SOURCE +/* + * 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(); -#include "debug.h" -#include "datetime.h" +#ifdef SUPPORT_DATE_AND_TIME +const datetime_t& current_moment(time_now); +#else +const datetime_t& current_moment(date_now); +#endif -#include <ctime> -#include <cctype> -#include <climits> -#include <cstdlib> +int current_year(current_moment.date().year()); -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"; +string input_time_format; +string output_time_format = "%Y/%m/%d"; -const char * date_t::formats[] = { +#if 0 +static const char * formats[] = { "%y/%m/%d", "%Y/%m/%d", "%m/%d", @@ -32,7 +69,12 @@ const char * date_t::formats[] = { "%Y", NULL }; +#endif + +bool day_before_month = false; +static bool day_before_month_initialized = false; +#if 0 datetime_t datetime_t::now(std::time(NULL)); namespace { @@ -48,35 +90,43 @@ namespace { const int year = -1); bool quick_parse_date(const char * date_str, std::time_t * result); } +#endif -date_t::date_t(const std::string& _when) +datetime_t parse_datetime(const char * str) { - if (! quick_parse_date(_when.c_str(), &when)) - throw new date_error - (std::string("Invalid date string: ") + _when); -} + 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')); -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())); + int mon = ((str[5] - '0') * 10 + + (str[6] - '0')); - 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); + int day = ((str[8] - '0') * 10 + + (str[9] - '0')); - when = std::mktime(&moment); - } else { - when = date_t(_when).when; - } + return datetime_t(boost::gregorian::date(year, mon, day)); +#endif } datetime_t interval_t::first(const datetime_t& moment) const { datetime_t quant(begin); - if (moment && moment > quant) { +#if 0 + // jww (2008-05-08): Implement + if (is_valid(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 @@ -102,14 +152,17 @@ datetime_t interval_t::first(const datetime_t& moment) const quant = temp; } } +#endif return quant; } datetime_t interval_t::increment(const datetime_t& moment) const { +#if 0 struct std::tm * desc = std::localtime(&moment.when); + // jww (2008-05-08): Implement if (years) desc->tm_year += years; if (months) @@ -124,16 +177,21 @@ datetime_t interval_t::increment(const datetime_t& moment) const desc->tm_isdst = -1; return std::mktime(desc); +#else + return datetime_t(); +#endif } namespace { - void parse_inclusion_specifier(const std::string& word, + void parse_inclusion_specifier(const string& word, datetime_t * begin, datetime_t * end) { +#if 0 + // jww (2008-05-08): Implement! struct std::tm when; if (! parse_date_mask(word.c_str(), &when)) - throw new datetime_error(std::string("Could not parse date mask: ") + word); + throw new datetime_error(string("Could not parse date mask: ") + word); when.tm_hour = 0; when.tm_min = 0; @@ -175,18 +233,19 @@ namespace { *end = std::mktime(&when); assert(int(*end) != -1); } +#endif } - inline void read_lower_word(std::istream& in, std::string& word) { + inline void read_lower_word(std::istream& in, string& word) { in >> word; for (int i = 0, l = word.length(); i < l; i++) word[i] = std::tolower(word[i]); } - void parse_date_words(std::istream& in, std::string& word, + void parse_date_words(std::istream& in, string& word, datetime_t * begin, datetime_t * end) { - std::string type; + string type; bool mon_spec = false; char buf[32]; @@ -202,12 +261,18 @@ namespace { } if (word == "month") { +#if 0 + // jww (2008-05-08): std::strftime(buf, 31, "%B", datetime_t::now.localtime()); +#endif word = buf; mon_spec = true; } else if (word == "year") { +#if 0 + // jww (2008-05-08): std::strftime(buf, 31, "%Y", datetime_t::now.localtime()); +#endif word = buf; } @@ -244,7 +309,7 @@ namespace { void interval_t::parse(std::istream& in) { - std::string word; + string word; while (! in.eof()) { read_lower_word(in, word); @@ -327,21 +392,26 @@ void interval_t::parse(std::istream& in) namespace { bool parse_date_mask(const char * date_str, struct std::tm * result) { +#if 0 + // jww (2008-05-08): if (! date_t::input_format.empty()) { std::memset(result, -1, 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, -1, sizeof(struct std::tm)); + for (const char ** f = formats; *f; f++) { + std::memset(result, INT_MAX, sizeof(struct std::tm)); if (strptime(date_str, *f, result)) return true; } +#endif return false; } bool parse_date(const char * date_str, std::time_t * result, const int year) { +#if 0 + // jww (2008-05-08): struct std::tm when; if (! parse_date_mask(date_str, &when)) @@ -361,12 +431,20 @@ namespace { when.tm_mday = 1; *result = std::mktime(&when); +#endif return true; } bool quick_parse_date(const char * date_str, std::time_t * result) { +#if 0 + // jww (2008-05-08): return parse_date(date_str, result, date_t::current_year); +#else + return false; +#endif } } + +} // namespace ledger diff --git a/times.h b/times.h new file mode 100644 index 00000000..d8da7937 --- /dev/null +++ b/times.h @@ -0,0 +1,155 @@ +/* + * 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 datetime_t; +typedef datetime_t::time_duration_type duration_t; + +inline bool is_valid(const datetime_t& moment) { + return ! moment.is_not_a_date_time(); +} + +#else // SUPPORT_DATE_AND_TIME + +typedef boost::gregorian::date datetime_t; +typedef boost::gregorian::date_duration duration_t; + +inline bool is_valid(const datetime_t& moment) { + return ! moment.is_not_a_date(); +} + +#endif // SUPPORT_DATE_AND_TIME + +extern const datetime_t& current_moment; + +extern int current_year; +extern string input_time_format; +extern string output_time_format; + +DECLARE_EXCEPTION(error, datetime_error); + +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 datetime_t& _begin = datetime_t(), + const datetime_t& _end = datetime_t()) + : years(_years), months(_months), days(_days), + hours(0), minutes(0), seconds(0), + begin(_begin), end(_end) {} + + interval_t(const 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); +}; + +#if 0 +inline datetime_t ptime_local_to_utc(const datetime_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 datetime_t ptime_from_local_date_string(const string& date_string) { + return ptime_local_to_utc(datetime_t(boost::gregorian::from_string(date_string), + time_duration())); +} + +inline datetime_t ptime_from_local_time_string(const string& time_string) { + return ptime_local_to_utc(boost::posix_time::time_from_string(time_string)); +} +#endif + +datetime_t parse_datetime(const char * str); + +inline datetime_t parse_datetime(const string& str) { + return parse_datetime(str.c_str()); +} + +inline string format_datetime(const datetime_t& when) { + return ""; // jww (2008-07-19): NYI +} + +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::datetime_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 <ctime> - -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 <boost/mpl/int.hpp> +#include <boost/mpl/next.hpp> + +/** + * 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<your_type_t> storage_t; + * storage_t* the_storage = reinterpret_cast<storage_t*>( 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<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<TTuple> storage_t; + storage_t* the_storage = reinterpret_cast<storage_t*>( 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<element_type> type_checker( element ); + if( !type_checker.check() ){ + return false; + } + else{ + return convertible_impl( py_sequence, details::increment_index<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_type>( element ); + + construct_impl( py_sequence, c_tuple, details::increment_index<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<TTuple> >(); + + converter::registry::push_back( &from_py_sequence<TTuple>::convertible + , &from_py_sequence<TTuple>::construct + , type_id<TTuple>() ); +}; + +} } //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 <iostream> - -#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..4cc68667 --- /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<std::string, std::size_t> allocation_pair; +typedef std::map<void *, allocation_pair> live_memory_map; +typedef std::multimap<void *, allocation_pair> live_objects_map; + +typedef std::pair<unsigned int, std::size_t> count_size_pair; +typedef std::map<std::string, count_size_pair> object_count_map; + +static live_memory_map * live_memory = NULL; +static object_count_map * live_memory_count = NULL; +static object_count_map * total_memory_count = NULL; + +static bool memory_tracing_active = false; + +static live_objects_map * live_objects = NULL; +static object_count_map * live_object_count = NULL; +static object_count_map * total_object_count = NULL; +static object_count_map * total_ctor_count = NULL; + +void initialize_memory_tracing() +{ + memory_tracing_active = false; + + live_memory = new live_memory_map; + live_memory_count = new object_count_map; + total_memory_count = new object_count_map; + + live_objects = new live_objects_map; + live_object_count = new object_count_map; + total_object_count = new object_count_map; + total_ctor_count = new object_count_map; + + memory_tracing_active = true; +} + +void shutdown_memory_tracing() +{ + memory_tracing_active = false; + + if (live_objects) { + IF_DEBUG("memory.counts") + report_memory(std::cerr, true); + else + IF_DEBUG("memory.counts.live") + report_memory(std::cerr); + else if (live_objects->size() > 0) + report_memory(std::cerr); + } + + checked_delete(live_memory); live_memory = NULL; + checked_delete(live_memory_count); live_memory_count = NULL; + checked_delete(total_memory_count); total_memory_count = NULL; + + checked_delete(live_objects); live_objects = NULL; + checked_delete(live_object_count); live_object_count = NULL; + checked_delete(total_object_count); total_object_count = NULL; + checked_delete(total_ctor_count); total_ctor_count = NULL; +} + +inline void add_to_count_map(object_count_map& the_map, + const char * name, std::size_t size) +{ + object_count_map::iterator k = the_map.find(name); + if (k != the_map.end()) { + (*k).second.first++; + (*k).second.second += size; + } else { + std::pair<object_count_map::iterator, bool> result = + the_map.insert(object_count_map::value_type(name, count_size_pair(1, size))); + VERIFY(result.second); + } +} + +std::size_t current_memory_size() +{ + std::size_t memory_size = 0; + + 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 - current_moment).total_milliseconds(); + } + + *_log_stream << std::right << std::setw(5) + << (CURRENT_TIME() - logger_start).total_milliseconds(); + + IF_VERIFY() { + *_log_stream << std::right << std::setw(6) << std::setprecision(3); + stream_memory_size(*_log_stream, current_objects_size()); + *_log_stream << std::right << std::setw(6) << std::setprecision(3); + stream_memory_size(*_log_stream, current_memory_size()); + } + + *_log_stream << " " << std::left << std::setw(7); + + switch (level) { + case LOG_CRIT: *_log_stream << "[CRIT]"; break; + case LOG_FATAL: *_log_stream << "[FATAL]"; break; + case LOG_ASSERT: *_log_stream << "[ASSRT]"; break; + case LOG_ERROR: *_log_stream << "[ERROR]"; break; + case LOG_VERIFY: *_log_stream << "[VERFY]"; break; + case LOG_WARN: *_log_stream << "[WARN]"; break; + case LOG_INFO: *_log_stream << "[INFO]"; break; + case LOG_EXCEPT: *_log_stream << "[EXCPT]"; break; + case LOG_DEBUG: *_log_stream << "[DEBUG]"; break; + case LOG_TRACE: *_log_stream << "[TRACE]"; break; + + case LOG_OFF: + case LOG_ALL: + assert(false); + break; + } + + *_log_stream << ' ' << _log_buffer.str(); + + if (appender) + *_log_stream << " (" << appender << "ms startup)"; + + *_log_stream << std::endl; + + _log_buffer.str(""); + + return true; +} + +} // namespace ledger + +#if defined(DEBUG_ON) + +namespace ledger { + +optional<std::string> _log_category; + +} // namespace ledger + +#endif // DEBUG_ON +#endif // LOGGING_ON + +/********************************************************************** + * + * Timers (allows log entries to specify cumulative time spent) + */ + +#if defined(LOGGING_ON) && defined(TIMERS_ON) + +namespace ledger { + +struct timer_t { + log_level_t level; + ptime begin; + time_duration spent; + std::string description; + bool active; + + timer_t(log_level_t _level, std::string _description) + : level(_level), begin(CURRENT_TIME()), + spent(time_duration(0, 0, 0, 0)), + description(_description), active(true) {} +}; + +typedef std::map<std::string, timer_t> timer_map; + +static timer_map timers; + +void start_timer(const char * name, log_level_t lvl) +{ +#if defined(VERIFY_ON) + memory_tracing_active = false; +#endif + + timer_map::iterator i = timers.find(name); + if (i == timers.end()) { + timers.insert(timer_map::value_type(name, timer_t(lvl, _log_buffer.str()))); + } else { + assert((*i).second.description == _log_buffer.str()); + (*i).second.begin = CURRENT_TIME(); + (*i).second.active = true; + } + _log_buffer.str(""); + +#if defined(VERIFY_ON) + memory_tracing_active = true; +#endif +} + +void stop_timer(const char * name) +{ +#if defined(VERIFY_ON) + memory_tracing_active = false; +#endif + + timer_map::iterator i = timers.find(name); + assert(i != timers.end()); + + (*i).second.spent += CURRENT_TIME() - (*i).second.begin; + (*i).second.active = false; + +#if defined(VERIFY_ON) + memory_tracing_active = true; +#endif +} + +void finish_timer(const char * name) +{ +#if defined(VERIFY_ON) + memory_tracing_active = false; +#endif + + timer_map::iterator i = timers.find(name); + if (i == timers.end()) + return; + + time_duration spent = (*i).second.spent; + if ((*i).second.active) { + spent = CURRENT_TIME() - (*i).second.begin; + (*i).second.active = false; + } + + _log_buffer << (*i).second.description << ' '; + + bool need_paren = + (*i).second.description[(*i).second.description.size() - 1] != ':'; + + if (need_paren) + _log_buffer << '('; + + _log_buffer << spent.total_milliseconds() << "ms"; + + if (need_paren) + _log_buffer << ')'; + + logger_func((*i).second.level); + + timers.erase(i); + +#if defined(VERIFY_ON) + memory_tracing_active = true; +#endif +} + +} // namespace ledger + +#endif // LOGGING_ON && TIMERS_ON + +/********************************************************************** + * + * Exception handling + */ + +namespace ledger { + +std::ostringstream _exc_buffer; +/*ptr_list<context> 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 <system.hh> + +/********************************************************************** + * + * Default values + */ + +#if defined(DEBUG_MODE) +#define VERIFY_ON 1 +#define TRACING_ON 1 +#define DEBUG_ON 1 +#define TIMERS_ON 1 +#elif defined(NDEBUG) +#define NO_ASSERTS 1 +#define NO_LOGGING 1 +#else +#define VERIFY_ON 1 // compiled in, use --verify to enable +#define TRACING_ON 1 // use --trace X to enable +#define TIMERS_ON 1 +#endif + +/********************************************************************** + * + * Forward declarations + */ + +namespace ledger { + using namespace boost; + +#if defined(VERIFY_ON) + class string; +#else + typedef std::string string; +#endif + + typedef 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<std::string> _log_category; + +inline bool category_matches(const char * cat) { + return _log_category && starts_with(cat, *_log_category); +} + +#define SHOW_DEBUG(cat) \ + (ledger::_log_level >= ledger::LOG_DEBUG && ledger::category_matches(cat)) +#define SHOW_DEBUG_() SHOW_DEBUG(_this_category) + +#define DEBUG(cat, msg) \ + (SHOW_DEBUG(cat) ? \ + ((ledger::_log_buffer << msg), \ + ledger::logger_func(ledger::LOG_DEBUG)) : false) +#define DEBUG_(msg) DEBUG(_this_category, msg) + +#else // DEBUG_ON + +#define SHOW_DEBUG(cat) false +#define SHOW_DEBUG_() false +#define DEBUG(cat, msg) +#define DEBUG_(msg) + +#endif // DEBUG_ON + +#define LOG_MACRO(level, msg) \ + (ledger::_log_level >= level ? \ + ((ledger::_log_buffer << msg), ledger::logger_func(level)) : false) + +#define SHOW_INFO() (ledger::_log_level >= ledger::LOG_INFO) +#define SHOW_WARN() (ledger::_log_level >= ledger::LOG_WARN) +#define SHOW_ERROR() (ledger::_log_level >= ledger::LOG_ERROR) +#define SHOW_FATAL() (ledger::_log_level >= ledger::LOG_FATAL) +#define SHOW_CRITICAL() (ledger::_log_level >= ledger::LOG_CRIT) + +#define INFO(msg) LOG_MACRO(ledger::LOG_INFO, msg) +#define WARN(msg) LOG_MACRO(ledger::LOG_WARN, msg) +#define ERROR(msg) LOG_MACRO(ledger::LOG_ERROR, msg) +#define FATAL(msg) LOG_MACRO(ledger::LOG_FATAL, msg) +#define CRITICAL(msg) LOG_MACRO(ledger::LOG_CRIT, msg) +#define EXCEPTION(msg) LOG_MACRO(ledger::LOG_EXCEPT, msg) + +} // namespace ledger + +#else // ! LOGGING_ON + +#define LOGGER(cat) + +#define SHOW_TRACE(lvl) false +#define SHOW_DEBUG(cat) false +#define SHOW_DEBUG_() false +#define SHOW_INFO() false +#define SHOW_WARN() false +#define SHOW_ERROR() false +#define SHOW_FATAL() false +#define SHOW_CRITICAL() false + +#define TRACE(lvl, msg) +#define DEBUG(cat, msg) +#define DEBUG_(msg) +#define INFO(msg) +#define WARN(msg) +#define ERROR(msg) +#define FATAL(msg) +#define CRITICAL(msg) + +#endif // LOGGING_ON + +#define IF_TRACE(lvl) if (SHOW_TRACE(lvl)) +#define IF_DEBUG(cat) if (SHOW_DEBUG(cat)) +#define IF_DEBUG_() if (SHOW_DEBUG_()) +#define IF_INFO() if (SHOW_INFO()) +#define IF_WARN() if (SHOW_WARN()) +#define IF_ERROR() if (SHOW_ERROR()) +#define IF_FATAL() if (SHOW_FATAL()) +#define IF_CRITICAL() if (SHOW_CRITICAL()) + +/********************************************************************** + * + * Timers (allows log entries to specify cumulative time spent) + */ + +#if defined(LOGGING_ON) && defined(TIMERS_ON) + +namespace ledger { + +void start_timer(const char * name, log_level_t lvl); +void stop_timer(const char * name); +void finish_timer(const char * name); + +#if defined(TRACING_ON) +#define TRACE_START(name, lvl, msg) \ + (SHOW_TRACE(lvl) ? \ + ((ledger::_log_buffer << msg), \ + ledger::start_timer(#name, ledger::LOG_TRACE)) : ((void)0)) +#define TRACE_STOP(name, lvl) \ + (SHOW_TRACE(lvl) ? ledger::stop_timer(#name) : ((void)0)) +#define TRACE_FINISH(name, lvl) \ + (SHOW_TRACE(lvl) ? ledger::finish_timer(#name) : ((void)0)) +#else +#define TRACE_START(name, lvl, msg) +#define TRACE_STOP(name) +#define TRACE_FINISH(name) +#endif + +#if defined(DEBUG_ON) +#define DEBUG_START(name, cat, msg) \ + (SHOW_DEBUG(cat) ? \ + ((ledger::_log_buffer << msg), \ + ledger::start_timer(#name, ledger::LOG_DEBUG)) : ((void)0)) +#define DEBUG_START_(name, msg) \ + DEBUG_START_(name, _this_category, msg) +#define DEBUG_STOP(name, cat) \ + (SHOW_DEBUG(cat) ? ledger::stop_timer(#name) : ((void)0)) +#define DEBUG_STOP_(name) \ + DEBUG_STOP_(name, _this_category) +#define DEBUG_FINISH(name, cat) \ + (SHOW_DEBUG(cat) ? ledger::finish_timer(#name) : ((void)0)) +#define DEBUG_FINISH_(name) \ + DEBUG_FINISH_(name, _this_category) +#else +#define DEBUG_START(name, cat, msg) +#define DEBUG_START_(name, msg) +#define DEBUG_STOP(name) +#define DEBUG_FINISH(name) +#endif + +#define INFO_START(name, msg) \ + (SHOW_INFO() ? \ + ((ledger::_log_buffer << msg), \ + ledger::start_timer(#name, ledger::LOG_INFO)) : ((void)0)) +#define INFO_STOP(name) \ + (SHOW_INFO() ? stop_timer(#name) : ((void)0)) +#define INFO_FINISH(name) \ + (SHOW_INFO() ? finish_timer(#name) : ((void)0)) + +} // namespace ledger + +#else // ! (LOGGING_ON && TIMERS_ON) + +#define TRACE_START(lvl, msg, name) +#define TRACE_STOP(name) +#define TRACE_FINISH(name) + +#define DEBUG_START(name, msg) +#define DEBUG_START_(name, cat, msg) +#define DEBUG_STOP(name) +#define DEBUG_FINISH(name) + +#define INFO_START(name, msg) +#define INFO_STOP(name) +#define INFO_FINISH(name) + +#endif // TIMERS_ON + +/********************************************************************** + * + * Exception handling + */ + +#include "error.h" + +namespace ledger { + +extern std::ostringstream _exc_buffer; + +template <typename T> +inline void throw_func(const std::string& message) { + _exc_buffer.str(""); + throw T(message); +} + +#define throw_(cls, msg) \ + ((_exc_buffer << msg), throw_func<cls>(_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 <typename T, typename U> +inline T& downcast(U& object) { + return *polymorphic_downcast<T *>(&object); +} + +path resolve_path(const path& pathname); + +#ifdef HAVE_REALPATH +extern "C" char * realpath(const char *, char resolved_path[]); +#endif + +inline const string& either_or(const string& first, + const string& second) { + return first.empty() ? second : first; +} + +} // namespace ledger + +#endif // _UTILS_H @@ -1,9 +1,6 @@ #include "valexpr.h" #include "walk.h" -#include "error.h" -#include "datetime.h" -#include "debug.h" -#include "util.h" +#include "utils.h" namespace ledger { @@ -16,7 +13,7 @@ datetime_t terminus; details_t::details_t(const transaction_t& _xact) : entry(_xact.entry), xact(&_xact), account(xact_account(_xact)) { - DEBUG_PRINT("ledger.memory.ctors", "ctor details_t"); + DEBUG("ledger.memory.ctors", "ctor details_t"); } bool compute_amount(value_expr_t * expr, amount_t& amt, @@ -26,7 +23,7 @@ bool compute_amount(value_expr_t * expr, amount_t& amt, try { expr->compute(result, xact ? details_t(*xact) : details_t(), context); result.cast(value_t::AMOUNT); - amt = *((amount_t *) result.data); + amt = result.as_amount_lval(); } catch (error * err) { if (err->context.empty() || @@ -44,9 +41,9 @@ bool compute_amount(value_expr_t * expr, amount_t& amt, value_expr_t::~value_expr_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor value_expr_t " << this); + DEBUG("ledger.memory.dtors", "dtor value_expr_t " << this); - DEBUG_PRINT("ledger.valexpr.memory", "Destroying " << this); + DEBUG("ledger.valexpr.memory", "Destroying " << this); assert(refc == 0); if (left) @@ -171,15 +168,20 @@ void value_expr_t::compute(value_t& result, const details_t& details, if (transaction_has_xdata(*details.xact)) { transaction_xdata_t& xdata(transaction_xdata_(*details.xact)); if (xdata.dflags & TRANSACTION_COMPOUND) { - result = xdata.value.price(); + result = xdata.value.value(); set = true; } } - if (! set) - result = details.xact->amount.price(); + if (! set) { + optional<amount_t> value = details.xact->amount.value(); + if (value) + result = *value; + else + result = 0L; + } } else if (details.account && account_has_xdata(*details.account)) { - result = account_xdata(*details.account).value.price(); + result = account_xdata(*details.account).value.value(); } else { result = 0L; @@ -222,9 +224,9 @@ void value_expr_t::compute(value_t& result, const details_t& details, break; case PRICE_TOTAL: if (details.xact && transaction_has_xdata(*details.xact)) - result = transaction_xdata_(*details.xact).total.price(); + result = transaction_xdata_(*details.xact).total.value(); else if (details.account && account_has_xdata(*details.account)) - result = account_xdata(*details.account).total.price(); + result = account_xdata(*details.account).total.value(); else result = 0L; break; @@ -252,7 +254,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, case DATE: if (details.xact && transaction_has_xdata(*details.xact) && - transaction_xdata_(*details.xact).date) + is_valid(transaction_xdata_(*details.xact).date)) result = transaction_xdata_(*details.xact).date; else if (details.xact) result = details.xact->date(); @@ -264,7 +266,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, case ACT_DATE: if (details.xact && transaction_has_xdata(*details.xact) && - transaction_xdata_(*details.xact).date) + is_valid(transaction_xdata_(*details.xact).date)) result = transaction_xdata_(*details.xact).date; else if (details.xact) result = details.xact->actual_date(); @@ -276,7 +278,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, case EFF_DATE: if (details.xact && transaction_has_xdata(*details.xact) && - transaction_xdata_(*details.xact).date) + is_valid(transaction_xdata_(*details.xact).date)) result = transaction_xdata_(*details.xact).date; else if (details.xact) result = details.xact->effective_date(); @@ -342,7 +344,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); - result = result.price(); + result = result.value(); break; } @@ -350,7 +352,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); - result = result.date(); + result = result.as_datetime_lval(); break; } @@ -358,7 +360,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); - result = result.date(); + result = result.as_datetime_lval(); if (! result) break; @@ -366,7 +368,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, expr = find_leaf(context, 1, arg_index); value_t moment; expr->compute(moment, details, context); - if (moment.type == value_t::DATETIME) { + if (moment.is_type(value_t::DATETIME)) { result.cast(value_t::INTEGER); moment.cast(value_t::INTEGER); result -= moment; @@ -384,20 +386,22 @@ void value_expr_t::compute(value_t& result, const details_t& details, value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); - if (result.type != value_t::DATETIME) + if (! result.is_type(value_t::DATETIME)) throw new compute_error("Invalid date passed to year|month|day(date)", new valexpr_context(expr)); - datetime_t& moment(*((datetime_t *)result.data)); + datetime_t& moment(result.as_datetime_lval()); switch (kind) { case F_YEAR: - result = (long)moment.year(); + result = (long)moment.date().year(); break; case F_MONTH: - result = (long)moment.month(); + result = (long)moment.date().month(); break; case F_DAY: - result = (long)moment.day(); + result = (long)moment.date().day(); + break; + default: break; } break; @@ -446,11 +450,11 @@ void value_expr_t::compute(value_t& result, const details_t& details, int arg_index = 0; value_expr_t * expr = find_leaf(context, 0, arg_index); expr->compute(result, details, context); - if (result.type != value_t::AMOUNT) + if (! result.is_type(value_t::AMOUNT)) throw new compute_error("Argument to commodity() must be a commoditized amount", new valexpr_context(expr)); amount_t temp("1"); - temp.set_commodity(((amount_t *) result.data)->commodity()); + temp.set_commodity(result.as_amount_lval().commodity()); result = temp; break; } @@ -464,12 +468,12 @@ void value_expr_t::compute(value_t& result, const details_t& details, arg_index = 0; expr = find_leaf(context, 1, arg_index); expr->compute(result, details, context); - if (result.type != value_t::AMOUNT) + if (! result.is_type(value_t::AMOUNT)) throw new compute_error ("Second argument to set_commodity() must be a commoditized amount", new valexpr_context(expr)); amount_t one("1"); - one.set_commodity(((amount_t *) result.data)->commodity()); + one.set_commodity(result.as_amount_lval().commodity()); result = one; result *= temp; @@ -482,20 +486,20 @@ void value_expr_t::compute(value_t& result, const details_t& details, expr->compute(result, details, context); balance_t * bal = NULL; - switch (result.type) { + switch (result.type()) { case value_t::BALANCE_PAIR: - bal = &((balance_pair_t *) result.data)->quantity; + bal = &(result.as_balance_pair_lval().quantity()); // fall through... case value_t::BALANCE: if (! bal) - bal = (balance_t *) result.data; + bal = &(result.as_balance_lval()); if (bal->amounts.size() < 2) { result.cast(value_t::AMOUNT); } else { value_t temp; - for (amounts_map::const_iterator i = bal->amounts.begin(); + for (balance_t::amounts_map::const_iterator i = bal->amounts.begin(); i != bal->amounts.end(); i++) { amount_t x = (*i).second; @@ -503,12 +507,12 @@ void value_expr_t::compute(value_t& result, const details_t& details, temp += x; } result = temp; - assert(temp.type == value_t::AMOUNT); + assert(temp.is_type(value_t::AMOUNT)); } // fall through... case value_t::AMOUNT: - ((amount_t *) result.data)->clear_commodity(); + result.as_amount_lval().clear_commodity(); break; default: @@ -612,11 +616,11 @@ void value_expr_t::compute(value_t& result, const details_t& details, expr = find_leaf(context, 1, arg_index); value_t moment; expr->compute(moment, details, context); - if (moment.type != value_t::DATETIME) + if (! moment.is_type(value_t::DATETIME)) throw new compute_error("Invalid date passed to P(value,date)", new valexpr_context(expr)); - result = result.value(*((datetime_t *)moment.data)); + result = result.value(moment.as_datetime_lval()); break; } @@ -675,7 +679,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, case O_LTE: result = temp <= result; break; case O_GT: result = temp > result; break; case O_GTE: result = temp >= result; break; - default: assert(0); break; + default: assert(false); break; } break; } @@ -700,7 +704,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, case O_SUB: result -= temp; break; case O_MUL: result *= temp; break; case O_DIV: result /= temp; break; - default: assert(0); break; + default: assert(false); break; } break; } @@ -716,7 +720,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, case LAST: default: - assert(0); + assert(false); break; } } @@ -731,15 +735,15 @@ void value_expr_t::compute(value_t& result, const details_t& details, static inline void unexpected(char c, char wanted = '\0') { if ((unsigned char) c == 0xff) { if (wanted) - throw new value_expr_error(std::string("Missing '") + wanted + "'"); + throw new value_expr_error(string("Missing '") + wanted + "'"); else throw new value_expr_error("Unexpected end"); } else { if (wanted) - throw new value_expr_error(std::string("Invalid char '") + c + + throw new value_expr_error(string("Invalid char '") + c + "' (wanted '" + wanted + "')"); else - throw new value_expr_error(std::string("Invalid char '") + c + "'"); + throw new value_expr_error(string("Invalid char '") + c + "'"); } } @@ -887,7 +891,7 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope, // Define the value associated with the defined identifier value_expr def(parse_boolean_expr(in, params.get(), flags)); if (! def.get()) - throw new value_expr_error(std::string("Definition failed for '") + buf + "'"); + throw new value_expr_error(string("Definition failed for '") + buf + "'"); node.reset(new value_expr_t(value_expr_t::O_DEF)); node->set_left(new value_expr_t(value_expr_t::ARG_INDEX)); @@ -905,14 +909,14 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope, in.unget(); goto find_term; } - throw new value_expr_error(std::string("Unknown identifier '") + + throw new value_expr_error(string("Unknown identifier '") + buf + "'"); } else if (def->kind == value_expr_t::O_DEF) { node.reset(new value_expr_t(value_expr_t::O_REF)); node->set_left(def->right); - int count = 0; + unsigned int count = 0; if (have_args) { in.clear(); in.seekg(beg, std::ios::beg); @@ -1502,7 +1506,7 @@ value_expr_t * parse_value_expr(std::istream& in, scope_t * scope, if (! node.get()) { in.get(c); if (in.eof()) - throw new value_expr_error(std::string("Failed to parse value expression")); + throw new value_expr_error(string("Failed to parse value expression")); else unexpected(c); } else if (! (flags & PARSE_VALEXPR_PARTIAL)) { @@ -1517,8 +1521,8 @@ value_expr_t * parse_value_expr(std::istream& in, scope_t * scope, } valexpr_context::valexpr_context(const ledger::value_expr_t * _expr, - const std::string& desc) throw() - : expr(_expr), error_node(_expr), error_context(desc) + const string& desc) throw() + : error_context(desc), expr(_expr), error_node(_expr) { error_node->acquire(); } @@ -1543,12 +1547,12 @@ void valexpr_context::describe(std::ostream& out) const throw() unsigned long start = (long)out.tellp() - 1; unsigned long begin; unsigned long end; - bool found = ledger::write_value_expr(out, expr, true, + bool found = ledger::print_value_expr(out, expr, true, error_node, &begin, &end); out << std::endl; if (found) { out << " "; - for (int i = 0; i < end - start; i++) { + for (unsigned int i = 0; i < end - start; i++) { if (i >= begin - start) out << "^"; else @@ -1558,23 +1562,21 @@ void valexpr_context::describe(std::ostream& out) const throw() } } -bool write_value_expr(std::ostream& out, +bool print_value_expr(std::ostream& out, const value_expr_t * node, const bool relaxed, const value_expr_t * node_to_find, unsigned long * start_pos, unsigned long * end_pos) { - int arg_index = 0; bool found = false; - value_expr_t * expr; if (start_pos && node == node_to_find) { *start_pos = (long)out.tellp() - 1; found = true; } - std::string symbol; + string symbol; switch (node->kind) { case value_expr_t::ARG_INDEX: @@ -1582,9 +1584,9 @@ bool write_value_expr(std::ostream& out, break; case value_expr_t::CONSTANT: - switch (node->value->type) { + switch (node->value->type()) { case value_t::BOOLEAN: - assert(0); + assert(false); break; case value_t::DATETIME: out << '[' << *(node->value) << ']'; @@ -1597,9 +1599,10 @@ bool write_value_expr(std::ostream& out, if (! relaxed) out << '}'; break; - case value_t::BALANCE: - case value_t::BALANCE_PAIR: - assert(0); + //case value_t::BALANCE: + //case value_t::BALANCE_PAIR: + default: + assert(false); break; } break; @@ -1640,12 +1643,12 @@ bool write_value_expr(std::ostream& out, symbol = "now"; break; case value_expr_t::VALUE_EXPR: - if (write_value_expr(out, amount_expr.get(), relaxed, + if (print_value_expr(out, amount_expr.get(), relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::TOTAL_EXPR: - if (write_value_expr(out, total_expr.get(), relaxed, + if (print_value_expr(out, total_expr.get(), relaxed, node_to_find, start_pos, end_pos)) found = true; break; @@ -1676,37 +1679,37 @@ bool write_value_expr(std::ostream& out, symbol = "dayof"; break; case value_expr_t::F_CODE_MASK: - out << "c/" << node->mask->pattern << "/"; + out << "c/" << node->mask->expr.str() << "/"; break; case value_expr_t::F_PAYEE_MASK: - out << "p/" << node->mask->pattern << "/"; + out << "p/" << node->mask->expr.str() << "/"; break; case value_expr_t::F_NOTE_MASK: - out << "e/" << node->mask->pattern << "/"; + out << "e/" << node->mask->expr.str() << "/"; break; case value_expr_t::F_ACCOUNT_MASK: - out << "W/" << node->mask->pattern << "/"; + out << "W/" << node->mask->expr.str() << "/"; break; case value_expr_t::F_SHORT_ACCOUNT_MASK: - out << "w/" << node->mask->pattern << "/"; + out << "w/" << node->mask->expr.str() << "/"; break; case value_expr_t::F_COMMODITY_MASK: - out << "C/" << node->mask->pattern << "/"; + out << "C/" << node->mask->expr.str() << "/"; break; case value_expr_t::O_NOT: out << "!"; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::O_NEG: out << "-"; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::O_PERC: out << "%"; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; break; @@ -1715,20 +1718,20 @@ bool write_value_expr(std::ostream& out, break; case value_expr_t::O_DEF: out << "<def args=\""; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << "\" value=\""; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << "\">"; break; case value_expr_t::O_REF: - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; if (node->right) { out << "("; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; } @@ -1736,149 +1739,149 @@ bool write_value_expr(std::ostream& out, case value_expr_t::O_COM: if (node->left && - write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ", "; if (node->right && - write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::O_QUES: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " ? "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_COL: - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " : "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; break; case value_expr_t::O_AND: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " & "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_OR: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " | "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_NEQ: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " != "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_EQ: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " == "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_LT: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " < "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_LTE: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " <= "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_GT: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " > "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_GTE: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " >= "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_ADD: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " + "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_SUB: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " - "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_MUL: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " * "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::O_DIV: out << "("; - if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos)) found = true; out << " / "; - if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) + if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos)) found = true; out << ")"; break; case value_expr_t::LAST: default: - assert(0); + assert(false); break; } if (! symbol.empty()) { - if (commodity_t::find(symbol)) + if (amount_t::current_pool->find(symbol)) out << '@'; out << symbol; } @@ -1974,7 +1977,7 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node, case value_expr_t::LAST: default: - assert(0); + assert(false); break; } @@ -2,7 +2,7 @@ #define _VALEXPR_H #include "value.h" -#include "error.h" +#include "utils.h" #include "mask.h" #include <memory> @@ -22,16 +22,16 @@ struct details_t details_t() : entry(NULL), xact(NULL), account(NULL) {} details_t(const entry_t& _entry) : entry(&_entry), xact(NULL), account(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor details_t"); + DEBUG("ledger.memory.ctors", "ctor details_t"); } details_t(const transaction_t& _xact); details_t(const account_t& _account) : entry(NULL), xact(NULL), account(&_account) { - DEBUG_PRINT("ledger.memory.ctors", "ctor details_t"); + DEBUG("ledger.memory.ctors", "ctor details_t"); } #ifdef DEBUG_ENABLED ~details_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor details_t"); + DEBUG("ledger.memory.dtors", "dtor details_t"); } #endif }; @@ -134,26 +134,26 @@ struct value_expr_t value_expr_t(const kind_t _kind) : kind(_kind), refc(0), left(NULL), right(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr_t " << this); + DEBUG("ledger.memory.ctors", "ctor value_expr_t " << this); } ~value_expr_t(); void release() const { - DEBUG_PRINT("ledger.valexpr.memory", + DEBUG("ledger.valexpr.memory", "Releasing " << this << ", refc now " << refc - 1); assert(refc > 0); if (--refc == 0) delete this; } value_expr_t * acquire() { - DEBUG_PRINT("ledger.valexpr.memory", + DEBUG("ledger.valexpr.memory", "Acquiring " << this << ", refc now " << refc + 1); assert(refc >= 0); refc++; return this; } const value_expr_t * acquire() const { - DEBUG_PRINT("ledger.valexpr.memory", + DEBUG("ledger.valexpr.memory", "Acquiring " << this << ", refc now " << refc + 1); refc++; return this; @@ -186,7 +186,7 @@ struct value_expr_t private: value_expr_t(const value_expr_t&) { - DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr_t (copy) " << this); + DEBUG("ledger.memory.ctors", "ctor value_expr_t (copy) " << this); } }; @@ -196,7 +196,7 @@ class valexpr_context : public error_context { const ledger::value_expr_t * error_node; valexpr_context(const ledger::value_expr_t * _expr, - const std::string& desc = "") throw(); + const string& desc = "") throw(); virtual ~valexpr_context() throw(); virtual void describe(std::ostream& out) const throw(); @@ -204,14 +204,14 @@ class valexpr_context : public error_context { class compute_error : public error { public: - compute_error(const std::string& reason, error_context * ctxt = NULL) throw() + compute_error(const string& reason, error_context * ctxt = NULL) throw() : error(reason, ctxt) {} virtual ~compute_error() throw() {} }; class value_expr_error : public error { public: - value_expr_error(const std::string& reason, + value_expr_error(const string& reason, error_context * ctxt = NULL) throw() : error(reason, ctxt) {} virtual ~value_expr_error() throw() {} @@ -221,24 +221,24 @@ struct scope_t { scope_t * parent; - typedef std::map<const std::string, value_expr_t *> symbol_map; - typedef std::pair<const std::string, value_expr_t *> symbol_pair; + typedef std::map<const string, value_expr_t *> symbol_map; + typedef std::pair<const string, value_expr_t *> symbol_pair; symbol_map symbols; scope_t(scope_t * _parent = NULL) : parent(_parent) { - DEBUG_PRINT("ledger.memory.ctors", "ctor scope_t"); + DEBUG("ledger.memory.ctors", "ctor scope_t"); } ~scope_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor scope_t"); + DEBUG("ledger.memory.dtors", "dtor scope_t"); for (symbol_map::iterator i = symbols.begin(); i != symbols.end(); i++) (*i).second->release(); } - void define(const std::string& name, value_expr_t * def) { - DEBUG_PRINT("ledger.valexpr.syms", + void define(const string& name, value_expr_t * def) { + DEBUG("ledger.valexpr.syms", "Defining '" << name << "' = " << def); std::pair<symbol_map::iterator, bool> result = symbols.insert(symbol_pair(name, def)); @@ -248,13 +248,13 @@ struct scope_t = symbols.insert(symbol_pair(name, def)); if (! result.second) { def->release(); - throw new compute_error(std::string("Redefinition of '") + + throw new compute_error(string("Redefinition of '") + name + "' in same scope"); } } def->acquire(); } - value_expr_t * lookup(const std::string& name) { + value_expr_t * lookup(const string& name) { symbol_map::const_iterator i = symbols.find(name); if (i != symbols.end()) return (*i).second; @@ -286,9 +286,9 @@ value_expr_t * parse_value_expr(std::istream& in, const short flags = PARSE_VALEXPR_RELAXED); inline value_expr_t * -parse_value_expr(const std::string& str, - scope_t * scope = NULL, - const short flags = PARSE_VALEXPR_RELAXED) { +parse_value_expr(const string& str, + scope_t * scope = NULL, + const short flags = PARSE_VALEXPR_RELAXED) { std::istringstream stream(str); try { return parse_value_expr(stream, scope, flags); @@ -305,13 +305,13 @@ inline value_expr_t * parse_value_expr(const char * p, scope_t * scope = NULL, const short flags = PARSE_VALEXPR_RELAXED) { - return parse_value_expr(std::string(p), scope, flags); + return parse_value_expr(string(p), scope, flags); } void dump_value_expr(std::ostream& out, const value_expr_t * node, const int depth = 0); -bool write_value_expr(std::ostream& out, +bool print_value_expr(std::ostream& out, const value_expr_t * node, const bool relaxed = true, const value_expr_t * node_to_find = NULL, @@ -354,12 +354,12 @@ class value_expr { value_expr_t * ptr; public: - std::string expr; + string expr; value_expr() : ptr(NULL) {} - value_expr(const std::string& _expr) : expr(_expr) { - DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr"); + value_expr(const string& _expr) : expr(_expr) { + DEBUG("ledger.memory.ctors", "ctor value_expr"); if (! _expr.empty()) ptr = parse_value_expr(expr)->acquire(); else @@ -367,20 +367,20 @@ public: } value_expr(value_expr_t * _ptr) : ptr(_ptr ? _ptr->acquire(): NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr"); + DEBUG("ledger.memory.ctors", "ctor value_expr"); } value_expr(const value_expr& other) : ptr(other.ptr ? other.ptr->acquire() : NULL), expr(other.expr) { - DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr"); + DEBUG("ledger.memory.ctors", "ctor value_expr"); } virtual ~value_expr() { - DEBUG_PRINT("ledger.memory.dtors", "dtor value_expr"); + DEBUG("ledger.memory.dtors", "dtor value_expr"); if (ptr) ptr->release(); } - value_expr& operator=(const std::string& _expr) { + value_expr& operator=(const string& _expr) { expr = _expr; reset(parse_value_expr(expr)); return *this; @@ -399,7 +399,7 @@ public: operator bool() const throw() { return ptr != NULL; } - operator std::string() const throw() { + operator string() const throw() { return expr; } operator value_expr_t *() const throw() { @@ -439,7 +439,7 @@ public: return temp; } - friend bool write_value_expr(std::ostream& out, + friend bool print_value_expr(std::ostream& out, const value_expr_t * node, const value_expr_t * node_to_find, unsigned long * start_pos, @@ -474,7 +474,7 @@ inline value_t compute_total(const details_t& details = details_t()) { value_expr_t * parse_boolean_expr(std::istream& in, scope_t * scope, const short flags); -inline void parse_value_definition(const std::string& str, +inline void parse_value_definition(const string& str, scope_t * scope = NULL) { std::istringstream def(str); value_expr expr @@ -490,18 +490,18 @@ class item_predicate public: const value_expr_t * predicate; - item_predicate(const std::string& _predicate) : predicate(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor item_predicate<T>"); + item_predicate(const string& _predicate) : predicate(NULL) { + DEBUG("ledger.memory.ctors", "ctor item_predicate<T>"); if (! _predicate.empty()) predicate = parse_value_expr(_predicate)->acquire(); } item_predicate(const value_expr_t * _predicate = NULL) : predicate(_predicate->acquire()) { - DEBUG_PRINT("ledger.memory.ctors", "ctor item_predicate<T>"); + DEBUG("ledger.memory.ctors", "ctor item_predicate<T>"); } ~item_predicate() { - DEBUG_PRINT("ledger.memory.dtors", "dtor item_predicate<T>"); + DEBUG("ledger.memory.dtors", "dtor item_predicate<T>"); if (predicate) predicate->release(); } 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 @@ -1,1250 +1,1310 @@ +/* + * 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::storage_t> value_t::true_value; +intrusive_ptr<value_t::storage_t> value_t::false_value; + +void value_t::storage_t::destroy() { switch (type) { case AMOUNT: - ((amount_t *)data)->~amount_t(); + reinterpret_cast<amount_t *>(data)->~amount_t(); break; case BALANCE: - ((balance_t *)data)->~balance_t(); + checked_delete(*reinterpret_cast<balance_t **>(data)); break; case BALANCE_PAIR: - ((balance_pair_t *)data)->~balance_pair_t(); + checked_delete(*reinterpret_cast<balance_pair_t **>(data)); + break; + case STRING: + reinterpret_cast<string *>(data)->~string(); + break; + case SEQUENCE: + checked_delete(*reinterpret_cast<sequence_t **>(data)); + break; + case POINTER: + reinterpret_cast<boost::any *>(data)->~any(); break; + default: break; } + type = VOID; } -void value_t::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<bool *>(true_value->data) = true; + + false_value = new storage_t; + false_value->type = BOOLEAN; + *reinterpret_cast<bool *>(false_value->data) = false; + + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(bool)); + BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(datetime_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(datetime_t) + << " sizeof(datetime_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<storage_t>(); + false_value = intrusive_ptr<storage_t>(); +} + +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(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) +datetime_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); + as_amount_lval() *= val.as_long(); + return *this; + case AMOUNT: + if (as_amount().commodity() == val.as_amount().commodity() || + ! val.as_amount().has_commodity()) { + as_amount_lval() *= val.as_amount(); + return *this; + } + break; + default: break; + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() *= val.as_long(); + return *this; case AMOUNT: - *((amount_t *) data) *= *((amount_t *) value.data); + if (! val.as_amount().has_commodity()) { + as_balance_lval() *= val.as_amount(); + return *this; + } break; - case BALANCE: - cast(BALANCE); - *((balance_t *) data) *= *((balance_t *) value.data); + default: break; - case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) *= *((balance_pair_t *) value.data); + } + break; + + case BALANCE_PAIR: + switch (val.type()) { + case INTEGER: + as_balance_pair_lval() *= val.as_long(); + return *this; + case AMOUNT: + if (! val.as_amount().has_commodity()) { + as_balance_pair_lval() *= val.as_amount(); + return *this; + } break; default: - assert(0); break; } break; - case BALANCE: - switch (value.type) { + default: + break; + } + + throw_(value_error, "Cannot multiply " << label() << " with " << val.label()); + + return *this; +} + +value_t& value_t::operator/=(const value_t& val) +{ + switch (type()) { + case INTEGER: + switch (val.type()) { case INTEGER: - *((balance_t *) data) *= *((long *) value.data); + as_long_lval() /= val.as_long(); + return *this; + case AMOUNT: + set_amount(val.as_amount() / as_long()); + return *this; + default: break; + } + break; + + case AMOUNT: + switch (val.type()) { + case INTEGER: + as_amount_lval() /= val.as_long(); + return *this; + case AMOUNT: - *((balance_t *) data) *= *((amount_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; - case BALANCE: - *((balance_t *) data) *= *((balance_t *) value.data); + default: break; - case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) *= *((balance_pair_t *) value.data); + } + break; + + case BALANCE: + switch (val.type()) { + case INTEGER: + as_balance_lval() /= val.as_long(); + return *this; + case AMOUNT: + if (! val.as_amount().has_commodity()) { + as_balance_lval() /= val.as_amount(); + return *this; + } break; default: - 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 divide " << label() << " by " << val.label()); + return *this; } -value_t& value_t::operator/=(const value_t& value) -{ - 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) { +bool value_t::operator==(const value_t& val) const +{ + switch (type()) { case BOOLEAN: - throw new value_error("Cannot divide a value by a boolean"); + if (val.is_boolean()) + return as_boolean() == val.as_boolean(); + break; + + case DATETIME: + if (val.is_datetime()) + return as_datetime() == val.as_datetime(); + break; case INTEGER: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((long *) data) /= *((long *) value.data); - break; + return as_long() == val.as_long(); case AMOUNT: - cast(AMOUNT); - *((amount_t *) data) /= *((amount_t *) value.data); - break; + return val.as_amount() == to_amount(); case BALANCE: - cast(BALANCE); - *((balance_t *) data) /= *((balance_t *) value.data); - break; + return val.as_balance() == to_amount(); case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); - break; + return val.as_balance_pair() == to_amount(); default: - assert(0); break; } break; case AMOUNT: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((amount_t *) data) /= *((long *) value.data); - break; + return as_amount() == val.as_long(); case AMOUNT: - *((amount_t *) data) /= *((amount_t *) value.data); - break; + return as_amount() == val.as_amount(); case BALANCE: - cast(BALANCE); - *((balance_t *) data) /= *((balance_t *) value.data); - break; + return val.as_balance() == as_amount(); case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); - break; + return val.as_balance_pair() == as_amount(); default: - assert(0); break; } break; case BALANCE: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((balance_t *) data) /= *((long *) value.data); - break; + return as_balance() == val.to_amount(); case AMOUNT: - *((balance_t *) data) /= *((amount_t *) value.data); - break; + return as_balance() == val.as_amount(); case BALANCE: - *((balance_t *) data) /= *((balance_t *) value.data); - break; + return as_balance() == val.as_balance(); case BALANCE_PAIR: - cast(BALANCE_PAIR); - *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); - break; + return val.as_balance_pair() == as_balance(); default: - assert(0); break; } break; case BALANCE_PAIR: - switch (value.type) { + switch (val.type()) { case INTEGER: - *((balance_pair_t *) data) /= *((long *) value.data); - break; + return as_balance_pair() == val.to_amount(); case AMOUNT: - *((balance_pair_t *) data) /= *((amount_t *) value.data); - break; + return as_balance_pair() == val.as_amount(); case BALANCE: - *((balance_pair_t *) data) /= *((balance_t *) value.data); - break; + return as_balance_pair() == val.as_balance(); case BALANCE_PAIR: - *((balance_pair_t *) data) /= *((balance_pair_t *) value.data); - break; + return as_balance_pair() == val.as_balance_pair(); default: - assert(0); 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: - 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 compare " << label() << " to " << val.label()); -DEF_VALUE_CMP_OP(==) -DEF_VALUE_CMP_OP(<) -DEF_VALUE_CMP_OP(<=) -DEF_VALUE_CMP_OP(>) -DEF_VALUE_CMP_OP(>=) + return *this; +} -template <> -value_t::operator long() const +bool value_t::operator<(const value_t& val) const { - switch (type) { - case BOOLEAN: - throw new value_error("Cannot convert a boolean to an integer"); - case INTEGER: - return *((long *) data); + switch (type()) { 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_datetime()) + return as_datetime() < val.as_datetime(); 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); + switch (val.type()) { + case INTEGER: + return as_long() < val.as_long(); + case AMOUNT: + return val.as_amount() < as_long(); + default: + break; + } + break; + 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"); + switch (val.type()) { + case INTEGER: + return as_amount() < val.as_long(); + case AMOUNT: + return as_amount() < val.as_amount(); + default: + break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() < val.as_string(); + break; default: - assert(0); break; } - assert(0); - return 0; + + throw_(value_error, "Cannot compare " << label() << " to " << val.label()); + + return *this; } -template <> -value_t::operator double() const +#if 0 +bool value_t::operator>(const value_t& val) const { - switch (type) { - case BOOLEAN: - throw new value_error("Cannot convert a boolean to a double"); - case INTEGER: - return *((long *) data); + switch (type()) { 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); + if (val.is_datetime()) + return as_datetime() > val.as_datetime(); break; - } - assert(0); - return 0; -} -void value_t::cast(type_t cast_type) -{ - switch (type) { - case BOOLEAN: - switch (cast_type) { - case BOOLEAN: - break; + case INTEGER: + 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"); - case BALANCE: - throw new value_error("Cannot convert a boolean to a balance"); - case BALANCE_PAIR: - throw new value_error("Cannot convert a boolean to a balance pair"); - + return val.as_amount() > as_long(); 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; - case BALANCE: - new((balance_t *)data) balance_t(amount_t(*((long *) data))); - break; - case BALANCE_PAIR: - new((balance_pair_t *)data) balance_pair_t(amount_t(*((long *) data))); + return as_amount() > val.as_amount(); + default: break; + } + break; + + case STRING: + if (val.is_string()) + return as_string() > val.as_string(); + break; + + default: + break; + } + + throw_(value_error, + "Cannot compare " << label() << " to " << val.label()); + + return *this; +} +#endif + +void value_t::in_place_cast(type_t cast_type) +{ + if (type() == cast_type) + return; + + if (cast_type == BOOLEAN) { + set_boolean(bool(*this)); + return; + } + else if (cast_type == SEQUENCE) { + sequence_t temp; + if (! is_null()) + temp.push_back(*this); + set_sequence(temp); + return; + } + switch (type()) { + case BOOLEAN: + switch (cast_type) { + case STRING: + set_string(as_boolean() ? "true" : "false"); + return; default: - assert(0); break; } break; - case DATETIME: + case INTEGER: switch (cast_type) { - case BOOLEAN: - *((bool *) data) = *((datetime_t *) data); - break; - case INTEGER: - *((long *) data) = *((datetime_t *) data); - break; - case DATETIME: - break; case AMOUNT: - throw new value_error("Cannot convert a date/time to an amount"); + set_amount(as_long()); + return; case BALANCE: - throw new value_error("Cannot convert a date/time to a balance"); + set_balance(to_amount()); + return; case BALANCE_PAIR: - throw new value_error("Cannot convert a date/time to a balance pair"); - + set_balance_pair(to_amount()); + return; + case STRING: + set_string(lexical_cast<string>(as_long())); + return; default: - assert(0); break; } break; case AMOUNT: switch (cast_type) { - case BOOLEAN: { - bool temp = *((amount_t *) data); - destroy(); - *((bool *)data) = temp; - break; - } - case INTEGER: { - long temp = *((amount_t *) data); - destroy(); - *((long *)data) = temp; - break; - } - case DATETIME: - throw new value_error("Cannot convert an amount to a date/time"); - case AMOUNT: - break; - case BALANCE: { - amount_t temp = *((amount_t *) data); - destroy(); - new((balance_t *)data) balance_t(temp); - break; - } - case BALANCE_PAIR: { - amount_t temp = *((amount_t *) data); - destroy(); - new((balance_pair_t *)data) balance_pair_t(temp); - break; - } - + case 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: - assert(0); break; } break; case BALANCE: switch (cast_type) { - case BOOLEAN: { - bool temp = *((balance_t *) data); - destroy(); - *((bool *)data) = temp; - 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"); - 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<long>(as_string())); + return; + } else { + throw_(value_error, + "Cannot convert string '" << *this << "' to an integer"); + } + break; + } + case AMOUNT: + set_amount(amount_t(as_string())); + return; default: - 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: - cast(INTEGER); - negate(); - break; + 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(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<datetime_t>& 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<amount_t> val = as_amount().value(moment)) + return *val; + return false; + } + case BALANCE: { + if (optional<balance_t> bal = as_balance().value(moment)) + return *bal; + return false; + } + case BALANCE_PAIR: { + if (optional<balance_t> bal_pair = + as_balance_pair().quantity().value(moment)) + return *bal_pair; + return false; } + + default: + break; + } + + throw_(value_error, "Cannot find the value of " << label()); + return 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::abs() const { - switch (type) { - case BOOLEAN: - throw new value_error("Cannot round a boolean"); - case DATETIME: - throw new value_error("Cannot round a date/time"); - case INTEGER: - break; + switch (type()) { + case INTEGER: { + long val = const_cast<value_t&>(*this).as_long_lval(); + if (val < 0) + return - val; + return val; + } case AMOUNT: - *((amount_t *) data) = ((amount_t *) data)->round(); - break; + return const_cast<value_t&>(*this).as_amount_lval().abs(); case BALANCE: - ((balance_t *) data)->round(); - break; + return const_cast<value_t&>(*this).as_balance_lval().abs(); case BALANCE_PAIR: - ((balance_pair_t *) data)->round(); + return const_cast<value_t&>(*this).as_balance_pair_lval().abs(); + default: break; } + + throw_(value_error, "Cannot abs " << label()); + return value_t(); } -value_t value_t::unround() const +value_t value_t::round() 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().round(); + default: break; } - return temp; + + throw_(value_error, "Cannot round " << label()); + return value_t(); } -value_t value_t::price() const +value_t value_t::unround() const { - switch (type) { - case BOOLEAN: - throw new value_error("Cannot find the price of a boolean"); + switch (type()) { case INTEGER: return *this; - case DATETIME: - throw new value_error("Cannot find the price of a date/time"); - case AMOUNT: - return ((amount_t *) data)->price(); + return as_amount().unround(); + default: + break; + } - case BALANCE: - return ((balance_t *) data)->price(); + throw_(value_error, "Cannot unround " << label()); + return value_t(); +} - case BALANCE_PAIR: - return ((balance_pair_t *) data)->quantity.price(); +value_t value_t::annotated_price() const +{ + switch (type()) { + case AMOUNT: { + optional<amount_t> temp = as_amount().annotation_details().price; + if (! temp) + return false; + return *temp; + } default: - assert(0); break; } - assert(0); + + throw_(value_error, "Cannot find the annotated price of " << label()); return value_t(); } -value_t value_t::date() const +value_t value_t::annotated_date() 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 AMOUNT: { + optional<datetime_t> temp = as_amount().annotation_details().date; + if (! temp) + return false; + return *temp; + } - case BALANCE: - return datetime_t(((balance_t *) data)->date()); + default: + break; + } - case BALANCE_PAIR: - return datetime_t(((balance_pair_t *) data)->quantity.date()); + throw_(value_error, "Cannot find the annotated date of " << label()); + return value_t(); +} + +value_t value_t::annotated_tag() const +{ + switch (type()) { + case DATETIME: + return *this; + + case AMOUNT: { + optional<string> 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(); } @@ -1252,109 +1312,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<amount_t>& 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() +void value_t::print(std::ostream& out, const int first_width, + const int latter_width) const { - delete bal; + switch (type()) { + case VOID: + out << "NULL"; + break; + + 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; + } + + case BALANCE: + as_balance().print(out, first_width, latter_width); + break; + case BALANCE_PAIR: + as_balance_pair().print(out, first_width, latter_width); + break; + default: + assert(false); + break; + } } void value_context::describe(std::ostream& out) const throw() @@ -1362,33 +1460,33 @@ void value_context::describe(std::ostream& out) const throw() if (! desc.empty()) out << desc << std::endl; - ledger::balance_t * ptr = NULL; + balance_t * ptr = NULL; out << std::right; out.width(20); - switch (bal->type) { - case ledger::value_t::BOOLEAN: - out << (*((bool *) bal->data) ? "true" : "false"); + switch (bal.type()) { + case value_t::BOOLEAN: + out << (const_cast<value_t&>(bal).as_boolean_lval() ? "true" : "false"); break; - case ledger::value_t::INTEGER: - out << *((long *) bal->data); + case value_t::INTEGER: + out << const_cast<value_t&>(bal).as_long_lval(); break; - case ledger::value_t::DATETIME: - out << *((datetime_t *) bal->data); + case value_t::DATETIME: + out << const_cast<value_t&>(bal).as_datetime_lval(); break; - case ledger::value_t::AMOUNT: - out << *((ledger::amount_t *) bal->data); + case value_t::AMOUNT: + out << const_cast<value_t&>(bal).as_amount_lval(); break; - case ledger::value_t::BALANCE: - ptr = (ledger::balance_t *) bal->data; + case value_t::BALANCE: + ptr = &(const_cast<value_t&>(bal).as_balance_lval()); // fall through... - case ledger::value_t::BALANCE_PAIR: + case value_t::BALANCE_PAIR: if (! ptr) - ptr = &((ledger::balance_pair_t *) bal->data)->quantity; + ptr = &(const_cast<value_t&>(bal).as_balance_pair_lval().quantity()); - ptr->write(out, 20); + ptr->print(out, 20); break; default: assert(0); @@ -1398,299 +1496,3 @@ void value_context::describe(std::ostream& out) const throw() } } // namespace ledger - -#ifdef USE_BOOST_PYTHON - -#include <boost/python.hpp> - -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); - 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); - 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<value_t>()) - .def(init<balance_pair_t>()) - .def(init<balance_t>()) - .def(init<amount_t>()) - .def(init<std::string>()) - .def(init<double>()) - .def(init<long>()) - .def(init<datetime_t>()) - - .def(self + self) - .def(self + other<balance_pair_t>()) - .def(self + other<balance_t>()) - .def(self + other<amount_t>()) - .def(self + long()) - .def(self + double()) - - .def(other<balance_pair_t>() + self) - .def(other<balance_t>() + self) - .def(other<amount_t>() + self) - .def(long() + self) - .def(double() + self) - - .def(self - self) - .def(self - other<balance_pair_t>()) - .def(self - other<balance_t>()) - .def(self - other<amount_t>()) - .def(self - long()) - .def(self - double()) - - .def(other<balance_pair_t>() - self) - .def(other<balance_t>() - self) - .def(other<amount_t>() - self) - .def(long() - self) - .def(double() - self) - - .def(self * self) - .def(self * other<balance_pair_t>()) - .def(self * other<balance_t>()) - .def(self * other<amount_t>()) - .def(self * long()) - .def(self * double()) - - .def(other<balance_pair_t>() * self) - .def(other<balance_t>() * self) - .def(other<amount_t>() * self) - .def(long() * self) - .def(double() * self) - - .def(self / self) - .def(self / other<balance_pair_t>()) - .def(self / other<balance_t>()) - .def(self / other<amount_t>()) - .def(self / long()) - .def(self / double()) - - .def(other<balance_pair_t>() / self) - .def(other<balance_t>() / self) - .def(other<amount_t>() / self) - .def(long() / self) - .def(double() / self) - - .def(- self) - - .def(self += self) - .def(self += other<balance_pair_t>()) - .def(self += other<balance_t>()) - .def(self += other<amount_t>()) - .def(self += long()) - .def(self += double()) - - .def(self -= self) - .def(self -= other<balance_pair_t>()) - .def(self -= other<balance_t>()) - .def(self -= other<amount_t>()) - .def(self -= long()) - .def(self -= double()) - - .def(self *= self) - .def(self *= other<balance_pair_t>()) - .def(self *= other<balance_t>()) - .def(self *= other<amount_t>()) - .def(self *= long()) - .def(self *= double()) - - .def(self /= self) - .def(self /= other<balance_pair_t>()) - .def(self /= other<balance_t>()) - .def(self /= other<amount_t>()) - .def(self /= long()) - .def(self /= double()) - - .def(self < self) - .def(self < other<balance_pair_t>()) - .def(self < other<balance_t>()) - .def(self < other<amount_t>()) - .def(self < long()) - .def(self < other<datetime_t>()) - .def(self < double()) - - .def(other<balance_pair_t>() < self) - .def(other<balance_t>() < self) - .def(other<amount_t>() < self) - .def(long() < self) - .def(other<datetime_t>() < self) - .def(double() < self) - - .def(self <= self) - .def(self <= other<balance_pair_t>()) - .def(self <= other<balance_t>()) - .def(self <= other<amount_t>()) - .def(self <= long()) - .def(self <= other<datetime_t>()) - .def(self <= double()) - - .def(other<balance_pair_t>() <= self) - .def(other<balance_t>() <= self) - .def(other<amount_t>() <= self) - .def(long() <= self) - .def(other<datetime_t>() <= self) - .def(double() <= self) - - .def(self > self) - .def(self > other<balance_pair_t>()) - .def(self > other<balance_t>()) - .def(self > other<amount_t>()) - .def(self > long()) - .def(self > other<datetime_t>()) - .def(self > double()) - - .def(other<balance_pair_t>() > self) - .def(other<balance_t>() > self) - .def(other<amount_t>() > self) - .def(long() > self) - .def(other<datetime_t>() > self) - .def(double() > self) - - .def(self >= self) - .def(self >= other<balance_pair_t>()) - .def(self >= other<balance_t>()) - .def(self >= other<amount_t>()) - .def(self >= long()) - .def(self >= other<datetime_t>()) - .def(self >= double()) - - .def(other<balance_pair_t>() >= self) - .def(other<balance_t>() >= self) - .def(other<amount_t>() >= self) - .def(long() >= self) - .def(other<datetime_t>() >= self) - .def(double() >= self) - - .def(self == self) - .def(self == other<balance_pair_t>()) - .def(self == other<balance_t>()) - .def(self == other<amount_t>()) - .def(self == long()) - .def(self == other<datetime_t>()) - .def(self == double()) - - .def(other<balance_pair_t>() == self) - .def(other<balance_t>() == self) - .def(other<amount_t>() == self) - .def(long() == self) - .def(other<datetime_t>() == self) - .def(double() == self) - - .def(self != self) - .def(self != other<balance_pair_t>()) - .def(self != other<balance_t>()) - .def(self != other<amount_t>()) - .def(self != long()) - .def(self != other<datetime_t>()) - .def(self != double()) - - .def(other<balance_pair_t>() != self) - .def(other<balance_t>() != self) - .def(other<amount_t>() != self) - .def(long() != self) - .def(other<datetime_t>() != 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 @@ -1,443 +1,845 @@ +/* + * 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 <exception> +#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<value_t, + equality_comparable<value_t, balance_pair_t, + equality_comparable<value_t, balance_t, + additive<value_t, balance_pair_t, + additive<value_t, balance_t, + multiplicative<value_t, balance_pair_t, + multiplicative<value_t, balance_t, + ordered_field_operators<value_t, amount_t, + ordered_field_operators<value_t, double, + ordered_field_operators<value_t, unsigned long, + ordered_field_operators<value_t, long> > > > > > > > > > > { - 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<value_t> sequence_t; + + typedef sequence_t::iterator iterator; + typedef sequence_t::const_iterator const_iterator; + typedef sequence_t::difference_type difference_type; + + /** + * type_t gives the type of the data contained or referenced by a + * value_t object. Use the type() method to get a value of type + * type_t. + */ enum type_t { - 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_t> 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<storage_t>(); + } + + /** + * Because boolean "true" and "false" are so common, a pair of + * static references are kept to prevent the creation of throwaway + * storage_t objects just to represent these two common values. + */ + static intrusive_ptr<storage_t> true_value; + static intrusive_ptr<storage_t> false_value; + +public: + // jww (2007-05-03): Make these private, and make ledger::initialize + // a member function of session_t. + static void initialize(); + static void shutdown(); + +public: + /** + * Constructors. value_t objects may be constructed from almost any + * value type that they can contain, including variations on those + * types (such as long, unsigned long, etc). The ordering of the + * methods here reflects the ordering of the constants in type_t + * above. + * + * One constructor of special note is that taking a string or + * character pointer as an argument. Because value_t("$100") is + * interpreted as a commoditized amount, the form value_t("$100", + * true) is required to represent the literal string "$100", and not + * the amount "one hundred dollars". + */ + value_t() { + TRACE_CTOR(value_t, ""); + } + value_t(const bool val) { + TRACE_CTOR(value_t, "const bool"); + set_boolean(val); + } + value_t(const long val) { + TRACE_CTOR(value_t, "const long"); + set_long(val); + } + value_t(const datetime_t val) { + TRACE_CTOR(value_t, "const datetime_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 <typename T> + 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 <typename T> + bool operator==(const T& amt) const { + return *this == value_t(amt); } - value_t(const balance_t& value) : type(INTEGER) { - *this = value; + template <typename T> + bool operator<(const T& amt) const { + return *this < value_t(amt); + } + + /** + * Binary arithmetic operators. + * + * add(amount_t, optional<amount_t>) allows for the possibility of + * adding both an amount and its cost in a single operation. + * Otherwise, there is no way to separately represent the "cost" + * part of an amount addition statement. + */ + value_t& operator+=(const value_t& val); + value_t& operator-=(const value_t& val); + value_t& operator*=(const value_t& val); + value_t& operator/=(const value_t& val); + + // jww (2008-04-24): This could be expensive; perhaps it should be + // optional<amount_t&>&? + value_t& add(const amount_t& amount, + const optional<amount_t>& cost = none); + + /** + * Unary arithmetic operators. + */ + value_t negate() const { + value_t temp = *this; + temp.in_place_negate(); + return temp; } - 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<datetime_t>& 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); + datetime_t& as_datetime_lval() { + assert(is_datetime()); + _dup(); + return *(datetime_t *) storage->data; } - value_t& operator=(const std::string& value) { - return *this = amount_t(value); + const datetime_t& as_datetime() const { + assert(is_datetime()); + return *(datetime_t *) storage->data; } - value_t& operator=(const char * value) { - return *this = amount_t(value); + void set_datetime(const datetime_t& val) { + set_type(DATETIME); + new((datetime_t *) storage->data) datetime_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 <typename T> - value_t& operator+=(const T& value) { - return *this += value_t(value); + bool is_balance_pair() const { + return is_type(BALANCE_PAIR); } - template <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - value_t operator+(const T& value) { - return *this + value_t(value); + bool is_sequence() const { + return is_type(SEQUENCE); } - template <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - 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 <typename T> - bool operator<=(const T& value) { - return *this <= value_t(value); + T *& as_pointer_lval() { + assert(is_pointer()); + _dup(); + return any_cast<T *>(*(boost::any *) storage->data); } template <typename T> - bool operator>(const T& value) { - return *this > value_t(value); + T& as_ref_lval() { + assert(is_pointer()); + _dup(); + return *any_cast<T *>(*(boost::any *) storage->data); } - template <typename T> - 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 <typename T> - bool operator==(const T& value) { - return *this == value_t(value); + T * as_pointer() const { + assert(is_pointer()); + return any_cast<T *>(*(boost::any *) storage->data); } template <typename T> - bool operator!=(const T& value) { - return ! (*this == value); + T& as_ref() const { + assert(is_pointer()); + return *any_cast<T *>(*(boost::any *) storage->data); + } + void set_any_pointer(const boost::any& val) { + set_type(POINTER); + new((boost::any *) storage->data) boost::any(val); } - template <typename T> - 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; + datetime_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; - bool realzero() const { - switch (type) { + 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()); + + 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<type_t> 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 "<invalid>"; } - 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; - 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; - } + /** + * Printing methods. + */ + void print(std::ostream& out, const int first_width, + const int latter_width = -1) const; - 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 <typename T> \ - 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 <typename T> -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; + value_t bal; public: - value_context(const value_t& _bal, - const std::string& desc = "") throw(); - virtual ~value_context() throw(); + value_context(const value_t& _bal, const string& desc = "") throw() + : error_context(desc), bal(_bal) {} + 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/' @@ -44,8 +44,9 @@ void add_transaction_to(const transaction_t& xact, value_t& value) transaction_xdata_(xact).dflags & TRANSACTION_COMPOUND) { value += transaction_xdata_(xact).value; } - else if (xact.cost || ! value.realzero()) { - value.add(xact.amount, xact.cost); + else if (xact.cost || ! value.is_realzero()) { + // jww (2008-04-24): Is this costly? + value.add(xact.amount, xact.cost ? optional<amount_t>(*xact.cost) : none); } else { value = xact.amount; @@ -211,12 +212,12 @@ void handle_value(const value_t& value, transaction_xdata_t& xdata(transaction_xdata(xact)); - if (date) + if (is_valid(date)) xdata.date = date; value_t temp(value); - switch (value.type) { + switch (value.type()) { case value_t::BOOLEAN: case value_t::DATETIME: case value_t::INTEGER: @@ -224,7 +225,7 @@ void handle_value(const value_t& value, // fall through... case value_t::AMOUNT: - xact.amount = *((amount_t *) temp.data); + xact.amount = temp.as_amount_lval(); break; case value_t::BALANCE: @@ -232,6 +233,10 @@ void handle_value(const value_t& value, xdata.value = temp; flags |= TRANSACTION_COMPOUND; break; + + default: + assert(false); // jww (2008-04-24): What to do here? + break; } if (flags) @@ -320,7 +325,10 @@ void changed_value_transactions::output_diff(const datetime_t& current) transaction_xdata(*last_xact).date = current; compute_total(cur_bal, details_t(*last_xact)); cur_bal.round(); + // jww (2008-04-24): What does this do? +#if 0 transaction_xdata(*last_xact).date = 0; +#endif if (value_t diff = cur_bal - last_balance) { entry_temps.push_back(entry_t()); @@ -370,11 +378,16 @@ void subtotal_transactions::report_subtotal(const char * spec_fmt) { std::ostringstream out_date; if (! spec_fmt) { - std::string fmt = "- "; - fmt += date_t::output_format; + string fmt = "- "; + fmt += output_time_format; // jww (2008-04-24): output_date_format? + // jww (2008-04-24): There is no date output function? +#if 0 finish.write(out_date, fmt); +#endif } else { +#if 0 finish.write(out_date, spec_fmt); +#endif } entry_temps.push_back(entry_t()); @@ -393,9 +406,9 @@ void subtotal_transactions::report_subtotal(const char * spec_fmt) void subtotal_transactions::operator()(transaction_t& xact) { - if (! start || xact.date() < start) + if (! is_valid(start) || xact.date() < start) start = xact.date(); - if (! finish || xact.date() > finish) + if (! is_valid(finish) || xact.date() > finish) finish = xact.date(); account_t * acct = xact_account(xact); @@ -434,8 +447,13 @@ void interval_transactions::report_subtotal(const datetime_t& moment) assert(last_xact); start = interval.begin; - if (moment) + if (is_valid(moment)) + // jww (2008-04-24): How to change this back into a datetime? +#if 0 finish = moment - 86400L; +#else + ; +#endif else finish = last_xact->date(); @@ -448,13 +466,13 @@ void interval_transactions::operator()(transaction_t& xact) { const datetime_t date = xact.date(); - if ((interval.begin && date < interval.begin) || - (interval.end && date >= interval.end)) + if ((is_valid(interval.begin) && date < interval.begin) || + (is_valid(interval.end) && date >= interval.end)) return; if (interval) { if (! started) { - if (! interval.begin) + if (! is_valid(interval.begin)) interval.start(date); start = interval.begin; started = true; @@ -572,7 +590,10 @@ void set_code_as_payee::operator()(transaction_t& xact) void dow_transactions::flush() { for (int i = 0; i < 7; i++) { + // jww (2008-04-24): What to use here? +#if 0 start = finish = 0; +#endif for (transactions_list::iterator d = days_of_the_week[i].begin(); d != days_of_the_week[i].end(); d++) @@ -614,19 +635,22 @@ void budget_transactions::report_budget_items(const datetime_t& moment) i != pending_xacts.end(); i++) { datetime_t& begin = (*i).first.begin; - if (! begin) { + if (! is_valid(begin)) { (*i).first.start(moment); begin = (*i).first.begin; } if (begin < moment && - (! (*i).first.end || begin < (*i).first.end)) { + (! is_valid((*i).first.end) || begin < (*i).first.end)) { transaction_t& xact = *(*i).second; - DEBUG_PRINT("ledger.walk.budget", "Reporting budget for " + DEBUG("ledger.walk.budget", "Reporting budget for " << xact_account(xact)->fullname()); - DEBUG_PRINT_TIME("ledger.walk.budget", begin); - DEBUG_PRINT_TIME("ledger.walk.budget", moment); +#if 0 + // jww (2008-04-24): Need a new debug macro here + DEBUG_TIME("ledger.walk.budget", begin); + DEBUG_TIME("ledger.walk.budget", moment); +#endif entry_temps.push_back(entry_t()); entry_t& entry = entry_temps.back(); @@ -686,11 +710,11 @@ void forecast_transactions::add_transaction(const interval_t& period, generate_transactions::add_transaction(period, xact); interval_t& i = pending_xacts.back().first; - if (! i.begin) { - i.start(datetime_t::now); + if (! is_valid(i.begin)) { + i.start(current_moment); i.begin = i.increment(i.begin); } else { - while (i.begin < datetime_t::now) + while (i.begin < current_moment) i.begin = i.increment(i.begin); } } @@ -710,7 +734,7 @@ void forecast_transactions::flush() datetime_t& begin = (*least).first.begin; - if ((*least).first.end && begin >= (*least).first.end) { + if (is_valid((*least).first.end) && begin >= (*least).first.end) { pending_xacts.erase(least); passed.remove((*least).second); continue; @@ -731,8 +755,9 @@ void forecast_transactions::flush() entry.add_transaction(&temp); datetime_t next = (*least).first.increment(begin); + // jww (2008-04-24): Does seconds() here give the total seconds? if (next < begin || // wraparound - (last && (next - last) > 365 * 5 * 24 * 3600)) + (is_valid(last) && (next - last).seconds() > 365 * 5 * 24 * 3600)) break; begin = next; @@ -811,7 +836,7 @@ void sum_accounts(account_t& account) value_t result; compute_amount(result, details_t(account)); - if (! result.realzero()) + if (! result.is_realzero()) xdata.total += result; xdata.total_count += xdata.count; } @@ -854,7 +879,7 @@ void walk_accounts(account_t& account, void walk_accounts(account_t& account, item_handler<account_t>& handler, - const std::string& sort_string) + const string& sort_string) { if (! sort_string.empty()) { value_expr sort_order; @@ -865,25 +890,26 @@ void walk_accounts(account_t& account, } } -void walk_commodities(commodities_map& commodities, +void walk_commodities(commodity_pool_t::commodities_by_ident& commodities, item_handler<transaction_t>& handler) { std::list<transaction_t> xact_temps; std::list<entry_t> entry_temps; std::list<account_t> acct_temps; - for (commodities_map::iterator i = commodities.begin(); + for (commodity_pool_t::commodities_by_ident::iterator + i = commodities.begin(); i != commodities.end(); i++) { - if ((*i).second->flags() & COMMODITY_STYLE_NOMARKET) + if ((*i)->has_flags(COMMODITY_STYLE_NOMARKET)) continue; entry_temps.push_back(entry_t()); - acct_temps.push_back(account_t(NULL, (*i).second->symbol())); + acct_temps.push_back(account_t(NULL, (*i)->symbol())); - if ((*i).second->history()) - for (history_map::iterator j = (*i).second->history()->prices.begin(); - j != (*i).second->history()->prices.end(); + if ((*i)->history()) + for (commodity_t::history_map::iterator j = (*i)->history()->prices.begin(); + j != (*i)->history()->prices.end(); j++) { entry_temps.back()._date = (*j).first; @@ -4,7 +4,6 @@ #include "journal.h" #include "balance.h" #include "valexpr.h" -#include "datetime.h" #include <iostream> #include <fstream> @@ -18,13 +17,13 @@ struct item_handler { public: item_handler() : handler(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor item_handler<T>"); + DEBUG("ledger.memory.ctors", "ctor item_handler<T>"); } item_handler(item_handler * _handler) : handler(_handler) { - DEBUG_PRINT("ledger.memory.ctors", "ctor item_handler<T>"); + DEBUG("ledger.memory.ctors", "ctor item_handler<T>"); } virtual ~item_handler() { - DEBUG_PRINT("ledger.memory.dtors", "dtor item_handler<T>"); + DEBUG("ledger.memory.dtors", "dtor item_handler<T>"); } virtual void flush() { @@ -99,11 +98,11 @@ struct transaction_xdata_t transaction_xdata_t() : index(0), dflags(0), account(NULL), ptr(NULL), component_xacts(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_xdata_t " << this); + DEBUG("ledger.memory.ctors", "ctor transaction_xdata_t " << this); } ~transaction_xdata_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor transaction_xdata_t " << this); + DEBUG("ledger.memory.dtors", "dtor transaction_xdata_t " << this); if (component_xacts) delete component_xacts; } @@ -255,7 +254,7 @@ class sort_transactions : public item_handler<transaction_t> sort_order(_sort_order->acquire()) {} sort_transactions(item_handler<transaction_t> * handler, - const std::string& _sort_order) + const string& _sort_order) : item_handler<transaction_t>(handler) { assert(! _sort_order.empty()); sort_order = parse_value_expr(_sort_order)->acquire(); @@ -289,7 +288,7 @@ class sort_entries : public item_handler<transaction_t> : sorter(handler, _sort_order) {} sort_entries(item_handler<transaction_t> * handler, - const std::string& _sort_order) + const string& _sort_order) : sorter(handler, _sort_order) {} virtual void flush() { @@ -317,7 +316,7 @@ class filter_transactions : public item_handler<transaction_t> : item_handler<transaction_t>(handler), pred(predicate) {} filter_transactions(item_handler<transaction_t> * handler, - const std::string& predicate) + const string& predicate) : item_handler<transaction_t>(handler), pred(predicate) {} virtual void operator()(transaction_t& xact) { @@ -397,7 +396,7 @@ class component_transactions : public item_handler<transaction_t> : item_handler<transaction_t>(handler), pred(predicate) {} component_transactions(item_handler<transaction_t> * handler, - const std::string& predicate) + const string& predicate) : item_handler<transaction_t>(handler), pred(predicate) {} virtual void operator()(transaction_t& xact); @@ -445,7 +444,7 @@ class changed_value_transactions : public item_handler<transaction_t> virtual void flush() { if (last_xact) { - output_diff(datetime_t::now); + output_diff(current_moment); last_xact = NULL; } item_handler<transaction_t>::flush(); @@ -470,8 +469,8 @@ class subtotal_transactions : public item_handler<transaction_t> : account(av.account), value(av.value) {} }; - typedef std::map<std::string, acct_value_t> values_map; - typedef std::pair<std::string, acct_value_t> values_pair; + typedef std::map<string, acct_value_t> values_map; + typedef std::pair<string, acct_value_t> values_pair; protected: values_map values; @@ -490,7 +489,7 @@ class subtotal_transactions : public item_handler<transaction_t> remember_components(_remember_components) {} #ifdef DEBUG_ENABLED subtotal_transactions(const subtotal_transactions&) { - assert(0); + assert(false); } #endif virtual ~subtotal_transactions() { @@ -509,7 +508,7 @@ class subtotal_transactions : public item_handler<transaction_t> class interval_expr_error : public error { public: - interval_expr_error(const std::string& reason, + interval_expr_error(const string& reason, error_context * ctxt = NULL) throw() : error(reason, ctxt) {} virtual ~interval_expr_error() throw() {} @@ -529,7 +528,7 @@ class interval_transactions : public subtotal_transactions interval(_interval), last_xact(NULL), started(false) {} interval_transactions(item_handler<transaction_t> * _handler, - const std::string& _interval, + const string& _interval, bool remember_components = false) : subtotal_transactions(_handler, remember_components), interval(_interval), last_xact(NULL), started(false) {} @@ -546,8 +545,8 @@ class interval_transactions : public subtotal_transactions class by_payee_transactions : public item_handler<transaction_t> { - typedef std::map<std::string, subtotal_transactions *> payee_subtotals_map; - typedef std::pair<std::string, subtotal_transactions *> payee_subtotals_pair; + typedef std::map<string, subtotal_transactions *> payee_subtotals_map; + typedef std::pair<string, subtotal_transactions *> payee_subtotals_pair; payee_subtotals_map payee_subtotals; bool remember_components; @@ -606,7 +605,7 @@ class dow_transactions : public subtotal_transactions virtual void flush(); virtual void operator()(transaction_t& xact) { - days_of_the_week[xact.date().wday()].push_back(&xact); + days_of_the_week[xact.date().date().day_of_week()].push_back(&xact); } }; @@ -661,7 +660,7 @@ class forecast_transactions : public generate_transactions : generate_transactions(handler), pred(predicate) {} forecast_transactions(item_handler<transaction_t> * handler, - const std::string& predicate) + const string& predicate) : generate_transactions(handler), pred(predicate) {} virtual void add_transaction(const interval_t& period, @@ -729,11 +728,11 @@ void walk_accounts(account_t& account, const value_expr_t * sort_order = NULL); void walk_accounts(account_t& account, item_handler<account_t>& handler, - const std::string& sort_string); + const string& sort_string); ////////////////////////////////////////////////////////////////////// -void walk_commodities(commodities_map& commodities, +void walk_commodities(commodity_pool_t::commodities_by_ident& commodities, item_handler<transaction_t>& handler); inline void clear_journal_xdata(journal_t * journal) { @@ -1,19 +1,6 @@ #include "xml.h" #include "journal.h" -#include "datetime.h" -#include "error.h" - -#include <iostream> -#include <sstream> -#include <cstring> - -extern "C" { -#if defined(HAVE_EXPAT) -#include <expat.h> // expat XML parser -#elif defined(HAVE_XMLPARSE) -#include <xmlparse.h> // expat XML parser -#endif -} +#include "utils.h" namespace ledger { @@ -25,13 +12,13 @@ static unsigned int count; static journal_t * curr_journal; static entry_t * curr_entry; static commodity_t * curr_comm; -static std::string comm_flags; +static string comm_flags; static transaction_t::state_t curr_state; -static std::string data; +static string data; static bool ignore; -static std::string have_error; +static string have_error; static void startElement(void *userData, const char *name, const char **attrs) { @@ -50,7 +37,7 @@ static void startElement(void *userData, const char *name, const char **attrs) curr_entry->transactions.back()->state = curr_state; } else if (std::strcmp(name, "commodity") == 0) { - if (std::string(attrs[0]) == "flags") + if (string(attrs[0]) == "flags") comm_flags = attrs[1]; } else if (std::strcmp(name, "total") == 0) { @@ -83,10 +70,10 @@ static void endElement(void *userData, const char *name) curr_entry = NULL; } else if (std::strcmp(name, "en:date") == 0) { - curr_entry->_date = data; + curr_entry->_date = parse_datetime(data); } else if (std::strcmp(name, "en:date_eff") == 0) { - curr_entry->_date_eff = data; + curr_entry->_date_eff = parse_datetime(data); } else if (std::strcmp(name, "en:code") == 0) { curr_entry->code = data; @@ -117,11 +104,11 @@ static void endElement(void *userData, const char *name) } else if (std::strcmp(name, "symbol") == 0) { assert(! curr_comm); - curr_comm = commodity_t::find_or_create(data); + curr_comm = amount_t::current_pool->find_or_create(data); assert(curr_comm); curr_comm->add_flags(COMMODITY_STYLE_SUFFIXED); if (! comm_flags.empty()) { - for (std::string::size_type i = 0, l = comm_flags.length(); i < l; i++) { + for (string::size_type i = 0, l = comm_flags.length(); i < l; i++) { switch (comm_flags[i]) { case 'P': curr_comm->drop_flags(COMMODITY_STYLE_SUFFIXED); break; case 'S': curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); break; @@ -148,8 +135,8 @@ static void endElement(void *userData, const char *name) else if (std::strcmp(name, "quantity") == 0) { curr_entry->transactions.back()->amount.parse(data); if (curr_comm) { - std::string::size_type i = data.find('.'); - if (i != std::string::npos) { + string::size_type i = data.find('.'); + if (i != string::npos) { int precision = data.length() - i - 1; if (precision > curr_comm->precision()) curr_comm->set_precision(precision); @@ -166,7 +153,7 @@ static void endElement(void *userData, const char *name) static void dataHandler(void *userData, const char *s, int len) { if (! ignore) - data = std::string(s, len); + data = string(s, len); } bool xml_parser_t::test(std::istream& in) const @@ -192,11 +179,11 @@ bool xml_parser_t::test(std::istream& in) const return true; } -unsigned int xml_parser_t::parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) +unsigned int xml_parser_t::parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master, + const path * original_file) { char buf[BUFSIZ]; @@ -206,7 +193,6 @@ unsigned int xml_parser_t::parse(std::istream& in, curr_comm = NULL; ignore = false; - unsigned int offset = 2; XML_Parser parser = XML_ParserCreate(NULL); current_parser = parser; @@ -221,20 +207,20 @@ unsigned int xml_parser_t::parse(std::istream& in, result = XML_Parse(parser, buf, std::strlen(buf), in.eof()); } catch (const std::exception& err) { - unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; XML_ParserFree(parser); throw new parse_error(err.what()); } if (! have_error.empty()) { - unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; parse_error err(have_error); std::cerr << "Error: " << err.what() << std::endl; have_error = ""; } if (! result) { - unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; const char * err = XML_ErrorString(XML_GetErrorCode(parser)); XML_ParserFree(parser); throw new parse_error(err); @@ -290,46 +276,49 @@ void xml_write_amount(std::ostream& out, const amount_t& amount, void xml_write_value(std::ostream& out, const value_t& value, const int depth = 0) { - balance_t * bal = NULL; + const balance_t * bal = NULL; for (int i = 0; i < depth; i++) out << ' '; out << "<value type=\""; - switch (value.type) { + switch (value.type()) { case value_t::BOOLEAN: out << "boolean"; break; case value_t::INTEGER: out << "integer"; break; case value_t::AMOUNT: out << "amount"; break; case value_t::BALANCE: case value_t::BALANCE_PAIR: out << "balance"; break; + default: + assert(false); + break; } out << "\">\n"; - switch (value.type) { + switch (value.type()) { case value_t::BOOLEAN: for (int i = 0; i < depth + 2; i++) out << ' '; - out << "<boolean>" << *((bool *) value.data) << "</boolean>\n"; + out << "<boolean>" << value.as_boolean() << "</boolean>\n"; break; case value_t::INTEGER: for (int i = 0; i < depth + 2; i++) out << ' '; - out << "<integer>" << *((long *) value.data) << "</integer>\n"; + out << "<integer>" << value.as_long() << "</integer>\n"; break; case value_t::AMOUNT: - xml_write_amount(out, *((amount_t *) value.data), depth + 2); + xml_write_amount(out, value.as_amount(), depth + 2); break; case value_t::BALANCE: - bal = (balance_t *) value.data; + bal = &(value.as_balance()); // fall through... case value_t::BALANCE_PAIR: if (! bal) - bal = &((balance_pair_t *) value.data)->quantity; + bal = &(value.as_balance_pair().quantity()); for (int i = 0; i < depth + 2; i++) out << ' '; out << "<balance>\n"; - for (amounts_map::const_iterator i = bal->amounts.begin(); + for (balance_t::amounts_map::const_iterator i = bal->amounts.begin(); i != bal->amounts.end(); i++) xml_write_amount(out, (*i).second, depth + 4); @@ -339,7 +328,7 @@ void xml_write_value(std::ostream& out, const value_t& value, break; default: - assert(0); + assert(false); break; } @@ -347,7 +336,7 @@ void xml_write_value(std::ostream& out, const value_t& value, out << "</value>\n"; } -void output_xml_string(std::ostream& out, const std::string& str) +void output_xml_string(std::ostream& out, const string& str) { for (const char * s = str.c_str(); *s; s++) { switch (*s) { @@ -369,14 +358,17 @@ void output_xml_string(std::ostream& out, const std::string& str) void format_xml_entries::format_last_entry() { +#if 0 + // jww (2008-05-08): Need to format these dates output_stream << " <entry>\n" << " <en:date>" << last_entry->_date.to_string("%Y/%m/%d") << "</en:date>\n"; - if (last_entry->_date_eff) + if (is_valid(last_entry->_date_eff)) output_stream << " <en:date_eff>" << last_entry->_date_eff.to_string("%Y/%m/%d") << "</en:date_eff>\n"; +#endif if (! last_entry->code.empty()) { output_stream << " <en:code>"; @@ -403,15 +395,18 @@ void format_xml_entries::format_last_entry() output_stream << " <transaction>\n"; +#if 0 + // jww (2008-05-08): Need to format these if ((*i)->_date) output_stream << " <tr:date>" << (*i)->_date.to_string("%Y/%m/%d") << "</tr:date>\n"; - if ((*i)->_date_eff) + if (is_valid((*i)->_date_eff)) output_stream << " <tr:date_eff>" << (*i)->_date_eff.to_string("%Y/%m/%d") << "</tr:date_eff>\n"; +#endif if ((*i)->state == transaction_t::CLEARED) output_stream << " <tr:cleared/>\n"; @@ -424,7 +419,7 @@ void format_xml_entries::format_last_entry() output_stream << " <tr:generated/>\n"; if ((*i)->account) { - std::string name = (*i)->account->fullname(); + string name = (*i)->account->fullname(); if (name == "<Total>") name = "[TOTAL]"; else if (name == "<Unknown>") @@ -13,11 +13,11 @@ class xml_parser_t : public parser_t public: virtual bool test(std::istream& in) const; - virtual unsigned int parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); + virtual unsigned int parse(std::istream& in, + config_t& config, + journal_t * journal, + account_t * master = NULL, + const path * original_file = NULL); }; #endif |