ledger/src/textual.cc
Christophe Rhodes da51f5b510 support payee uuid directive
The semantics of this are a little bit tricky: we want, if we come
across a transaction with exactly the given UUID, to set the
transaction's payee to be the specified one.

We need to set that payee before the first post in the transaction is
parsed, otherwise that post will inherit the wrong payee; however, we
need to do it after the transaction's tags have been parsed.  The
implementation as it is in this commit is potentially a little
wasteful, if there are post-like (non-comment non-assertion) entries
in the transaction that don't successfully parse as posts.
2014-05-13 16:08:10 +01:00

1971 lines
54 KiB
C++

/*
* Copyright (c) 2003-2014, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <system.hh>
#include "journal.h"
#include "context.h"
#include "xact.h"
#include "post.h"
#include "account.h"
#include "option.h"
#include "query.h"
#include "pstream.h"
#include "pool.h"
#if HAVE_BOOST_PYTHON
#include "pyinterp.h"
#endif
#define TIMELOG_SUPPORT 1
#if defined(TIMELOG_SUPPORT)
#include "timelog.h"
#endif
namespace ledger {
namespace {
typedef std::pair<commodity_t *, amount_t> fixed_rate_t;
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:
parse_context_stack_t& context_stack;
parse_context_t& context;
std::istream& in;
instance_t * parent;
std::list<application_t> apply_stack;
bool no_assertions;
#if defined(TIMELOG_SUPPORT)
time_log_t timelog;
#endif
instance_t(parse_context_stack_t& _context_stack,
parse_context_t& _context,
instance_t * _parent = NULL,
const bool _no_assertions = false)
: context_stack(_context_stack), context(_context),
in(*context.stream.get()), parent(_parent),
no_assertions(_no_assertions), timelog(context) {}
virtual string description() {
return _("textual parser");
}
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);
}
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;
}
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 HAVE_BOOST_PYTHON
bool peek_blank_line() {
return (in.good() && ! in.eof() &&
(in.peek() == '\n' || in.peek() == '\r'));
}
#endif
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
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 * args);
void alias_directive(char * line);
void payee_directive(char * line);
void payee_alias_directive(const string& payee, string alias);
void payee_uuid_directive(const string& payee, string uuid);
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 include_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 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 parse_posts(account_t * account,
xact_base_t& xact,
const bool defer_expr = false);
xact_t * parse_xact(char * line,
std::streamsize len,
account_t * account);
virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
const string& name);
};
void parse_amount_expr(std::istream& in,
scope_t& scope,
post_t& post,
amount_t& amount,
const parse_flags_t& flags = PARSE_DEFAULT,
const bool defer_expr = false,
optional<expr_t> * amount_expr = NULL)
{
expr_t expr(in, flags.plus_flags(PARSE_PARTIAL));
DEBUG("textual.parse", "Parsed an amount expression");
if (expr) {
if (amount_expr)
*amount_expr = expr;
if (! defer_expr)
amount = post.resolve_expr(scope, expr);
}
}
}
void instance_t::parse()
{
INFO("Parsing file " << context.pathname);
TRACE_START(instance_parse, 1, "Done parsing file " << context.pathname);
if (! in.good() || in.eof())
return;
context.linenum = 0;
context.curr_pos = in.tellg();
bool error_flag = false;
while (in.good() && ! in.eof()) {
try {
read_next_directive(error_flag);
}
catch (const std::exception& err) {
error_flag = true;
string current_context = error_context();
if (parent) {
std::list<instance_t *> instances;
for (instance_t * instance = parent;
instance;
instance = instance->parent)
instances.push_front(instance);
foreach (instance_t * instance, instances)
add_error_context(_f("In file included from %1%")
% instance->context.location());
}
add_error_context(_f("While parsing file %1%") % context.location());
if (caught_signal != NONE_CAUGHT)
throw;
string err_context = error_context();
if (! err_context.empty())
std::cerr << err_context << std::endl;
if (! current_context.empty())
std::cerr << current_context << std::endl;
std::cerr << _("Error: ") << err.what() << std::endl;
context.errors++;
}
}
#if defined(TIMELOG_SUPPORT)
timelog.close();
#endif // TIMELOG_SUPPORT
TRACE_STOP(instance_parse, 1);
}
std::streamsize instance_t::read_line(char *& line)
{
assert(in.good());
assert(! in.eof()); // no one should call us in that case
context.line_beg_pos = context.curr_pos;
check_for_signal();
in.getline(context.linebuf, parse_context_t::MAX_LINE);
std::streamsize len = in.gcount();
if (len > 0) {
context.linenum++;
context.curr_pos = context.line_beg_pos;
context.curr_pos += len;
if (context.linenum == 0 && utf8::is_bom(context.linebuf)) {
line = &context.linebuf[3];
len -= 3;
} else {
line = context.linebuf;
}
--len;
while (len > 0 && std::isspace(line[len - 1])) // strip trailing whitespace
line[--len] = '\0';
return len;
}
return 0;
}
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':
if (! error_flag)
throw parse_error(_("Unexpected whitespace at beginning of line"));
break;
case ';': // comments
case '#':
case '*':
case '|':
break;
case '-': // option setting
option_directive(line);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
xact_directive(line, len);
break;
case '=': // automated xact
automated_xact_directive(line);
break;
case '~': // period xact
period_xact_directive(line);
break;
case '@':
case '!':
line++;
// fall through...
default: // some other directive
if (! general_directive(line)) {
switch (line[0]) {
#if defined(TIMELOG_SUPPORT)
case 'i':
clock_in_directive(line, false);
break;
case 'I':
clock_in_directive(line, true);
break;
case 'o':
clock_out_directive(line, false);
break;
case 'O':
clock_out_directive(line, true);
break;
case 'h':
case 'b':
break;
#endif // TIMELOG_SUPPORT
case 'A': // a default account for unbalanced posts
default_account_directive(line + 1);
break;
case 'C': // a set of conversions
price_conversion_directive(line);
break;
case 'D': // a default commodity for "xact"
default_commodity_directive(line);
break;
case 'N': // don't download prices
nomarket_directive(line);
break;
case 'P': // a pricing xact
price_xact_directive(line);
break;
case 'Y': // set the current year
apply_year_directive(line + 1);
break;
}
}
break;
}
}
#if defined(TIMELOG_SUPPORT)
void instance_t::clock_in_directive(char * line, bool capitalized)
{
string datetime(line, 2, 19);
char * p = skip_ws(line + 22);
char * n = next_element(p, true);
char * end = n ? next_element(n, true) : NULL;
if (end && *end == ';')
end = skip_ws(end + 1);
else
end = NULL;
position_t position;
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), capitalized,
p ? top_account()->find_account(p) : NULL,
n ? n : "",
end ? end : "");
timelog.clock_in(event);
}
void instance_t::clock_out_directive(char * line, bool capitalized)
{
string datetime(line, 2, 19);
char * p = skip_ws(line + 22);
char * n = next_element(p, true);
char * end = n ? next_element(n, true) : NULL;
if (end && *end == ';')
end = skip_ws(end + 1);
else
end = NULL;
position_t position;
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), capitalized,
p ? top_account()->find_account(p) : NULL,
n ? n : "",
end ? end : "");
context.count += timelog.clock_out(event);
}
#endif // TIMELOG_SUPPORT
void instance_t::default_commodity_directive(char * line)
{
amount_t amt(skip_ws(line + 1));
VERIFY(amt.valid());
commodity_pool_t::current_pool->default_commodity = &amt.commodity();
amt.commodity().add_flags(COMMODITY_KNOWN);
}
void instance_t::default_account_directive(char * line)
{
context.journal->bucket = top_account()->find_account(skip_ws(line));
context.journal->bucket->add_flags(ACCOUNT_KNOWN);
}
void instance_t::price_conversion_directive(char * line)
{
if (char * p = std::strchr(line + 1, '=')) {
*p++ = '\0';
amount_t::parse_conversion(line + 1, p);
}
}
void instance_t::price_xact_directive(char * line)
{
optional<std::pair<commodity_t *, price_point_t> > point =
commodity_pool_t::current_pool->parse_price_directive(skip_ws(line + 1));
if (! point)
throw parse_error(_("Pricing entry failed to parse"));
}
void instance_t::nomarket_directive(char * line)
{
char * p = skip_ws(line + 1);
string symbol;
commodity_t::parse_symbol(p, symbol);
if (commodity_t * commodity =
commodity_pool_t::current_pool->find_or_create(symbol))
commodity->add_flags(COMMODITY_NOMARKET | COMMODITY_KNOWN);
}
void instance_t::option_directive(char * line)
{
char * p = next_element(line);
if (! p) {
p = std::strchr(line, '=');
if (p)
*p++ = '\0';
}
if (! process_option(context.pathname.string(), line + 2, *context.scope,
p, line))
throw_(option_error, _f("Illegal option --%1%") % (line + 2));
}
void instance_t::automated_xact_directive(char * line)
{
istream_pos_type pos = context.line_beg_pos;
bool reveal_context = true;
try {
query_t query;
keep_details_t keeper(true, true, true);
expr_t::ptr_op_t expr =
query.parse_args(string_value(skip_ws(line + 1)).to_sequence(),
keeper, false, true);
unique_ptr<auto_xact_t> ae(new auto_xact_t(predicate_t(expr, keeper)));
ae->pos = position_t();
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;
const std::size_t remlen = std::strlen(p);
if (*p == ';') {
item_t * item;
if (last_post)
item = last_post;
else
item = ae.get();
// This is a trailing note, and possibly a metadata info tag
ae->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' &&
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, "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 = expr_t::check_expr_list();
ae->check_exprs->push_back
(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), top_account(), NULL, true)) {
reveal_context = true;
ae->add_post(post);
ae->active_post = last_post = post;
}
reveal_context = true;
}
}
context.journal->auto_xacts.push_back(ae.get());
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(context.pathname, pos,
context.curr_pos, "> "));
}
throw;
}
}
void instance_t::period_xact_directive(char * line)
{
istream_pos_type pos = context.line_beg_pos;
bool reveal_context = true;
try {
unique_ptr<period_xact_t> pe(new period_xact_t(skip_ws(line + 1)));
pe->pos = position_t();
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(top_account(), *pe.get())) {
reveal_context = true;
pe->journal = context.journal;
if (pe->finalize()) {
context.journal->extend_xact(pe.get());
context.journal->period_xacts.push_back(pe.get());
pe->pos->end_pos = context.curr_pos;
pe->pos->end_line = context.linenum;
pe.release();
} else {
reveal_context = true;
pe->journal = NULL;
throw parse_error(_("Period transaction failed to balance"));
}
}
}
catch (const std::exception&) {
if (reveal_context) {
add_error_context(_("While parsing periodic transaction:"));
add_error_context(source_context(context.pathname, pos,
context.curr_pos, "> "));
}
throw;
}
}
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, top_account())) {
unique_ptr<xact_t> manager(xact);
if (context.journal->add_xact(xact)) {
manager.release(); // it's owned by the journal now
context.count++;
}
// It's perfectly valid for the journal to reject the xact, which it
// will do if the xact has no substantive effect (for example, a
// checking xact, all of whose postings have null amounts).
} else {
throw parse_error(_("Failed to parse transaction"));
}
TRACE_STOP(xacts, 1);
}
void instance_t::include_directive(char * line)
{
path filename;
DEBUG("textual.include", "include: " << line);
if (line[0] != '/' && line[0] != '\\' && line[0] != '~') {
DEBUG("textual.include", "received a relative path");
DEBUG("textual.include", "parent file path: " << context.pathname);
string pathstr(context.pathname.string());
string::size_type pos = pathstr.rfind('/');
if (pos == string::npos)
pos = pathstr.rfind('\\');
if (pos != string::npos) {
filename = path(string(pathstr, 0, pos + 1)) / line;
DEBUG("textual.include", "normalized path: " << filename.string());
} else {
filename = path(string(".")) / line;
}
} else {
filename = line;
}
filename = resolve_path(filename);
DEBUG("textual.include", "resolved path: " << filename.string());
mask_t glob;
#if BOOST_VERSION >= 103700
path parent_path = filename.parent_path();
#if BOOST_VERSION >= 104600
glob.assign_glob('^' + filename.filename().string() + '$');
#else
glob.assign_glob('^' + filename.filename() + '$');
#endif
#else // BOOST_VERSION >= 103700
path parent_path = filename.branch_path();
glob.assign_glob('^' + filename.leaf() + '$');
#endif // BOOST_VERSION >= 103700
bool files_found = false;
if (exists(parent_path)) {
filesystem::directory_iterator end;
for (filesystem::directory_iterator iter(parent_path);
iter != end;
++iter) {
#if BOOST_VERSION <= 103500
if (is_regular(*iter))
#else
if (is_regular_file(*iter))
#endif
{
#if BOOST_VERSION >= 103700
#if BOOST_VERSION >= 104600
string base = (*iter).path().filename().string();
#else
string base = (*iter).filename();
#endif
#else // BOOST_VERSION >= 103700
string base = (*iter).leaf();
#endif // BOOST_VERSION >= 103700
if (glob.match(base)) {
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, no_assertions);
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;
}
}
}
}
if (! files_found)
throw_(std::runtime_error,
_f("File to include was not found: %1%") % filename);
}
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 = top_account()->find_account(line))
apply_stack.push_front(application_t("account", acct));
#if !NO_ASSERTS
else
assert("Failed to create account" == NULL);
#endif
}
void instance_t::apply_tag_directive(char * line)
{
string tag(trim_ws(line));
if (tag.find(':') == string::npos)
tag = string(":") + 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, 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)));
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,
_f("'end apply %1%' found, but no enclosing 'apply' directive")
% name);
}
}
if (! name.empty() && name != apply_stack.front().label)
throw_(std::runtime_error,
_f("'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);
// Ensure that no alias like "alias Foo=Foo" is registered.
if ( alias == account->fullname()) {
throw_(parse_error, _f("Illegal alias %1%=%2%")
% alias % account->fullname());
}
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)
{
if (char * e = std::strchr(line, '=')) {
char * z = e - 1;
while (std::isspace(*z))
*z-- = '\0';
*e++ = '\0';
e = skip_ws(e);
account_alias_directive(top_account()->find_account(e), line);
}
}
void instance_t::account_payee_directive(account_t * account, string payee)
{
trim(payee);
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;
}
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()) {
read_line(line);
char * p = skip_ws(line);
if (! *p)
break;
char * b = next_element(p);
string keyword(p);
if (keyword == "alias")
payee_alias_directive(payee, b);
if (keyword == "uuid")
payee_uuid_directive(payee, b);
}
}
void instance_t::payee_alias_directive(const string& payee, string alias)
{
trim(alias);
context.journal->payee_alias_mappings
.push_back(payee_alias_mapping_t(mask_t(alias), payee));
}
void instance_t::payee_uuid_directive(const string& payee, string uuid)
{
trim(uuid);
context.journal->payee_uuid_mappings
.push_back(payee_uuid_mapping_t(uuid, payee));
}
void instance_t::commodity_directive(char * line)
{
char * p = skip_ws(line);
string symbol;
commodity_t::parse_symbol(p, symbol);
if (commodity_t * commodity =
commodity_pool_t::current_pool->find_or_create(symbol)) {
context.journal->register_commodity(*commodity, 0);
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")
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)
{
char * p = skip_ws(line);
context.journal->register_metadata(p, NULL_VALUE, 0);
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 == "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::eval_directive(char * line)
{
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())
throw_(parse_error, _f("Assertion failed: %1%") % line);
}
void instance_t::check_directive(char * line)
{
expr_t expr(line);
if (! expr.calc(*context.scope).to_boolean())
context.warning(_f("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)
{
while (in.good() && ! in.eof()) {
if (read_line(line) > 0) {
std::string buf(line);
if (starts_with(buf, "end comment") || starts_with(buf, "end test"))
break;
}
}
}
#if HAVE_BOOST_PYTHON
void instance_t::import_directive(char * line)
{
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];
std::strcpy(buf, line);
char * p = buf;
char * arg = next_element(buf);
if (*p == '@' || *p == '!')
p++;
switch (*p) {
case 'a':
if (std::strcmp(p, "account") == 0) {
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;
}
break;
case 'b':
if (std::strcmp(p, "bucket") == 0) {
default_account_directive(arg);
return true;
}
break;
case 'c':
if (std::strcmp(p, "check") == 0) {
check_directive(arg);
return true;
}
else if (std::strcmp(p, "comment") == 0) {
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) {
eval_directive(arg);
return true;
}
break;
case 'e':
if (std::strcmp(p, "end") == 0) {
end_apply_directive(arg);
return true;
}
else if (std::strcmp(p, "expr") == 0 || std::strcmp(p, "eval") == 0) {
eval_directive(arg);
return true;
}
break;
case 'i':
if (std::strcmp(p, "include") == 0) {
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_directive(arg);
return true;
}
else if (std::strcmp(p, "python") == 0) {
python_directive(arg);
return true;
}
break;
case 't':
if (std::strcmp(p, "tag") == 0) {
tag_directive(arg);
return true;
}
else if (std::strcmp(p, "test") == 0) {
comment_directive(arg);
return true;
}
break;
case 'v':
if (std::strcmp(p, "value") == 0) {
value_directive(arg);
return true;
}
break;
}
if (expr_t::ptr_op_t op = lookup(symbol_t::DIRECTIVE, p)) {
call_scope_t args(*this);
args.push_back(string_value(p));
op->as_function()(args);
return true;
}
return false;
}
post_t * instance_t::parse_post(char * line,
std::streamsize len,
account_t * account,
xact_t * xact,
bool defer_expr)
{
TRACE_START(post_details, 1, "Time spent parsing postings:");
unique_ptr<post_t> post(new post_t);
post->xact = xact; // this could be NULL
post->pos = position_t();
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[parse_context_t::MAX_LINE + 1];
std::strcpy(buf, line);
std::streamsize beg = 0;
try {
// Parse the state flag
assert(line);
assert(*line);
char * p = skip_ws(line);
switch (*p) {
case '*':
post->set_state(item_t::CLEARED);
p = skip_ws(p + 1);
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 " << context.linenum << ": "
<< "Parsed the PENDING flag");
break;
}
if (xact &&
((xact->_state == item_t::CLEARED && post->_state != item_t::CLEARED) ||
(xact->_state == item_t::PENDING && post->_state == item_t::UNCLEARED)))
post->set_state(xact->_state);
// Parse the account name
if (! *p)
throw parse_error(_("Posting has no account"));
char * next = next_element(p, true);
char * e = p + std::strlen(p);
while (e > p && std::isspace(*(e - 1)))
e--;
if ((*p == '[' && *(e - 1) == ']') || (*p == '(' && *(e - 1) == ')')) {
post->add_flags(POST_VIRTUAL);
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Parsed a virtual account name");
if (*p == '[') {
post->add_flags(POST_MUST_BALANCE);
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Posting must balance");
}
p++; e--;
}
else if (*p == '<' && *(e - 1) == '>') {
post->add_flags(POST_DEFERRED);
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Parsed a deferred account name");
p++; e--;
}
string name(p, static_cast<string::size_type>(e - p));
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Parsed account name " << name);
post->account =
context.journal->register_account(name, post.get(), account);
// Parse the optional amount
if (next && *next && (*next != ';' && *next != '=')) {
beg = static_cast<std::streamsize>(next - line);
ptristream stream(next, static_cast<std::size_t>(len - beg));
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_NO_REDUCE | PARSE_SINGLE | PARSE_NO_ASSIGN,
defer_expr, &post->amount_expr);
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "post amount = " << post->amount);
if (! post->amount.is_null() && post->amount.has_commodity()) {
context.journal->register_commodity(post->amount.commodity(), post.get());
if (! post->amount.has_annotation()) {
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);
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "applied rate = " << post->amount);
break;
}
}
}
}
if (stream.eof()) {
next = NULL;
} else {
next = skip_ws(next + static_cast<std::ptrdiff_t>(stream.tellg()));
// Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST)
if (*next == '@' || (*next == '(' && *(next + 1) == '@')) {
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Found a price indicator");
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 " << 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);
if (*p) {
post->cost = amount_t();
bool fixed_cost = false;
if (*p == '=') {
p++;
fixed_cost = true;
if (*p == '\0')
throw parse_error(_("Posting is missing a cost amount"));
}
beg = static_cast<std::streamsize>(p - line);
ptristream cstream(p, static_cast<std::size_t>(len - beg));
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_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_ASSIGN);
if (post->cost->sign() < 0)
throw parse_error(_("A posting's cost may not be negative"));
post->cost->in_place_unround();
if (per_unit) {
// For the sole case where the cost might be uncommoditized,
// guarantee that the commodity of the cost after multiplication
// is the same as it was before.
commodity_t& cost_commodity(post->cost->commodity());
*post->cost *= post->amount;
post->cost->set_commodity(cost_commodity);
}
else if (post->amount.sign() < 0) {
post->cost->in_place_negate();
}
if (fixed_cost)
post->add_flags(POST_COST_FIXATED);
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Total cost is " << *post->cost);
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Annotated amount is " << post->amount);
if (cstream.eof())
next = NULL;
else
next = skip_ws(p + static_cast<std::ptrdiff_t>(cstream.tellg()));
} else {
throw parse_error(_("Expected a cost amount"));
}
}
}
}
// Parse the optional balance assignment
if (xact && next && *next == '=') {
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Found a balance assignment indicator");
beg = static_cast<std::streamsize>(++next - line);
p = skip_ws(next);
if (*p) {
post->assigned_amount = amount_t();
beg = static_cast<std::streamsize>(p - line);
ptristream stream(p, static_cast<std::size_t>(len - beg));
if (*p != '(') // indicates a value expression
post->assigned_amount->parse(stream, PARSE_NO_MIGRATE);
else
parse_amount_expr(stream, *context.scope, *post.get(),
*post->assigned_amount,
PARSE_SINGLE | PARSE_NO_MIGRATE);
if (post->assigned_amount->is_null()) {
if (post->amount.is_null())
throw parse_error(_("Balance assignment must evaluate to a constant"));
else
throw parse_error(_("Balance assertion must evaluate to a constant"));
}
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 " << context.linenum << ": " << "post amount = " << amt);
amount_t diff = amt;
amount_t tot;
switch (account_total.type()) {
case value_t::AMOUNT:
tot = account_total.as_amount();
break;
case value_t::BALANCE:
if (optional<amount_t> comm_bal =
account_total.as_balance().commodity_amount(amt.commodity()))
tot = *comm_bal;
break;
default:
break;
}
diff -= tot;
DEBUG("post.assign",
"line " << context.linenum << ": " << "diff = " << diff);
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "POST assign: diff = " << diff);
if (! diff.is_zero()) {
if (! post->amount.is_null()) {
diff -= post->amount;
if (! no_assertions && ! diff.is_zero())
throw_(parse_error,
_f("Balance assertion off by %1% (expected to see %2%)")
% diff % tot);
} else {
post->amount = diff;
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Overwrite null posting");
}
}
if (stream.eof())
next = NULL;
else
next = skip_ws(p + static_cast<std::ptrdiff_t>(stream.tellg()));
} else {
throw parse_error(_("Expected an balance assignment/assertion amount"));
}
}
// Parse the optional note
if (next && *next == ';') {
post->append_note(++next, *context.scope, true);
next = line + len;
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Parsed a posting note");
}
// There should be nothing more to read
if (next && *next)
throw_(parse_error,
_f("Unexpected char '%1%' (Note: inline math requires parentheses)")
% *next);
post->pos->end_pos = context.curr_pos;
post->pos->end_line = context.linenum;
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);
return post.release();
}
catch (const std::exception&) {
add_error_context(_("While parsing posting:"));
add_error_context(line_context(buf, static_cast<string::size_type>(beg),
static_cast<string::size_type>(len)));
throw;
}
}
bool instance_t::parse_posts(account_t * account,
xact_base_t& xact,
const bool defer_expr)
{
TRACE_START(xact_posts, 1, "Time spent parsing postings:");
bool added = false;
while (peek_whitespace_line()) {
char * line;
std::streamsize len = read_line(line);
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;
}
}
}
TRACE_STOP(xact_posts, 1);
return added;
}
xact_t * instance_t::parse_xact(char * line,
std::streamsize len,
account_t * account)
{
TRACE_START(xact_text, 1, "Time spent parsing transaction text:");
unique_ptr<xact_t> xact(new xact_t);
xact->pos = position_t();
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;
try {
// Parse the date
char * next = next_element(line);
if (char * p = std::strchr(line, '=')) {
*p++ = '\0';
xact->_date_aux = parse_date(p);
}
xact->_date = parse_date(line);
// Parse the optional cleared flag: *
if (next) {
switch (*next) {
case '*':
xact->_state = item_t::CLEARED;
next = skip_ws(++next);
break;
case '!':
xact->_state = item_t::PENDING;
next = skip_ws(++next);
break;
}
}
// Parse the optional code: (TEXT)
if (next && *next == '(') {
if (char * p = std::strchr(next++, ')')) {
*p++ = '\0';
xact->code = next;
next = skip_ws(p);
}
}
// Parse the description text
if (next && *next) {
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;
}
xact->payee = context.journal->register_payee(next, xact.get());
next = p;
} else {
xact->payee = _("<Unspecified payee>");
}
// Parse the xact note
if (next && *next == ';')
xact->append_note(++next, *context.scope, false);
TRACE_STOP(xact_text, 1);
// Parse all of the posts associated with this xact
TRACE_START(xact_details, 1, "Time spent parsing transaction details:");
post_t * last_post = NULL;
while (peek_whitespace_line()) {
len = read_line(line);
char * p = skip_ws(line);
if (! *p)
break;
const std::size_t remlen = std::strlen(p);
item_t * item;
if (last_post)
item = last_post;
else
item = xact.get();
if (*p == ';') {
// This is a trailing note, and possibly a metadata info tag
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' &&
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]))) {
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);
if (c == 'e') {
expr.calc(bound_scope);
}
else if (! expr.calc(bound_scope).to_boolean()) {
if (c == 'a') {
throw_(parse_error, _f("Transaction assertion failed: %1%") % p);
} else {
context.warning(_f("Transaction check failed: %1%") % p);
}
}
}
else {
reveal_context = false;
if (!last_post) {
if (xact->has_tag(_("UUID"))) {
string uuid = xact->get_tag(_("UUID"))->to_string();
foreach (payee_uuid_mapping_t value, context.journal->payee_uuid_mappings) {
if (value.first.compare(uuid) == 0) {
xact->payee = value.second;
}
}
}
}
if (post_t * post =
parse_post(p, len - (p - line), account, xact.get())) {
reveal_context = true;
xact->add_post(post);
last_post = post;
}
reveal_context = true;
}
}
#if 0
if (xact->_state == item_t::UNCLEARED) {
item_t::application_t result = item_t::CLEARED;
foreach (post_t * post, xact->posts) {
if (post->_state == item_t::UNCLEARED) {
result = item_t::UNCLEARED;
break;
}
else if (post->_state == item_t::PENDING) {
result = item_t::PENDING;
}
}
}
#endif
xact->pos->end_pos = context.curr_pos;
xact->pos->end_line = context.linenum;
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);
return xact.release();
}
catch (const std::exception&) {
if (reveal_context) {
add_error_context(_("While parsing transaction:"));
add_error_context(source_context(xact->pos->pathname,
xact->pos->beg_pos,
context.curr_pos, "> "));
}
throw;
}
}
expr_t::ptr_op_t instance_t::lookup(const symbol_t::kind_t kind,
const string& name)
{
return context.scope->lookup(kind, name);
}
std::size_t journal_t::read_textual(parse_context_stack_t& context_stack)
{
TRACE_START(parsing_total, 1, "Total time spent parsing text:");
{
instance_t instance(context_stack, context_stack.get_current(), NULL,
checking_style == journal_t::CHECK_PERMISSIVE);
instance.apply_stack.push_front
(application_t("account", context_stack.get_current().master));
instance.parse();
}
TRACE_STOP(parsing_total, 1);
// Apply any deferred postings at this time
master->apply_deferred_posts();
// These tracers were started in textual.cc
TRACE_FINISH(xact_text, 1);
TRACE_FINISH(xact_details, 1);
TRACE_FINISH(xact_posts, 1);
TRACE_FINISH(xacts, 1);
TRACE_FINISH(instance_parse, 1); // report per-instance timers
TRACE_FINISH(parsing_total, 1);
if (context_stack.get_current().errors > 0)
throw error_count(context_stack.get_current().errors);
return context_stack.get_current().count;
}
} // namespace ledger