[intl] Implement NumberFormat v8 string input handling

Design Document:
https://docs.google.com/document/d/14zxGub6Os6nARzH6XstOZX05w2537sZo_ZSSlGjGpBM/edit#


Bug: v8:10776
Change-Id: I506eadcf70f6855a79601a7cb1ce47849cfc066a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3594118
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Commit-Queue: Frank Tang <ftang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81168}
This commit is contained in:
Frank Tang 2022-06-14 20:42:54 -07:00 committed by V8 LUCI CQ
parent b66d08241d
commit fe53fc04d2
11 changed files with 1210 additions and 250 deletions

View File

@ -85,15 +85,7 @@ BUILTIN(NumberFormatPrototypeFormatToParts) {
Handle<Object> x;
if (args.length() >= 2) {
Handle<Object> value = args.at(1);
if (FLAG_harmony_intl_number_format_v3) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, x,
Intl::ToIntlMathematicalValueAsNumberBigIntOrString(isolate, value));
} else {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, x,
Object::ToNumeric(isolate, value));
}
x = args.at(1);
} else {
x = isolate->factory()->nan_value();
}
@ -505,25 +497,8 @@ BUILTIN(NumberFormatInternalFormatNumber) {
// 3. If value is not provided, let value be undefined.
Handle<Object> value = args.atOrUndefined(isolate, 1);
// 4. Let x be ? ToNumeric(value).
Handle<Object> numeric_obj;
if (FLAG_harmony_intl_number_format_v3) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, numeric_obj,
Intl::ToIntlMathematicalValueAsNumberBigIntOrString(isolate, value));
} else {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, numeric_obj,
Object::ToNumeric(isolate, value));
}
icu::number::LocalizedNumberFormatter* icu_localized_number_formatter =
number_format->icu_number_formatter().raw();
CHECK_NOT_NULL(icu_localized_number_formatter);
// Return FormatNumber(nf, x).
RETURN_RESULT_OR_FAILURE(
isolate, JSNumberFormat::FormatNumeric(
isolate, *icu_localized_number_formatter, numeric_obj));
RETURN_RESULT_OR_FAILURE(isolate, JSNumberFormat::NumberFormatFunction(
isolate, number_format, value));
}
// Common code for NumberFormatPrototypeFormtRange(|ToParts)
@ -553,19 +528,7 @@ V8_WARN_UNUSED_RESULT Object NumberFormatRange(BuiltinArguments args,
factory->NewStringFromStaticChars("end"), end));
}
// 4. Let x be ? ToIntlMathematicalValue(start).
Handle<Object> x;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, x,
Intl::ToIntlMathematicalValueAsNumberBigIntOrString(isolate, start));
// 5. Let y be ? ToIntlMathematicalValue(end).
Handle<Object> y;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, y,
Intl::ToIntlMathematicalValueAsNumberBigIntOrString(isolate, end));
RETURN_RESULT_OR_FAILURE(isolate, F(isolate, nf, x, y));
RETURN_RESULT_OR_FAILURE(isolate, F(isolate, nf, start, end));
}
BUILTIN(NumberFormatPrototypeFormatRange) {

View File

@ -242,7 +242,8 @@ class BigInt : public BigIntBase {
Isolate* isolate, Handle<Object> number);
// ECMAScript's ToBigInt (throws for Number input)
static MaybeHandle<BigInt> FromObject(Isolate* isolate, Handle<Object> obj);
V8_EXPORT_PRIVATE static MaybeHandle<BigInt> FromObject(Isolate* isolate,
Handle<Object> obj);
class BodyDescriptor;

View File

@ -2891,28 +2891,6 @@ int32_t Intl::GetTimeZoneIndex(Isolate* isolate, Handle<String> identifier) {
UNREACHABLE();
}
// #sec-tointlmathematicalvalue
MaybeHandle<Object> Intl::ToIntlMathematicalValueAsNumberBigIntOrString(
Isolate* isolate, Handle<Object> input) {
if (input->IsNumber() || input->IsBigInt()) return input; // Shortcut.
// TODO(ftang) revisit the following after the resolution of
// https://github.com/tc39/proposal-intl-numberformat-v3/pull/82
if (input->IsOddball()) {
return Oddball::ToNumber(isolate, Handle<Oddball>::cast(input));
}
if (input->IsSymbol()) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kSymbolToNumber),
Object);
}
ASSIGN_RETURN_ON_EXCEPTION(
isolate, input,
JSReceiver::ToPrimitive(isolate, Handle<JSReceiver>::cast(input),
ToPrimitiveHint::kNumber),
Object);
if (input->IsString()) UNIMPLEMENTED();
return input;
}
Intl::FormatRangeSourceTracker::FormatRangeSourceTracker() {
start_[0] = start_[1] = limit_[0] = limit_[1] = 0;
}

View File

@ -367,12 +367,6 @@ class Intl {
// ecma402/#sec-coerceoptionstoobject
V8_WARN_UNUSED_RESULT static MaybeHandle<JSReceiver> CoerceOptionsToObject(
Isolate* isolate, Handle<Object> options, const char* service);
// #sec-tointlmathematicalvalue
// The implementation preserve the Object in String, BigInt or Number
V8_WARN_UNUSED_RESULT static MaybeHandle<Object>
ToIntlMathematicalValueAsNumberBigIntOrString(Isolate* isolate,
Handle<Object> input);
};
} // namespace internal

View File

