Improved the numerical parser for basic amounts

1,00,000 now causes an error, for example, whereas before the commas
were largely ignored.
This commit is contained in:
John Wiegley 2009-11-10 00:10:25 -05:00
parent 5f01659b1c
commit 687c71c71d

View file

@ -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, // exeception thrown by any of the function calls after this point,
// the destructor will never be called and the memory never freed. // 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) { if (quantity) {
quantity = new bigint_t; if (quantity->refc > 1)
safe_holder.reset(quantity); _release();
} else
else if (quantity->refc > 1) { new_quantity.reset(quantity);
_release(); quantity = NULL;
quantity = new bigint_t;
safe_holder.reset(quantity);
} }
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 // Create the commodity if has not already been seen, and update the
// precision if something greater was used for the quantity. // 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); commodity_ = current_pool->find_or_create(*commodity_, details);
} }
// Determine the precision of the amount, based on the usage of // Quickly scan through and verify the correctness of the amount's use of
// comma or period. // punctuation.
string::size_type last_comma = quant.rfind(','); precision_t decimal_offset = 0;
string::size_type last_period = quant.rfind('.'); 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) { bool no_more_commas = false;
comm_flags |= COMMODITY_STYLE_THOUSANDS; bool no_more_periods = false;
if (last_comma > last_period) { bool european_style = (commodity_t::european_by_default ||
comm_flags |= COMMODITY_STYLE_EUROPEAN; commodity().has_flags(COMMODITY_STYLE_EUROPEAN));
quantity->prec = static_cast<precision_t>(quant.length() -
last_comma - 1); new_quantity->prec = 0;
} else {
quantity->prec = static_cast<precision_t>(quant.length() - BOOST_REVERSE_FOREACH (const char& ch, quant) {
last_period - 1); 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)) { 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_) { else if (commodity_) {
commodity().add_flags(comm_flags); commodity().add_flags(comm_flags);
if (quantity->prec > commodity().precision()) if (new_quantity->prec > commodity().precision())
commodity().set_precision(quantity->prec); commodity().set_precision(new_quantity->prec);
} }
// Now we have the final number. Remove commas and periods, if // Now we have the final number. Remove commas and periods, if necessary.
// necessary.
if (last_comma != string::npos || last_period != string::npos) { if (last_comma != string::npos || last_period != string::npos) {
string::size_type len = quant.length(); string::size_type len = quant.length();
@ -1021,27 +1074,28 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
} }
*t = '\0'; *t = '\0';
mpq_set_str(MP(quantity), buf.get(), 10); mpq_set_str(MP(new_quantity.get()), buf.get(), 10);
mpz_ui_pow_ui(temp, 10, quantity->prec); mpz_ui_pow_ui(temp, 10, new_quantity->prec);
mpq_set_z(tempq, temp); 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") { 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); DEBUG("amount.parse", "Rational parsed = " << buf);
std::free(buf); std::free(buf);
} }
} else { } else {
mpq_set_str(MP(quantity), quant.c_str(), 10); mpq_set_str(MP(new_quantity.get()), quant.c_str(), 10);
} }
if (negative) if (negative)
in_place_negate(); mpq_neg(MP(new_quantity.get()), MP(new_quantity.get()));
new_quantity->refc++;
quantity = new_quantity.release();
if (! flags.has_flags(PARSE_NO_REDUCE)) if (! flags.has_flags(PARSE_NO_REDUCE))
in_place_reduce(); in_place_reduce(); // will not throw an exception
safe_holder.release(); // `this->quantity' owns the pointer
VERIFY(valid()); VERIFY(valid());