summaryrefslogtreecommitdiff
path: root/src/textual.cc
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2012-04-26 16:39:25 -0500
committerJohn Wiegley <johnw@newartisans.com>2012-04-26 16:39:25 -0500
commit64a9b42381c26baf24e58b40f50f0b253e551811 (patch)
tree5447a29dff64c3a8b7be8100a01bcb4a2d73b0bb /src/textual.cc
parent7cc550fc22357e2ded194d3e65287c6b3317f5ae (diff)
parentb4407c10c0071365322b2963747bf42a57fd7304 (diff)
downloadfork-ledger-64a9b42381c26baf24e58b40f50f0b253e551811.tar.gz
fork-ledger-64a9b42381c26baf24e58b40f50f0b253e551811.tar.bz2
fork-ledger-64a9b42381c26baf24e58b40f50f0b253e551811.zip
Merge branch 'release/v3.0.0-20120426'
Diffstat (limited to 'src/textual.cc')
-rw-r--r--src/textual.cc1171
1 files changed, 726 insertions, 445 deletions
diff --git a/src/textual.cc b/src/textual.cc
index c7c49e2a..d0e4dad2 100644
--- a/src/textual.cc
+++ b/src/textual.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
@@ -32,6 +32,7 @@
#include <system.hh>
#include "journal.h"
+#include "context.h"
#include "xact.h"
#include "post.h"
#include "account.h"
@@ -39,7 +40,9 @@
#include "query.h"
#include "pstream.h"
#include "pool.h"
-#include "session.h"
+#if defined(HAVE_BOOST_PYTHON)
+#include "pyinterp.h"
+#endif
#define TIMELOG_SUPPORT 1
#if defined(TIMELOG_SUPPORT)
@@ -49,123 +52,150 @@
namespace ledger {
namespace {
- typedef std::pair<commodity_t *, amount_t> fixed_rate_t;
- typedef variant<account_t *, string, fixed_rate_t> state_t;
+ typedef std::pair<commodity_t *, amount_t> fixed_rate_t;
- class parse_context_t : public noncopyable
+ struct application_t
+ {
+ string label;
+ 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)
+ : label(_label), value(tag) {}
+ application_t(string _label, fixed_rate_t rate)
+ : label(_label), value(rate) {}
+ };
+
+ class instance_t : public noncopyable, public scope_t
{
public:
- journal_t& journal;
- scope_t& scope;
- std::list<state_t> state_stack;
+ parse_context_stack_t& context_stack;
+ parse_context_t& context;
+ std::istream& in;
+ instance_t * parent;
+ std::list<application_t> apply_stack;
#if defined(TIMELOG_SUPPORT)
- time_log_t timelog;
+ time_log_t timelog;
#endif
- bool strict;
- 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),
- strict(false), count(0), errors(0), sequence(1) {
- timelog.context_count = &count;
- }
- bool front_is_account() {
- return state_stack.front().type() == typeid(account_t *);
- }
- bool front_is_string() {
- return state_stack.front().type() == typeid(string);
- }
- bool front_is_fixed_rate() {
- return state_stack.front().type() == typeid(fixed_rate_t);
- }
+ 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) {}
- account_t * top_account() {
- foreach (state_t& state, state_stack)
- if (state.type() == typeid(account_t *))
- return boost::get<account_t *>(state);
- return NULL;
+ virtual string description() {
+ return _("textual parser");
}
- void close() {
- timelog.close();
+ template <typename T>
+ void get_applications(std::vector<T>& result) {
+ foreach (application_t& state, apply_stack) {
+ if (state.value.type() == typeid(T))
+ result.push_back(boost::get<T>(state.value));
+ }
+ if (parent)
+ parent->get_applications<T>(result);
}
- };
- class instance_t : public noncopyable, public scope_t
- {
- static const std::size_t MAX_LINE = 1024;
-
- public:
- parse_context_t& context;
- instance_t * parent;
- accounts_map account_aliases;
- 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();
+ template <typename T>
+ optional<T> get_application() {
+ foreach (application_t& state, apply_stack) {
+ if (state.value.type() == typeid(T))
+ return boost::get<T>(state.value);
+ }
+ return parent ? parent->get_application<T>() : none;
+ }
- virtual string description() {
- return _("textual parser");
+ account_t * top_account() {
+ if (optional<account_t *> acct = get_application<account_t *>())
+ return *acct;
+ else
+ return NULL;
}
void parse();
+
std::streamsize read_line(char *& line);
+
bool peek_whitespace_line() {
return (in.good() && ! in.eof() &&
(in.peek() == ' ' || in.peek() == '\t'));
}
+#if defined(HAVE_BOOST_PYTHON)
+ bool peek_blank_line() {
+ return (in.good() && ! in.eof() &&
+ (in.peek() == '\n' || in.peek() == '\r'));
+ }
+#endif
- void read_next_directive();
+ void read_next_directive(bool& error_flag);
#if defined(TIMELOG_SUPPORT)
void clock_in_directive(char * line, bool capitalized);
void clock_out_directive(char * line, bool capitalized);
#endif
- void default_commodity_directive(char * line);
+ bool general_directive(char * line);
+
+ void account_directive(char * line);
+ void account_alias_directive(account_t * account, string alias);
+ void account_payee_directive(account_t * account, string payee);
+ void account_value_directive(account_t * account, string expr_str);
+ void account_default_directive(account_t * account);
+
void default_account_directive(char * line);
- void price_conversion_directive(char * line);
+ void alias_directive(char * line);
+
+ void payee_directive(char * line);
+ void payee_alias_directive(const string& payee, string alias);
+
+ void commodity_directive(char * line);
+ void commodity_alias_directive(commodity_t& comm, string alias);
+ void commodity_value_directive(commodity_t& comm, string expr_str);
+ void commodity_format_directive(commodity_t& comm, string format);
+ void commodity_nomarket_directive(commodity_t& comm);
+ void commodity_default_directive(commodity_t& comm);
+
+ void default_commodity_directive(char * line);
+
+ void tag_directive(char * line);
+
+ void apply_directive(char * line);
+ void apply_account_directive(char * line);
+ void apply_tag_directive(char * line);
+ void apply_rate_directive(char * line);
+ void apply_year_directive(char * line);
+ void end_apply_directive(char * line);
+
+ void xact_directive(char * line, std::streamsize len);
+ void period_xact_directive(char * line);
+ void automated_xact_directive(char * line);
void price_xact_directive(char * line);
+ void price_conversion_directive(char * line);
void nomarket_directive(char * line);
- void year_directive(char * line);
- void option_directive(char * line);
- void automated_xact_directive(char * line);
- void period_xact_directive(char * line);
- void xact_directive(char * line, std::streamsize len);
+
void include_directive(char * line);
- void master_account_directive(char * line);
- void end_directive(char * line);
- void alias_directive(char * line);
- void fixed_directive(char * line);
- void payee_mapping_directive(char * line);
- void account_mapping_directive(char * line);
- void tag_directive(char * line);
- void define_directive(char * line);
+ void option_directive(char * line);
+ void comment_directive(char * line);
+
+ void eval_directive(char * line);
void assert_directive(char * line);
void check_directive(char * line);
- void comment_directive(char * line);
- void expr_directive(char * line);
- bool general_directive(char * line);
+ void value_directive(char * line);
+
+ void import_directive(char * line);
+ void python_directive(char * line);
post_t * parse_post(char * line,
std::streamsize len,
account_t * account,
xact_t * xact,
- bool defer_expr = false);
+ bool defer_expr = false);
bool parse_posts(account_t * account,
xact_base_t& xact,
@@ -209,43 +239,27 @@ 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();
+
+ bool error_flag = false;
while (in.good() && ! in.eof()) {
try {
- read_next_directive();
+ read_next_directive(error_flag);
}
catch (const std::exception& err) {
+ error_flag = true;
+
string current_context = error_context();
if (parent) {
@@ -258,11 +272,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;
@@ -287,48 +299,55 @@ 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];
- else
- line = linebuf;
+ context.linenum++;
- if (line[len - 1] == '\r') // strip Windows CRLF down to LF
- line[--len] = '\0';
+ context.curr_pos = context.line_beg_pos;
+ context.curr_pos += len;
- linenum++;
+ if (context.linenum == 0 && utf8::is_bom(context.linebuf)) {
+ line = &context.linebuf[3];
+ len -= 3;
+ } else {
+ line = context.linebuf;
+ }
- curr_pos = line_beg_pos;
- curr_pos += len;
+ --len;
+ while (len > 0 && std::isspace(line[len - 1])) // strip trailing whitespace
+ line[--len] = '\0';
- return len - 1; // LF is being silently dropped
+ return len;
}
return 0;
}
-void instance_t::read_next_directive()
+void instance_t::read_next_directive(bool& error_flag)
{
char * line;
std::streamsize len = read_line(line);
if (len == 0 || line == NULL)
return;
+ if (! std::isspace(line[0]))
+ error_flag = false;
+
switch (line[0]) {
case '\0':
assert(false); // shouldn't ever reach here
break;
case ' ':
- case '\t': {
+ case '\t':
+ if (! error_flag)
+ throw parse_error(_("Unexpected whitespace at beginning of line"));
break;
- }
case ';': // comments
case '#':
@@ -402,7 +421,7 @@ void instance_t::read_next_directive()
price_xact_directive(line);
break;
case 'Y': // set the current year
- year_directive(line);
+ apply_year_directive(line);
break;
}
}
@@ -426,19 +445,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*/)
@@ -455,20 +474,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);
- context.count++;
+ context.count += timelog.clock_out(event);
}
#endif // TIMELOG_SUPPORT
@@ -483,8 +501,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)
@@ -514,16 +532,6 @@ void instance_t::nomarket_directive(char * line)
commodity->add_flags(COMMODITY_NOMARKET | COMMODITY_KNOWN);
}
-void instance_t::year_directive(char * line)
-{
- unsigned short year(lexical_cast<unsigned short>(skip_ws(line + 1)));
- DEBUG("times.epoch", "Setting current year to " << year);
- // 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.
- epoch = datetime_t(date_t(year, 12, 31));
-}
-
void instance_t::option_directive(char * line)
{
char * p = next_element(line);
@@ -533,13 +541,15 @@ void instance_t::option_directive(char * line)
*p++ = '\0';
}
- if (! process_option(pathname.string(), line + 2, context.scope, p, line))
+ path abs_path(filesystem::absolute(context.pathname,
+ context.current_directory));
+ if (! process_option(abs_path.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;
@@ -550,18 +560,17 @@ void instance_t::automated_xact_directive(char * line)
query.parse_args(string_value(skip_ws(line + 1)).to_sequence(),
keeper, false, true);
- std::auto_ptr<auto_xact_t> ae(new auto_xact_t(predicate_t(expr, keeper)));
+ unique_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;
while (peek_whitespace_line()) {
std::streamsize len = read_line(line);
-
char * p = skip_ws(line);
if (! *p)
break;
@@ -576,59 +585,56 @@ 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->pos->end_pos = curr_pos;
+ item->append_note(p + 1, *context.scope, true);
+ item->add_flags(ITEM_NOTE_ON_NEXT_LINE);
+ item->pos->end_pos = context.curr_pos;
item->pos->end_line++;
-
- // If there was no last_post yet, then deferred notes get applied to
- // the matched posting. Other notes get applied to the auto-generated
- // posting.
- ae->deferred_notes->back().apply_to_post = last_post;
}
else if ((remlen > 7 && *p == 'a' &&
std::strncmp(p, "assert", 6) == 0 && std::isspace(p[6])) ||
(remlen > 6 && *p == 'c' &&
std::strncmp(p, "check", 5) == 0 && std::isspace(p[5])) ||
(remlen > 5 && *p == 'e' &&
- std::strncmp(p, "expr", 4) == 0 && std::isspace(p[4]))) {
+ ((std::strncmp(p, "expr", 4) == 0 && std::isspace(p[4])) ||
+ (std::strncmp(p, "eval", 4) == 0 && std::isspace(p[4]))))) {
const char c = *p;
p = skip_ws(&p[*p == 'a' ? 6 : (*p == 'c' ? 5 : 4)]);
if (! ae->check_exprs)
- ae->check_exprs = auto_xact_t::check_expr_list();
+ ae->check_exprs = expr_t::check_expr_list();
ae->check_exprs->push_back
- (auto_xact_t::check_expr_pair(expr_t(p),
- c == 'a' ?
- auto_xact_t::EXPR_ASSERTION :
- (c == 'c' ?
- auto_xact_t::EXPR_CHECK :
- auto_xact_t::EXPR_GENERAL)));
+ (expr_t::check_expr_pair(expr_t(p),
+ c == 'a' ?
+ expr_t::EXPR_ASSERTION :
+ (c == 'c' ?
+ expr_t::EXPR_CHECK :
+ expr_t::EXPR_GENERAL)));
}
else {
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;
+ ae->active_post = last_post = post;
}
reveal_context = true;
}
}
- 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;
}
@@ -636,31 +642,31 @@ 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;
try {
- std::auto_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1)));
+ unique_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 {
@@ -674,7 +680,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;
}
@@ -684,10 +691,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())) {
- std::auto_ptr<xact_t> manager(xact);
+ if (xact_t * xact = parse_xact(line, len, top_account())) {
+ unique_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++;
}
@@ -709,12 +716,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;
@@ -761,10 +769,42 @@ 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 = top_account();
+ scope_t * scope = context.scope;
+ std::size_t& errors = context.errors;
+ std::size_t& count = context.count;
+ std::size_t& sequence = context.sequence;
+
+ DEBUG("textual.include", "Including: " << *iter);
+ DEBUG("textual.include", "Master account: " << master->fullname());
+
+ context_stack.push(*iter);
+
+ context_stack.get_current().journal = journal;
+ context_stack.get_current().master = master;
+ context_stack.get_current().scope = scope;
+ try {
+ instance_t instance(context_stack,
+ context_stack.get_current(), this);
+ instance.apply_stack.push_front(application_t("account", master));
+ instance.parse();
+ }
+ catch (...) {
+ errors += context_stack.get_current().errors;
+ count += context_stack.get_current().count;
+ sequence += context_stack.get_current().sequence;
+
+ context_stack.pop();
+ throw;
+ }
+
+ errors += context_stack.get_current().errors;
+ count += context_stack.get_current().count;
+ sequence += context_stack.get_current().sequence;
+
+ context_stack.pop();
+
files_found = true;
}
}
@@ -773,156 +813,348 @@ void instance_t::include_directive(char * line)
if (! files_found)
throw_(std::runtime_error,
- _("File to include was not found: '%1'") << filename);
+ _("File to include was not found: %1") << filename);
}
-void instance_t::master_account_directive(char * line)
+void instance_t::apply_directive(char * line)
+{
+ char * b = next_element(line);
+ string keyword(line);
+ if (keyword == "account")
+ apply_account_directive(b);
+ else if (keyword == "tag")
+ apply_tag_directive(b);
+ else if (keyword == "fixed" || keyword == "rate")
+ apply_rate_directive(b);
+ else if (keyword == "year")
+ apply_year_directive(b);
+}
+
+void instance_t::apply_account_directive(char * line)
{
- if (account_t * acct = context.top_account()->find_account(line))
- context.state_stack.push_front(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);
#endif
}
-void instance_t::end_directive(char * kind)
+void instance_t::apply_tag_directive(char * line)
{
- string name(kind ? kind : "");
+ string tag(trim_ws(line));
- if ((name.empty() || name == "account") && ! context.front_is_account())
- throw_(std::runtime_error,
- _("'end account' directive does not match open directive"));
- else if (name == "tag" && ! context.front_is_string())
- throw_(std::runtime_error,
- _("'end tag' directive does not match open directive"));
- else if (name == "fixed" && ! context.front_is_fixed_rate())
- throw_(std::runtime_error,
- _("'end fixed' directive does not match open directive"));
+ if (tag.find(':') == string::npos)
+ tag = string(":") + tag + ":";
- if (context.state_stack.size() <= 1)
+ 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)) {
+ apply_stack.push_front
+ (application_t("fixed", fixed_rate_t(price_point->first,
+ price_point->second.price)));
+ } else {
+ throw_(std::runtime_error, _("Error in fixed directive"));
+ }
+}
+
+void instance_t::apply_year_directive(char * line)
+{
+ 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));
+}
+
+void instance_t::end_apply_directive(char * kind)
+{
+ char * b = kind ? next_element(kind) : NULL;
+ string name(b ? b : "");
+
+ if (apply_stack.size() <= 1) {
+ if (name.empty()) {
+ throw_(std::runtime_error,
+ _("'end' or 'end apply' found, but no enclosing 'apply' directive"));
+ } else {
+ throw_(std::runtime_error,
+ _("'end apply %1' found, but no enclosing 'apply' directive")
+ << name);
+ }
+ }
+
+ if (! name.empty() && name != apply_stack.front().label)
throw_(std::runtime_error,
- _("'end' found, but no enclosing tag or account directive"));
- else
- context.state_stack.pop_front();
+ _("'end apply %1' directive does not match 'apply %2' directive")
+ << name << apply_stack.front().label);
+
+ 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 = 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, top_account());
+ unique_ptr<auto_xact_t> ae;
+
+ while (peek_whitespace_line()) {
+ read_line(line);
+ char * q = skip_ws(line);
+ if (! *q)
+ break;
+
+ char * b = next_element(q);
+ string keyword(q);
+ if (keyword == "alias") {
+ account_alias_directive(account, b);
+ }
+ else if (keyword == "payee") {
+ account_payee_directive(account, b);
+ }
+ else if (keyword == "value") {
+ account_value_directive(account, b);
+ }
+ else if (keyword == "default") {
+ account_default_directive(account);
+ }
+ else if (keyword == "assert" || keyword == "check") {
+ keep_details_t keeper(true, true, true);
+ expr_t expr(string("account == \"") + account->fullname() + "\"");
+ predicate_t pred(expr.get_op(), keeper);
+
+ if (! ae.get()) {
+ ae.reset(new auto_xact_t(pred));
+
+ ae->pos = position_t();
+ ae->pos->pathname = context.pathname;
+ ae->pos->beg_pos = beg_pos;
+ ae->pos->beg_line = beg_linenum;
+ ae->pos->sequence = context.sequence++;
+ ae->check_exprs = expr_t::check_expr_list();
+ }
+
+ ae->check_exprs->push_back
+ (expr_t::check_expr_pair(expr_t(b),
+ keyword == "assert" ?
+ expr_t::EXPR_ASSERTION :
+ expr_t::EXPR_CHECK));
+ }
+ 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);
+ expr_t(b).calc(bound_scope);
+ }
+ else if (keyword == "note") {
+ account->note = b;
+ }
+ }
+
+ if (ae.get()) {
+ context.journal->auto_xacts.push_back(ae.get());
+
+ ae->journal = context.journal;
+ ae->pos->end_pos = in.tellg();
+ ae->pos->end_line = context.linenum;
+
+ ae.release();
+ }
+}
+
+void instance_t::account_alias_directive(account_t * account, string alias)
+{
+ // Once we have an alias name (alias) and the target account
+ // (account), add a reference to the account in the `account_aliases'
+ // map, which is used by the post parser to resolve alias references.
+ trim(alias);
+ std::pair<accounts_map::iterator, bool> result =
+ context.journal->account_aliases.insert
+ (accounts_map::value_type(alias, account));
+ if (! result.second)
+ (*result.first).second = account;
}
void instance_t::alias_directive(char * line)
{
- char * b = skip_ws(line);
- if (char * e = std::strchr(b, '=')) {
+ if (char * e = std::strchr(line, '=')) {
char * z = e - 1;
while (std::isspace(*z))
*z-- = '\0';
*e++ = '\0';
e = skip_ws(e);
- // Once we have an alias name (b) and the target account
- // name (e), add a reference to the account in the
- // `account_aliases' map, which is used by the post
- // parser to resolve alias references.
- account_t * acct = context.top_account()->find_account(e);
- std::pair<accounts_map::iterator, bool> result
- = account_aliases.insert(accounts_map::value_type(b, acct));
- assert(result.second);
+ account_alias_directive(top_account()->find_account(e), line);
}
}
-void instance_t::fixed_directive(char * line)
+void instance_t::account_payee_directive(account_t * account, string payee)
{
- if (optional<std::pair<commodity_t *, price_point_t> > price_point =
- commodity_pool_t::current_pool->parse_price_directive(trim_ws(line),
- true)) {
- context.state_stack.push_front(fixed_rate_t(price_point->first,
- price_point->second.price));
- } else {
- throw_(std::runtime_error, _("Error in fixed directive"));
- }
+ trim(payee);
+ context.journal->payees_for_unknown_accounts
+ .push_back(account_mapping_t(mask_t(payee), account));
}
-void instance_t::payee_mapping_directive(char * line)
+void instance_t::account_default_directive(account_t * account)
{
- char * payee = skip_ws(line);
- char * regex = next_element(payee, true);
+ context.journal->bucket = account;
+}
- if (regex)
- context.journal.payee_mappings.push_back
- (payee_mapping_t(mask_t(regex), payee));
+void instance_t::account_value_directive(account_t * account, string expr_str)
+{
+ account->value_expr = expr_t(expr_str);
+}
+
+void instance_t::payee_directive(char * line)
+{
+ string payee = context.journal->register_payee(line, NULL);
while (peek_whitespace_line()) {
-#if defined(NO_ASSERTS)
read_line(line);
-#else
- std::streamsize len = read_line(line);
- assert(len > 0);
-#endif
-
- regex = skip_ws(line);
- if (! *regex)
+ char * p = skip_ws(line);
+ if (! *p)
break;
- context.journal.payee_mappings.push_back
- (payee_mapping_t(mask_t(regex), payee));
+ char * b = next_element(p);
+ string keyword(p);
+ if (keyword == "alias")
+ payee_alias_directive(payee, b);
}
}
-void instance_t::account_mapping_directive(char * line)
+void instance_t::payee_alias_directive(const string& payee, string alias)
{
- char * account_name = skip_ws(line);
- char * payee_regex = next_element(account_name, true);
+ trim(alias);
+ context.journal->payee_mappings
+ .push_back(payee_mapping_t(mask_t(alias), payee));
+}
- if (payee_regex)
- context.journal.account_mappings.push_back
- (account_mapping_t(mask_t(payee_regex),
- context.top_account()->find_account(account_name)));
+void instance_t::commodity_directive(char * line)
+{
+ char * p = skip_ws(line);
+ string symbol;
+ commodity_t::parse_symbol(p, symbol);
- while (peek_whitespace_line()) {
-#if defined(NO_ASSERTS)
- read_line(line);
-#else
- std::streamsize len = read_line(line);
- assert(len > 0);
-#endif
+ if (commodity_t * commodity =
+ commodity_pool_t::current_pool->find_or_create(symbol)) {
+ context.journal->register_commodity(*commodity, 0);
- payee_regex = skip_ws(line);
- if (! *payee_regex)
- break;
+ while (peek_whitespace_line()) {
+ read_line(line);
+ char * q = skip_ws(line);
+ if (! *q)
+ break;
- context.journal.account_mappings.push_back
- (account_mapping_t(mask_t(payee_regex),
- context.top_account()->find_account(account_name)));
+ char * b = next_element(q);
+ string keyword(q);
+ if (keyword == "alias")
+ commodity_alias_directive(*commodity, b);
+ else if (keyword == "value")
+ commodity_value_directive(*commodity, b);
+ else if (keyword == "format")
+ commodity_format_directive(*commodity, b);
+ else if (keyword == "nomarket")
+ commodity_nomarket_directive(*commodity);
+ else if (keyword == "default")
+ commodity_default_directive(*commodity);
+ else if (keyword == "note")
+ commodity->set_note(string(b));
+ }
}
}
+void instance_t::commodity_alias_directive(commodity_t& comm, string alias)
+{
+ trim(alias);
+ commodity_pool_t::current_pool->alias(alias, comm);
+}
+
+void instance_t::commodity_value_directive(commodity_t& comm, string expr_str)
+{
+ comm.set_value_expr(expr_t(expr_str));
+}
+
+void instance_t::commodity_format_directive(commodity_t&, string format)
+{
+ // jww (2012-02-27): A format specified this way should turn off
+ // observational formatting.
+ trim(format);
+ amount_t amt;
+ amt.parse(format);
+ VERIFY(amt.valid());
+}
+
+void instance_t::commodity_nomarket_directive(commodity_t& comm)
+{
+ comm.add_flags(COMMODITY_NOMARKET);
+}
+
+void instance_t::commodity_default_directive(commodity_t& comm)
+{
+ commodity_pool_t::current_pool->default_commodity = &comm;
+}
+
void instance_t::tag_directive(char * line)
{
- string tag(trim_ws(line));
+ char * p = skip_ws(line);
+ context.journal->register_metadata(p, NULL_VALUE, 0);
- if (tag.find(':') == string::npos)
- tag = string(":") + tag + ":";
+ while (peek_whitespace_line()) {
+ read_line(line);
+ char * q = skip_ws(line);
+ if (! *q)
+ break;
- context.state_stack.push_front(tag);
+ char * b = next_element(q);
+ string keyword(q);
+ if (keyword == "assert" || keyword == "check") {
+ context.journal->tag_check_exprs.insert
+ (tag_check_exprs_map::value_type
+ (string(p), expr_t::check_expr_pair(expr_t(b),
+ keyword == "assert" ?
+ expr_t::EXPR_ASSERTION :
+ expr_t::EXPR_CHECK)));
+ }
+ }
}
-void instance_t::define_directive(char * line)
+void instance_t::eval_directive(char * line)
{
- expr_t def(skip_ws(line));
- def.compile(context.scope); // causes definitions to be established
+ expr_t expr(line);
+ 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_(_("Check failed: %1") << line);
+ if (! expr.calc(*context.scope).to_boolean())
+ context.warning(STR(_("Check failed: %1") << line));
+}
+
+void instance_t::value_directive(char * line)
+{
+ context.journal->value_expr = expr_t(line);
}
void instance_t::comment_directive(char * line)
@@ -936,12 +1168,71 @@ void instance_t::comment_directive(char * line)
}
}
-void instance_t::expr_directive(char * line)
+#if defined(HAVE_BOOST_PYTHON)
+
+void instance_t::import_directive(char * line)
{
- expr_t expr(line);
- expr.calc(context.scope);
+ string module_name(line);
+ trim(module_name);
+ python_session->import_option(module_name);
+}
+
+void instance_t::python_directive(char * line)
+{
+ std::ostringstream script;
+
+ if (line)
+ script << skip_ws(line) << '\n';
+
+ std::size_t indent = 0;
+
+ while (peek_whitespace_line() || peek_blank_line()) {
+ if (read_line(line) > 0) {
+ if (! indent) {
+ const char * p = line;
+ while (*p && std::isspace(*p)) {
+ ++indent;
+ ++p;
+ }
+ }
+
+ const char * p = line;
+ for (std::size_t i = 0; i < indent; i++) {
+ if (std::isspace(*p))
+ ++p;
+ else
+ break;
+ }
+
+ if (*p)
+ script << p << '\n';
+ }
+ }
+
+ if (! python_session->is_initialized)
+ python_session->initialize();
+
+ python_session->main_module->define_global
+ ("journal", python::object(python::ptr(context.journal)));
+ python_session->eval(script.str(), python_interpreter_t::PY_EVAL_MULTI);
+}
+
+#else
+
+void instance_t::import_directive(char *)
+{
+ throw_(parse_error,
+ _("'python' directive seen, but Python support is missing"));
+}
+
+void instance_t::python_directive(char *)
+{
+ throw_(parse_error,
+ _("'import' directive seen, but Python support is missing"));
}
+#endif // HAVE_BOOST_PYTHON
+
bool instance_t::general_directive(char * line)
{
char buf[8192];
@@ -957,13 +1248,17 @@ bool instance_t::general_directive(char * line)
switch (*p) {
case 'a':
if (std::strcmp(p, "account") == 0) {
- master_account_directive(arg);
+ account_directive(arg);
return true;
}
else if (std::strcmp(p, "alias") == 0) {
alias_directive(arg);
return true;
}
+ else if (std::strcmp(p, "apply") == 0) {
+ apply_directive(arg);
+ return true;
+ }
else if (std::strcmp(p, "assert") == 0) {
assert_directive(arg);
return true;
@@ -978,11 +1273,7 @@ bool instance_t::general_directive(char * line)
break;
case 'c':
- if (std::strcmp(p, "capture") == 0) {
- account_mapping_directive(arg);
- return true;
- }
- else if (std::strcmp(p, "check") == 0) {
+ if (std::strcmp(p, "check") == 0) {
check_directive(arg);
return true;
}
@@ -990,29 +1281,26 @@ bool instance_t::general_directive(char * line)
comment_directive(arg);
return true;
}
+ else if (std::strcmp(p, "commodity") == 0) {
+ commodity_directive(arg);
+ return true;
+ }
break;
case 'd':
if (std::strcmp(p, "def") == 0 || std::strcmp(p, "define") == 0) {
- define_directive(arg);
+ eval_directive(arg);
return true;
}
break;
case 'e':
if (std::strcmp(p, "end") == 0) {
- end_directive(arg);
+ end_apply_directive(arg);
return true;
}
- else if (std::strcmp(p, "expr") == 0) {
- expr_directive(arg);
- return true;
- }
- break;
-
- case 'f':
- if (std::strcmp(p, "fixed") == 0) {
- fixed_directive(arg);
+ else if (std::strcmp(p, "expr") == 0 || std::strcmp(p, "eval") == 0) {
+ eval_directive(arg);
return true;
}
break;
@@ -1022,11 +1310,19 @@ bool instance_t::general_directive(char * line)
include_directive(arg);
return true;
}
+ else if (std::strcmp(p, "import") == 0) {
+ import_directive(arg);
+ return true;
+ }
break;
case 'p':
if (std::strcmp(p, "payee") == 0) {
- payee_mapping_directive(arg);
+ payee_directive(arg);
+ return true;
+ }
+ else if (std::strcmp(p, "python") == 0) {
+ python_directive(arg);
return true;
}
break;
@@ -1042,9 +1338,9 @@ bool instance_t::general_directive(char * line)
}
break;
- case 'y':
- if (std::strcmp(p, "year") == 0) {
- year_directive(arg);
+ case 'v':
+ if (std::strcmp(p, "value") == 0) {
+ value_directive(arg);
return true;
}
break;
@@ -1068,16 +1364,16 @@ post_t * instance_t::parse_post(char * line,
{
TRACE_START(post_details, 1, "Time spent parsing postings:");
- std::auto_ptr<post_t> post(new post_t);
+ unique_ptr<post_t> post(new post_t);
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;
@@ -1094,14 +1390,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;
}
@@ -1124,44 +1420,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<std::string::size_type>(e - p));
- DEBUG("textual.parse", "line " << linenum << ": "
+ string name(p, static_cast<string::size_type>(e - p));
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Parsed account name " << name);
- if (account_aliases.size() > 0) {
- accounts_map::const_iterator i = account_aliases.find(name);
- if (i != account_aliases.end())
- post->account = (*i).second;
- }
- if (! post->account)
- post->account = account->find_account(name);
-
- if (context.strict && ! post->account->has_flags(ACCOUNT_KNOWN)) {
- if (post->_state == item_t::UNCLEARED)
- warning_(_("\"%1\", line %2: Unknown account '%3'")
- << pathname.string() << linenum << post->account->fullname());
- post->account->add_flags(ACCOUNT_KNOWN);
- }
-
- if (post->account->name == _("Unknown")) {
- foreach (account_mapping_t& value, context.journal.account_mappings) {
- if (value.first.match(xact->payee)) {
- post->account = value.second;
- break;
- }
- }
- }
+ post->account =
+ context.journal->register_account(name, post.get(), account);
// Parse the optional amount
@@ -1172,35 +1447,28 @@ 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()) {
- if (context.strict &&
- ! post->amount.commodity().has_flags(COMMODITY_KNOWN)) {
- if (post->_state == item_t::UNCLEARED)
- warning_(_("\"%1\", line %2: Unknown commodity '%3'")
- << pathname.string() << linenum << post->amount.commodity());
- post->amount.commodity().add_flags(COMMODITY_KNOWN);
- }
+ context.journal->register_commodity(post->amount.commodity(), post.get());
if (! post->amount.has_annotation()) {
- foreach (state_t& state, context.state_stack) {
- if (state.type() == typeid(fixed_rate_t)) {
- fixed_rate_t& rate(boost::get<fixed_rate_t>(state));
- if (*rate.first == post->amount.commodity()) {
- annotation_t details(rate.second);
- details.add_flags(ANNOTATION_PRICE_FIXATED);
- post->amount.annotate(details);
- break;
- }
+ std::vector<fixed_rate_t> rates;
+ get_applications<fixed_rate_t>(rates);
+ foreach (fixed_rate_t& rate, rates) {
+ if (*rate.first == post->amount.commodity()) {
+ annotation_t details(rate.second);
+ details.add_flags(ANNOTATION_PRICE_FIXATED);
+ post->amount.annotate(details);
+ break;
}
}
}
}
- DEBUG("textual.parse", "line " << linenum << ": "
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "post amount = " << post->amount);
if (stream.eof()) {
@@ -1210,19 +1478,26 @@ post_t * instance_t::parse_post(char * line,
// Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST)
- if (*next == '@') {
- DEBUG("textual.parse", "line " << linenum << ": "
+ if (*next == '@' || (*next == '(' && *(next + 1) == '@')) {
+ DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Found a price indicator");
- bool per_unit = true;
+ if (*next == '(') {
+ post->add_flags(POST_COST_VIRTUAL);
+ ++next;
+ }
+ bool per_unit = true;
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");
}
+ if (post->has_flags(POST_COST_VIRTUAL) && *(next + 1) == ')')
+ ++next;
+
beg = static_cast<std::streamsize>(++next - line);
p = skip_ws(next);
@@ -1243,7 +1518,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)
@@ -1266,9 +1541,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())
@@ -1285,7 +1560,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);
@@ -1300,7 +1575,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);
@@ -1311,17 +1586,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;
@@ -1341,9 +1616,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()) {
@@ -1352,7 +1627,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");
}
}
@@ -1369,9 +1644,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");
}
@@ -1382,14 +1657,13 @@ 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.state_stack.empty()) {
- foreach (const state_t& state, context.state_stack)
- if (state.type() == typeid(string))
- post->parse_tags(boost::get<string>(state).c_str(), context.scope, true);
- }
+ std::vector<string> tags;
+ get_applications<string>(tags);
+ foreach (string& tag, tags)
+ post->parse_tags(tag.c_str(), *context.scope, true);
TRACE_STOP(post_details, 1);
@@ -1398,8 +1672,8 @@ post_t * instance_t::parse_post(char * line,
}
catch (const std::exception&) {
add_error_context(_("While parsing posting:"));
- add_error_context(line_context(buf, static_cast<std::string::size_type>(beg),
- static_cast<std::string::size_type>(len)));
+ add_error_context(line_context(buf, static_cast<string::size_type>(beg),
+ static_cast<string::size_type>(len)));
throw;
}
}
@@ -1415,11 +1689,12 @@ bool instance_t::parse_posts(account_t * account,
while (peek_whitespace_line()) {
char * line;
std::streamsize len = read_line(line);
- assert(len > 0);
-
- if (post_t * post = parse_post(line, len, account, NULL, defer_expr)) {
- xact.add_post(post);
- added = true;
+ char * p = skip_ws(line);
+ if (*p != ';') {
+ if (post_t * post = parse_post(line, len, account, NULL, defer_expr)) {
+ xact.add_post(post);
+ added = true;
+ }
}
}
@@ -1437,9 +1712,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;
@@ -1452,7 +1727,7 @@ xact_t * instance_t::parse_xact(char * line,
if (char * p = std::strchr(line, '=')) {
*p++ = '\0';
- xact->_date_eff = parse_date(p);
+ xact->_date_aux = parse_date(p);
}
xact->_date = parse_date(line);
@@ -1484,15 +1759,31 @@ xact_t * instance_t::parse_xact(char * line,
// Parse the description text
if (next && *next) {
- char * p = next_element(next, true);
- foreach (payee_mapping_t& value, context.journal.payee_mappings) {
- if (value.first.match(next)) {
- xact->payee = value.second;
+ char * p = next;
+ std::size_t spaces = 0;
+ std::size_t tabs = 0;
+ while (*p) {
+ if (*p == ' ') {
+ ++spaces;
+ }
+ else if (*p == '\t') {
+ ++tabs;
+ }
+ else if (*p == ';' && (tabs > 0 || spaces > 1)) {
+ char *q = p - 1;
+ while (q > next && std::isspace(*q))
+ --q;
+ if (q > next)
+ *(q + 1) = '\0';
break;
}
+ else {
+ spaces = 0;
+ tabs = 0;
+ }
+ ++p;
}
- if (xact->payee.empty())
- xact->payee = next;
+ xact->payee = context.journal->register_payee(next, xact.get());
next = p;
} else {
xact->payee = _("<Unspecified payee>");
@@ -1501,7 +1792,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);
@@ -1513,7 +1804,6 @@ xact_t * instance_t::parse_xact(char * line,
while (peek_whitespace_line()) {
len = read_line(line);
-
char * p = skip_ws(line);
if (! *p)
break;
@@ -1528,8 +1818,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->pos->end_pos = curr_pos;
+ item->append_note(p + 1, *context.scope, true);
+ item->add_flags(ITEM_NOTE_ON_NEXT_LINE);
+ item->pos->end_pos = context.curr_pos;
item->pos->end_line++;
}
else if ((remlen > 7 && *p == 'a' &&
@@ -1541,7 +1832,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);
}
@@ -1549,7 +1840,7 @@ xact_t * instance_t::parse_xact(char * line,
if (c == 'a') {
throw_(parse_error, _("Transaction assertion failed: %1") << p);
} else {
- warning_(_("Transaction check failed: %1") << p);
+ context.warning(STR(_("Transaction check failed: %1") << p));
}
}
}
@@ -1568,7 +1859,7 @@ xact_t * instance_t::parse_xact(char * line,
#if 0
if (xact->_state == item_t::UNCLEARED) {
- item_t::state_t result = item_t::CLEARED;
+ item_t::application_t result = item_t::CLEARED;
foreach (post_t * post, xact->posts) {
if (post->_state == item_t::UNCLEARED) {
@@ -1582,15 +1873,13 @@ 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.state_stack.empty()) {
- foreach (const state_t& state, context.state_stack)
- if (state.type() == typeid(string))
- xact->parse_tags(boost::get<string>(state).c_str(), context.scope,
- false);
- }
+ std::vector<string> tags;
+ get_applications<string>(tags);
+ foreach (string& tag, tags)
+ xact->parse_tags(tag.c_str(), *context.scope, false);
TRACE_STOP(xact_details, 1);
@@ -1601,7 +1890,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;
}
@@ -1610,27 +1900,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,
- bool strict)
+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);
- context.strict = strict;
- if (master_account || this->master)
- context.state_stack.push_front(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
@@ -1641,10 +1922,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 error_count(context_stack.get_current().errors);
- return context.count;
+ return context_stack.get_current().count;
}
} // namespace ledger