@ -12,11 +12,13 @@
#include <string>
#include "src/execution/isolate.h"
#include "src/numbers/conversions.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-number-format-inl.h"
#include "src/objects/managed-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/option-utils.h"
#include "src/strings/char-predicates-inl.h"
#include "unicode/currunit.h"
#include "unicode/locid.h"
#include "unicode/numberformatter.h"
@ -937,9 +939,9 @@ Handle<JSObject> JSNumberFormat::ResolvedOptions(
Factory* factory = isolate->factory();
UErrorCode status = U_ZERO_ERROR;
icu::number::LocalizedNumberFormatter* icu_number_formatter =
icu::number::LocalizedNumberFormatter* fmt =
number_format->icu_number_formatter().raw();
icu::UnicodeString skeleton = icu_number_formatter->toSkeleton(status);
icu::UnicodeString skeleton = fmt->toSkeleton(status);
DCHECK(U_SUCCESS(status));
// 4. Let options be ! ObjectCreate(%ObjectPrototype%).
@ -1645,14 +1647,12 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::New(Isolate* isolate,
// 30. Set numberFormat.[[NegativePattern]] to
// stylePatterns.[[negativePattern]].
//
icu::number::LocalizedNumberFormatter icu_number_formatter =
settings.locale(icu_locale);
icu::number::LocalizedNumberFormatter fmt = settings.locale(icu_locale);
Handle<Managed<icu::number::LocalizedNumberFormatter>>
managed_number_formatter =
Managed<icu::number::LocalizedNumberFormatter>::FromRawPtr(
isolate, 0,
new icu::number::LocalizedNumberFormatter(icu_number_formatter));
isolate, 0, new icu::number::LocalizedNumberFormatter(fmt));
// Now all properties are ready, so we can allocate the result object.
Handle<JSNumberFormat> number_format = Handle<JSNumberFormat>::cast(
@ -1669,6 +1669,293 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::New(Isolate* isolate,
namespace {
// StrDecimalLiteral is a helper class which is only use in step 2a ("y < x")
// of PartitionNumberRangePattern while x and y are equal in double precision
// but maybe differ in highier precision. It is only used by
// IntlMathmeticalValue::IsLessThan to compare one value is less than the other.
// Both x and y are String following the syntax of StrDecimalLiteral
// which is (+|-)?((\d+(.\d*)?)|(.\d+))([eE](+|-)?\d+)?
class V8_NODISCARD StrDecimalLiteral {
public:
bool IsLessThan(const StrDecimalLiteral& y) const;
// Factory method
static StrDecimalLiteral From(Isolate* isolate, Handle<String> string);
private:
bool negative_;
double exp_value_;
int integer_start_pos_; // the position of the first digit in the integer
// part.
int integer_end_pos_; // 1 beyond the position of the last digit in the
// integer part.
int fraction_start_pos_; // the position of the first digit in the fraction
// part.
int fraction_end_pos_; // 1 beyond the position of the last digit in the
// fraction part.
Handle<String> str_;
StrDecimalLiteral();
int Digit(int index) const;
int length() const {
return integer_end_pos_ - integer_start_pos_ + fraction_end_pos_ -
fraction_start_pos_;
}
template <typename Char>
static StrDecimalLiteral Parse(const base::Vector<const Char>& src);
};
StrDecimalLiteral::StrDecimalLiteral()
: negative_(false),
exp_value_(0),
integer_start_pos_(0),
integer_end_pos_(0),
fraction_start_pos_(0),
fraction_end_pos_(0) {}
template <typename Char>
StrDecimalLiteral StrDecimalLiteral::Parse(
const base::Vector<const Char>& src) {
StrDecimalLiteral result;
if (src.length() == 0) return result;
int i = 0;
uint16_t ch = static_cast<uint16_t>(src.at(i));
// Scan the sign
if (ch == '-') {
result.negative_ = true;
i++;
} else if (ch == '+') {
i++;
}
// Scan the Integer part
// Skip leading '0'
for (; i < src.length() && src.at(i) == '0'; i++) {
}
result.integer_start_pos_ = i;
// Scan 0-9
for (; i < src.length(); i++) {
ch = static_cast<uint16_t>(src.at(i));
if (!('0' <= ch && ch <= '9')) break;
}
result.integer_end_pos_ = i;
// We normalize to value in the range [0.1, 1.0) x 10 ^ $exp_value_
// For example, if the input is 123.45e6 we will turn it into
// 123.45e6 exp_value_ = 3 at this point. Will add 6 to become 9 later.
// ^ ^
// | +------ integer_end_pos_
// +--------- integer_start_pos_
// So conceptually we know we have 0.123 x10^3 after parsed the "123." part.
result.exp_value_ =
static_cast<double>(result.integer_end_pos_ - result.integer_start_pos_);
// If there are no fractional nor exponment, then
if (i == src.length()) {
// Trim trailing '0' in Integer part.
while (result.integer_start_pos_ < result.integer_end_pos_ &&
src.at(result.integer_end_pos_ - 1) == '0') {
result.integer_end_pos_--;
}
// For example, if we have input as 1230000e4, we will now have
// 1230000e4 exp_value_ = 7
// ^ ^
// | +------ integer_end_pos_
// +--------- integer_start_pos_
// This part is unnecessary for correctness but to skip unnecessary later
// comparsion for performance.
// So conceptually we know we have 0.123 x10^7 after parsed "1230000".
return result;
}
if (src.at(i) == '.') {
i++;
// Scan the Fraction part
for (result.fraction_start_pos_ = i; i < src.length(); i++) {
ch = static_cast<uint16_t>(src.at(i));
if (!('0' <= ch && ch <= '9')) break;
}
result.fraction_end_pos_ = i;
// For example, if the input is 123.45e6 we will turn it into
// 123.45e6 exp_value_ = 3 at this point. Will add 6 to become 9 later.
// ^ ^^ ^
// | || +--- fraction_end_pos_
// | |+----- fraction_start_pos_
// | +------ integer_end_pos_
// +--------- integer_start_pos_
// So conceptually we know we have 0.123456 x10^3 after parsed "123.45".
}
// Scan Exponent
if (i < src.length()) {
ch = static_cast<uint16_t>(src.at(i));
if (ch == 'e' || ch == 'E') {
i++;
if (i < src.length()) {
double exponent_sign = 1.0;
ch = static_cast<uint16_t>(src.at(i));
if (ch == '-') {
exponent_sign = -1.0;
i++;
} else if (ch == '+') {
i++;
}
double scan_exponent = 0;
for (; i < src.length(); i++) {
ch = src.at(i);
if (!('0' <= ch && ch <= '9')) {
break;
}
scan_exponent *= 10;
scan_exponent += ch - '0';
}
CHECK_EQ(i, src.length());
result.exp_value_ += exponent_sign * scan_exponent;
}
}
}
// If we do not have Integer part, adjust exp_value_ by removing leading '0'
// in Fraction part.
if (result.integer_start_pos_ == result.integer_end_pos_) {
while (result.fraction_start_pos_ < result.fraction_end_pos_ &&
src.at(result.fraction_start_pos_) == '0') {
result.fraction_start_pos_++;
result.exp_value_--;
}
// For example, if the input is .00045e9 we will turn it into
// .00045e9 exp_value_ = 6
// ^ ^ ^
// | | +--- fraction_end_pos_
// | +----- fraction_start_pos_
// +--------- integer_end_pos_
// +--------- integer_start_pos_
// So conceptually we know we have 0.456 x10^6 instead of 0.000456 x 10^9
}
// Trim trialing '0' in Fraction part
// For example, if the input is .0004500e9 the following while loop will
// turn it into
// .0004500e9 exp_value_ = 6
// ^ ^ ^
// | | +--- fraction_end_pos_
// | +----- fraction_start_pos_
// +--------- integer_end_pos_
// +--------- integer_start_pos_
// So conceptually we know we have 0.456 x 10^6 instead of 0.45600 x 10^6.
while (result.fraction_start_pos_ < result.fraction_end_pos_ &&
src.at(result.fraction_end_pos_ - 1) == '0') {
result.fraction_end_pos_--;
}
// If there are no fraction in the input, trim trailing '0' in Integer part.
if (result.fraction_start_pos_ == result.fraction_end_pos_) {
// For example, if the input is .0004500e9 the following while loop will
// turn it into
// 123000e9 exp_value_ = 15
// ^ ^ ^
// | | +--- fraction_end_pos_
// | | +--- fraction_start_pos_
// | +------ integer_end_pos_
// +--------- integer_start_pos_
// So conceptually we know we have 0.123 x 10^15 instead of 0.123000 x 10^15
while (result.integer_start_pos_ < result.integer_end_pos_ &&
src.at(result.integer_end_pos_ - 1) == '0') {
result.integer_end_pos_--;
}
}
DCHECK_EQ(i, src.length());
return result;
}
StrDecimalLiteral StrDecimalLiteral::From(Isolate* isolate,
Handle<String> string) {
string = String::Flatten(isolate, string);
DisallowGarbageCollection no_gc;
String::FlatContent flat = string->GetFlatContent(no_gc);
StrDecimalLiteral result;
if (flat.IsOneByte()) {
result = Parse(flat.ToOneByteVector());
} else {
result = Parse(flat.ToUC16Vector());
}
result.str_ = string;
return result;
}
int StrDecimalLiteral::Digit(int index) const {
if (index < integer_end_pos_ - integer_start_pos_) {
return str_->Get(index + integer_start_pos_) - '0';
}
if (index < length()) {
return str_->Get(index - (integer_end_pos_ - integer_start_pos_) +
fraction_start_pos_) -
'0';
}
return 0;
}
bool StrDecimalLiteral::IsLessThan(const StrDecimalLiteral& y) const {
if (negative_ != y.negative_) {
return negative_;
}
if (exp_value_ != y.exp_value_) {
return exp_value_ < y.exp_value_ != negative_;
}
int length = std::max(y.length(), this->length());
for (int i = 0; i < length; i++) {
int dx = Digit(i);
int dy = y.Digit(i);
if (dx != dy) {
return dx < dy != negative_;
}
}
return false;
}
icu::number::FormattedNumber FormatDecimalString(
Isolate* isolate,
const icu::number::LocalizedNumberFormatter& number_format,
Handle<String> string, UErrorCode& status) {
string = String::Flatten(isolate, string);
DisallowGarbageCollection no_gc;
const String::FlatContent& flat = string->GetFlatContent(no_gc);
int32_t length = string->length();
if (flat.IsOneByte()) {
const char* char_buffer =
reinterpret_cast<const char*>(flat.ToOneByteVector().begin());
return number_format.formatDecimal({char_buffer, length}, status);
}
return number_format.formatDecimal({string->ToCString().get(), length},
status);
}
} // namespace
bool IntlMathematicalValue::IsNegative() const {
if (value_->IsBigInt()) {
return Handle<BigInt>::cast(value_)->IsNegative();
}
return approx_ < 0;
}
bool IntlMathematicalValue::IsMinusZero() const {
return ::v8::internal::IsMinusZero(approx_);
}
bool IntlMathematicalValue::IsNaN() const { return value_->IsNaN(); }
bool IntlMathematicalValue::IsInfinity() const {
return value_->IsNumber() && std::isinf(approx_);
}
MaybeHandle<String> IntlMathematicalValue::ToString(Isolate* isolate) const {
Handle<String> string;
if (value_->IsNumber()) {
return isolate->factory()->NumberToString(value_);
}
if (value_->IsBigInt()) {
return BigInt::ToString(isolate, Handle<BigInt>::cast(value_));
}
DCHECK(value_->IsString());
return Handle<String>::cast(value_);
}
namespace {
Maybe<icu::number::FormattedNumber> IcuFormatNumber(
Isolate* isolate,
const icu::number::LocalizedNumberFormatter& number_format,
@ -1729,38 +2016,268 @@ Maybe<icu::number::FormattedNumber> IcuFormatNumber(
return Just(std::move(formatted));
}
Maybe<icu::Formattable> ToFormattable(Isolate* isolate, Handle<Object> obj,
const char* field) {
if (obj->IsBigInt()) {
Handle<BigInt> big_int = Handle<BigInt>::cast(obj);
Handle<String> big_int_string;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, big_int_string,
BigInt::ToString(isolate, big_int),
Nothing<icu::Formattable>());
big_int_string = String::Flatten(isolate, big_int_string);
{
DisallowGarbageCollection no_gc;
const String::FlatContent& flat = big_int_string->GetFlatContent(no_gc);
int32_t length = big_int_string->length();
DCHECK(flat.IsOneByte());
const char* char_buffer =
reinterpret_cast<const char*>(flat.ToOneByteVector().begin());
UErrorCode status = U_ZERO_ERROR;
icu::Formattable result({char_buffer, length}, status);
if (U_SUCCESS(status)) return Just(result);
}
THROW_NEW_ERROR_RETURN_VALUE(isolate,
NewTypeError(MessageTemplate::kIcuError),
Nothing<icu::Formattable>());
}
// TODO(ftang) Handle the case of IsString after the resolution of
// https://github.com/tc39/proposal-intl-numberformat-v3/pull/82
} // namespace
// FormatRange(|ToParts) does not allow NaN
DCHECK(!obj->IsNaN());
return Just(icu::Formattable(obj->Number()));
Maybe<icu::number::FormattedNumber> IntlMathematicalValue::FormatNumeric(
Isolate* isolate,
const icu::number::LocalizedNumberFormatter& number_format,
const IntlMathematicalValue& x) {
if (x.value_->IsString()) {
Handle<String> string;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, string, x.ToString(isolate),
Nothing<icu::number::FormattedNumber>());
UErrorCode status = U_ZERO_ERROR;
icu::number::FormattedNumber result =
FormatDecimalString(isolate, number_format, string, status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR_RETURN_VALUE(isolate,
NewTypeError(MessageTemplate::kIcuError),
Nothing<icu::number::FormattedNumber>());
}
return Just(std::move(result));
}
CHECK(x.value_->IsNumber() || x.value_->IsBigInt());
return IcuFormatNumber(isolate, number_format, x.value_);
}
Maybe<icu::number::FormattedNumberRange> IntlMathematicalValue::FormatRange(
Isolate* isolate,
const icu::number::LocalizedNumberRangeFormatter& number_range_format,
const IntlMathematicalValue& x, const IntlMathematicalValue& y) {
icu::Formattable x_formatable;
MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, x_formatable, x.ToFormattable(isolate),
Nothing<icu::number::FormattedNumberRange>());
icu::Formattable y_formatable;
MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, y_formatable, y.ToFormattable(isolate),
Nothing<icu::number::FormattedNumberRange>());
UErrorCode status = U_ZERO_ERROR;
icu::number::FormattedNumberRange result =
number_range_format.formatFormattableRange(x_formatable, y_formatable,
status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR_RETURN_VALUE(isolate,
NewTypeError(MessageTemplate::kIcuError),
Nothing<icu::number::FormattedNumberRange>());
}
return Just(std::move(result));
}
bool IntlMathematicalValue::IsLessThan(Isolate* isolate,
const IntlMathematicalValue& y) const {
DCHECK(IsMathematicalValue());
DCHECK(y.IsMathematicalValue());
if (approx_ != y.approx_) {
return approx_ < y.approx_;
}
DCHECK(value_->IsNumber() || value_->IsString() || value_->IsBigInt());
DCHECK(y.value_->IsNumber() || y.value_->IsString() || y.value_->IsBigInt());
if (value_->IsNumber() && y.value_->IsNumber()) {
// If both are Number, return false since we have no more precision than
// approx_. So we have to conclude they are equal and therefore false for
// IsLessThan.
return false;
}
if (value_->IsBigInt()) {
Handle<BigInt> x_bigint = Handle<BigInt>::cast(value_);
if (y.value_->IsBigInt()) {
return ComparisonResult::kLessThan ==
BigInt::CompareToBigInt(x_bigint, Handle<BigInt>::cast(y.value_));
}
if (y.value_->IsNumber()) {
return ComparisonResult::kLessThan ==
BigInt::CompareToNumber(x_bigint, y.value_);
}
}
if (value_->IsNumber() && y.value_->IsBigInt()) {
return ComparisonResult::kGreaterThan ==
BigInt::CompareToNumber(Handle<BigInt>::cast(y.value_), value_);
}
// Both value_ and y.value_ should be String now.
Handle<String> x_str = ToString(isolate).ToHandleChecked();
Handle<String> y_str = y.ToString(isolate).ToHandleChecked();
return StrDecimalLiteral::From(isolate, x_str)
.IsLessThan(StrDecimalLiteral::From(isolate, y_str));
}
namespace {
// Return the index of the end of leading white space or line terminator
// and the index of the start of trailing white space or line terminator.
template <typename Char>
std::pair<int, int> FindLeadingAndTrailingWhiteSpaceOrLineTerminator(
const base::Vector<const Char>& src) {
size_t leading_end = 0;
// Find the length of leading StrWhiteSpaceChar.
while (leading_end < src.size() &&
IsWhiteSpaceOrLineTerminator(
static_cast<uint16_t>(src.at(leading_end)))) {
leading_end++;
}
size_t trailing_start = src.size();
// Find the start of the trailing StrWhiteSpaceChar
while (trailing_start > leading_end &&
IsWhiteSpaceOrLineTerminator(
static_cast<uint16_t>(src.at(trailing_start - 1)))) {
trailing_start--;
}
return std::make_pair(leading_end, trailing_start);
}
Handle<String> TrimWhiteSpaceOrLineTerminator(Isolate* isolate,
Handle<String> string) {
string = String::Flatten(isolate, string);
std::pair<int, int> whitespace_offsets;
{
DisallowGarbageCollection no_gc;
String::FlatContent flat = string->GetFlatContent(no_gc);
if (flat.IsOneByte()) {
whitespace_offsets = FindLeadingAndTrailingWhiteSpaceOrLineTerminator(
flat.ToOneByteVector());
} else {
whitespace_offsets =
FindLeadingAndTrailingWhiteSpaceOrLineTerminator(flat.ToUC16Vector());
}
}
if (whitespace_offsets.first == 0 &&
string->length() == whitespace_offsets.second) {
return string;
}
return isolate->factory()->NewSubString(string, whitespace_offsets.first,
whitespace_offsets.second);
}
} // namespace
// #sec-tointlmathematicalvalue
Maybe<IntlMathematicalValue> IntlMathematicalValue::From(Isolate* isolate,
Handle<Object> value) {
Factory* factory = isolate->factory();
// 1. Let primValue be ? ToPrimitive(value, number).
Handle<Object> prim_value;
if (value->IsJSReceiver()) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, prim_value,
JSReceiver::ToPrimitive(isolate, Handle<JSReceiver>::cast(value),
ToPrimitiveHint::kNumber),
Nothing<IntlMathematicalValue>());
} else {
prim_value = value;
}
IntlMathematicalValue result;
// 2. If Type(primValue) is BigInt, return the mathematical value of
// primValue.
if (prim_value->IsBigInt()) {
result.value_ = prim_value;
result.approx_ = Handle<BigInt>::cast(prim_value)->AsInt64();
return Just(result);
}
if (prim_value->IsOddball()) {
prim_value = Oddball::ToNumber(isolate, Handle<Oddball>::cast(prim_value));
}
if (prim_value->IsNumber()) {
result.value_ = prim_value;
result.approx_ = prim_value->Number();
return Just(result);
}
if (!prim_value->IsString()) {
// No need to convert from Number to String, just call ToNumber.
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, result.value_,
Object::ToNumber(isolate, prim_value),
Nothing<IntlMathematicalValue>());
result.approx_ = result.value_->Number();
return Just(result);
}
Handle<String> string = Handle<String>::cast(prim_value);
string = TrimWhiteSpaceOrLineTerminator(isolate, string);
if (string->length() == 0) {
result.value_ = handle(Smi::zero(), isolate);
result.approx_ = 0;
return Just(result);
}
// We may have a NonDecimalIntegerLiteral:
if (2 < string->length() && string->Get(0) == '0') {
uint16_t ch = string->Get(1);
if (ch == 'b' || ch == 'B' || ch == 'o' || ch == 'O' || ch == 'x' ||
ch == 'X') {
result.approx_ = StringToDouble(
isolate, string, ALLOW_HEX | ALLOW_OCTAL | ALLOW_BINARY, 0);
// If approx is within the precision, just return as Number.
if (result.approx_ < kMaxSafeInteger) {
result.value_ = isolate->factory()->NewNumber(result.approx_);
return Just(result);
}
// Otherwise return the BigInt
MaybeHandle<BigInt> maybe_bigint = StringToBigInt(isolate, string);
// If the parsing of BigInt fail, return nan
if (maybe_bigint.is_null()) {
isolate->clear_pending_exception();
result.value_ = factory->nan_value();
return Just(result);
}
result.value_ = maybe_bigint.ToHandleChecked();
return Just(result);
}
}
// If it does not fit StrDecimalLiteral StrWhiteSpace_opt, StringToDouble will
// parse it as NaN, in that case, return NaN.
result.approx_ = StringToDouble(isolate, string, NO_CONVERSION_FLAGS, 0);
if (std::isnan(result.approx_)) {
result.value_ = factory->nan_value();
return Just(result);
}
// Handle Infinity / +Infinity / -Infinity
if (!std::isfinite(result.approx_)) {
if (result.approx_ < 0) {
result.value_ = factory->minus_infinity_value();
} else {
result.value_ = factory->infinity_value();
}
return Just(result);
}
// At this point, str is for sure fit
// "StrNumericLiteral StrWhiteSpace_opt" excluding "(+|-)?Infinity"
result.value_ = string;
return Just(result);
}
Maybe<icu::Formattable> IntlMathematicalValue::ToFormattable(
Isolate* isolate) const {
if (value_->IsNumber()) {
return Just(icu::Formattable(approx_));
}
Handle<String> string;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, string, ToString(isolate),
Nothing<icu::Formattable>());
UErrorCode status = U_ZERO_ERROR;
{
DisallowGarbageCollection no_gc;
const String::FlatContent& flat = string->GetFlatContent(no_gc);
int length = string->length();
if (flat.IsOneByte()) {
icu::Formattable result(
{reinterpret_cast<const char*>(flat.ToOneByteVector().begin()),
length},
status);
if (U_SUCCESS(status)) return Just(result);
} else {
icu::Formattable result({string->ToCString().get(), length}, status);
if (U_SUCCESS(status)) return Just(result);
}
}
THROW_NEW_ERROR_RETURN_VALUE(isolate,
NewTypeError(MessageTemplate::kIcuError),
Nothing<icu::Formattable>());
}
namespace {
bool cmp_NumberFormatSpan(const NumberFormatSpan& a,
const NumberFormatSpan& b) {
// Regions that start earlier should be encountered earlier.
@ -1943,138 +2460,100 @@ Maybe<int> ConstructParts(Isolate* isolate,
return Just(index);
}
bool IsPositiveInfinity(Isolate* isolate, Handle<Object> v) {
if (v->IsBigInt()) return false;
if (v->IsString()) {
return isolate->factory()->Infinity_string()->Equals(String::cast(*v));
}
CHECK(v->IsNumber());
double const value_number = v->Number();
return std::isinf(value_number) && (value_number > 0.0);
}
bool IsNegativeInfinity(Isolate* isolate, Handle<Object> v) {
if (v->IsBigInt()) return false;
if (v->IsString()) {
return isolate->factory()->minus_Infinity_string()->Equals(
String::cast(*v));
}
CHECK(v->IsNumber());
double const value_number = v->Number();
return std::isinf(value_number) && (value_number < 0.0);
}
bool IsNegativeZero(Isolate* isolate, Handle<Object> v) {
if (v->IsBigInt()) return false;
if (v->IsString()) {
return isolate->factory()->minus_0()->Equals(String::cast(*v));
}
CHECK(v->IsNumber());
return IsMinusZero(v->Number());
}
bool LessThan(Isolate* isolate, Handle<Object> a, Handle<Object> b) {
Maybe<ComparisonResult> comparison = Object::Compare(isolate, a, b);
return comparison.IsJust() &&
comparison.FromJust() == ComparisonResult::kLessThan;
}
bool IsFiniteNonMinusZeroNumberOrBigInt(Isolate* isolate, Handle<Object> v) {
return !(IsPositiveInfinity(isolate, v) || IsNegativeInfinity(isolate, v) ||
v->IsMinusZero());
}
// #sec-partitionnumberrangepattern
template <typename T, MaybeHandle<T> (*F)(
Isolate*, const icu::FormattedValue&,
const icu::number::LocalizedNumberFormatter&, bool)>
MaybeHandle<T> PartitionNumberRangePattern(Isolate* isolate,
Handle<JSNumberFormat> number_format,
Handle<Object> x, Handle<Object> y,
Handle<Object> start,
Handle<Object> end,
const char* func_name) {
Factory* factory = isolate->factory();
// 4. Let x be ? ToIntlMathematicalValue(start).
IntlMathematicalValue x;
MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, x, IntlMathematicalValue::From(isolate, start), Handle<T>());
// 1. If x is NaN or y is NaN, throw a RangeError exception.
if (x->IsNaN()) {
// 5. Let y be ? ToIntlMathematicalValue(end).
IntlMathematicalValue y;
MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, y, IntlMathematicalValue::From(isolate, end), Handle<T>());
// 1. If x is not-a-number or y is not-a-number, throw a RangeError exception.
if (x.IsNaN()) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate,
NewRangeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("start"), x),
factory->NewStringFromStaticChars("start"), start),
MaybeHandle<T>());
}
if (y->IsNaN()) {
if (y.IsNaN()) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate,
NewRangeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("end"), y),
factory->NewStringFromStaticChars("end"), end),
MaybeHandle<T>());
}
// 2. If x is a mathematical value, then
if (IsFiniteNonMinusZeroNumberOrBigInt(isolate, x)) {
if (x.IsMathematicalValue()) {
// a. If y is a mathematical value and y < x, throw a RangeError exception.
if (IsFiniteNonMinusZeroNumberOrBigInt(isolate, y) &&
LessThan(isolate, y, x)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
MaybeHandle<T>());
if (y.IsMathematicalValue()) {
if (y.IsLessThan(isolate, x)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalid, start, end),
MaybeHandle<T>());
}
}
// b. Else if y is -∞, throw a RangeError exception.
if (IsNegativeInfinity(isolate, y)) {
// b. Else if y is negative-infinity, throw a RangeError exception.
if (y.IsNegativeInfinity()) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
isolate, NewRangeError(MessageTemplate::kInvalid, start, end),
MaybeHandle<T>());
}
// c. Else if y is -0 and x ≥ 0, throw a RangeError exception.
if (y->IsMinusZero() &&
!LessThan(isolate, x, Handle<Object>(Smi::zero(), isolate))) {
if (y.IsMinusZero() && !x.IsNegative()) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
isolate, NewRangeError(MessageTemplate::kInvalid, start, end),
MaybeHandle<T>());
}
// 3. Else if x is +∞, then
} else if (IsPositiveInfinity(isolate, x)) {
// a. If y is a mathematical value, throw a RangeError exception.
if (IsFiniteNonMinusZeroNumberOrBigInt(isolate, y)) {
// Else if x is positive-infinity, then
} else if (x.IsPositiveInfinity()) {
// If y is a mathematical value, throw a RangeError exception.
if (y.IsMathematicalValue()) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
isolate, NewRangeError(MessageTemplate::kInvalid, start, end),
MaybeHandle<T>());
}
// b. Else if y is -∞, throw a RangeError exception.
if (IsNegativeInfinity(isolate, y)) {
// b. Else if y is negative-infinity, throw a RangeError exception.
if (y.IsNegativeInfinity()) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
isolate, NewRangeError(MessageTemplate::kInvalid, start, end),
MaybeHandle<T>());
}
// c. Else if y is -0, throw a RangeError exception.
if (IsNegativeZero(isolate, y)) {
// c. Else if y is negative-zero, throw a RangeError exception.
if (y.IsMinusZero()) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
isolate, NewRangeError(MessageTemplate::kInvalid, start, end),
MaybeHandle<T>());
}
// 4. Else if x is -0, then
} else if (IsNegativeZero(isolate, x)) {
// 4. Else if x is negative-zero, then
} else if (x.IsMinusZero()) {
// a. If y is a mathematical value and y < 0, throw a RangeError exception.
if (IsFiniteNonMinusZeroNumberOrBigInt(isolate, y) &&
LessThan(isolate, y, Handle<Object>(Smi::zero(), isolate))) {
if (y.IsMathematicalValue() && y.IsNegative()) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
isolate, NewRangeError(MessageTemplate::kInvalid, start, end),
MaybeHandle<T>());
}
// b. Else if y is -∞, throw a RangeError exception.
if (IsNegativeInfinity(isolate, y)) {
// b. Else if y is negative-infinity, throw a RangeError exception.
if (y.IsNegativeInfinity()) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
isolate, NewRangeError(MessageTemplate::kInvalid, start, end),
MaybeHandle<T>());
}
}
Maybe<icu::Formattable> maybe_x = ToFormattable(isolate, x, "start");
MAYBE_RETURN(maybe_x, MaybeHandle<T>());
Maybe<icu::Formattable> maybe_y = ToFormattable(isolate, y, "end");
MAYBE_RETURN(maybe_y, MaybeHandle<T>());
Maybe<icu::number::LocalizedNumberRangeFormatter> maybe_range_formatter =
JSNumberFormat::GetRangeFormatter(
isolate, number_format->locale(),
@ -2083,13 +2562,12 @@ MaybeHandle<T> PartitionNumberRangePattern(Isolate* isolate,
icu::number::LocalizedNumberRangeFormatter nrfmt =
maybe_range_formatter.FromJust();
UErrorCode status = U_ZERO_ERROR;
icu::number::FormattedNumberRange formatted = nrfmt.formatFormattableRange(
maybe_x.FromJust(), maybe_y.FromJust(), status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate, NewTypeError(MessageTemplate::kIcuError), MaybeHandle<T>());
}
Maybe<icu::number::FormattedNumberRange> maybe_formatted =
IntlMathematicalValue::FormatRange(isolate, nrfmt, x, y);
MAYBE_RETURN(maybe_formatted, Handle<T>());
icu::number::FormattedNumberRange formatted =
std::move(maybe_formatted).FromJust();
return F(isolate, formatted, *(number_format->icu_number_formatter().raw()),
false /* is_nan */);
@ -2117,9 +2595,15 @@ MaybeHandle<JSArray> FormatToJSArray(
Factory* factory = isolate->factory();
Handle<JSArray> result = factory->NewJSArray(0);
Maybe<int> maybe_format_to_parts = ConstructParts(
isolate, formatted, result, 0, is_unit, is_nan, output_source);
MAYBE_RETURN(maybe_format_to_parts, Handle<JSArray>());
int format_to_parts;
MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, format_to_parts,
ConstructParts(isolate, formatted, result, 0, is_unit, is_nan,
output_source),
Handle<JSArray>());
USE(format_to_parts);
return result;
}
@ -2160,26 +2644,110 @@ MaybeHandle<String> JSNumberFormat::FormatNumeric(
Maybe<icu::number::FormattedNumber> maybe_format =
IcuFormatNumber(isolate, number_format, numeric_obj);
MAYBE_RETURN(maybe_format, Handle<String>());
icu::number::FormattedNumber formatted = std::move(maybe_format).FromJust();
return FormatToString(isolate, formatted, number_format,
numeric_obj->IsNaN());
}
namespace {
MaybeHandle<String> NumberFormatFunctionV2(Isolate* isolate,
Handle<JSNumberFormat> number_format,
Handle<Object> value) {
icu::number::LocalizedNumberFormatter* fmt =
number_format->icu_number_formatter().raw();
CHECK_NOT_NULL(fmt);
Handle<Object> x;
// 4. Let x be ? ToNumeric(value).
ASSIGN_RETURN_ON_EXCEPTION(isolate, x, Object::ToNumeric(isolate, value),
String);
// 5. Return FormatNumeric(nf, x).
return JSNumberFormat::FormatNumeric(isolate, *fmt, x);
}
MaybeHandle<String> NumberFormatFunctionV3(Isolate* isolate,
Handle<JSNumberFormat> number_format,
Handle<Object> value) {
icu::number::LocalizedNumberFormatter* fmt =
number_format->icu_number_formatter().raw();
CHECK_NOT_NULL(fmt);
// 4. Let x be ? ToIntlMathematicalValue(value).
IntlMathematicalValue x;
MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, x, IntlMathematicalValue::From(isolate, value),
Handle<String>());
// 5. Return FormatNumeric(nf, x).
Maybe<icu::number::FormattedNumber> maybe_formatted =
IntlMathematicalValue::FormatNumeric(isolate, *fmt, x);
MAYBE_RETURN(maybe_formatted, Handle<String>());
icu::number::FormattedNumber formatted =
std::move(maybe_formatted).FromJust();
return FormatToString(isolate, formatted, *fmt, x.IsNaN());
}
MaybeHandle<JSArray> FormatToPartsV2(Isolate* isolate,
Handle<JSNumberFormat> number_format,
Handle<Object> numeric_obj) {
icu::number::LocalizedNumberFormatter* fmt =
number_format->icu_number_formatter().raw();
CHECK_NOT_NULL(fmt);
ASSIGN_RETURN_ON_EXCEPTION(isolate, numeric_obj,
Object::ToNumeric(isolate, numeric_obj), JSArray);
Maybe<icu::number::FormattedNumber> maybe_formatted =
IcuFormatNumber(isolate, *fmt, numeric_obj);
MAYBE_RETURN(maybe_formatted, Handle<JSArray>());
icu::number::FormattedNumber formatted =
std::move(maybe_formatted).FromJust();
return FormatToJSArray(isolate, formatted, *fmt, numeric_obj->IsNaN(), false);
}
MaybeHandle<JSArray> FormatToPartsV3(Isolate* isolate,
Handle<JSNumberFormat> number_format,
Handle<Object> numeric_obj) {
icu::number::LocalizedNumberFormatter* fmt =
number_format->icu_number_formatter().raw();
DCHECK_NOT_NULL(fmt);
IntlMathematicalValue value;
MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, value, IntlMathematicalValue::From(isolate, numeric_obj),
Handle<JSArray>());
Maybe<icu::number::FormattedNumber> maybe_formatted =
IntlMathematicalValue::FormatNumeric(isolate, *fmt, value);
MAYBE_RETURN(maybe_formatted, Handle<JSArray>());
icu::number::FormattedNumber formatted =
std::move(maybe_formatted).FromJust();
return FormatToJSArray(isolate, formatted, *fmt, value.IsNaN(), false);
}
} // namespace
// #sec-number-format-functions
MaybeHandle<String> JSNumberFormat::NumberFormatFunction(
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> value) {
if (FLAG_harmony_intl_number_format_v3) {
return NumberFormatFunctionV3(isolate, number_format, value);
} else {
return NumberFormatFunctionV2(isolate, number_format, value);
}
}
MaybeHandle<JSArray> JSNumberFormat::FormatToParts(
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> numeric_obj) {
CHECK(numeric_obj->IsNumeric() || FLAG_harmony_intl_number_format_v3);
icu::number::LocalizedNumberFormatter* fmt =
number_format->icu_number_formatter().raw();
CHECK_NOT_NULL(fmt);
Maybe<icu::number::FormattedNumber> maybe_format =
IcuFormatNumber(isolate, *fmt, numeric_obj);
MAYBE_RETURN(maybe_format, Handle<JSArray>());
icu::number::FormattedNumber formatted = std::move(maybe_format).FromJust();
return FormatToJSArray(isolate, formatted, *fmt, numeric_obj->IsNaN(), false);
if (FLAG_harmony_intl_number_format_v3) {
return FormatToPartsV3(isolate, number_format, numeric_obj);
} else {
return FormatToPartsV2(isolate, number_format, numeric_obj);
}
}
MaybeHandle<String> JSNumberFormat::FormatNumericRange(

View File

@ -23,11 +23,14 @@
#include "src/objects/object-macros.h"
namespace U_ICU_NAMESPACE {
class Formattable;
class UnicodeString;
namespace number {
class FormattedNumber;
class FormattedNumberRange;
class LocalizedNumberFormatter;
class UnlocalizedNumberFormatter;
class LocalizedNumberRangeFormatter;
class UnlocalizedNumberFormatter;
} // namespace number
} // namespace U_ICU_NAMESPACE
@ -48,6 +51,11 @@ class JSNumberFormat
V8_WARN_UNUSED_RESULT static MaybeHandle<JSNumberFormat> UnwrapNumberFormat(
Isolate* isolate, Handle<JSReceiver> format_holder);
// #sec-number-format-functions
V8_WARN_UNUSED_RESULT static MaybeHandle<String> NumberFormatFunction(
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> numeric_obj);
// ecma402/#sec-intl.numberformat.prototype.resolvedoptions
static Handle<JSObject> ResolvedOptions(Isolate* isolate,
Handle<JSNumberFormat> number_format);
@ -101,6 +109,49 @@ class JSNumberFormat
TQ_OBJECT_CONSTRUCTORS(JSNumberFormat)
};
// IntlMathematicalValue is designed only to be used as part of
// JSNumberFormat and can only be allocate on the stack. We place this class in
// the header so we can write unit test code for it. Please do NOT use this
// class outside JSNumberFormat implementation.
class V8_NODISCARD IntlMathematicalValue {
public:
IntlMathematicalValue() : approx_(0) {}
V8_EXPORT_PRIVATE bool IsNaN() const;
V8_EXPORT_PRIVATE bool IsMinusZero() const;
V8_EXPORT_PRIVATE bool IsNegative() const;
V8_EXPORT_PRIVATE bool IsNegativeInfinity() const {
return IsNegative() && IsInfinity();
}
V8_EXPORT_PRIVATE bool IsPositiveInfinity() const {
return !IsNegative() && IsInfinity();
}
V8_EXPORT_PRIVATE bool IsMathematicalValue() const {
return !(IsNaN() || IsMinusZero() || IsInfinity());
}
V8_EXPORT_PRIVATE bool IsLessThan(Isolate* isolate,
const IntlMathematicalValue& y) const;
V8_EXPORT_PRIVATE static Maybe<IntlMathematicalValue> From(
Isolate* isolate, Handle<Object> value);
static Maybe<icu::number::FormattedNumber> FormatNumeric(
Isolate* isolate,
const icu::number::LocalizedNumberFormatter& number_format,
const IntlMathematicalValue& x);
static Maybe<icu::number::FormattedNumberRange> FormatRange(
Isolate* isolate,
const icu::number::LocalizedNumberRangeFormatter& number_range_format,
const IntlMathematicalValue& x, const IntlMathematicalValue& y);
private:
double approx_;
Handle<Object> value_; // Number, BigInt or String
Maybe<icu::Formattable> ToFormattable(Isolate* isolate) const;
MaybeHandle<String> ToString(Isolate* isolate) const;
V8_EXPORT_PRIVATE bool IsInfinity() const;
};
} // namespace internal
} // namespace v8

