diff options
-rw-r--r-- | Makefile.am | 69 | ||||
-rw-r--r-- | amount.cc | 3 | ||||
-rw-r--r-- | binary.cc | 633 | ||||
-rw-r--r-- | binary.h | 20 | ||||
-rw-r--r-- | commodity.cc | 5 | ||||
-rw-r--r-- | csv.cc | 12 | ||||
-rw-r--r-- | expr.cc | 159 | ||||
-rw-r--r-- | expr.h | 114 | ||||
-rw-r--r-- | format.cc | 28 | ||||
-rw-r--r-- | format.h | 6 | ||||
-rw-r--r-- | gnucash.h | 4 | ||||
-rw-r--r-- | journal.h | 61 | ||||
-rw-r--r-- | ledger.h | 12 | ||||
-rw-r--r-- | main.cc | 28 | ||||
-rw-r--r-- | ofx.h | 4 | ||||
-rw-r--r-- | op.cc | 1151 | ||||
-rw-r--r-- | op.h | 324 | ||||
-rw-r--r-- | option.cc | 24 | ||||
-rw-r--r-- | option.h | 9 | ||||
-rw-r--r-- | parser.cc | 445 | ||||
-rw-r--r-- | parser.h | 201 | ||||
-rw-r--r-- | predicate.h | 74 | ||||
-rw-r--r-- | pyinterp.cc | 16 | ||||
-rw-r--r-- | pyinterp.h | 14 | ||||
-rw-r--r-- | qif.h | 4 | ||||
-rw-r--r-- | report.cc | 12 | ||||
-rw-r--r-- | report.h | 48 | ||||
-rw-r--r-- | scope.cc | 136 | ||||
-rw-r--r-- | scope.h | 374 | ||||
-rw-r--r-- | session.cc | 26 | ||||
-rw-r--r-- | session.h | 41 | ||||
-rw-r--r-- | test/numerics/t_expr.cc | 25 | ||||
-rw-r--r-- | test/numerics/t_expr.h | 30 | ||||
-rw-r--r-- | textual.cc | 40 | ||||
-rw-r--r-- | textual.h | 4 | ||||
-rw-r--r-- | token.cc | 398 | ||||
-rw-r--r-- | token.h | 119 | ||||
-rw-r--r-- | utils.h | 74 | ||||
-rw-r--r-- | walk.cc | 14 | ||||
-rw-r--r-- | walk.h | 26 | ||||
-rw-r--r-- | xml.h | 4 |
41 files changed, 4082 insertions, 709 deletions
diff --git a/Makefile.am b/Makefile.am index beaea2f9..e1aaa45c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,23 +38,28 @@ endif libledger_la_CPPFLAGS = $(libamounts_la_CPPFLAGS) libledger_la_SOURCES = \ - binary.cc \ - csv.cc \ - derive.cc \ - emacs.cc \ + mask.cc \ + token.cc \ + parser.cc \ + op.cc \ + expr.cc \ + scope.cc \ format.cc \ + \ journal.cc \ - mask.cc \ - option.cc \ - parsexp.cc \ - qif.cc \ - reconcile.cc \ report.cc \ session.cc \ - textual.cc \ - valexpr.cc \ walk.cc \ - xml.cc + \ + textual.cc \ + binary.cc \ + csv.cc \ + emacs.cc \ + qif.cc \ + xml.cc \ + derive.cc \ + reconcile.cc \ + option.cc # quotes.cc this is currently not being included if HAVE_EXPAT libledger_la_SOURCES += gnucash.cc @@ -78,28 +83,36 @@ pkginclude_HEADERS = \ value.h \ times.h \ utils.h \ + error.h \ + \ + expr.h \ + op.h \ + parser.h \ + token.h \ + scope.h \ + predicate.h \ + format.h \ + mask.h \ \ + journal.h \ + report.h \ + session.h \ + walk.h \ + \ + textual.h \ binary.h \ csv.h \ - derive.h \ emacs.h \ - error.h \ - format.h \ gnucash.h \ - journal.h \ - ledger.h \ - mask.h \ - option.h \ - parser.h \ - parsexp.h \ qif.h \ - quotes.h \ + xml.h \ + \ + derive.h \ reconcile.h \ - report.h \ - textual.h \ - valexpr.h \ - walk.h \ - xml.h + quotes.h \ + option.h \ + \ + ledger.h if USE_PCH nodist_libledger_la_SOURCES = system.hh.gch @@ -173,7 +186,7 @@ nodist_UnitTests_SOURCES = test/UnitTests.cc \ test/numerics/t_commodity.cc \ test/numerics/t_amount.cc \ test/numerics/t_balance.cc \ - test/numerics/t_valexpr.cc + test/numerics/t_expr.cc UnitTests_CPPFLAGS = -I$(srcdir)/test $(libledger_la_CPPFLAGS) UnitTests_LDFLAGS = $(LIBADD_DL) @@ -42,7 +42,6 @@ */ #include "amount.h" -#include "parser.h" #include "binary.h" namespace ledger { @@ -1245,7 +1244,7 @@ void amount_t::print(std::ostream& _out, bool omit_commodity, #if 0 -// jww (2008-05-08): Should these be global? +// jww (2008-07-29): Should these be static? namespace { #endif char * bigints; @@ -31,6 +31,7 @@ #include "binary.h" #include "journal.h" +#include "session.h" namespace ledger { @@ -58,7 +59,7 @@ extern char * bigints_next; extern unsigned int bigints_index; extern unsigned int bigints_count; -bool binary_parser_t::test(std::istream& in) const +bool journal_t::binary_parser_t::test(std::istream& in) const { if (binary::read_number_nocheck<unsigned long>(in) == binary_magic_number && binary::read_number_nocheck<unsigned long>(in) == format_version) @@ -76,14 +77,13 @@ namespace binary { account_t * master); } -unsigned int binary_parser_t::parse(std::istream& in, - session_t& session, - journal_t& journal, - account_t * master, - const path * original_file) +unsigned int journal_t::binary_parser_t::parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master, + const path * original_file) { - return binary::read_journal(in, original_file ? *original_file : "", - journal, master); + return journal.read(in, original_file ? *original_file : "", master); } namespace binary { @@ -281,67 +281,17 @@ inline void read_value(const char *& data, value_t& val) } } -inline void read_mask(const char *& data, mask_t *& mask) +inline void read_mask(const char *& data, mask_t& mask) { bool exclude; read_number(data, exclude); string pattern; read_string(data, pattern); - mask = new mask_t(pattern); - mask->exclude = exclude; + mask = mask_t(pattern); + mask.exclude = exclude; } -inline expr::ptr_op_t read_value_expr(const char *& data) -{ - if (! read_bool(data)) - return expr::ptr_op_t(); - - expr::op_t::kind_t kind; - read_number(data, kind); - - expr::ptr_op_t expr = new expr::op_t(kind); - - if (kind > expr::op_t::TERMINALS) - expr->set_left(read_value_expr(data)); - - switch (expr->kind) { - case expr::op_t::O_ARG: - case expr::op_t::INDEX: { - long temp; - read_long(data, temp); - expr->set_long(temp); - break; - } - case expr::op_t::VALUE: { - value_t temp; - read_value(data, temp); - expr->set_value(temp); - break; - } - - case expr::op_t::F_CODE_MASK: - case expr::op_t::F_PAYEE_MASK: - case expr::op_t::F_NOTE_MASK: - case expr::op_t::F_ACCOUNT_MASK: - case expr::op_t::F_SHORT_ACCOUNT_MASK: - case expr::op_t::F_COMMODITY_MASK: -#if 0 - if (read_bool(data)) - read_mask(data, expr->mask); -#endif - break; - - default: - if (kind > expr::op_t::TERMINALS) - expr->set_right(read_value_expr(data)); - break; - } - - return expr; -} - - inline void read_transaction(const char *& data, transaction_t * xact) { read_number(data, xact->_date); @@ -354,24 +304,20 @@ inline void read_transaction(const char *& data, transaction_t * xact) } else if (flag == 1) { read_amount(data, xact->amount); - read_string(data, xact->amount_expr.expr_str); + string str; + read_string(data, str); + xact->amount_expr.set_text(str); } else { - expr::ptr_op_t ptr = read_value_expr(data); - assert(ptr.get()); - xact->amount_expr.reset(ptr); - read_string(data, xact->amount_expr.expr_str); + xact->amount_expr.read(data); } if (read_bool(data)) { xact->cost = amount_t(); read_amount(data, *xact->cost); - expr::ptr_op_t ptr = read_value_expr(data); - assert(ptr.get()); - value_expr expr; - expr.reset(ptr); - xact->cost_expr = expr; + xact->cost_expr = expr_t(); + xact->cost_expr->read(data); } else { xact->cost = none; } @@ -388,8 +334,10 @@ inline void read_transaction(const char *& data, transaction_t * xact) xact->data = NULL; +#if 0 if (xact->amount_expr) - expr::compute_amount(xact->amount_expr.get(), xact->amount, xact); + expr_t::compute_amount(xact->amount_expr.get(), xact->amount, xact); +#endif } inline void read_entry_base(const char *& data, entry_base_t * entry, @@ -429,7 +377,10 @@ inline void read_auto_entry(const char *& data, auto_entry_t * entry, { bool ignore; read_entry_base(data, entry, xact_pool, ignore); - entry->predicate = item_predicate<transaction_t>(read_value_expr(data)); + + expr_t expr; + expr.read(data); + entry->predicate = item_predicate<transaction_t>(expr); } inline void read_period_entry(const char *& data, period_entry_t * entry, @@ -597,218 +548,6 @@ account_t * read_account(const char *& data, journal_t& journal, return acct; } -unsigned int read_journal(std::istream& in, - const path& file, - journal_t& journal, - account_t * master) -{ - account_index = - base_commodity_index = - commodity_index = 0; - - // Read in the files that participated in this journal, so that they - // can be checked for changes on reading. - - if (! file.empty()) { - for (unsigned short i = 0, - count = read_number<unsigned short>(in); - i < count; - i++) { - path pathname = read_string(in); - std::time_t old_mtime; - read_number(in, old_mtime); - struct stat info; - // jww (2008-04-22): can this be done differently now? - stat(pathname.string().c_str(), &info); - if (std::difftime(info.st_mtime, old_mtime) > 0) - return 0; - - journal.sources.push_back(pathname); - } - - // Make sure that the cache uses the same price database, - // otherwise it means that LEDGER_PRICE_DB has been changed, and - // we should ignore this cache file. - if (read_bool(in)) { - string pathname; - read_string(in, pathname); - if (! journal.price_db || - journal.price_db->string() != std::string(pathname)) - return 0; - } - } - - // Read all of the data in at once, so that we're just dealing with - // a big data buffer. - - unsigned long data_size = read_number<unsigned long>(in); - - char * data_pool = new char[data_size]; - in.read(data_pool, data_size); - - // Read in the accounts - - const char * data = data_pool; - - account_t::ident_t a_count = read_long<account_t::ident_t>(data); - accounts = accounts_next = new account_t *[a_count]; - - assert(journal.master); - checked_delete(journal.master); - journal.master = read_account(data, journal, master); - - if (read_bool(data)) - journal.basket = accounts[read_long<account_t::ident_t>(data) - 1]; - - // Allocate the memory needed for the entries and transactions in - // one large block, which is then chopped up and custom constructed - // as necessary. - - unsigned long count = read_long<unsigned long>(data); - unsigned long auto_count = read_long<unsigned long>(data); - unsigned long period_count = read_long<unsigned long>(data); - unsigned long xact_count = read_number<unsigned long>(data); - unsigned long bigint_count = read_number<unsigned long>(data); - - std::size_t pool_size = (sizeof(entry_t) * count + - sizeof(transaction_t) * xact_count + - amount_t::sizeof_bigint_t() * bigint_count); - - char * item_pool = new char[pool_size]; - - journal.item_pool = item_pool; - journal.item_pool_end = item_pool + pool_size; - - entry_t * entry_pool = (entry_t *) item_pool; - transaction_t * xact_pool = (transaction_t *) (item_pool + - sizeof(entry_t) * count); - bigints_index = 0; - bigints = bigints_next = (item_pool + sizeof(entry_t) * count + - sizeof(transaction_t) * xact_count); - - // Read in the base commodities and then derived commodities - - commodity_t::ident_t bc_count = read_long<commodity_t::ident_t>(data); - base_commodities = base_commodities_next = new commodity_t::base_t *[bc_count]; - - for (commodity_t::ident_t i = 0; i < bc_count; i++) { -#if 0 - commodity_t::base_t * base = read_commodity_base(data); - - // jww (2008-04-22): How does the pool get created here? - amount_t::current_pool->commodities.push_back(commodity); - - // jww (2008-04-22): What about this logic here? - if (! result.second) { - base_commodities_map::iterator c = - commodity_t::base_t::commodities.find(commodity->symbol); - - // It's possible the user might have used a commodity in a value - // expression passed to an option, we'll just override the - // flags, but keep the commodity pointer intact. - if (c == commodity_t::base_t::commodities.end()) - throw new error(string("Failed to read base commodity from cache: ") + - commodity->symbol); - - (*c).second->name = commodity->name; - (*c).second->note = commodity->note; - (*c).second->precision = commodity->precision; - (*c).second->flags = commodity->flags; - if ((*c).second->smaller) - checked_delete((*c).second->smaller); - (*c).second->smaller = commodity->smaller; - if ((*c).second->larger) - checked_delete((*c).second->larger); - (*c).second->larger = commodity->larger; - - *(base_commodities_next - 1) = (*c).second; - checked_delete(commodity); - } -#endif - } - - commodity_t::ident_t c_count = read_long<commodity_t::ident_t>(data); - commodities = commodities_next = new commodity_t *[c_count]; - - for (commodity_t::ident_t i = 0; i < c_count; i++) { - commodity_t * commodity; - string mapping_key; - - if (! read_bool(data)) { - commodity = read_commodity(data); - mapping_key = commodity->base->symbol; - } else { - read_string(data, mapping_key); - commodity = read_commodity_annotated(data); - } - - // jww (2008-04-22): What do I do with mapping_key here? - amount_t::current_pool->commodities.push_back(commodity); -#if 0 - // jww (2008-04-22): What about the error case? - if (! result.second) { - commodities_map::iterator c = - commodity_t::commodities.find(mapping_key); - if (c == commodity_t::commodities.end()) - throw new error(string("Failed to read commodity from cache: ") + - commodity->symbol()); - - *(commodities_next - 1) = (*c).second; - checked_delete(commodity); - } -#endif - } - - for (commodity_t::ident_t i = 0; i < bc_count; i++) - read_commodity_base_extra(data, i); - - commodity_t::ident_t ident; - read_long(data, ident); - if (ident == 0xffffffff || ident == 0) - amount_t::current_pool->default_commodity = NULL; - else - amount_t::current_pool->default_commodity = commodities[ident - 1]; - - // Read in the entries and transactions - - for (unsigned long i = 0; i < count; i++) { - new(entry_pool) entry_t; - bool finalize = false; - read_entry(data, entry_pool, xact_pool, finalize); - entry_pool->journal = &journal; - if (finalize && ! entry_pool->finalize()) - continue; - journal.entries.push_back(entry_pool++); - } - - for (unsigned long i = 0; i < auto_count; i++) { - auto_entry_t * auto_entry = new auto_entry_t; - read_auto_entry(data, auto_entry, xact_pool); - auto_entry->journal = &journal; - journal.auto_entries.push_back(auto_entry); - } - - for (unsigned long i = 0; i < period_count; i++) { - period_entry_t * period_entry = new period_entry_t; - bool finalize = false; - read_period_entry(data, period_entry, xact_pool, finalize); - period_entry->journal = &journal; - if (finalize && ! period_entry->finalize()) - continue; - journal.period_entries.push_back(period_entry); - } - - // Clean up and return the number of entries read - - checked_array_delete(accounts); - checked_array_delete(commodities); - checked_array_delete(data_pool); - - VERIFY(journal.valid()); - - return count; -} - void write_amount(std::ostream& out, const amount_t& amt) { if (amt.commodity_) @@ -844,60 +583,14 @@ void write_value(std::ostream& out, const value_t& val) } } -void write_mask(std::ostream& out, mask_t * mask) -{ - write_number(out, mask->exclude); - write_string(out, mask->expr.str()); -} - -void write_value_expr(std::ostream& out, const expr::ptr_op_t expr) +void write_mask(std::ostream& out, mask_t& mask) { - if (! expr) { - write_bool(out, false); - return; - } - - write_bool(out, true); - write_number(out, expr->kind); - - if (expr->kind > expr::op_t::TERMINALS) - write_value_expr(out, expr->left()); - - switch (expr->kind) { - case expr::op_t::O_ARG: - case expr::op_t::INDEX: - write_long(out, expr->as_long()); - break; - case expr::op_t::VALUE: - write_value(out, expr->as_value()); - break; - - case expr::op_t::F_CODE_MASK: - case expr::op_t::F_PAYEE_MASK: - case expr::op_t::F_NOTE_MASK: - case expr::op_t::F_ACCOUNT_MASK: - case expr::op_t::F_SHORT_ACCOUNT_MASK: - case expr::op_t::F_COMMODITY_MASK: -#if 0 - if (expr->mask) { - write_bool(out, true); - write_mask(out, expr->mask); - } else { - write_bool(out, false); - } -#endif - break; - - default: - if (expr->kind > expr::op_t::TERMINALS) - write_value_expr(out, expr->right()); - break; - } - + write_number(out, mask.exclude); + write_string(out, mask.expr.str()); } void write_transaction(std::ostream& out, transaction_t * xact, - bool ignore_calculated) + bool ignore_calculated) { write_number(out, xact->_date); write_number(out, xact->_date_eff); @@ -909,13 +602,12 @@ void write_transaction(std::ostream& out, transaction_t * xact, } else if (xact->amount_expr) { write_number<unsigned char>(out, 2); - write_value_expr(out, xact->amount_expr.get()); - write_string(out, xact->amount_expr.expr_str); + xact->amount_expr.write(out); } - else if (! xact->amount_expr.expr_str.empty()) { + else if (! xact->amount_expr.text().empty()) { write_number<unsigned char>(out, 1); write_amount(out, xact->amount); - write_string(out, xact->amount_expr.expr_str); + write_string(out, xact->amount_expr.text()); } else { write_number<unsigned char>(out, 0); @@ -926,7 +618,7 @@ void write_transaction(std::ostream& out, transaction_t * xact, (! (ignore_calculated && xact->has_flags(TRANSACTION_CALCULATED)))) { write_bool(out, true); write_amount(out, *xact->cost); - write_string(out, xact->cost_expr->expr_str); + xact->cost_expr->write(out); } else { write_bool(out, false); } @@ -979,7 +671,7 @@ void write_entry(std::ostream& out, entry_t * entry) void write_auto_entry(std::ostream& out, auto_entry_t * entry) { write_entry_base(out, entry); - write_value_expr(out, entry->predicate.predicate.get()); + entry->predicate.predicate.write(out); } void write_period_entry(std::ostream& out, period_entry_t * entry) @@ -1102,8 +794,226 @@ void write_account(std::ostream& out, account_t * account) write_account(out, (*i).second); } -void write_journal(std::ostream& out, journal_t& journal) +} // namespace binary + +unsigned int journal_t::read(std::istream& in, + const path& file, + account_t * master) +{ + using namespace binary; + + account_index = + base_commodity_index = + commodity_index = 0; + + // Read in the files that participated in this journal, so that they + // can be checked for changes on reading. + + if (! file.empty()) { + for (unsigned short i = 0, + count = read_number<unsigned short>(in); + i < count; + i++) { + path pathname = read_string(in); + std::time_t old_mtime; + read_number(in, old_mtime); + struct stat info; + // jww (2008-04-22): can this be done differently now? + stat(pathname.string().c_str(), &info); + if (std::difftime(info.st_mtime, old_mtime) > 0) + return 0; + + sources.push_back(pathname); + } + + // Make sure that the cache uses the same price database, + // otherwise it means that LEDGER_PRICE_DB has been changed, and + // we should ignore this cache file. + if (read_bool(in)) { + string pathname; + read_string(in, pathname); + if (! price_db || + price_db->string() != std::string(pathname)) + return 0; + } + } + + // Read all of the data in at once, so that we're just dealing with + // a big data buffer. + + unsigned long data_size = read_number<unsigned long>(in); + + char * data_pool = new char[data_size]; + in.read(data_pool, data_size); + + // Read in the accounts + + const char * data = data_pool; + + account_t::ident_t a_count = read_long<account_t::ident_t>(data); + accounts = accounts_next = new account_t *[a_count]; + + // jww (2008-07-29): Does this still apply? + assert(owner->master); + checked_delete(owner->master); + owner->master = read_account(data, *this, master); + + if (read_bool(data)) + basket = accounts[read_long<account_t::ident_t>(data) - 1]; + + // Allocate the memory needed for the entries and transactions in + // one large block, which is then chopped up and custom constructed + // as necessary. + + unsigned long count = read_long<unsigned long>(data); + unsigned long auto_count = read_long<unsigned long>(data); + unsigned long period_count = read_long<unsigned long>(data); + unsigned long xact_count = read_number<unsigned long>(data); + unsigned long bigint_count = read_number<unsigned long>(data); + + std::size_t pool_size = (sizeof(entry_t) * count + + sizeof(transaction_t) * xact_count + + amount_t::sizeof_bigint_t() * bigint_count); + + char * item_pool = new char[pool_size]; + + item_pool = item_pool; + item_pool_end = item_pool + pool_size; + + entry_t * entry_pool = (entry_t *) item_pool; + transaction_t * xact_pool = (transaction_t *) (item_pool + + sizeof(entry_t) * count); + bigints_index = 0; + bigints = bigints_next = (item_pool + sizeof(entry_t) * count + + sizeof(transaction_t) * xact_count); + + // Read in the base commodities and then derived commodities + + commodity_t::ident_t bc_count = read_long<commodity_t::ident_t>(data); + base_commodities = base_commodities_next = new commodity_t::base_t *[bc_count]; + + for (commodity_t::ident_t i = 0; i < bc_count; i++) { +#if 0 + commodity_t::base_t * base = read_commodity_base(data); + + // jww (2008-04-22): How does the pool get created here? + amount_t::current_pool->commodities.push_back(commodity); + + // jww (2008-04-22): What about this logic here? + if (! result.second) { + base_commodities_map::iterator c = + commodity_t::base_t::commodities.find(commodity->symbol); + + // It's possible the user might have used a commodity in a value + // expression passed to an option, we'll just override the + // flags, but keep the commodity pointer intact. + if (c == commodity_t::base_t::commodities.end()) + throw new error(string("Failed to read base commodity from cache: ") + + commodity->symbol); + + (*c).second->name = commodity->name; + (*c).second->note = commodity->note; + (*c).second->precision = commodity->precision; + (*c).second->flags = commodity->flags; + if ((*c).second->smaller) + checked_delete((*c).second->smaller); + (*c).second->smaller = commodity->smaller; + if ((*c).second->larger) + checked_delete((*c).second->larger); + (*c).second->larger = commodity->larger; + + *(base_commodities_next - 1) = (*c).second; + checked_delete(commodity); + } +#endif + } + + commodity_t::ident_t c_count = read_long<commodity_t::ident_t>(data); + commodities = commodities_next = new commodity_t *[c_count]; + + for (commodity_t::ident_t i = 0; i < c_count; i++) { + commodity_t * commodity; + string mapping_key; + + if (! read_bool(data)) { + commodity = read_commodity(data); + mapping_key = commodity->base->symbol; + } else { + read_string(data, mapping_key); + commodity = read_commodity_annotated(data); + } + + // jww (2008-04-22): What do I do with mapping_key here? + amount_t::current_pool->commodities.push_back(commodity); +#if 0 + // jww (2008-04-22): What about the error case? + if (! result.second) { + commodities_map::iterator c = + commodity_t::commodities.find(mapping_key); + if (c == commodity_t::commodities.end()) + throw new error(string("Failed to read commodity from cache: ") + + commodity->symbol()); + + *(commodities_next - 1) = (*c).second; + checked_delete(commodity); + } +#endif + } + + for (commodity_t::ident_t i = 0; i < bc_count; i++) + read_commodity_base_extra(data, i); + + commodity_t::ident_t ident; + read_long(data, ident); + if (ident == 0xffffffff || ident == 0) + amount_t::current_pool->default_commodity = NULL; + else + amount_t::current_pool->default_commodity = commodities[ident - 1]; + + // Read in the entries and transactions + + for (unsigned long i = 0; i < count; i++) { + new(entry_pool) entry_t; + bool finalize = false; + read_entry(data, entry_pool, xact_pool, finalize); + entry_pool->journal = this; + if (finalize && ! entry_pool->finalize()) + continue; + entries.push_back(entry_pool++); + } + + for (unsigned long i = 0; i < auto_count; i++) { + auto_entry_t * auto_entry = new auto_entry_t; + read_auto_entry(data, auto_entry, xact_pool); + auto_entry->journal = this; + auto_entries.push_back(auto_entry); + } + + for (unsigned long i = 0; i < period_count; i++) { + period_entry_t * period_entry = new period_entry_t; + bool finalize = false; + read_period_entry(data, period_entry, xact_pool, finalize); + period_entry->journal = this; + if (finalize && ! period_entry->finalize()) + continue; + period_entries.push_back(period_entry); + } + + // Clean up and return the number of entries read + + checked_array_delete(accounts); + checked_array_delete(commodities); + checked_array_delete(data_pool); + + VERIFY(valid()); + + return count; +} + +void journal_t::write(std::ostream& out) { + using namespace binary; + account_index = base_commodity_index = commodity_index = 0; @@ -1114,12 +1024,12 @@ void write_journal(std::ostream& out, journal_t& journal) // Write out the files that participated in this journal, so that // they can be checked for changes on reading. - if (journal.sources.empty()) { + if (sources.empty()) { write_number<unsigned short>(out, 0); } else { - write_number<unsigned short>(out, journal.sources.size()); - for (paths_list::const_iterator i = journal.sources.begin(); - i != journal.sources.end(); + write_number<unsigned short>(out, sources.size()); + for (paths_list::const_iterator i = sources.begin(); + i != sources.end(); i++) { write_string(out, (*i).string()); struct stat info; @@ -1129,9 +1039,9 @@ void write_journal(std::ostream& out, journal_t& journal) // Write out the price database that relates to this data file, so // that if it ever changes the cache can be invalidated. - if (journal.price_db) { + if (price_db) { write_bool(out, true); - write_string(out, journal.price_db->string()); + write_string(out, price_db->string()); } else { write_bool(out, false); } @@ -1142,21 +1052,21 @@ void write_journal(std::ostream& out, journal_t& journal) // Write out the accounts - write_long<account_t::ident_t>(out, count_accounts(journal.master)); - write_account(out, journal.master); + write_long<account_t::ident_t>(out, count_accounts(master)); + write_account(out, master); - if (journal.basket) { + if (basket) { write_bool(out, true); - write_long(out, journal.basket->ident); + write_long(out, basket->ident); } else { write_bool(out, false); } // Write out the number of entries, transactions, and amounts - write_long<unsigned long>(out, journal.entries.size()); - write_long<unsigned long>(out, journal.auto_entries.size()); - write_long<unsigned long>(out, journal.period_entries.size()); + write_long<unsigned long>(out, entries.size()); + write_long<unsigned long>(out, auto_entries.size()); + write_long<unsigned long>(out, period_entries.size()); ostream_pos_type xacts_val = out.tellp(); write_number<unsigned long>(out, 0); @@ -1220,22 +1130,22 @@ void write_journal(std::ostream& out, journal_t& journal) unsigned long xact_count = 0; - for (entries_list::const_iterator i = journal.entries.begin(); - i != journal.entries.end(); + for (entries_list::const_iterator i = entries.begin(); + i != entries.end(); i++) { write_entry(out, *i); xact_count += (*i)->transactions.size(); } - for (auto_entries_list::const_iterator i = journal.auto_entries.begin(); - i != journal.auto_entries.end(); + for (auto_entries_list::const_iterator i = auto_entries.begin(); + i != auto_entries.end(); i++) { write_auto_entry(out, *i); xact_count += (*i)->transactions.size(); } - for (period_entries_list::const_iterator i = journal.period_entries.begin(); - i != journal.period_entries.end(); + for (period_entries_list::const_iterator i = period_entries.begin(); + i != period_entries.end(); i++) { write_period_entry(out, *i); xact_count += (*i)->transactions.size(); @@ -1254,5 +1164,4 @@ void write_journal(std::ostream& out, journal_t& journal) write_number<unsigned long>(out, bigints_count); } -} // namespace binary } // namespace ledger @@ -32,12 +32,9 @@ #ifndef BINARY_H #define BINARY_H -#include "parser.h" +#include "utils.h" namespace ledger { - -class journal_t; - namespace binary { template <typename T> @@ -272,22 +269,7 @@ inline void write_object(std::ostream& out, const T& journal) { assert(false); } -void write_journal(std::ostream& out, journal_t& journal); - } // namespace binary - -class binary_parser_t : public parser_t -{ - public: - virtual bool test(std::istream& in) const; - - virtual unsigned int parse(std::istream& in, - session_t& session, - journal_t& journal, - account_t * master = NULL, - const path * original_file = NULL); -}; - } // namespace ledger #endif // BINARY_H diff --git a/commodity.cc b/commodity.cc index 0a62a861..173ebbc8 100644 --- a/commodity.cc +++ b/commodity.cc @@ -40,7 +40,6 @@ */ #include "amount.h" -#include "parser.h" // for parsing utility functions namespace ledger { @@ -248,7 +247,7 @@ void commodity_t::parse_symbol(char *& p, string& symbol) if (*p == '"') { char * q = std::strchr(p + 1, '"'); if (! q) - throw_(parse_error, "Quoted commodity symbol lacks closing quote"); + throw_(amount_error, "Quoted commodity symbol lacks closing quote"); symbol = string(p + 1, 0, q - p - 1); p = q + 2; } else { @@ -260,7 +259,7 @@ void commodity_t::parse_symbol(char *& p, string& symbol) p += symbol.length(); } if (symbol.empty()) - throw_(parse_error, "Failed to parse commodity"); + throw_(amount_error, "Failed to parse commodity"); } bool commodity_t::valid() const @@ -25,7 +25,9 @@ void format_csv_transactions::operator()(transaction_t& xact) { format_t fmt("%D"); std::ostringstream str; +#if 0 fmt.format(str, details_t(xact)); +#endif write_escaped_string(out, str.str()); } out << ','; @@ -33,7 +35,9 @@ void format_csv_transactions::operator()(transaction_t& xact) { format_t fmt("%P"); std::ostringstream str; +#if 0 fmt.format(str, details_t(xact)); +#endif write_escaped_string(out, str.str()); } out << ','; @@ -41,7 +45,9 @@ void format_csv_transactions::operator()(transaction_t& xact) { format_t fmt("%A"); std::ostringstream str; +#if 0 fmt.format(str, details_t(xact)); +#endif write_escaped_string(out, str.str()); } out << ','; @@ -49,7 +55,9 @@ void format_csv_transactions::operator()(transaction_t& xact) { format_t fmt("%t"); std::ostringstream str; +#if 0 fmt.format(str, details_t(xact)); +#endif write_escaped_string(out, str.str()); } out << ','; @@ -57,7 +65,9 @@ void format_csv_transactions::operator()(transaction_t& xact) { format_t fmt("%T"); std::ostringstream str; +#if 0 fmt.format(str, details_t(xact)); +#endif write_escaped_string(out, str.str()); } out << ','; @@ -94,7 +104,9 @@ void format_csv_transactions::operator()(transaction_t& xact) { format_t fmt("%N"); std::ostringstream str; +#if 0 fmt.format(str, details_t(xact)); +#endif write_escaped_string(out, str.str()); } out << '\n'; diff --git a/expr.cc b/expr.cc new file mode 100644 index 00000000..0319e243 --- /dev/null +++ b/expr.cc @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "expr.h" +#include "parser.h" +#include "op.h" + +namespace ledger { + +std::auto_ptr<expr_t::parser_t> expr_t::parser; + +expr_t::expr_t() +{ + TRACE_CTOR(expr_t, ""); +} + +expr_t::expr_t(const expr_t& other) : ptr(other.ptr), str(other.str) +{ + TRACE_CTOR(expr_t, "copy"); +} + +expr_t::expr_t(const string& _str, const unsigned int flags) : str(_str) +{ + TRACE_CTOR(expr_t, "const string&"); + + if (! _str.empty()) + ptr = parser->parse(str, flags); +} + + expr_t::expr_t(std::istream& in, const unsigned int flags) +{ + TRACE_CTOR(expr_t, "std::istream&"); + + ptr = parser->parse(in, flags); +} + +expr_t::expr_t(const ptr_op_t& _ptr, const string& _str) + : ptr(_ptr), str(_str) +{ + TRACE_CTOR(expr_t, "const ptr_op_t&, const string&"); +} + +expr_t::~expr_t() throw() +{ + TRACE_DTOR(expr_t); +} + +expr_t& expr_t::operator=(const expr_t& _expr) +{ + str = _expr.str; + ptr = _expr.ptr; + return *this; +} + +void expr_t::parse(const string& _str, const unsigned int flags) +{ + if (! parser.get()) + throw_(parse_error, "Value expression parser not initialized"); + + str = _str; + ptr = parser->parse(str, flags); +} + +void expr_t::parse(std::istream& in, const unsigned int flags) +{ + if (! parser.get()) + throw_(parse_error, "Value expression parser not initialized"); + + str = "<stream>"; + ptr = parser->parse(in, flags); +} + +void expr_t::compile(scope_t& scope) +{ + if (ptr.get()) + ptr = ptr->compile(scope); +} + +value_t expr_t::calc(scope_t& scope) const +{ + if (ptr.get()) + return ptr->calc(scope); + return NULL_VALUE; +} + +value_t expr_t::eval(const string& _expr, scope_t& scope) +{ + return expr_t(_expr).calc(scope); +} + +void expr_t::print(std::ostream& out, scope_t& scope) const +{ + op_t::print_context_t context(scope); + ptr->print(out, context); +} + +void expr_t::dump(std::ostream& out) const +{ + if (ptr) + ptr->dump(out, 0); +} + +void expr_t::read(std::ostream& in) +{ + if (ptr) + ptr->read(in); +} + +void expr_t::read(const char *& data) +{ + if (ptr) + ptr->read(data); +} + +void expr_t::write(std::ostream& out) const +{ + if (ptr) + ptr->write(out); +} + +void expr_t::initialize() +{ + parser.reset(new expr_t::parser_t); +} + +void expr_t::shutdown() +{ + parser.reset(); +} + +} // namespace ledger @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _EXPR_H +#define _EXPR_H + +#include "value.h" + +namespace ledger { + +DECLARE_EXCEPTION(error, parse_error); +DECLARE_EXCEPTION(error, compile_error); +DECLARE_EXCEPTION(error, calc_error); + +class scope_t; +class call_scope_t; + +typedef function<value_t (call_scope_t&)> function_t; + +class expr_t +{ + struct token_t; + + class parser_t; + static std::auto_ptr<parser_t> parser; + +public: + class op_t; + typedef intrusive_ptr<op_t> ptr_op_t; + +private: + ptr_op_t ptr; + string str; + + static void initialize(); + static void shutdown(); + + friend class session_t; + +public: + expr_t(); + expr_t(const expr_t& other); + expr_t(const ptr_op_t& _ptr, const string& _str = ""); + + expr_t(const string& _str, const unsigned int flags = 0); + expr_t(std::istream& in, const unsigned int flags = 0); + + virtual ~expr_t() throw(); + + expr_t& operator=(const expr_t& _expr); + expr_t& operator=(const string& _expr) { + parse(_expr); + return *this; + } + + operator bool() const throw() { + return ptr.get() != NULL; + } + string text() const throw() { + return str; + } + + // This has special use in the textual parser + void set_text(const string& txt) { + str = txt; + } + + void parse(const string& _str, const unsigned int flags = 0); + void parse(std::istream& in, const unsigned int flags = 0); + + void compile(scope_t& scope); + value_t calc(scope_t& scope) const; + + void print(std::ostream& out, scope_t& scope) const; + void dump(std::ostream& out) const; + + void read(std::ostream& in); + void read(const char *& data); + void write(std::ostream& out) const; + + static value_t eval(const string& _expr, scope_t& scope); +}; + +} // namespace ledger + +#endif // _EXPR_H @@ -219,7 +219,7 @@ element_t * format_t::parse_elements(const string& fmt) current->type = element_t::VALUE_EXPR; assert(! current->val_expr); - current->val_expr = string(b, p); + current->val_expr.parse(string(b, p)); break; } @@ -320,7 +320,7 @@ namespace { } } -void format_t::format(std::ostream& out_str, const details_t& details) const +void format_t::format(std::ostream& out_str, const scope_t& scope) const { for (const element_t * elem = elements; elem; elem = elem->next) { std::ostringstream out; @@ -340,10 +340,11 @@ void format_t::format(std::ostream& out_str, const details_t& details) const out << elem->chars; break; +#if 0 case element_t::AMOUNT: case element_t::TOTAL: case element_t::VALUE_EXPR: { - value_expr * calc; + expr_t * calc; switch (elem->type) { case element_t::AMOUNT: assert(value_expr::amount_expr.get()); @@ -747,6 +748,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const out << " "; } break; +#endif default: assert(false); @@ -779,6 +781,7 @@ format_transactions::format_transactions(std::ostream& _output_stream, void format_transactions::operator()(transaction_t& xact) { +#if 0 if (! transaction_has_xdata(xact) || ! (transaction_xdata_(xact).dflags & TRANSACTION_DISPLAYED)) { if (last_entry != xact.entry) { @@ -795,10 +798,12 @@ void format_transactions::operator()(transaction_t& xact) transaction_xdata(xact).dflags |= TRANSACTION_DISPLAYED; last_xact = &xact; } +#endif } void format_entries::format_last_entry() { +#if 0 bool first = true; for (transactions_list::const_iterator i = last_entry->transactions.begin(); i != last_entry->transactions.end(); @@ -814,6 +819,7 @@ void format_entries::format_last_entry() transaction_xdata_(**i).dflags |= TRANSACTION_DISPLAYED; } } +#endif } void format_entries::operator()(transaction_t& xact) @@ -838,7 +844,7 @@ void print_entry(std::ostream& out, const entry_base_t& entry_base, } else if (const auto_entry_t * entry = dynamic_cast<const auto_entry_t *>(&entry_base)) { - out << "= " << entry->predicate.predicate.expr_str << '\n'; + out << "= " << entry->predicate.predicate.text() << '\n'; print_format = prefix + " %-34A %12o\n"; } else if (const period_entry_t * entry = @@ -867,14 +873,17 @@ bool disp_subaccounts_p(const account_t& account, const account_t *& to_show) { bool display = false; +#if 0 unsigned int counted = 0; bool matches = disp_pred ? (*disp_pred)(account) : true; - value_t acct_total; bool computed = false; +#endif + value_t acct_total; value_t result; to_show = NULL; +#if 0 for (accounts_map::const_iterator i = account.accounts.begin(); i != account.accounts.end(); i++) { @@ -894,6 +903,7 @@ bool disp_subaccounts_p(const account_t& account, to_show = (*i).second; counted++; } +#endif return display; } @@ -922,6 +932,7 @@ bool display_account(const account_t& account, void format_accounts::operator()(account_t& account) { +#if 0 if (display_account(account, disp_pred)) { if (! account.parent) { account_xdata(account).dflags |= ACCOUNT_TO_DISPLAY; @@ -930,6 +941,7 @@ void format_accounts::operator()(account_t& account) account_xdata(account).dflags |= ACCOUNT_DISPLAYED; } } +#endif } format_equity::format_equity(std::ostream& _output_stream, @@ -937,6 +949,7 @@ format_equity::format_equity(std::ostream& _output_stream, const string& display_predicate) : output_stream(_output_stream), disp_pred(display_predicate) { +#if 0 const char * f = _format.c_str(); if (const char * p = std::strstr(f, "%/")) { first_line_format.reset(string(f, 0, p - f)); @@ -950,10 +963,12 @@ format_equity::format_equity(std::ostream& _output_stream, header_entry.payee = "Opening Balances"; header_entry._date = current_moment; first_line_format.format(output_stream, details_t(header_entry)); +#endif } void format_equity::flush() { +#if 0 account_xdata_t xdata; xdata.value = total; xdata.value.negate(); @@ -980,10 +995,12 @@ void format_equity::flush() next_lines_format.format(output_stream, details_t(summary)); } output_stream.flush(); +#endif } void format_equity::operator()(account_t& account) { +#if 0 if (display_account(account, disp_pred)) { if (account_has_xdata(account)) { value_t val = account_xdata_(account).value; @@ -1011,6 +1028,7 @@ void format_equity::operator()(account_t& account) } account_xdata(account).dflags |= ACCOUNT_DISPLAYED; } +#endif } } // namespace ledger @@ -2,7 +2,7 @@ #define _FORMAT_H #include "journal.h" -#include "valexpr.h" +#include "expr.h" #include "walk.h" namespace ledger { @@ -53,7 +53,7 @@ struct element_t : public noncopyable string chars; unsigned char min_width; unsigned char max_width; - value_expr val_expr; + expr_t val_expr; struct element_t * next; @@ -110,7 +110,7 @@ struct format_t : public noncopyable static string truncate(const string& str, unsigned int width, const bool is_account = false); - void format(std::ostream& out, const details_t& details) const; + void format(std::ostream& out, const scope_t& scope) const; }; class format_transactions : public item_handler<transaction_t> @@ -1,11 +1,11 @@ #ifndef _GNUCASH_H #define _GNUCASH_H -#include "parser.h" +#include "journal.h" namespace ledger { -class gnucash_parser_t : public parser_t +class gnucash_parser_t : public journal_t::parser_t { public: virtual bool test(std::istream& in) const; @@ -34,8 +34,8 @@ #include "amount.h" #include "value.h" -#include "valexpr.h" -#include "utils.h" +#include "expr.h" +#include "predicate.h" namespace ledger { @@ -62,9 +62,9 @@ class transaction_t : public supports_flags<> optional<datetime_t> _date; optional<datetime_t> _date_eff; amount_t amount; - value_expr amount_expr; + expr_t amount_expr; optional<amount_t> cost; - optional<value_expr> cost_expr; + optional<expr_t> cost_expr; optional<string> note; istream_pos_type beg_pos; unsigned long beg_line; @@ -452,7 +452,7 @@ class session_t; class journal_t : public noncopyable { - public: +public: session_t * owner; account_t * master; account_t * basket; @@ -488,6 +488,57 @@ class journal_t : public noncopyable } bool valid() const; + +/** + * @class journal_t::parser_t + * + * @brief Provides an abstract interface for writing journal parsers. + * + * Any data format for Ledger data is possible, as long as it can be parsed + * into a journal_t data tree. This class provides the basic interface which + * must be implemented by every such journal parser. + */ + class parser_t : public noncopyable + { + public: + parser_t() { + TRACE_CTOR(parser_t, ""); + } + virtual ~parser_t() { + TRACE_DTOR(parser_t); + } + + virtual bool test(std::istream& in) const = 0; + + virtual unsigned int parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master = NULL, + const path * original_file = NULL) = 0; + }; + + class binary_parser_t : public parser_t + { + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + session_t& session, + journal_t& journal, + account_t * master = NULL, + const path * original_file = NULL); + }; + + unsigned int read(std::istream& in, const path& file, account_t * master); + void write(std::ostream& out); + + class parse_error : public error + { + public: + parse_error(const string& reason, error_context * ctxt = NULL) throw() + : error(reason, ctxt) {} + virtual ~parse_error() throw() {} + }; }; inline void extend_entry_base(journal_t * journal, entry_base_t& entry, @@ -51,8 +51,7 @@ #include <emacs.h> #include <csv.h> //#include <quotes.h> -#include <valexpr.h> -#include <parsexp.h> +#include <expr.h> #include <walk.h> #include <derive.h> #include <reconcile.h> @@ -67,15 +66,6 @@ #include <qif.h> #include <ofx.h> -namespace ledger { - extern parser_t * binary_parser_ptr; - extern parser_t * xml_parser_ptr; - extern parser_t * gnucash_parser_ptr; - extern parser_t * ofx_parser_ptr; - extern parser_t * qif_parser_ptr; - extern parser_t * textual_parser_ptr; -} - #include <session.h> #include <report.h> @@ -46,10 +46,10 @@ #endif namespace ledger { - value_t register_command(expr::call_scope_t& args) + value_t register_command(call_scope_t& args) { - expr::var_t<report_t> report(args, 0); - expr::var_t<std::ostream> ostream(args, 1); + var_t<report_t> report(args, 0); + var_t<std::ostream> ostream(args, 1); report->transactions_report (xact_handler_ptr(new format_transactions @@ -168,9 +168,8 @@ static int read_and_report(ledger::report_t& report, int argc, char * argv[], string verb = *arg++; if (verb == "parse") { - value_expr expr(*arg); + expr_t expr(*arg); -#if 0 IF_INFO() { std::cout << "Value expression tree:" << std::endl; expr.dump(std::cout); @@ -194,7 +193,6 @@ static int read_and_report(ledger::report_t& report, int argc, char * argv[], } std::cout << expr.calc(report).strip_annotations() << std::endl; -#endif return 0; } @@ -227,28 +225,26 @@ static int read_and_report(ledger::report_t& report, int argc, char * argv[], // Are we handling the expr commands? Do so now. if (verb == "expr") { - value_expr expr(*arg); + expr_t expr(*arg); -#if 0 IF_INFO() { *out << "Value expression tree:" << std::endl; expr.dump(*out); *out << std::endl; *out << "Value expression parsed was:" << std::endl; - expr.print(*out, doc_scope); + expr.print(*out, report); *out << std::endl << std::endl; *out << "Result of calculation: "; } - *out << expr.calc(doc_scope).strip_annotations() << std::endl; -#endif + *out << expr.calc(report).strip_annotations() << std::endl; return 0; } // Read the command word and create a command object based on it - expr::function_t command; + function_t command; if (verb == "register" || verb == "reg" || verb == "r") command = register_command; @@ -285,7 +281,7 @@ static int read_and_report(ledger::report_t& report, int argc, char * argv[], std::strcpy(buf, "command_"); std::strcat(buf, verb.c_str()); - if (expr::ptr_op_t def = report.lookup(buf)) + if (expr_t::ptr_op_t def = report.lookup(buf)) command = def->as_function(); if (! command) @@ -295,7 +291,7 @@ static int read_and_report(ledger::report_t& report, int argc, char * argv[], // Create an argument scope containing the report command's // arguments, and then invoke the command. - expr::call_scope_t command_args(report); + call_scope_t command_args(report); command_args.push_back(value_t(&report)); command_args.push_back(value_t(out)); @@ -320,7 +316,7 @@ static int read_and_report(ledger::report_t& report, int argc, char * argv[], TRACE_START(binary_cache, 1, "Wrote binary journal file"); ofstream stream(*session.cache_file); - binary::write_journal(stream, journal); + journal.write(stream); TRACE_FINISH(binary_cache, 1); } @@ -400,7 +396,7 @@ int main(int argc, char * argv[], char * envp[]) ledger::set_session_context(session.get()); - session->register_parser(new ledger::binary_parser_t); + session->register_parser(new ledger::journal_t::binary_parser_t); #if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) session->register_parser(new ledger::xml_parser_t); session->register_parser(new ledger::gnucash_parser_t); @@ -1,11 +1,11 @@ #ifndef _OFX_H #define _OFX_H -#include "parser.h" +#include "journal.h" namespace ledger { -class ofx_parser_t : public parser_t +class ofx_parser_t : public journal_t::parser_t { public: virtual bool test(std::istream& in) const; @@ -0,0 +1,1151 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "op.h" +#include "scope.h" +#include "binary.h" + +namespace ledger { + +#if 0 +void expr_t::op_t::compute(value_t& result, + const details_t& details, + ptr_op_t context) const +{ + try { + switch (kind) { + case INDEX: + throw new compute_error("Cannot directly compute an argument index"); + + case VALUE: + result = as_value(); + break; + + case F_NOW: + result = terminus; + break; + + case AMOUNT: + if (details.xact) { + if (transaction_has_xdata(*details.xact) && + transaction_xdata_(*details.xact).dflags & TRANSACTION_COMPOUND) + result = transaction_xdata_(*details.xact).value; + else + result = details.xact->amount; + } + else if (details.account && account_has_xdata(*details.account)) { + result = account_xdata(*details.account).value; + } + else { + result = 0L; + } + break; + + case PRICE: + if (details.xact) { + bool set = false; + if (transaction_has_xdata(*details.xact)) { + transaction_xdata_t& xdata(transaction_xdata_(*details.xact)); + if (xdata.dflags & TRANSACTION_COMPOUND) { + result = xdata.value.value(); + set = true; + } + } + if (! set) { + optional<amount_t> value = details.xact->amount.value(); + if (value) + result = *value; + else + result = 0L; + } + } + else if (details.account && account_has_xdata(*details.account)) { + result = account_xdata(*details.account).value.value(); + } + else { + result = 0L; + } + break; + + case COST: + if (details.xact) { + bool set = false; + if (transaction_has_xdata(*details.xact)) { + transaction_xdata_t& xdata(transaction_xdata_(*details.xact)); + if (xdata.dflags & TRANSACTION_COMPOUND) { + result = xdata.value.cost(); + set = true; + } + } + + if (! set) { + if (details.xact->cost) + result = *details.xact->cost; + else + result = details.xact->amount; + } + } + else if (details.account && account_has_xdata(*details.account)) { + result = account_xdata(*details.account).value.cost(); + } + else { + result = 0L; + } + break; + + case TOTAL: + if (details.xact && transaction_has_xdata(*details.xact)) + result = transaction_xdata_(*details.xact).total; + else if (details.account && account_has_xdata(*details.account)) + result = account_xdata(*details.account).total; + else + result = 0L; + break; + case PRICE_TOTAL: + if (details.xact && transaction_has_xdata(*details.xact)) + result = transaction_xdata_(*details.xact).total.value(); + else if (details.account && account_has_xdata(*details.account)) + result = account_xdata(*details.account).total.value(); + else + result = 0L; + break; + case COST_TOTAL: + if (details.xact && transaction_has_xdata(*details.xact)) + result = transaction_xdata_(*details.xact).total.cost(); + else if (details.account && account_has_xdata(*details.account)) + result = account_xdata(*details.account).total.cost(); + else + result = 0L; + break; + + case VALUE_EXPR: + if (value_expr::amount_expr.get()) + value_expr::amount_expr->compute(result, details, context); + else + result = 0L; + break; + case TOTAL_EXPR: + if (value_expr::total_expr.get()) + value_expr::total_expr->compute(result, details, context); + else + result = 0L; + break; + + case DATE: + if (details.xact && transaction_has_xdata(*details.xact) && + is_valid(transaction_xdata_(*details.xact).date)) + result = transaction_xdata_(*details.xact).date; + else if (details.xact) + result = details.xact->date(); + else if (details.entry) + result = details.entry->date(); + else + result = terminus; + break; + + case ACT_DATE: + if (details.xact && transaction_has_xdata(*details.xact) && + is_valid(transaction_xdata_(*details.xact).date)) + result = transaction_xdata_(*details.xact).date; + else if (details.xact) + result = details.xact->actual_date(); + else if (details.entry) + result = details.entry->actual_date(); + else + result = terminus; + break; + + case EFF_DATE: + if (details.xact && transaction_has_xdata(*details.xact) && + is_valid(transaction_xdata_(*details.xact).date)) + result = transaction_xdata_(*details.xact).date; + else if (details.xact) + result = details.xact->effective_date(); + else if (details.entry) + result = details.entry->effective_date(); + else + result = terminus; + break; + + case CLEARED: + if (details.xact) + result = details.xact->state == transaction_t::CLEARED; + else + result = false; + break; + case PENDING: + if (details.xact) + result = details.xact->state == transaction_t::PENDING; + else + result = false; + break; + + case REAL: + if (details.xact) + result = ! (details.xact->has_flags(TRANSACTION_VIRTUAL)); + else + result = true; + break; + + case ACTUAL: + if (details.xact) + result = ! (details.xact->has_flags(TRANSACTION_AUTO)); + else + result = true; + break; + + case INDEX: + if (details.xact && transaction_has_xdata(*details.xact)) + result = long(transaction_xdata_(*details.xact).index + 1); + else if (details.account && account_has_xdata(*details.account)) + result = long(account_xdata(*details.account).count); + else + result = 0L; + break; + + case COUNT: + if (details.xact && transaction_has_xdata(*details.xact)) + result = long(transaction_xdata_(*details.xact).index + 1); + else if (details.account && account_has_xdata(*details.account)) + result = long(account_xdata(*details.account).total_count); + else + result = 0L; + break; + + case DEPTH: + if (details.account) + result = long(details.account->depth); + else + result = 0L; + break; + + case F_PRICE: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + result = result.value(); + break; + } + + case F_DATE: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + result = result.as_datetime(); + break; + } + + case F_DATECMP: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + result = result.as_datetime(); + if (! result) + break; + + arg_index = 0; + expr = find_leaf(context, 1, arg_index); + value_t moment; + expr->compute(moment, details, context); + if (moment.is_type(value_t::DATETIME)) { + result.cast(value_t::INTEGER); + moment.cast(value_t::INTEGER); + result -= moment; + } else { + throw new compute_error("Invalid date passed to datecmp(value,date)", + new valexpr_context(expr)); + } + break; + } + + case F_YEAR: + case F_MONTH: + case F_DAY: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + + if (! result.is_type(value_t::DATETIME)) + throw new compute_error("Invalid date passed to year|month|day(date)", + new valexpr_context(expr)); + + const datetime_t& moment(result.as_datetime()); + switch (kind) { + case F_YEAR: + result = (long)moment.date().year(); + break; + case F_MONTH: + result = (long)moment.date().month(); + break; + case F_DAY: + result = (long)moment.date().day(); + break; + default: + break; + } + break; + } + + case F_ARITH_MEAN: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + if (details.xact && transaction_has_xdata(*details.xact)) { + expr->compute(result, details, context); + result /= amount_t(long(transaction_xdata_(*details.xact).index + 1)); + } + else if (details.account && account_has_xdata(*details.account) && + account_xdata(*details.account).total_count) { + expr->compute(result, details, context); + result /= amount_t(long(account_xdata(*details.account).total_count)); + } + else { + result = 0L; + } + break; + } + + case F_PARENT: + if (details.account && details.account->parent) + left()->compute(result, details_t(*details.account->parent), context); + break; + + case F_ABS: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + result.abs(); + break; + } + + case F_ROUND: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + result.round(); + break; + } + + case F_COMMODITY: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + if (! result.is_type(value_t::AMOUNT)) + throw new compute_error("Argument to commodity() must be a commoditized amount", + new valexpr_context(expr)); + amount_t temp("1"); + temp.set_commodity(result.as_amount().commodity()); + result = temp; + break; + } + + case F_SET_COMMODITY: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + value_t temp; + expr->compute(temp, details, context); + + arg_index = 0; + expr = find_leaf(context, 1, arg_index); + expr->compute(result, details, context); + if (! result.is_type(value_t::AMOUNT)) + throw new compute_error + ("Second argument to set_commodity() must be a commoditized amount", + new valexpr_context(expr)); + amount_t one("1"); + one.set_commodity(result.as_amount().commodity()); + result = one; + + result *= temp; + break; + } + + case F_QUANTITY: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + + const balance_t * bal = NULL; + switch (result.type()) { + case value_t::BALANCE_PAIR: + bal = &(result.as_balance_pair().quantity()); + // fall through... + + case value_t::BALANCE: + if (! bal) + bal = &result.as_balance(); + + if (bal->amounts.size() < 2) { + result.cast(value_t::AMOUNT); + } else { + value_t temp; + for (balance_t::amounts_map::const_iterator i = bal->amounts.begin(); + i != bal->amounts.end(); + i++) { + amount_t x = (*i).second; + x.clear_commodity(); + temp += x; + } + result = temp; + assert(temp.is_type(value_t::AMOUNT)); + } + // fall through... + + case value_t::AMOUNT: + result.as_amount_lval().clear_commodity(); + break; + + default: + break; + } + break; + } + + case F_CODE_MASK: + if (details.entry && details.entry->code) + result = as_mask().match(*details.entry->code); + else + result = false; + break; + + case F_PAYEE_MASK: + if (details.entry) + result = as_mask().match(details.entry->payee); + else + result = false; + break; + + case F_NOTE_MASK: + if (details.xact && details.xact->note) + result = as_mask().match(*details.xact->note); + else + result = false; + break; + + case F_ACCOUNT_MASK: + if (details.account) + result = as_mask().match(details.account->fullname()); + else + result = false; + break; + + case F_SHORT_ACCOUNT_MASK: + if (details.account) + result = as_mask().match(details.account->name); + else + result = false; + break; + + case F_COMMODITY_MASK: + if (details.xact) + result = as_mask().match(details.xact->amount.commodity().base_symbol()); + else + result = false; + break; + + case O_ARG: { + long arg_index = 0; + assert(left()->kind == INDEX); + ptr_op_t expr = find_leaf(context, left()->as_long(), arg_index); + if (expr) + expr->compute(result, details, context); + else + result = 0L; + break; + } + + case O_COMMA: + if (! left()) + throw new compute_error("Comma operator missing left operand", + new valexpr_context(const_cast<op_t *>(this))); + if (! right()) + throw new compute_error("Comma operator missing right operand", + new valexpr_context(const_cast<op_t *>(this))); + left()->compute(result, details, context); + right()->compute(result, details, context); + break; + + case O_DEF: + result = 0L; + break; + + case O_REF: { + assert(left()); + if (right()) { + value_expr args(reduce_leaves(right(), details, context)); + left()->compute(result, details, args.get()); + } else { + left()->compute(result, details, context); + } + break; + } + + case F_VALUE: { + long arg_index = 0; + ptr_op_t expr = find_leaf(context, 0, arg_index); + expr->compute(result, details, context); + + arg_index = 0; + expr = find_leaf(context, 1, arg_index); + value_t moment; + expr->compute(moment, details, context); + if (! moment.is_type(value_t::DATETIME)) + throw new compute_error("Invalid date passed to P(value,date)", + new valexpr_context(expr)); + + result = result.value(moment.as_datetime()); + break; + } + + case O_NOT: + left()->compute(result, details, context); + if (result.strip_annotations()) + result = false; + else + result = true; + break; + + case O_QUES: { + assert(left()); + assert(right()); + assert(right()->kind == O_COL); + left()->compute(result, details, context); + if (result.strip_annotations()) + right()->left()->compute(result, details, context); + else + right()->right()->compute(result, details, context); + break; + } + + case O_AND: + assert(left()); + assert(right()); + left()->compute(result, details, context); + result = result.strip_annotations(); + if (result) + right()->compute(result, details, context); + break; + + case O_OR: + assert(left()); + assert(right()); + left()->compute(result, details, context); + if (! result.strip_annotations()) + right()->compute(result, details, context); + break; + + case O_NEQ: + case O_EQ: + case O_LT: + case O_LTE: + case O_GT: + case O_GTE: { + assert(left()); + assert(right()); + value_t temp; + left()->compute(temp, details, context); + right()->compute(result, details, context); + switch (kind) { + case O_NEQ: result = temp != result; break; + case O_EQ: result = temp == result; break; + case O_LT: result = temp < result; break; + case O_LTE: result = temp <= result; break; + case O_GT: result = temp > result; break; + case O_GTE: result = temp >= result; break; + default: assert(false); break; + } + break; + } + + case O_NEG: + assert(left()); + left()->compute(result, details, context); + result.negate(); + break; + + case O_ADD: + case O_SUB: + case O_MUL: + case O_DIV: { + assert(left()); + assert(right()); + value_t temp; + right()->compute(temp, details, context); + left()->compute(result, details, context); + switch (kind) { + case O_ADD: result += temp; break; + case O_SUB: result -= temp; break; + case O_MUL: result *= temp; break; + case O_DIV: result /= temp; break; + default: assert(false); break; + } + break; + } + + case O_PERC: { + assert(left()); + result = "100.0%"; + value_t temp; + left()->compute(temp, details, context); + result *= temp; + break; + } + + case LAST: + default: + assert(false); + break; + } + } + catch (error * err) { + if (err->context.empty() || + ! dynamic_cast<valexpr_context *>(err->context.back())) + err->context.push_back(new valexpr_context(const_cast<op_t *>(this))); + throw err; + } +} +#endif + +expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope) +{ + switch (kind) { + case IDENT: + if (ptr_op_t def = scope.lookup(as_ident())) { +#if 1 + return def; +#else + // Aren't definitions compiled when they go in? Would + // recompiling here really add any benefit? + return def->compile(scope); +#endif + } + return this; + + default: + break; + } + + if (kind < TERMINALS) + return this; + + ptr_op_t lhs(left()->compile(scope)); + ptr_op_t rhs(right() ? right()->compile(scope) : ptr_op_t()); + + if (lhs == left() && (! rhs || rhs == right())) + return this; + + ptr_op_t intermediate(copy(lhs, rhs)); + + if (lhs->is_value() && (! rhs || rhs->is_value())) + return wrap_value(intermediate->calc(scope)); + + return intermediate; +} + +value_t expr_t::op_t::calc(scope_t& scope) +{ + switch (kind) { + case VALUE: + return as_value(); + + case IDENT: + if (ptr_op_t reference = compile(scope)) { + if (reference != this) + return reference->calc(scope); + } + throw_(calc_error, "Unknown identifier '" << as_ident() << "'"); + + case FUNCTION: + // This should never be evaluated directly; it only appears as the + // left node of an O_CALL operator. + assert(false); + break; + + case O_CALL: { + call_scope_t call_args(scope); + + if (right()) + call_args.set_args(right()->calc(scope)); + + ptr_op_t func = left(); + string name; + + if (func->kind == IDENT) { + name = func->as_ident(); + ptr_op_t def = func->compile(scope); + if (def == func) + throw_(calc_error, + "Attempt to call unknown function '" << name << "'"); + func = def; + } + + if (func->kind != FUNCTION) + throw_(calc_error, "Attempt to call non-function"); + + return func->as_function()(call_args); + } + + case INDEX: { + call_scope_t args(scope); + + if (as_index() >= 0 && as_index() < args.size()) + return args[as_index()]; + else + throw_(calc_error, "Reference to non-existing argument " << as_index()); + break; + } + + case O_NEQ: + return left()->calc(scope) != right()->calc(scope); + case O_EQ: + return left()->calc(scope) == right()->calc(scope); + case O_LT: + return left()->calc(scope) < right()->calc(scope); + case O_LTE: + return left()->calc(scope) <= right()->calc(scope); + case O_GT: + return left()->calc(scope) > right()->calc(scope); + case O_GTE: + return left()->calc(scope) >= right()->calc(scope); + + case O_ADD: + return left()->calc(scope) + right()->calc(scope); + case O_SUB: + return left()->calc(scope) - right()->calc(scope); + case O_MUL: + return left()->calc(scope) * right()->calc(scope); + case O_DIV: + return left()->calc(scope) / right()->calc(scope); + + case O_NEG: + assert(! right()); + return left()->calc(scope).negate(); + + case O_NOT: + assert(! right()); + return ! left()->calc(scope); + + case O_AND: + return left()->calc(scope) && right()->calc(scope); + case O_OR: + return left()->calc(scope) || right()->calc(scope); + + case O_COMMA: { + value_t result(left()->calc(scope)); + + ptr_op_t next = right(); + while (next) { + ptr_op_t value_op; + if (next->kind == O_COMMA /* || next->kind == O_UNION */) { + value_op = next->left(); + next = next->right(); + } else { + value_op = next; + next = NULL; + } + + result.push_back(value_op->calc(scope)); + } + return result; + } + + case LAST: + default: + assert(false); + break; + } + + return NULL_VALUE; +} + +bool expr_t::op_t::print(std::ostream& out, print_context_t& context) const +{ + bool found = false; + + if (context.start_pos && this == context.op_to_find) { + *context.start_pos = (long)out.tellp() - 1; + found = true; + } + + string symbol; + + switch (kind) { + case VALUE: { + as_value().print(out, context.relaxed); + break; + } + + case IDENT: + out << as_ident(); + break; + + case FUNCTION: + out << "<FUNCTION>"; + break; + + case INDEX: + out << '@' << as_index(); + break; + + case O_NOT: + out << "!"; + if (left() && left()->print(out, context)) + found = true; + break; + case O_NEG: + out << "-"; + if (left() && left()->print(out, context)) + found = true; + break; + + case O_ADD: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " + "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_SUB: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " - "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_MUL: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " * "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_DIV: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " / "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case O_NEQ: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " != "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_EQ: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " == "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_LT: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " < "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_LTE: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " <= "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_GT: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " > "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_GTE: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " >= "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case O_AND: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " & "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + case O_OR: + out << "("; + if (left() && left()->print(out, context)) + found = true; + out << " | "; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case O_COMMA: + if (left() && left()->print(out, context)) + found = true; + out << ", "; + if (right() && right()->print(out, context)) + found = true; + break; + + case O_CALL: + if (left() && left()->print(out, context)) + found = true; + out << "("; + if (right() && right()->print(out, context)) + found = true; + out << ")"; + break; + + case LAST: + default: + assert(false); + break; + } + + if (! symbol.empty()) { + if (amount_t::current_pool->find(symbol)) + out << '@'; + out << symbol; + } + + if (context.end_pos && this == context.op_to_find) + *context.end_pos = (long)out.tellp() - 1; + + return found; +} + +void expr_t::op_t::dump(std::ostream& out, const int depth) const +{ + out.setf(std::ios::left); + out.width(10); + out << this << " "; + + for (int i = 0; i < depth; i++) + out << " "; + + switch (kind) { + case VALUE: + out << "VALUE - " << as_value(); + break; + + case IDENT: + out << "IDENT - " << as_ident(); + break; + + case INDEX: + out << "INDEX - " << as_index(); + break; + + case FUNCTION: + out << "FUNCTION"; + break; + + case O_CALL: out << "O_CALL"; break; + + case O_NOT: out << "O_NOT"; break; + case O_NEG: out << "O_NEG"; break; + + case O_ADD: out << "O_ADD"; break; + case O_SUB: out << "O_SUB"; break; + case O_MUL: out << "O_MUL"; break; + case O_DIV: out << "O_DIV"; break; + + case O_NEQ: out << "O_NEQ"; break; + case O_EQ: out << "O_EQ"; break; + case O_LT: out << "O_LT"; break; + case O_LTE: out << "O_LTE"; break; + case O_GT: out << "O_GT"; break; + case O_GTE: out << "O_GTE"; break; + + case O_AND: out << "O_AND"; break; + case O_OR: out << "O_OR"; break; + + case O_COMMA: out << "O_COMMA"; break; + + case LAST: + default: + assert(false); + break; + } + + out << " (" << refc << ')' << std::endl; + + if (kind > TERMINALS) { + if (left()) { + left()->dump(out, depth + 1); + if (right()) + right()->dump(out, depth + 1); + } else { + assert(! right()); + } + } +} + +void expr_t::op_t::read(std::ostream& in) +{ +} + +void expr_t::op_t::read(const char *& data) +{ +#if 0 + if (! read_bool(data)) + return expr_t::ptr_op_t(); + + expr_t::op_t::kind_t kind; + read_number(data, kind); + + expr_t::ptr_op_t expr = new expr_t::op_t(kind); + + if (kind > expr_t::op_t::TERMINALS) + expr->set_left(read_value_expr(data)); + + switch (expr->kind) { + case expr_t::op_t::INDEX: { + long temp; + read_long(data, temp); + expr->set_index(temp); + break; + } + case expr_t::op_t::VALUE: { + value_t temp; + read_value(data, temp); + expr->set_value(temp); + break; + } + + case expr_t::op_t::MASK: + if (read_bool(data)) + read_mask(data, expr->as_mask_lval()); + break; + + default: + if (kind > expr_t::op_t::TERMINALS) + expr->set_right(read_value_expr(data)); + break; + } + + return expr; +#endif +} + +void expr_t::op_t::write(std::ostream& out) const +{ +#if 0 + if (! expr) { + write_bool(out, false); + return; + } + + write_bool(out, true); + write_number(out, expr->kind); + + if (expr->kind > expr_t::op_t::TERMINALS) + write_value_expr(out, expr->left()); + + switch (expr->kind) { + case expr_t::op_t::INDEX: + write_long(out, expr->as_index()); + break; + case expr_t::op_t::IDENT: + write_long(out, expr->as_ident()); + break; + case expr_t::op_t::VALUE: + write_value(out, expr->as_value()); + break; + + case expr_t::op_t::MASK: + if (expr->as_mask()) { + write_bool(out, true); + write_mask(out, expr->as_mask()); + } else { + write_bool(out, false); + } + break; + + default: + if (expr->kind > expr_t::op_t::TERMINALS) + write_value_expr(out, expr->right()); + break; + } +#endif +} + +#if 0 +class op_predicate : public noncopyable +{ + ptr_op_t op; + + op_predicate(); + +public: + explicit op_predicate(ptr_op_t _op) : op(_op) { + TRACE_CTOR(op_predicate, "ptr_op_t"); + } + ~op_predicate() throw() { + TRACE_DTOR(op_predicate); + } + bool operator()(scope_t& scope) { + return op->calc(scope).to_boolean(); + } +}; + +#endif + +} // namespace ledger @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _OP_H +#define _OP_H + +#include "expr.h" +#include "mask.h" + +namespace ledger { + +class expr_t::op_t : public noncopyable +{ + friend class expr_t; + friend class expr_t::parser_t; + + op_t(); + +public: + typedef expr_t::ptr_op_t ptr_op_t; + +private: + mutable short refc; + ptr_op_t left_; + + variant<unsigned int, // used by constant INDEX + value_t, // used by constant VALUE + string, // used by constant IDENT + mask_t, // used by constant MASK + function_t, // used by terminal FUNCTION + ptr_op_t> // used by all binary operators + data; + +public: + enum kind_t { + // Constants + VALUE, + IDENT, + MASK, + INDEX, + + CONSTANTS, + + FUNCTION, + + TERMINALS, + + // Binary operators + O_NOT, + O_EQ, + O_NEQ, + O_LT, + O_LTE, + O_GT, + O_GTE, + + O_AND, + O_OR, + + O_NEG, + O_ADD, + O_SUB, + O_MUL, + O_DIV, + + O_QUERY, + O_COLON, + + O_COMMA, + + O_CALL, + + OPERATORS, + + LAST + }; + + kind_t kind; + + explicit op_t(const kind_t _kind) : refc(0), kind(_kind) { + TRACE_CTOR(op_t, "const kind_t"); + } + ~op_t() { + TRACE_DTOR(op_t); + assert(refc == 0); + } + + bool is_index() const { + return data.type() == typeid(unsigned int); + } + unsigned int& as_index_lval() { + assert(kind == INDEX); + return boost::get<unsigned int>(data); + } + const unsigned int& as_index() const { + return const_cast<op_t *>(this)->as_index_lval(); + } + void set_index(unsigned int val) { + data = val; + } + + bool is_value() const { + if (kind == VALUE) { + assert(data.type() == typeid(value_t)); + return true; + } + return false; + } + value_t& as_value_lval() { + assert(is_value()); + value_t& val(boost::get<value_t>(data)); + assert(val.valid()); + return val; + } + const value_t& as_value() const { + return const_cast<op_t *>(this)->as_value_lval(); + } + void set_value(const value_t& val) { + assert(val.valid()); + data = val; + } + + bool is_ident() const { + if (kind == IDENT) { + assert(data.type() == typeid(string)); + return true; + } + return false; + } + string& as_ident_lval() { + assert(is_ident()); + return boost::get<string>(data); + } + const string& as_ident() const { + return const_cast<op_t *>(this)->as_ident_lval(); + } + void set_ident(const string& val) { + data = val; + } + + bool is_mask() const { + if (kind == MASK) { + assert(data.type() == typeid(mask_t)); + return true; + } + return false; + } + mask_t& as_mask_lval() { + assert(is_mask()); + return boost::get<mask_t>(data); + } + const mask_t& as_mask() const { + return const_cast<op_t *>(this)->as_mask_lval(); + } + void set_mask(const mask_t& val) { + data = val; + } + void set_mask(const string& expr) { + data = mask_t(expr); + } + + bool is_function() const { + return kind == FUNCTION; + } + function_t& as_function_lval() { + assert(kind == FUNCTION); + return boost::get<function_t>(data); + } + const function_t& as_function() const { + return const_cast<op_t *>(this)->as_function_lval(); + } + void set_function(const function_t& val) { + data = val; + } + + ptr_op_t& left() { + return left_; + } + const ptr_op_t& left() const { + assert(kind > TERMINALS); + return left_; + } + void set_left(const ptr_op_t& expr) { + assert(kind > TERMINALS); + left_ = expr; + } + + ptr_op_t& as_op_lval() { + assert(kind > TERMINALS); + return boost::get<ptr_op_t>(data); + } + const ptr_op_t& as_op() const { + return const_cast<op_t *>(this)->as_op_lval(); + } + + ptr_op_t& right() { + assert(kind > TERMINALS); + return as_op_lval(); + } + const ptr_op_t& right() const { + assert(kind > TERMINALS); + return as_op(); + } + void set_right(const ptr_op_t& expr) { + assert(kind > TERMINALS); + data = expr; + } + +private: + void acquire() const { + DEBUG("ledger.xpath.memory", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + } + void release() const { + DEBUG("ledger.xpath.memory", + "Releasing " << this << ", refc now " << refc - 1); + assert(refc > 0); + if (--refc == 0) + checked_delete(this); + } + + friend inline void intrusive_ptr_add_ref(op_t * op) { + op->acquire(); + } + friend inline void intrusive_ptr_release(op_t * op) { + op->release(); + } + + static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL, + ptr_op_t _right = NULL); + + ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const { + return new_node(kind, _left, _right); + } + +public: + ptr_op_t compile(scope_t& scope); + value_t calc(scope_t& scope); + + struct print_context_t + { + scope_t& scope; + const bool relaxed; + const ptr_op_t& op_to_find; + unsigned long * start_pos; + unsigned long * end_pos; + + print_context_t(scope_t& _scope, + const bool _relaxed = false, + const ptr_op_t& _op_to_find = ptr_op_t(), + unsigned long * _start_pos = NULL, + unsigned long * _end_pos = NULL) + : scope(_scope), relaxed(_relaxed), op_to_find(_op_to_find), + start_pos(_start_pos), end_pos(_end_pos) {} + }; + + bool print(std::ostream& out, print_context_t& context) const; + void dump(std::ostream& out, const int depth) const; + + void read(std::ostream& in); + void read(const char *& data); + void write(std::ostream& out) const; + + static ptr_op_t wrap_value(const value_t& val); + static ptr_op_t wrap_functor(const function_t& fobj); +}; + +inline expr_t::ptr_op_t +expr_t::op_t::new_node(kind_t _kind, ptr_op_t _left, ptr_op_t _right) +{ + ptr_op_t node(new op_t(_kind)); + node->set_left(_left); + node->set_right(_right); + return node; +} + +inline expr_t::ptr_op_t expr_t::op_t::wrap_value(const value_t& val) { + ptr_op_t temp(new op_t(op_t::VALUE)); + temp->set_value(val); + return temp; +} + +inline expr_t::ptr_op_t expr_t::op_t::wrap_functor(const function_t& fobj) { + ptr_op_t temp(new op_t(op_t::FUNCTION)); + temp->set_function(fobj); + return temp; +} + +#define MAKE_FUNCTOR(x) expr_t::op_t::wrap_functor(bind(&x, this, _1)) +#define WRAP_FUNCTOR(x) expr_t::op_t::wrap_functor(x) + +} // namespace ledger + +#endif // _OP_H @@ -34,9 +34,9 @@ namespace ledger { namespace { - typedef tuple<expr::ptr_op_t, bool> op_bool_tuple; + typedef tuple<expr_t::ptr_op_t, bool> op_bool_tuple; - op_bool_tuple find_option(expr::scope_t& scope, const string& name) + op_bool_tuple find_option(scope_t& scope, const string& name) { char buf[128]; std::strcpy(buf, "option_"); @@ -49,7 +49,7 @@ namespace { } *p = '\0'; - expr::ptr_op_t op = scope.lookup(buf); + expr_t::ptr_op_t op = scope.lookup(buf); if (op) return op_bool_tuple(op, false); @@ -59,14 +59,14 @@ namespace { return op_bool_tuple(scope.lookup(buf), true); } - op_bool_tuple find_option(expr::scope_t& scope, const char letter) + op_bool_tuple find_option(scope_t& scope, const char letter) { char buf[10]; std::strcpy(buf, "option_"); buf[7] = letter; buf[8] = '\0'; - expr::ptr_op_t op = scope.lookup(buf); + expr_t::ptr_op_t op = scope.lookup(buf); if (op) return op_bool_tuple(op, false); @@ -76,13 +76,13 @@ namespace { return op_bool_tuple(scope.lookup(buf), true); } - void process_option(const expr::function_t& opt, - expr::scope_t& scope, const char * arg) + void process_option(const function_t& opt, scope_t& scope, + const char * arg) { #if 0 try { #endif - expr::call_scope_t args(scope); + call_scope_t args(scope); if (arg) args.push_back(value_t(arg, true)); @@ -101,7 +101,7 @@ namespace { } } -void process_option(const string& name, expr::scope_t& scope, +void process_option(const string& name, scope_t& scope, const char * arg) { op_bool_tuple opt(find_option(scope, name)); @@ -110,7 +110,7 @@ void process_option(const string& name, expr::scope_t& scope, } void process_environment(const char ** envp, const string& tag, - expr::scope_t& scope) + scope_t& scope) { const char * tag_p = tag.c_str(); unsigned int tag_len = tag.length(); @@ -149,7 +149,7 @@ void process_environment(const char ** envp, const string& tag, } void process_arguments(int argc, char ** argv, const bool anywhere, - expr::scope_t& scope, std::list<string>& args) + scope_t& scope, std::list<string>& args) { for (char ** i = argv; *i; i++) { if ((*i)[0] != '-') { @@ -190,7 +190,7 @@ void process_arguments(int argc, char ** argv, const bool anywhere, throw_(option_error, "illegal option -"); } else { - typedef tuple<expr::ptr_op_t, bool, char> op_bool_char_tuple; + typedef tuple<expr_t::ptr_op_t, bool, char> op_bool_char_tuple; std::list<op_bool_char_tuple> option_queue; @@ -32,19 +32,18 @@ #ifndef _OPTION_H #define _OPTION_H -#include "valexpr.h" +#include "scope.h" namespace ledger { -void process_option(const string& name, expr::scope_t& scope, +void process_option(const string& name, scope_t& scope, const char * arg = NULL); void process_environment(const char ** envp, const string& tag, - expr::scope_t& scope); + scope_t& scope); void process_arguments(int argc, char ** argv, const bool anywhere, - expr::scope_t& scope, - std::list<string>& args); + scope_t& scope, std::list<string>& args); DECLARE_EXCEPTION(error, option_error); diff --git a/parser.cc b/parser.cc new file mode 100644 index 00000000..a03ea9df --- /dev/null +++ b/parser.cc @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "parser.h" + +namespace ledger { + +expr_t::ptr_op_t +expr_t::parser_t::parse_value_term(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node; + + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::VALUE: + node = new op_t(op_t::VALUE); + node->set_value(tok.value); + break; + + case token_t::MASK: { + // A /mask/ is just a shorthand for calling match(). + node = new op_t(op_t::O_CALL); + + ptr_op_t ident = new op_t(op_t::IDENT); + ident->set_ident("match"); + node->set_left(ident); + + ptr_op_t args = new op_t(op_t::O_COMMA); + node->set_right(args); + + ptr_op_t mask = new op_t(op_t::MASK); + mask->set_mask(tok.value.as_string()); + + ident = new op_t(op_t::IDENT); + + args->set_left(mask); + args->set_right(ident); + + switch (tok.flags()) { + case TOKEN_SHORT_ACCOUNT_MASK: + ident->set_ident("account_base"); + break; + case TOKEN_CODE_MASK: + ident->set_ident("code"); + break; +#if 0 + case TOKEN_COMMODITY_MASK: + ident->set_ident("commodity"); + break; +#endif + case TOKEN_PAYEE_MASK: + ident->set_ident("payee"); + break; + case TOKEN_NOTE_MASK: + ident->set_ident("note"); + break; + case TOKEN_ACCOUNT_MASK: + ident->set_ident("account"); + break; + } + break; + } + + case token_t::IDENT: { +#if 0 +#ifdef USE_BOOST_PYTHON + if (tok.value->as_string() == "lambda") // special + try { + char c, buf[4096]; + + std::strcpy(buf, "lambda "); + READ_INTO(in, &buf[7], 4000, c, true); + + ptr_op_t eval = new op_t(op_t::O_EVAL); + ptr_op_t lambda = new op_t(op_t::FUNCTION); + lambda->functor = new python_functor_t(python_eval(buf)); + eval->set_left(lambda); + ptr_op_t sym = new op_t(op_t::SYMBOL); + sym->name = new string("__ptr"); + eval->set_right(sym); + + node = eval; + + goto done; + } + catch(const boost::python::error_already_set&) { + throw_(parse_error, "Error parsing lambda expression"); + } +#endif /* USE_BOOST_PYTHON */ +#endif + + string ident = tok.value.as_string(); + + // An identifier followed by ( represents a function call + tok = next_token(in, tflags); + + if (tok.kind == token_t::LPAREN) { + node = new op_t(op_t::IDENT); + node->set_ident(ident); + + ptr_op_t call_node(new op_t(op_t::O_CALL)); + call_node->set_left(node); + call_node->set_right(parse_value_expr(in, tflags | EXPR_PARSE_PARTIAL)); + + tok = next_token(in, tflags); + if (tok.kind != token_t::RPAREN) + tok.unexpected(0xff, ')'); + + node = call_node; + } else { + if (std::isdigit(ident[0])) { + node = new op_t(op_t::INDEX); + node->set_index(lexical_cast<unsigned int>(ident.c_str())); + } else { + node = new op_t(op_t::IDENT); + node->set_ident(ident); + } + push_token(tok); + } + break; + } + + case token_t::LPAREN: + node = new op_t(op_t::O_COMMA); + node->set_left(parse_value_expr(in, tflags | EXPR_PARSE_PARTIAL)); + if (! node->left()) + throw_(parse_error, tok.symbol << " operator not followed by argument"); + + tok = next_token(in, tflags); + if (tok.kind != token_t::RPAREN) + tok.unexpected(0xff, ')'); + break; + + default: + push_token(tok); + break; + } + +#if 0 +#ifdef USE_BOOST_PYTHON + done: +#endif +#endif + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_unary_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node; + + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::EXCLAM: { + ptr_op_t term(parse_value_term(in, tflags)); + if (! term) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + + // A very quick optimization + if (term->kind == op_t::VALUE) { + term->as_value_lval().in_place_negate(); + node = term; + } else { + node = new op_t(op_t::O_NOT); + node->set_left(term); + } + break; + } + + case token_t::MINUS: { + ptr_op_t term(parse_value_term(in, tflags)); + if (! term) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + + // A very quick optimization + if (term->kind == op_t::VALUE) { + term->as_value_lval().in_place_negate(); + node = term; + } else { + node = new op_t(op_t::O_NEG); + node->set_left(term); + } + break; + } + + default: + push_token(tok); + node = parse_value_term(in, tflags); + break; + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_mul_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_unary_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::STAR || tok.kind == token_t::KW_DIV) { + ptr_op_t prev(node); + node = new op_t(tok.kind == token_t::STAR ? + op_t::O_MUL : op_t::O_DIV); + node->set_left(prev); + node->set_right(parse_mul_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + + tok = next_token(in, tflags); + } + push_token(tok); + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_add_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_mul_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::PLUS || + tok.kind == token_t::MINUS) { + ptr_op_t prev(node); + node = new op_t(tok.kind == token_t::PLUS ? + op_t::O_ADD : op_t::O_SUB); + node->set_left(prev); + node->set_right(parse_add_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + + tok = next_token(in, tflags); + } + push_token(tok); + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_logic_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_add_expr(in, tflags)); + + if (node) { + op_t::kind_t kind = op_t::LAST; + flags_t _flags = tflags; + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::EQUAL: + if (tflags & EXPR_PARSE_NO_ASSIGN) + tok.rewind(in); + else + kind = op_t::O_EQ; + break; + case token_t::NEQUAL: + kind = op_t::O_NEQ; + break; + case token_t::LESS: + kind = op_t::O_LT; + break; + case token_t::LESSEQ: + kind = op_t::O_LTE; + break; + case token_t::GREATER: + kind = op_t::O_GT; + break; + case token_t::GREATEREQ: + kind = op_t::O_GTE; + break; + default: + push_token(tok); + break; + } + + if (kind != op_t::LAST) { + ptr_op_t prev(node); + node = new op_t(kind); + node->set_left(prev); + node->set_right(parse_add_expr(in, _flags)); + + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + } + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_and_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_logic_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::KW_AND) { + ptr_op_t prev(node); + node = new op_t(op_t::O_AND); + node->set_left(prev); + node->set_right(parse_and_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_or_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_and_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::KW_OR) { + ptr_op_t prev(node); + node = new op_t(op_t::O_OR); + node->set_left(prev); + node->set_right(parse_or_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse_value_expr(std::istream& in, + const flags_t tflags) const +{ + ptr_op_t node(parse_or_expr(in, tflags)); + + if (node) { + token_t& tok = next_token(in, tflags); + + if (tok.kind == token_t::COMMA) { + ptr_op_t prev(node); + node = new op_t(op_t::O_COMMA); + node->set_left(prev); + node->set_right(parse_value_expr(in, tflags)); + if (! node->right()) + throw_(parse_error, + tok.symbol << " operator not followed by argument"); + tok = next_token(in, tflags); + } + + if (tok.kind != token_t::TOK_EOF) { + if (tflags & EXPR_PARSE_PARTIAL) + push_token(tok); + else + tok.unexpected(); + } + } + else if (! (tflags & EXPR_PARSE_PARTIAL)) { + throw_(parse_error, "Failed to parse value expression"); + } + + return node; +} + +expr_t::ptr_op_t +expr_t::parser_t::parse(std::istream& in, const flags_t flags) +{ +#if 0 + try { +#endif + ptr_op_t top_node = parse_value_expr(in, flags); + + if (use_lookahead) { + use_lookahead = false; + lookahead.rewind(in); + } + lookahead.clear(); + + return top_node; +#if 0 + } + catch (error * err) { + err->context.push_back + (new line_context(str, (long)in.tellg() - 1, + "While parsing value expression:")); + throw err; + } +#endif +} + +} // namespace ledger @@ -1,137 +1,102 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + #ifndef _PARSER_H #define _PARSER_H -#include "utils.h" +#include "token.h" +#include "op.h" namespace ledger { -class account_t; -class journal_t; -class session_t; - -class parser_t : public noncopyable +class expr_t::parser_t : public noncopyable { -public: - parser_t() { - TRACE_CTOR(parser_t, ""); - } - virtual ~parser_t() { - TRACE_DTOR(parser_t); +#define EXPR_PARSE_NORMAL 0x00 +#define EXPR_PARSE_PARTIAL 0x01 +#define EXPR_PARSE_NO_MIGRATE 0x02 +#define EXPR_PARSE_NO_REDUCE 0x04 +#define EXPR_PARSE_NO_ASSIGN 0x08 +#define EXPR_PARSE_NO_DATES 0x10 + + mutable token_t lookahead; + mutable bool use_lookahead; + + token_t& next_token(std::istream& in, token_t::flags_t tflags) const + { + if (use_lookahead) + use_lookahead = false; + else + lookahead.next(in, tflags); + return lookahead; } - virtual bool test(std::istream& in) const = 0; - - virtual unsigned int parse(std::istream& in, - session_t& session, - journal_t& journal, - account_t * master = NULL, - const path * original_file = NULL) = 0; -}; - -unsigned int parse_journal(std::istream& in, - session_t& session, - journal_t& journal, - account_t * master = NULL, - const path * original_file = NULL); - -unsigned int parse_journal_file(const path& path, - session_t& session, - journal_t& journal, - account_t * master = NULL, - const path * original_file = NULL); + void push_token(const token_t& tok) const + { + assert(&tok == &lookahead); + use_lookahead = true; + } -unsigned int parse_ledger_data(session_t& session, - journal_t& journal, - parser_t * cache_parser = NULL, - parser_t * xml_parser = NULL, - parser_t * stdin_parser = NULL); + void push_token() const + { + use_lookahead = true; + } -class parse_error : public error -{ public: - parse_error(const string& reason, error_context * ctxt = NULL) throw() - : error(reason, ctxt) {} - virtual ~parse_error() throw() {} -}; - -/************************************************************************ - * - * General utility parsing functions - */ - -inline char * skip_ws(char * ptr) { - while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') - ptr++; - return ptr; -} + typedef uint_least8_t flags_t; + +private: + ptr_op_t parse_value_term(std::istream& in, const flags_t flags) const; + ptr_op_t parse_unary_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_mul_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_add_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_logic_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_and_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_or_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_querycolon_expr(std::istream& in, const flags_t flags) const; + ptr_op_t parse_value_expr(std::istream& in, const flags_t flags) const; -inline char * next_element(char * buf, bool variable = false) { - for (char * p = buf; *p; p++) { - if (! (*p == ' ' || *p == '\t')) - continue; - - if (! variable) { - *p = '\0'; - return skip_ws(p + 1); - } - else if (*p == '\t') { - *p = '\0'; - return skip_ws(p + 1); - } - else if (*(p + 1) == ' ') { - *p = '\0'; - return skip_ws(p + 2); - } +public: + parser_t() : use_lookahead(false) { + TRACE_CTOR(parser_t, ""); } - return NULL; -} - -inline char peek_next_nonws(std::istream& in) { - char c = in.peek(); - while (! in.eof() && std::isspace(c)) { - in.get(c); - c = in.peek(); + ~parser_t() throw() { + TRACE_DTOR(parser_t); } - return c; -} - -#define READ_INTO(str, targ, size, var, cond) { \ - char * _p = targ; \ - var = str.peek(); \ - while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \ - str.get(var); \ - if (str.eof()) \ - break; \ - if (var == '\\') { \ - str.get(var); \ - if (in.eof()) \ - break; \ - } \ - *_p++ = var; \ - var = str.peek(); \ - } \ - *_p = '\0'; \ -} -#define READ_INTO_(str, targ, size, var, idx, cond) { \ - char * _p = targ; \ - var = str.peek(); \ - while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \ - str.get(var); \ - if (str.eof()) \ - break; \ - idx++; \ - if (var == '\\') { \ - str.get(var); \ - if (in.eof()) \ - break; \ - idx++; \ - } \ - *_p++ = var; \ - var = str.peek(); \ - } \ - *_p = '\0'; \ -} + ptr_op_t parse(std::istream& in, const flags_t flags = EXPR_PARSE_NORMAL); + ptr_op_t parse(string& str, const flags_t flags = EXPR_PARSE_NORMAL) { + std::istringstream stream(str); + return parse(stream, flags); + } +}; } // namespace ledger diff --git a/predicate.h b/predicate.h new file mode 100644 index 00000000..1913e1d7 --- /dev/null +++ b/predicate.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PREDICATE_H +#define _PREDICATE_H + +#include "expr.h" +#include "scope.h" + +namespace ledger { + +template <typename T> +class item_predicate +{ +public: + expr_t predicate; + + item_predicate() { + TRACE_CTOR(item_predicate, ""); + } + item_predicate(const item_predicate& other) : predicate(other.predicate) { + TRACE_CTOR(item_predicate, "copy"); + } + item_predicate(const expr_t& _predicate) : predicate(_predicate) { + TRACE_CTOR(item_predicate, "const expr_t&"); + } + item_predicate(const string& _predicate) : predicate(expr_t(_predicate)) { + TRACE_CTOR(item_predicate, "const string&"); + } + ~item_predicate() throw() { + TRACE_DTOR(item_predicate); + } + + bool operator()(const T& item) const { +#if 0 + template context_t<T> context(item); + return ! predicate || predicate->calc(context).strip_annotations(); +#else + return false; +#endif + } +}; + +} // namespace ledger + +#endif // _PREDICATE_H diff --git a/pyinterp.cc b/pyinterp.cc index 85d2a422..d702f291 100644 --- a/pyinterp.cc +++ b/pyinterp.cc @@ -86,12 +86,12 @@ struct python_run } }; -python_interpreter_t::python_interpreter_t(expr::scope_t& parent) - : expr::symbol_scope_t(parent), +python_interpreter_t::python_interpreter_t(expr_t::scope_t& parent) + : expr_t::symbol_scope_t(parent), mmodule(borrowed(PyImport_AddModule("__main__"))), nspace(handle<>(borrowed(PyModule_GetDict(mmodule.get())))) { - TRACE_CTOR(python_interpreter_t, "expr::scope_t&"); + TRACE_CTOR(python_interpreter_t, "expr_t::scope_t&"); Py_Initialize(); boost::python::detail::init_module("ledger", &initialize_for_python); @@ -179,7 +179,7 @@ object python_interpreter_t::eval(const string& str, py_eval_mode_t mode) } value_t python_interpreter_t::functor_t::operator() - (expr::call_scope_t& args) + (expr_t::call_scope_t& args) { try { if (! PyCallable_Check(func.ptr())) { @@ -202,7 +202,7 @@ value_t python_interpreter_t::functor_t::operator() } else if (PyObject * err = PyErr_Occurred()) { PyErr_Print(); - throw_(expr::calc_error, + throw_(expr_t::calc_error, "While calling Python function '" /*<< name() <<*/ "': " << err); } else { assert(false); @@ -214,14 +214,14 @@ value_t python_interpreter_t::functor_t::operator() } catch (const error_already_set&) { PyErr_Print(); - throw_(expr::calc_error, + throw_(expr_t::calc_error, "While calling Python function '" /*<< name() <<*/ "'"); } return NULL_VALUE; } value_t python_interpreter_t::lambda_t::operator() - (expr::call_scope_t& args) + (expr_t::call_scope_t& args) { try { assert(args.size() == 1); @@ -231,7 +231,7 @@ value_t python_interpreter_t::lambda_t::operator() } catch (const error_already_set&) { PyErr_Print(); - throw_(expr::calc_error, + throw_(expr_t::calc_error, "While evaluating Python lambda expression"); } return NULL_VALUE; @@ -32,7 +32,7 @@ #ifndef _PYINTERP_H #define _PYINTERP_H -#include "valexpr.h" +#include "scope.h" #include <boost/python.hpp> #include <Python.h> @@ -40,7 +40,7 @@ namespace ledger { class python_interpreter_t - : public noncopyable, public expr::symbol_scope_t + : public noncopyable, public expr_t::symbol_scope_t { boost::python::handle<> mmodule; @@ -49,7 +49,7 @@ class python_interpreter_t public: boost::python::dict nspace; - python_interpreter_t(expr::scope_t& parent); + python_interpreter_t(expr_t::scope_t& parent); virtual ~python_interpreter_t() { TRACE_DTOR(python_interpreter_t); @@ -89,14 +89,14 @@ public: virtual ~functor_t() throw() { TRACE_DTOR(functor_t); } - virtual value_t operator()(expr::call_scope_t& args); + virtual value_t operator()(expr_t::call_scope_t& args); }; - virtual expr::ptr_op_t lookup(const string& name) { + virtual expr_t::ptr_op_t lookup(const string& name) { if (boost::python::object func = eval(name)) return WRAP_FUNCTOR(functor_t(name, func)); else - return expr::symbol_scope_t::lookup(name); + return expr_t::symbol_scope_t::lookup(name); } class lambda_t : public functor_t { @@ -111,7 +111,7 @@ public: virtual ~lambda_t() throw() { TRACE_DTOR(lambda_t); } - virtual value_t operator()(expr::call_scope_t& args); + virtual value_t operator()(expr_t::call_scope_t& args); }; }; @@ -1,11 +1,11 @@ #ifndef _QIF_H #define _QIF_H -#include "parser.h" +#include "journal.h" namespace ledger { -class qif_parser_t : public parser_t +class qif_parser_t : public journal_t::parser_t { public: virtual bool test(std::istream& in) const; @@ -278,7 +278,7 @@ void report_t::entry_report(const entry_t& entry, const string& format) { } -value_t report_t::abbrev(expr::call_scope_t& args) +value_t report_t::abbrev(call_scope_t& args) { if (args.size() < 2) throw_(std::logic_error, "usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])"); @@ -304,7 +304,7 @@ value_t report_t::abbrev(expr::call_scope_t& args) #endif } -value_t report_t::ftime(expr::call_scope_t& args) +value_t report_t::ftime(call_scope_t& args) { if (args.size() < 1) throw_(std::logic_error, "usage: ftime(DATE [, DATE_FORMAT])"); @@ -327,7 +327,7 @@ value_t report_t::ftime(expr::call_scope_t& args) #if 0 optional<value_t> -report_t::resolve(const string& name, expr::call_scope_t& args) +report_t::resolve(const string& name, call_scope_t& args) { const char * p = name.c_str(); switch (*p) { @@ -343,11 +343,11 @@ report_t::resolve(const string& name, expr::call_scope_t& args) } break; } - return expr::scope_t::resolve(name, args); + return scope_t::resolve(name, args); } #endif -expr::ptr_op_t report_t::lookup(const string& name) +expr_t::ptr_op_t report_t::lookup(const string& name) { const char * p = name.c_str(); switch (*p) { @@ -453,7 +453,7 @@ expr::ptr_op_t report_t::lookup(const string& name) break; } - return expr::symbol_scope_t::lookup(name); + return symbol_scope_t::lookup(name); } } // namespace ledger @@ -81,7 +81,7 @@ namespace ledger { // says that the formatter should be "flushed" after the entities are // iterated. This does not happen for the commodities iteration, however. -class report_t : public expr::symbol_scope_t +class report_t : public symbol_scope_t { report_t(); @@ -134,7 +134,7 @@ public: session_t& session; explicit report_t(session_t& _session) - : expr::symbol_scope_t(downcast<expr::scope_t>(_session)), + : symbol_scope_t(downcast<scope_t>(_session)), head_entries(0), tail_entries(0), @@ -166,10 +166,10 @@ public: #if 0 eval("t=total,TOT=0,T()=(TOT=TOT+t,TOT)"); -#endif - value_expr::amount_expr.reset(new value_expr("a")); - value_expr::total_expr.reset(new value_expr("O")); + value_expr_t::amount_expr.reset(new value_expr("a")); + value_expr_t::total_expr.reset(new value_expr("O")); +#endif } virtual ~report_t() { @@ -201,8 +201,8 @@ public: // Utility functions for value expressions // - value_t ftime(expr::call_scope_t& args); - value_t abbrev(expr::call_scope_t& args); + value_t ftime(call_scope_t& args); + value_t abbrev(call_scope_t& args); // // Config options @@ -213,35 +213,35 @@ public: expr(expr).compile((xml::document_t *)NULL, this); #endif } - value_t option_eval(expr::call_scope_t& args) { + value_t option_eval(call_scope_t& args) { eval(args[0].as_string()); return NULL_VALUE; } - value_t option_amount(expr::call_scope_t& args) { + value_t option_amount(call_scope_t& args) { eval(string("t=") + args[0].as_string()); return NULL_VALUE; } - value_t option_total(expr::call_scope_t& args) { + value_t option_total(call_scope_t& args) { eval(string("T()=") + args[0].as_string()); return NULL_VALUE; } - value_t option_format(expr::call_scope_t& args) { + value_t option_format(call_scope_t& args) { format_string = args[0].as_string(); return NULL_VALUE; } - value_t option_raw(expr::call_scope_t& args) { + value_t option_raw(call_scope_t& args) { raw_mode = true; return NULL_VALUE; } - value_t option_foo(expr::call_scope_t& args) { + value_t option_foo(call_scope_t& args) { std::cout << "This is foo" << std::endl; return NULL_VALUE; } - value_t option_bar(expr::call_scope_t& args) { + value_t option_bar(call_scope_t& args) { std::cout << "This is bar: " << args[0] << std::endl; return NULL_VALUE; } @@ -251,44 +251,44 @@ public: // #if 0 - value_t option_select(expr::call_scope_t& args) { + value_t option_select(call_scope_t& args) { transforms.push_back(new select_transform(args[0].as_string())); return NULL_VALUE; } - value_t option_limit(expr::call_scope_t& args) { + value_t option_limit(call_scope_t& args) { string expr = (string("//xact[") + args[0].as_string() + "]"); transforms.push_back(new select_transform(expr)); return NULL_VALUE; } - value_t option_remove(expr::call_scope_t& args) { + value_t option_remove(call_scope_t& args) { transforms.push_back(new remove_transform(args[0].as_string())); return NULL_VALUE; } - value_t option_accounts(expr::call_scope_t& args) { + value_t option_accounts(call_scope_t& args) { transforms.push_back(new accounts_transform); return NULL_VALUE; } - value_t option_compact(expr::call_scope_t& args) { + value_t option_compact(call_scope_t& args) { transforms.push_back(new compact_transform); return NULL_VALUE; } - value_t option_clean(expr::call_scope_t& args) { + value_t option_clean(call_scope_t& args) { transforms.push_back(new clean_transform); return NULL_VALUE; } - value_t option_entries(expr::call_scope_t& args) { + value_t option_entries(call_scope_t& args) { transforms.push_back(new entries_transform); return NULL_VALUE; } - value_t option_split(expr::call_scope_t& args) { + value_t option_split(call_scope_t& args) { transforms.push_back(new split_transform); return NULL_VALUE; } - value_t option_merge(expr::call_scope_t& args) { + value_t option_merge(call_scope_t& args) { transforms.push_back(new merge_transform); return NULL_VALUE; } @@ -298,7 +298,7 @@ public: // Scope members // - virtual expr::ptr_op_t lookup(const string& name); + virtual expr_t::ptr_op_t lookup(const string& name); }; string abbrev(const string& str, unsigned int width, diff --git a/scope.cc b/scope.cc new file mode 100644 index 00000000..872c5454 --- /dev/null +++ b/scope.cc @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "scope.h" + +namespace ledger { + +void symbol_scope_t::define(const string& name, expr_t::ptr_op_t def) +{ + DEBUG("ledger.xpath.syms", "Defining '" << name << "' = " << def); + + std::pair<symbol_map::iterator, bool> result + = symbols.insert(symbol_map::value_type(name, def)); + if (! result.second) { + symbol_map::iterator i = symbols.find(name); + assert(i != symbols.end()); + symbols.erase(i); + + std::pair<symbol_map::iterator, bool> result2 + = symbols.insert(symbol_map::value_type(name, def)); + if (! result2.second) + throw_(compile_error, + "Redefinition of '" << name << "' in same scope"); + } +} + +value_t get_amount(scope_t& scope) +{ + assert("I can't get the amount!"); + return NULL_VALUE; +} + +expr_t::ptr_op_t symbol_scope_t::lookup(const string& name) +{ + switch (name[0]) { + case 'a': + if (name[1] == '\0' || name == "amount") + return WRAP_FUNCTOR(bind(get_amount, _1)); + break; + } + + symbol_map::const_iterator i = symbols.find(name); + if (i != symbols.end()) + return (*i).second; + + return child_scope_t::lookup(name); +} + +#if 0 +namespace { + int count_leaves(expr_t::ptr_op_t expr) + { + int count = 0; + if (expr->kind != expr_t::op_t::O_COMMA) { + count = 1; + } else { + count += count_leaves(expr->left()); + count += count_leaves(expr->right()); + } + return count; + } + + expr_t::ptr_op_t reduce_leaves(expr_t::ptr_op_t expr, + expr_t::ptr_op_t context) + { + if (! expr) + return NULL; + + expr_t::ptr_op_t temp; + + if (expr->kind != expr_t::op_t::O_COMMA) { + if (expr->kind < expr_t::op_t::TERMINALS) { + temp.reset(expr); + } else { + temp.reset(new op_t(expr_t::op_t::VALUE)); + temp->set_value(NULL_VALUE); + expr->compute(temp->as_value_lval(), context); + } + } else { + temp.reset(new op_t(expr_t::op_t::O_COMMA)); + temp->set_left(reduce_leaves(expr->left(), context)); + temp->set_right(reduce_leaves(expr->right(), context)); + } + return temp.release(); + } + + expr_t::ptr_op_t find_leaf(expr_t::ptr_op_t context, int goal, long& found) + { + if (! context) + return NULL; + + if (context->kind != expr_t::op_t::O_COMMA) { + if (goal == found++) + return context; + } else { + expr_t::ptr_op_t expr = find_leaf(context->left(), goal, found); + if (expr) + return expr; + expr = find_leaf(context->right(), goal, found); + if (expr) + return expr; + } + return NULL; + } +} +#endif + +} // namespace ledger diff --git a/scope.h b/scope.h new file mode 100644 index 00000000..4a031104 --- /dev/null +++ b/scope.h @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SCOPE_H +#define _SCOPE_H + +#include "expr.h" +#include "op.h" + +namespace ledger { + +class scope_t : public noncopyable +{ + scope_t(); + +protected: + enum type_t { + CHILD_SCOPE, + SYMBOL_SCOPE, + CALL_SCOPE, + CONTEXT_SCOPE + } type_; + +public: + explicit scope_t(type_t _type) : type_(_type) { + TRACE_CTOR(scope_t, "type_t"); + } + virtual ~scope_t() { + TRACE_DTOR(scope_t); + } + + const type_t type() const { + return type_; + } + + void define(const string& name, const value_t& val) { + define(name, expr_t::op_t::wrap_value(val)); + } + void define(const string& name, const function_t& func) { + define(name, expr_t::op_t::wrap_functor(func)); + } + + value_t resolve(const string& name) { + expr_t::ptr_op_t definition = lookup(name); + if (definition) + return definition->calc(*this); + else + return NULL_VALUE; + } + + virtual void define(const string& name, expr_t::ptr_op_t def) = 0; + virtual expr_t::ptr_op_t lookup(const string& name) = 0; + +protected: + virtual optional<scope_t&> find_scope(const type_t _type, + bool skip_this = false) = 0; + virtual optional<scope_t&> find_first_scope(const type_t _type1, + const type_t _type2, + bool skip_this = false) = 0; + template <typename T> + T& find_scope(bool skip_this = false) { + assert(false); + } + template <typename T> + optional<T&> maybe_find_scope(bool skip_this = false) { + assert(false); + } + + friend class child_scope_t; + friend class expr_t::op_t; +}; + +class child_scope_t : public scope_t +{ + scope_t * parent; + + child_scope_t(); + +public: + explicit child_scope_t(type_t _type = CHILD_SCOPE) + : scope_t(_type), parent(NULL) { + TRACE_CTOR(child_scope_t, "type_t"); + } + explicit child_scope_t(scope_t& _parent, type_t _type = CHILD_SCOPE) + : scope_t(_type), parent(&_parent) { + TRACE_CTOR(child_scope_t, "scope_t&, type_t"); + } + virtual ~child_scope_t() { + TRACE_DTOR(child_scope_t); + } + + virtual void define(const string& name, expr_t::ptr_op_t def) { + if (parent) + parent->define(name, def); + } + virtual expr_t::ptr_op_t lookup(const string& name) { + if (parent) + return parent->lookup(name); + return expr_t::ptr_op_t(); + } + +protected: + virtual optional<scope_t&> find_scope(type_t _type, + bool skip_this = false) { + for (scope_t * ptr = (skip_this ? parent : this); ptr; ) { + if (ptr->type() == _type) + return *ptr; + + ptr = polymorphic_downcast<child_scope_t *>(ptr)->parent; + } + return none; + } + + virtual optional<scope_t&> find_first_scope(const type_t _type1, + const type_t _type2, + bool skip_this = false) { + for (scope_t * ptr = (skip_this ? parent : this); ptr; ) { + if (ptr->type() == _type1 || ptr->type() == _type2) + return *ptr; + + ptr = polymorphic_downcast<child_scope_t *>(ptr)->parent; + } + return none; + } +}; + +class symbol_scope_t : public child_scope_t +{ + typedef std::map<const string, expr_t::ptr_op_t> symbol_map; + + symbol_map symbols; + +public: + explicit symbol_scope_t() + : child_scope_t(SYMBOL_SCOPE) { + TRACE_CTOR(symbol_scope_t, ""); + } + explicit symbol_scope_t(scope_t& _parent) + : child_scope_t(_parent, SYMBOL_SCOPE) { + TRACE_CTOR(symbol_scope_t, "scope_t&"); + } + virtual ~symbol_scope_t() { + TRACE_DTOR(symbol_scope_t); + } + + void define(const string& name, const value_t& val) { + scope_t::define(name, val); + } + void define(const string& name, const function_t& func) { + scope_t::define(name, func); + } + + virtual void define(const string& name, expr_t::ptr_op_t def); + virtual expr_t::ptr_op_t lookup(const string& name); +}; + +class call_scope_t : public child_scope_t +{ + value_t args; + + call_scope_t(); + +public: + explicit call_scope_t(scope_t& _parent) + : child_scope_t(_parent, CALL_SCOPE) { + TRACE_CTOR(call_scope_t, "scope_t&"); + } + virtual ~call_scope_t() { + TRACE_DTOR(call_scope_t); + } + + void set_args(const value_t& _args) { + args = _args; + } + value_t& value() { + return args; + } + + value_t& operator[](const unsigned int index) { + // jww (2008-07-21): exception here if it's out of bounds + return args[index]; + } + const value_t& operator[](const unsigned int index) const { + // jww (2008-07-21): exception here if it's out of bounds + return args[index]; + } + + void push_back(const value_t& val) { + args.push_back(val); + } + void pop_back() { + args.pop_back(); + } + + const std::size_t size() const { + return args.size(); + } +}; + +template <typename T> +class var_t : public noncopyable +{ + T * value; + + var_t(); + +public: + // jww (2008-07-21): Give a good exception here if we can't find "name" + var_t(scope_t& scope, const string& name) + : value(scope.resolve(name).template as_pointer<T>()) { + TRACE_CTOR(var_t, "scope_t&, const string&"); + } + var_t(call_scope_t& scope, const unsigned int idx) + : value(scope[idx].template as_pointer<T>()) { + TRACE_CTOR(var_t, "call_scope_t&, const unsigned int"); + } + ~var_t() throw() { + TRACE_DTOR(var_t); + } + + T& operator *() { return *value; } + T * operator->() { return value; } +}; + +#if 0 +class context_scope_t : public child_scope_t +{ +public: + value_t current_element; + std::size_t element_index; + std::size_t sequence_size; + + explicit context_scope_t(scope_t& _parent, + const value_t& _element = NULL_VALUE, + const std::size_t _element_index = 0, + const std::size_t _sequence_size = 0) + : child_scope_t(_parent, CONTEXT_SCOPE), current_element(_element), + element_index(_element_index), sequence_size(_sequence_size) + { + TRACE_CTOR(expr::context_scope_t, "scope_t&, const value_t&, ..."); + } + virtual ~context_scope_t() { + TRACE_DTOR(expr::context_scope_t); + } + + const std::size_t index() const { + return element_index; + } + const std::size_t size() const { + return sequence_size; + } + + value_t& value() { + return current_element; + } +}; + +struct context_t +{ + const entry_t * entry() { + return NULL; + } + const transaction_t * xact() { + return NULL; + } + const account_t * account() { + return NULL; + } +}; + +struct entry_context_t : public context_t +{ + const entry_t * entry_; + + const entry_t * entry() { + return entry_; + } +}; + +struct xact_context_t : public context_t +{ + const transaction_t * xact_; + + const entry_t * entry() { + return xact_->entry; + } + const transaction_t * xact() { + return xact_; + } + const account_t * account() { + return xact_->account; + } +}; + +struct account_context_t : public context_t +{ + const account_t * account_; + + const account_t * account() { + return account_; + } +}; +#endif + +template<> +inline symbol_scope_t& +scope_t::find_scope<symbol_scope_t>(bool skip_this) { + optional<scope_t&> scope = find_scope(SYMBOL_SCOPE, skip_this); + assert(scope); + return downcast<symbol_scope_t>(*scope); +} + +template<> +inline call_scope_t& +scope_t::find_scope<call_scope_t>(bool skip_this) { + optional<scope_t&> scope = find_scope(CALL_SCOPE, skip_this); + assert(scope); + return downcast<call_scope_t>(*scope); +} + +#if 0 +template<> +inline context_scope_t& +scope_t::find_scope<context_scope_t>(bool skip_this) { + optional<scope_t&> scope = find_scope(CONTEXT_SCOPE, skip_this); + assert(scope); + return downcast<context_scope_t>(*scope); +} +#endif + +#define FIND_SCOPE(scope_type, scope_ref) \ + downcast<scope_t>(scope_ref).find_scope<scope_type>() + +#define CALL_SCOPE(scope_ref) \ + FIND_SCOPE(call_scope_t, scope_ref) +#define SYMBOL_SCOPE(scope_ref) \ + FIND_SCOPE(symbol_scope_t, scope_ref) +#if 0 +#define CONTEXT_SCOPE(scope_ref) \ + FIND_SCOPE(context_scope_t, scope_ref) +#endif + +} // namespace ledger + +#endif // _SCOPE_H + @@ -30,7 +30,6 @@ */ #include "session.h" -#include "parsexp.h" #include "walk.h" namespace ledger { @@ -41,9 +40,6 @@ session_t * session_t::current = NULL; boost::mutex session_t::session_mutex; #endif -static void initialize(); -static void shutdown(); - void set_session_context(session_t * session) { #if 0 @@ -51,10 +47,10 @@ void set_session_context(session_t * session) #endif if (session && ! session_t::current) { - initialize(); + session_t::initialize(); } else if (! session && session_t::current) { - shutdown(); + session_t::shutdown(); #if 0 session_t::session_mutex.unlock(); #endif @@ -127,7 +123,7 @@ std::size_t session_t::read_journal(journal_t& journal, if (! master) master = journal.master; - foreach (parser_t& parser, parsers) + foreach (journal_t::parser_t& parser, parsers) if (parser.test(in)) return parser.parse(in, *this, journal, master, &pathname); @@ -264,7 +260,7 @@ void session_t::clean_accounts() } #if 0 -value_t session_t::resolve(const string& name, expr::scope_t& locals) +value_t session_t::resolve(const string& name, expr_t::scope_t& locals) { const char * p = name.c_str(); switch (*p) { @@ -291,11 +287,11 @@ value_t session_t::resolve(const string& name, expr::scope_t& locals) return value_t(register_format, true); break; } - return expr::scope_t::resolve(name, locals); + return expr_t::scope_t::resolve(name, locals); } #endif -expr::ptr_op_t session_t::lookup(const string& name) +expr_t::ptr_op_t session_t::lookup(const string& name) { const char * p = name.c_str(); switch (*p) { @@ -332,21 +328,21 @@ expr::ptr_op_t session_t::lookup(const string& name) break; } - return expr::symbol_scope_t::lookup(name); + return symbol_scope_t::lookup(name); } // jww (2007-04-26): All of Ledger should be accessed through a // session_t object -static void initialize() +void session_t::initialize() { amount_t::initialize(); value_t::initialize(); - value_expr::initialize(); + expr_t::initialize(); } -static void shutdown() +void session_t::shutdown() { - value_expr::shutdown(); + expr_t::shutdown(); value_t::shutdown(); amount_t::shutdown(); } @@ -32,15 +32,20 @@ #ifndef _SESSION_H #define _SESSION_H -#include "valexpr.h" +#include "scope.h" #include "journal.h" -#include "parser.h" namespace ledger { -class session_t : public expr::symbol_scope_t +class session_t : public symbol_scope_t { - public: + static void initialize(); + static void shutdown(); + + friend void set_session_context(session_t * session); + friend void release_session_context(); + +public: static session_t * current; path data_file; @@ -76,8 +81,8 @@ class session_t : public expr::symbol_scope_t bool ansi_codes; bool ansi_invert; - ptr_list<journal_t> journals; - ptr_list<parser_t> parsers; + ptr_list<journal_t> journals; + ptr_list<journal_t::parser_t> parsers; account_t * master; mutable accounts_map accounts_cache; @@ -121,11 +126,11 @@ class session_t : public expr::symbol_scope_t std::size_t read_data(journal_t& journal, const string& master_account = ""); - void register_parser(parser_t * parser) { + void register_parser(journal_t::parser_t * parser) { parsers.push_back(parser); } - void unregister_parser(parser_t * parser) { - for (ptr_list<parser_t>::iterator i = parsers.begin(); + void unregister_parser(journal_t::parser_t * parser) { + for (ptr_list<journal_t::parser_t>::iterator i = parsers.begin(); i != parsers.end(); i++) if (&*i == parser) { @@ -167,13 +172,13 @@ class session_t : public expr::symbol_scope_t // Scope members // - virtual expr::ptr_op_t lookup(const string& name); + virtual expr_t::ptr_op_t lookup(const string& name); // // Help options // - value_t option_version(expr::scope_t&) { + value_t option_version(scope_t&) { std::cout << "Ledger " << ledger::version << ", the command-line accounting tool"; std::cout << "\n\nCopyright (c) 2003-2008, John Wiegley. All rights reserved.\n\n\ This program is made available under the terms of the BSD Public License.\n\ @@ -193,17 +198,17 @@ See LICENSE file included with the distribution for details and disclaimer.\n"; // Debug options // - value_t option_trace_(expr::scope_t& locals) { + value_t option_trace_(scope_t& locals) { return NULL_VALUE; } - value_t option_debug_(expr::scope_t& locals) { + value_t option_debug_(scope_t& locals) { return NULL_VALUE; } - value_t option_verify(expr::scope_t&) { + value_t option_verify(scope_t&) { return NULL_VALUE; } - value_t option_verbose(expr::scope_t&) { + value_t option_verbose(scope_t&) { #if defined(LOGGING_ON) if (_log_level < LOG_INFO) _log_level = LOG_INFO; @@ -215,7 +220,7 @@ See LICENSE file included with the distribution for details and disclaimer.\n"; // Option handlers // - value_t option_file_(expr::call_scope_t& args) { + value_t option_file_(call_scope_t& args) { assert(args.size() == 1); data_file = args[0].as_string(); return NULL_VALUE; @@ -223,11 +228,11 @@ See LICENSE file included with the distribution for details and disclaimer.\n"; #if 0 #if defined(USE_BOOST_PYTHON) - value_t option_import_(expr::call_scope_t& args) { + value_t option_import_(call_scope_t& args) { python_import(optarg); return NULL_VALUE; } - value_t option_import_stdin(expr::call_scope_t& args) { + value_t option_import_stdin(call_scope_t& args) { python_eval(std::cin, PY_EVAL_MULTI); return NULL_VALUE; } diff --git a/test/numerics/t_expr.cc b/test/numerics/t_expr.cc new file mode 100644 index 00000000..026b4eec --- /dev/null +++ b/test/numerics/t_expr.cc @@ -0,0 +1,25 @@ +#include "t_valexpr.h" + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ValueExprTestCase, "numerics"); + +void ValueExprTestCase::setUp() +{ + ledger::set_session_context(&session); + + // Cause the display precision for dollars to be initialized to 2. + amount_t x1("$1.00"); + assertTrue(x1); + + amount_t::stream_fullstrings = true; // make reports from UnitTests accurate +} + +void ValueExprTestCase::tearDown() +{ + amount_t::stream_fullstrings = false; + + ledger::set_session_context(); +} + +void ValueExprTestCase::testConstructors() +{ +} diff --git a/test/numerics/t_expr.h b/test/numerics/t_expr.h new file mode 100644 index 00000000..3cac4ed6 --- /dev/null +++ b/test/numerics/t_expr.h @@ -0,0 +1,30 @@ +#ifndef _T_VALEXPR_H +#define _T_VALEXPR_H + +#include "UnitTests.h" + +class ValueExprTestCase : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE(ValueExprTestCase); + + CPPUNIT_TEST(testConstructors); + + CPPUNIT_TEST_SUITE_END(); + +public: + ledger::session_t session; + + ValueExprTestCase() {} + virtual ~ValueExprTestCase() {} + + virtual void setUp(); + virtual void tearDown(); + + void testConstructors(); + +private: + ValueExprTestCase(const ValueExprTestCase ©); + void operator=(const ValueExprTestCase ©); +}; + +#endif // _T_VALEXPR_H @@ -2,11 +2,9 @@ #define _XOPEN_SOURCE #endif -#include "journal.h" #include "textual.h" -#include "valexpr.h" -#include "parsexp.h" -#include "utils.h" +#include "expr.h" +#include "parser.h" #include "acconf.h" #define TIMELOG_SUPPORT 1 @@ -50,14 +48,12 @@ struct time_entry_t #endif namespace { - value_expr parse_amount_expr(std::istream& in, - amount_t& amount, - transaction_t * xact, - unsigned short flags = 0) + expr_t parse_amount_expr(std::istream& in, + amount_t& amount, + transaction_t * xact, + unsigned short flags = 0) { - value_expr expr = - value_expr::parser->parse(in, flags | - EXPR_PARSE_RELAXED | EXPR_PARSE_PARTIAL); + expr_t expr(in, flags | EXPR_PARSE_PARTIAL); DEBUG("ledger.textual.parse", "line " << linenum << ": " << "Parsed an amount expression"); @@ -71,12 +67,13 @@ namespace { } #endif +#if 0 if (expr) { - if (! expr::compute_amount(expr, amount, xact)) + if (! expr_t::compute_amount(expr, amount, xact)) throw new parse_error("Amount expression failed to compute"); #if 0 - if (expr->kind == expr::node_t::VALUE) { + if (expr->kind == expr_t::node_t::VALUE) { expr = NULL; } else { DEBUG_IF("ledger.textual.parse") { @@ -88,6 +85,7 @@ namespace { expr = value_expr(); #endif } +#endif DEBUG("ledger.textual.parse", "line " << linenum << ": " << "The transaction amount is " << xact->amount); @@ -201,7 +199,7 @@ transaction_t * parse_transaction(char * line, account_t * account, // always NULL right now if (xact->amount_expr) { unsigned long end = (long)in.tellg(); - xact->amount_expr.expr_str = string(line, beg, end - beg); + xact->amount_expr.set_text(string(line, beg, end - beg)); } } catch (error * err) { @@ -247,11 +245,11 @@ transaction_t * parse_transaction(char * line, account_t * account, if (xact->cost_expr) { unsigned long end = (long)in.tellg(); if (per_unit) - xact->cost_expr->expr_str = (string("@") + - string(line, beg, end - beg)); + xact->cost_expr->set_text(string("@") + + string(line, beg, end - beg)); else - xact->cost_expr->expr_str = (string("@@") + - string(line, beg, end - beg)); + xact->cost_expr->set_text(string("@@") + + string(line, beg, end - beg)); } } catch (error * err) { @@ -951,7 +949,7 @@ unsigned int textual_parser_t::parse(std::istream& in, } else if (word == "def") { #if 0 - if (! expr::global_scope.get()) + if (! expr_t::global_scope.get()) init_value_expr(); parse_value_definition(p); #endif @@ -1088,11 +1086,13 @@ void write_textual_journal(journal_t& journal, path pathname, while (! in.eof()) { entry_base_t * base = NULL; if (el != journal.entries.end() && pos == (*el)->beg_pos) { +#if 0 hdr_fmt.format(out, details_t(**el)); +#endif base = *el++; } else if (al != journal.auto_entries.end() && pos == (*al)->beg_pos) { - out << "= " << (*al)->predicate.predicate.expr_str << '\n'; + out << "= " << (*al)->predicate.predicate.text() << '\n'; base = *al++; } else if (pl != journal.period_entries.end() && pos == (*pl)->beg_pos) { @@ -1,13 +1,13 @@ #ifndef _TEXTUAL_H #define _TEXTUAL_H -#include "parser.h" +#include "journal.h" #include "format.h" #include "walk.h" namespace ledger { -class textual_parser_t : public parser_t +class textual_parser_t : public journal_t::parser_t { public: virtual bool test(std::istream& in) const; diff --git a/token.cc b/token.cc new file mode 100644 index 00000000..3d5eeb21 --- /dev/null +++ b/token.cc @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "token.h" +#include "parser.h" + +namespace ledger { + +void expr_t::token_t::parse_ident(std::istream& in) +{ + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + char c = peek_next_nonws(in); + + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + kind = IDENT; + length = 0; + + clear_flags(); + + char buf[256]; + READ_INTO_(in, buf, 255, c, length, + std::isalnum(c) || c == '_' || c == '.' || c == '-'); + + switch (buf[0]) { +#if 0 + case 'a': + if (std::strcmp(buf, "and") == 0) + kind = KW_AND; + break; + case 'd': + if (std::strcmp(buf, "div") == 0) + kind = KW_DIV; + break; + case 'e': + if (std::strcmp(buf, "eq") == 0) + kind = EQUAL; + break; +#endif + case 'f': + if (std::strcmp(buf, "false") == 0) { + kind = VALUE; + value = false; + } + break; +#if 0 + case 'g': + if (std::strcmp(buf, "gt") == 0) + kind = GREATER; + else if (std::strcmp(buf, "ge") == 0) + kind = GREATEREQ; + break; + case 'i': + if (std::strcmp(buf, "is") == 0) + kind = EQUAL; + break; + case 'l': + if (std::strcmp(buf, "lt") == 0) + kind = LESS; + else if (std::strcmp(buf, "le") == 0) + kind = LESSEQ; + break; + case 'm': + if (std::strcmp(buf, "mod") == 0) + kind = KW_MOD; + break; + case 'n': + if (std::strcmp(buf, "ne") == 0) + kind = NEQUAL; + break; + case 'o': + if (std::strcmp(buf, "or") == 0) + kind = KW_OR; + break; +#endif + case 't': + if (std::strcmp(buf, "true") == 0) { + kind = VALUE; + value = true; + } + break; + } + + if (kind == IDENT) + value.set_string(buf); +} + +void expr_t::token_t::next(std::istream& in, const unsigned int pflags) +{ + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + char c = peek_next_nonws(in); + + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + symbol[0] = c; + symbol[1] = '\0'; + + length = 1; + + switch (c) { + case '&': + in.get(c); + kind = KW_AND; + break; + + case '(': + in.get(c); + kind = LPAREN; + break; + case ')': + in.get(c); + kind = RPAREN; + break; + + case '[': { + in.get(c); + + char buf[256]; + READ_INTO_(in, buf, 255, c, length, c != ']'); + if (c != ']') + unexpected(c, ']'); + + in.get(c); + length++; + + interval_t timespan(buf); + kind = VALUE; + value = timespan.first(); + break; + } + + case '\'': + case '"': { + char delim; + in.get(delim); + char buf[4096]; + READ_INTO_(in, buf, 4095, c, length, c != delim); + if (c != delim) + unexpected(c, delim); + in.get(c); + length++; + kind = VALUE; + value.set_string(buf); + break; + } + + case '{': { + in.get(c); + amount_t temp; + temp.parse(in, AMOUNT_PARSE_NO_MIGRATE); + in.get(c); + if (c != '}') + unexpected(c, '}'); + length++; + kind = VALUE; + value = temp; + break; + } + + case '!': + in.get(c); + c = in.peek(); + if (c == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = NEQUAL; + length = 2; + break; + } + kind = EXCLAM; + break; + + case '-': + in.get(c); + kind = MINUS; + break; + case '+': + in.get(c); + kind = PLUS; + break; + + case '*': + in.get(c); + kind = STAR; + break; + + case 'c': + case 'C': + case 'p': + case 'w': + case 'W': + case 'e': + case '/': { + bool code_mask = c == 'c'; + bool commodity_mask = c == 'C'; + bool payee_mask = c == 'p'; + bool note_mask = c == 'e'; + bool short_account_mask = c == 'w'; + + in.get(c); + if (c == '/') { + c = peek_next_nonws(in); + if (c == '/') { + in.get(c); + c = in.peek(); + if (c == '/') { + in.get(c); + c = in.peek(); + short_account_mask = true; + } else { + payee_mask = true; + } + } + } else { + in.get(c); + } + + // Read in the regexp + char buf[256]; + READ_INTO_(in, buf, 255, c, length, c != '/'); + if (c != '/') + unexpected(c, '/'); + in.get(c); + length++; + + if (short_account_mask) + set_flags(TOKEN_SHORT_ACCOUNT_MASK); + else if (code_mask) + set_flags(TOKEN_CODE_MASK); + else if (commodity_mask) + set_flags(TOKEN_COMMODITY_MASK); + else if (payee_mask) + set_flags(TOKEN_PAYEE_MASK); + else if (note_mask) + set_flags(TOKEN_NOTE_MASK); + else + set_flags(TOKEN_ACCOUNT_MASK); + + kind = MASK; + value.set_string(buf); + break; + } + + case '=': + in.get(c); + kind = EQUAL; + break; + + case '<': + in.get(c); + if (in.peek() == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = LESSEQ; + length = 2; + break; + } + kind = LESS; + break; + + case '>': + in.get(c); + if (in.peek() == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = GREATEREQ; + length = 2; + break; + } + kind = GREATER; + break; + + case ',': + in.get(c); + kind = COMMA; + break; + + default: { + amount_t temp; + unsigned long pos = 0; + + // When in relaxed parsing mode, we want to migrate commodity + // flags so that any precision specified by the user updates the + // current maximum displayed precision. + pos = (long)in.tellg(); + + amount_t::flags_t parse_flags = 0; + if (pflags & EXPR_PARSE_NO_MIGRATE) + parse_flags |= AMOUNT_PARSE_NO_MIGRATE; + if (pflags & EXPR_PARSE_NO_REDUCE) + parse_flags |= AMOUNT_PARSE_NO_REDUCE; + + if (! temp.parse(in, parse_flags | AMOUNT_PARSE_SOFT_FAIL)) { + // If the amount had no commodity, it must be an unambiguous + // variable reference + + in.clear(); + in.seekg(pos, std::ios::beg); + + c = in.peek(); + assert(! (std::isdigit(c) || c == '.')); + parse_ident(in); + } else { + kind = VALUE; + value = temp; + } + break; + } + } +} + +void expr_t::token_t::rewind(std::istream& in) +{ + for (unsigned int i = 0; i < length; i++) + in.unget(); +} + + +void expr_t::token_t::unexpected() +{ + switch (kind) { + case TOK_EOF: + throw_(parse_error, "Unexpected end of expression"); + case IDENT: + throw_(parse_error, "Unexpected symbol '" << value << "'"); + case VALUE: + throw_(parse_error, "Unexpected value '" << value << "'"); + default: + throw_(parse_error, "Unexpected operator '" << symbol << "'"); + } +} + +void expr_t::token_t::unexpected(char c, char wanted) +{ + if ((unsigned char) c == 0xff) { + if (wanted) + throw_(parse_error, "Missing '" << wanted << "'"); + else + throw_(parse_error, "Unexpected end"); + } else { + if (wanted) + throw_(parse_error, "Invalid char '" << c + << "' (wanted '" << wanted << "')"); + else + throw_(parse_error, "Invalid char '" << c << "'"); + } +} + +} // namespace ledger diff --git a/token.h b/token.h new file mode 100644 index 00000000..df08b6bc --- /dev/null +++ b/token.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2003-2008, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TOKEN_H +#define _TOKEN_H + +#include "expr.h" + +namespace ledger { + +struct expr_t::token_t : public noncopyable, public supports_flags<> +{ +#define TOKEN_SHORT_ACCOUNT_MASK 0x01 +#define TOKEN_CODE_MASK 0x02 +#define TOKEN_COMMODITY_MASK 0x04 +#define TOKEN_PAYEE_MASK 0x08 +#define TOKEN_NOTE_MASK 0x10 +#define TOKEN_ACCOUNT_MASK 0x20 + + enum kind_t { + VALUE, // any kind of literal value + IDENT, // [A-Za-z_][-A-Za-z0-9_:]* + MASK, // /regexp/ + + LPAREN, // ( + RPAREN, // ) + + EQUAL, // == + NEQUAL, // != + LESS, // < + LESSEQ, // <= + GREATER, // > + GREATEREQ, // >= + + ASSIGN, // = + MINUS, // - + PLUS, // + + STAR, // * + KW_DIV, // / + + EXCLAM, // ! + KW_AND, // & + KW_OR, // | + KW_MOD, // % + + COMMA, // , + + TOK_EOF, + UNKNOWN + + } kind; + + char symbol[3]; + value_t value; + std::size_t length; + + explicit token_t() : supports_flags<>(), kind(UNKNOWN), length(0) { + TRACE_CTOR(token_t, ""); + } + ~token_t() throw() { + TRACE_DTOR(token_t); + } + + token_t& operator=(const token_t& other) { + if (&other == this) + return *this; + assert(false); + return *this; + } + + void clear() { + kind = UNKNOWN; + length = 0; + value = NULL_VALUE; + + symbol[0] = '\0'; + symbol[1] = '\0'; + symbol[2] = '\0'; + } + + void parse_ident(std::istream& in); + void next(std::istream& in, unsigned int flags); + void rewind(std::istream& in); + void unexpected(); + + static void unexpected(char c, char wanted = '\0'); +}; + +} // namespace ledger + +#endif // _TOKEN_H @@ -539,6 +539,80 @@ inline const string& either_or(const string& first, return first.empty() ? second : first; } +inline char * skip_ws(char * ptr) { + while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') + ptr++; + return ptr; +} + +inline char * next_element(char * buf, bool variable = false) { + for (char * p = buf; *p; p++) { + if (! (*p == ' ' || *p == '\t')) + continue; + + if (! variable) { + *p = '\0'; + return skip_ws(p + 1); + } + else if (*p == '\t') { + *p = '\0'; + return skip_ws(p + 1); + } + else if (*(p + 1) == ' ') { + *p = '\0'; + return skip_ws(p + 2); + } + } + return NULL; +} + +inline char peek_next_nonws(std::istream& in) { + char c = in.peek(); + while (! in.eof() && std::isspace(c)) { + in.get(c); + c = in.peek(); + } + return c; +} + +#define READ_INTO(str, targ, size, var, cond) { \ + char * _p = targ; \ + var = str.peek(); \ + while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \ + str.get(var); \ + if (str.eof()) \ + break; \ + if (var == '\\') { \ + str.get(var); \ + if (in.eof()) \ + break; \ + } \ + *_p++ = var; \ + var = str.peek(); \ + } \ + *_p = '\0'; \ +} + +#define READ_INTO_(str, targ, size, var, idx, cond) { \ + char * _p = targ; \ + var = str.peek(); \ + while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \ + str.get(var); \ + if (str.eof()) \ + break; \ + idx++; \ + if (var == '\\') { \ + str.get(var); \ + if (in.eof()) \ + break; \ + idx++; \ + } \ + *_p++ = var; \ + var = str.peek(); \ + } \ + *_p = '\0'; \ +} + } // namespace ledger #endif // _UTILS_H @@ -14,6 +14,7 @@ bool compare_items<transaction_t>::operator()(const transaction_t * left, assert(left); assert(right); +#if 0 transaction_xdata_t& lxdata(transaction_xdata(*left)); if (! (lxdata.dflags & TRANSACTION_SORT_CALC)) { sort_order.compute(lxdata.sort_value, details_t(*left)); @@ -34,6 +35,9 @@ bool compare_items<transaction_t>::operator()(const transaction_t * left, "rxdata.sort_value = " << rxdata.sort_value); return lxdata.sort_value < rxdata.sort_value; +#else + return false; +#endif } transaction_xdata_t& transaction_xdata(const transaction_t& xact) @@ -379,7 +383,9 @@ void changed_value_transactions::output_diff(const datetime_t& current) value_t cur_bal; transaction_xdata(*last_xact).date = current; +#if 0 compute_total(cur_bal, details_t(*last_xact)); +#endif cur_bal.round(); // jww (2008-04-24): What does this do? #if 0 @@ -413,7 +419,9 @@ void changed_value_transactions::operator()(transaction_t& xact) item_handler<transaction_t>::operator()(xact); +#if 0 compute_total(last_balance, details_t(xact)); +#endif last_balance.round(); last_xact = &xact; @@ -853,6 +861,7 @@ bool compare_items<account_t>::operator()(const account_t * left, assert(left); assert(right); +#if 0 account_xdata_t& lxdata(account_xdata(*left)); if (! (lxdata.dflags & ACCOUNT_SORT_CALC)) { sort_order.compute(lxdata.sort_value, details_t(*left)); @@ -866,6 +875,9 @@ bool compare_items<account_t>::operator()(const account_t * left, } return lxdata.sort_value < rxdata.sort_value; +#else + return false; +#endif } account_xdata_t& account_xdata(const account_t& account) @@ -891,7 +903,9 @@ void sum_accounts(account_t& account) } value_t result; +#if 0 compute_amount(result, details_t(account)); +#endif if (! result.is_realzero()) xdata.total += result; xdata.total_count += xdata.count; @@ -36,7 +36,7 @@ typedef shared_ptr<item_handler<transaction_t> > xact_handler_ptr; template <typename T> class compare_items { - value_expr sort_order; + expr_t sort_order; compare_items(); @@ -44,7 +44,7 @@ public: compare_items(const compare_items& other) : sort_order(other.sort_order) { TRACE_CTOR(compare_items, "copy"); } - compare_items(const value_expr& _sort_order) : sort_order(_sort_order) { + compare_items(const expr_t& _sort_order) : sort_order(_sort_order) { TRACE_CTOR(compare_items, "const value_expr&"); } ~compare_items() throw() { @@ -61,8 +61,10 @@ bool compare_items<T>::operator()(const T * left, const T * right) value_t left_result; value_t right_result; +#if 0 sort_order.compute(left_result, details_t(*left)); sort_order.compute(right_result, details_t(*right)); +#endif return left_result < right_result; } @@ -354,13 +356,13 @@ class sort_transactions : public item_handler<transaction_t> typedef std::deque<transaction_t *> transactions_deque; transactions_deque transactions; - const value_expr sort_order; + const expr_t sort_order; sort_transactions(); public: sort_transactions(xact_handler_ptr handler, - const value_expr& _sort_order) + const expr_t& _sort_order) : item_handler<transaction_t>(handler), sort_order(_sort_order) { TRACE_CTOR(sort_transactions, @@ -398,7 +400,7 @@ class sort_entries : public item_handler<transaction_t> public: sort_entries(xact_handler_ptr handler, - const value_expr& _sort_order) + const expr_t& _sort_order) : sorter(handler, _sort_order) { TRACE_CTOR(sort_entries, "xact_handler_ptr, const value_expr&"); @@ -436,7 +438,7 @@ class filter_transactions : public item_handler<transaction_t> public: filter_transactions(xact_handler_ptr handler, - const value_expr& predicate) + const expr_t& predicate) : item_handler<transaction_t>(handler), pred(predicate) { TRACE_CTOR(filter_transactions, "xact_handler_ptr, const value_expr&"); @@ -540,7 +542,7 @@ class component_transactions : public item_handler<transaction_t> public: component_transactions(xact_handler_ptr handler, - const value_expr& predicate) + const expr_t& predicate) : item_handler<transaction_t>(handler), pred(predicate) { TRACE_CTOR(component_transactions, "xact_handler_ptr, const value_expr&"); @@ -883,9 +885,9 @@ class forecast_transactions : public generate_transactions public: forecast_transactions(xact_handler_ptr handler, - const value_expr& predicate) + const expr_t& predicate) : generate_transactions(handler), pred(predicate) { - TRACE_CTOR(forecast_transactions, "xact_handler_ptr, const value_expr&"); + TRACE_CTOR(forecast_transactions, "xact_handler_ptr, const expr_t&"); } forecast_transactions(xact_handler_ptr handler, const string& predicate) @@ -972,7 +974,7 @@ public: class sorted_accounts_iterator : public noncopyable { - value_expr sort_cmp; + expr_t sort_cmp; typedef std::deque<account_t *> accounts_deque_t; @@ -983,11 +985,11 @@ class sorted_accounts_iterator : public noncopyable public: sorted_accounts_iterator(const string& sort_order) { TRACE_CTOR(sorted_accounts_iterator, "const string&"); - sort_cmp = value_expr(sort_order); + sort_cmp = expr_t(sort_order); } sorted_accounts_iterator(account_t& account, const string& sort_order) { TRACE_CTOR(sorted_accounts_iterator, "account_t&, const string&"); - sort_cmp = value_expr(sort_order); + sort_cmp = expr_t(sort_order); push_back(account); } virtual ~sorted_accounts_iterator() throw() { @@ -1,14 +1,14 @@ #ifndef _XML_H #define _XML_H -#include "parser.h" +#include "journal.h" #include "format.h" namespace ledger { #if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) -class xml_parser_t : public parser_t +class xml_parser_t : public journal_t::parser_t { public: virtual bool test(std::istream& in) const; |