Refactored the notion of "the current parsing context"

This commit is contained in:
John Wiegley 2012-03-01 03:31:28 -06:00
parent e2afc783db
commit 944e580825
23 changed files with 597 additions and 457 deletions

View file

@ -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

151
src/context.h Normal file
View file

@ -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<std::istream> 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<std::istream> _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<std::istream> stream(new ifstream(filename));
parse_context_t context(stream, parent);
context.pathname = filename;
return context;
}
class parse_context_stack_t
{
std::list<parse_context_t> parsing_context;
public:
void push(shared_ptr<std::istream> 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

View file

@ -63,12 +63,16 @@ value_t convert_command(call_scope_t& args)
print_xacts formatter(report);
path csv_file_path(args.get<string>(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();

View file

@ -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;

View file

@ -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<string> 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;
}
};

View file

@ -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<std::istringstream> 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++;

View file

@ -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'")

View file

@ -32,6 +32,7 @@
#include <system.hh>
#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<int, xact_t *, post_t *> context,
const string& location)
variant<int, xact_t *, post_t *> 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<int, xact_t *, post_t *> context,
const string& location)
{
std::pair<tag_check_exprs_map::iterator,
tag_check_exprs_map::iterator> 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<scope_t&>(*boost::get<xact_t *>(context)) :
static_cast<scope_t&>(*boost::get<post_t *>(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<int, xact_t *, post_t *> context,
const string& location)
variant<int, xact_t *, post_t *> context)
{
if (checking_style == CHECK_WARNING || checking_style == CHECK_ERROR) {
std::set<string>::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<tag_check_exprs_map::iterator,
tag_check_exprs_map::iterator> 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<scope_t&>(*boost::get<xact_t *>(context)) :
static_cast<scope_t&>(*boost::get<post_t *>(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_t> 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 = &current;
if (! scope)
current.count = 0;
if (! current.scope)
current.scope = scope_t::default_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)

View file

@ -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<xact_t *> xacts_list;
typedef std::list<auto_xact_t *> 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<int, xact_t *, post_t *> context,
const string& location);
variant<int, xact_t *, post_t *> context);
void register_metadata(const string& key, const value_t& value,
variant<int, xact_t *, post_t *> context,
const string& location);
variant<int, xact_t *, post_t *> 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. */

View file

@ -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<std::istringstream> 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();
}
}

View file

@ -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;
}

View file

@ -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<path>())
.def(init<string>())
#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<return_internal_reference<> >
(&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)

View file

@ -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<value_t> 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);

View file

@ -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);
}
};

View file

@ -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<std::istream> 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

View file

@ -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_t> journal;
parse_context_stack_t parsing_context;
explicit session_t();
virtual ~session_t() {

View file

@ -32,6 +32,7 @@
#include <system.hh>
#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<account_t *, string, fixed_rate_t> value;
variant<optional<datetime_t>, account_t *, string, fixed_rate_t> value;
application_t(string _label, optional<datetime_t> 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:
parse_context_stack_t& context_stack;
parse_context_t& context;
std::istream& in;
instance_t * parent;
std::list<application_t> apply_stack;
journal_t& journal;
scope_t& scope;
#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<datetime_t> 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<auto_xact_t> 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<period_xact_t> 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<xact_t> 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<std::pair<commodity_t *, price_point_t> > 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<unsigned short>(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<unsigned short>(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);
context.apply_stack.pop_front();
if (apply_stack.front().value.type() == typeid(optional<datetime_t>))
epoch = boost::get<optional<datetime_t> >(apply_stack.front().value);
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<auto_xact_t> 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<accounts_map::iterator, bool> 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<string::size_type>(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<fixed_rate_t>(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<std::streamsize>(++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<string>(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_t> 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 = _("<Unspecified 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<string>(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<int>(context.errors);
if (context_stack.get_current().errors > 0)
throw static_cast<int>(context_stack.get_current().errors);
return context.count;
return context_stack.get_current().count;
}
} // namespace ledger

View file

@ -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_xact_t>& 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

View file

@ -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_xact_t> 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);

View file

@ -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));
}
}
}

View file

@ -49,6 +49,7 @@ namespace ledger {
class post_t;
class journal_t;
class parse_context_t;
typedef std::list<post_t *> 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:

View file

@ -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

View file

@ -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