summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-11-10 12:22:19 -0500
committerJohn Wiegley <johnw@newartisans.com>2009-11-10 12:22:19 -0500
commit5ffa987daf4d97c52066e4c28733d826d3726297 (patch)
treeab058dec32702ccce9d1f7cacacb5b5c157d6798
parent9e07e61fadf7f3c9d1fd32a3805f6e97163eba15 (diff)
parent3f638d355c977fd5513ab1db380c5813599f3664 (diff)
downloadfork-ledger-5ffa987daf4d97c52066e4c28733d826d3726297.tar.gz
fork-ledger-5ffa987daf4d97c52066e4c28733d826d3726297.tar.bz2
fork-ledger-5ffa987daf4d97c52066e4c28733d826d3726297.zip
Merge branch 'next'
-rw-r--r--src/account.cc4
-rw-r--r--src/amount.cc156
-rw-r--r--src/compare.cc3
-rw-r--r--src/expr.cc23
-rw-r--r--src/format.cc10
-rw-r--r--src/op.cc91
-rw-r--r--src/op.h6
-rw-r--r--src/parser.cc2
-rw-r--r--src/post.cc77
-rw-r--r--src/predicate.cc46
-rw-r--r--src/predicate.h3
-rw-r--r--src/query.cc9
-rw-r--r--src/query.h12
-rw-r--r--src/report.cc27
-rw-r--r--src/report.h1
-rw-r--r--src/textual.cc155
-rw-r--r--src/value.h2
-rw-r--r--src/xact.cc4
-rw-r--r--test/baseline/opt-actual.test2
-rw-r--r--test/regress/5A03CFC3.test2
-rw-r--r--test/regress/727B2DF8.test2
-rw-r--r--test/regress/793F6BF0.test2
-rw-r--r--tools/Makefile.am1
-rwxr-xr-xtools/push3
24 files changed, 398 insertions, 245 deletions
diff --git a/src/account.cc b/src/account.cc
index 43b7cd56..5cc7e070 100644
--- a/src/account.cc
+++ b/src/account.cc
@@ -180,11 +180,11 @@ namespace {
}
value_t get_amount(account_t& account) {
- return VALUE_OR_ZERO(account.amount());
+ return SIMPLIFIED_VALUE_OR_ZERO(account.amount());
}
value_t get_total(account_t& account) {
- return VALUE_OR_ZERO(account.total());
+ return SIMPLIFIED_VALUE_OR_ZERO(account.total());
}
value_t get_subcount(account_t& account) {
diff --git a/src/amount.cc b/src/amount.cc
index 6fb0056b..77a5f8e3 100644
--- a/src/amount.cc
+++ b/src/amount.cc
@@ -930,18 +930,22 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
// exeception thrown by any of the function calls after this point,
// the destructor will never be called and the memory never freed.
- std::auto_ptr<bigint_t> safe_holder;
+ std::auto_ptr<bigint_t> new_quantity;
- if (! quantity) {
- quantity = new bigint_t;
- safe_holder.reset(quantity);
- }
- else if (quantity->refc > 1) {
- _release();
- quantity = new bigint_t;
- safe_holder.reset(quantity);
+ if (quantity) {
+ if (quantity->refc > 1)
+ _release();
+ else
+ new_quantity.reset(quantity);
+ quantity = NULL;
}
+ if (! new_quantity.get())
+ new_quantity.reset(new bigint_t);
+
+ // No one is holding a reference to this now.
+ new_quantity->refc--;
+
// Create the commodity if has not already been seen, and update the
// precision if something greater was used for the quantity.
@@ -961,52 +965,101 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
commodity_ = current_pool->find_or_create(*commodity_, details);
}
- // Determine the precision of the amount, based on the usage of
- // comma or period.
+ // Quickly scan through and verify the correctness of the amount's use of
+ // punctuation.
- string::size_type last_comma = quant.rfind(',');
- string::size_type last_period = quant.rfind('.');
+ precision_t decimal_offset = 0;
+ string::size_type string_index = quant.length();
+ string::size_type last_comma = string::npos;
+ string::size_type last_period = string::npos;
- if (last_comma != string::npos && last_period != string::npos) {
- comm_flags |= COMMODITY_STYLE_THOUSANDS;
- if (last_comma > last_period) {
- comm_flags |= COMMODITY_STYLE_EUROPEAN;
- quantity->prec = static_cast<precision_t>(quant.length() -
- last_comma - 1);
- } else {
- quantity->prec = static_cast<precision_t>(quant.length() -
- last_period - 1);
+ bool no_more_commas = false;
+ bool no_more_periods = false;
+ bool european_style = (commodity_t::european_by_default ||
+ commodity().has_flags(COMMODITY_STYLE_EUROPEAN));
+
+ new_quantity->prec = 0;
+
+ BOOST_REVERSE_FOREACH (const char& ch, quant) {
+ string_index--;
+
+ if (ch == '.') {
+ if (no_more_periods)
+ throw_(amount_error, _("Too many periods in amount"));
+
+ if (european_style) {
+ if (decimal_offset % 3 != 0)
+ throw_(amount_error, _("Incorrect use of european-style period"));
+ comm_flags |= COMMODITY_STYLE_THOUSANDS;
+ no_more_commas = true;
+ } else {
+ if (last_comma != string::npos) {
+ european_style = true;
+ if (decimal_offset % 3 != 0)
+ throw_(amount_error, _("Incorrect use of european-style period"));
+ } else {
+ no_more_periods = true;
+ new_quantity->prec = decimal_offset;
+ decimal_offset = 0;
+ }
+ }
+
+ if (last_period == string::npos)
+ last_period = string_index;
+ }
+ else if (ch == ',') {
+ if (no_more_commas)
+ throw_(amount_error, _("Too many commas in amount"));
+
+ if (european_style) {
+ if (last_period != string::npos) {
+ throw_(amount_error, _("Incorrect use of european-style comma"));
+ } else {
+ no_more_commas = true;
+ new_quantity->prec = decimal_offset;
+ decimal_offset = 0;
+ }
+ } else {
+ if (decimal_offset % 3 != 0) {
+ if (last_comma != string::npos ||
+ last_period != string::npos) {
+ throw_(amount_error, _("Incorrect use of American-style comma"));
+ } else {
+ european_style = true;
+ no_more_commas = true;
+ new_quantity->prec = decimal_offset;
+ decimal_offset = 0;
+ }
+ } else {
+ comm_flags |= COMMODITY_STYLE_THOUSANDS;
+ no_more_periods = true;
+ }
+ }
+
+ if (last_comma == string::npos)
+ last_comma = string_index;
+ }
+ else {
+ decimal_offset++;
}
- }
- else if (last_comma != string::npos &&
- (commodity_t::european_by_default ||
- commodity().has_flags(COMMODITY_STYLE_EUROPEAN))) {
- comm_flags |= COMMODITY_STYLE_EUROPEAN;
- quantity->prec = static_cast<precision_t>(quant.length() - last_comma - 1);
- }
- else if (last_period != string::npos &&
- ! (commodity_t::european_by_default ||
- commodity().has_flags(COMMODITY_STYLE_EUROPEAN))) {
- quantity->prec = static_cast<precision_t>(quant.length() - last_period - 1);
- }
- else {
- quantity->prec = 0;
}
- // Set the commodity's flags and precision accordingly
+ if (european_style)
+ comm_flags |= COMMODITY_STYLE_EUROPEAN;
if (flags.has_flags(PARSE_NO_MIGRATE)) {
- set_keep_precision(true);
+ // Can't call set_keep_precision here, because it assumes that `quantity'
+ // is non-NULL.
+ new_quantity->add_flags(BIGINT_KEEP_PREC);
}
else if (commodity_) {
commodity().add_flags(comm_flags);
- if (quantity->prec > commodity().precision())
- commodity().set_precision(quantity->prec);
+ if (new_quantity->prec > commodity().precision())
+ commodity().set_precision(new_quantity->prec);
}
- // Now we have the final number. Remove commas and periods, if
- // necessary.
+ // Now we have the final number. Remove commas and periods, if necessary.
if (last_comma != string::npos || last_period != string::npos) {
string::size_type len = quant.length();
@@ -1021,27 +1074,28 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
}
*t = '\0';
- mpq_set_str(MP(quantity), buf.get(), 10);
- mpz_ui_pow_ui(temp, 10, quantity->prec);
+ mpq_set_str(MP(new_quantity.get()), buf.get(), 10);
+ mpz_ui_pow_ui(temp, 10, new_quantity->prec);
mpq_set_z(tempq, temp);
- mpq_div(MP(quantity), MP(quantity), tempq);
+ mpq_div(MP(new_quantity.get()), MP(new_quantity.get()), tempq);
IF_DEBUG("amount.parse") {
- char * buf = mpq_get_str(NULL, 10, MP(quantity));
+ char * buf = mpq_get_str(NULL, 10, MP(new_quantity.get()));
DEBUG("amount.parse", "Rational parsed = " << buf);
std::free(buf);
}
} else {
- mpq_set_str(MP(quantity), quant.c_str(), 10);
+ mpq_set_str(MP(new_quantity.get()), quant.c_str(), 10);
}
if (negative)
- in_place_negate();
+ mpq_neg(MP(new_quantity.get()), MP(new_quantity.get()));
- if (! flags.has_flags(PARSE_NO_REDUCE))
- in_place_reduce();
+ new_quantity->refc++;
+ quantity = new_quantity.release();
- safe_holder.release(); // `this->quantity' owns the pointer
+ if (! flags.has_flags(PARSE_NO_REDUCE))
+ in_place_reduce(); // will not throw an exception
VERIFY(valid());
diff --git a/src/compare.cc b/src/compare.cc
index 65e6a1e3..36884a46 100644
--- a/src/compare.cc
+++ b/src/compare.cc
@@ -44,8 +44,7 @@ void push_sort_value(std::list<sort_value_t>& sort_values,
if (node->kind == expr_t::op_t::O_CONS) {
push_sort_value(sort_values, node->left(), scope);
push_sort_value(sort_values, node->right(), scope);
- }
- else {
+ } else {
bool inverted = false;
if (node->kind == expr_t::op_t::O_NEG) {
diff --git a/src/expr.cc b/src/expr.cc
index aa9a7b0e..c59f8a57 100644
--- a/src/expr.cc
+++ b/src/expr.cc
@@ -64,6 +64,29 @@ value_t expr_t::real_calc(scope_t& scope)
if (locus) {
add_error_context(_("While evaluating value expression:"));
add_error_context(op_context(ptr, locus));
+
+ if (SHOW_INFO()) {
+ add_error_context(_("The value expression tree was:"));
+ std::ostringstream buf;
+ ptr->dump(buf, 0);
+
+ std::istringstream in(buf.str());
+ std::ostringstream out;
+ char linebuf[1024];
+ bool first = true;
+ while (in.good() && ! in.eof()) {
+ in.getline(linebuf, 1023);
+ std::streamsize len = in.gcount();
+ if (len > 0) {
+ if (first)
+ first = false;
+ else
+ out << '\n';
+ out << " " << linebuf;
+ }
+ }
+ add_error_context(out.str());
+ }
}
throw;
}
diff --git a/src/format.cc b/src/format.cc
index d949c350..b93a42a4 100644
--- a/src/format.cc
+++ b/src/format.cc
@@ -263,12 +263,15 @@ format_t::element_t * format_t::parse_elements(const string& fmt,
args3_node->set_left(call1_node);
args3_node->set_right(args2_node);
+ expr_t::ptr_op_t seq1_node(new expr_t::op_t(expr_t::op_t::O_SEQ));
+ seq1_node->set_left(args3_node);
+
expr_t::ptr_op_t justify_node(new expr_t::op_t(expr_t::op_t::IDENT));
justify_node->set_ident("justify");
expr_t::ptr_op_t call2_node(new expr_t::op_t(expr_t::op_t::O_CALL));
call2_node->set_left(justify_node);
- call2_node->set_right(args3_node);
+ call2_node->set_right(seq1_node);
string prev_expr = boost::get<expr_t>(current->data).text();
@@ -280,9 +283,12 @@ format_t::element_t * format_t::parse_elements(const string& fmt,
args4_node->set_left(call2_node);
args4_node->set_right(colorize_op);
+ expr_t::ptr_op_t seq2_node(new expr_t::op_t(expr_t::op_t::O_SEQ));
+ seq2_node->set_left(args4_node);
+
expr_t::ptr_op_t call3_node(new expr_t::op_t(expr_t::op_t::O_CALL));
call3_node->set_left(ansify_if_node);
- call3_node->set_right(args4_node);
+ call3_node->set_right(seq2_node);
current->data = expr_t(call3_node);
} else {
diff --git a/src/op.cc b/src/op.cc
index c4925143..2815ac1a 100644
--- a/src/op.cc
+++ b/src/op.cc
@@ -118,7 +118,8 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
// directly, so we create an empty call_scope_t to reflect the scope for
// this implicit call.
call_scope_t call_args(scope);
- result = left()->calc(call_args, locus, depth + 1);
+ result = left()->compile(call_args, depth + 1)
+ ->calc(call_args, locus, depth + 1);
break;
}
@@ -135,7 +136,6 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
}
case O_DEFINE: {
- symbol_scope_t local_scope;
call_scope_t& call_args(downcast<call_scope_t>(scope));
std::size_t args_count = call_args.size();
std::size_t args_index = 0;
@@ -152,38 +152,32 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
if (! varname->is_ident())
throw_(calc_error, _("Invalid function definition"));
else if (args_index == args_count)
- local_scope.define(symbol_t::FUNCTION, varname->as_ident(),
- wrap_value(false));
+ scope.define(symbol_t::FUNCTION, varname->as_ident(),
+ wrap_value(false));
else
- local_scope.define(symbol_t::FUNCTION, varname->as_ident(),
- wrap_value(call_args[args_index++]));
+ scope.define(symbol_t::FUNCTION, varname->as_ident(),
+ wrap_value(call_args[args_index++]));
}
if (args_index < args_count)
throw_(calc_error,
_("Too many arguments in function call (saw %1)") << args_count);
- result = right()->compile(local_scope, depth + 1)
- ->calc(local_scope, locus, depth + 1);
+ result = right()->calc(scope, locus, depth + 1);
break;
}
case O_LOOKUP:
- if (left()->is_ident() &&
- left()->left() && left()->left()->is_function()) {
- call_scope_t call_args(scope);
- if (value_t obj = left()->left()->as_function()(call_args)) {
- if (obj.is_scope()) {
- if (obj.as_scope() == NULL) {
- throw_(calc_error,
- _("Left operand of . operator is NULL"));
- } else {
- scope_t& objscope(*obj.as_scope());
- if (ptr_op_t member =
- objscope.lookup(symbol_t::FUNCTION, right()->as_ident())) {
- result = member->calc(objscope, NULL, depth + 1);
- break;
- }
+ if (value_t obj = left()->calc(scope, locus, depth + 1)) {
+ if (obj.is_scope()) {
+ if (obj.as_scope() == NULL) {
+ throw_(calc_error, _("Left operand of . operator is NULL"));
+ } else {
+ scope_t& objscope(*obj.as_scope());
+ if (ptr_op_t member =
+ objscope.lookup(symbol_t::FUNCTION, right()->as_ident())) {
+ result = member->calc(objscope, NULL, depth + 1);
+ break;
}
}
}
@@ -321,20 +315,28 @@ value_t expr_t::op_t::calc(scope_t& scope, ptr_op_t * locus, const int depth)
break;
case O_SEQ: {
- left()->calc(scope, locus, depth + 1);
- assert(has_right());
-
- ptr_op_t next = right();
- while (next) {
- ptr_op_t value_op;
- if (next->kind == O_SEQ) {
- value_op = next->left();
- next = next->right();
- } else {
- value_op = next;
- next = NULL;
+ symbol_scope_t seq_scope(scope);
+
+ // An O_SEQ is very similar to an O_CONS except that only the last result
+ // value in the series is kept. O_CONS builds up a list.
+ //
+ // Another feature of O_SEQ is that it pushes a new symbol scope onto the
+ // stack.
+ result = left()->calc(seq_scope, locus, depth + 1);
+
+ if (has_right()) {
+ ptr_op_t next = right();
+ while (next) {
+ ptr_op_t value_op;
+ if (next->kind == O_SEQ) {
+ value_op = next->left();
+ next = next->right();
+ } else {
+ value_op = next;
+ next = NULL;
+ }
+ result = value_op->calc(seq_scope, locus, depth + 1);
}
- result = value_op->calc(scope, locus, depth + 1);
}
break;
}
@@ -394,13 +396,14 @@ namespace {
if (op->left()->print(out, context))
found = true;
- assert(op->has_right());
- out << "; ";
+ if (op->has_right()) {
+ out << "; ";
- if (op->right()->kind == expr_t::op_t::O_CONS)
- found = print_cons(out, op->right(), context);
- else if (op->right()->print(out, context))
- found = true;
+ if (op->right()->kind == expr_t::op_t::O_CONS)
+ found = print_cons(out, op->right(), context);
+ else if (op->right()->print(out, context))
+ found = true;
+ }
return found;
}
@@ -563,9 +566,7 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const
break;
case O_CONS:
- out << "(";
found = print_cons(out, this, context);
- out << ")";
break;
case O_SEQ:
@@ -594,7 +595,7 @@ bool expr_t::op_t::print(std::ostream& out, const context_t& context) const
if (left() && left()->print(out, context))
found = true;
if (has_right()) {
- if (right()->kind == O_CONS) {
+ if (right()->kind == O_SEQ) {
if (right()->print(out, context))
found = true;
} else {
diff --git a/src/op.h b/src/op.h
index 48d167b7..347eac1d 100644
--- a/src/op.h
+++ b/src/op.h
@@ -243,9 +243,6 @@ private:
op->release();
}
- static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL,
- ptr_op_t _right = NULL);
-
ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const {
ptr_op_t node(new_node(kind, _left, _right));
if (kind < TERMINALS)
@@ -254,6 +251,9 @@ private:
}
public:
+ static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL,
+ ptr_op_t _right = NULL);
+
ptr_op_t compile(scope_t& scope, const int depth = 0);
value_t calc(scope_t& scope, ptr_op_t * locus = NULL,
const int depth = 0);
diff --git a/src/parser.cc b/src/parser.cc
index d94cf37f..ef778411 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -82,7 +82,7 @@ expr_t::parser_t::parse_value_term(std::istream& in,
if (node->kind == op_t::O_CONS) {
ptr_op_t prev(node);
- node = new op_t(op_t::O_CONS);
+ node = new op_t(op_t::O_SEQ);
node->set_left(prev);
}
break;
diff --git a/src/post.cc b/src/post.cc
index 24705323..7dd0c9b2 100644
--- a/src/post.cc
+++ b/src/post.cc
@@ -34,6 +34,7 @@
#include "post.h"
#include "xact.h"
#include "account.h"
+#include "journal.h"
#include "interactive.h"
#include "unistring.h"
#include "format.h"
@@ -212,13 +213,47 @@ namespace {
value_t get_account(call_scope_t& scope)
{
- in_context_t<post_t> env(scope, "&l");
+ in_context_t<post_t> env(scope, "&v");
+
+ string name;
- string name = env->reported_account()->fullname();
+ if (env.has(0)) {
+ if (env.value_at(0).is_long()) {
+ if (env.get<long>(0) > 2)
+ name = format_t::truncate(env->reported_account()->fullname(),
+ env.get<long>(0) - 2,
+ 2 /* account_abbrev_length */);
+ else
+ name = env->reported_account()->fullname();
+ } else {
+ account_t * account = NULL;
+ account_t * master = env->account;
+ while (master->parent)
+ master = master->parent;
+
+ if (env.value_at(0).is_string()) {
+ name = env.get<string>(0);
+ account = master->find_account(name, false);
+ }
+ else if (env.value_at(0).is_mask()) {
+ name = env.get<mask_t>(0).str();
+ account = master->find_account_re(name);
+ }
+ else {
+ throw_(std::runtime_error,
+ _("Expected string or mask for argument 1, but received %1")
+ << env.value_at(0).label());
+ }
- if (env.has(0) && env.get<long>(0) > 2)
- name = format_t::truncate(name, env.get<long>(0) - 2,
- 2 /* account_abbrev_length */);
+ if (! account)
+ throw_(std::runtime_error,
+ _("Could not find an account matching ") << env.value_at(0));
+ else
+ return value_t(static_cast<scope_t *>(account));
+ }
+ } else {
+ name = env->reported_account()->fullname();
+ }
if (env->has_flags(POST_VIRTUAL)) {
if (env->must_balance())
@@ -233,36 +268,6 @@ namespace {
return string_value(post.reported_account()->name);
}
- value_t get_account_amount(call_scope_t& scope)
- {
- in_context_t<post_t> env(scope, "&v");
-
- account_t * account = NULL;
- if (env.has(0)) {
- account_t * master = env->account;
- while (master->parent)
- master = master->parent;
-
- if (env.value_at(0).is_string())
- account = master->find_account(env.get<string>(0), false);
- else if (env.value_at(0).is_mask())
- account = master->find_account_re(env.get<mask_t>(0).str());
- } else {
- account = env->reported_account();
- }
-
- if (! account)
- throw_(std::runtime_error, _("Cannot locate referenced account"));
-
- DEBUG("post.account_amount", "Found account: " << account->fullname());
-
- value_t total = account->amount();
- if (total.is_null())
- return 0L;
- else
- return total.simplified();
- }
-
value_t get_account_depth(post_t& post) {
return long(post.reported_account()->depth);
}
@@ -289,8 +294,6 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
return WRAP_FUNCTOR(get_wrapper<&get_amount>);
else if (name == "account")
return WRAP_FUNCTOR(get_account);
- else if (name == "account_amount")
- return WRAP_FUNCTOR(get_account_amount);
else if (name == "account_base")
return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
break;
diff --git a/src/predicate.cc b/src/predicate.cc
new file mode 100644
index 00000000..4da4decf
--- /dev/null
+++ b/src/predicate.cc
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2003-2009, 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 "predicate.h"
+#include "query.h"
+#include "op.h"
+
+namespace ledger {
+
+predicate_t::predicate_t(const query_t& other)
+ : expr_t(other), what_to_keep(other.what_to_keep)
+{
+ TRACE_CTOR(predicate_t, "query_t");
+}
+
+} // namespace ledger
diff --git a/src/predicate.h b/src/predicate.h
index 17a2d36a..943b5a12 100644
--- a/src/predicate.h
+++ b/src/predicate.h
@@ -48,6 +48,8 @@
namespace ledger {
+class query_t;
+
class predicate_t : public expr_t
{
public:
@@ -61,6 +63,7 @@ public:
: expr_t(other), what_to_keep(other.what_to_keep) {
TRACE_CTOR(predicate_t, "copy");
}
+ predicate_t(const query_t& other);
predicate_t(const string& str, const keep_details_t& _what_to_keep,
const parse_flags_t& flags = PARSE_DEFAULT)
diff --git a/src/query.cc b/src/query.cc
index 98a3de1d..e48e65b5 100644
--- a/src/query.cc
+++ b/src/query.cc
@@ -307,15 +307,14 @@ query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_contex
throw_(parse_error,
_("Metadata equality operator not followed by term"));
- expr_t::ptr_op_t cons = new expr_t::op_t(expr_t::op_t::O_CONS);
-
expr_t::ptr_op_t arg2 = new expr_t::op_t(expr_t::op_t::VALUE);
assert(tok.value);
arg2->set_value(mask_t(*tok.value));
- cons->set_left(arg1);
- cons->set_right(arg2);
- node->set_right(cons);
+ node->set_right(expr_t::op_t::new_node
+ (expr_t::op_t::O_SEQ,
+ expr_t::op_t::new_node
+ (expr_t::op_t::O_CONS, arg1, arg2)));
} else {
node->set_right(arg1);
}
diff --git a/src/query.h b/src/query.h
index e64588ad..e3545396 100644
--- a/src/query.h
+++ b/src/query.h
@@ -254,11 +254,19 @@ public:
: predicate_t(other) {
TRACE_CTOR(query_t, "copy");
}
-
- query_t(const value_t& args,
+ query_t(const string& arg,
const keep_details_t& _what_to_keep = keep_details_t())
: predicate_t(_what_to_keep) {
TRACE_CTOR(query_t, "string, keep_details_t");
+ if (! arg.empty()) {
+ value_t temp(string_value(arg));
+ parse_args(temp.to_sequence());
+ }
+ }
+ query_t(const value_t& args,
+ const keep_details_t& _what_to_keep = keep_details_t())
+ : predicate_t(_what_to_keep) {
+ TRACE_CTOR(query_t, "value_t, keep_details_t");
if (! args.empty())
parse_args(args);
}
diff --git a/src/report.cc b/src/report.cc
index 24f0c054..e05b4bc1 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -315,31 +315,6 @@ value_t report_t::fn_price(call_scope_t& scope)
return args.value_at(0).price();
}
-value_t report_t::fn_account_total(call_scope_t& args)
-{
- account_t * acct = NULL;
- string name;
- if (args[0].is_string()) {
- name = args[0].as_string();
- acct = session.journal->find_account(name, false);
- }
- else if (args[0].is_mask()) {
- name = args[0].as_mask().str();
- acct = session.journal->find_account_re(name);
- }
- else {
- throw_(std::runtime_error,
- _("Expected string or mask for argument 1, but received %1")
- << args[0].label());
- }
-
- if (! acct)
- throw_(std::runtime_error,
- _("Could not find an account matching ") << name);
-
- return acct->amount();
-}
-
value_t report_t::fn_lot_date(call_scope_t& scope)
{
interactive_t args(scope, "v");
@@ -739,8 +714,6 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return MAKE_FUNCTOR(report_t::fn_ansify_if);
else if (is_eq(p, "abs"))
return MAKE_FUNCTOR(report_t::fn_abs);
- else if (is_eq(p, "account_total"))
- return MAKE_FUNCTOR(report_t::fn_account_total);
break;
case 'b':
diff --git a/src/report.h b/src/report.h
index 515dbd26..38b2b07e 100644
--- a/src/report.h
+++ b/src/report.h
@@ -151,7 +151,6 @@ public:
value_t fn_ansify_if(call_scope_t& scope);
value_t fn_percent(call_scope_t& scope);
value_t fn_price(call_scope_t& scope);
- value_t fn_account_total(call_scope_t& scope);
value_t fn_lot_date(call_scope_t& scope);
value_t fn_lot_price(call_scope_t& scope);
value_t fn_lot_tag(call_scope_t& scope);
diff --git a/src/textual.cc b/src/textual.cc
index 37c38e55..1d0d7998 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -36,6 +36,7 @@
#include "post.h"
#include "account.h"
#include "option.h"
+#include "query.h"
#include "pstream.h"
#include "pool.h"
#include "session.h"
@@ -119,13 +120,13 @@ namespace {
void period_xact_directive(char * line);
void xact_directive(char * line, std::streamsize len);
void include_directive(char * line);
- void account_directive(char * line);
+ void master_account_directive(char * line);
void end_directive(char * line);
void alias_directive(char * line);
void tag_directive(char * line);
void pop_directive(char * line);
void define_directive(char * line);
- void general_directive(char * line);
+ bool general_directive(char * line);
post_t * parse_post(char * line,
std::streamsize len,
@@ -354,51 +355,53 @@ void instance_t::read_next_directive()
period_xact_directive(line);
break;
-#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);
- 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
- year_directive(line);
- break;
-
case '@':
case '!':
line++;
// fall through...
default: // some other directive
- general_directive(line);
+ 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);
+ 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
+ year_directive(line);
+ break;
+ }
+ }
break;
}
}
@@ -506,8 +509,8 @@ void instance_t::automated_xact_directive(char * line)
}
std::auto_ptr<auto_xact_t> ae
- (new auto_xact_t(predicate_t(skip_ws(line + 1),
- keep_details_t(true, true, true))));
+ (new auto_xact_t(query_t(string(skip_ws(line + 1)),
+ keep_details_t(true, true, true))));
reveal_context = false;
@@ -606,13 +609,27 @@ void instance_t::xact_directive(char * line, std::streamsize len)
void instance_t::include_directive(char * line)
{
- path filename(line);
+ path filename;
+
+ if (line[0] != '/' && line[0] != '\\' && line[0] != '~') {
+ string::size_type pos = pathname.string().rfind('/');
+ if (pos == string::npos)
+ pos = pathname.string().rfind('\\');
+ if (pos != string::npos)
+ filename = path(string(pathname.string(), 0, pos + 1)) / line;
+ } else {
+ filename = line;
+ }
filename = resolve_path(filename);
DEBUG("textual.include", "Line " << linenum << ": " <<
"Including path '" << filename << "'");
+ if (! exists(filename))
+ throw_(std::runtime_error,
+ _("File to include was not found: '%1'" << filename));
+
ifstream stream(filename);
instance_t instance(account_stack, tag_stack,
@@ -627,7 +644,7 @@ void instance_t::include_directive(char * line)
count += instance.count;
}
-void instance_t::account_directive(char * line)
+void instance_t::master_account_directive(char * line)
{
if (account_t * acct = account_stack.front()->find_account(line))
account_stack.push_front(acct);
@@ -637,9 +654,9 @@ void instance_t::account_directive(char * line)
void instance_t::end_directive(char *)
{
- if (account_stack.empty())
+ if (account_stack.size() <= 1)
throw_(std::runtime_error,
- _("'end' directive found, but no account currently active"));
+ _("'end' directive found, but no master account currently active"));
else
account_stack.pop_back();
}
@@ -685,10 +702,14 @@ void instance_t::define_directive(char * line)
def.compile(scope); // causes definitions to be established
}
-void instance_t::general_directive(char * line)
+bool instance_t::general_directive(char * line)
{
- char * p = line;
- char * arg = next_element(line);
+ char buf[8192];
+
+ std::strcpy(buf, line);
+
+ char * p = buf;
+ char * arg = next_element(buf);
if (*p == '@' || *p == '!')
p++;
@@ -696,47 +717,54 @@ void instance_t::general_directive(char * line)
switch (*p) {
case 'a':
if (std::strcmp(p, "account") == 0) {
- account_directive(arg);
- return;
+ master_account_directive(arg);
+ return true;
}
else if (std::strcmp(p, "alias") == 0) {
alias_directive(arg);
- return;
+ return true;
+ }
+ break;
+
+ case 'b':
+ if (std::strcmp(p, "bucket") == 0) {
+ default_account_directive(arg);
+ return true;
}
break;
case 'd':
- if (std::strcmp(p, "def") == 0) {
+ if (std::strcmp(p, "def") == 0 || std::strcmp(p, "define") == 0) {
define_directive(arg);
- return;
+ return true;
}
break;
case 'e':
if (std::strcmp(p, "end") == 0) {
end_directive(arg);
- return;
+ return true;
}
break;
case 'i':
if (std::strcmp(p, "include") == 0) {
include_directive(arg);
- return;
+ return true;
}
break;
case 'p':
if (std::strcmp(p, "pop") == 0) {
pop_directive(arg);
- return;
+ return true;
}
break;
case 't':
if (std::strcmp(p, "tag") == 0) {
tag_directive(arg);
- return;
+ return true;
}
break;
}
@@ -745,7 +773,10 @@ void instance_t::general_directive(char * line)
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,
@@ -966,8 +997,8 @@ post_t * instance_t::parse_post(char * line,
<< "POST assign: parsed amt = " << *post->assigned_amount);
amount_t& amt(*post->assigned_amount);
- value_t account_total(post->account->amount(false)
- .strip_annotations(keep_details_t()));
+ value_t account_total
+ (post->account->amount(false).strip_annotations(keep_details_t()));
DEBUG("post.assign",
"line " << linenum << ": " "account balance = " << account_total);
diff --git a/src/value.h b/src/value.h
index 94002bef..96a3078a 100644
--- a/src/value.h
+++ b/src/value.h
@@ -946,6 +946,8 @@ inline value_t string_value(const string& str = "") {
}
#define VALUE_OR_ZERO(val) ((val).is_null() ? value_t(0L) : (val))
+#define SIMPLIFIED_VALUE_OR_ZERO(val) \
+ ((val).is_null() ? value_t(0L) : (val).simplified())
inline value_t mask_value(const string& str) {
return value_t(mask_t(str));
diff --git a/src/xact.cc b/src/xact.cc
index f4331a29..561170bd 100644
--- a/src/xact.cc
+++ b/src/xact.cc
@@ -493,7 +493,9 @@ void auto_xact_t::extend_xact(xact_base_t& xact, bool post_handler)
amount_t amt;
assert(post->amount);
if (! post->amount.commodity()) {
- if (post_handler || initial_post->amount.is_null())
+ if ((post_handler &&
+ ! initial_post->has_flags(POST_CALCULATED)) ||
+ initial_post->amount.is_null())
continue;
amt = initial_post->amount * post->amount;
} else {
diff --git a/test/baseline/opt-actual.test b/test/baseline/opt-actual.test
index 1d9ddc89..8e7b432e 100644
--- a/test/baseline/opt-actual.test
+++ b/test/baseline/opt-actual.test
@@ -1,6 +1,6 @@
print --actual
<<<
-= account =~ /Books/
+= Books
Expenses:Taxes 0.05
Assets:Checking -0.05
diff --git a/test/regress/5A03CFC3.test b/test/regress/5A03CFC3.test
index a5a12af3..440ff960 100644
--- a/test/regress/5A03CFC3.test
+++ b/test/regress/5A03CFC3.test
@@ -1,6 +1,6 @@
bal assets
<<<
-= account =~ /^Income/
+= /^Income/
(Liabilities:Tithe) 0.12
~ Monthly
diff --git a/test/regress/727B2DF8.test b/test/regress/727B2DF8.test
index 8c29e1ff..a13e8292 100644
--- a/test/regress/727B2DF8.test
+++ b/test/regress/727B2DF8.test
@@ -2,7 +2,7 @@ reg --color --force-color
<<<
N $
-= account =~ /^Expenses:Books/
+= /^Expenses:Books/
(Liabilities:Taxes) -0.10
~ Monthly
diff --git a/test/regress/793F6BF0.test b/test/regress/793F6BF0.test
index 754ddca3..059bd9b6 100644
--- a/test/regress/793F6BF0.test
+++ b/test/regress/793F6BF0.test
@@ -2,7 +2,7 @@ entry 2009/03/15 book 10
<<<
N $
-= account =~ /^Expenses:Books/
+= /^Expenses:Books/
(Liabilities:Taxes) -0.10
~ Monthly
diff --git a/tools/Makefile.am b/tools/Makefile.am
index c0404606..8c042a94 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -43,6 +43,7 @@ libledger_expr_la_SOURCES = \
src/option.cc \
src/format.cc \
src/query.cc \
+ src/predicate.cc \
src/scope.cc \
src/interactive.cc \
src/expr.cc \
diff --git a/tools/push b/tools/push
index 2d8be6a2..206ec87c 100755
--- a/tools/push
+++ b/tools/push
@@ -9,3 +9,6 @@ git merge --no-ff next
git checkout next
git rebase master
git push
+git checkout master
+./acprep upload
+git checkout next