diff options
author | John Wiegley <johnw@newartisans.com> | 2009-11-10 12:22:19 -0500 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2009-11-10 12:22:19 -0500 |
commit | 5ffa987daf4d97c52066e4c28733d826d3726297 (patch) | |
tree | ab058dec32702ccce9d1f7cacacb5b5c157d6798 | |
parent | 9e07e61fadf7f3c9d1fd32a3805f6e97163eba15 (diff) | |
parent | 3f638d355c977fd5513ab1db380c5813599f3664 (diff) | |
download | fork-ledger-5ffa987daf4d97c52066e4c28733d826d3726297.tar.gz fork-ledger-5ffa987daf4d97c52066e4c28733d826d3726297.tar.bz2 fork-ledger-5ffa987daf4d97c52066e4c28733d826d3726297.zip |
Merge branch 'next'
-rw-r--r-- | src/account.cc | 4 | ||||
-rw-r--r-- | src/amount.cc | 156 | ||||
-rw-r--r-- | src/compare.cc | 3 | ||||
-rw-r--r-- | src/expr.cc | 23 | ||||
-rw-r--r-- | src/format.cc | 10 | ||||
-rw-r--r-- | src/op.cc | 91 | ||||
-rw-r--r-- | src/op.h | 6 | ||||
-rw-r--r-- | src/parser.cc | 2 | ||||
-rw-r--r-- | src/post.cc | 77 | ||||
-rw-r--r-- | src/predicate.cc | 46 | ||||
-rw-r--r-- | src/predicate.h | 3 | ||||
-rw-r--r-- | src/query.cc | 9 | ||||
-rw-r--r-- | src/query.h | 12 | ||||
-rw-r--r-- | src/report.cc | 27 | ||||
-rw-r--r-- | src/report.h | 1 | ||||
-rw-r--r-- | src/textual.cc | 155 | ||||
-rw-r--r-- | src/value.h | 2 | ||||
-rw-r--r-- | src/xact.cc | 4 | ||||
-rw-r--r-- | test/baseline/opt-actual.test | 2 | ||||
-rw-r--r-- | test/regress/5A03CFC3.test | 2 | ||||
-rw-r--r-- | test/regress/727B2DF8.test | 2 | ||||
-rw-r--r-- | test/regress/793F6BF0.test | 2 | ||||
-rw-r--r-- | tools/Makefile.am | 1 | ||||
-rwxr-xr-x | tools/push | 3 |
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 { @@ -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 { @@ -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 \ @@ -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 |