View File

@ -309,6 +309,197 @@ TEST(StringLocaleCompareFastPath) {
}
}
TEST(IntlMathematicalValueFromString) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
HandleScope handle_scope(isolate);
struct TestCase {
bool is_nan;
bool is_minus_zero;
bool is_negative;
bool is_negative_infinity;
bool is_positive_infinity;
bool is_mathematical_value;
const char* string;
} cases[] = {
{false, false, false, false, false, true, "+1"},
{false, false, false, false, false, true,
"+1234567890123456789012345678901234567890"},
{false, false, false, false, true, false,
"+1234567890123456789012345678901234567890e987654321"},
{false, false, false, false, true, false,
" +1234567890123456789012345678901234567890e987654321 "},
{true, false, false, false, false, false,
" +12 345 67 "}, // space between digit is invalid
{true, false, false, false, false, false,
" -12 345 67 "}, // space between digit is invalid
{false, false, false, false, false, true,
"1234567890123456789012345678901234567890"},
{false, false, false, false, false, true,
"+.1234567890123456789012345678901234567890"},
{false, false, false, false, false, true,
".1234567890123456789012345678901234567890"},
{false, false, false, false, false, true, ".1234567890123456789e123"},
{false, false, false, false, false, true, ".1234567890123456789E123"},
{false, false, false, false, false, true, ".1234567890123456789e+123"},
{false, false, false, false, false, true, ".1234567890123456789E+123"},
{false, false, false, false, false, true, ".1234567890123456789e-0123"},
{false, false, false, false, false, true, ".1234567890123456789E-0123"},
{false, false, false, false, false, true,
"1234567890123456789012345678901234567.890"},
{false, false, false, false, false, true,
"1234567890123456789012345678901234567890."},
{true, false, false, false, false, false,
"1234567.90123456789012345678901234567.890"}, // two '.'
{true, false, false, false, false, false,
".1234567890123456789e12.3"}, // two '.'
{false, false, true, false, false, true, "-1"},
{false, false, true, false, false, true, "-1e33 "},
{false, false, true, false, false, true, " -0.21e33"},
{false, false, false, false, false, true, " 0.21e33"},
{false, true, false, false, false, false, "-0"},
{false, false, false, false, false, true, "1"},
{false, false, true, false, false, true, " -1234.567e-20 "},
{false, true, false, false, false, false, " -1234.567e-9876 "},
{false, false, false, false, true, false, " Infinity "},
{false, false, true, true, false, false, " -Infinity "},
{true, false, false, false, false, false, "yz"}, // not digits
{false, false, true, false, false, true,
" -12345678901234567890122345.6778901234567890e234 "},
{false, false, false, false, false, true,
" 12345678901234567890122345.6778901234567890e-234 "},
{false, false, false, false, false, true, " 0b01010001 "},
{false, false, false, false, false, true, " 0B01010001 "},
{true, false, false, false, false, false,
" -0b01010001 "}, // invalid binary becaues of -
{true, false, false, false, false, false,
" -0B01010001 "}, // invalid binary becaues of -
{true, false, false, false, false, false,
" 0b01010002 "}, // invalid binary becaues of 2
{true, false, false, false, false, false,
" 0B01010003 "}, // invalid binary becaues of 3
{false, false, false, false, false, true, " 0o01234567 "},
{false, false, false, false, false, true, " 0O76543210 "},
{true, false, false, false, false, false,
" -0o01234567 "}, // invalid oct becaues of -
{true, false, false, false, false, false,
" -0O76543210 "}, // invalid oct becaues of -
{true, false, false, false, false, false,
" 0o012345678 "}, // invalid oct becaues of 8
{true, false, false, false, false, false,
" 0O765432108 "}, // invalid oct becaues of 8
{false, false, false, false, false, true, " 0x123456789aBcDeF "},
{false, false, false, false, false, true, " 0X123456789AbCdEf "},
{true, false, false, false, false, false,
" -0x123456789aBcDeF "}, // invalid hex because of -
{true, false, false, false, false, false,
" -0X123456789AbCdEf "}, // invalid hex because of -
{true, false, false, false, false, false,
" 0x012345678xyz "}, // invalid hex because xyz
{true, false, false, false, false, false,
" 0X765432108xyz "}, // invalid hex because xyz
};
for (auto& cas : cases) {
IntlMathematicalValue x =
IntlMathematicalValue::From(
isolate, isolate->factory()->NewStringFromAsciiChecked(cas.string))
.ToChecked();
CHECK_EQ(x.IsNaN(), cas.is_nan);
CHECK_EQ(x.IsMinusZero(), cas.is_minus_zero);
CHECK_EQ(x.IsNegative(), cas.is_negative);
CHECK_EQ(x.IsNegativeInfinity(), cas.is_negative_infinity);
CHECK_EQ(x.IsPositiveInfinity(), cas.is_positive_infinity);
CHECK_EQ(x.IsMathematicalValue(), cas.is_mathematical_value);
}
}
TEST(IntlMathematicalValueFromBigInt) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
HandleScope handle_scope(isolate);
struct TestCase {
bool is_negative;
const char* bigint_string;
} cases[] = {
{false, "12"},
{false, "12345678901234567890123456789012345678901234567890"},
{true, "-12345678901234567890123456789012345678901234567890"},
{false, "0"},
{true, "-20"},
};
for (auto& cas : cases) {
printf("%s\n", cas.bigint_string);
Handle<String> str =
isolate->factory()->NewStringFromAsciiChecked(cas.bigint_string);
IntlMathematicalValue x =
IntlMathematicalValue::From(
isolate, BigInt::FromObject(isolate, str).ToHandleChecked())
.ToChecked();
CHECK_EQ(x.IsNaN(), false);
CHECK_EQ(x.IsMinusZero(), false);
CHECK_EQ(x.IsNegative(), cas.is_negative);
CHECK_EQ(x.IsNegativeInfinity(), false);
CHECK_EQ(x.IsPositiveInfinity(), false);
CHECK_EQ(x.IsMathematicalValue(), true);
}
}
TEST(IntlMathematicalValueLessThanString) {
LocalContext env;
Isolate* isolate = CcTest::i_isolate();
HandleScope handle_scope(isolate);
struct TestCase {
const char* x;
const char* y;
bool is_x_less_than_y;
} cases[] = {
{" 1 ", " 2", true},
{" 1 ", " 2 ", true},
{" 1e-1 ", " 2 ", true},
{" 1e1 ", " 2 ", false},
{" 1 ", " 20e-3", false},
{" -1e10 ", " -1e9 ", true},
{" -1e-10 ", " -1e-9 ", false},
{" 123456789012345678901234567890 ", " 123456789012345678901234567890 ",
false},
{" .123456789012345678901234567890 ", " .123456789012345678901234567890 ",
false},
{" .123456789012345678901234567890000 ",
" .12345678901234567890123456789 ", false},
{" .12345678901234567890123456789 ",
" .123456789012345678901234567890000 ", false},
{" 123456789012345678901234567890 ", " 1234567890123456789012345678901 ",
true},
{" 1234567890123456789012345678902 ", " 1234567890123456789012345678901 ",
false},
{" 123456789012345.678901234567890e33 ",
" 12345678901234.5678901234567890e34 ", false},
{" 123456789012345.678901234567890e33 ",
" 12345678901234.5678901234567890e35 ", true},
{" 12345678901234.5678901234567890e34 ",
" 123456789012345.678901234567890e33 ", false},
{" 123456789012345678.901234567890e30 ",
" 12345678901234.5678901234567890e35 ", true},
{" .12345678901234567890123456789 ",
" .1234567890123456789012345678900000001 ", true},
{" -.1234567890123456789012345678900000001 ",
" -.123456789012345678901234567890000 ", true},
{" -.1234567890123456789012345678900000001 ",
" -0.00000123456789012345678901234567890000e5 ", true},
};
for (auto& cas : cases) {
IntlMathematicalValue x =
IntlMathematicalValue::From(
isolate, isolate->factory()->NewStringFromAsciiChecked(cas.x))
.ToChecked();
IntlMathematicalValue y =
IntlMathematicalValue::From(
isolate, isolate->factory()->NewStringFromAsciiChecked(cas.y))
.ToChecked();
CHECK_EQ(x.IsLessThan(isolate, y), cas.is_x_less_than_y);
}
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,116 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-number-format-v3
// Test the throw in formatRange
let df = new Intl.NumberFormat();
// https://tc39.es/proposal-intl-numberformat-v3/out/numberformat/diff.html#sec-partitionnumberrangepattern
// 2. If x is not-a-number or y is not-a-number, throw a RangeError exception.
assertThrows(() => { df.formatRange("xyz", "123") }, RangeError);
assertThrows(() => { df.formatRange("123", "xyz") }, RangeError);
assertThrows(() => { df.formatRange("1", "-0b1111") }, RangeError);
assertThrows(() => { df.formatRange("1", "-0o7654") }, RangeError);
assertThrows(() => { df.formatRange("1", "-0xabcde") }, RangeError);
// 2. If x is a mathematical value, then
// 2a. If y is a mathematical value and y < x, throw a RangeError exception.
assertThrows(() => { df.formatRange(
" +1234567890123456789012345678901234567890123456789012345678901 ",
" +123456789012345678901234567890123456789012345678901234567890 ") }, RangeError);
assertThrows(() => { df.formatRange(
" +123456789012345678901234567890.123456789012345678901234567890e25 ",
" +12345678901234567890.1234567890123456789012345678901234567890e25 ") }, RangeError);
assertThrows(() => { df.formatRange(
" +12345678901234567890.1234567890123456789012345678901234567890e35 ",
" +123456789012345678901234567890.123456789012345678901234567890e24 ") }, RangeError);
assertThrows(() => { df.formatRange(
" -123456789012345678901234567890123456789012345678901234567890 ",
" -1234567890123456789012345678901234567890123456789012345678901 ") }, RangeError);
assertThrows(() => { df.formatRange(
" -12345678901234567890.1234567890123456789012345678901234567890e25 ",
" -123456789012345678901234567890.123456789012345678901234567890e25 ") }, RangeError);
assertThrows(() => { df.formatRange(
" -123456789012345678901234567890.123456789012345678901234567890e24 ",
" -12345678901234567890.1234567890123456789012345678901234567890e35 ") }, RangeError);
assertThrows(() => { df.formatRange(
" +.1234567890123456789012345678901234567890123456789012345678901 ",
" +.123456789012345678901234567890123456789012345678901234567890 ") }, RangeError);
assertThrows(() => { df.formatRange(
" +.123456789012345678901234567890123456789012345678901234567890 ",
" -.1234567890123456789012345678901234567890123456789012345678901 ") }, RangeError);
assertThrows(() => { df.formatRange(
" +.12e3 ", " +.12e2 ") }, RangeError);
assertThrows(() => { df.formatRange(
" +123 ", " +.12e2 ") }, RangeError);
assertThrows(() => { df.formatRange(
" -123 ", " -.12e4 ") }, RangeError);
// 2b. Else if y is negative-infinity, throw a RangeError exception.
assertThrows(() => { df.formatRange( " 123 ", " -Infinity ") }, RangeError);
// 2c. Else if y is negative-zero and x ≥ 0, throw a RangeError exception.
assertThrows(() => { df.formatRange( " 123 ", " -0 ") }, RangeError);
// other case which won't throw under 2
assertDoesNotThrow(() => { df.formatRange( " 123 ", " Infinity ") })
assertEquals("123∞", df.formatRange( " 123 ", " Infinity "));
assertDoesNotThrow(() => { df.formatRange(
" +.123456789012345678901234567890123456789012345678901234567890 ", " Infinity ") })
assertEquals("0.123–∞", df.formatRange(
" +.123456789012345678901234567890123456789012345678901234567890 ",
" Infinity "));
assertDoesNotThrow(() => { df.formatRange(
" +.123456789012345678901234567890123456789012345678901234567890 ",
" +.123456789012345678901234567890123456789012345678901234567890 ")})
assertDoesNotThrow(() => { df.formatRange(
" +.123456789012345678901234567890123456789012345678901234567890 ",
" +.1234567890123456789012345678901234567890123456789012345678901 ")})
assertDoesNotThrow(() => { df.formatRange(
" +12345678901234567890.123456789012345678901234567890123456789000000001e20 ",
" +1234567890.12345678901234567890123456789012345678901234567890e31 ")})
// 3. Else if x is positive-infinity, then
// 3a. If y is a mathematical value, throw a RangeError exception.
assertThrows(() => { df.formatRange( " Infinity ", " 123 ") }, RangeError);
assertThrows(() => { df.formatRange( " +Infinity ", " 123 ") }, RangeError);
// 3b. Else if y is negative-infinity, throw a RangeError exception.
assertThrows(() => { df.formatRange( " Infinity ", " -Infinity ") }, RangeError);
assertThrows(() => { df.formatRange( " +Infinity ", " -Infinity ") }, RangeError);
// 3c. Else if y is negative-zero, throw a RangeError exception.
assertThrows(() => { df.formatRange( " Infinity ", " -0 ") }, RangeError);
assertThrows(() => { df.formatRange( " +Infinity ", " -0 ") }, RangeError);
// other case which won't throw under 3
assertDoesNotThrow(() => { df.formatRange( " Infinity ", " Infinity ") })
assertEquals("~∞", df.formatRange(" Infinity ", " Infinity "));
// 4. Else if x is negative-zero, then
// 4a. If y is a mathematical value and y < 0, throw a RangeError exception.
assertThrows(() => { df.formatRange( " -0 ", " -1e-30 ") }, RangeError);
assertThrows(() => { df.formatRange( " -0.000e200 ", " -1e-30 ") }, RangeError);
// 4b. Else if y is negative-infinity, throw a RangeError exception.
assertThrows(() => { df.formatRange( " -0 ", " -Infinity ") }, RangeError);
// other case which won't throw under 4
assertDoesNotThrow(() => { df.formatRange( " -0 ", " Infinity ") })
assertEquals("-0 ∞", df.formatRange(" -0 ", " Infinity "));
assertDoesNotThrow(() => { df.formatRange( " -0 ", " -0 ") })
assertDoesNotThrow(() => { df.formatRange( " -0 ", " 12345 ") })
assertDoesNotThrow(() => { df.formatRange( " -0 ", " 12345e-30 ") })
assertEquals("-0 0", df.formatRange(" -0 ", " 12345e-30 "));
assertDoesNotThrow(() => { df.formatRange( " -0 ", " .12345e-30 ") })
assertDoesNotThrow(() => { df.formatRange( " -0 ", " .12345e34 ") })
assertEquals("-0 12,345,000,000,000,000,000,000,000,000,000",
df.formatRange(" -0 ", " .12345e32 "));
// other cases which won't throw not under 2-4
assertDoesNotThrow(() => { df.formatRange( " -Infinity ", " -Infinity ") })
assertEquals("~-∞", df.formatRange(" -Infinity ", " -Infinity "));
assertDoesNotThrow(() => { df.formatRange( " -Infinity ", " -3e20 ") })
assertDoesNotThrow(() => { df.formatRange( " -Infinity ", " -3e20 ") })
assertDoesNotThrow(() => { df.formatRange( " -Infinity ", " -0 ") })
assertDoesNotThrow(() => { df.formatRange( " -Infinity ", " 0 ") })
assertDoesNotThrow(() => { df.formatRange( " -Infinity ", " .3e20 ") })
assertDoesNotThrow(() => { df.formatRange( " -Infinity ", " Infinity ") })
assertEquals("-∞ ∞", df.formatRange(" -Infinity ", " Infinity "));

View File

@ -0,0 +1,132 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-number-format-v3
let nf = new Intl.NumberFormat("en");
// Basic case
assertEquals("123", nf.format("123"));
assertEquals("123.45", nf.format("123.45"));
assertEquals("123", nf.format("+123"));
assertEquals("123.45", nf.format("+123.45"));
assertEquals("-123", nf.format("-123"));
assertEquals("-123.45", nf.format("-123.45"));
// with _
assertEquals("NaN", nf.format("1_2_3_4"));
assertEquals("NaN", nf.format("1_2.3_4"));
assertEquals("NaN", nf.format("1_2.34"));
assertEquals("NaN", nf.format("12.3_4"));
assertEquals("NaN", nf.format(".1_2_3"));
assertEquals("NaN", nf.format("123e1_2"));
assertEquals("NaN", nf.format("123e-1_2"));
assertEquals("NaN", nf.format("1.23e1_2"));
assertEquals("NaN", nf.format("12.3e-1_2"));
assertEquals("NaN", nf.format("12.3e+1_2"));
let str_white_space = " \u0009\u000b\u000c\ufeff\u000a\u000d\u2028\u2029";
// With StrWhiteSpace_opt only
assertEquals("0", nf.format(str_white_space));
// With StrWhiteSpace_opt prefix/postfix
assertEquals("123", nf.format(str_white_space + "123" + str_white_space));
assertEquals("123.45", nf.format(str_white_space + "123.45" + str_white_space));
assertEquals("123", nf.format(str_white_space + "+123" + str_white_space));
assertEquals("123.45",
nf.format(str_white_space + "+123.45" + str_white_space));
assertEquals("-123", nf.format(str_white_space + "-123" + str_white_space));
assertEquals("-123.45",
nf.format(str_white_space + "-123.45" + str_white_space));
// NonDecimalIntegerLiteral
assertEquals("10", nf.format("0b1010"));
assertEquals("11", nf.format("0B1011"));
assertEquals("10", nf.format("0o12"));
assertEquals("11", nf.format("0O13"));
assertEquals("10", nf.format("0x000A"));
assertEquals("11", nf.format("0X00B"));
// With StrWhiteSpace_opt prefix/postfix
assertEquals("10", nf.format(str_white_space + "0b1010" + str_white_space));
assertEquals("11", nf.format(str_white_space + "0B1011" + str_white_space));
assertEquals("10", nf.format(str_white_space + "0o12" + str_white_space));
assertEquals("11", nf.format(str_white_space + "0O13" + str_white_space));
assertEquals("10", nf.format(str_white_space + "0x000A" + str_white_space));
assertEquals("11", nf.format(str_white_space + "0X00B" + str_white_space));
// Very large NonDecimalIntegerLiteral
assertEquals("1,208,925,819,614,629,174,706,175",
nf.format("0xFFFFFFFFFFFFFFFFFFFF"));
assertEquals("1,208,925,819,614,629,174,706,174",
nf.format("0XFFFFFFFFFFFFFFFFFFFE"));
// NaN
// Infinity
assertEquals("∞", nf.format("Infinity"));
assertEquals("∞", nf.format("+Infinity"));
assertEquals("-∞", nf.format("-Infinity"));
// With StrWhiteSpace_opt prefix/postfix
assertEquals("∞", nf.format(str_white_space + "Infinity" + str_white_space));
assertEquals("∞", nf.format(str_white_space + "+Infinity" + str_white_space));
assertEquals("-∞", nf.format(str_white_space + "-Infinity" + str_white_space));
// Extra SPACE
assertEquals("NaN", nf.format("+ Infinity"));
assertEquals("NaN", nf.format("- Infinity"));
// DecimalDigits . DecimalDigits_opt ExponentPart_opt
assertEquals("123.45", nf.format("12345e-2"));
assertEquals("123.45", nf.format("+12345e-2"));
assertEquals("-123.45", nf.format("-12345e-2"));
assertEquals("123.45", nf.format("1.2345e2"));
assertEquals("123.45", nf.format("1.2345e+2"));
assertEquals("-123.45", nf.format("-1.2345e2"));
assertEquals("-123.45", nf.format("-1.2345e+2"));
// With StrWhiteSpace_opt prefix/postfix
assertEquals("123.45",
nf.format(str_white_space + "12345e-2" + str_white_space));
assertEquals("123.45",
nf.format(str_white_space + "+12345e-2" + str_white_space));
assertEquals("-123.45",
nf.format(str_white_space + "-12345e-2" + str_white_space));
assertEquals("123.45",
nf.format(str_white_space + "1.2345e2" + str_white_space));
assertEquals("123.45",
nf.format(str_white_space + "1.2345e+2" + str_white_space));
assertEquals("-123.45", nf.format("-1.2345e2"));
assertEquals("-123.45", nf.format("-1.2345e+2"));
// . DecimalDigits ExponentPart_opt
assertEquals("123.45", nf.format(".12345e3"));
assertEquals("123.45", nf.format("+.12345e+3"));
assertEquals("-123.45", nf.format("-.12345e3"));
assertEquals("-123.45", nf.format("-.12345e+3"));
// With StrWhiteSpace_opt prefix/postfix
assertEquals("123.45",
nf.format(str_white_space + ".12345e3" + str_white_space));
assertEquals("123.45",
nf.format(str_white_space + "+.12345e+3" + str_white_space));
assertEquals("-123.45",
nf.format(str_white_space + "-.12345e3" + str_white_space));
assertEquals("-123.45",
nf.format(str_white_space + "-.12345e+3" + str_white_space));
assertEquals("1,234,567,890,123,456,789,012,345,678,901,234,567,890,123",
nf.format("1234567890123456789012345678901234567890123"));
assertEquals("-1,234,567,890,123,456,789,012,345,678,901,234,567,890,123",
nf.format("-1234567890123456789012345678901234567890123"));
assertEquals(
"1,234,567,890,123,456,789,012,345,678,901,234,567,890,123,000,000,000,000",
nf.format("1234567890123456789012345678901234567890123e12"));
// With StrWhiteSpace_opt prefix/postfix
assertEquals("1,234,567,890,123,456,789,012,345,678,901,234,567,890,123",
nf.format(str_white_space +
"1234567890123456789012345678901234567890123" + str_white_space));
assertEquals("-1,234,567,890,123,456,789,012,345,678,901,234,567,890,123",
nf.format(str_white_space +
"-1234567890123456789012345678901234567890123" + str_white_space));
assertEquals(
"1,234,567,890,123,456,789,012,345,678,901,234,567,890,123,000,000,000,000",
nf.format(str_white_space +
"1234567890123456789012345678901234567890123e12" + str_white_space));

View File

@ -42,7 +42,8 @@ validRoundingIncrements.forEach(function(roundingIncrement) {
});
invalidRoundingIncrements.forEach(function(roundingIncrement) {
assertThrows(() => { let nf = new Intl.NumberFormat(undefined, {roundingIncrement,
minimumFractionDigits:3})},
assertThrows(() => {
let nf = new Intl.NumberFormat(undefined,
{roundingIncrement, minimumFractionDigits:3})},
RangeError);
});

View File

@ -1940,42 +1940,7 @@
'harness/temporalHelpers-one-shift-time-zone': [SKIP],
# https://bugs.chromium.org/p/v8/issues/detail?id=10776
# NumberFormat.prototype.formatRange
'intl402/NumberFormat/prototype/formatRange/en-US': [FAIL],
'intl402/NumberFormat/prototype/formatRange/pt-PT': [FAIL],
'intl402/NumberFormat/prototype/format/format-max-min-fraction-significant-digits': [FAIL],
# String handling
'intl402/NumberFormat/prototype/format/format-rounding-increment-1000': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-100': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-10': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-1': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-2000': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-200': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-20': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-2500': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-250': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-25': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-2': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-5000': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-500': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-50': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-increment-5': [FAIL],
'intl402/NumberFormat/prototype/format/value-decimal-string': [FAIL],
'intl402/NumberFormat/prototype/format/format-rounding-mode-ceil': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-mode-expand': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-mode-floor': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-mode-half-ceil': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-mode-half-even': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-mode-half-expand': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-mode-half-floor': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-mode-half-trunc': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-mode-trunc': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-priority-auto': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-priority-less-precision': [SKIP],
'intl402/NumberFormat/prototype/format/format-rounding-priority-more-precision': [SKIP],
'intl402/NumberFormat/test-option-roundingPriority-mixed-options': [SKIP],
'intl402/NumberFormat/test-option-roundingPriority-mixed-options': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=11660
'intl402/DurationFormat/prototype/prototype_attributes': [FAIL],