summaryrefslogtreecommitdiff
path: root/src/textual.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/textual.cc')
-rw-r--r--src/textual.cc132
1 files changed, 105 insertions, 27 deletions
diff --git a/src/textual.cc b/src/textual.cc
index 9e19ee37..8007ca0d 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003-2013, John Wiegley. All rights reserved.
+ * Copyright (c) 2003-2015, 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
@@ -77,16 +77,18 @@ namespace {
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)
+ instance_t * _parent = NULL,
+ const bool _no_assertions = false)
: context_stack(_context_stack), context(_context),
in(*context.stream.get()), parent(_parent),
- timelog(context) {}
+ no_assertions(_no_assertions), timelog(context) {}
virtual string description() {
return _("textual parser");
@@ -153,6 +155,7 @@ namespace {
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);
@@ -282,6 +285,11 @@ void instance_t::parse()
}
}
+ if (apply_stack.front().value.type() == typeid(optional<datetime_t>))
+ epoch = boost::get<optional<datetime_t> >(apply_stack.front().value);
+
+ apply_stack.pop_front();
+
#if defined(TIMELOG_SUPPORT)
timelog.close();
#endif // TIMELOG_SUPPORT
@@ -416,7 +424,9 @@ void instance_t::read_next_directive(bool& error_flag)
price_xact_directive(line);
break;
case 'Y': // set the current year
- apply_year_directive(line);
+ if (std::strlen(line+1) == 0)
+ throw_(parse_error, _f("Directive '%1%' requires an argument") % line[0]);
+ apply_year_directive(line+1);
break;
}
}
@@ -779,8 +789,8 @@ void instance_t::include_directive(char * line)
context_stack.get_current().master = master;
context_stack.get_current().scope = scope;
try {
- instance_t instance(context_stack,
- context_stack.get_current(), this);
+ instance_t instance(context_stack, context_stack.get_current(),
+ this, no_assertions);
instance.apply_stack.push_front(application_t("account", master));
instance.parse();
}
@@ -860,14 +870,17 @@ void instance_t::apply_rate_directive(char * line)
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));
+ try {
+ unsigned short year(lexical_cast<unsigned short>(skip_ws(line)));
+ apply_stack.push_front(application_t("year", epoch));
+ 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));
+ } catch(bad_lexical_cast &) {
+ throw_(parse_error, _f("Argument '%1%' not a valid year") % skip_ws(line));
+ }
}
void instance_t::end_apply_directive(char * kind)
@@ -915,6 +928,10 @@ void instance_t::account_directive(char * line)
char * b = next_element(q);
string keyword(q);
+ // Ensure there's an argument for the directives that need one.
+ if (! b && keyword != "default")
+ throw_(parse_error, _f("Account directive '%1%' requires an argument") % keyword);
+
if (keyword == "alias") {
account_alias_directive(account, b);
}
@@ -977,6 +994,11 @@ void instance_t::account_alias_directive(account_t * account, string alias)
// (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));
@@ -1026,16 +1048,28 @@ void instance_t::payee_directive(char * line)
char * b = next_element(p);
string keyword(p);
+ if (! b)
+ throw_(parse_error, _f("Payee directive '%1%' requires an argument") % keyword);
+
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_mappings
- .push_back(payee_mapping_t(mask_t(alias), payee));
+ 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)
@@ -1056,6 +1090,10 @@ void instance_t::commodity_directive(char * line)
char * b = next_element(q);
string keyword(q);
+ // Ensure there's an argument for the directives that need one.
+ if (! b && keyword != "nomarket" && keyword != "default")
+ throw_(parse_error, _f("Commodity directive '%1%' requires an argument") % keyword);
+
if (keyword == "alias")
commodity_alias_directive(*commodity, b);
else if (keyword == "value")
@@ -1217,13 +1255,13 @@ void instance_t::python_directive(char * line)
void instance_t::import_directive(char *)
{
throw_(parse_error,
- _("'python' directive seen, but Python support is missing"));
+ _("'import' directive seen, but Python support is missing"));
}
void instance_t::python_directive(char *)
{
throw_(parse_error,
- _("'import' directive seen, but Python support is missing"));
+ _("'python' directive seen, but Python support is missing"));
}
#endif // HAVE_BOOST_PYTHON
@@ -1240,6 +1278,14 @@ bool instance_t::general_directive(char * line)
if (*p == '@' || *p == '!')
p++;
+ // Ensure there's an argument for all directives that need one.
+ if (! arg &&
+ std::strcmp(p, "comment") != 0 && std::strcmp(p, "end") != 0
+ && std::strcmp(p, "python") != 0 && std::strcmp(p, "test") != 0 &&
+ *p != 'Y') {
+ throw_(parse_error, _f("Directive '%1%' requires an argument") % p);
+ }
+
switch (*p) {
case 'a':
if (std::strcmp(p, "account") == 0) {
@@ -1339,6 +1385,13 @@ bool instance_t::general_directive(char * line)
return true;
}
break;
+
+ case 'y':
+ if (std::strcmp(p, "year") == 0) {
+ apply_year_directive(arg);
+ return true;
+ }
+ break;
}
if (expr_t::ptr_op_t op = lookup(symbol_t::DIRECTIVE, p)) {
@@ -1398,8 +1451,7 @@ post_t * instance_t::parse_post(char * line,
}
if (xact &&
- ((xact->_state == item_t::CLEARED && post->_state != item_t::CLEARED) ||
- (xact->_state == item_t::PENDING && post->_state == item_t::UNCLEARED)))
+ (xact->_state != item_t::UNCLEARED && post->_state == item_t::UNCLEARED))
post->set_state(xact->_state);
// Parse the account name
@@ -1425,6 +1477,12 @@ post_t * instance_t::parse_post(char * line,
}
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 << ": "
@@ -1490,13 +1548,12 @@ post_t * instance_t::parse_post(char * line,
post->add_flags(POST_COST_IN_FULL);
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "And it's for a total price");
+ next++;
}
- if (post->has_flags(POST_COST_VIRTUAL) && *(next + 1) == ')')
+ if (post->has_flags(POST_COST_VIRTUAL) && *next == ')')
++next;
- beg = static_cast<std::streamsize>(++next - line);
-
p = skip_ws(next);
if (*p) {
post->cost = amount_t();
@@ -1538,6 +1595,8 @@ post_t * instance_t::parse_post(char * line,
if (fixed_cost)
post->add_flags(POST_COST_FIXATED);
+ post->given_cost = post->cost;
+
DEBUG("textual.parse", "line " << context.linenum << ": "
<< "Total cost is " << *post->cost);
DEBUG("textual.parse", "line " << context.linenum << ": "
@@ -1612,6 +1671,8 @@ post_t * instance_t::parse_post(char * line,
break;
}
+ amount_t tot = amt - diff;
+
DEBUG("post.assign",
"line " << context.linenum << ": " << "diff = " << diff);
DEBUG("textual.parse", "line " << context.linenum << ": "
@@ -1620,8 +1681,10 @@ post_t * instance_t::parse_post(char * line,
if (! diff.is_zero()) {
if (! post->amount.is_null()) {
diff -= post->amount;
- if (! diff.is_zero())
- throw_(parse_error, _f("Balance assertion off by %1%") % diff);
+ 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 << ": "
@@ -1770,7 +1833,7 @@ xact_t * instance_t::parse_xact(char * line,
char *q = p - 1;
while (q > next && std::isspace(*q))
--q;
- if (q > next)
+ if (q >= next)
*(q + 1) = '\0';
break;
}
@@ -1844,6 +1907,17 @@ xact_t * instance_t::parse_xact(char * line,
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;
@@ -1904,13 +1978,17 @@ 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());
+ 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);