From 1741c80fe4f9dd896d4c8912b2ba35b14500ca1c Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 4 Aug 2004 03:12:26 -0400 Subject: rewrote the way registers are computed -- again --- Makefile | 46 ++-- README | 2 +- account.cc | 40 +-- amount.cc | 112 +++++++-- amount.h | 244 ++++++++++++++++++ autoxact.cc | 28 +++ autoxact.h | 79 ++++++ balance.cc | 2 - balance.h | 7 +- binary.cc | 6 +- binary.h | 2 +- datetime.cc | 125 ++++++++++ datetime.h | 19 ++ error.h | 21 +- expr.cc | 721 ----------------------------------------------------- expr.h | 183 -------------- format.cc | 138 +++++------ format.h | 32 +-- item.cc | 196 --------------- item.h | 82 ------- ledger.cc | 28 ++- ledger.h | 337 ++++--------------------- main.cc | 136 +++++----- textual.cc | 252 ++----------------- textual.h | 5 - valexpr.cc | 804 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ valexpr.h | 150 ++++++++++++ walk.h | 234 ++++++++++++++++++ 28 files changed, 2091 insertions(+), 1940 deletions(-) create mode 100644 amount.h create mode 100644 autoxact.cc create mode 100644 autoxact.h create mode 100644 datetime.cc create mode 100644 datetime.h delete mode 100644 expr.cc delete mode 100644 expr.h delete mode 100644 item.cc delete mode 100644 item.h create mode 100644 valexpr.cc create mode 100644 valexpr.h create mode 100644 walk.h diff --git a/Makefile b/Makefile index 4c57bab2..74856448 100644 --- a/Makefile +++ b/Makefile @@ -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)) diff --git a/README b/README index fc992bad..18e177bb 100644 --- a/README +++ b/README @@ -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 diff --git a/account.cc b/account.cc index df57622b..697aa609 100644 --- a/account.cc +++ b/account.cc @@ -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 diff --git a/amount.cc b/amount.cc index d1dd6150..c86177b1 100644 --- a/amount.cc +++ b/amount.cc @@ -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 +#include +#include +#include +#include + +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 history_map; +typedef std::pair history_pair; + +typedef std::map commodities_map; +typedef std::pair 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 + +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_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 diff --git a/balance.cc b/balance.cc index 5d806080..eeef3d49 100644 --- a/balance.cc +++ b/balance.cc @@ -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) {} diff --git a/balance.h b/balance.h index edc7db2f..a41bcf6f 100644 --- a/balance.h +++ b/balance.h @@ -1,7 +1,11 @@ #ifndef _BALANCE_H #define _BALANCE_H -#include "ledger.h" +#include +#include +#include + +#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 { diff --git a/binary.cc b/binary.cc index 9f2bed96..68fe9608 100644 --- a/binary.cc +++ b/binary.cc @@ -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 { diff --git a/binary.h b/binary.h index 46c7f55b..e56d2f17 100644 --- a/binary.h +++ b/binary.h @@ -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 + +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 diff --git a/error.h b/error.h index 51d152cd..4f506d70 100644 --- a/error.h +++ b/error.h @@ -1,8 +1,19 @@ #ifndef _ERROR_H #define _ERROR_H +#include "ledger.h" + #include -#include +#include + +#ifdef DEBUG +#include +#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 diff --git a/expr.cc b/expr.cc deleted file mode 100644 index c49dc33b..00000000 --- a/expr.cc +++ /dev/null @@ -1,721 +0,0 @@ -#include "expr.h" -#include "error.h" -#include "textual.h" - -#include - -namespace ledger { - -mask_t::mask_t(const std::string& pat) : exclude(false) -{ - const char * p = pat.c_str(); - if (*p == '-') { - exclude = true; - p++; - while (std::isspace(*p)) - p++; - } - else if (*p == '+') { - p++; - while (std::isspace(*p)) - p++; - } - pattern = p; - - const char *error; - int erroffset; - regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS, - &error, &erroffset, NULL); - if (! regexp) - std::cerr << "Warning: Failed to compile regexp: " << pattern - << std::endl; -} - -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); -} - -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; -} - -mask_t::~mask_t() { - pcre_free((pcre *)regexp); -} - -#if 1 - -bool matches(const masks_list& regexps, const std::string& str, - bool * by_exclusion) -{ - if (regexps.empty()) - return false; - - bool match = false; - bool definite = false; - - for (masks_list::const_iterator r = regexps.begin(); - r != regexps.end(); - r++) { - static int ovec[30]; - int result = pcre_exec((pcre *)(*r).regexp, NULL, - str.c_str(), str.length(), 0, 0, ovec, 30); - if (result >= 0) { - match = ! (*r).exclude; - definite = true; - } - else if ((*r).exclude) { - if (! match) - match = ! definite; - } - else { - definite = true; - } - } - - if (by_exclusion) - *by_exclusion = match && ! definite && by_exclusion; - - return match; -} - -#endif - -balance_t node_t::compute(const item_t * item) const -{ - balance_t temp; - - switch (type) { - case CONSTANT_A: - temp = constant_a; - break; - - case CONSTANT_T: - temp = amount_t((unsigned int) constant_t); - break; - - case AMOUNT: - temp = item->value.quantity; - break; - case COST: - temp = item->value.cost; - break; - - case BALANCE: - temp = item->total.quantity - item->value.quantity; - break; - case COST_BALANCE: - temp = item->total.cost - item->value.cost; - break; - - case TOTAL: - temp = item->total.quantity; - break; - case COST_TOTAL: - temp = item->total.cost; - break; - - case DATE: - temp = amount_t((unsigned int) item->date); - break; - - case CLEARED: - temp = amount_t(item->state == entry_t::CLEARED ? 1 : 0); - break; - - case REAL: - temp = amount_t(item->flags & TRANSACTION_VIRTUAL ? 0 : 1); - break; - - case INDEX: - temp = amount_t(item->index + 1); - break; - - case F_ARITH_MEAN: - assert(left); - temp = left->compute(item); - temp /= amount_t(item->index + 1); - break; - - case F_NEG: - assert(left); - temp = left->compute(item).negated(); - break; - - case F_ABS: - assert(left); - temp = abs(left->compute(item)); - break; - - case F_PAYEE_MASK: - assert(mask); - temp = (mask->match(item->payee) || mask->match(item->note)) ? 1 : 0; - break; - - case F_ACCOUNT_MASK: - assert(mask); - temp = (item->account && - mask->match(item->account->fullname())) ? 1 : 0; - break; - - case F_VALUE: { - assert(left); - temp = left->compute(item); - - std::time_t moment = -1; - if (right) { - switch (right->type) { - case DATE: moment = item->date; break; - default: - throw compute_error("Invalid date passed to P(v,d)"); - } - } - temp = temp.value(moment); - break; - } - - case O_NOT: - temp = left->compute(item) ? 0 : 1; - break; - - case O_QUES: - temp = left->compute(item); - if (temp) - temp = right->left->compute(item); - else - temp = right->right->compute(item); - break; - - case O_AND: - case O_OR: - case O_EQ: - case O_LT: - case O_LTE: - case O_GT: - case O_GTE: - 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); - 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; - default: assert(0); break; - } - break; - } - - case LAST: - default: - assert(0); - break; - } - - return temp; -} - -node_t * parse_term(std::istream& in); - -inline node_t * parse_term(const char * p) { - std::istringstream stream(p); - return parse_term(stream); -} - -node_t * parse_term(std::istream& in) -{ - node_t * node = NULL; - - char c = in.peek(); - if (std::isdigit(c) || c == '.' || c == '{') { - std::string ident; - - if (c == '{') { - in.get(c); - c = in.peek(); - while (! in.eof() && c != '}') { - in.get(c); - ident += c; - c = in.peek(); - } - if (c == '}') - in.get(c); - else - throw expr_error("Missing '}'"); - } else { - while (! in.eof() && std::isdigit(c) || c == '.') { - in.get(c); - ident += c; - c = in.peek(); - } - } - - if (! ident.empty()) { - node = new node_t(node_t::CONSTANT_A); - node->constant_a.parse(ident); - } - return node; - } - - in.get(c); - switch (c) { - // Basic terms - case 'a': node = new node_t(node_t::AMOUNT); break; - case 'c': node = new node_t(node_t::COST); break; - case 'd': node = new node_t(node_t::DATE); break; - case 'X': node = new node_t(node_t::CLEARED); break; - case 'R': node = new node_t(node_t::REAL); break; - case 'i': node = new node_t(node_t::INDEX); break; - case 'B': node = new node_t(node_t::BALANCE); break; - case 'T': node = new node_t(node_t::TOTAL); break; - case 'C': node = new node_t(node_t::COST_TOTAL); break; - - // Compound terms - case 'v': node = parse_expr("P(a,d)"); break; - case 'V': node = parse_term("P(T,d)"); break; - case 'g': node = parse_expr("v-c"); break; - case 'G': node = parse_expr("V-C"); break; - case 'o': node = parse_expr("d-b"); break; - case 'w': node = parse_expr("e-d"); break; - - // Functions - case '-': - node = new node_t(node_t::F_NEG); - node->left = parse_term(in); - break; - - case 'A': - node = new node_t(node_t::F_ABS); - node->left = parse_term(in); - break; - - case 'M': - node = new node_t(node_t::F_ARITH_MEAN); - node->left = parse_term(in); - break; - - case 'D': { - node = new node_t(node_t::O_SUB); - node->left = parse_term("a"); - node->right = parse_term(in); - break; - } - - case 'P': - node = new node_t(node_t::F_VALUE); - if (in.peek() == '(') { - in.get(c); - node->left = parse_expr(in); - if (in.peek() == ',') { - in.get(c); - node->right = parse_expr(in); - } - if (in.peek() == ')') - in.get(c); - else - throw expr_error("Missing ')'"); - } else { - node->left = parse_term(in); - } - break; - - // Other - case '/': { - std::string ident; - bool payee_mask = false; - - c = in.peek(); - if (c == '/') { - payee_mask = true; - in.get(c); - c = in.peek(); - } - - while (! in.eof() && c != '/') { - in.get(c); - if (c == '\\') - in.get(c); - ident += c; - c = in.peek(); - } - - if (c == '/') { - in.get(c); - node = new node_t(payee_mask ? - node_t::F_PAYEE_MASK : node_t::F_ACCOUNT_MASK); - node->mask = new mask_t(ident); - } else { - throw expr_error("Missing closing '/'"); - } - break; - } - - case '(': - node = parse_expr(in); - if (in.peek() == ')') - in.get(c); - else - throw expr_error("Missing ')'"); - break; - - case '[': { - std::string ident; - - c = in.peek(); - while (! in.eof() && c != ']') { - in.get(c); - ident += c; - c = in.peek(); - } - if (c == ']') { - in.get(c); - node = new node_t(node_t::CONSTANT_T); - if (! parse_date(ident.c_str(), &node->constant_t)) - throw expr_error("Failed to parse date"); - } else { - throw expr_error("Missing ']'"); - } - break; - } - - default: - in.unget(); - break; - } - - return node; -} - -node_t * parse_mul_expr(std::istream& in) -{ - node_t * node = NULL; - - node = parse_term(in); - - if (node && ! in.eof()) { - char c = in.peek(); - while (c == '*' || c == '/') { - in.get(c); - switch (c) { - case '*': { - node_t * prev = node; - node = new node_t(node_t::O_MUL); - node->left = prev; - node->right = parse_term(in); - break; - } - - case '/': { - node_t * prev = node; - node = new node_t(node_t::O_DIV); - node->left = prev; - node->right = parse_term(in); - break; - } - } - c = in.peek(); - } - } - - return node; -} - -node_t * parse_add_expr(std::istream& in) -{ - node_t * node = NULL; - - node = parse_mul_expr(in); - - if (node && ! in.eof()) { - char c = in.peek(); - while (c == '+' || c == '-') { - in.get(c); - switch (c) { - case '+': { - node_t * prev = node; - node = new node_t(node_t::O_ADD); - node->left = prev; - node->right = parse_mul_expr(in); - break; - } - - case '-': { - node_t * prev = node; - node = new node_t(node_t::O_SUB); - node->left = prev; - node->right = parse_mul_expr(in); - break; - } - } - c = in.peek(); - } - } - - return node; -} - -node_t * parse_logic_expr(std::istream& in) -{ - node_t * node = NULL; - - if (in.peek() == '!') { - char c; - in.get(c); - node = new node_t(node_t::O_NOT); - node->left = parse_logic_expr(in); - return node; - } - - node = parse_add_expr(in); - - if (node && ! in.eof()) { - char c = in.peek(); - if (c == '=' || c == '<' || c == '>') { - in.get(c); - switch (c) { - case '=': { - node_t * prev = node; - node = new node_t(node_t::O_EQ); - node->left = prev; - node->right = parse_add_expr(in); - break; - } - - case '<': { - node_t * prev = node; - node = new node_t(node_t::O_LT); - if (in.peek() == '=') { - in.get(c); - node->type = node_t::O_LTE; - } - node->left = prev; - node->right = parse_add_expr(in); - break; - } - - case '>': { - node_t * prev = node; - node = new node_t(node_t::O_GT); - if (in.peek() == '=') { - in.get(c); - node->type = node_t::O_GTE; - } - node->left = prev; - node->right = parse_add_expr(in); - break; - } - - default: - if (! in.eof()) { - std::ostringstream err; - err << "Unexpected character '" << c << "'"; - throw expr_error(err.str()); - } - } - } - } - - return node; -} - -node_t * parse_expr(std::istream& in) -{ - node_t * node = NULL; - - node = parse_logic_expr(in); - - if (node && ! in.eof()) { - char c = in.peek(); - while (c == '&' || c == '|' || c == '?') { - in.get(c); - switch (c) { - case '&': { - node_t * prev = node; - node = new node_t(node_t::O_AND); - node->left = prev; - node->right = parse_logic_expr(in); - break; - } - - case '|': { - node_t * prev = node; - node = new node_t(node_t::O_OR); - node->left = prev; - node->right = parse_logic_expr(in); - break; - } - - case '?': { - node_t * prev = node; - node = new node_t(node_t::O_QUES); - node->left = prev; - node_t * choices = new node_t(node_t::O_COL); - node->right = choices; - choices->left = parse_logic_expr(in); - c = in.peek(); - if (c != ':') { - std::ostringstream err; - err << "Unexpected character '" << c << "'"; - throw expr_error(err.str()); - } - in.get(c); - choices->right = parse_logic_expr(in); - break; - } - - default: - if (! in.eof()) { - std::ostringstream err; - err << "Unexpected character '" << c << "'"; - throw expr_error(err.str()); - } - } - c = in.peek(); - } - } - - return node; -} - -} // namespace ledger - - -#ifdef TEST - -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::AMOUNT: out << "AMOUNT"; break; - case node_t::COST: out << "COST"; break; - case node_t::DATE: out << "DATE"; break; - case node_t::CLEARED: out << "CLEARED"; break; - case node_t::REAL: out << "REAL"; break; - case node_t::INDEX: out << "INDEX"; break; - case node_t::BALANCE: out << "BALANCE"; break; - case node_t::COST_BALANCE: out << "COST_BALANCE"; break; - case node_t::TOTAL: out << "TOTAL"; break; - case node_t::COST_TOTAL: out << "COST_TOTAL"; break; - - case node_t::F_ARITH_MEAN: - out << "MEAN("; - dump_tree(out, node->left); - out << ")"; - break; - - case node_t::F_NEG: - out << "ABS("; - dump_tree(out, node->left); - out << ")"; - break; - - case node_t::F_ABS: - out << "ABS("; - dump_tree(out, node->left); - out << ")"; - break; - - case node_t::F_PAYEE_MASK: - assert(node->mask); - out << "P_MASK(" << node->mask->pattern << ")"; - break; - - case node_t::F_ACCOUNT_MASK: - assert(node->mask); - out << "A_MASK(" << node->mask->pattern << ")"; - break; - - case node_t::F_VALUE: - out << "VALUE("; - dump_tree(out, node->left); - if (node->right) { - out << ", "; - dump_tree(out, node->right); - } - out << ")"; - break; - - case node_t::O_NOT: - out << "!"; - dump_tree(out, node->left); - break; - - case node_t::O_QUES: - dump_tree(out, node->left); - out << "?"; - dump_tree(out, node->right->left); - out << ":"; - dump_tree(out, node->right->right); - break; - - case node_t::O_AND: - case node_t::O_OR: - 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; - case node_t::O_ADD: out << "+"; break; - case node_t::O_SUB: out << "-"; break; - case node_t::O_MUL: out << "*"; break; - case node_t::O_DIV: out << "/"; break; - default: assert(0); break; - } - dump_tree(out, node->right); - out << ")"; - break; - - case node_t::LAST: - default: - assert(0); - break; - } -} - -} // namespace ledger - -int main(int argc, char *argv[]) -{ - ledger::dump_tree(std::cout, ledger::parse_expr(argv[1], NULL)); - std::cout << std::endl; -} - -#endif // TEST diff --git a/expr.h b/expr.h deleted file mode 100644 index 937e2fa8..00000000 --- a/expr.h +++ /dev/null @@ -1,183 +0,0 @@ -#ifndef _EXPR_H -#define _EXPR_H - -#include "ledger.h" -#include "balance.h" -#include "item.h" - -namespace ledger { - -class mask_t -{ - public: - bool exclude; - std::string pattern; - void * regexp; - - explicit mask_t(const std::string& pattern); - mask_t(const mask_t&); - - ~mask_t(); - - bool match(const std::string& str) const; -}; - -#if 1 -typedef std::list masks_list; - -bool matches(const masks_list& regexps, const std::string& str, - bool * by_exclusion = NULL); -#endif - - -struct node_t -{ - enum kind_t { - // Constants - CONSTANT_A, - CONSTANT_T, - - // Item details - AMOUNT, - COST, - DATE, - CLEARED, - REAL, - INDEX, - - // Item totals - BALANCE, - COST_BALANCE, - TOTAL, - COST_TOTAL, - - // Functions - F_ARITH_MEAN, - F_VALUE, - F_NEG, - F_ABS, - F_PAYEE_MASK, - F_ACCOUNT_MASK, - - // Binary operators - O_ADD, - O_SUB, - O_MUL, - O_DIV, - O_EQ, - O_LT, - O_LTE, - O_GT, - O_GTE, - O_NOT, - O_AND, - O_OR, - O_QUES, - O_COL, - - LAST - }; - - kind_t type; - node_t * left; - node_t * right; - - amount_t constant_a; - std::time_t constant_t; - mask_t * mask; - - node_t(const kind_t _type) - : type(_type), left(NULL), right(NULL), mask(NULL) {} - - ~node_t() { - if (mask) delete mask; - if (left) delete left; - if (right) delete right; - } - - balance_t compute(const item_t * item) const; -}; - -node_t * parse_expr(std::istream& in); - -inline node_t * parse_expr(const char * p) { - std::istringstream stream(p); - return parse_expr(stream); -} - -inline node_t * parse_expr(const std::string& str) { - return parse_expr(str.c_str()); -} - -inline node_t * find_node(node_t * node, node_t::kind_t type) { - node_t * result = NULL; - if (node->type == type) - result = node; - if (! result && node->left) - result = find_node(node->left, type); - if (! result && node->right) - result = find_node(node->right, 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 diff --git a/format.cc b/format.cc index 33adc15c..189d84ff 100644 --- a/format.cc +++ b/format.cc @@ -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); diff --git a/format.h b/format.h index f2bdad10..661e7884 100644 --- a/format.h +++ b/format.h @@ -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 elements; - static std::auto_ptr value_expr; - static std::auto_ptr total_expr; + static std::auto_ptr value_expr; + static std::auto_ptr 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 - 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 - -namespace ledger { - -struct node_t; -struct item_t; -typedef std::deque 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 diff --git a/ledger.cc b/ledger.cc index 25ac845f..fd400654 100644 --- a/ledger.cc +++ b/ledger.cc @@ -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); diff --git a/ledger.h b/ledger.h index 5d9d0283..a1e2e7ae 100644 --- a/ledger.h +++ b/ledger.h @@ -14,291 +14,32 @@ #include #include #include -#include #include -#include -#ifdef DEBUG -#include -#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 history_map; -typedef std::pair history_pair; - -typedef std::map commodities_map; -typedef std::pair 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 accounts_map; typedef std::pair 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 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 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(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 diff --git a/main.cc b/main.cc index c9cfdd66..9234e629 100644 --- a/main.cc +++ b/main.cc @@ -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 #include @@ -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 top; - std::auto_ptr 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 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 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 - format(new ledger::format_t(first_line_format)); - std::auto_ptr - 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 + format(new ledger::format_t(first_line_format)); + std::auto_ptr + 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 diff --git a/textual.cc b/textual.cc index e262d864..2e7ef86f 100644 --- a/textual.cc +++ b/textual.cc @@ -1,8 +1,9 @@ #include "textual.h" +#include "datetime.h" +#include "autoxact.h" +#include "valexpr.h" #include "error.h" -#include "expr.h" -#include #include #include #include @@ -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_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; diff --git a/textual.h b/textual.h index 55d7da97..8ae24b83 100644 --- a/textual.h +++ b/textual.h @@ -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 diff --git a/valexpr.cc b/valexpr.cc new file mode 100644 index 00000000..889c57c8 --- /dev/null +++ b/valexpr.cc @@ -0,0 +1,804 @@ +#include "valexpr.h" +#include "error.h" +#include "datetime.h" +#include "textual.h" + +#include + +namespace ledger { + +mask_t::mask_t(const std::string& pat) : exclude(false) +{ + const char * p = pat.c_str(); + if (*p == '-') { + exclude = true; + p++; + while (std::isspace(*p)) + p++; + } + else if (*p == '+') { + p++; + while (std::isspace(*p)) + p++; + } + pattern = p; + + const char *error; + int erroffset; + regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS, + &error, &erroffset, NULL); + if (! regexp) + std::cerr << "Warning: Failed to compile regexp: " << pattern + << std::endl; +} + +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); +} + +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; +} + +mask_t::~mask_t() { + pcre_free((pcre *)regexp); +} + +#if 1 + +bool matches(const masks_list& regexps, const std::string& str, + bool * by_exclusion) +{ + if (regexps.empty()) + return false; + + bool match = false; + bool definite = false; + + for (masks_list::const_iterator r = regexps.begin(); + r != regexps.end(); + r++) { + static int ovec[30]; + int result = pcre_exec((pcre *)(*r).regexp, NULL, + str.c_str(), str.length(), 0, 0, ovec, 30); + if (result >= 0) { + match = ! (*r).exclude; + definite = true; + } + else if ((*r).exclude) { + if (! match) + match = ! definite; + } + else { + definite = true; + } + } + + if (by_exclusion) + *by_exclusion = match && ! definite && by_exclusion; + + return match; +} + +#endif + +void node_t::compute(balance_t& result, const details_t& details) const +{ + switch (type) { + case CONSTANT_A: + result = constant_a; + break; + + case CONSTANT_T: + result = (unsigned int) constant_t; + break; + + case AMOUNT: + if (details.xact) + result = details.xact->amount; + else if (details.account) + result = details.account->value.quantity; + break; + + case COST: + if (details.xact) + result = details.xact->cost; + else if (details.account) + result = details.account->value.cost; + break; + + case BALANCE: + 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: + 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: + if (details.balance) + result = details.balance->quantity; + else if (details.account) + result = details.account->total.quantity; + break; + case COST_TOTAL: + if (details.balance) + result = details.balance->cost; + else if (details.account) + result = details.account->total.cost; + break; + + case DATE: + if (details.entry) + result = (unsigned int) details.entry->date; + break; + + case CLEARED: + if (details.entry) + result = details.entry->state == entry_t::CLEARED; + break; + + case REAL: + if (details.xact) + result = bool(details.xact->flags & TRANSACTION_VIRTUAL); + break; + + case INDEX: + if (details.index) + result = *details.index + 1; + break; + + case F_ARITH_MEAN: + if (details.index) { + assert(left); + left->compute(result, details); + result /= amount_t(*details.index + 1); + } + break; + + case F_NEG: + assert(left); + left->compute(result, details); + result.negate(); + break; + + case F_ABS: + assert(left); + left->compute(result, details); + result = abs(result); + break; + + case F_PAYEE_MASK: + assert(mask); + if (details.entry) + result = mask->match(details.entry->payee); + break; + + case F_ACCOUNT_MASK: + assert(mask); + if (details.account) + result = mask->match(details.account->fullname()); + break; + + case F_VALUE: { + assert(left); + left->compute(result, details); + + std::time_t moment = -1; + if (right && details.entry) { + switch (right->type) { + case DATE: moment = details.entry->date; break; + default: + throw compute_error("Invalid date passed to P(v,d)"); + } + } + result = result.value(moment); + break; + } + + case O_NOT: + left->compute(result, details); + result = result ? false : true; + break; + + case O_QUES: + assert(left); + assert(right); + assert(right->type == O_COL); + left->compute(result, details); + if (result) + right->left->compute(result, details); + else + 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: { + 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); + right->compute(result, details); + balance_t temp = result; + left->compute(result, details); + switch (type) { + 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; + } + + case LAST: + default: + assert(0); + break; + } +} + +node_t * parse_term(std::istream& in); + +inline node_t * parse_term(const char * p) { + std::istringstream stream(p); + return parse_term(stream); +} + +node_t * parse_term(std::istream& in) +{ + node_t * node = NULL; + + char c = in.peek(); + if (std::isdigit(c) || c == '.' || c == '{') { + std::string ident; + + if (c == '{') { + in.get(c); + c = in.peek(); + while (! in.eof() && c != '}') { + in.get(c); + ident += c; + c = in.peek(); + } + if (c == '}') + in.get(c); + else + throw expr_error("Missing '}'"); + } else { + while (! in.eof() && std::isdigit(c) || c == '.') { + in.get(c); + ident += c; + c = in.peek(); + } + } + + if (! ident.empty()) { + node = new node_t(node_t::CONSTANT_A); + node->constant_a.parse(ident); + } + return node; + } + + in.get(c); + switch (c) { + // Basic terms + case 'a': node = new node_t(node_t::AMOUNT); break; + case 'c': node = new node_t(node_t::COST); break; + case 'd': node = new node_t(node_t::DATE); break; + case 'X': node = new node_t(node_t::CLEARED); break; + case 'R': node = new node_t(node_t::REAL); break; + case 'i': node = new node_t(node_t::INDEX); break; + case 'B': node = new node_t(node_t::BALANCE); break; + case 'T': node = new node_t(node_t::TOTAL); break; + case 'C': node = new node_t(node_t::COST_TOTAL); break; + + // Compound terms + case 'v': node = parse_expr("P(a,d)"); break; + case 'V': node = parse_term("P(T,d)"); break; + case 'g': node = parse_expr("v-c"); break; + case 'G': node = parse_expr("V-C"); break; + case 'o': node = parse_expr("d-b"); break; + case 'w': node = parse_expr("e-d"); break; + + // Functions + case '-': + node = new node_t(node_t::F_NEG); + node->left = parse_term(in); + break; + + case 'A': + node = new node_t(node_t::F_ABS); + node->left = parse_term(in); + break; + + case 'M': + node = new node_t(node_t::F_ARITH_MEAN); + node->left = parse_term(in); + break; + + case 'D': { + node = new node_t(node_t::O_SUB); + node->left = parse_term("a"); + node->right = parse_term(in); + break; + } + + case 'P': + node = new node_t(node_t::F_VALUE); + if (in.peek() == '(') { + in.get(c); + node->left = parse_expr(in); + if (in.peek() == ',') { + in.get(c); + node->right = parse_expr(in); + } + if (in.peek() == ')') + in.get(c); + else + throw expr_error("Missing ')'"); + } else { + node->left = parse_term(in); + } + break; + + // Other + case '/': { + std::string ident; + bool payee_mask = false; + + c = in.peek(); + if (c == '/') { + payee_mask = true; + in.get(c); + c = in.peek(); + } + + while (! in.eof() && c != '/') { + in.get(c); + if (c == '\\') + in.get(c); + ident += c; + c = in.peek(); + } + + if (c == '/') { + in.get(c); + node = new node_t(payee_mask ? + node_t::F_PAYEE_MASK : node_t::F_ACCOUNT_MASK); + node->mask = new mask_t(ident); + } else { + throw expr_error("Missing closing '/'"); + } + break; + } + + case '(': + node = parse_expr(in); + if (in.peek() == ')') + in.get(c); + else + throw expr_error("Missing ')'"); + break; + + case '[': { + std::string ident; + + c = in.peek(); + while (! in.eof() && c != ']') { + in.get(c); + ident += c; + c = in.peek(); + } + if (c == ']') { + in.get(c); + node = new node_t(node_t::CONSTANT_T); + if (! parse_date(ident.c_str(), &node->constant_t)) + throw expr_error("Failed to parse date"); + } else { + throw expr_error("Missing ']'"); + } + break; + } + + default: + in.unget(); + break; + } + + return node; +} + +node_t * parse_mul_expr(std::istream& in) +{ + node_t * node = NULL; + + node = parse_term(in); + + if (node && ! in.eof()) { + char c = in.peek(); + while (c == '*' || c == '/') { + in.get(c); + switch (c) { + case '*': { + node_t * prev = node; + node = new node_t(node_t::O_MUL); + node->left = prev; + node->right = parse_term(in); + break; + } + + case '/': { + node_t * prev = node; + node = new node_t(node_t::O_DIV); + node->left = prev; + node->right = parse_term(in); + break; + } + } + c = in.peek(); + } + } + + return node; +} + +node_t * parse_add_expr(std::istream& in) +{ + node_t * node = NULL; + + node = parse_mul_expr(in); + + if (node && ! in.eof()) { + char c = in.peek(); + while (c == '+' || c == '-') { + in.get(c); + switch (c) { + case '+': { + node_t * prev = node; + node = new node_t(node_t::O_ADD); + node->left = prev; + node->right = parse_mul_expr(in); + break; + } + + case '-': { + node_t * prev = node; + node = new node_t(node_t::O_SUB); + node->left = prev; + node->right = parse_mul_expr(in); + break; + } + } + c = in.peek(); + } + } + + return node; +} + +node_t * parse_logic_expr(std::istream& in) +{ + node_t * node = NULL; + + if (in.peek() == '!') { + char c; + in.get(c); + node = new node_t(node_t::O_NOT); + node->left = parse_logic_expr(in); + return node; + } + + node = parse_add_expr(in); + + if (node && ! in.eof()) { + char c = in.peek(); + if (c == '=' || c == '<' || c == '>') { + in.get(c); + switch (c) { + case '=': { + node_t * prev = node; + node = new node_t(node_t::O_EQ); + node->left = prev; + node->right = parse_add_expr(in); + break; + } + + case '<': { + node_t * prev = node; + node = new node_t(node_t::O_LT); + if (in.peek() == '=') { + in.get(c); + node->type = node_t::O_LTE; + } + node->left = prev; + node->right = parse_add_expr(in); + break; + } + + case '>': { + node_t * prev = node; + node = new node_t(node_t::O_GT); + if (in.peek() == '=') { + in.get(c); + node->type = node_t::O_GTE; + } + node->left = prev; + node->right = parse_add_expr(in); + break; + } + + default: + if (! in.eof()) { + std::ostringstream err; + err << "Unexpected character '" << c << "'"; + throw expr_error(err.str()); + } + } + } + } + + return node; +} + +node_t * parse_expr(std::istream& in) +{ + node_t * node = NULL; + + node = parse_logic_expr(in); + + if (node && ! in.eof()) { + char c = in.peek(); + while (c == '&' || c == '|' || c == '?') { + in.get(c); + switch (c) { + case '&': { + node_t * prev = node; + node = new node_t(node_t::O_AND); + node->left = prev; + node->right = parse_logic_expr(in); + break; + } + + case '|': { + node_t * prev = node; + node = new node_t(node_t::O_OR); + node->left = prev; + node->right = parse_logic_expr(in); + break; + } + + case '?': { + node_t * prev = node; + node = new node_t(node_t::O_QUES); + node->left = prev; + node_t * choices = new node_t(node_t::O_COL); + node->right = choices; + choices->left = parse_logic_expr(in); + c = in.peek(); + if (c != ':') { + std::ostringstream err; + err << "Unexpected character '" << c << "'"; + throw expr_error(err.str()); + } + in.get(c); + choices->right = parse_logic_expr(in); + break; + } + + default: + if (! in.eof()) { + std::ostringstream err; + err << "Unexpected character '" << c << "'"; + throw expr_error(err.str()); + } + } + c = in.peek(); + } + } + + return node; +} + +} // namespace ledger + + +#ifdef TEST + +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::AMOUNT: out << "AMOUNT"; break; + case node_t::COST: out << "COST"; break; + case node_t::DATE: out << "DATE"; break; + case node_t::CLEARED: out << "CLEARED"; break; + case node_t::REAL: out << "REAL"; break; + case node_t::INDEX: out << "INDEX"; break; + case node_t::BALANCE: out << "BALANCE"; break; + case node_t::COST_BALANCE: out << "COST_BALANCE"; break; + case node_t::TOTAL: out << "TOTAL"; break; + case node_t::COST_TOTAL: out << "COST_TOTAL"; break; + + case node_t::F_ARITH_MEAN: + out << "MEAN("; + dump_tree(out, node->left); + out << ")"; + break; + + case node_t::F_NEG: + out << "ABS("; + dump_tree(out, node->left); + out << ")"; + break; + + case node_t::F_ABS: + out << "ABS("; + dump_tree(out, node->left); + out << ")"; + break; + + case node_t::F_PAYEE_MASK: + assert(node->mask); + out << "P_MASK(" << node->mask->pattern << ")"; + break; + + case node_t::F_ACCOUNT_MASK: + assert(node->mask); + out << "A_MASK(" << node->mask->pattern << ")"; + break; + + case node_t::F_VALUE: + out << "VALUE("; + dump_tree(out, node->left); + if (node->right) { + out << ", "; + dump_tree(out, node->right); + } + out << ")"; + break; + + case node_t::O_NOT: + out << "!"; + dump_tree(out, node->left); + break; + + case node_t::O_QUES: + dump_tree(out, node->left); + out << "?"; + dump_tree(out, node->right->left); + out << ":"; + dump_tree(out, node->right->right); + break; + + 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: + out << "("; + dump_tree(out, node->left); + switch (node->type) { + 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; + case node_t::O_DIV: out << "/"; break; + default: assert(0); break; + } + dump_tree(out, node->right); + out << ")"; + break; + + case node_t::LAST: + default: + assert(0); + break; + } +} + +} // namespace ledger + +int main(int argc, char *argv[]) +{ + ledger::dump_tree(std::cout, ledger::parse_expr(argv[1])); + std::cout << std::endl; +} + +#endif // TEST diff --git a/valexpr.h b/valexpr.h new file mode 100644 index 00000000..0d4413e7 --- /dev/null +++ b/valexpr.h @@ -0,0 +1,150 @@ +#ifndef _EXPR_H +#define _EXPR_H + +#include "ledger.h" + +namespace ledger { + +class mask_t +{ + public: + bool exclude; + std::string pattern; + void * regexp; + + explicit mask_t(const std::string& pattern); + mask_t(const mask_t&); + + ~mask_t(); + + bool match(const std::string& str) const; +}; + +#if 1 +typedef std::list masks_list; + +bool matches(const masks_list& regexps, const std::string& str, + bool * by_exclusion = NULL); +#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 { + // Constants + CONSTANT_A, + CONSTANT_T, + + // Item details + AMOUNT, + COST, + DATE, + CLEARED, + REAL, + INDEX, + + // Item totals + BALANCE, + COST_BALANCE, + TOTAL, + COST_TOTAL, + + // Functions + F_ARITH_MEAN, + F_VALUE, + F_NEG, + F_ABS, + F_PAYEE_MASK, + F_ACCOUNT_MASK, + + // Binary operators + O_ADD, + O_SUB, + O_MUL, + O_DIV, + O_EQ, + O_LT, + O_LTE, + O_GT, + O_GTE, + O_NOT, + O_AND, + O_OR, + O_QUES, + O_COL, + + LAST + }; + + kind_t type; + node_t * left; + node_t * right; + + amount_t constant_a; + std::time_t constant_t; + mask_t * mask; + + node_t(const kind_t _type) + : type(_type), left(NULL), right(NULL), mask(NULL) {} + + ~node_t() { + if (mask) delete mask; + if (left) delete left; + if (right) delete right; + } + + void compute(balance_t& result, const details_t& details) const; +}; + +node_t * parse_expr(std::istream& in); + +inline node_t * parse_expr(const char * p) { + std::istringstream stream(p); + return parse_expr(stream); +} + +inline node_t * parse_expr(const std::string& str) { + return parse_expr(str.c_str()); +} + +inline node_t * find_node(node_t * node, node_t::kind_t type) { + node_t * result = NULL; + if (node->type == type) + result = node; + if (! result && node->left) + result = find_node(node->left, type); + if (! result && node->right) + result = find_node(node->right, type); + return result; +} + +} // namespace report + +#endif // _REPORT_H diff --git a/walk.h b/walk.h new file mode 100644 index 00000000..b509d1dc --- /dev/null +++ b/walk.h @@ -0,0 +1,234 @@ +#ifndef _WALK_H +#define _WALK_H + +#include "ledger.h" +#include "balance.h" +#include "format.h" +#include "valexpr.h" + +#include + +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 +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 +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 +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 +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(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 -- cgit v1.2.3