summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/textual.cc152
-rw-r--r--src/xact.cc15
-rw-r--r--src/xact.h12
3 files changed, 161 insertions, 18 deletions
diff --git a/src/textual.cc b/src/textual.cc
index 4d133302..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,
@@ -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());
@@ -585,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"));
}
@@ -823,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];
@@ -845,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':
@@ -859,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':
@@ -873,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':
@@ -1383,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;
}
}
diff --git a/src/xact.cc b/src/xact.cc
index eeb487d9..0bf1fc2c 100644
--- a/src/xact.cc
+++ b/src/xact.cc
@@ -691,6 +691,21 @@ void auto_xact_t::extend_xact(xact_base_t& xact,
current_year);
}
}
+ if (check_exprs) {
+ foreach (check_expr_pair& pair, *check_exprs) {
+ if (pair.second == auto_xact_t::EXPR_GENERAL) {
+ pair.first.calc(bound_scope);
+ }
+ else if (! pair.first.calc(bound_scope).to_boolean()) {
+ if (pair.second == auto_xact_t::EXPR_ASSERTION) {
+ throw_(parse_error,
+ _("Transaction assertion failed: %1" << pair.first));
+ } else {
+ warning_(_("Transaction check failed: %1" << pair.first));
+ }
+ }
+ }
+ }
foreach (post_t * post, posts) {
amount_t post_amount;
diff --git a/src/xact.h b/src/xact.h
index 407eed57..a3c639b9 100644
--- a/src/xact.h
+++ b/src/xact.h
@@ -150,6 +150,17 @@ public:
std::map<string, bool> memoized_results;
+ enum xact_expr_kind_t {
+ EXPR_GENERAL,
+ EXPR_ASSERTION,
+ EXPR_CHECK
+ };
+
+ typedef std::pair<expr_t, xact_expr_kind_t> check_expr_pair;
+ typedef std::list<check_expr_pair> check_expr_list;
+
+ optional<check_expr_list> check_exprs;
+
struct deferred_tag_data_t {
string tag_data;
bool overwrite_existing;
@@ -205,6 +216,7 @@ private:
void serialize(Archive& ar, const unsigned int /* version */) {
ar & boost::serialization::base_object<xact_base_t>(*this);
ar & predicate;
+ ar & check_exprs;
ar & deferred_notes;
}
#endif // HAVE_BOOST_SERIALIZATION