From e2afc783db0dff1927b00dc506390353d9e3bbd2 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 29 Feb 2012 22:32:23 -0600 Subject: Increased file copyrights to 2012 --- src/py_journal.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/py_journal.cc') diff --git a/src/py_journal.cc b/src/py_journal.cc index bd781225..4f5427f5 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, 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 -- cgit v1.2.3 From 944e580825f0d9ce060b6dcdffe8990b6c2cca20 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 1 Mar 2012 03:31:28 -0600 Subject: Refactored the notion of "the current parsing context" --- src/accum.h | 10 +- src/context.h | 151 ++++++++++++++ src/convert.cc | 12 +- src/csv.cc | 81 ++++---- src/csv.h | 33 +--- src/generate.cc | 10 +- src/global.cc | 7 +- src/journal.cc | 139 ++++++------- src/journal.h | 32 ++- src/precmd.cc | 10 +- src/py_commodity.cc | 2 +- src/py_journal.cc | 11 +- src/pyinterp.cc | 35 +++- src/scope.h | 2 +- src/session.cc | 31 ++- src/session.h | 3 + src/textual.cc | 448 +++++++++++++++++++----------------------- src/timelog.cc | 15 +- src/timelog.h | 11 +- src/xact.cc | 6 +- src/xact.h | 3 +- test/baseline/dir-tag.test | 2 +- test/baseline/feat-check.test | 2 +- 23 files changed, 598 insertions(+), 458 deletions(-) create mode 100644 src/context.h (limited to 'src/py_journal.cc') diff --git a/src/accum.h b/src/accum.h index 349aeba9..dde93c30 100644 --- a/src/accum.h +++ b/src/accum.h @@ -86,10 +86,16 @@ public: extern straccstream _accum; extern std::ostringstream _accum_buffer; +inline string str_helper_func() { + string buf = _accum_buffer.str(); + _accum_buffer.clear(); + _accum_buffer.str(""); + return buf; +} + #define STR(msg) \ ((_accum_buffer << ACCUM(_accum << msg)), \ - _accum.clear(), \ - _accum_buffer.str()) + _accum.clear(), str_helper_func()) } // namespace ledger diff --git a/src/context.h b/src/context.h new file mode 100644 index 00000000..0533536f --- /dev/null +++ b/src/context.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2003-2012, 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. + */ + +/** + * @addtogroup data + */ + +/** + * @file context.h + * @author John Wiegley + * + * @ingroup data + */ +#ifndef _CONTEXT_H +#define _CONTEXT_H + +#include "utils.h" +#include "times.h" + +namespace ledger { + +class journal_t; +class account_t; +class scope_t; + +class parse_context_t +{ +public: + static const std::size_t MAX_LINE = 4096; + + shared_ptr stream; + + path pathname; + path current_directory; + journal_t * journal; + account_t * master; + scope_t * scope; + char linebuf[MAX_LINE + 1]; + istream_pos_type line_beg_pos; + istream_pos_type curr_pos; + std::size_t linenum; + std::size_t errors; + std::size_t count; + std::size_t sequence; + + explicit parse_context_t(shared_ptr _stream, + const path& cwd) + : stream(_stream), current_directory(cwd), master(NULL), + scope(NULL), linenum(0), errors(0), count(0), sequence(1) {} + + parse_context_t(const parse_context_t& context) + : stream(context.stream), + pathname(context.pathname), + current_directory(context.current_directory), + journal(context.journal), + master(context.master), + scope(context.scope), + line_beg_pos(context.line_beg_pos), + curr_pos(context.curr_pos), + linenum(context.linenum), + errors(context.errors), + count(context.count), + sequence(context.sequence) { + std::memcpy(linebuf, context.linebuf, MAX_LINE); + } + + string location() const { + return file_context(pathname, linenum); + } + + void warning(const string& what) const { + warning_func(location() + what); + } +}; + +inline parse_context_t open_for_reading(const path& pathname, + const path& cwd) +{ + path filename = resolve_path(pathname); + + if (! exists(filename)) + throw_(std::runtime_error, + _("Cannot read journal file %1") << filename); + + path parent(filesystem::absolute(pathname, cwd).parent_path()); + shared_ptr stream(new ifstream(filename)); + parse_context_t context(stream, parent); + context.pathname = filename; + return context; +} + +class parse_context_stack_t +{ + std::list parsing_context; + +public: + void push(shared_ptr stream, + const path& cwd = filesystem::current_path()) { + parsing_context.push_front(parse_context_t(stream, cwd)); + } + void push(const path& pathname, + const path& cwd = filesystem::current_path()) { + parsing_context.push_front(open_for_reading(pathname, cwd)); + } + + void push(const parse_context_t& context) { + parsing_context.push_front(context); + } + + void pop() { + assert(! parsing_context.empty()); + parsing_context.pop_front(); + } + + parse_context_t& get_current() { + assert(! parsing_context.empty()); + return parsing_context.front(); + } +}; + +} // namespace ledger + +#endif // _CONTEXT_H diff --git a/src/convert.cc b/src/convert.cc index 1b1bf814..e8ca241e 100644 --- a/src/convert.cc +++ b/src/convert.cc @@ -63,12 +63,16 @@ value_t convert_command(call_scope_t& args) print_xacts formatter(report); path csv_file_path(args.get(0)); - ifstream data(csv_file_path); - csv_reader reader(data, csv_file_path); + + report.session.parsing_context.push(csv_file_path); + parse_context_t& context(report.session.parsing_context.get_current()); + context.journal = &journal; + context.master = bucket; + + csv_reader reader(context); try { - while (xact_t * xact = reader.read_xact(journal, bucket, - report.HANDLED(rich_data))) { + while (xact_t * xact = reader.read_xact(report.HANDLED(rich_data))) { if (report.HANDLED(invert)) { foreach (post_t * post, xact->posts) post->amount.in_place_negate(); diff --git a/src/csv.cc b/src/csv.cc index 823238c7..305db992 100644 --- a/src/csv.cc +++ b/src/csv.cc @@ -40,27 +40,27 @@ namespace ledger { -string csv_reader::read_field(std::istream& sin) +string csv_reader::read_field(std::istream& in) { string field; char c; - if (sin.peek() == '"' || sin.peek() == '|') { - sin.get(c); + if (in.peek() == '"' || in.peek() == '|') { + in.get(c); char x; - while (sin.good() && ! sin.eof()) { - sin.get(x); + while (in.good() && ! in.eof()) { + in.get(x); if (x == '\\') { - sin.get(x); + in.get(x); } - else if (x == '"' && sin.peek() == '"') { - sin.get(x); + else if (x == '"' && in.peek() == '"') { + in.get(x); } else if (x == c) { if (x == '|') - sin.unget(); - else if (sin.peek() == ',') - sin.get(c); + in.unget(); + else if (in.peek() == ',') + in.get(c); break; } if (x != '\0') @@ -68,9 +68,9 @@ string csv_reader::read_field(std::istream& sin) } } else { - while (sin.good() && ! sin.eof()) { - sin.get(c); - if (sin.good()) { + while (in.good() && ! in.eof()) { + in.get(c); + if (in.good()) { if (c == ',') break; if (c != '\0') @@ -82,22 +82,22 @@ string csv_reader::read_field(std::istream& sin) return field; } -char * csv_reader::next_line(std::istream& sin) +char * csv_reader::next_line(std::istream& in) { - while (sin.good() && ! sin.eof() && sin.peek() == '#') - sin.getline(linebuf, MAX_LINE); + while (in.good() && ! in.eof() && in.peek() == '#') + in.getline(context.linebuf, parse_context_t::MAX_LINE); - if (! sin.good() || sin.eof()) + if (! in.good() || in.eof()) return NULL; - sin.getline(linebuf, MAX_LINE); + in.getline(context.linebuf, parse_context_t::MAX_LINE); - return linebuf; + return context.linebuf; } -void csv_reader::read_index(std::istream& sin) +void csv_reader::read_index(std::istream& in) { - char * line = next_line(sin); + char * line = next_line(in); if (! line) return; @@ -130,13 +130,12 @@ void csv_reader::read_index(std::istream& sin) } } -xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket, - bool rich_data) +xact_t * csv_reader::read_xact(bool rich_data) { - char * line = next_line(in); + char * line = next_line(*context.stream.get()); if (! line || index.empty()) return NULL; - linenum++; + context.linenum++; std::istringstream instr(line); @@ -146,18 +145,18 @@ xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket, xact->set_state(item_t::CLEARED); xact->pos = position_t(); - xact->pos->pathname = pathname; - xact->pos->beg_pos = in.tellg(); - xact->pos->beg_line = linenum; - xact->pos->sequence = sequence++; + xact->pos->pathname = context.pathname; + xact->pos->beg_pos = context.stream->tellg(); + xact->pos->beg_line = context.linenum; + xact->pos->sequence = context.sequence++; post->xact = xact.get(); post->pos = position_t(); - post->pos->pathname = pathname; - post->pos->beg_pos = in.tellg(); - post->pos->beg_line = linenum; - post->pos->sequence = sequence++; + post->pos->pathname = context.pathname; + post->pos->beg_pos = context.stream->tellg(); + post->pos->beg_line = context.linenum; + post->pos->sequence = context.sequence++; post->set_state(item_t::CLEARED); post->account = NULL; @@ -186,7 +185,7 @@ xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket, case FIELD_PAYEE: { bool found = false; - foreach (payee_mapping_t& value, journal.payee_mappings) { + foreach (payee_mapping_t& value, context.journal->payee_mappings) { DEBUG("csv.mappings", "Looking for payee mapping: " << value.first); if (value.first.match(field)) { xact->payee = value.second; @@ -244,7 +243,7 @@ xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket, // Translate the account name, if we have enough information to do so - foreach (account_mapping_t& value, journal.account_mappings) { + foreach (account_mapping_t& value, context.journal->account_mappings) { if (value.first.match(xact->payee)) { post->account = value.second; break; @@ -260,13 +259,13 @@ xact_t * csv_reader::read_xact(journal_t& journal, account_t * bucket, post->xact = xact.get(); post->pos = position_t(); - post->pos->pathname = pathname; - post->pos->beg_pos = in.tellg(); - post->pos->beg_line = linenum; - post->pos->sequence = sequence++; + post->pos->pathname = context.pathname; + post->pos->beg_pos = context.stream->tellg(); + post->pos->beg_line = context.linenum; + post->pos->sequence = context.sequence++; post->set_state(item_t::CLEARED); - post->account = bucket; + post->account = context.master; if (! amt.is_null()) post->amount = - amt; diff --git a/src/csv.h b/src/csv.h index 4d6e1253..24ea9121 100644 --- a/src/csv.h +++ b/src/csv.h @@ -43,6 +43,7 @@ #define _CSV_H #include "value.h" +#include "context.h" namespace ledger { @@ -52,13 +53,7 @@ class account_t; class csv_reader { - static const std::size_t MAX_LINE = 4096; - - std::istream& in; - path pathname; - char linebuf[MAX_LINE]; - std::size_t linenum; - std::size_t sequence; + parse_context_t context; enum headers_t { FIELD_DATE = 0, @@ -86,9 +81,8 @@ class csv_reader std::vector names; public: - csv_reader(std::istream& _in, const path& _pathname) - : in(_in), pathname(_pathname), - linenum(0), sequence(0), + csv_reader(parse_context_t& _context) + : context(_context), date_mask("date"), date_aux_mask("posted( ?date)?"), code_mask("code"), @@ -97,32 +91,23 @@ public: cost_mask("cost"), total_mask("total"), note_mask("note") { - read_index(in); + read_index(*context.stream.get()); } void read_index(std::istream& in); string read_field(std::istream& in); char * next_line(std::istream& in); - xact_t * read_xact(journal_t& journal, account_t * bucket, bool rich_data); + xact_t * read_xact(bool rich_data); const char * get_last_line() const { - return linebuf; + return context.linebuf; } - path get_pathname() const { - return pathname; + return context.pathname; } std::size_t get_linenum() const { - return linenum; - } - - void reset() { - pathname.clear(); - index.clear(); - names.clear(); - linenum = 0; - sequence = 0; + return context.linenum; } }; diff --git a/src/generate.cc b/src/generate.cc index bf9a8036..edd58632 100644 --- a/src/generate.cc +++ b/src/generate.cc @@ -360,9 +360,15 @@ void generate_posts_iterator::increment() DEBUG("generate.post", "The post we intend to parse:\n" << buf.str()); - std::istringstream in(buf.str()); try { - if (session.journal->parse(in, session) != 0) { + shared_ptr in(new std::istringstream(buf.str())); + + parse_context_stack_t parsing_context; + parsing_context.push(in); + parsing_context.get_current().journal = session.journal.get(); + parsing_context.get_current().scope = &session; + + if (session.journal->read(parsing_context) != 0) { VERIFY(session.journal->xacts.back()->valid()); posts.reset(*session.journal->xacts.back()); post = *posts++; diff --git a/src/global.cc b/src/global.cc index ee921fc5..5b7bb1c1 100644 --- a/src/global.cc +++ b/src/global.cc @@ -112,9 +112,12 @@ void global_scope_t::read_init() if (exists(init_file)) { TRACE_START(init, 1, "Read initialization file"); - ifstream init(init_file); + parse_context_stack_t parsing_context; + parsing_context.push(init_file); + parsing_context.get_current().journal = session().journal.get(); + parsing_context.get_current().scope = &report(); - if (session().journal->read(init_file, NULL, &report()) > 0 || + if (session().journal->read(parsing_context) > 0 || session().journal->auto_xacts.size() > 0 || session().journal->period_xacts.size() > 0) { throw_(parse_error, _("Transactions found in initialization file '%1'") diff --git a/src/journal.cc b/src/journal.cc index 2ebe90fb..55c89dd9 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -32,6 +32,7 @@ #include #include "journal.h" +#include "context.h" #include "amount.h" #include "commodity.h" #include "pool.h" @@ -47,6 +48,7 @@ journal_t::journal_t() initialize(); } +#if 0 journal_t::journal_t(const path& pathname) { TRACE_CTOR(journal_t, "path"); @@ -60,6 +62,7 @@ journal_t::journal_t(const string& str) initialize(); read(str); } +#endif journal_t::~journal_t() { @@ -87,6 +90,7 @@ void journal_t::initialize() fixed_payees = false; fixed_commodities = false; fixed_metadata = false; + current_context = NULL; was_loaded = false; force_checking = false; check_payees = false; @@ -114,7 +118,6 @@ account_t * journal_t::find_account_re(const string& regexp) } account_t * journal_t::register_account(const string& name, post_t * post, - const string& location, account_t * master_account) { account_t * result = NULL; @@ -147,8 +150,8 @@ account_t * journal_t::register_account(const string& name, post_t * post, result->add_flags(ACCOUNT_KNOWN); } else if (checking_style == CHECK_WARNING) { - warning_(_("%1Unknown account '%2'") << location - << result->fullname()); + current_context->warning(STR(_("Unknown account '%1'") + << result->fullname())); } else if (checking_style == CHECK_ERROR) { throw_(parse_error, _("Unknown account '%1'") << result->fullname()); @@ -159,8 +162,7 @@ account_t * journal_t::register_account(const string& name, post_t * post, return result; } -string journal_t::register_payee(const string& name, xact_t * xact, - const string& location) +string journal_t::register_payee(const string& name, xact_t * xact) { string payee; @@ -178,7 +180,7 @@ string journal_t::register_payee(const string& name, xact_t * xact, known_payees.insert(name); } else if (checking_style == CHECK_WARNING) { - warning_(_("%1Unknown payee '%2'") << location << name); + current_context->warning(STR(_("Unknown payee '%1'") << name)); } else if (checking_style == CHECK_ERROR) { throw_(parse_error, _("Unknown payee '%1'") << name); @@ -197,8 +199,7 @@ string journal_t::register_payee(const string& name, xact_t * xact, } void journal_t::register_commodity(commodity_t& comm, - variant context, - const string& location) + variant context) { if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) { if (! comm.has_flags(COMMODITY_KNOWN)) { @@ -215,7 +216,7 @@ void journal_t::register_commodity(commodity_t& comm, comm.add_flags(COMMODITY_KNOWN); } else if (checking_style == CHECK_WARNING) { - warning_(_("%1Unknown commodity '%2'") << location << comm); + current_context->warning(STR(_("Unknown commodity '%1'") << comm)); } else if (checking_style == CHECK_ERROR) { throw_(parse_error, _("Unknown commodity '%1'") << comm); @@ -224,40 +225,8 @@ void journal_t::register_commodity(commodity_t& comm, } } -namespace { - void check_metadata(journal_t& journal, - const string& key, const value_t& value, - variant context, - const string& location) - { - std::pair range = - journal.tag_check_exprs.equal_range(key); - - for (tag_check_exprs_map::iterator i = range.first; - i != range.second; - ++i) { - value_scope_t val_scope - (context.which() == 1 ? - static_cast(*boost::get(context)) : - static_cast(*boost::get(context)), value); - - if (! (*i).second.first.calc(val_scope).to_boolean()) { - if ((*i).second.second == expr_t::EXPR_ASSERTION) - throw_(parse_error, - _("Metadata assertion failed for (%1: %2): %3") - << key << value << (*i).second.first); - else - warning_(_("%1Metadata check failed for (%2: %3): %4") - << location << key << value << (*i).second.first); - } - } - } -} - void journal_t::register_metadata(const string& key, const value_t& value, - variant context, - const string& location) + variant context) { if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) { std::set::iterator i = known_tags.find(key); @@ -276,7 +245,7 @@ void journal_t::register_metadata(const string& key, const value_t& value, known_tags.insert(key); } else if (checking_style == CHECK_WARNING) { - warning_(_("%1Unknown metadata tag '%2'") << location << key); + current_context->warning(STR(_("Unknown metadata tag '%1'") << key)); } else if (checking_style == CHECK_ERROR) { throw_(parse_error, _("Unknown metadata tag '%1'") << key); @@ -284,8 +253,33 @@ void journal_t::register_metadata(const string& key, const value_t& value, } } - if (! value.is_null()) - check_metadata(*this, key, value, context, location); + if (! value.is_null()) { + std::pair range = + tag_check_exprs.equal_range(key); + + for (tag_check_exprs_map::iterator i = range.first; + i != range.second; + ++i) { + bind_scope_t bound_scope + (*current_context->scope, + context.which() == 1 ? + static_cast(*boost::get(context)) : + static_cast(*boost::get(context))); + value_scope_t val_scope(bound_scope, value); + + if (! (*i).second.first.calc(val_scope).to_boolean()) { + if ((*i).second.second == expr_t::EXPR_ASSERTION) + throw_(parse_error, + _("Metadata assertion failed for (%1: %2): %3") + << key << value << (*i).second.first); + else + current_context->warning + (STR(_("Metadata check failed for (%1: %2): %3") + << key << value << (*i).second.first)); + } + } + } } namespace { @@ -300,13 +294,10 @@ namespace { xact ? *xact->metadata : *post->metadata) { const string& key(pair.first); - // jww (2012-02-27): We really need to know the parsing context, - // both here and for the call to warning_ in - // xact_t::extend_xact. if (optional value = pair.second.first) - journal.register_metadata(key, *value, context, ""); + journal.register_metadata(key, *value, context); else - journal.register_metadata(key, NULL_VALUE, context, ""); + journal.register_metadata(key, NULL_VALUE, context); } } } @@ -351,7 +342,7 @@ bool journal_t::add_xact(xact_t * xact) void journal_t::extend_xact(xact_base_t * xact) { foreach (auto_xact_t * auto_xact, auto_xacts) - auto_xact->extend_xact(*xact); + auto_xact->extend_xact(*xact, *current_context); } bool journal_t::remove_xact(xact_t * xact) @@ -372,25 +363,36 @@ bool journal_t::remove_xact(xact_t * xact) return true; } -std::size_t journal_t::read(std::istream& in, - const path& pathname, - account_t * master_alt, - scope_t * scope) +std::size_t journal_t::read(parse_context_stack_t& context) { std::size_t count = 0; try { - if (! scope) - scope = scope_t::default_scope; + parse_context_t& current(context.get_current()); + current_context = ¤t; + + current.count = 0; + if (! current.scope) + current.scope = scope_t::default_scope; - if (! scope) + if (! current.scope) throw_(std::runtime_error, _("No default scope in which to read journal file '%1'") - << pathname); + << current.pathname); - count = parse(in, *scope, master_alt ? master_alt : master, &pathname); + if (! current.master) + current.master = master; + + count = read_textual(context); + if (count > 0) { + if (! current.pathname.empty()) + sources.push_back(fileinfo_t(current.pathname)); + else + sources.push_back(fileinfo_t()); + } } catch (...) { clear_xdata(); + current_context = NULL; throw; } @@ -402,23 +404,6 @@ std::size_t journal_t::read(std::istream& in, return count; } -std::size_t journal_t::read(const path& pathname, - account_t * master_account, - scope_t * scope) -{ - path filename = resolve_path(pathname); - - if (! exists(filename)) - throw_(std::runtime_error, - _("Cannot read journal file %1") << filename); - - ifstream stream(filename); - std::size_t count = read(stream, filename, master_account, scope); - if (count > 0) - sources.push_back(fileinfo_t(filename)); - return count; -} - bool journal_t::has_xdata() { foreach (xact_t * xact, xacts) diff --git a/src/journal.h b/src/journal.h index 6d10ffda..8b750993 100644 --- a/src/journal.h +++ b/src/journal.h @@ -55,7 +55,8 @@ class auto_xact_t; class period_xact_t; class post_t; class account_t; -class scope_t; +class parse_context_t; +class parse_context_stack_t; typedef std::list xacts_list; typedef std::list auto_xacts_list; @@ -132,6 +133,7 @@ public: account_mappings_t payees_for_unknown_accounts; checksum_map_t checksum_map; tag_check_exprs_map tag_check_exprs; + parse_context_t * current_context; bool was_loaded; bool force_checking; bool check_payees; @@ -143,8 +145,10 @@ public: } checking_style; journal_t(); +#if 0 journal_t(const path& pathname); journal_t(const string& str); +#endif ~journal_t(); void initialize(); @@ -162,16 +166,12 @@ public: account_t * find_account_re(const string& regexp); account_t * register_account(const string& name, post_t * post, - const string& location, account_t * master = NULL); - string register_payee(const string& name, xact_t * xact, - const string& location); + string register_payee(const string& name, xact_t * xact); void register_commodity(commodity_t& comm, - variant context, - const string& location); + variant context); void register_metadata(const string& key, const value_t& value, - variant context, - const string& location); + variant context); bool add_xact(xact_t * xact); void extend_xact(xact_base_t * xact); @@ -196,24 +196,16 @@ public: return period_xacts.end(); } - std::size_t read(std::istream& in, - const path& pathname, - account_t * master = NULL, - scope_t * scope = NULL); - std::size_t read(const path& pathname, - account_t * master = NULL, - scope_t * scope = NULL); - - std::size_t parse(std::istream& in, - scope_t& session_scope, - account_t * master = NULL, - const path * original_file = NULL); + std::size_t read(parse_context_stack_t& context); bool has_xdata(); void clear_xdata(); bool valid() const; +private: + std::size_t read_textual(parse_context_stack_t& context); + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ diff --git a/src/precmd.cc b/src/precmd.cc index 6f8becb6..6b106a8b 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -83,8 +83,14 @@ namespace { out << _("--- Context is first posting of the following transaction ---") << std::endl << str << std::endl; { - std::istringstream in(str); - report.session.journal->parse(in, report.session); + shared_ptr in(new std::istringstream(str)); + + parse_context_stack_t parsing_context; + parsing_context.push(in); + parsing_context.get_current().journal = report.session.journal.get(); + parsing_context.get_current().scope = &report.session; + + report.session.journal->read(parsing_context); report.session.journal->clear_xdata(); } } diff --git a/src/py_commodity.cc b/src/py_commodity.cc index 11ebe844..ffa903f4 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -113,7 +113,7 @@ namespace { if (i == pool.commodities.end()) { PyErr_SetString(PyExc_ValueError, (string("Could not find commodity ") + symbol).c_str()); - throw boost::python::error_already_set(); + throw_error_already_set(); } return (*i).second; } diff --git a/src/py_journal.cc b/src/py_journal.cc index 4f5427f5..a72b8528 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -135,10 +135,12 @@ namespace { return journal.find_account(name, auto_create); } +#if 0 std::size_t py_read(journal_t& journal, const string& pathname) { - return journal.read(pathname); + return journal.read(context_stack); } +#endif struct collector_wrapper { @@ -264,9 +266,10 @@ void export_journal() ; class_< journal_t, boost::noncopyable > ("Journal") +#if 0 .def(init()) .def(init()) - +#endif .add_property("master", make_getter(&journal_t::master, return_internal_reference<1, @@ -311,9 +314,9 @@ void export_journal() (&journal_t::period_xacts_begin, &journal_t::period_xacts_end)) .def("sources", python::range > (&journal_t::sources_begin, &journal_t::sources_end)) - +#if 0 .def("read", py_read) - +#endif .def("has_xdata", &journal_t::has_xdata) .def("clear_xdata", &journal_t::clear_xdata) diff --git a/src/pyinterp.cc b/src/pyinterp.cc index 44bea2cd..de9c94cb 100644 --- a/src/pyinterp.cc +++ b/src/pyinterp.cc @@ -194,18 +194,19 @@ object python_interpreter_t::import_option(const string& str) if (! is_initialized) initialize(); - path file(str); - string name(str); - python::object sys_module = python::import("sys"); python::object sys_dict = sys_module.attr("__dict__"); + path file(str); + string name(str); python::list paths(sys_dict["path"]); if (contains(str, ".py")) { #if BOOST_VERSION >= 103700 - path& cwd(get_parsing_context().current_directory); - paths.insert(0, filesystem::absolute(file, cwd).parent_path().string()); + path& cwd(parsing_context.get_current().current_directory); + path parent(filesystem::absolute(file, cwd).parent_path()); + DEBUG("python.interp", "Adding " << parent << " to PYTHONPATH"); + paths.insert(0, parent.string()); sys_dict["path"] = paths; #if BOOST_VERSION >= 104600 @@ -220,7 +221,24 @@ object python_interpreter_t::import_option(const string& str) #endif // BOOST_VERSION >= 103700 } - return python::import(python::str(name.c_str())); + try { + if (contains(str, ".py")) { + import_into_main(name); + } else { + object obj = python::import(python::str(name.c_str())); + main_nspace[name.c_str()] = obj; + return obj; + } + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(std::runtime_error, _("Python failed to import: %1") << str); + } + catch (...) { + throw; + } + + return object(); } object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode) @@ -348,13 +366,13 @@ value_t python_interpreter_t::server_command(call_scope_t& args) functor_t func(main_function, "main"); try { func(args); + return true; } catch (const error_already_set&) { PyErr_Print(); throw_(std::runtime_error, _("Error while invoking ledger.server's main() function")); } - return true; } else { throw_(std::runtime_error, _("The ledger.server module is missing its main() function!")); @@ -455,6 +473,7 @@ value_t python_interpreter_t::functor_t::operator()(call_scope_t& args) if (! PyCallable_Check(func.ptr())) { extract val(func); + DEBUG("python.interp", "Value of Python '" << name << "': " << val); std::signal(SIGINT, sigint_handler); if (val.check()) return val(); @@ -476,6 +495,8 @@ value_t python_interpreter_t::functor_t::operator()(call_scope_t& args) value_t result; if (xval.check()) { result = xval(); + DEBUG("python.interp", + "Return from Python '" << name << "': " << result); Py_DECREF(val); } else { Py_DECREF(val); diff --git a/src/scope.h b/src/scope.h index 6fcd67e9..c42ec66b 100644 --- a/src/scope.h +++ b/src/scope.h @@ -664,7 +664,7 @@ public: if (name == "value") return MAKE_FUNCTOR(value_scope_t::get_value); - return NULL; + return child_scope_t::lookup(kind, name); } }; diff --git a/src/session.cc b/src/session.cc index 9d994a9b..e07026b6 100644 --- a/src/session.cc +++ b/src/session.cc @@ -120,8 +120,17 @@ std::size_t session_t::read_data(const string& master_account) #endif // HAVE_BOOST_SERIALIZATION if (price_db_path) { if (exists(*price_db_path)) { - if (journal->read(*price_db_path) > 0) - throw_(parse_error, _("Transactions not allowed in price history file")); + parsing_context.push(*price_db_path); + parsing_context.get_current().journal = journal.get(); + try { + if (journal->read(parsing_context) > 0) + throw_(parse_error, _("Transactions not allowed in price history file")); + } + catch (...) { + parsing_context.pop(); + throw; + } + parsing_context.pop(); } } @@ -140,12 +149,22 @@ std::size_t session_t::read_data(const string& master_account) } buffer.flush(); - std::istringstream buf_in(buffer.str()); - xact_count += journal->read(buf_in, "/dev/stdin", acct); - journal->sources.push_back(journal_t::fileinfo_t()); + shared_ptr stream(new std::istringstream(buffer.str())); + parsing_context.push(stream); } else { - xact_count += journal->read(pathname, acct); + parsing_context.push(pathname); + } + + parsing_context.get_current().journal = journal.get(); + parsing_context.get_current().master = acct; + try { + xact_count += journal->read(parsing_context); + } + catch (...) { + parsing_context.pop(); + throw; } + parsing_context.pop(); } DEBUG("ledger.read", "xact_count [" << xact_count diff --git a/src/session.h b/src/session.h index 93bee8ba..54b9912a 100644 --- a/src/session.h +++ b/src/session.h @@ -44,6 +44,7 @@ #include "account.h" #include "journal.h" +#include "context.h" #include "option.h" #include "commodity.h" @@ -57,7 +58,9 @@ class session_t : public symbol_scope_t public: bool flush_on_next_data_file; + std::auto_ptr journal; + parse_context_stack_t parsing_context; explicit session_t(); virtual ~session_t() { diff --git a/src/textual.cc b/src/textual.cc index 15642cae..97c80e4f 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -32,6 +32,7 @@ #include #include "journal.h" +#include "context.h" #include "xact.h" #include "post.h" #include "account.h" @@ -53,8 +54,10 @@ namespace { struct application_t { string label; - variant value; + variant, account_t *, string, fixed_rate_t> value; + application_t(string _label, optional epoch) + : label(_label), value(epoch) {} application_t(string _label, account_t * acct) : label(_label), value(acct) {} application_t(string _label, string tag) @@ -63,24 +66,27 @@ namespace { : label(_label), value(rate) {} }; - class parse_context_t : public noncopyable + class instance_t : public noncopyable, public scope_t { + public: - std::list apply_stack; + parse_context_stack_t& context_stack; + parse_context_t& context; + std::istream& in; + instance_t * parent; - journal_t& journal; - scope_t& scope; + std::list apply_stack; #if defined(TIMELOG_SUPPORT) - time_log_t timelog; + time_log_t timelog; #endif - std::size_t count; - std::size_t errors; - std::size_t sequence; - - parse_context_t(journal_t& _journal, scope_t& _scope) - : journal(_journal), scope(_scope), timelog(journal, scope), - count(0), errors(0), sequence(1) { - timelog.context_count = &count; + + instance_t(parse_context_stack_t& _context_stack, + parse_context_t& _context, instance_t * _parent = NULL) + : context_stack(_context_stack), context(_context), + in(*context.stream.get()), parent(_parent), timelog(context) {} + + virtual string description() { + return _("textual parser"); } account_t * top_account() { @@ -90,40 +96,10 @@ namespace { return NULL; } - void close() { - timelog.close(); - } - }; - - class instance_t : public noncopyable, public scope_t - { - static const std::size_t MAX_LINE = 1024; - - public: - parse_context_t& context; - instance_t * parent; - const path * original_file; - path pathname; - std::istream& in; - char linebuf[MAX_LINE + 1]; - std::size_t linenum; - istream_pos_type line_beg_pos; - istream_pos_type curr_pos; - optional prev_epoch; - - instance_t(parse_context_t& _context, - std::istream& _in, - const path * _original_file = NULL, - instance_t * _parent = NULL); - - ~instance_t(); - - virtual string description() { - return _("textual parser"); - } - void parse(); + std::streamsize read_line(char *& line); + bool peek_whitespace_line() { return (in.good() && ! in.eof() && (in.peek() == ' ' || in.peek() == '\t')); @@ -229,37 +205,17 @@ namespace { } } -instance_t::instance_t(parse_context_t& _context, - std::istream& _in, - const path * _original_file, - instance_t * _parent) - : context(_context), parent(_parent), original_file(_original_file), - pathname(original_file ? *original_file : "/dev/stdin"), in(_in) -{ - TRACE_CTOR(instance_t, "..."); - DEBUG("times.epoch", "Saving epoch " << epoch); - prev_epoch = epoch; // declared in times.h -} - -instance_t::~instance_t() -{ - TRACE_DTOR(instance_t); - epoch = prev_epoch; - DEBUG("times.epoch", "Restored epoch to " << epoch); -} - void instance_t::parse() { - INFO("Parsing file '" << pathname.string() << "'"); + INFO("Parsing file " << context.pathname); - TRACE_START(instance_parse, 1, - "Done parsing file '" << pathname.string() << "'"); + TRACE_START(instance_parse, 1, "Done parsing file " << context.pathname); if (! in.good() || in.eof()) return; - linenum = 0; - curr_pos = in.tellg(); + context.linenum = 0; + context.curr_pos = in.tellg(); while (in.good() && ! in.eof()) { try { @@ -278,11 +234,9 @@ void instance_t::parse() foreach (instance_t * instance, instances) add_error_context(_("In file included from %1") - << file_context(instance->pathname, - instance->linenum)); + << instance->context.location()); } - add_error_context(_("While parsing file %1") - << file_context(pathname, linenum)); + add_error_context(_("While parsing file %1") << context.location()); if (caught_signal != NONE_CAUGHT) throw; @@ -307,26 +261,26 @@ std::streamsize instance_t::read_line(char *& line) assert(in.good()); assert(! in.eof()); // no one should call us in that case - line_beg_pos = curr_pos; + context.line_beg_pos = context.curr_pos; check_for_signal(); - in.getline(linebuf, MAX_LINE); + in.getline(context.linebuf, parse_context_t::MAX_LINE); std::streamsize len = in.gcount(); if (len > 0) { - if (linenum == 0 && utf8::is_bom(linebuf)) - line = &linebuf[3]; + if (context.linenum == 0 && utf8::is_bom(context.linebuf)) + line = &context.linebuf[3]; else - line = linebuf; + line = context.linebuf; if (line[len - 1] == '\r') // strip Windows CRLF down to LF line[--len] = '\0'; - linenum++; + context.linenum++; - curr_pos = line_beg_pos; - curr_pos += len; + context.curr_pos = context.line_beg_pos; + context.curr_pos += len; return len - 1; // LF is being silently dropped } @@ -446,19 +400,19 @@ void instance_t::clock_in_directive(char * line, bool /*capitalized*/) end = NULL; position_t position; - position.pathname = pathname; - position.beg_pos = line_beg_pos; - position.beg_line = linenum; - position.end_pos = curr_pos; - position.end_line = linenum; + position.pathname = context.pathname; + position.beg_pos = context.line_beg_pos; + position.beg_line = context.linenum; + position.end_pos = context.curr_pos; + position.end_line = context.linenum; position.sequence = context.sequence++; time_xact_t event(position, parse_datetime(datetime), - p ? context.top_account()->find_account(p) : NULL, + p ? top_account()->find_account(p) : NULL, n ? n : "", end ? end : ""); - context.timelog.clock_in(event); + timelog.clock_in(event); } void instance_t::clock_out_directive(char * line, bool /*capitalized*/) @@ -475,19 +429,19 @@ void instance_t::clock_out_directive(char * line, bool /*capitalized*/) end = NULL; position_t position; - position.pathname = pathname; - position.beg_pos = line_beg_pos; - position.beg_line = linenum; - position.end_pos = curr_pos; - position.end_line = linenum; + position.pathname = context.pathname; + position.beg_pos = context.line_beg_pos; + position.beg_line = context.linenum; + position.end_pos = context.curr_pos; + position.end_line = context.linenum; position.sequence = context.sequence++; time_xact_t event(position, parse_datetime(datetime), - p ? context.top_account()->find_account(p) : NULL, + p ? top_account()->find_account(p) : NULL, n ? n : "", end ? end : ""); - context.timelog.clock_out(event); + timelog.clock_out(event); context.count++; } @@ -503,8 +457,8 @@ void instance_t::default_commodity_directive(char * line) void instance_t::default_account_directive(char * line) { - context.journal.bucket = context.top_account()->find_account(skip_ws(line + 1)); - context.journal.bucket->add_flags(ACCOUNT_KNOWN); + context.journal->bucket = top_account()->find_account(skip_ws(line + 1)); + context.journal->bucket->add_flags(ACCOUNT_KNOWN); } void instance_t::price_conversion_directive(char * line) @@ -543,13 +497,14 @@ void instance_t::option_directive(char * line) *p++ = '\0'; } - if (! process_option(pathname.string(), line + 2, context.scope, p, line)) + if (! process_option(context.pathname.string(), line + 2, + *context.scope, p, line)) throw_(option_error, _("Illegal option --%1") << line + 2); } void instance_t::automated_xact_directive(char * line) { - istream_pos_type pos= line_beg_pos; + istream_pos_type pos = context.line_beg_pos; bool reveal_context = true; @@ -562,9 +517,9 @@ void instance_t::automated_xact_directive(char * line) std::auto_ptr ae(new auto_xact_t(predicate_t(expr, keeper))); ae->pos = position_t(); - ae->pos->pathname = pathname; - ae->pos->beg_pos = line_beg_pos; - ae->pos->beg_line = linenum; + ae->pos->pathname = context.pathname; + ae->pos->beg_pos = context.line_beg_pos; + ae->pos->beg_line = context.linenum; ae->pos->sequence = context.sequence++; post_t * last_post = NULL; @@ -586,9 +541,9 @@ void instance_t::automated_xact_directive(char * line) item = ae.get(); // This is a trailing note, and possibly a metadata info tag - item->append_note(p + 1, context.scope, true); + item->append_note(p + 1, *context.scope, true); item->add_flags(ITEM_NOTE_ON_NEXT_LINE); - item->pos->end_pos = curr_pos; + item->pos->end_pos = context.curr_pos; item->pos->end_line++; // If there was no last_post yet, then deferred notes get applied to @@ -619,8 +574,7 @@ void instance_t::automated_xact_directive(char * line) reveal_context = false; if (post_t * post = - parse_post(p, len - (p - line), context.top_account(), - NULL, true)) { + parse_post(p, len - (p - line), top_account(), NULL, true)) { reveal_context = true; ae->add_post(post); last_post = post; @@ -629,18 +583,19 @@ void instance_t::automated_xact_directive(char * line) } } - context.journal.auto_xacts.push_back(ae.get()); + context.journal->auto_xacts.push_back(ae.get()); - ae->journal = &context.journal; - ae->pos->end_pos = curr_pos; - ae->pos->end_line = linenum; + ae->journal = context.journal; + ae->pos->end_pos = context.curr_pos; + ae->pos->end_line = context.linenum; ae.release(); } catch (const std::exception&) { if (reveal_context) { add_error_context(_("While parsing automated transaction:")); - add_error_context(source_context(pathname, pos, curr_pos, "> ")); + add_error_context(source_context(context.pathname, pos, + context.curr_pos, "> ")); } throw; } @@ -648,7 +603,7 @@ void instance_t::automated_xact_directive(char * line) void instance_t::period_xact_directive(char * line) { - istream_pos_type pos = line_beg_pos; + istream_pos_type pos = context.line_beg_pos; bool reveal_context = true; @@ -656,23 +611,23 @@ void instance_t::period_xact_directive(char * line) std::auto_ptr pe(new period_xact_t(skip_ws(line + 1))); pe->pos = position_t(); - pe->pos->pathname = pathname; - pe->pos->beg_pos = line_beg_pos; - pe->pos->beg_line = linenum; + pe->pos->pathname = context.pathname; + pe->pos->beg_pos = context.line_beg_pos; + pe->pos->beg_line = context.linenum; pe->pos->sequence = context.sequence++; reveal_context = false; - if (parse_posts(context.top_account(), *pe.get())) { + if (parse_posts(top_account(), *pe.get())) { reveal_context = true; - pe->journal = &context.journal; + pe->journal = context.journal; if (pe->finalize()) { - context.journal.extend_xact(pe.get()); - context.journal.period_xacts.push_back(pe.get()); + context.journal->extend_xact(pe.get()); + context.journal->period_xacts.push_back(pe.get()); - pe->pos->end_pos = curr_pos; - pe->pos->end_line = linenum; + pe->pos->end_pos = context.curr_pos; + pe->pos->end_line = context.linenum; pe.release(); } else { @@ -686,7 +641,8 @@ void instance_t::period_xact_directive(char * line) catch (const std::exception&) { if (reveal_context) { add_error_context(_("While parsing periodic transaction:")); - add_error_context(source_context(pathname, pos, curr_pos, "> ")); + add_error_context(source_context(context.pathname, pos, + context.curr_pos, "> ")); } throw; } @@ -696,10 +652,10 @@ void instance_t::xact_directive(char * line, std::streamsize len) { TRACE_START(xacts, 1, "Time spent handling transactions:"); - if (xact_t * xact = parse_xact(line, len, context.top_account())) { + if (xact_t * xact = parse_xact(line, len, top_account())) { std::auto_ptr manager(xact); - if (context.journal.add_xact(xact)) { + if (context.journal->add_xact(xact)) { manager.release(); // it's owned by the journal now context.count++; } @@ -721,12 +677,13 @@ void instance_t::include_directive(char * line) if (line[0] != '/' && line[0] != '\\' && line[0] != '~') { DEBUG("textual.include", "received a relative path"); - DEBUG("textual.include", "parent file path: " << pathname.string()); - string::size_type pos = pathname.string().rfind('/'); + DEBUG("textual.include", "parent file path: " << context.pathname); + string pathstr(context.pathname.string()); + string::size_type pos = pathstr.rfind('/'); if (pos == string::npos) - pos = pathname.string().rfind('\\'); + pos = pathstr.rfind('\\'); if (pos != string::npos) { - filename = path(string(pathname.string(), 0, pos + 1)) / line; + filename = path(string(pathstr, 0, pos + 1)) / line; DEBUG("textual.include", "normalized path: " << filename.string()); } else { filename = path(string(".")) / line; @@ -773,10 +730,24 @@ void instance_t::include_directive(char * line) string base = (*iter).leaf(); #endif // BOOST_VERSION >= 103700 if (glob.match(base)) { - path inner_file(*iter); - ifstream stream(inner_file); - instance_t instance(context, stream, &inner_file, this); - instance.parse(); + journal_t * journal = context.journal; + account_t * master = context.master; + + context_stack.push(*iter); + + context_stack.get_current().journal = journal; + context_stack.get_current().master = master; + try { + instance_t instance(context_stack, + context_stack.get_current(), this); + instance.parse(); + } + catch (...) { + context_stack.pop(); + throw; + } + context_stack.pop(); + files_found = true; } } @@ -805,8 +776,8 @@ void instance_t::apply_directive(char * line) void instance_t::apply_account_directive(char * line) { - if (account_t * acct = context.top_account()->find_account(line)) - context.apply_stack.push_front(application_t("account", acct)); + if (account_t * acct = top_account()->find_account(line)) + apply_stack.push_front(application_t("account", acct)); #if !defined(NO_ASSERTS) else assert("Failed to create account" == NULL); @@ -820,14 +791,14 @@ void instance_t::apply_tag_directive(char * line) if (tag.find(':') == string::npos) tag = string(":") + tag + ":"; - context.apply_stack.push_front(application_t("tag", tag)); + apply_stack.push_front(application_t("tag", tag)); } void instance_t::apply_rate_directive(char * line) { if (optional > price_point = commodity_pool_t::current_pool->parse_price_directive(trim_ws(line), true)) { - context.apply_stack.push_front + apply_stack.push_front (application_t("fixed", fixed_rate_t(price_point->first, price_point->second.price))); } else { @@ -837,11 +808,13 @@ void instance_t::apply_rate_directive(char * line) void instance_t::apply_year_directive(char * line) { - unsigned short year(lexical_cast(skip_ws(line + 1))); - DEBUG("times.epoch", "Setting current year to " << year); + apply_stack.push_front(application_t("year", epoch)); + // This must be set to the last day of the year, otherwise partial // dates like "11/01" will refer to last year's november, not the // current year. + unsigned short year(lexical_cast(skip_ws(line + 1))); + DEBUG("times.epoch", "Setting current year to " << year); epoch = datetime_t(date_t(year, 12, 31)); } @@ -850,28 +823,30 @@ void instance_t::end_apply_directive(char * kind) char * b = next_element(kind); string name(b ? b : " "); - if (context.apply_stack.size() <= 1) + if (apply_stack.size() <= 1) throw_(std::runtime_error, _("'end apply %1' found, but no enclosing 'apply %2' directive") << name << name); - if (name != " " && name != context.apply_stack.front().label) + if (name != " " && name != apply_stack.front().label) throw_(std::runtime_error, _("'end apply %1' directive does not match 'apply %2' directive") - << name << context.apply_stack.front().label); + << name << apply_stack.front().label); + + if (apply_stack.front().value.type() == typeid(optional)) + epoch = boost::get >(apply_stack.front().value); - context.apply_stack.pop_front(); + apply_stack.pop_front(); } void instance_t::account_directive(char * line) { - istream_pos_type beg_pos = line_beg_pos; - std::size_t beg_linenum = linenum; + istream_pos_type beg_pos = context.line_beg_pos; + std::size_t beg_linenum = context.linenum; char * p = skip_ws(line); account_t * account = - context.journal.register_account(p, NULL, file_context(pathname, linenum), - context.top_account()); + context.journal->register_account(p, NULL, top_account()); std::auto_ptr ae; while (peek_whitespace_line()) { @@ -900,7 +875,7 @@ void instance_t::account_directive(char * line) ae.reset(new auto_xact_t(pred)); ae->pos = position_t(); - ae->pos->pathname = pathname; + ae->pos->pathname = context.pathname; ae->pos->beg_pos = beg_pos; ae->pos->beg_line = beg_linenum; ae->pos->sequence = context.sequence++; @@ -916,7 +891,7 @@ void instance_t::account_directive(char * line) else if (keyword == "eval" || keyword == "expr") { // jww (2012-02-27): Make account into symbol scopes so that this // can be used to override definitions within the account. - bind_scope_t bound_scope(context.scope, *account); + bind_scope_t bound_scope(*context.scope, *account); expr_t(b).calc(bound_scope); } else if (keyword == "note") { @@ -925,11 +900,11 @@ void instance_t::account_directive(char * line) } if (ae.get()) { - context.journal.auto_xacts.push_back(ae.get()); + context.journal->auto_xacts.push_back(ae.get()); - ae->journal = &context.journal; + ae->journal = context.journal; ae->pos->end_pos = in.tellg(); - ae->pos->end_line = linenum; + ae->pos->end_line = context.linenum; ae.release(); } @@ -943,7 +918,7 @@ void instance_t::account_alias_directive(account_t * account, string alias) trim(alias); std::pair result = context.journal - .account_aliases.insert(accounts_map::value_type(alias, account)); + ->account_aliases.insert(accounts_map::value_type(alias, account)); assert(result.second); } @@ -957,26 +932,25 @@ void instance_t::alias_directive(char * line) *e++ = '\0'; e = skip_ws(e); - account_alias_directive(context.top_account()->find_account(e), b); + account_alias_directive(top_account()->find_account(e), b); } } void instance_t::account_payee_directive(account_t * account, string payee) { trim(payee); - context.journal.payees_for_unknown_accounts + context.journal->payees_for_unknown_accounts .push_back(account_mapping_t(mask_t(payee), account)); } void instance_t::account_default_directive(account_t * account) { - context.journal.bucket = account; + context.journal->bucket = account; } void instance_t::payee_directive(char * line) { - string payee = context.journal - .register_payee(line, NULL, file_context(pathname, linenum)); + string payee = context.journal->register_payee(line, NULL); while (peek_whitespace_line()) { read_line(line); @@ -994,7 +968,7 @@ void instance_t::payee_directive(char * line) void instance_t::payee_alias_directive(const string& payee, string alias) { trim(alias); - context.journal.payee_mappings + context.journal->payee_mappings .push_back(payee_mapping_t(mask_t(alias), payee)); } @@ -1006,8 +980,7 @@ void instance_t::commodity_directive(char * line) if (commodity_t * commodity = commodity_pool_t::current_pool->find_or_create(symbol)) { - context.journal.register_commodity(*commodity, 0, - file_context(pathname, linenum)); + context.journal->register_commodity(*commodity, 0); while (peek_whitespace_line()) { read_line(line); @@ -1067,8 +1040,7 @@ void instance_t::commodity_default_directive(commodity_t& comm) void instance_t::tag_directive(char * line) { char * p = skip_ws(line); - context.journal.register_metadata(p, NULL_VALUE, 0, - file_context(pathname, linenum)); + context.journal->register_metadata(p, NULL_VALUE, 0); while (peek_whitespace_line()) { read_line(line); @@ -1079,7 +1051,7 @@ void instance_t::tag_directive(char * line) char * b = next_element(q); string keyword(q); if (keyword == "assert" || keyword == "check") { - context.journal.tag_check_exprs.insert + context.journal->tag_check_exprs.insert (tag_check_exprs_map::value_type (string(p), expr_t::check_expr_pair(expr_t(b), @@ -1093,22 +1065,21 @@ void instance_t::tag_directive(char * line) void instance_t::eval_directive(char * line) { expr_t expr(line); - expr.calc(context.scope); + expr.calc(*context.scope); } void instance_t::assert_directive(char * line) { expr_t expr(line); - if (! expr.calc(context.scope).to_boolean()) + if (! expr.calc(*context.scope).to_boolean()) throw_(parse_error, _("Assertion failed: %1") << line); } void instance_t::check_directive(char * line) { expr_t expr(line); - if (! expr.calc(context.scope).to_boolean()) - warning_(_("%1Check failed: %2") - << file_context(pathname, linenum) << line); + if (! expr.calc(*context.scope).to_boolean()) + context.warning(STR(_("Check failed: %1") << line)); } void instance_t::comment_directive(char * line) @@ -1242,12 +1213,12 @@ post_t * instance_t::parse_post(char * line, post->xact = xact; // this could be NULL post->pos = position_t(); - post->pos->pathname = pathname; - post->pos->beg_pos = line_beg_pos; - post->pos->beg_line = linenum; + post->pos->pathname = context.pathname; + post->pos->beg_pos = context.line_beg_pos; + post->pos->beg_line = context.linenum; post->pos->sequence = context.sequence++; - char buf[MAX_LINE + 1]; + char buf[parse_context_t::MAX_LINE + 1]; std::strcpy(buf, line); std::streamsize beg = 0; @@ -1264,14 +1235,14 @@ post_t * instance_t::parse_post(char * line, case '*': post->set_state(item_t::CLEARED); p = skip_ws(p + 1); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed the CLEARED flag"); break; case '!': post->set_state(item_t::PENDING); p = skip_ws(p + 1); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed the PENDING flag"); break; } @@ -1294,25 +1265,23 @@ post_t * instance_t::parse_post(char * line, if ((*p == '[' && *(e - 1) == ']') || (*p == '(' && *(e - 1) == ')')) { post->add_flags(POST_VIRTUAL); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed a virtual account name"); if (*p == '[') { post->add_flags(POST_MUST_BALANCE); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Posting must balance"); } p++; e--; } string name(p, static_cast(e - p)); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed account name " << name); post->account = - context.journal.register_account(name, post.get(), - file_context(pathname, linenum), - account); + context.journal->register_account(name, post.get(), account); // Parse the optional amount @@ -1323,16 +1292,15 @@ post_t * instance_t::parse_post(char * line, if (*next != '(') // indicates a value expression post->amount.parse(stream, PARSE_NO_REDUCE); else - parse_amount_expr(stream, context.scope, *post.get(), post->amount, + parse_amount_expr(stream, *context.scope, *post.get(), post->amount, PARSE_NO_REDUCE | PARSE_SINGLE | PARSE_NO_ASSIGN, defer_expr, &post->amount_expr); if (! post->amount.is_null() && post->amount.has_commodity()) { - context.journal.register_commodity(post->amount.commodity(), post.get(), - file_context(pathname, linenum)); + context.journal->register_commodity(post->amount.commodity(), post.get()); if (! post->amount.has_annotation()) { - foreach (application_t& state, context.apply_stack) { + foreach (application_t& state, apply_stack) { if (state.value.type() == typeid(fixed_rate_t)) { fixed_rate_t& rate(boost::get(state.value)); if (*rate.first == post->amount.commodity()) { @@ -1346,7 +1314,7 @@ post_t * instance_t::parse_post(char * line, } } - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "post amount = " << post->amount); if (stream.eof()) { @@ -1357,7 +1325,7 @@ post_t * instance_t::parse_post(char * line, // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST) if (*next == '@') { - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Found a price indicator"); bool per_unit = true; @@ -1365,7 +1333,7 @@ post_t * instance_t::parse_post(char * line, if (*++next == '@') { per_unit = false; post->add_flags(POST_COST_IN_FULL); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "And it's for a total price"); } @@ -1389,7 +1357,7 @@ post_t * instance_t::parse_post(char * line, if (*p != '(') // indicates a value expression post->cost->parse(cstream, PARSE_NO_MIGRATE); else - parse_amount_expr(cstream, context.scope, *post.get(), *post->cost, + parse_amount_expr(cstream, *context.scope, *post.get(), *post->cost, PARSE_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_ASSIGN); if (post->cost->sign() < 0) @@ -1412,9 +1380,9 @@ post_t * instance_t::parse_post(char * line, if (fixed_cost) post->add_flags(POST_COST_FIXATED); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Total cost is " << *post->cost); - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Annotated amount is " << post->amount); if (cstream.eof()) @@ -1431,7 +1399,7 @@ post_t * instance_t::parse_post(char * line, // Parse the optional balance assignment if (xact && next && *next == '=') { - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Found a balance assignment indicator"); beg = static_cast(++next - line); @@ -1446,7 +1414,7 @@ post_t * instance_t::parse_post(char * line, if (*p != '(') // indicates a value expression post->assigned_amount->parse(stream, PARSE_NO_MIGRATE); else - parse_amount_expr(stream, context.scope, *post.get(), + parse_amount_expr(stream, *context.scope, *post.get(), *post->assigned_amount, PARSE_SINGLE | PARSE_NO_MIGRATE); @@ -1457,17 +1425,17 @@ post_t * instance_t::parse_post(char * line, throw parse_error(_("Balance assertion must evaluate to a constant")); } - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "POST assign: parsed amt = " << *post->assigned_amount); amount_t& amt(*post->assigned_amount); value_t account_total (post->account->amount().strip_annotations(keep_details_t())); + DEBUG("post.assign", "line " << context.linenum << ": " + << "account balance = " << account_total); DEBUG("post.assign", - "line " << linenum << ": " "account balance = " << account_total); - DEBUG("post.assign", - "line " << linenum << ": " "post amount = " << amt); + "line " << context.linenum << ": " << "post amount = " << amt); amount_t diff = amt; @@ -1487,9 +1455,9 @@ post_t * instance_t::parse_post(char * line, } DEBUG("post.assign", - "line " << linenum << ": " << "diff = " << diff); - DEBUG("textual.parse", - "line " << linenum << ": " << "POST assign: diff = " << diff); + "line " << context.linenum << ": " << "diff = " << diff); + DEBUG("textual.parse", "line " << context.linenum << ": " + << "POST assign: diff = " << diff); if (! diff.is_zero()) { if (! post->amount.is_null()) { @@ -1498,7 +1466,7 @@ post_t * instance_t::parse_post(char * line, throw_(parse_error, _("Balance assertion off by %1") << diff); } else { post->amount = diff; - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Overwrite null posting"); } } @@ -1515,9 +1483,9 @@ post_t * instance_t::parse_post(char * line, // Parse the optional note if (next && *next == ';') { - post->append_note(++next, context.scope, true); + post->append_note(++next, *context.scope, true); next = line + len; - DEBUG("textual.parse", "line " << linenum << ": " + DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed a posting note"); } @@ -1528,14 +1496,14 @@ post_t * instance_t::parse_post(char * line, _("Unexpected char '%1' (Note: inline math requires parentheses)") << *next); - post->pos->end_pos = curr_pos; - post->pos->end_line = linenum; + post->pos->end_pos = context.curr_pos; + post->pos->end_line = context.linenum; - if (! context.apply_stack.empty()) { - foreach (const application_t& state, context.apply_stack) + if (! apply_stack.empty()) { + foreach (const application_t& state, apply_stack) if (state.value.type() == typeid(string)) post->parse_tags(boost::get(state.value).c_str(), - context.scope, true); + *context.scope, true); } TRACE_STOP(post_details, 1); @@ -1587,9 +1555,9 @@ xact_t * instance_t::parse_xact(char * line, unique_ptr xact(new xact_t); xact->pos = position_t(); - xact->pos->pathname = pathname; - xact->pos->beg_pos = line_beg_pos; - xact->pos->beg_line = linenum; + xact->pos->pathname = context.pathname; + xact->pos->beg_pos = context.line_beg_pos; + xact->pos->beg_line = context.linenum; xact->pos->sequence = context.sequence++; bool reveal_context = true; @@ -1635,9 +1603,7 @@ xact_t * instance_t::parse_xact(char * line, if (next && *next) { char * p = next_element(next, true); - xact->payee = - context.journal.register_payee(next, xact.get(), - file_context(pathname, linenum)); + xact->payee = context.journal->register_payee(next, xact.get()); next = p; } else { xact->payee = _(""); @@ -1646,7 +1612,7 @@ xact_t * instance_t::parse_xact(char * line, // Parse the xact note if (next && *next == ';') - xact->append_note(++next, context.scope, false); + xact->append_note(++next, *context.scope, false); TRACE_STOP(xact_text, 1); @@ -1673,9 +1639,9 @@ xact_t * instance_t::parse_xact(char * line, if (*p == ';') { // This is a trailing note, and possibly a metadata info tag - item->append_note(p + 1, context.scope, true); + item->append_note(p + 1, *context.scope, true); item->add_flags(ITEM_NOTE_ON_NEXT_LINE); - item->pos->end_pos = curr_pos; + item->pos->end_pos = context.curr_pos; item->pos->end_line++; } else if ((remlen > 7 && *p == 'a' && @@ -1687,7 +1653,7 @@ xact_t * instance_t::parse_xact(char * line, const char c = *p; p = skip_ws(&p[*p == 'a' ? 6 : (*p == 'c' ? 5 : 4)]); expr_t expr(p); - bind_scope_t bound_scope(context.scope, *item); + bind_scope_t bound_scope(*context.scope, *item); if (c == 'e') { expr.calc(bound_scope); } @@ -1695,8 +1661,7 @@ xact_t * instance_t::parse_xact(char * line, if (c == 'a') { throw_(parse_error, _("Transaction assertion failed: %1") << p); } else { - warning_(_("%1Transaction check failed: %2") - << file_context(pathname, linenum) << p); + context.warning(STR(_("Transaction check failed: %1") << p)); } } } @@ -1729,14 +1694,14 @@ xact_t * instance_t::parse_xact(char * line, } #endif - xact->pos->end_pos = curr_pos; - xact->pos->end_line = linenum; + xact->pos->end_pos = context.curr_pos; + xact->pos->end_line = context.linenum; - if (! context.apply_stack.empty()) { - foreach (const application_t& state, context.apply_stack) + if (! apply_stack.empty()) { + foreach (const application_t& state, apply_stack) if (state.value.type() == typeid(string)) xact->parse_tags(boost::get(state.value).c_str(), - context.scope, false); + *context.scope, false); } TRACE_STOP(xact_details, 1); @@ -1748,7 +1713,8 @@ xact_t * instance_t::parse_xact(char * line, if (reveal_context) { add_error_context(_("While parsing transaction:")); add_error_context(source_context(xact->pos->pathname, - xact->pos->beg_pos, curr_pos, "> ")); + xact->pos->beg_pos, + context.curr_pos, "> ")); } throw; } @@ -1757,26 +1723,18 @@ xact_t * instance_t::parse_xact(char * line, expr_t::ptr_op_t instance_t::lookup(const symbol_t::kind_t kind, const string& name) { - return context.scope.lookup(kind, name); + return context.scope->lookup(kind, name); } -std::size_t journal_t::parse(std::istream& in, - scope_t& scope, - account_t * master_account, - const path * original_file) +std::size_t journal_t::read_textual(parse_context_stack_t& context_stack) { TRACE_START(parsing_total, 1, "Total time spent parsing text:"); - - parse_context_t context(*this, scope); - if (master_account || this->master) - context.apply_stack.push_front(application_t("account", - master_account ? - master_account : this->master)); - - instance_t instance(context, in, original_file); - instance.parse(); - context.close(); - + { + instance_t instance(context_stack, context_stack.get_current()); + instance.apply_stack.push_front + (application_t("account", context_stack.get_current().master)); + instance.parse(); + } TRACE_STOP(parsing_total, 1); // These tracers were started in textual.cc @@ -1787,10 +1745,10 @@ std::size_t journal_t::parse(std::istream& in, TRACE_FINISH(instance_parse, 1); // report per-instance timers TRACE_FINISH(parsing_total, 1); - if (context.errors > 0) - throw static_cast(context.errors); + if (context_stack.get_current().errors > 0) + throw static_cast(context_stack.get_current().errors); - return context.count; + return context_stack.get_current().count; } } // namespace ledger diff --git a/src/timelog.cc b/src/timelog.cc index 8d3d69c1..67ea1015 100644 --- a/src/timelog.cc +++ b/src/timelog.cc @@ -36,14 +36,14 @@ #include "post.h" #include "account.h" #include "journal.h" +#include "context.h" namespace ledger { namespace { void clock_out_from_timelog(std::list& time_xacts, time_xact_t out_event, - journal_t& journal, - scope_t& scope) + parse_context_t& context) { time_xact_t event; @@ -95,7 +95,7 @@ namespace { curr->pos = event.position; if (! event.note.empty()) - curr->append_note(event.note.c_str(), scope); + curr->append_note(event.note.c_str(), *context.scope); char buf[32]; std::sprintf(buf, "%lds", long((out_event.checkin - event.checkin) @@ -110,7 +110,7 @@ namespace { curr->add_post(post); event.account->add_post(post); - if (! journal.add_xact(curr.get())) + if (! context.journal->add_xact(curr.get())) throw parse_error(_("Failed to record 'out' timelog transaction")); else curr.release(); @@ -129,9 +129,8 @@ void time_log_t::close() DEBUG("timelog", "Clocking out from account " << account->fullname()); clock_out_from_timelog(time_xacts, time_xact_t(none, CURRENT_TIME(), account), - journal, scope); - if (context_count) - (*context_count)++; + context); + context.count++; } assert(time_xacts.empty()); } @@ -154,7 +153,7 @@ void time_log_t::clock_out(time_xact_t event) if (time_xacts.empty()) throw std::logic_error(_("Timelog check-out event without a check-in")); - clock_out_from_timelog(time_xacts, event, journal, scope); + clock_out_from_timelog(time_xacts, event, context); } } // namespace ledger diff --git a/src/timelog.h b/src/timelog.h index 12083302..ed5a2d36 100644 --- a/src/timelog.h +++ b/src/timelog.h @@ -50,6 +50,7 @@ namespace ledger { class account_t; class journal_t; +class parse_context_t; class time_xact_t { @@ -86,15 +87,11 @@ public: class time_log_t : public boost::noncopyable { std::list time_xacts; - journal_t& journal; - scope_t& scope; + parse_context_t& context; public: - std::size_t * context_count; - - time_log_t(journal_t& _journal, scope_t& _scope) - : journal(_journal), scope(_scope), context_count(NULL) { - TRACE_CTOR(time_log_t, "journal_t&, scope_t&, std::size&"); + time_log_t(parse_context_t& _context) : context(_context) { + TRACE_CTOR(time_log_t, "parse_context_t&"); } ~time_log_t() { TRACE_DTOR(time_log_t); diff --git a/src/xact.cc b/src/xact.cc index 0fedb42a..0251edde 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -35,6 +35,7 @@ #include "post.h" #include "account.h" #include "journal.h" +#include "context.h" #include "pool.h" namespace ledger { @@ -608,7 +609,7 @@ namespace { } } -void auto_xact_t::extend_xact(xact_base_t& xact) +void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) { posts_list initial_posts(xact.posts.begin(), xact.posts.end()); @@ -674,7 +675,8 @@ void auto_xact_t::extend_xact(xact_base_t& xact) throw_(parse_error, _("Transaction assertion failed: %1") << pair.first); else - warning_(_("Transaction check failed: %1") << pair.first); + context.warning(STR(_("Transaction check failed: %1") + << pair.first)); } } } diff --git a/src/xact.h b/src/xact.h index cb7bdeb3..7d7fb826 100644 --- a/src/xact.h +++ b/src/xact.h @@ -49,6 +49,7 @@ namespace ledger { class post_t; class journal_t; +class parse_context_t; typedef std::list posts_list; @@ -209,7 +210,7 @@ public: deferred_notes->push_back(deferred_tag_data_t(p, overwrite_existing)); } - virtual void extend_xact(xact_base_t& xact); + virtual void extend_xact(xact_base_t& xact, parse_context_t& context); #if defined(HAVE_BOOST_SERIALIZATION) private: diff --git a/test/baseline/dir-tag.test b/test/baseline/dir-tag.test index b1858146..496334a0 100644 --- a/test/baseline/dir-tag.test +++ b/test/baseline/dir-tag.test @@ -17,5 +17,5 @@ test reg 12-Feb-28 KFC food $20.00 $20.00 Assets:Cash $-20.00 0 __ERROR__ -Warning: Metadata check failed for (Happy: Summer): (value == "Valley") +Warning: "/Users/johnw/Projects/ledger/test/baseline/dir-tag.test", line 8: Metadata check failed for (Happy: Summer): (value == "Valley") end test diff --git a/test/baseline/feat-check.test b/test/baseline/feat-check.test index 9a2e72df..a9db1ec4 100644 --- a/test/baseline/feat-check.test +++ b/test/baseline/feat-check.test @@ -13,6 +13,6 @@ test bal -------------------- 0 __ERROR__ -Warning: Transaction check failed: (account =~ /Foo/) +Warning: "$sourcepath/test/baseline/feat-check.test", line 6: Transaction check failed: (account =~ /Foo/) Warning: "$sourcepath/test/baseline/feat-check.test", line 8: Check failed: account("Assets:Checking").all(account =~ /Expense/) end test -- cgit v1.2.3 From 9ec9cdf41e5176f7fcf06da5f75593d9ba3d4028 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Thu, 1 Mar 2012 05:50:07 -0600 Subject: Started writing Python unit tests --- src/error.h | 6 +++ src/global.h | 2 +- src/main.cc | 7 ++-- src/py_amount.cc | 2 +- src/py_commodity.cc | 40 +++++++++----------- src/py_journal.cc | 93 ++++++++++++++++++++++------------------------ src/py_post.cc | 2 +- src/py_session.cc | 76 +++++++++++++++++++++++++++++++++++++ src/py_xact.cc | 8 ++++ src/pyinterp.cc | 2 + src/session.cc | 39 +++++++++++++++++-- src/session.h | 4 +- src/system.hh.in | 2 + src/textual.cc | 2 +- src/utils.cc | 3 +- test/python/JournalTest.py | 25 ++++++++----- tools/Makefile.am | 1 + 17 files changed, 220 insertions(+), 94 deletions(-) create mode 100644 src/py_session.cc (limited to 'src/py_journal.cc') diff --git a/src/error.h b/src/error.h index 7630f017..86d9de76 100644 --- a/src/error.h +++ b/src/error.h @@ -101,6 +101,12 @@ string source_context(const path& file, virtual ~name() throw() {} \ } +struct error_count { + std::size_t count; + explicit error_count(std::size_t _count) : count(_count) {} + const char * what() const { return ""; } +}; + } // namespace ledger #endif // _ERROR_H diff --git a/src/global.h b/src/global.h index 28bffc3a..2cb7842e 100644 --- a/src/global.h +++ b/src/global.h @@ -155,7 +155,7 @@ See LICENSE file included with the distribution for details and disclaimer."); OPTION_(global_scope_t, version, DO() { // -v parent->show_version_info(std::cout); - throw int(0); // exit immediately + throw error_count(0); // exit immediately }); }; diff --git a/src/main.cc b/src/main.cc index 2202a5de..aafbdbcb 100644 --- a/src/main.cc +++ b/src/main.cc @@ -191,9 +191,10 @@ int main(int argc, char * argv[], char * envp[]) std::cerr << "Exception during initialization: " << err.what() << std::endl; } - catch (int _status) { - status = _status; // used for a "quick" exit, and is used only - // if help text (such as --help) was displayed + catch (const error_count& errors) { + // used for a "quick" exit, and is used only if help text (such as + // --help) was displayed + status = static_cast(errors.count); } // If memory verification is being performed (which can be very slow), clean diff --git a/src/py_amount.cc b/src/py_amount.cc index f10595e8..25ec8e26 100644 --- a/src/py_amount.cc +++ b/src/py_amount.cc @@ -263,7 +263,7 @@ internal precision.")) .add_property("commodity", make_function(&amount_t::commodity, - return_value_policy()), + return_internal_reference<>()), make_function(&amount_t::set_commodity, with_custodian_and_ward<1, 2>())) .def("has_commodity", &amount_t::has_commodity) diff --git a/src/py_commodity.cc b/src/py_commodity.cc index ffa903f4..b5230850 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -269,18 +269,14 @@ void export_commodity() .def("make_qualified_name", &commodity_pool_t::make_qualified_name) - .def("create", py_create_1, - return_value_policy()) - .def("create", py_create_2, - return_value_policy()) + .def("create", py_create_1, return_internal_reference<>()) + .def("create", py_create_2, return_internal_reference<>()) - .def("find_or_create", py_find_or_create_1, - return_value_policy()) - .def("find_or_create", py_find_or_create_2, - return_value_policy()) + .def("find_or_create", py_find_or_create_1, return_internal_reference<>()) + .def("find_or_create", py_find_or_create_2, return_internal_reference<>()) - .def("find", py_find_1, return_value_policy()) - .def("find", py_find_2, return_value_policy()) + .def("find", py_find_1, return_internal_reference<>()) + .def("find", py_find_2, return_internal_reference<>()) .def("exchange", py_exchange_2, with_custodian_and_ward<1, 2>()) .def("exchange", py_exchange_3, with_custodian_and_ward<1, 2>()) @@ -288,23 +284,23 @@ void export_commodity() .def("parse_price_directive", &commodity_pool_t::parse_price_directive) .def("parse_price_expression", &commodity_pool_t::parse_price_expression, - return_value_policy()) + return_internal_reference<>()) .def("__getitem__", py_pool_getitem, - return_value_policy()) + return_internal_reference<>()) .def("keys", py_pool_keys) .def("has_key", py_pool_contains) .def("__contains__", py_pool_contains) .def("__iter__", - python::range > + python::range > (py_pool_commodities_begin, py_pool_commodities_end)) .def("iteritems", - python::range > + python::range > (py_pool_commodities_begin, py_pool_commodities_end)) .def("iterkeys", python::range<>(py_pool_commodities_keys_begin, py_pool_commodities_keys_end)) .def("itervalues", - python::range > + python::range > (py_pool_commodities_values_begin, py_pool_commodities_values_end)) ; @@ -349,17 +345,17 @@ void export_commodity() .add_property("referent", make_function(py_commodity_referent, - return_value_policy())) + return_internal_reference<>())) .def("has_annotation", &commodity_t::has_annotation) .def("strip_annotations", py_strip_annotations_0, - return_value_policy()) + return_internal_reference<>()) .def("strip_annotations", py_strip_annotations_1, - return_value_policy()) + return_internal_reference<>()) .def("write_annotations", &commodity_t::write_annotations) .def("pool", &commodity_t::pool, - return_value_policy()) + return_internal_reference<>()) .add_property("base_symbol", &commodity_t::base_symbol) .add_property("symbol", &commodity_t::symbol) @@ -441,12 +437,12 @@ void export_commodity() .add_property("referent", make_function(py_annotated_commodity_referent, - return_value_policy())) + return_internal_reference<>())) .def("strip_annotations", py_strip_ann_annotations_0, - return_value_policy()) + return_internal_reference<>()) .def("strip_annotations", py_strip_ann_annotations_1, - return_value_policy()) + return_internal_reference<>()) .def("write_annotations", &annotated_commodity_t::write_annotations) ; } diff --git a/src/py_journal.cc b/src/py_journal.cc index a72b8528..550fb14e 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -144,10 +144,10 @@ namespace { struct collector_wrapper { - journal_t& journal; - report_t report; - collect_posts * posts_collector; - post_handler_ptr chain; + journal_t& journal; + report_t report; + + post_handler_ptr posts_collector; collector_wrapper(journal_t& _journal, report_t& base) : journal(_journal), report(base), @@ -157,31 +157,32 @@ namespace { } std::size_t length() const { - return posts_collector->length(); + return dynamic_cast(posts_collector.get())->length(); } std::vector::iterator begin() { - return posts_collector->begin(); + return dynamic_cast(posts_collector.get())->begin(); } std::vector::iterator end() { - return posts_collector->end(); + return dynamic_cast(posts_collector.get())->end(); } }; - shared_ptr - py_collect(journal_t& journal, const string& query) + shared_ptr py_query(journal_t& journal, + const string& query) { if (journal.has_xdata()) { PyErr_SetString(PyExc_RuntimeError, - _("Cannot have multiple journal collections open at once")); + _("Cannot have more than one active journal query")); throw_error_already_set(); } report_t& current_report(downcast(*scope_t::default_scope)); - shared_ptr coll(new collector_wrapper(journal, - current_report)); - unique_ptr save_journal(current_report.session.journal.release()); - current_report.session.journal.reset(&journal); + shared_ptr + coll(new collector_wrapper(journal, current_report)); + + unique_ptr save_journal(coll->report.session.journal.release()); + coll->report.session.journal.reset(&coll->journal); try { strings_list remaining = @@ -191,60 +192,48 @@ namespace { value_t args; foreach (const string& arg, remaining) args.push_back(string_value(arg)); - coll->report.parse_query_args(args, "@Journal.collect"); + coll->report.parse_query_args(args, "@Journal.query"); - journal_posts_iterator walker(coll->journal); - coll->chain = - chain_post_handlers(post_handler_ptr(coll->posts_collector), - coll->report); - pass_down_posts(coll->chain, walker); + coll->report.posts_report(coll->posts_collector); } catch (...) { - current_report.session.journal.release(); - current_report.session.journal.reset(save_journal.release()); + coll->report.session.journal.release(); + coll->report.session.journal.reset(save_journal.release()); throw; } - current_report.session.journal.release(); - current_report.session.journal.reset(save_journal.release()); + coll->report.session.journal.release(); + coll->report.session.journal.reset(save_journal.release()); return coll; } post_t * posts_getitem(collector_wrapper& collector, long i) { - post_t * post = - collector.posts_collector->posts[static_cast(i)]; - std::cerr << typeid(post).name() << std::endl; - std::cerr << typeid(*post).name() << std::endl; - std::cerr << typeid(post->account).name() << std::endl; - std::cerr << typeid(*post->account).name() << std::endl; - return post; + return dynamic_cast(collector.posts_collector.get()) + ->posts[static_cast(i)]; } } // unnamed namespace +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_RuntimeError, err.what()); \ + } + +EXC_TRANSLATOR(parse_error) +EXC_TRANSLATOR(error_count) + void export_journal() { class_< item_handler, shared_ptr >, boost::noncopyable >("PostHandler") ; - class_< collect_posts, bases >, - shared_ptr, boost::noncopyable >("PostCollector") - .def("__len__", &collect_posts::length) - .def("__iter__", python::range > > - (&collect_posts::begin, &collect_posts::end)) - ; - class_< collector_wrapper, shared_ptr, boost::noncopyable >("PostCollectorWrapper", no_init) .def("__len__", &collector_wrapper::length) - .def("__getitem__", posts_getitem, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) - .def("__iter__", - python::range > > + .def("__getitem__", posts_getitem, return_internal_reference<>()) + .def("__iter__", python::range > (&collector_wrapper::begin, &collector_wrapper::end)) ; @@ -286,13 +275,13 @@ void export_journal() .def("find_account", py_find_account_1, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) .def("find_account", py_find_account_2, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) .def("find_account_re", &journal_t::find_account_re, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) .def("add_xact", &journal_t::add_xact) .def("remove_xact", &journal_t::remove_xact) @@ -301,7 +290,7 @@ void export_journal() #if 0 .def("__getitem__", xacts_getitem, return_internal_reference<1, - with_custodian_and_ward_postcall<0, 1> >()) + with_custodian_and_ward_postcall<1, 0> >()) #endif .def("__iter__", python::range > @@ -320,10 +309,16 @@ void export_journal() .def("has_xdata", &journal_t::has_xdata) .def("clear_xdata", &journal_t::clear_xdata) - .def("collect", py_collect, with_custodian_and_ward_postcall<0, 1>()) + .def("query", py_query) .def("valid", &journal_t::valid) ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator(&exc_translate_ ## type); + + EXC_TRANSLATE(parse_error); + EXC_TRANSLATE(error_count); } } // namespace ledger diff --git a/src/py_post.cc b/src/py_post.cc index bd599604..2789082e 100644 --- a/src/py_post.cc +++ b/src/py_post.cc @@ -116,7 +116,7 @@ void export_post() make_setter(&post_t::xdata_t::datetime)) .add_property("account", make_getter(&post_t::xdata_t::account, - return_value_policy()), + return_internal_reference<>()), make_setter(&post_t::xdata_t::account, with_custodian_and_ward<1, 2>())) .add_property("sort_values", diff --git a/src/py_session.cc b/src/py_session.cc new file mode 100644 index 00000000..f411d5e1 --- /dev/null +++ b/src/py_session.cc @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "pyinterp.h" +#include "pyutils.h" +#include "session.h" + +namespace ledger { + +using namespace boost::python; + +namespace { + journal_t * py_read_journal(const string& pathname) + { + return python_session->read_journal(path(pathname)); + } + + journal_t * py_read_journal_from_string(const string& data) + { + return python_session->read_journal_from_string(data); + } +} + +void export_session() +{ + class_< session_t, boost::noncopyable > ("Session") + .def("read_journal", &session_t::read_journal, + return_internal_reference<>()) + .def("read_journal_from_string", &session_t::read_journal_from_string, + return_internal_reference<>()) + .def("read_journal_files", &session_t::read_journal_files, + return_internal_reference<>()) + .def("close_journal_files", &session_t::close_journal_files) + ; + + scope().attr("session") = + object(ptr(static_cast(python_session.get()))); + scope().attr("read_journal") = + python::make_function(&py_read_journal, + return_internal_reference<>()); + scope().attr("read_journal_from_string") = + python::make_function(&py_read_journal_from_string, + return_internal_reference<>()); +} + +} // namespace ledger diff --git a/src/py_xact.cc b/src/py_xact.cc index 97d5df47..3d792c7b 100644 --- a/src/py_xact.cc +++ b/src/py_xact.cc @@ -76,6 +76,12 @@ namespace { return **elem; } + string py_xact_to_string(xact_t&) + { + // jww (2012-03-01): TODO + return empty_string; + } + } // unnamed namespace using namespace boost::python; @@ -110,6 +116,8 @@ void export_xact() .def("id", &xact_t::id) .def("seq", &xact_t::seq) + .def("__str__", py_xact_to_string) + .add_property("code", make_getter(&xact_t::code), make_setter(&xact_t::code)) diff --git a/src/pyinterp.cc b/src/pyinterp.cc index de9c94cb..4dbb7134 100644 --- a/src/pyinterp.cc +++ b/src/pyinterp.cc @@ -51,6 +51,7 @@ void export_commodity(); void export_expr(); void export_format(); void export_item(); +void export_session(); void export_journal(); void export_post(); void export_times(); @@ -72,6 +73,7 @@ void initialize_for_python() export_item(); export_post(); export_xact(); + export_session(); export_journal(); } diff --git a/src/session.cc b/src/session.cc index e07026b6..db01fbf6 100644 --- a/src/session.cc +++ b/src/session.cc @@ -89,8 +89,10 @@ std::size_t session_t::read_data(const string& master_account) std::size_t xact_count = 0; - account_t * acct = journal->master; - if (! master_account.empty()) + account_t * acct; + if (master_account.empty()) + acct = journal->master; + else acct = journal->find_account(master_account); optional price_db_path; @@ -185,7 +187,7 @@ std::size_t session_t::read_data(const string& master_account) return journal->xacts.size(); } -void session_t::read_journal_files() +journal_t * session_t::read_journal_files() { INFO_START(journal, "Read journal file"); @@ -203,6 +205,37 @@ void session_t::read_journal_files() #if defined(DEBUG_ON) INFO("Found " << count << " transactions"); #endif + + return journal.get(); +} + +journal_t * session_t::read_journal(const path& pathname) +{ + HANDLER(file_).data_files.clear(); + HANDLER(file_).data_files.push_back(pathname); + + return read_journal_files(); +} + +journal_t * session_t::read_journal_from_string(const string& data) +{ + HANDLER(file_).data_files.clear(); + + shared_ptr stream(new std::istringstream(data)); + parsing_context.push(stream); + + parsing_context.get_current().journal = journal.get(); + parsing_context.get_current().master = journal->master; + try { + journal->read(parsing_context); + } + catch (...) { + parsing_context.pop(); + throw; + } + parsing_context.pop(); + + return journal.get(); } void session_t::close_journal_files() diff --git a/src/session.h b/src/session.h index 54b9912a..38062b78 100644 --- a/src/session.h +++ b/src/session.h @@ -75,9 +75,11 @@ public: flush_on_next_data_file = truth; } + journal_t * read_journal(const path& pathname); + journal_t * read_journal_from_string(const string& data); std::size_t read_data(const string& master_account = ""); - void read_journal_files(); + journal_t * read_journal_files(); void close_journal_files(); value_t fn_account(call_scope_t& scope); diff --git a/src/system.hh.in b/src/system.hh.in index e14166b2..3aa60f71 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -257,4 +257,6 @@ void serialize(Archive& ar, istream_pos_type& pos, const unsigned int) #include #include +#include + #endif // HAVE_BOOST_PYTHON diff --git a/src/textual.cc b/src/textual.cc index 97c80e4f..2f09c063 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1746,7 +1746,7 @@ std::size_t journal_t::read_textual(parse_context_stack_t& context_stack) TRACE_FINISH(parsing_total, 1); if (context_stack.get_current().errors > 0) - throw static_cast(context_stack.get_current().errors); + throw error_count(context_stack.get_current().errors); return context_stack.get_current().count; } diff --git a/src/utils.cc b/src/utils.cc index 09526267..eb1d8009 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -612,8 +612,7 @@ optional _log_category_re; struct __maybe_enable_debugging { __maybe_enable_debugging() { - const char * p = std::getenv("LEDGER_DEBUG"); - if (p != NULL) { + if (const char * p = std::getenv("LEDGER_DEBUG")) { _log_level = LOG_DEBUG; _log_category = p; } diff --git a/test/python/JournalTest.py b/test/python/JournalTest.py index 66447f87..e65c671d 100644 --- a/test/python/JournalTest.py +++ b/test/python/JournalTest.py @@ -1,22 +1,27 @@ # -*- coding: utf-8 -*- import unittest -import exceptions -import operator from ledger import * -from StringIO import * -from datetime import * class JournalTestCase(unittest.TestCase): - def setUp(self): - pass - def tearDown(self): - pass + session.close_journal_files() + + def testBasicRead(self): + journal = read_journal_from_string(""" +2012-03-01 KFC + Expenses:Food $21.34 + Assets:Cash +""") + self.assertEqual(type(journal), Journal) + + for xact in journal: + self.assertEqual(xact.payee, "KFC") - def test_(self): - pass + for post in journal.query("food"): + self.assertEqual(str(post.account), "Expenses:Food") + self.assertEqual(post.amount, Amount("$21.34")) def suite(): return unittest.TestLoader().loadTestsFromTestCase(JournalTestCase) diff --git a/tools/Makefile.am b/tools/Makefile.am index fe0681e5..4fdd8393 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -226,6 +226,7 @@ libledger_python_la_SOURCES = \ src/py_expr.cc \ src/py_format.cc \ src/py_item.cc \ + src/py_session.cc \ src/py_journal.cc \ src/py_post.cc \ src/py_times.cc \ -- cgit v1.2.3 From f9088f88360019bb4be8743dd8091036502adb9c Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 18 Mar 2012 01:01:30 -0500 Subject: Added --verify-memory and missing TRACE_[CD]TOR calls --- doc/ledger.1 | 3 +- src/account.h | 11 +++- src/accum.h | 11 +++- src/commodity.h | 4 +- src/csv.h | 4 ++ src/draft.h | 16 ++++- src/global.cc | 21 +++++-- src/global.h | 1 + src/history.cc | 7 ++- src/iterators.cc | 8 ++- src/iterators.h | 103 ++++++++++++++++++++++++++++---- src/main.cc | 1 + src/pstream.h | 5 ++ src/py_journal.cc | 5 +- src/report.cc | 7 ++- src/report.h | 23 +++++++- src/scope.h | 14 ++++- src/temps.h | 4 ++ src/utils.cc | 110 ++++++++++++++++++++++++++--------- test/baseline/opt-verify-memory.test | 0 20 files changed, 299 insertions(+), 59 deletions(-) create mode 100644 test/baseline/opt-verify-memory.test (limited to 'src/py_journal.cc') diff --git a/doc/ledger.1 b/doc/ledger.1 index cd76d5b0..9cfa41c0 100644 --- a/doc/ledger.1 +++ b/doc/ledger.1 @@ -1,4 +1,4 @@ -.Dd March 17, 2012 +.Dd March 18, 2012 .Dt ledger 1 .Sh NAME .Nm ledger @@ -431,6 +431,7 @@ appeared in the original journal file. .It Fl \-value-expr Ar EXPR .It Fl \-verbose .It Fl \-verify +.It Fl \-verify-memory .It Fl \-version .It Fl \-weekly Pq Fl W .It Fl \-wide Pq Fl w diff --git a/src/account.h b/src/account.h index 4ddd85e7..fee12595 100644 --- a/src/account.h +++ b/src/account.h @@ -189,7 +189,16 @@ public: posts_cleared_count(0), posts_last_7_count(0), posts_last_30_count(0), - posts_this_month_count(0) {} + posts_this_month_count(0) { + TRACE_CTOR(account_t::xdata_t::details_t, ""); + } + // A copy copies nothing + details_t(const details_t&) { + TRACE_CTOR(account_t::xdata_t::details_t, "copy"); + } + ~details_t() throw() { + TRACE_DTOR(account_t::xdata_t::details_t); + } details_t& operator+=(const details_t& other); diff --git a/src/accum.h b/src/accum.h index dde93c30..628a6b36 100644 --- a/src/accum.h +++ b/src/accum.h @@ -51,7 +51,12 @@ protected: std::string::size_type index; public: - straccbuf() : index(0) {} + straccbuf() : index(0) { + TRACE_CTOR(straccbuf, ""); + } + ~straccbuf() throw() { + TRACE_DTOR(straccbuf); + } protected: virtual std::streamsize xsputn(const char * s, std::streamsize num); @@ -66,8 +71,12 @@ protected: public: straccstream() : std::ostream(0) { + TRACE_CTOR(straccstream, ""); rdbuf(&buf); } + ~straccstream() throw() { + TRACE_DTOR(straccstream); + } void clear() { std::ostream::clear(); diff --git a/src/commodity.h b/src/commodity.h index 148a3636..ba47a572 100644 --- a/src/commodity.h +++ b/src/commodity.h @@ -132,10 +132,10 @@ protected: static_cast(COMMODITY_STYLE_DECIMAL_COMMA) : static_cast(COMMODITY_STYLE_DEFAULTS)), symbol(_symbol), precision(0) { - TRACE_CTOR(base_t, "const string&"); + TRACE_CTOR(commodity_t::base_t, "const string&"); } virtual ~base_t() { - TRACE_DTOR(base_t); + TRACE_DTOR(commodity_t::base_t); } #if defined(HAVE_BOOST_SERIALIZATION) diff --git a/src/csv.h b/src/csv.h index 24ea9121..d98c0567 100644 --- a/src/csv.h +++ b/src/csv.h @@ -91,8 +91,12 @@ public: cost_mask("cost"), total_mask("total"), note_mask("note") { + TRACE_CTOR(csv_reader, "parse_context_t&"); read_index(*context.stream.get()); } + ~csv_reader() { + TRACE_DTOR(csv_reader); + } void read_index(std::istream& in); string read_field(std::istream& in); diff --git a/src/draft.h b/src/draft.h index 41485731..e5d29134 100644 --- a/src/draft.h +++ b/src/draft.h @@ -68,12 +68,22 @@ class draft_t : public expr_base_t optional cost_operator; optional cost; - post_template_t() : from(false) {} + post_template_t() : from(false) { + TRACE_CTOR(post_template_t, ""); + } + ~post_template_t() throw() { + TRACE_DTOR(post_template_t); + } }; std::list posts; - xact_template_t() {} + xact_template_t() { + TRACE_CTOR(xact_template_t, ""); + } + ~xact_template_t() throw() { + TRACE_DTOR(xact_template_t); + } void dump(std::ostream& out) const; }; @@ -86,7 +96,7 @@ public: if (! args.empty()) parse_args(args); } - virtual ~draft_t() { + virtual ~draft_t() throw() { TRACE_DTOR(draft_t); } diff --git a/src/global.cc b/src/global.cc index d7742161..b5ceb614 100644 --- a/src/global.cc +++ b/src/global.cc @@ -272,6 +272,7 @@ void global_scope_t::report_options(report_t& report, std::ostream& out) HANDLER(trace_).report(out); HANDLER(verbose).report(out); HANDLER(verify).report(out); + HANDLER(verify_memory).report(out); out << std::endl << "[Session scope options]" << std::endl; report.session.report_options(out); @@ -315,6 +316,7 @@ option_t * global_scope_t::lookup_option(const char * p) case 'v': OPT_(verbose); else OPT(verify); + else OPT(verify_memory); else OPT(version); break; } @@ -452,29 +454,36 @@ void handle_debug_options(int argc, char * argv[]) if (std::strcmp(argv[i], "--args-only") == 0) { args_only = true; } + else if (std::strcmp(argv[i], "--verify-memory") == 0) { +#if defined(VERIFY_ON) + verify_enabled = true; + + _log_level = LOG_DEBUG; + _log_category = "memory\\.counts"; +#endif + } else if (std::strcmp(argv[i], "--verify") == 0) { #if defined(VERIFY_ON) - verify_enabled = true; // global in utils.h + verify_enabled = true; #endif } else if (std::strcmp(argv[i], "--verbose") == 0 || std::strcmp(argv[i], "-v") == 0) { #if defined(LOGGING_ON) - _log_level = LOG_INFO; // global in utils.h + _log_level = LOG_INFO; #endif } else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { #if defined(DEBUG_ON) - _log_level = LOG_DEBUG; // global in utils.h - _log_category = argv[i + 1]; // global in utils.h + _log_level = LOG_DEBUG; + _log_category = argv[i + 1]; i++; #endif } else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) { #if defined(TRACING_ON) - _log_level = LOG_TRACE; // global in utils.h + _log_level = LOG_TRACE; try { - // global in utils.h _trace_level = boost::lexical_cast(argv[i + 1]); } catch (const boost::bad_lexical_cast&) { diff --git a/src/global.h b/src/global.h index 0c11e025..5786bb89 100644 --- a/src/global.h +++ b/src/global.h @@ -158,6 +158,7 @@ See LICENSE file included with the distribution for details and disclaimer."); OPTION(global_scope_t, trace_); OPTION(global_scope_t, verbose); OPTION(global_scope_t, verify); + OPTION(global_scope_t, verify_memory); OPTION_(global_scope_t, version, DO() { // -v parent->show_version_info(std::cout); diff --git a/src/history.cc b/src/history.cc index f1e88401..d94ec647 100644 --- a/src/history.cc +++ b/src/history.cc @@ -423,7 +423,12 @@ commodity_history_t::find_price(const commodity_t& source, template class label_writer { public: - label_writer(Name _name) : name(_name) {} + label_writer(Name _name) : name(_name) { + TRACE_CTOR(label_writer, "Name"); + } + ~label_writer() throw() { + TRACE_DTOR(label_writer); + } template void operator()(std::ostream& out, const VertexOrEdge& v) const { diff --git a/src/iterators.cc b/src/iterators.cc index acbb581f..7cc1291a 100644 --- a/src/iterators.cc +++ b/src/iterators.cc @@ -88,7 +88,13 @@ namespace { create_price_xact(journal_t& _journal, account_t * _account, temporaries_t& _temps, xacts_list& _xact_temps) : journal(_journal), account(_account), temps(_temps), - xact_temps(_xact_temps) {} + xact_temps(_xact_temps) { + TRACE_CTOR(create_price_xact, + "journal_t&, account_t *, temporaries_t&, xacts_list&"); + } + ~create_price_xact() throw() { + TRACE_DTOR(create_price_xact); + } void operator()(datetime_t& date, const amount_t& price) { xact_t * xact; diff --git a/src/iterators.h b/src/iterators.h index 5bb9de6f..922ebccd 100644 --- a/src/iterators.h +++ b/src/iterators.h @@ -58,7 +58,15 @@ class iterator_facade_base typedef Value node_base; public: - iterator_facade_base() : m_node(NULL) {} + iterator_facade_base() : m_node(NULL) { + TRACE_CTOR(iterator_facade_base, ""); + } + iterator_facade_base(const iterator_facade_base& i) : m_node(i.m_node) { + TRACE_CTOR(iterator_facade_base, "copy"); + } + ~iterator_facade_base() throw() { + TRACE_DTOR(iterator_facade_base); + } explicit iterator_facade_base(node_base p) : m_node(p) {} @@ -87,12 +95,24 @@ class xact_posts_iterator bool posts_uninitialized; public: - xact_posts_iterator() : posts_uninitialized(true) {} + xact_posts_iterator() : posts_uninitialized(true) { + TRACE_CTOR(xact_posts_iterator, ""); + } xact_posts_iterator(xact_t& xact) : posts_uninitialized(true) { + TRACE_CTOR(xact_posts_iterator, "xact_t&"); reset(xact); } - ~xact_posts_iterator() throw() {} + xact_posts_iterator(const xact_posts_iterator& i) + : iterator_facade_base(i), + posts_i(i.posts_i), posts_end(i.posts_end), + posts_uninitialized(i.posts_uninitialized) { + TRACE_CTOR(xact_posts_iterator, "copy"); + } + ~xact_posts_iterator() throw() { + TRACE_DTOR(xact_posts_iterator); + } void reset(xact_t& xact) { posts_i = xact.posts.begin(); @@ -121,15 +141,28 @@ public: bool xacts_uninitialized; - xacts_iterator() : xacts_uninitialized(true) {} + xacts_iterator() : xacts_uninitialized(true) { + TRACE_CTOR(xacts_iterator, ""); + } xacts_iterator(journal_t& journal) : xacts_uninitialized(false) { + TRACE_CTOR(xacts_iterator, "journal_t&"); reset(journal); } xacts_iterator(xacts_list::iterator beg, xacts_list::iterator end) : xacts_uninitialized(false) { + TRACE_CTOR(xacts_iterator, "xacts_list::iterator, xacts_list::iterator"); reset(beg, end); } - ~xacts_iterator() throw() {} + xacts_iterator(const xacts_iterator& i) + : iterator_facade_base(i), + xacts_i(i.xacts_i), xacts_end(i.xacts_end), + xacts_uninitialized(i.xacts_uninitialized) { + TRACE_CTOR(xacts_iterator, "copy"); + } + ~xacts_iterator() throw() { + TRACE_DTOR(xacts_iterator); + } void reset(journal_t& journal); @@ -150,11 +183,22 @@ class journal_posts_iterator xact_posts_iterator posts; public: - journal_posts_iterator() {} + journal_posts_iterator() { + TRACE_CTOR(journal_posts_iterator, ""); + } journal_posts_iterator(journal_t& journal) { + TRACE_CTOR(journal_posts_iterator, "journal_t&"); reset(journal); } - ~journal_posts_iterator() throw() {} + journal_posts_iterator(const journal_posts_iterator& i) + : iterator_facade_base(i), + xacts(i.xacts), posts(i.posts) { + TRACE_CTOR(journal_posts_iterator, "copy"); + } + ~journal_posts_iterator() throw() { + TRACE_DTOR(journal_posts_iterator); + } void reset(journal_t& journal); @@ -173,11 +217,23 @@ protected: temporaries_t temps; public: - posts_commodities_iterator() {} + posts_commodities_iterator() { + TRACE_CTOR(posts_commodities_iterator, ""); + } posts_commodities_iterator(journal_t& journal) { + TRACE_CTOR(posts_commodities_iterator, "journal_t&"); reset(journal); } - ~posts_commodities_iterator() throw() {} + posts_commodities_iterator(const posts_commodities_iterator& i) + : iterator_facade_base(i), + journal_posts(i.journal_posts), xacts(i.xacts), posts(i.posts), + xact_temps(i.xact_temps), temps(i.temps) { + TRACE_CTOR(posts_commodities_iterator, "copy"); + } + ~posts_commodities_iterator() throw() { + TRACE_DTOR(posts_commodities_iterator); + } void reset(journal_t& journal); @@ -192,12 +248,23 @@ class basic_accounts_iterator std::list accounts_end; public: - basic_accounts_iterator() {} + basic_accounts_iterator() { + TRACE_CTOR(basic_accounts_iterator, ""); + } basic_accounts_iterator(account_t& account) { + TRACE_CTOR(basic_accounts_iterator, "account_t&"); push_back(account); increment(); } - ~basic_accounts_iterator() throw() {} + basic_accounts_iterator(const basic_accounts_iterator& i) + : iterator_facade_base(i), + accounts_i(i.accounts_i), accounts_end(i.accounts_end) { + TRACE_CTOR(basic_accounts_iterator, "copy"); + } + ~basic_accounts_iterator() throw() { + TRACE_DTOR(basic_accounts_iterator); + } void increment(); @@ -225,10 +292,22 @@ public: sorted_accounts_iterator(account_t& account, const expr_t& _sort_cmp, bool _flatten_all) : sort_cmp(_sort_cmp), flatten_all(_flatten_all) { + TRACE_CTOR(sorted_accounts_iterator, "account_t&, expr_t, bool"); push_back(account); increment(); } - ~sorted_accounts_iterator() throw() {} + sorted_accounts_iterator(const sorted_accounts_iterator& i) + : iterator_facade_base(i), + sort_cmp(i.sort_cmp), flatten_all(i.flatten_all), + accounts_list(i.accounts_list), + sorted_accounts_i(i.sorted_accounts_i), + sorted_accounts_end(i.sorted_accounts_end) { + TRACE_CTOR(sorted_accounts_iterator, "copy"); + } + ~sorted_accounts_iterator() throw() { + TRACE_DTOR(sorted_accounts_iterator); + } void increment(); diff --git a/src/main.cc b/src/main.cc index 9d2ba311..0130d5c6 100644 --- a/src/main.cc +++ b/src/main.cc @@ -58,6 +58,7 @@ int main(int argc, char * argv[], char * envp[]) // --verbose ; turns on logging // --debug CATEGORY ; turns on debug logging // --trace LEVEL ; turns on trace logging + // --memory ; turns on memory usage tracing handle_debug_options(argc, argv); #if defined(VERIFY_ON) IF_VERIFY() initialize_memory_tracing(); diff --git a/src/pstream.h b/src/pstream.h index a894325d..dfb27056 100644 --- a/src/pstream.h +++ b/src/pstream.h @@ -58,6 +58,8 @@ class ptristream : public std::istream public: ptrinbuf(char * _ptr, std::size_t _len) : ptr(_ptr), len(_len) { + TRACE_CTOR(ptrinbuf, "char *, std::size_t"); + if (*ptr && len == 0) len = std::strlen(ptr); @@ -65,6 +67,9 @@ class ptristream : public std::istream ptr, // read position ptr+len); // end position } + ~ptrinbuf() throw() { + TRACE_DTOR(ptrinbuf); + } protected: virtual int_type underflow() { diff --git a/src/py_journal.cc b/src/py_journal.cc index 550fb14e..50a52be9 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -151,8 +151,11 @@ namespace { collector_wrapper(journal_t& _journal, report_t& base) : journal(_journal), report(base), - posts_collector(new collect_posts) {} + posts_collector(new collect_posts) { + TRACE_CTOR(collector_wrapper, "journal_t&, report_t&"); + } ~collector_wrapper() { + TRACE_DTOR(collector_wrapper); journal.clear_xdata(); } diff --git a/src/report.cc b/src/report.cc index bff068b8..28836d0f 100644 --- a/src/report.cc +++ b/src/report.cc @@ -321,7 +321,12 @@ namespace { report_t& report; posts_flusher(post_handler_ptr _handler, report_t& _report) - : handler(_handler), report(_report) {} + : handler(_handler), report(_report) { + TRACE_CTOR(posts_flusher, "post_handler_ptr, report_t&"); + } + ~posts_flusher() throw() { + TRACE_DTOR(posts_flusher); + } void operator()(const value_t&) { report.session.journal->clear_xdata(); diff --git a/src/report.h b/src/report.h index aca4f466..37123377 100644 --- a/src/report.h +++ b/src/report.h @@ -119,9 +119,19 @@ public: explicit report_t(session_t& _session) : session(_session), terminus(CURRENT_TIME()), - budget_flags(BUDGET_NO_BUDGET) {} + budget_flags(BUDGET_NO_BUDGET) { + TRACE_CTOR(report_t, "session_t&"); + } + report_t(const report_t& report) + : session(report.session), + output_stream(report.output_stream), + terminus(report.terminus), + budget_flags(report.budget_flags) { + TRACE_CTOR(report_t, "copy"); + } virtual ~report_t() { + TRACE_DTOR(report_t); output_stream.close(); } @@ -1045,7 +1055,16 @@ class reporter public: reporter(shared_ptr > _handler, report_t& _report, const string& _whence) - : handler(_handler), report(_report), whence(_whence) {} + : handler(_handler), report(_report), whence(_whence) { + TRACE_CTOR(reporter, "item_handler, report_t&, string"); + } + reporter(const reporter& other) + : handler(other.handler), report(other.report), whence(other.whence) { + TRACE_CTOR(reporter, "copy"); + } + ~reporter() throw() { + TRACE_DTOR(reporter); + } value_t operator()(call_scope_t& args) { diff --git a/src/scope.h b/src/scope.h index 134babb2..9318fc5c 100644 --- a/src/scope.h +++ b/src/scope.h @@ -142,6 +142,13 @@ private: class empty_scope_t : public scope_t { public: + empty_scope_t() { + TRACE_CTOR(empty_scope_t, ""); + } + ~empty_scope_t() throw() { + TRACE_DTOR(empty_scope_t); + } + virtual string description() { return _(""); } @@ -683,7 +690,12 @@ class value_scope_t : public child_scope_t public: value_scope_t(scope_t& _parent, const value_t& _value) - : child_scope_t(_parent), value(_value) {} + : child_scope_t(_parent), value(_value) { + TRACE_CTOR(value_scope_t, "scope_t&, value_t"); + } + ~value_scope_t() throw() { + TRACE_DTOR(value_scope_t); + } virtual string description() { return parent->description(); diff --git a/src/temps.h b/src/temps.h index ad4e5672..f41c487c 100644 --- a/src/temps.h +++ b/src/temps.h @@ -51,7 +51,11 @@ class temporaries_t optional > acct_temps; public: + temporaries_t() { + TRACE_CTOR(temporaries_t, ""); + } ~temporaries_t() { + TRACE_DTOR(temporaries_t); clear(); } diff --git a/src/utils.cc b/src/utils.cc index 628fb158..5a364008 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -270,13 +270,79 @@ void operator delete[](void * ptr, const std::nothrow_t&) throw() { namespace ledger { -inline void report_count_map(std::ostream& out, object_count_map& the_map) -{ - foreach (object_count_map::value_type& pair, the_map) - out << " " << std::right << std::setw(12) << pair.second.first - << " " << std::right << std::setw(7) << pair.second.second - << " " << std::left << pair.first - << std::endl; +namespace { + void stream_commified_number(std::ostream& out, std::size_t num) + { + std::ostringstream buf; + std::ostringstream obuf; + + buf << num; + + int integer_digits = 0; + // Count the number of integer digits + for (const char * p = buf.str().c_str(); *p; p++) { + if (*p == '.') + break; + else if (*p != '-') + integer_digits++; + } + + for (const char * p = buf.str().c_str(); *p; p++) { + if (*p == '.') { + obuf << *p; + assert(integer_digits <= 3); + } + else if (*p == '-') { + obuf << *p; + } + else { + obuf << *p; + + if (integer_digits > 3 && --integer_digits % 3 == 0) + obuf << ','; + } + } + + out << obuf.str(); + } + + void stream_memory_size(std::ostream& out, std::size_t size) + { + std::ostringstream obuf; + + if (size > 10 * 1024 * 1024) + obuf << "\033[1m"; + if (size > 100 * 1024 * 1024) + obuf << "\033[31m"; + + obuf << std::setw(7); + + if (size < 1024) + obuf << size << 'b'; + else if (size < (1024 * 1024)) + obuf << int(double(size) / 1024.0) << 'K'; + else if (size < (1024 * 1024 * 1024)) + obuf << int(double(size) / (1024.0 * 1024.0)) << 'M'; + else + obuf << int(double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G'; + + if (size > 10 * 1024 * 1024) + obuf << "\033[0m"; + + out << obuf.str(); + } + + void report_count_map(std::ostream& out, object_count_map& the_map) + { + foreach (object_count_map::value_type& pair, the_map) { + out << " " << std::right << std::setw(12); + stream_commified_number(out, pair.second.first); + out << " " << std::right << std::setw(7); + stream_memory_size(out, pair.second.second); + out << " " << std::left << pair.first + << std::endl; + } + } } std::size_t current_objects_size() @@ -354,7 +420,7 @@ void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size) void report_memory(std::ostream& out, bool report_all) { - if (! live_memory || ! memory_tracing_active) return; + if (! live_memory) return; if (live_memory_count->size() > 0) { out << "NOTE: There may be memory held by Boost " @@ -366,11 +432,13 @@ void report_memory(std::ostream& out, bool report_all) if (live_memory->size() > 0) { out << "Live memory:" << std::endl; - foreach (const memory_map::value_type& pair, *live_memory) + foreach (const memory_map::value_type& pair, *live_memory) { out << " " << std::right << std::setw(12) << pair.first - << " " << std::right << std::setw(7) << pair.second.second - << " " << std::left << pair.second.first + << " " << std::right << std::setw(7); + stream_memory_size(out, pair.second.second); + out << " " << std::left << pair.second.first << std::endl; + } } if (report_all && total_memory_count->size() > 0) { @@ -386,11 +454,13 @@ void report_memory(std::ostream& out, bool report_all) if (live_objects->size() > 0) { out << "Live objects:" << std::endl; - foreach (const objects_map::value_type& pair, *live_objects) + foreach (const objects_map::value_type& pair, *live_objects) { out << " " << std::right << std::setw(12) << pair.first - << " " << std::right << std::setw(7) << pair.second.second - << " " << std::left << pair.second.first + << " " << std::right << std::setw(7); + stream_memory_size(out, pair.second.second); + out << " " << std::left << pair.second.first << std::endl; + } } if (report_all) { @@ -529,18 +599,6 @@ std::ostringstream _log_buffer; uint8_t _trace_level; #endif -static inline void stream_memory_size(std::ostream& out, std::size_t size) -{ - if (size < 1024) - out << size << 'b'; - else if (size < (1024 * 1024)) - out << (double(size) / 1024.0) << 'K'; - else if (size < (1024 * 1024 * 1024)) - out << (double(size) / (1024.0 * 1024.0)) << 'M'; - else - out << (double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G'; -} - static bool logger_has_run = false; static ptime logger_start; diff --git a/test/baseline/opt-verify-memory.test b/test/baseline/opt-verify-memory.test new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3