summaryrefslogtreecommitdiff
path: root/src/textual.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/textual.cc')
-rw-r--r--src/textual.cc174
1 files changed, 144 insertions, 30 deletions
diff --git a/src/textual.cc b/src/textual.cc
index 9a4fee8e..113bafe8 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -146,6 +146,9 @@ namespace {
void account_mapping_directive(char * line);
void tag_directive(char * line);
void define_directive(char * line);
+ void assert_directive(char * line);
+ void check_directive(char * line);
+ void expr_directive(char * line);
bool general_directive(char * line);
post_t * parse_post(char * line,
@@ -158,9 +161,9 @@ namespace {
xact_base_t& xact,
const bool defer_expr = false);
- xact_t * parse_xact(char * line,
- std::streamsize len,
- account_t * account);
+ 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);
@@ -523,20 +526,74 @@ void instance_t::automated_xact_directive(char * line)
bool reveal_context = true;
try {
+ std::auto_ptr<auto_xact_t> ae
+ (new auto_xact_t(query_t(string(skip_ws(line + 1)),
+ keep_details_t(true, true, true), false)));
+ ae->pos = position_t();
+ ae->pos->pathname = pathname;
+ ae->pos->beg_pos = line_beg_pos;
+ ae->pos->beg_line = 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;
- std::auto_ptr<auto_xact_t> ae
- (new auto_xact_t(query_t(string(skip_ws(line + 1)),
- keep_details_t(true, true, true), false)));
- ae->pos = position_t();
- ae->pos->pathname = pathname;
- ae->pos->beg_pos = line_beg_pos;
- ae->pos->beg_line = linenum;
- ae->pos->sequence = context.sequence++;
+ const std::size_t remlen = std::strlen(p);
- reveal_context = false;
+ if (*p == ';') {
+ item_t * item;
+ if (last_post)
+ item = last_post;
+ else
+ item = ae.get();
- if (parse_posts(context.top_account(), *ae.get(), true)) {
- reveal_context = true;
+ // This is a trailing note, and possibly a metadata info tag
+ item->append_note(p + 1, context.scope, true, current_year);
+ item->pos->end_pos = 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]))) {
+ 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->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)));
+ }
+ else {
+ reveal_context = false;
+
+ if (post_t * post =
+ parse_post(p, len - (p - line), context.top_account(),
+ NULL, true)) {
+ reveal_context = true;
+ ae->add_post(post);
+ last_post = post;
+ }
+ reveal_context = true;
+ }
+ }
context.journal.auto_xacts.push_back(ae.get());
@@ -546,11 +603,9 @@ void instance_t::automated_xact_directive(char * line)
ae.release();
}
-
- }
catch (const std::exception& err) {
if (reveal_context) {
- add_error_context(_("While parsing periodic transaction:"));
+ add_error_context(_("While parsing automated transaction:"));
add_error_context(source_context(pathname, pos, curr_pos, "> "));
}
throw;
@@ -579,7 +634,7 @@ void instance_t::period_xact_directive(char * line)
pe->journal = &context.journal;
if (pe->finalize()) {
- context.journal.extend_xact(pe.get());
+ context.journal.extend_xact(pe.get(), current_year);
context.journal.period_xacts.push_back(pe.get());
pe->pos->end_pos = curr_pos;
@@ -587,6 +642,7 @@ void instance_t::period_xact_directive(char * line)
pe.release();
} else {
+ reveal_context = true;
pe->journal = NULL;
throw parse_error(_("Period transaction failed to balance"));
}
@@ -613,9 +669,9 @@ void instance_t::xact_directive(char * line, std::streamsize len)
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).
+ // 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"));
}
@@ -825,6 +881,26 @@ void instance_t::define_directive(char * line)
def.compile(context.scope); // causes definitions to be established
}
+void instance_t::assert_directive(char * line)
+{
+ expr_t expr(line);
+ 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));
+}
+
+void instance_t::expr_directive(char * line)
+{
+ expr_t expr(line);
+ expr.calc(context.scope);
+}
+
bool instance_t::general_directive(char * line)
{
char buf[8192];
@@ -847,6 +923,10 @@ bool instance_t::general_directive(char * line)
alias_directive(arg);
return true;
}
+ else if (std::strcmp(p, "assert") == 0) {
+ assert_directive(arg);
+ return true;
+ }
break;
case 'b':
@@ -861,6 +941,10 @@ bool instance_t::general_directive(char * line)
account_mapping_directive(arg);
return true;
}
+ else if (std::strcmp(p, "check") == 0) {
+ check_directive(arg);
+ return true;
+ }
break;
case 'd':
@@ -875,6 +959,10 @@ bool instance_t::general_directive(char * line)
end_directive(arg);
return true;
}
+ else if (std::strcmp(p, "expr") == 0) {
+ expr_directive(arg);
+ return true;
+ }
break;
case 'f':
@@ -1256,7 +1344,7 @@ post_t * instance_t::parse_post(char * line,
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);
+ true, current_year);
}
TRACE_STOP(post_details, 1);
@@ -1385,25 +1473,51 @@ xact_t * instance_t::parse_xact(char * line,
if (! *p)
break;
- if (*p == ';') {
- item_t * item;
- if (last_post)
- item = last_post;
- else
- item = xact.get();
+ 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, current_year);
item->pos->end_pos = curr_pos;
item->pos->end_line++;
- } else {
+ }
+ 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, _("Transaction assertion failed: %1" << p));
+ } else {
+ warning_(_("Transaction check failed: %1" << p));
+ }
+ }
+ }
+ else {
reveal_context = false;
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;
}
}
@@ -1428,7 +1542,7 @@ xact_t * instance_t::parse_xact(char * line,
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);
+ false, current_year);
}
TRACE_STOP(xact_details, 1);