diff options
author | John Wiegley <johnw@newartisans.com> | 2004-08-04 03:12:26 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2004-08-04 03:12:26 -0400 |
commit | 1741c80fe4f9dd896d4c8912b2ba35b14500ca1c (patch) | |
tree | cfdcb49cc929b023b71ecd9e126eff4830d77a0c | |
parent | f570e6644fce27b612562282b11d00fd0691873d (diff) | |
download | fork-ledger-1741c80fe4f9dd896d4c8912b2ba35b14500ca1c.tar.gz fork-ledger-1741c80fe4f9dd896d4c8912b2ba35b14500ca1c.tar.bz2 fork-ledger-1741c80fe4f9dd896d4c8912b2ba35b14500ca1c.zip |
rewrote the way registers are computed -- again
-rw-r--r-- | Makefile | 46 | ||||
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | account.cc | 40 | ||||
-rw-r--r-- | amount.cc | 112 | ||||
-rw-r--r-- | amount.h | 244 | ||||
-rw-r--r-- | autoxact.cc | 28 | ||||
-rw-r--r-- | autoxact.h | 79 | ||||
-rw-r--r-- | balance.cc | 2 | ||||
-rw-r--r-- | balance.h | 7 | ||||
-rw-r--r-- | binary.cc | 6 | ||||
-rw-r--r-- | binary.h | 2 | ||||
-rw-r--r-- | datetime.cc | 125 | ||||
-rw-r--r-- | datetime.h | 19 | ||||
-rw-r--r-- | error.h | 21 | ||||
-rw-r--r-- | format.cc | 138 | ||||
-rw-r--r-- | format.h | 32 | ||||
-rw-r--r-- | item.cc | 196 | ||||
-rw-r--r-- | item.h | 82 | ||||
-rw-r--r-- | ledger.cc | 28 | ||||
-rw-r--r-- | ledger.h | 337 | ||||
-rw-r--r-- | main.cc | 136 | ||||
-rw-r--r-- | textual.cc | 252 | ||||
-rw-r--r-- | textual.h | 5 | ||||
-rw-r--r-- | valexpr.cc (renamed from expr.cc) | 199 | ||||
-rw-r--r-- | valexpr.h (renamed from expr.h) | 89 | ||||
-rw-r--r-- | walk.h | 234 |
26 files changed, 1306 insertions, 1155 deletions
@@ -1,17 +1,34 @@ -CODE = amount.cc balance.cc account.cc ledger.cc \ - item.cc expr.cc format.cc textual.cc binary.cc +CODE = account.cc \ + amount.cc \ + autoxact.cc \ + balance.cc \ + binary.cc \ + datetime.cc \ + error.cc \ + format.cc \ + ledger.cc \ + textual.cc \ + valexpr.cc \ + walk.cc + OBJS = $(patsubst %.cc,%.o,$(CODE)) + #CXX = cc CXX = g++ + CFLAGS = -Wall -ansi -pedantic #DFLAGS = -O3 -fomit-frame-pointer DFLAGS = -g -DDEBUG=1 #DFLAGS = -g -pg -INCS = -I/sw/include -I/usr/include/gcc/darwin/3.3/c++ -I/usr/include/gcc/darwin/3.3/c++/ppc-darwin -LIBS = -L/sw/lib -lgmpxx -lgmp -lpcre + +INCS = -I/sw/include \ + -I/usr/include/gcc/darwin/3.3/c++ \ + -I/usr/include/gcc/darwin/3.3/c++/ppc-darwin +LIBS = -L/sw/lib -lgmpxx -lgmp -lpcre ifdef GNUCASH CODE := $(CODE) gnucash.cc +OBJS := $(OBJS) gnucash.o CFLAGS := $(CFLAGS) -DREAD_GNUCASH=1 INCS := $(INCS) -I/usr/include/httpd/xml LIBS := $(LIBS) -L/sw/lib -lxmlparse @@ -34,8 +51,8 @@ libledger.a: $(OBJS) ledger: libledger.a main.o $(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -o $@ main.o -L. -lledger $(LIBS) -report: libledger.a report.cc - $(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -DTEST -o $@ report.cc \ +valexpr: libledger.a valexpr.cc + $(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -DTEST -o $@ valexpr.cc \ -L. -lledger $(LIBS) ledger.info: ledger.texi @@ -48,9 +65,9 @@ ledger.pdf: ledger.texi $(CXX) $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $< clean: - rm -f ledger report libledger.a *.o *.elc *~ .\#* + rm -f ledger valexpr libledger.a *.o *.elc *~ .\#* rm -f *.aux *.cp *.fn *.ky *.log *.pg *.toc *.tp *.vr - rm -f .gdb_history + rm -f .gdb_history gmon.out out distclean fullclean: clean rm -f *.texi *.info *.html *.pdf *.elc make.deps TAGS @@ -60,7 +77,7 @@ rebuild: clean deps all deps: make.deps make.deps: Makefile - cc -M $(INCS) $(CODE) > $@ + cc -M $(INCS) $(CODE) main.cc > $@ include make.deps @@ -95,11 +112,6 @@ dist: mv t Makefile && \ perl -ne 'print if 1 .. /^include make.deps/;' Makefile > t && \ mv t Makefile && \ - cd $(HOME)/Public && \ - tar cvzf ledger-$(VERSION).tar.gz /tmp/ledger-$(VERSION)) - -publish: dist - (cd $(HOME)/Public && \ - ln -sf ledger-$(VERSION).tar.gz ledger.tar.gz && \ - rm -fr /tmp/ledger-$(VERSION) && \ - upload) + cd /tmp && \ + tar cvzf $(HOME)/Sites/ledger/ledger-$(VERSION).tar.gz \ + ledger-$(VERSION)) @@ -201,7 +201,7 @@ amount, if it is the same as the first line: For this entry, Ledger will figure out that $-23.00 must come from "Assets:Checking" in order to balance the entry. -** Transactions: Additions and Subtractions +** Stating where money goes Accountants will talk of `credits' and `debits', but their meaning is often different from the layman's definitions. To avoid this semantic @@ -15,30 +15,26 @@ account_t::~account_t() delete (*i).second; } -account_t * account_t::find_account(const std::string& ident, +account_t * account_t::find_account(const std::string& name, const bool auto_create) { - accounts_map::const_iterator c = accounts_cache.find(ident); - if (c != accounts_cache.end()) - return (*c).second; - - accounts_map::const_iterator i = accounts.find(ident); + accounts_map::const_iterator i = accounts.find(name); if (i != accounts.end()) return (*i).second; static char buf[256]; - std::string::size_type sep = ident.find(':'); + std::string::size_type sep = name.find(':'); const char * first, * rest; if (sep == std::string::npos) { - first = ident.c_str(); + first = name.c_str(); rest = NULL; } else { - std::strncpy(buf, ident.c_str(), sep); + std::strncpy(buf, name.c_str(), sep); buf[sep] = '\0'; first = buf; - rest = ident.c_str() + sep + 1; + rest = name.c_str() + sep + 1; } account_t * account; @@ -56,8 +52,6 @@ account_t * account_t::find_account(const std::string& ident, if (rest) account = account->find_account(rest, auto_create); - accounts_cache.insert(accounts_pair(ident, account)); - return account; } @@ -76,16 +70,22 @@ bool account_t::remove_transaction(transaction_t * xact) std::string account_t::fullname() const { - const account_t * first = this; - std::string fullname = name; + if (! _fullname.empty()) { + return _fullname; + } else { + const account_t * first = this; + std::string fullname = name; - while (first->parent) { - first = first->parent; - if (! first->name.empty()) - fullname = first->name + ":" + fullname; - } + while (first->parent) { + first = first->parent; + if (! first->name.empty()) + fullname = first->name + ":" + fullname; + } + + _fullname = fullname; - return fullname; + return fullname; + } } } // namespace ledger @@ -10,6 +10,24 @@ namespace ledger { +static mpz_t full_divisor; +static mpz_t true_value; + +static class init_amounts +{ + public: + init_amounts() { + mpz_init(full_divisor); + mpz_init(true_value); + mpz_ui_pow_ui(full_divisor, 10, MAX_PRECISION); + mpz_mul_ui(true_value, full_divisor, 1); + } + ~init_amounts() { + mpz_clear(full_divisor); + mpz_clear(true_value); + } +} initializer; + static void mpz_round(mpz_t out, mpz_t value, int precision) { mpz_t divisor; @@ -52,7 +70,49 @@ static void mpz_round(mpz_t out, mpz_t value, int precision) mpz_clear(remainder); } -// destructor +amount_t::amount_t(const bool value) + : quantity(NULL), commodity(NULL) +{ + if (value) { + commodity = commodity_t::null_commodity; + quantity = new MP_INT; + mpz_init_set(MPZ(quantity), true_value); + } +} + +amount_t::amount_t(const int value) + : quantity(NULL), commodity(NULL) +{ + if (value != 0) { + _init(); + commodity = commodity_t::null_commodity; + mpz_set_si(MPZ(quantity), value); + mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor); + } +} + +amount_t::amount_t(const unsigned int value) + : quantity(NULL), commodity(NULL) +{ + if (value != 0) { + _init(); + commodity = commodity_t::null_commodity; + mpz_set_ui(MPZ(quantity), value); + mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor); + } +} + +amount_t::amount_t(const double value) + : quantity(NULL), commodity(NULL) +{ + if (value != 0.0) { + _init(); + commodity = commodity_t::null_commodity; + mpz_set_d(MPZ(quantity), value); + mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor); + } +} + void amount_t::_clear() { mpz_clear(MPZ(quantity)); @@ -92,6 +152,22 @@ amount_t& amount_t::operator=(const amount_t& amt) return *this; } +amount_t& amount_t::operator=(const bool value) +{ + if (! value) { + if (quantity) { + _clear(); + quantity = NULL; + commodity = NULL; + } + } else { + commodity = commodity_t::null_commodity; + quantity = new MP_INT; + mpz_init_set(MPZ(quantity), true_value); + } + return *this; +} + amount_t& amount_t::operator=(const int value) { if (value == 0) { @@ -101,10 +177,9 @@ amount_t& amount_t::operator=(const int value) commodity = NULL; } } else { - std::string str; - std::ostringstream strstr(str); - strstr << value; - parse(strstr.str()); + commodity = commodity_t::null_commodity; + mpz_set_si(MPZ(quantity), value); + mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor); } return *this; } @@ -118,10 +193,9 @@ amount_t& amount_t::operator=(const unsigned int value) commodity = NULL; } } else { - std::string str; - std::ostringstream strstr(str); - strstr << value; - parse(strstr.str()); + commodity = commodity_t::null_commodity; + mpz_set_ui(MPZ(quantity), value); + mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor); } return *this; } @@ -135,10 +209,9 @@ amount_t& amount_t::operator=(const double value) commodity = NULL; } } else { - std::string str; - std::ostringstream strstr(str); - strstr << value; - parse(strstr.str()); + commodity = commodity_t::null_commodity; + mpz_set_d(MPZ(quantity), value); + mpz_mul(MPZ(quantity), MPZ(quantity), full_divisor); } return *this; } @@ -279,7 +352,16 @@ amount_t::operator bool() const { if (quantity) { assert(commodity); - return mpz_sgn(MPZ(round().quantity)) != 0; + mpz_t temp; + mpz_t divisor; + mpz_init_set(temp, MPZ(quantity)); + mpz_init(divisor); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - commodity->precision); + mpz_tdiv_q(temp, temp, divisor); + bool zero = mpz_sgn(temp) == 0; + mpz_clear(divisor); + mpz_clear(temp); + return ! zero; } else { return false; } @@ -682,6 +764,8 @@ void (*commodity_t::updater)(commodity_t * commodity, const std::time_t moment) = NULL; commodities_map commodity_t::commodities; +commodity_t * commodity_t::null_commodity = + commodity_t::find_commodity("", true); struct cleanup_commodities { diff --git a/amount.h b/amount.h new file mode 100644 index 00000000..bfcea2c2 --- /dev/null +++ b/amount.h @@ -0,0 +1,244 @@ +#ifndef _AMOUNT_H +#define _AMOUNT_H + +#include <map> +#include <string> +#include <ctime> +#include <iostream> +#include <sstream> + +namespace ledger { + +class commodity_t; + +class amount_t +{ + typedef void * base_type; + + void _init(); + void _copy(const amount_t& amt); + void _clear(); + + public: + base_type quantity; // amount, to MAX_PRECISION + commodity_t * commodity; + + bool valid() const { + if (quantity) + return commodity != NULL; + else + return commodity == NULL; + } + + // constructors + amount_t(commodity_t * _commodity = NULL) + : quantity(NULL), commodity(_commodity) {} + + amount_t(const amount_t& amt) : quantity(NULL) { + if (amt.quantity) + _copy(amt); + else + commodity = amt.commodity; + } + amount_t(const std::string& value) { + _init(); + std::istringstream str(value); + str >> *this; + } + amount_t(const bool value); + amount_t(const int value); + amount_t(const unsigned int value); + amount_t(const double value); + + // destructor + ~amount_t() { + if (quantity) + _clear(); + } + + // assignment operator + amount_t& operator=(const amount_t& amt); + amount_t& operator=(const std::string& value); + amount_t& operator=(const bool value); + amount_t& operator=(const int value); + amount_t& operator=(const unsigned int value); + amount_t& operator=(const double value); + + // general methods + amount_t round(int precision = -1) const; + + // in-place arithmetic + amount_t& operator*=(const amount_t& amt); + amount_t& operator/=(const amount_t& amt); + amount_t& operator%=(const amount_t& amt); + amount_t& operator+=(const amount_t& amt); + amount_t& operator-=(const amount_t& amt); + + // simple arithmetic + amount_t operator*(const amount_t& amt) const { + amount_t temp = *this; + temp *= amt; + return temp; + } + amount_t operator/(const amount_t& amt) const { + amount_t temp = *this; + temp /= amt; + return temp; + } + amount_t operator%(const amount_t& amt) const { + amount_t temp = *this; + temp %= amt; + return temp; + } + amount_t operator+(const amount_t& amt) const { + amount_t temp = *this; + temp += amt; + return temp; + } + amount_t operator-(const amount_t& amt) const { + amount_t temp = *this; + temp -= amt; + return temp; + } + + // unary negation + amount_t& negate(); + amount_t negated() const { + amount_t temp = *this; + temp.negate(); + return temp; + } + amount_t operator-() const { + return negated(); + } + + // test for non-zero (use ! for zero) + operator bool() const; + + // comparisons to zero + bool operator<(const int num) const; + bool operator<=(const int num) const; + bool operator>(const int num) const; + bool operator>=(const int num) const; + + // comparisons between amounts + bool operator<(const amount_t& amt) const; + bool operator<=(const amount_t& amt) const; + bool operator>(const amount_t& amt) const; + bool operator>=(const amount_t& amt) const; + bool operator==(const amount_t& amt) const; + bool operator!=(const amount_t& amt) const { + if (commodity != amt.commodity) + return true; + return ! (*this == amt); + } + + amount_t value(const std::time_t moment) const; + + operator std::string() const; + + void parse(std::istream& in); + void parse(const std::string& str) { + std::istringstream stream(str); + parse(stream); + } + + void write_quantity(std::ostream& out) const; + void read_quantity(std::istream& in); + + friend std::istream& operator>>(std::istream& in, amount_t& amt); +}; + +void parse_quantity(std::istream& in, std::string& value); +void parse_commodity(std::istream& in, std::string& symbol); + +inline amount_t abs(const amount_t& amt) { + return amt < 0 ? amt.negated() : amt; +} + +inline std::istream& operator>>(std::istream& in, amount_t& amt) { + amt.parse(in); + return in; +} + +std::ostream& operator<<(std::ostream& out, const amount_t& amt); + + +#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_CONSULTED 0x10 +#define COMMODITY_STYLE_NOMARKET 0x20 + +typedef std::map<const std::time_t, amount_t> history_map; +typedef std::pair<const std::time_t, amount_t> history_pair; + +typedef std::map<const std::string, commodity_t *> commodities_map; +typedef std::pair<const std::string, commodity_t *> commodities_pair; + +class commodity_t +{ + public: + std::string symbol; + std::string name; + std::string note; + unsigned int precision; + unsigned int flags; + history_map history; + amount_t conversion; + unsigned long ident; + + // If set, this global function pointer is called to determine + // whether prices have been updated in the meanwhile. + + static void (*updater)(commodity_t * commodity, + const std::time_t date, + const amount_t& price, + const std::time_t moment); + + // This map remembers all commodities that have been + // defined thus far. + + static commodities_map commodities; + static commodity_t * null_commodity; + + static void add_commodity(commodity_t * commodity, + const std::string symbol = "") { + commodities.insert(commodities_pair((symbol.empty() ? + commodity->symbol : symbol), + commodity)); + } + static bool remove_commodity(commodity_t * commodity) { + commodities_map::size_type n = commodities.erase(commodity->symbol); + return n > 0; + } + static commodity_t * find_commodity(const std::string& symbol, + bool auto_create = false); + + // Now the per-object constructor and methods + + commodity_t(const std::string& _symbol = "", + unsigned int _precision = 2, + unsigned int _flags = COMMODITY_STYLE_DEFAULTS) + : symbol(_symbol), precision(_precision), flags(_flags) {} + + void add_price(const std::time_t date, const amount_t& price) { + history.insert(history_pair(date, price)); + } + bool remove_price(const std::time_t date) { + history_map::size_type n = history.erase(date); + return n > 0; + } + + void set_conversion(const amount_t& price) { + conversion = price; + } + + amount_t value(const std::time_t moment = std::time(NULL)); +}; + +} // namespace ledger + +#endif // _AMOUNT_H diff --git a/autoxact.cc b/autoxact.cc new file mode 100644 index 00000000..c49d1d38 --- /dev/null +++ b/autoxact.cc @@ -0,0 +1,28 @@ +#include "autoxact.h" + +namespace ledger { + +void automated_transaction_t::extend_entry(entry_t * entry) +{ + for (transactions_list::iterator i = entry->transactions.begin(); + i != entry->transactions.end(); + i++) + if (matches(masks, *((*i)->account))) { + for (transactions_list::iterator t = transactions.begin(); + t != transactions.end(); + t++) { + amount_t amt; + if ((*t)->amount.commodity->symbol.empty()) + amt = (*i)->amount * (*t)->amount; + else + amt = (*t)->amount; + + transaction_t * xact + = new transaction_t(entry, (*t)->account, amt, amt, + (*t)->flags | TRANSACTION_AUTO); + entry->add_transaction(xact); + } + } +} + +} // namespace ledger diff --git a/autoxact.h b/autoxact.h new file mode 100644 index 00000000..717ebe96 --- /dev/null +++ b/autoxact.h @@ -0,0 +1,79 @@ +#ifndef _AUTOXACT_H +#define _AUTOXACT_H + +#include "ledger.h" +#include "valexpr.h" + +#include <deque> + +namespace ledger { + +class automated_transaction_t +{ +public: + masks_list masks; + transactions_list transactions; + + automated_transaction_t(masks_list& _masks, + transactions_list& _transactions) { + masks.insert(masks.begin(), _masks.begin(), _masks.end()); + transactions.insert(transactions.begin(), + _transactions.begin(), _transactions.end()); + // Take over ownership of the pointers + _transactions.clear(); + } + + ~automated_transaction_t() { + for (transactions_list::iterator i = transactions.begin(); + i != transactions.end(); + i++) + delete *i; + } + + void extend_entry(entry_t * entry); +}; + + +typedef std::deque<automated_transaction_t *> automated_transactions_deque; + +class automated_transactions_t +{ +public: + automated_transactions_deque automated_transactions; + + ~automated_transactions_t() { + for (automated_transactions_deque::iterator i + = automated_transactions.begin(); + i != automated_transactions.end(); + i++) + delete *i; + } + + void extend_entry(entry_t * entry) { + for (automated_transactions_deque::iterator i + = automated_transactions.begin(); + i != automated_transactions.end(); + i++) + (*i)->extend_entry(entry); + } + + void add_automated_transaction(automated_transaction_t * auto_xact) { + automated_transactions.push_back(auto_xact); + } + bool remove_automated_transaction(automated_transaction_t * auto_xact) { + for (automated_transactions_deque::iterator i + = automated_transactions.begin(); + i != automated_transactions.end(); + i++) { + if (*i == auto_xact) { + automated_transactions.erase(i); + return true; + } + } + return false; + } +}; + +} // namespace ledger + +#endif // _AUTOXACT_H @@ -1,5 +1,4 @@ #include "ledger.h" -#include "balance.h" namespace ledger { @@ -86,7 +85,6 @@ void balance_t::write(std::ostream& out, } } - balance_pair_t::balance_pair_t(const transaction_t& xact) : quantity(xact.amount), cost(xact.cost) {} @@ -1,7 +1,11 @@ #ifndef _BALANCE_H #define _BALANCE_H -#include "ledger.h" +#include <map> +#include <ctime> +#include <iostream> + +#include "amount.h" namespace ledger { @@ -349,6 +353,7 @@ inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) { } #endif +class transaction_t; class balance_pair_t { @@ -14,7 +14,7 @@ namespace ledger { - unsigned long magic_number = 0xFFEED765; + unsigned long binary_magic_number = 0xFFEED765; static unsigned long format_version = 0x00020009; static char buf[4096]; @@ -292,7 +292,7 @@ unsigned int read_binary_ledger(std::istream& in, unsigned long magic; in.read((char *)&magic, sizeof(magic)); - if (magic != magic_number) + if (magic != binary_magic_number) return 0; #ifdef DEBUG @@ -560,7 +560,7 @@ void write_binary_account(std::ostream& out, account_t * account) void write_binary_ledger(std::ostream& out, ledger_t * ledger, const std::string& leader) { - out.write((char *)&magic_number, sizeof(magic_number)); + out.write((char *)&binary_magic_number, sizeof(binary_magic_number)); #ifdef DEBUG { @@ -5,7 +5,7 @@ namespace ledger { -extern unsigned long magic_number; +extern unsigned long binary_magic_number; extern unsigned int read_binary_ledger(std::istream& in, const std::string& leader, diff --git a/datetime.cc b/datetime.cc new file mode 100644 index 00000000..19f54f92 --- /dev/null +++ b/datetime.cc @@ -0,0 +1,125 @@ +#include "datetime.h" + +#include <ctime> + +namespace ledger { + +static std::time_t now = std::time(NULL); + struct std::tm * now_tm = std::localtime(&now); + +static std::time_t base = -1; +static int base_year = -1; + +static const int month_days[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +static const char * formats[] = { + "%Y/%m/%d", + "%m/%d", + "%Y.%m.%d", + "%m.%d", + "%Y-%m-%d", + "%m-%d", + "%a", + "%A", + "%b", + "%B", + "%Y", + NULL +}; + +bool parse_date_mask(const char * date_str, struct std::tm * result) +{ + for (const char ** f = formats; *f; f++) { + memset(result, INT_MAX, sizeof(struct std::tm)); + if (strptime(date_str, *f, result)) + return true; + } + return false; +} + +bool parse_date(const char * date_str, std::time_t * result, const int year) +{ + struct std::tm when; + + if (! parse_date_mask(date_str, &when)) + return false; + + when.tm_hour = 0; + when.tm_min = 0; + when.tm_sec = 0; + + if (when.tm_year == -1) + when.tm_year = ((year == -1) ? now_tm->tm_year : (year - 1900)); + + if (when.tm_mon == -1) + when.tm_mon = 0; + + if (when.tm_mday == -1) + when.tm_mday = 1; + + *result = std::mktime(&when); + + return true; +} + +bool quick_parse_date(char * date_str, std::time_t * result) +{ + int year = -1, month = -1, day, num = 0; + + for (char * p = date_str; *p; p++) { + if (*p == '/' || *p == '-' || *p == '.') { + if (year == -1) + year = num; + else + month = num; + num = 0; + } + else if (*p < '0' || *p > '9') { + return false; + } + else { + num *= 10; + num += *p - '0'; + } + } + + day = num; + + if (month == -1) { + month = year; + year = -1; + } + + if (base == -1 || year != base_year) { + struct std::tm when; + + when.tm_hour = 0; + when.tm_min = 0; + when.tm_sec = 0; + + base_year = year == -1 ? now_tm->tm_year + 1900 : year; + when.tm_year = year == -1 ? now_tm->tm_year : year - 1900; + when.tm_mon = 0; + when.tm_mday = 1; + + base = std::mktime(&when); + } + + *result = base; + + --month; + while (--month >= 0) { + *result += month_days[month] * 24 * 60 * 60; + if (month == 1 && year % 4 == 0 && year != 2000) // february in leap years + *result += 24 * 60 * 60; + } + + if (--day) + *result += day * 24 * 60 * 60; + + return true; +} + +} // namespace ledger diff --git a/datetime.h b/datetime.h new file mode 100644 index 00000000..6c9d001a --- /dev/null +++ b/datetime.h @@ -0,0 +1,19 @@ +#ifndef _DATETIME_H +#define _DATETIME_H + +#include "ledger.h" + +namespace ledger { + +extern bool parse_date_mask(const char * date_str, struct std::tm * result); + +extern bool parse_date(const char * date_str, std::time_t * result, + const int year = -1); + +extern bool quick_parse_date(char * date_str, std::time_t * result); + +extern struct std::tm * now_tm; + +} // namespace ledger + +#endif // _DATETIME_H @@ -1,8 +1,19 @@ #ifndef _ERROR_H #define _ERROR_H +#include "ledger.h" + #include <exception> -#include <sstream> +#include <string> + +#ifdef DEBUG +#include <cassert> +#else +#ifdef assert +#undef assert +#endif +#define assert(x) +#endif namespace ledger { @@ -49,13 +60,9 @@ class parse_error : public error : error(reason), line(_line), file(_file) {} virtual ~parse_error() throw() {} - virtual const char* what() const throw() { - static std::ostringstream msg; - msg << file << ", line " << line << ": " << error::what(); - return msg.str().c_str(); - } + virtual const char* what() const throw(); }; } // namespace ledger -#endif // _CONSTRAINT_H +#endif // _ERROR_H @@ -14,13 +14,18 @@ std::string truncated(const std::string& str, unsigned int width) return buf; } -std::string maximal_account_name(const item_t * item, const item_t * parent) +std::string partial_account_name(const account_t * account, + const unsigned int start_depth) { - std::string name = item->account->name; - for (const item_t * i = item->parent; - i && i->account && i != parent; - i = i->parent) - name = i->account->name + ":" + name; + std::string name = account->name; + const account_t * acct = account->parent; + + for (int i = account->depth - start_depth - 1; + --i >= 0 && acct->parent; ) { + assert(acct); + name = acct->name + ":" + name; + acct = acct->parent; + } return name; } @@ -139,14 +144,10 @@ element_t * format_t::parse_elements(const std::string& fmt) return result; } -void format_t::format_elements(std::ostream& out, const item_t * item, - const item_t * displayed_parent) const +void format_t::format_elements(std::ostream& out, + const details_t& details) const { - std::string result; - - for (const element_t * elem = elements; - elem; - elem = elem->next) { + for (const element_t * elem = elements.get(); elem; elem = elem->next) { if (elem->align_left) out << std::left; else @@ -161,16 +162,18 @@ void format_t::format_elements(std::ostream& out, const item_t * item, break; case element_t::VALUE_EXPR: { - balance_t value = elem->val_expr->compute(item); - value.write(out, elem->min_width, - elem->max_width > 0 ? elem->max_width : elem->min_width); + balance_t value; + elem->val_expr->compute(value, details); + value.write(out, elem->min_width, (elem->max_width > 0 ? + elem->max_width : elem->min_width)); break; } case element_t::DATE_STRING: - if (item->date != -1) { + if (details.entry && details.entry->date != -1) { char buf[256]; - std::strftime(buf, 255, elem->chars.c_str(), std::gmtime(&item->date)); + std::strftime(buf, 255, elem->chars.c_str(), + std::gmtime(&details.entry->date)); out << (elem->max_width == 0 ? buf : truncated(buf, elem->max_width)); } else { out << " "; @@ -178,35 +181,38 @@ void format_t::format_elements(std::ostream& out, const item_t * item, break; case element_t::CLEARED: - if (item->state == entry_t::CLEARED) + if (details.entry && details.entry->state == entry_t::CLEARED) out << "* "; else out << ""; break; case element_t::CODE: - if (! item->code.empty()) - out << "(" << item->code << ") "; + if (details.entry && ! details.entry->code.empty()) + out << "(" << details.entry->code << ") "; else out << ""; break; case element_t::PAYEE: - out << (elem->max_width == 0 ? - item->payee : truncated(item->payee, elem->max_width)); + if (details.entry) + out << (elem->max_width == 0 ? + details.entry->payee : truncated(details.entry->payee, + elem->max_width)); break; case element_t::ACCOUNT_NAME: case element_t::ACCOUNT_FULLNAME: - if (item->account) { + if (details.account) { std::string name = (elem->type == element_t::ACCOUNT_FULLNAME ? - item->account->fullname() : - maximal_account_name(item, displayed_parent)); + details.account->fullname() : + partial_account_name(details.account, + details.depth)); if (elem->max_width > 0) name = truncated(name, elem->max_width); - if (item->flags & TRANSACTION_VIRTUAL) { - if (item->flags & TRANSACTION_BALANCE) + if (details.xact && details.xact->flags & TRANSACTION_VIRTUAL) { + if (details.xact->flags & TRANSACTION_BALANCE) name = "[" + name + "]"; else name = "(" + name + ")"; @@ -218,81 +224,67 @@ void format_t::format_elements(std::ostream& out, const item_t * item, break; case element_t::OPT_AMOUNT: { + if (! details.entry || ! details.xact) + break; + std::string disp; bool use_disp = false; - if (std::find(displayed_parent->subitems.begin(), - displayed_parent->subitems.end(), item) != - displayed_parent->subitems.end()) { - if (displayed_parent->subitems.size() == 2 && - item == displayed_parent->subitems.back() && - (displayed_parent->subitems.front()->value.quantity == - displayed_parent->subitems.front()->value.cost) && - (displayed_parent->subitems.front()->value == - - displayed_parent->subitems.back()->value)) { + if (std::find(details.entry->transactions.begin(), + details.entry->transactions.end(), details.xact) != + details.entry->transactions.end()) { + if (details.entry->transactions.size() == 2 && + details.xact == details.entry->transactions.back() && + (details.entry->transactions.front()->amount == + details.entry->transactions.front()->cost) && + (details.entry->transactions.front()->amount == + - details.entry->transactions.back()->amount)) { use_disp = true; } - else if (displayed_parent->subitems.size() != 2 && - item->value.quantity != item->value.cost && - item->value.quantity.amounts.size() == 1 && - item->value.cost.amounts.size() == 1 && - ((*item->value.quantity.amounts.begin()).first != - (*item->value.cost.amounts.begin()).first)) { - amount_t unit_cost - = ((*item->value.cost.amounts.begin()).second / - (*item->value.quantity.amounts.begin()).second); + else if (details.entry->transactions.size() != 2 && + details.xact->amount != details.xact->cost) { + amount_t unit_cost = details.xact->cost / details.xact->amount; std::ostringstream stream; - stream << item->value.quantity << " @ " << unit_cost; + stream << details.xact->amount << " @ " << unit_cost; disp = stream.str(); use_disp = true; } } - if (use_disp) - out << disp; - else - item->value.quantity.write(out, elem->min_width, - elem->max_width > 0 ? - elem->max_width : elem->min_width); + if (! use_disp) + disp = std::string(details.xact->amount); + out << disp; // jww (2004-07-31): this should be handled differently - if (! item->note.empty()) - out << " ; " << item->note; + if (! details.xact->note.empty()) + out << " ; " << details.xact->note; break; } case element_t::VALUE: { - balance_t value = compute_value(item); - value.write(out, elem->min_width, - elem->max_width > 0 ? elem->max_width : elem->min_width); + balance_t value; + compute_value(value, details); + value.write(out, elem->min_width, (elem->max_width > 0 ? + elem->max_width : elem->min_width)); break; } case element_t::TOTAL: { - balance_t value = compute_total(item); - value.write(out, elem->min_width, - elem->max_width > 0 ? elem->max_width : elem->min_width); + balance_t value; + compute_total(value, details); + value.write(out, elem->min_width, (elem->max_width > 0 ? + elem->max_width : elem->min_width)); break; } - case element_t::SPACER: { - int depth = 0; - for (const item_t * i = item; i->parent; i = i->parent) - depth++; - - for (const item_t * i = item->parent; - i && i->account && i != displayed_parent; - i = i->parent) - depth--; - - while (--depth >= 0) { + case element_t::SPACER: + for (unsigned int i = 0; i < details.depth; i++) { if (elem->min_width > 0 || elem->max_width > 0) out.width(elem->min_width > elem->max_width ? elem->min_width : elem->max_width); out << " "; } break; - } default: assert(0); @@ -2,13 +2,14 @@ #define _FORMAT_H #include "ledger.h" -#include "balance.h" -#include "expr.h" +#include "valexpr.h" namespace ledger { std::string truncated(const std::string& str, unsigned int width); -std::string maximal_account_name(const item_t * item, const item_t * parent); + +std::string partial_account_name(const account_t * account, + const unsigned int start_depth); struct element_t { @@ -48,28 +49,27 @@ struct element_t struct format_t { - element_t * elements; + std::auto_ptr<element_t> elements; - static std::auto_ptr<node_t> value_expr; - static std::auto_ptr<node_t> total_expr; + static std::auto_ptr<node_t> value_expr; + static std::auto_ptr<node_t> total_expr; format_t(const std::string& _format) { - elements = parse_elements(_format); - } - ~format_t() { - if (elements) delete elements; + elements.reset(parse_elements(_format)); } static element_t * parse_elements(const std::string& fmt); - void format_elements(std::ostream& out, const item_t * item, - const item_t * displayed_parent = NULL) const; + void format_elements(std::ostream& out, const details_t& details) const; - static balance_t compute_value(const item_t * item) { - return value_expr.get() ? value_expr->compute(item) : balance_t(); + static void compute_value(balance_t& result, const details_t& details) { + if (value_expr.get()) + value_expr->compute(result, details); } - static balance_t compute_total(const item_t * item) { - return total_expr.get() ? total_expr->compute(item) : balance_t(); + + static void compute_total(balance_t& result, const details_t& details) { + if (total_expr.get()) + total_expr->compute(result, details); } }; diff --git a/item.cc b/item.cc deleted file mode 100644 index 62ea9b8c..00000000 --- a/item.cc +++ /dev/null @@ -1,196 +0,0 @@ -#include "item.h" -#include "expr.h" - -namespace ledger { - -static inline void sum_items(const item_t * top, - const bool show_subtotals, - item_t * item) -{ - if (top->account == item->account) { - item->value += top->value; - if (show_subtotals) - item->total += top->value; - } - - for (items_deque::const_iterator i = top->subitems.begin(); - i != top->subitems.end(); - i++) - sum_items(*i, show_subtotals, item); -} - -item_t * walk_accounts(const item_t * top, - account_t * account, - const node_t * predicate, - const bool show_subtotals, - const bool show_flattened) -{ - item_t * item = new item_t; - item->account = account; - - if (top) { - sum_items(top, show_subtotals, item); - } else { - std::time_t latest = 0; - for (transactions_list::iterator i - = std::find_if(account->transactions.begin(), - account->transactions.end(), - value_predicate(predicate)); - i != account->transactions.end(); - i = std::find_if(++i, account->transactions.end(), - value_predicate(predicate))) { - if (std::difftime(latest, (*i)->entry->date) < 0) - latest = (*i)->entry->date; - - item->value += *(*i); - if (show_subtotals) - item->total += *(*i); - } - item->date = latest; - } - - for (accounts_map::iterator i = account->accounts.begin(); - i != account->accounts.end(); - i++) { - std::auto_ptr<item_t> - subitem(walk_accounts(top, (*i).second, predicate, show_subtotals, - show_flattened)); - subitem->parent = item; - - if (std::difftime(item->date, subitem->date) < 0) - item->date = subitem->date; - - if (show_flattened) { - item_t * ptr = item; - balance_pair_t total; - - for (items_deque::const_iterator i = subitem->subitems.begin(); - i != subitem->subitems.end(); - i++) - if (show_subtotals ? (*i)->total : (*i)->value) { - if (! account->parent) { - if (! total) { - item_t * temp = new item_t; - temp->date = top ? top->date : item->date; - temp->payee = "Opening balance"; - item->subitems.push_back(temp); - ptr = temp; - } - total += show_subtotals ? (*i)->total : (*i)->value; - } - - ptr->subitems.push_back(new item_t(*i)); - ptr->subitems.back()->date = ptr->date; - ptr->subitems.back()->payee = ptr->payee; - } - - if (total) { - item_t * temp = new item_t; - temp->date = ptr->date; - temp->payee = ptr->payee; - temp->account = account->find_account("Equity:Opening Balances"); - temp->value = total; - temp->value.negate(); - ptr->subitems.push_back(temp); - } - } - - if (show_subtotals) - item->total += subitem->total; - - if ((! show_flattened || account->parent) && - show_subtotals ? subitem->total : subitem->value) - item->subitems.push_back(subitem.release()); - } - - return item; -} - -item_t * walk_entries(entries_list::const_iterator begin, - entries_list::const_iterator end, - const node_t * predicate, - const bool show_related, - const bool show_inverted) -{ - unsigned int count = 0; - item_t * result = NULL; - value_predicate pred_obj(predicate); - - for (entries_list::const_iterator i = std::find_if(begin, end, pred_obj); - i != end; - i = std::find_if(++i, end, pred_obj)) { - transactions_list reckoned; - item_t * item = NULL; - - for (transactions_list::const_iterator j - = std::find_if((*i)->transactions.begin(), - (*i)->transactions.end(), pred_obj); - j != (*i)->transactions.end(); - j = std::find_if(++j, - transactions_list::const_iterator((*i)->transactions.end()), - pred_obj)) { - assert(*i == (*j)->entry); - - if (! item) { - item = new item_t(*i); - item->index = count++; - } - - // If show_inverted is true, it implies show_related. - if (! show_inverted && - std::find(reckoned.begin(), - reckoned.end(), *j) == reckoned.end()) { - item->add_item(new item_t(*j)); - reckoned.push_back(*j); - } - - if (show_related) - for (transactions_list::iterator k = (*i)->transactions.begin(); - k != (*i)->transactions.end(); - k++) { - if (*k == *j || ((*k)->flags & TRANSACTION_AUTO) || - std::find(reckoned.begin(), - reckoned.end(), *k) != reckoned.end()) - continue; - - item->add_item(new item_t(*k)); - if (show_inverted) - item->subitems.back()->value.negate(); - reckoned.push_back(*k); - } - } - - if (item) { - if (! result) - result = new item_t; - item->parent = result; - result->subitems.push_back(item); - - if (std::difftime(result->date, item->date) < 0) - result->date = item->date; - } - } - - return result; -} - -struct cmp_items { - const node_t * sort_order; - - cmp_items(const node_t * _sort_order) : sort_order(_sort_order) { - assert(sort_order); - } - - bool operator()(const item_t * left, const item_t * right) const { - assert(left); - assert(right); - return sort_order->compute(left) < sort_order->compute(right); - } -}; - -void item_t::sort(const node_t * sort_order) -{ - std::stable_sort(subitems.begin(), subitems.end(), cmp_items(sort_order)); -} - -} // namespace ledger diff --git a/item.h b/item.h deleted file mode 100644 index 15c710f3..00000000 --- a/item.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef _ITEM_H -#define _ITEM_H - -#include "ledger.h" -#include "balance.h" - -#include <deque> - -namespace ledger { - -struct node_t; -struct item_t; -typedef std::deque<item_t *> items_deque; - -struct item_t -{ - struct item_t * parent; - items_deque subitems; - - unsigned int index; - std::time_t date; - entry_t::entry_state_t state; - std::string code; - std::string payee; - unsigned int flags; - const account_t * account; - balance_pair_t value; - balance_pair_t total; - std::string note; - - item_t() : parent(NULL), index(0), date(-1), - state(entry_t::UNCLEARED), flags(0), account(NULL) {} - - item_t(const item_t * item) - : parent(NULL), index(0), date(item->date), state(item->state), - code(item->code), payee(item->payee), flags(item->flags), - account(item->account), value(item->value), total(item->total), - note(item->note) {} - - item_t(const entry_t * entry) - : parent(NULL), index(0), date(entry->date), state(entry->state), - code(entry->code), payee(entry->payee) {} - - item_t(const transaction_t * xact) - : parent(NULL), index(0), date(xact->entry->date), - state(xact->entry->state), code(xact->entry->code), - payee(xact->entry->payee), flags(xact->flags), - account(xact->account), value(*xact), note(xact->note) {} - - ~item_t() { - for (items_deque::iterator i = subitems.begin(); - i != subitems.end(); - i++) - delete *i; - } - - void add_item(item_t * item) { - item->parent = this; - value += item->value; - subitems.push_back(item); - } - - void sort(const node_t * sort_order); -}; - -struct node_t; - -item_t * walk_accounts(const item_t * top, - account_t * account, - const node_t * predicate = NULL, - const bool show_subtotals = true, - const bool show_flattened = false); - -item_t * walk_entries(entries_list::const_iterator begin, - entries_list::const_iterator end, - const node_t * predicate = NULL, - const bool show_related = false, - const bool show_inverted = false); - -} // namespace report - -#endif // _REPORT_H @@ -1,5 +1,6 @@ #include "ledger.h" -#include "expr.h" +#include "valexpr.h" +#include "datetime.h" #include "textual.h" #include "binary.h" @@ -9,6 +10,29 @@ namespace ledger { const std::string version = "2.0b"; +#if 0 + +struct cmp_items { + const node_t * sort_order; + + cmp_items(const node_t * _sort_order) : sort_order(_sort_order) { + assert(sort_order); + } + + bool operator()(const item_t * left, const item_t * right) const { + assert(left); + assert(right); + return sort_order->compute(left) < sort_order->compute(right); + } +}; + +void item_t::sort(const node_t * sort_order) +{ + std::stable_sort(subitems.begin(), subitems.end(), cmp_items(sort_order)); +} + +#endif + ledger_t::~ledger_t() { delete master; @@ -200,7 +224,7 @@ int parse_ledger_file(char * p, ledger_t * journal) stream.read((char *)&magic, sizeof(magic)); stream.seekg(start); - if (magic == magic_number) + if (magic == binary_magic_number) return read_binary_ledger(stream, "", journal, master); else return parse_textual_ledger(stream, journal, master); @@ -14,291 +14,32 @@ #include <list> #include <string> #include <ctime> -#include <cctype> #include <iostream> -#include <sstream> -#ifdef DEBUG -#include <cassert> -#else -#ifdef assert -#undef assert -#endif -#define assert(x) -#endif +#include "amount.h" +#include "balance.h" namespace ledger { -extern const std::string version; +#define TRANSACTION_NORMAL 0x00 +#define TRANSACTION_VIRTUAL 0x01 +#define TRANSACTION_BALANCE 0x02 +#define TRANSACTION_AUTO 0x04 +#define TRANSACTION_HANDLED 0x08 +#define TRANSACTION_DISPLAYED 0x10 -class commodity_t; -class amount_t; -class transaction_t; class entry_t; class account_t; -class ledger_t; - -class amount_t -{ - typedef void * base_type; - - void _init(); - void _copy(const amount_t& amt); - void _clear(); - - public: - base_type quantity; // amount, to MAX_PRECISION - commodity_t * commodity; - - bool valid() const { - if (quantity) - return commodity != NULL; - else - return commodity == NULL; - } - - // constructors - amount_t(commodity_t * _commodity = NULL) - : quantity(NULL), commodity(_commodity) {} - - amount_t(const amount_t& amt) : quantity(NULL) { - if (amt.quantity) - _copy(amt); - else - commodity = amt.commodity; - } - amount_t(const std::string& value) { - _init(); - std::istringstream str(value); - str >> *this; - } - amount_t(const int value) : quantity(NULL), commodity(NULL) { - if (value != 0) { - std::string str; - std::ostringstream strstr(str); - strstr << value; - parse(strstr.str()); - } - } - amount_t(const unsigned int value) : quantity(NULL), commodity(NULL) { - if (value != 0) { - std::string str; - std::ostringstream strstr(str); - strstr << value; - parse(strstr.str()); - } - } - amount_t(const double value) : quantity(NULL), commodity(NULL) { - if (value != 0.0) { - std::string str; - std::ostringstream strstr(str); - strstr << value; - parse(strstr.str()); - } - } - - // destructor - ~amount_t() { - if (quantity) - _clear(); - } - - // assignment operator - amount_t& operator=(const amount_t& amt); - amount_t& operator=(const std::string& value); - amount_t& operator=(const int value); - amount_t& operator=(const unsigned int value); - amount_t& operator=(const double value); - - // general methods - amount_t round(int precision = -1) const; - - // in-place arithmetic - amount_t& operator*=(const amount_t& amt); - amount_t& operator/=(const amount_t& amt); - amount_t& operator%=(const amount_t& amt); - amount_t& operator+=(const amount_t& amt); - amount_t& operator-=(const amount_t& amt); - - // simple arithmetic - amount_t operator*(const amount_t& amt) const { - amount_t temp = *this; - temp *= amt; - return temp; - } - amount_t operator/(const amount_t& amt) const { - amount_t temp = *this; - temp /= amt; - return temp; - } - amount_t operator%(const amount_t& amt) const { - amount_t temp = *this; - temp %= amt; - return temp; - } - amount_t operator+(const amount_t& amt) const { - amount_t temp = *this; - temp += amt; - return temp; - } - amount_t operator-(const amount_t& amt) const { - amount_t temp = *this; - temp -= amt; - return temp; - } - - // unary negation - amount_t& negate(); - amount_t negated() const { - amount_t temp = *this; - temp.negate(); - return temp; - } - amount_t operator-() const { - return negated(); - } - - // test for non-zero (use ! for zero) - operator bool() const; - - // comparisons to zero - bool operator<(const int num) const; - bool operator<=(const int num) const; - bool operator>(const int num) const; - bool operator>=(const int num) const; - - // comparisons between amounts - bool operator<(const amount_t& amt) const; - bool operator<=(const amount_t& amt) const; - bool operator>(const amount_t& amt) const; - bool operator>=(const amount_t& amt) const; - bool operator==(const amount_t& amt) const; - bool operator!=(const amount_t& amt) const { - if (commodity != amt.commodity) - return true; - return ! (*this == amt); - } - - amount_t value(const std::time_t moment) const; - - operator std::string() const; - - void parse(std::istream& in); - void parse(const std::string& str) { - std::istringstream stream(str); - parse(stream); - } - - void write_quantity(std::ostream& out) const; - void read_quantity(std::istream& in); - - friend std::istream& operator>>(std::istream& in, amount_t& amt); -}; - -void parse_quantity(std::istream& in, std::string& value); -void parse_commodity(std::istream& in, std::string& symbol); - -inline amount_t abs(const amount_t& amt) { - return amt < 0 ? amt.negated() : amt; -} - -inline std::istream& operator>>(std::istream& in, amount_t& amt) { - amt.parse(in); - return in; -} - -std::ostream& operator<<(std::ostream& out, const amount_t& amt); - - -#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_CONSULTED 0x10 -#define COMMODITY_STYLE_NOMARKET 0x20 - -typedef std::map<const std::time_t, amount_t> history_map; -typedef std::pair<const std::time_t, amount_t> history_pair; - -typedef std::map<const std::string, commodity_t *> commodities_map; -typedef std::pair<const std::string, commodity_t *> commodities_pair; - -class commodity_t -{ - public: - std::string symbol; - std::string name; - std::string note; - unsigned int precision; - unsigned int flags; - history_map history; - amount_t conversion; - unsigned long ident; - - // If set, this global function pointer is called to determine - // whether prices have been updated in the meanwhile. - - static void (*updater)(commodity_t * commodity, - const std::time_t date, - const amount_t& price, - const std::time_t moment); - - // This map remembers all commodities that have been - // defined thus far. - - static commodities_map commodities; - - static void add_commodity(commodity_t * commodity, - const std::string symbol = "") { - commodities.insert(commodities_pair((symbol.empty() ? - commodity->symbol : symbol), - commodity)); - } - static bool remove_commodity(commodity_t * commodity) { - commodities_map::size_type n = commodities.erase(commodity->symbol); - return n > 0; - } - static commodity_t * find_commodity(const std::string& symbol, - bool auto_create = false); - - // Now the per-object constructor and methods - - commodity_t(const std::string& _symbol = "", - unsigned int _precision = 2, - unsigned int _flags = COMMODITY_STYLE_DEFAULTS) - : symbol(_symbol), precision(_precision), flags(_flags) {} - - void add_price(const std::time_t date, const amount_t& price) { - history.insert(history_pair(date, price)); - } - bool remove_price(const std::time_t date) { - history_map::size_type n = history.erase(date); - return n > 0; - } - - void set_conversion(const amount_t& price) { - conversion = price; - } - - amount_t value(const std::time_t moment = std::time(NULL)); -}; - - -#define TRANSACTION_NORMAL 0x0 -#define TRANSACTION_VIRTUAL 0x1 -#define TRANSACTION_BALANCE 0x2 -#define TRANSACTION_AUTO 0x4 class transaction_t { public: - entry_t * entry; - account_t * account; - amount_t amount; - amount_t cost; - unsigned int flags; - std::string note; + entry_t * entry; + account_t * account; + amount_t amount; + amount_t cost; + unsigned int flags; + std::string note; transaction_t(entry_t * _entry, account_t * _account) : entry(_entry), account(_account), flags(TRANSACTION_NORMAL) {} @@ -351,23 +92,27 @@ class entry_t typedef std::map<const std::string, account_t *> accounts_map; typedef std::pair<const std::string, account_t *> accounts_pair; -inline std::ostream& operator<<(std::ostream& out, const account_t& acct); - class account_t { public: - const account_t * parent; - std::string name; - std::string note; - accounts_map accounts; - mutable accounts_map accounts_cache; - transactions_list transactions; - unsigned long ident; - static unsigned long next_ident; - - account_t(const account_t * _parent, const std::string& _name = "", + account_t * parent; + std::string name; + std::string note; + accounts_map accounts; + transactions_list transactions; + balance_pair_t value; + balance_pair_t total; + unsigned long depth; + unsigned long ident; + + mutable std::string _fullname; + static unsigned long next_ident; + + account_t(account_t * _parent, + const std::string& _name = "", const std::string& _note = "") - : parent(_parent), name(_name), note(_note) {} + : parent(_parent), name(_name), note(_note), + depth(parent ? parent->depth + 1 : 0) {} ~account_t(); @@ -409,9 +154,9 @@ typedef std::list<entry_t *> entries_list; class ledger_t { public: - account_t * master; - entries_list entries; - + account_t * master; + entries_list entries; + mutable accounts_map accounts_cache; std::list<std::string> sources; ledger_t() { @@ -430,10 +175,18 @@ class ledger_t } account_t * find_account(const std::string& name, bool auto_create = true) { - return master->find_account(name, auto_create); + accounts_map::iterator c = accounts_cache.find(name); + if (c != accounts_cache.end()) + return (*c).second; + + account_t * account = master->find_account(name, auto_create); + accounts_cache.insert(accounts_pair(name, account)); + return account; } account_t * find_account(const std::string& name) const { - return master->find_account(name, false); + // With auto_create false, the other `find_account' will not + // change the object. + return const_cast<ledger_t *>(this)->find_account(name, false); } bool add_entry(entry_t * entry); @@ -444,6 +197,8 @@ class ledger_t int parse_ledger_file(char * p, ledger_t * journal); +extern const std::string version; + } // namespace ledger #endif // _LEDGER_H @@ -1,11 +1,10 @@ #include "ledger.h" -#include "balance.h" #include "error.h" #include "textual.h" #include "binary.h" -#include "item.h" -#include "expr.h" +#include "valexpr.h" #include "format.h" +#include "walk.h" #include <fstream> #include <cstring> @@ -20,7 +19,9 @@ namespace ledger { // The command-line balance report // -static const std::string bal_fmt = "%20T%2_%-n\n"; +static const std::string bal_fmt = "%20T %2_%-n\n"; + +#if 0 unsigned int show_balances(std::ostream& out, items_deque& items, @@ -80,6 +81,7 @@ void balance_report(std::ostream& out, } } +#endif ////////////////////////////////////////////////////////////////////// // @@ -96,6 +98,8 @@ static const std::string print_fmt static bool show_commodities_revalued = false; static bool show_commodities_revalued_only = false; +#if 0 + static void report_value_change(std::ostream& out, const std::time_t date, const balance_pair_t& balance, @@ -218,6 +222,7 @@ void register_report(std::ostream& out, first_line_format, next_lines_format); } +#endif void set_price_conversion(const std::string& setting) { @@ -362,6 +367,7 @@ int main(int argc, char * argv[]) bool show_expanded = false; bool show_related = false; bool show_inverted = false; + bool show_empty = false; #ifdef DEBUG bool debug = false; @@ -492,11 +498,9 @@ int main(int argc, char * argv[]) format_string = optarg; break; -#if 0 case 'E': show_empty = true; break; -#endif case 'n': show_subtotals = false; @@ -708,7 +712,11 @@ int main(int argc, char * argv[]) else predicate_string += "("; first = false; - } else { + } + else if (argv[index][0] == '-') { + predicate_string += "&"; + } + else { predicate_string += "|"; } @@ -736,6 +744,8 @@ int main(int argc, char * argv[]) if (first) first = false; + else if (argv[index][0] == '-') + predicate_string += "&"; else predicate_string += "|"; @@ -753,7 +763,7 @@ int main(int argc, char * argv[]) predicate_string += ")"; } - // Compile the predicate + // Compile the predicates if (! predicate_string.empty()) { #ifdef DEBUG @@ -763,6 +773,9 @@ int main(int argc, char * argv[]) predicate.reset(ledger::parse_expr(predicate_string)); } + if (display_predicate_string.empty() && command == "b" && ! show_empty) + display_predicate_string = "T"; + if (! display_predicate_string.empty()) { #ifdef DEBUG if (debug) @@ -795,74 +808,53 @@ int main(int argc, char * argv[]) show_inverted = true; } - std::auto_ptr<ledger::item_t> top; - std::auto_ptr<ledger::item_t> list; - - if (command == "e") { - top.reset(new ledger::item_t); - ledger::item_t * item = new ledger::item_t(new_entry.get()); - for (ledger::transactions_list::const_iterator i - = new_entry->transactions.begin(); - i != new_entry->transactions.end(); - i++) - item->add_item(new ledger::item_t(*i)); - top->add_item(item); - } - else if ((! show_related || ! predicate.get()) && - (command == "b" || command == "E")) { - top.reset(ledger::walk_accounts(NULL, journal->master, predicate.get(), - command != "E" && show_subtotals, - command == "E")); - } - else { - top.reset(ledger::walk_entries(journal->entries.begin(), - journal->entries.end(), predicate.get(), - show_related, show_inverted)); - if (top.get() && command == "b" || command == "E") { - list.reset(top.release()); - top.reset(ledger::walk_accounts(list.get(), journal->master, - predicate.get(), - command != "E" && show_subtotals, - command == "E")); + const char * f; + if (! format_string.empty()) + f = format_string.c_str(); + else if (command == "b") + f = ledger::bal_fmt.c_str(); + else if (command == "r") + f = ledger::reg_fmt.c_str(); + else + f = ledger::print_fmt.c_str(); + + if (command == "b") { + std::auto_ptr<ledger::format_t> format(new ledger::format_t(f)); + + ledger::walk_accounts(journal->master, + ledger::format_account(std::cout, *format.get()), + predicate.get(), show_related, show_inverted, + show_subtotals, display_predicate.get()); + + if (! display_predicate.get() || + ledger::item_predicate(display_predicate.get())(journal->master)) { + std::string end_format = "--------------------\n"; + end_format += f; + format.get()->elements.reset(ledger::format_t::parse_elements(end_format)); + ledger::format_account(std::cout, *format.get())(journal->master, true); } - } + } else { + std::string first_line_format; + std::string next_lines_format; - if (top.get()) { - const char * f; - if (! format_string.empty()) - f = format_string.c_str(); - else if (command == "b") - f = ledger::bal_fmt.c_str(); - else if (command == "r") - f = ledger::reg_fmt.c_str(); - else - f = ledger::print_fmt.c_str(); - - if (command == "b") { - std::auto_ptr<ledger::format_t> format(new ledger::format_t(f)); - ledger::balance_report(std::cout, top.get(), display_predicate.get(), - sort_order.get(), *format, show_expanded, - show_subtotals); + if (const char * p = std::strstr(f, "%/")) { + first_line_format = std::string(f, 0, p - f); + next_lines_format = std::string(p + 2); } else { - std::string first_line_format; - std::string next_lines_format; - - if (const char * p = std::strstr(f, "%/")) { - first_line_format = std::string(f, 0, p - f); - next_lines_format = std::string(p + 2); - } else { - first_line_format = next_lines_format = f; - } - - std::auto_ptr<ledger::format_t> - format(new ledger::format_t(first_line_format)); - std::auto_ptr<ledger::format_t> - nformat(new ledger::format_t(next_lines_format)); - - ledger::register_report(std::cout, top.get(), display_predicate.get(), - sort_order.get(), *format, *nformat, - show_expanded); + first_line_format = next_lines_format = f; } + + std::auto_ptr<ledger::format_t> + format(new ledger::format_t(first_line_format)); + std::auto_ptr<ledger::format_t> + nformat(new ledger::format_t(next_lines_format)); + + ledger::walk_entries(journal->entries.begin(), journal->entries.end(), + ledger::format_transaction(std::cout, + first_line_format, + next_lines_format), + predicate.get(), show_related, show_inverted, + display_predicate.get()); } // Save the cache, if need be @@ -1,8 +1,9 @@ #include "textual.h" +#include "datetime.h" +#include "autoxact.h" +#include "valexpr.h" #include "error.h" -#include "expr.h" -#include <vector> #include <fstream> #include <sstream> #include <cstring> @@ -13,11 +14,6 @@ namespace ledger { -#if 0 -static const std::string entry1_fmt = "%10d %p"; -static const std::string entryn_fmt = " %-30a %15t"; -#endif - #define MAX_LINE 1024 std::string path; @@ -29,31 +25,6 @@ static account_t * last_account; static std::string last_desc; #endif -static std::time_t now = std::time(NULL); -static struct std::tm * now_tm = std::localtime(&now); - -static std::time_t base = -1; -static int base_year = -1; - -static const int month_days[12] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 -}; - -static const char * formats[] = { - "%Y/%m/%d", - "%m/%d", - "%Y.%m.%d", - "%m.%d", - "%Y-%m-%d", - "%m-%d", - "%a", - "%A", - "%b", - "%B", - "%Y", - NULL -}; - inline char * skip_ws(char * ptr) { while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') @@ -61,6 +32,16 @@ inline char * skip_ws(char * ptr) return ptr; } +inline char peek_next_nonws(std::istream& in) +{ + char c = in.peek(); + while (! in.eof() && std::isspace(c) && c != '\n') { + in.get(c); + c = in.peek(); + } + return c; +} + inline char * next_element(char * buf, bool variable = false) { for (char * p = buf; *p; p++) { @@ -83,109 +64,6 @@ inline char * next_element(char * buf, bool variable = false) return NULL; } -bool parse_date_mask(const char * date_str, struct std::tm * result) -{ - for (const char ** f = formats; *f; f++) { - memset(result, INT_MAX, sizeof(struct std::tm)); - if (strptime(date_str, *f, result)) - return true; - } - return false; -} - -bool parse_date(const char * date_str, std::time_t * result, const int year) -{ - struct std::tm when; - - if (! parse_date_mask(date_str, &when)) - return false; - - when.tm_hour = 0; - when.tm_min = 0; - when.tm_sec = 0; - - if (when.tm_year == -1) - when.tm_year = ((year == -1) ? now_tm->tm_year : (year - 1900)); - - if (when.tm_mon == -1) - when.tm_mon = 0; - - if (when.tm_mday == -1) - when.tm_mday = 1; - - *result = std::mktime(&when); - - return true; -} - -static bool quick_parse_date(char * date_str, std::time_t * result) -{ - int year = -1, month = -1, day, num = 0; - - for (char * p = date_str; *p; p++) { - if (*p == '/' || *p == '-' || *p == '.') { - if (year == -1) - year = num; - else - month = num; - num = 0; - } - else if (*p < '0' || *p > '9') { - return false; - } - else { - num *= 10; - num += *p - '0'; - } - } - - day = num; - - if (month == -1) { - month = year; - year = -1; - } - - if (base == -1 || year != base_year) { - struct std::tm when; - - when.tm_hour = 0; - when.tm_min = 0; - when.tm_sec = 0; - - base_year = year == -1 ? now_tm->tm_year + 1900 : year; - when.tm_year = year == -1 ? now_tm->tm_year : year - 1900; - when.tm_mon = 0; - when.tm_mday = 1; - - base = std::mktime(&when); - } - - *result = base; - - --month; - while (--month >= 0) { - *result += month_days[month] * 24 * 60 * 60; - if (month == 1 && year % 4 == 0 && year != 2000) // february in leap years - *result += 24 * 60 * 60; - } - - if (--day) - *result += day * 24 * 60 * 60; - - return true; -} - -inline char peek_next_nonws(std::istream& in) -{ - char c = in.peek(); - while (! in.eof() && std::isspace(c) && c != '\n') { - in.get(c); - c = in.peek(); - } - return c; -} - transaction_t * parse_transaction_text(char * line, account_t * account, entry_t * entry) { @@ -233,9 +111,9 @@ transaction_t * parse_transaction_text(char * line, account_t * account, xact->account = account->find_account(p); if (! xact->amount.commodity) - xact->amount.commodity = commodity_t::find_commodity("", true); + xact->amount.commodity = commodity_t::null_commodity; if (! xact->cost.commodity) - xact->cost.commodity = commodity_t::find_commodity("", true); + xact->cost.commodity = commodity_t::null_commodity; return xact; } @@ -250,95 +128,6 @@ transaction_t * parse_transaction(std::istream& in, account_t * account, return parse_transaction_text(line, account, entry); } -class automated_transaction_t -{ -public: - masks_list masks; - transactions_list transactions; - - automated_transaction_t(masks_list& _masks, - transactions_list& _transactions) { - masks.insert(masks.begin(), _masks.begin(), _masks.end()); - transactions.insert(transactions.begin(), - _transactions.begin(), _transactions.end()); - // Take over ownership of the pointers - _transactions.clear(); - } - - ~automated_transaction_t() { - for (transactions_list::iterator i = transactions.begin(); - i != transactions.end(); - i++) - delete *i; - } - - void extend_entry(entry_t * entry); -}; - -typedef std::vector<automated_transaction_t *> - automated_transactions_vector; - -void automated_transaction_t::extend_entry(entry_t * entry) -{ - for (transactions_list::iterator i = entry->transactions.begin(); - i != entry->transactions.end(); - i++) - if (matches(masks, *((*i)->account))) { - for (transactions_list::iterator t = transactions.begin(); - t != transactions.end(); - t++) { - amount_t amt; - if ((*t)->amount.commodity->symbol.empty()) - amt = (*i)->amount * (*t)->amount; - else - amt = (*t)->amount; - - transaction_t * xact - = new transaction_t(entry, (*t)->account, amt, amt, - (*t)->flags | TRANSACTION_AUTO); - entry->add_transaction(xact); - } - } -} - -class automated_transactions_t -{ -public: - automated_transactions_vector automated_transactions; - - ~automated_transactions_t() { - for (automated_transactions_vector::iterator i - = automated_transactions.begin(); - i != automated_transactions.end(); - i++) - delete *i; - } - - void extend_entry(entry_t * entry) { - for (automated_transactions_vector::iterator i - = automated_transactions.begin(); - i != automated_transactions.end(); - i++) - (*i)->extend_entry(entry); - } - - void add_automated_transaction(automated_transaction_t * auto_xact) { - automated_transactions.push_back(auto_xact); - } - bool remove_automated_transaction(automated_transaction_t * auto_xact) { - for (automated_transactions_vector::iterator i - = automated_transactions.begin(); - i != automated_transactions.end(); - i++) { - if (*i == auto_xact) { - automated_transactions.erase(i); - return true; - } - } - return false; - } -}; - void parse_automated_transactions(std::istream& in, account_t * account, automated_transactions_t& auto_xacts) { @@ -498,11 +287,6 @@ entry_t * parse_entry(std::istream& in, account_t * master) return curr; } -////////////////////////////////////////////////////////////////////// -// -// Textual ledger parser -// - unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal, account_t * master) { @@ -676,7 +460,8 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal, amount_t price; parse_commodity(in, symbol); - in >> line; // the price + in.getline(line, MAX_LINE); + linenum++; price.parse(line); commodity_t * commodity = commodity_t::find_commodity(symbol, true); @@ -733,7 +518,8 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal, unsigned int curr_linenum = linenum; std::string curr_path = path; - count += parse_textual_ledger(stream, journal, account_stack.front()); + count += parse_textual_ledger(stream, journal, + account_stack.front()); linenum = curr_linenum; path = curr_path; @@ -8,11 +8,6 @@ namespace ledger { extern unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger, account_t * master = NULL); -extern bool parse_date_mask(const char * date_str, struct std::tm * result); - -extern bool parse_date(const char * date_str, std::time_t * result, - const int year = -1); - } // namespace ledger #endif // _TEXTUAL_H @@ -1,5 +1,6 @@ -#include "expr.h" +#include "valexpr.h" #include "error.h" +#include "datetime.h" #include "textual.h" #include <pcre.h> @@ -90,138 +91,199 @@ bool matches(const masks_list& regexps, const std::string& str, #endif -balance_t node_t::compute(const item_t * item) const +void node_t::compute(balance_t& result, const details_t& details) const { - balance_t temp; - switch (type) { case CONSTANT_A: - temp = constant_a; + result = constant_a; break; case CONSTANT_T: - temp = amount_t((unsigned int) constant_t); + result = (unsigned int) constant_t; break; case AMOUNT: - temp = item->value.quantity; + if (details.xact) + result = details.xact->amount; + else if (details.account) + result = details.account->value.quantity; break; + case COST: - temp = item->value.cost; + if (details.xact) + result = details.xact->cost; + else if (details.account) + result = details.account->value.cost; break; case BALANCE: - temp = item->total.quantity - item->value.quantity; + if (details.balance) { + result = details.balance->quantity; + if (details.xact) + result -= details.xact->amount; + else if (details.account) + result -= details.account->value.quantity; + } break; + case COST_BALANCE: - temp = item->total.cost - item->value.cost; + if (details.balance) { + result = details.balance->cost; + if (details.xact) + result -= details.xact->cost; + else if (details.account) + result -= details.account->value.cost; + } break; case TOTAL: - temp = item->total.quantity; + if (details.balance) + result = details.balance->quantity; + else if (details.account) + result = details.account->total.quantity; break; case COST_TOTAL: - temp = item->total.cost; + if (details.balance) + result = details.balance->cost; + else if (details.account) + result = details.account->total.cost; break; case DATE: - temp = amount_t((unsigned int) item->date); + if (details.entry) + result = (unsigned int) details.entry->date; break; case CLEARED: - temp = amount_t(item->state == entry_t::CLEARED ? 1 : 0); + if (details.entry) + result = details.entry->state == entry_t::CLEARED; break; case REAL: - temp = amount_t(item->flags & TRANSACTION_VIRTUAL ? 0 : 1); + if (details.xact) + result = bool(details.xact->flags & TRANSACTION_VIRTUAL); break; case INDEX: - temp = amount_t(item->index + 1); + if (details.index) + result = *details.index + 1; break; case F_ARITH_MEAN: - assert(left); - temp = left->compute(item); - temp /= amount_t(item->index + 1); + if (details.index) { + assert(left); + left->compute(result, details); + result /= amount_t(*details.index + 1); + } break; case F_NEG: assert(left); - temp = left->compute(item).negated(); + left->compute(result, details); + result.negate(); break; case F_ABS: assert(left); - temp = abs(left->compute(item)); + left->compute(result, details); + result = abs(result); break; case F_PAYEE_MASK: assert(mask); - temp = (mask->match(item->payee) || mask->match(item->note)) ? 1 : 0; + if (details.entry) + result = mask->match(details.entry->payee); break; case F_ACCOUNT_MASK: assert(mask); - temp = (item->account && - mask->match(item->account->fullname())) ? 1 : 0; + if (details.account) + result = mask->match(details.account->fullname()); break; case F_VALUE: { assert(left); - temp = left->compute(item); + left->compute(result, details); std::time_t moment = -1; - if (right) { + if (right && details.entry) { switch (right->type) { - case DATE: moment = item->date; break; + case DATE: moment = details.entry->date; break; default: throw compute_error("Invalid date passed to P(v,d)"); } } - temp = temp.value(moment); + result = result.value(moment); break; } case O_NOT: - temp = left->compute(item) ? 0 : 1; + left->compute(result, details); + result = result ? false : true; break; case O_QUES: - temp = left->compute(item); - if (temp) - temp = right->left->compute(item); + assert(left); + assert(right); + assert(right->type == O_COL); + left->compute(result, details); + if (result) + right->left->compute(result, details); else - temp = right->right->compute(item); + right->right->compute(result, details); break; case O_AND: + assert(left); + assert(right); + left->compute(result, details); + if (result) + right->compute(result, details); + break; + case O_OR: + assert(left); + assert(right); + left->compute(result, details); + if (! result) + right->compute(result, details); + break; + case O_EQ: case O_LT: case O_LTE: case O_GT: - case O_GTE: + case O_GTE: { + assert(left); + assert(right); + left->compute(result, details); + balance_t temp = result; + right->compute(result, details); + switch (type) { + case O_EQ: result = temp == result; break; + case O_LT: result = temp < result; break; + case O_LTE: result = temp <= result; break; + case O_GT: result = temp > result; break; + case O_GTE: result = temp >= result; break; + default: assert(0); break; + } + break; + } + case O_ADD: case O_SUB: case O_MUL: case O_DIV: { assert(left); assert(right); - balance_t left_bal = left->compute(item); - balance_t right_bal = right->compute(item); + right->compute(result, details); + balance_t temp = result; + left->compute(result, details); switch (type) { - case O_AND: temp = (left_bal && right_bal) ? 1 : 0; break; - case O_OR: temp = (left_bal || right_bal) ? 1 : 0; break; - case O_EQ: temp = (left_bal == right_bal) ? 1 : 0; break; - case O_LT: temp = (left_bal < right_bal) ? 1 : 0; break; - case O_LTE: temp = (left_bal <= right_bal) ? 1 : 0; break; - case O_GT: temp = (left_bal > right_bal) ? 1 : 0; break; - case O_GTE: temp = (left_bal >= right_bal) ? 1 : 0; break; - case O_ADD: temp = left_bal + right_bal; break; - case O_SUB: temp = left_bal - right_bal; break; - case O_MUL: temp = left_bal * right_bal; break; - case O_DIV: temp = left_bal / right_bal; break; + case O_ADD: result += temp; break; + case O_SUB: result -= temp; break; + case O_MUL: result *= temp; break; + case O_DIV: result /= temp; break; default: assert(0); break; } break; @@ -232,8 +294,6 @@ balance_t node_t::compute(const item_t * item) const assert(0); break; } - - return temp; } node_t * parse_term(std::istream& in); @@ -608,8 +668,13 @@ namespace ledger { static void dump_tree(std::ostream& out, node_t * node) { switch (node->type) { - case node_t::CONSTANT_A: out << "CONST[" << node->constant_a << "]"; break; - case node_t::CONSTANT_T: out << "DATE/TIME[" << node->constant_t << "]"; break; + case node_t::CONSTANT_A: + out << "CONST[" << node->constant_a << "]"; + break; + case node_t::CONSTANT_T: + out << "DATE/TIME[" << node->constant_t << "]"; + break; + case node_t::AMOUNT: out << "AMOUNT"; break; case node_t::COST: out << "COST"; break; case node_t::DATE: out << "DATE"; break; @@ -674,25 +739,43 @@ static void dump_tree(std::ostream& out, node_t * node) case node_t::O_AND: case node_t::O_OR: + out << "("; + dump_tree(out, node->left); + switch (node->type) { + case node_t::O_AND: out << " & "; break; + case node_t::O_OR: out << " | "; break; + default: assert(0); break; + } + dump_tree(out, node->right); + out << ")"; + break; + case node_t::O_EQ: case node_t::O_LT: case node_t::O_LTE: case node_t::O_GT: case node_t::O_GTE: - case node_t::O_ADD: - case node_t::O_SUB: - case node_t::O_MUL: - case node_t::O_DIV: out << "("; dump_tree(out, node->left); switch (node->type) { - case node_t::O_AND: out << " & "; break; - case node_t::O_OR: out << " | "; break; case node_t::O_EQ: out << "="; break; case node_t::O_LT: out << "<"; break; case node_t::O_LTE: out << "<="; break; case node_t::O_GT: out << ">"; break; case node_t::O_GTE: out << ">="; break; + default: assert(0); break; + } + dump_tree(out, node->right); + out << ")"; + break; + + case node_t::O_ADD: + case node_t::O_SUB: + case node_t::O_MUL: + case node_t::O_DIV: + out << "("; + dump_tree(out, node->left); + switch (node->type) { case node_t::O_ADD: out << "+"; break; case node_t::O_SUB: out << "-"; break; case node_t::O_MUL: out << "*"; break; @@ -714,7 +797,7 @@ static void dump_tree(std::ostream& out, node_t * node) int main(int argc, char *argv[]) { - ledger::dump_tree(std::cout, ledger::parse_expr(argv[1], NULL)); + ledger::dump_tree(std::cout, ledger::parse_expr(argv[1])); std::cout << std::endl; } @@ -2,8 +2,6 @@ #define _EXPR_H #include "ledger.h" -#include "balance.h" -#include "item.h" namespace ledger { @@ -30,6 +28,33 @@ bool matches(const masks_list& regexps, const std::string& str, #endif +struct details_t +{ + const entry_t * entry; + const transaction_t * xact; + const account_t * account; + const balance_pair_t * balance; + const unsigned int * index; + const unsigned int depth; + + details_t(const entry_t * _entry, + const balance_pair_t * _balance = NULL, + const unsigned int * _index = NULL) + : entry(_entry), xact(NULL), account(NULL), + balance(_balance), index(_index), depth(0) {} + + details_t(const transaction_t * _xact, + const balance_pair_t * _balance = NULL, + const unsigned int * _index = NULL) + : entry(_xact->entry), xact(_xact), account(_xact->account), + balance(_balance), index(_index), depth(0) {} + + details_t(const account_t * _account, + const unsigned int _depth = 0) + : entry(NULL), xact(NULL), account(_account), + balance(NULL), index(NULL), depth(_depth) {} +}; + struct node_t { enum kind_t { @@ -95,7 +120,7 @@ struct node_t if (right) delete right; } - balance_t compute(const item_t * item) const; + void compute(balance_t& result, const details_t& details) const; }; node_t * parse_expr(std::istream& in); @@ -120,64 +145,6 @@ inline node_t * find_node(node_t * node, node_t::kind_t type) { return result; } -void dump_tree(std::ostream& out, node_t * node); - -class value_predicate -{ - public: - const node_t * predicate; - - explicit value_predicate(const node_t * _predicate) - : predicate(_predicate) {} - - bool operator ()(const transaction_t * xact) const { - if (! predicate) { - return true; - } else { - item_t temp; - temp.date = xact->entry->date; - temp.state = xact->entry->state; - temp.code = xact->entry->code; - temp.payee = xact->entry->payee; - temp.flags = xact->flags; - temp.account = xact->account; - return predicate->compute(&temp); - } - } - - bool operator ()(const entry_t * entry) const { - if (! predicate) { - return true; - } else { - item_t temp; - temp.date = entry->date; - temp.payee = entry->payee; - temp.state = entry->state; - temp.code = entry->code; - - // Although there may be conflicting account masks for the whole - // set of transactions -- for example, /rent/&!/expenses/, which - // might match one by not another transactions -- we let the - // entry through if at least one of the transactions meets the - // criterion - - for (transactions_list::const_iterator i = entry->transactions.begin(); - i != entry->transactions.end(); - i++) { - temp.flags = (*i)->flags; - temp.account = (*i)->account; - if (predicate->compute(&temp)) - return true; - } - return false; - } - } - - bool operator ()(const item_t * item) const { - return ! predicate || predicate->compute(item); - } -}; - } // namespace report #endif // _REPORT_H @@ -0,0 +1,234 @@ +#ifndef _WALK_H +#define _WALK_H + +#include "ledger.h" +#include "balance.h" +#include "format.h" +#include "valexpr.h" + +#include <iostream> + +namespace ledger { + +class item_predicate +{ + const node_t * predicate; + balance_pair_t * balance; + unsigned int * index; + + public: + item_predicate(const node_t * _predicate, + balance_pair_t * _balance = NULL, + unsigned int * _index = NULL) + : predicate(_predicate), balance(_balance), index(_index) {} + + bool operator()(const entry_t * entry) const { + if (predicate) { + balance_t result; + predicate->compute(result, details_t(entry, balance, index)); + return result; + } else { + return true; + } + } + + bool operator()(const transaction_t * xact) const { + if (predicate) { + balance_t result; + predicate->compute(result, details_t(xact, balance, index)); + return result; + } else { + return true; + } + } + + bool operator()(const account_t * account) const { + if (predicate) { + balance_t result; + predicate->compute(result, details_t(account)); + return result; + } else { + return true; + } + } +}; + +inline void add_to_balance_pair(balance_pair_t& balance, + transaction_t * xact, + const bool inverted = false) +{ + if (inverted) { + balance.quantity += - xact->amount; + balance.cost += - xact->cost; + } else { + balance += *xact; + } +} + +class format_transaction +{ + std::ostream& output_stream; + const format_t& first_line_format; + const format_t& next_lines_format; + mutable entry_t * last_entry; + + public: + format_transaction(std::ostream& _output_stream, + const format_t& _first_line_format, + const format_t& _next_lines_format) + : output_stream(_output_stream), + first_line_format(_first_line_format), + next_lines_format(_next_lines_format), + last_entry(NULL) {} + + void operator()(transaction_t * xact, + balance_pair_t * balance, + unsigned int * index, + const bool inverted) const; +}; + +class ignore_transaction +{ + public: + void operator()(transaction_t * xact, + balance_pair_t * balance, + unsigned int * index, + const bool inverted) const {} +}; + +template <typename Function> +void handle_transaction(transaction_t * xact, + Function functor, + item_predicate& pred_functor, + const bool related, + const bool inverted, + balance_pair_t * balance = NULL, + unsigned int * index = NULL) +{ + // If inverted is true, it implies related. + if (! inverted && ! (xact->flags & TRANSACTION_HANDLED)) { + xact->flags |= TRANSACTION_HANDLED; + if (pred_functor(xact)) { + xact->flags |= TRANSACTION_DISPLAYED; + functor(xact, balance, index, inverted); + } + } + + if (related) + for (transactions_list::iterator i = xact->entry->transactions.begin(); + i != xact->entry->transactions.end(); + i++) { + if (*i == xact || ((*i)->flags & (TRANSACTION_AUTO | + TRANSACTION_HANDLED))) + continue; + + (*i)->flags |= TRANSACTION_HANDLED; + if (pred_functor(xact)) { + xact->flags |= TRANSACTION_DISPLAYED; + functor(*i, balance, index, inverted); + } + } +} + +template <typename Function> +void walk_entries(entries_list::iterator begin, + entries_list::iterator end, + Function functor, + const node_t * predicate, + const bool related, + const bool inverted, + const node_t * display_predicate = NULL) +{ + balance_pair_t balance; + unsigned int index; + item_predicate pred_functor(predicate, &balance, &index); + item_predicate disp_pred_functor(display_predicate, &balance, &index); + + for (entries_list::iterator i = begin; i != end; i++) + for (transactions_list::iterator j = (*i)->transactions.begin(); + j != (*i)->transactions.end(); + j++) + if (pred_functor(*j)) + handle_transaction(*j, functor, disp_pred_functor, + related, inverted, &balance, &index); +} + +class format_account +{ + std::ostream& output_stream; + const format_t& format; + + mutable const account_t * last_account; + + public: + format_account(std::ostream& _output_stream, const format_t& _format) + : output_stream(_output_stream), format(_format) {} + + void operator()(const account_t * account, bool report_top = false); +}; + +void calc__accounts(account_t * account, + const node_t * predicate, + const bool related, + const bool inverted, + const bool calc_subtotals); + +template <typename Function> +void walk__accounts(const account_t * account, + Function functor, + const node_t * display_predicate) +{ + if (! display_predicate || item_predicate(display_predicate)(account)) + functor(account); + + for (accounts_map::const_iterator i = account->accounts.begin(); + i != account->accounts.end(); + i++) + walk__accounts((*i).second, functor, display_predicate); +} + +template <typename Function> +void walk_accounts(account_t * account, + Function functor, + const node_t * predicate, + const bool related, + const bool inverted, + const bool calc_subtotals, + const node_t * display_predicate = NULL) +{ + calc__accounts(account, predicate, related, inverted, calc_subtotals); + walk__accounts<Function>(account, functor, display_predicate); +} + +#if 0 + +void sum_entries(entries_list& entries, + account_t * account, + const bool show_subtotals) +{ + for (entries_list::const_iterator i = entries.begin(); + i != entries.end(); + i++) + for (transactions_list::const_iterator j = (*i)->transactions.begin(); + j != (*i)->transactions.end(); + j++) + if ((*j)->account == account) { + account->value += *(*j); + if (show_subtotals) + for (account_t * a = account; + a; + a = a->parent) + a->total += *(*j); + } + + for (accounts_map::iterator i = account->accounts.begin(); + i != account->accounts.end(); + i++) + sum_items(entries, *i, show_subtotals); +} + +#endif + +} // namespace ledger + +#endif // _WALK_H |