[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:
parent
b66d08241d
commit
fe53fc04d2
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
116
test/intl/number-format/format-range-string.js
Normal file
116
test/intl/number-format/format-range-string.js
Normal 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 "));
|
132
test/intl/number-format/format-string.js
Normal file
132
test/intl/number-format/format-string.js
Normal 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));
|
@ -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);
|
||||
});
|
||||
|
@ -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],
|
||||
|
Loading…
Reference in New Issue
Block a user