[intl] Part 3 of NumberFormat v3
Add NumberFormat.prototype.formatRange(ToParts)? https://github.com/tc39/proposal-intl-numberformat-v3 https://chromestatus.com/guide/edit/5707621009981440 Design Doc: https://docs.google.com/document/d/19jAogPBb6W4Samt8NWGZKu47iv0_KoQhBvLgQH3xvr8/edit Bug: v8:10776 Change-Id: I9bb163c0c15ccac9d3a2d5e55ad38aa5c06bbaa6 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3429464 Reviewed-by: Shu-yu Guo <syg@chromium.org> Commit-Queue: Frank Tang <ftang@chromium.org> Cr-Commit-Position: refs/heads/main@{#79373}
This commit is contained in:
parent
82c4c977b3
commit
a4bdc77fe8
@ -1771,6 +1771,10 @@ namespace internal {
|
||||
CPP(NumberFormatInternalFormatNumber) \
|
||||
/* ecma402 #sec-intl.numberformat.prototype.format */ \
|
||||
CPP(NumberFormatPrototypeFormatNumber) \
|
||||
/* ecma402 #sec-intl.numberformat.prototype.formatrange */ \
|
||||
CPP(NumberFormatPrototypeFormatRange) \
|
||||
/* ecma402 #sec-intl.numberformat.prototype.formatrangetoparts */ \
|
||||
CPP(NumberFormatPrototypeFormatRangeToParts) \
|
||||
/* ecma402 #sec-intl.numberformat.prototype.formattoparts */ \
|
||||
CPP(NumberFormatPrototypeFormatToParts) \
|
||||
/* ecma402 #sec-intl.numberformat.prototype.resolvedoptions */ \
|
||||
|
@ -163,11 +163,10 @@ BUILTIN(DateTimeFormatPrototypeFormatToParts) {
|
||||
}
|
||||
|
||||
// Common code for DateTimeFormatPrototypeFormtRange(|ToParts)
|
||||
template <class T>
|
||||
template <class T, MaybeHandle<T> (*F)(Isolate*, Handle<JSDateTimeFormat>,
|
||||
double, double)>
|
||||
V8_WARN_UNUSED_RESULT Object DateTimeFormatRange(
|
||||
BuiltinArguments args, Isolate* isolate, const char* const method_name,
|
||||
MaybeHandle<T> (*format)(Isolate*, Handle<JSDateTimeFormat>, double,
|
||||
double)) {
|
||||
BuiltinArguments args, Isolate* isolate, const char* const method_name) {
|
||||
// 1. Let dtf be this value.
|
||||
// 2. If Type(dtf) is not Object, throw a TypeError exception.
|
||||
CHECK_RECEIVER(JSObject, date_format_holder, method_name);
|
||||
@ -211,22 +210,22 @@ V8_WARN_UNUSED_RESULT Object DateTimeFormatRange(
|
||||
// 8. Return ? FormatDateTimeRange(dtf, x, y)
|
||||
// OR
|
||||
// 8. Return ? FormatDateTimeRangeToParts(dtf, x, y).
|
||||
RETURN_RESULT_OR_FAILURE(isolate, format(isolate, dtf, x, y));
|
||||
RETURN_RESULT_OR_FAILURE(isolate, F(isolate, dtf, x, y));
|
||||
}
|
||||
|
||||
BUILTIN(DateTimeFormatPrototypeFormatRange) {
|
||||
const char* const method_name = "Intl.DateTimeFormat.prototype.formatRange";
|
||||
HandleScope handle_scope(isolate);
|
||||
return DateTimeFormatRange<String>(args, isolate, method_name,
|
||||
JSDateTimeFormat::FormatRange);
|
||||
return DateTimeFormatRange<String, JSDateTimeFormat::FormatRange>(
|
||||
args, isolate, method_name);
|
||||
}
|
||||
|
||||
BUILTIN(DateTimeFormatPrototypeFormatRangeToParts) {
|
||||
const char* const method_name =
|
||||
"Intl.DateTimeFormat.prototype.formatRangeToParts";
|
||||
HandleScope handle_scope(isolate);
|
||||
return DateTimeFormatRange<JSArray>(args, isolate, method_name,
|
||||
JSDateTimeFormat::FormatRangeToParts);
|
||||
return DateTimeFormatRange<JSArray, JSDateTimeFormat::FormatRangeToParts>(
|
||||
args, isolate, method_name);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@ -527,6 +526,63 @@ BUILTIN(NumberFormatInternalFormatNumber) {
|
||||
isolate, *icu_localized_number_formatter, numeric_obj));
|
||||
}
|
||||
|
||||
// Common code for NumberFormatPrototypeFormtRange(|ToParts)
|
||||
template <class T, MaybeHandle<T> (*F)(Isolate*, Handle<JSNumberFormat>,
|
||||
Handle<Object>, Handle<Object>)>
|
||||
V8_WARN_UNUSED_RESULT Object NumberFormatRange(BuiltinArguments args,
|
||||
Isolate* isolate,
|
||||
const char* const method_name) {
|
||||
// 1. Let nf be this value.
|
||||
// 2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
|
||||
CHECK_RECEIVER(JSNumberFormat, nf, method_name);
|
||||
|
||||
Handle<Object> start = args.atOrUndefined(isolate, 1);
|
||||
Handle<Object> end = args.atOrUndefined(isolate, 2);
|
||||
|
||||
Factory* factory = isolate->factory();
|
||||
// 3. If start is undefined or end is undefined, throw a TypeError exception.
|
||||
if (start->IsUndefined(isolate)) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate,
|
||||
NewTypeError(MessageTemplate::kInvalid,
|
||||
factory->NewStringFromStaticChars("start"), start));
|
||||
}
|
||||
if (end->IsUndefined(isolate)) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate, NewTypeError(MessageTemplate::kInvalid,
|
||||
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));
|
||||
}
|
||||
|
||||
BUILTIN(NumberFormatPrototypeFormatRange) {
|
||||
const char* const method_name = "Intl.NumberFormat.prototype.formatRange";
|
||||
HandleScope handle_scope(isolate);
|
||||
return NumberFormatRange<String, JSNumberFormat::FormatNumericRange>(
|
||||
args, isolate, method_name);
|
||||
}
|
||||
|
||||
BUILTIN(NumberFormatPrototypeFormatRangeToParts) {
|
||||
const char* const method_name =
|
||||
"Intl.NumberFormat.prototype.formatRangeToParts";
|
||||
HandleScope handle_scope(isolate);
|
||||
return NumberFormatRange<JSArray, JSNumberFormat::FormatNumericRangeToParts>(
|
||||
args, isolate, method_name);
|
||||
}
|
||||
|
||||
BUILTIN(DateTimeFormatConstructor) {
|
||||
HandleScope scope(isolate);
|
||||
|
||||
|
@ -4427,7 +4427,6 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_error_cause)
|
||||
|
||||
#ifdef V8_INTL_SUPPORT
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_best_fit_matcher)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_number_format_v3)
|
||||
#endif // V8_INTL_SUPPORT
|
||||
|
||||
#undef EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE
|
||||
@ -5335,6 +5334,32 @@ void Genesis::InitializeGlobal_harmony_intl_locale_info() {
|
||||
Builtin::kLocalePrototypeWeekInfo, true);
|
||||
}
|
||||
|
||||
void Genesis::InitializeGlobal_harmony_intl_number_format_v3() {
|
||||
if (!FLAG_harmony_intl_number_format_v3) return;
|
||||
|
||||
Handle<JSObject> intl = Handle<JSObject>::cast(
|
||||
JSReceiver::GetProperty(
|
||||
isolate(),
|
||||
Handle<JSReceiver>(native_context()->global_object(), isolate()),
|
||||
factory()->InternalizeUtf8String("Intl"))
|
||||
.ToHandleChecked());
|
||||
|
||||
Handle<JSFunction> number_format_constructor = Handle<JSFunction>::cast(
|
||||
JSReceiver::GetProperty(
|
||||
isolate(), Handle<JSReceiver>(JSReceiver::cast(*intl), isolate()),
|
||||
factory()->InternalizeUtf8String("NumberFormat"))
|
||||
.ToHandleChecked());
|
||||
|
||||
Handle<JSObject> prototype(
|
||||
JSObject::cast(number_format_constructor->prototype()), isolate());
|
||||
|
||||
SimpleInstallFunction(isolate(), prototype, "formatRange",
|
||||
Builtin::kNumberFormatPrototypeFormatRange, 2, false);
|
||||
SimpleInstallFunction(isolate(), prototype, "formatRangeToParts",
|
||||
Builtin::kNumberFormatPrototypeFormatRangeToParts, 2,
|
||||
false);
|
||||
}
|
||||
|
||||
void Genesis::InitializeGlobal_harmony_intl_enumeration() {
|
||||
if (!FLAG_harmony_intl_enumeration) return;
|
||||
|
||||
|
@ -85,6 +85,7 @@
|
||||
V(_, minimumFractionDigits_string, "minimumFractionDigits") \
|
||||
V(_, minimumIntegerDigits_string, "minimumIntegerDigits") \
|
||||
V(_, minimumSignificantDigits_string, "minimumSignificantDigits") \
|
||||
V(_, minus_0, "-0") \
|
||||
V(_, minusSign_string, "minusSign") \
|
||||
V(_, morePrecision_string, "morePrecision") \
|
||||
V(_, nan_string, "nan") \
|
||||
|
@ -2916,5 +2916,45 @@ MaybeHandle<Object> Intl::ToIntlMathematicalValueAsNumberBigIntOrString(
|
||||
return input;
|
||||
}
|
||||
|
||||
Intl::FormatRangeSourceTracker::FormatRangeSourceTracker() {
|
||||
start_[0] = start_[1] = limit_[0] = limit_[1] = 0;
|
||||
}
|
||||
|
||||
void Intl::FormatRangeSourceTracker::Add(int32_t field, int32_t start,
|
||||
int32_t limit) {
|
||||
DCHECK_LT(field, 2);
|
||||
start_[field] = start;
|
||||
limit_[field] = limit;
|
||||
}
|
||||
|
||||
Intl::FormatRangeSource Intl::FormatRangeSourceTracker::GetSource(
|
||||
int32_t start, int32_t limit) const {
|
||||
FormatRangeSource source = FormatRangeSource::kShared;
|
||||
if (FieldContains(0, start, limit)) {
|
||||
source = FormatRangeSource::kStartRange;
|
||||
} else if (FieldContains(1, start, limit)) {
|
||||
source = FormatRangeSource::kEndRange;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
bool Intl::FormatRangeSourceTracker::FieldContains(int32_t field, int32_t start,
|
||||
int32_t limit) const {
|
||||
DCHECK_LT(field, 2);
|
||||
return (start_[field] <= start) && (start <= limit_[field]) &&
|
||||
(start_[field] <= limit) && (limit <= limit_[field]);
|
||||
}
|
||||
|
||||
Handle<String> Intl::SourceString(Isolate* isolate, FormatRangeSource source) {
|
||||
switch (source) {
|
||||
case FormatRangeSource::kShared:
|
||||
return ReadOnlyRoots(isolate).shared_string_handle();
|
||||
case FormatRangeSource::kStartRange:
|
||||
return ReadOnlyRoots(isolate).startRange_string_handle();
|
||||
case FormatRangeSource::kEndRange:
|
||||
return ReadOnlyRoots(isolate).endRange_string_handle();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -59,6 +59,24 @@ class Intl {
|
||||
kLength
|
||||
};
|
||||
|
||||
enum class FormatRangeSource { kShared, kStartRange, kEndRange };
|
||||
|
||||
class FormatRangeSourceTracker {
|
||||
public:
|
||||
FormatRangeSourceTracker();
|
||||
void Add(int32_t field, int32_t start, int32_t limit);
|
||||
FormatRangeSource GetSource(int32_t start, int32_t limit) const;
|
||||
|
||||
private:
|
||||
int32_t start_[2];
|
||||
int32_t limit_[2];
|
||||
|
||||
bool FieldContains(int32_t field, int32_t start, int32_t limit) const;
|
||||
};
|
||||
|
||||
static Handle<String> SourceString(Isolate* isolate,
|
||||
FormatRangeSource source);
|
||||
|
||||
// Build a set of ICU locales from a list of Locales. If there is a locale
|
||||
// with a script tag then the locales also include a locale without the
|
||||
// script; eg, pa_Guru_IN (language=Panjabi, script=Gurmukhi, country-India)
|
||||
|
@ -2101,56 +2101,12 @@ Handle<String> JSDateTimeFormat::HourCycleAsString() const {
|
||||
}
|
||||
}
|
||||
|
||||
enum Source { kShared, kStartRange, kEndRange };
|
||||
|
||||
namespace {
|
||||
|
||||
class SourceTracker {
|
||||
public:
|
||||
SourceTracker() { start_[0] = start_[1] = limit_[0] = limit_[1] = 0; }
|
||||
void Add(int32_t field, int32_t start, int32_t limit) {
|
||||
DCHECK_LT(field, 2);
|
||||
start_[field] = start;
|
||||
limit_[field] = limit;
|
||||
}
|
||||
|
||||
Source GetSource(int32_t start, int32_t limit) const {
|
||||
Source source = Source::kShared;
|
||||
if (FieldContains(0, start, limit)) {
|
||||
source = Source::kStartRange;
|
||||
} else if (FieldContains(1, start, limit)) {
|
||||
source = Source::kEndRange;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t start_[2];
|
||||
int32_t limit_[2];
|
||||
|
||||
bool FieldContains(int32_t field, int32_t start, int32_t limit) const {
|
||||
DCHECK_LT(field, 2);
|
||||
return (start_[field] <= start) && (start <= limit_[field]) &&
|
||||
(start_[field] <= limit) && (limit <= limit_[field]);
|
||||
}
|
||||
};
|
||||
|
||||
Handle<String> SourceString(Isolate* isolate, Source source) {
|
||||
switch (source) {
|
||||
case Source::kShared:
|
||||
return ReadOnlyRoots(isolate).shared_string_handle();
|
||||
case Source::kStartRange:
|
||||
return ReadOnlyRoots(isolate).startRange_string_handle();
|
||||
case Source::kEndRange:
|
||||
return ReadOnlyRoots(isolate).endRange_string_handle();
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<bool> AddPartForFormatRange(Isolate* isolate, Handle<JSArray> array,
|
||||
const icu::UnicodeString& string,
|
||||
int32_t index, int32_t field, int32_t start,
|
||||
int32_t end, const SourceTracker& tracker) {
|
||||
Maybe<bool> AddPartForFormatRange(
|
||||
Isolate* isolate, Handle<JSArray> array, const icu::UnicodeString& string,
|
||||
int32_t index, int32_t field, int32_t start, int32_t end,
|
||||
const Intl::FormatRangeSourceTracker& tracker) {
|
||||
Handle<String> substring;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, substring,
|
||||
Intl::ToString(isolate, string, start, end),
|
||||
@ -2158,7 +2114,7 @@ Maybe<bool> AddPartForFormatRange(Isolate* isolate, Handle<JSArray> array,
|
||||
Intl::AddElement(isolate, array, index,
|
||||
IcuDateFieldIdToDateType(field, isolate), substring,
|
||||
isolate->factory()->source_string(),
|
||||
SourceString(isolate, tracker.GetSource(start, end)));
|
||||
Intl::SourceString(isolate, tracker.GetSource(start, end)));
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
@ -2193,7 +2149,7 @@ MaybeHandle<JSArray> FormattedDateIntervalToJSArray(
|
||||
icu::ConstrainedFieldPosition cfpos;
|
||||
int index = 0;
|
||||
int32_t previous_end_pos = 0;
|
||||
SourceTracker tracker;
|
||||
Intl::FormatRangeSourceTracker tracker;
|
||||
*outputRange = false;
|
||||
while (formatted.nextPosition(cfpos, status)) {
|
||||
int32_t category = cfpos.getCategory();
|
||||
@ -2241,13 +2197,11 @@ MaybeHandle<JSArray> FormattedDateIntervalToJSArray(
|
||||
}
|
||||
|
||||
// The shared code between formatRange and formatRangeToParts
|
||||
template <typename T>
|
||||
MaybeHandle<T> FormatRangeCommon(
|
||||
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
|
||||
double y,
|
||||
const std::function<MaybeHandle<T>(Isolate*, const icu::FormattedValue&,
|
||||
bool*)>& formatToResult,
|
||||
bool* outputRange) {
|
||||
template <typename T,
|
||||
MaybeHandle<T> (*F)(Isolate*, const icu::FormattedValue&, bool*)>
|
||||
MaybeHandle<T> FormatRangeCommon(Isolate* isolate,
|
||||
Handle<JSDateTimeFormat> date_time_format,
|
||||
double x, double y, bool* outputRange) {
|
||||
// Track newer feature formateRange and formatRangeToParts
|
||||
isolate->CountUsage(v8::Isolate::UseCounterFeature::kDateTimeFormatRange);
|
||||
|
||||
@ -2290,7 +2244,7 @@ MaybeHandle<T> FormatRangeCommon(
|
||||
if (U_FAILURE(status)) {
|
||||
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T);
|
||||
}
|
||||
return formatToResult(isolate, formatted, outputRange);
|
||||
return F(isolate, formatted, outputRange);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@ -2299,8 +2253,8 @@ MaybeHandle<String> JSDateTimeFormat::FormatRange(
|
||||
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
|
||||
double y) {
|
||||
bool outputRange = true;
|
||||
MaybeHandle<String> ret = FormatRangeCommon<String>(
|
||||
isolate, date_time_format, x, y, FormattedToString, &outputRange);
|
||||
MaybeHandle<String> ret = FormatRangeCommon<String, FormattedToString>(
|
||||
isolate, date_time_format, x, y, &outputRange);
|
||||
if (outputRange) {
|
||||
return ret;
|
||||
}
|
||||
@ -2313,8 +2267,8 @@ MaybeHandle<JSArray> JSDateTimeFormat::FormatRangeToParts(
|
||||
double y) {
|
||||
bool outputRange = true;
|
||||
MaybeHandle<JSArray> ret =
|
||||
FormatRangeCommon<JSArray>(isolate, date_time_format, x, y,
|
||||
FormattedDateIntervalToJSArray, &outputRange);
|
||||
FormatRangeCommon<JSArray, FormattedDateIntervalToJSArray>(
|
||||
isolate, date_time_format, x, y, &outputRange);
|
||||
if (outputRange) {
|
||||
return ret;
|
||||
}
|
||||
|
@ -25,6 +25,9 @@ TQ_OBJECT_CONSTRUCTORS_IMPL(JSNumberFormat)
|
||||
ACCESSORS(JSNumberFormat, icu_number_formatter,
|
||||
Managed<icu::number::LocalizedNumberFormatter>,
|
||||
kIcuNumberFormatterOffset)
|
||||
ACCESSORS(JSNumberFormat, icu_number_range_formatter,
|
||||
Managed<icu::number::LocalizedNumberRangeFormatter>,
|
||||
kIcuNumberRangeFormatterOffset)
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "unicode/currunit.h"
|
||||
#include "unicode/locid.h"
|
||||
#include "unicode/numberformatter.h"
|
||||
#include "unicode/numberrangeformatter.h"
|
||||
#include "unicode/numsys.h"
|
||||
#include "unicode/ucurr.h"
|
||||
#include "unicode/uloc.h"
|
||||
@ -1610,12 +1611,24 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::New(Isolate* isolate,
|
||||
icu::number::LocalizedNumberFormatter icu_number_formatter =
|
||||
settings.locale(icu_locale);
|
||||
|
||||
icu::number::LocalizedNumberRangeFormatter icu_number_range_formatter =
|
||||
icu::number::UnlocalizedNumberRangeFormatter()
|
||||
.numberFormatterBoth(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));
|
||||
|
||||
Handle<Managed<icu::number::LocalizedNumberRangeFormatter>>
|
||||
managed_number_range_formatter =
|
||||
Managed<icu::number::LocalizedNumberRangeFormatter>::FromRawPtr(
|
||||
isolate, 0,
|
||||
new icu::number::LocalizedNumberRangeFormatter(
|
||||
icu_number_range_formatter));
|
||||
|
||||
// Now all properties are ready, so we can allocate the result object.
|
||||
Handle<JSNumberFormat> number_format = Handle<JSNumberFormat>::cast(
|
||||
isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
|
||||
@ -1623,6 +1636,8 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::New(Isolate* isolate,
|
||||
number_format->set_locale(*locale_str);
|
||||
|
||||
number_format->set_icu_number_formatter(*managed_number_formatter);
|
||||
number_format->set_icu_number_range_formatter(
|
||||
*managed_number_range_formatter);
|
||||
number_format->set_bound_format(*factory->undefined_value());
|
||||
|
||||
// 31. Return numberFormat.
|
||||
@ -1653,6 +1668,8 @@ Maybe<bool> IcuFormatNumber(
|
||||
*formatted = number_format.formatDecimal({char_buffer, length}, status);
|
||||
} else {
|
||||
if (FLAG_harmony_intl_number_format_v3 && numeric_obj->IsString()) {
|
||||
// TODO(ftang) Correct the handling of string after the resolution of
|
||||
// https://github.com/tc39/proposal-intl-numberformat-v3/pull/82
|
||||
Handle<String> string =
|
||||
String::Flatten(isolate, Handle<String>::cast(numeric_obj));
|
||||
DisallowGarbageCollection no_gc;
|
||||
@ -1687,6 +1704,38 @@ Maybe<bool> IcuFormatNumber(
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// FormatRange(|ToParts) does not allow NaN
|
||||
DCHECK(!obj->IsNaN());
|
||||
return Just(icu::Formattable(obj->Number()));
|
||||
}
|
||||
|
||||
bool cmp_NumberFormatSpan(const NumberFormatSpan& a,
|
||||
const NumberFormatSpan& b) {
|
||||
// Regions that start earlier should be encountered earlier.
|
||||
@ -1797,7 +1846,7 @@ std::vector<NumberFormatSpan> FlattenRegionsToParts(
|
||||
namespace {
|
||||
Maybe<int> ConstructParts(Isolate* isolate, icu::FormattedValue* formatted,
|
||||
Handle<JSArray> result, int start_index,
|
||||
bool style_is_unit, bool is_nan) {
|
||||
bool style_is_unit, bool is_nan, bool output_source) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
icu::UnicodeString formatted_text = formatted->toString(status);
|
||||
if (U_FAILURE(status)) {
|
||||
@ -1814,12 +1863,21 @@ Maybe<int> ConstructParts(Isolate* isolate, icu::FormattedValue* formatted,
|
||||
// there's another field with exactly the same begin and end as this backdrop,
|
||||
// in which case the backdrop's field_id of -1 will give it lower priority.
|
||||
regions.push_back(NumberFormatSpan(-1, 0, formatted_text.length()));
|
||||
Intl::FormatRangeSourceTracker tracker;
|
||||
{
|
||||
icu::ConstrainedFieldPosition cfp;
|
||||
cfp.constrainCategory(UFIELD_CATEGORY_NUMBER);
|
||||
while (formatted->nextPosition(cfp, status)) {
|
||||
regions.push_back(
|
||||
NumberFormatSpan(cfp.getField(), cfp.getStart(), cfp.getLimit()));
|
||||
icu::ConstrainedFieldPosition cfpos;
|
||||
while (formatted->nextPosition(cfpos, status)) {
|
||||
int32_t category = cfpos.getCategory();
|
||||
int32_t field = cfpos.getField();
|
||||
int32_t start = cfpos.getStart();
|
||||
int32_t limit = cfpos.getLimit();
|
||||
if (category == UFIELD_CATEGORY_NUMBER_RANGE_SPAN) {
|
||||
DCHECK_LE(field, 2);
|
||||
DCHECK(FLAG_harmony_intl_number_format_v3);
|
||||
tracker.Add(field, start, limit);
|
||||
} else {
|
||||
regions.push_back(NumberFormatSpan(field, start, limit));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1844,13 +1902,168 @@ Maybe<int> ConstructParts(Isolate* isolate, icu::FormattedValue* formatted,
|
||||
Intl::ToString(isolate, formatted_text, part.begin_pos, part.end_pos),
|
||||
Nothing<int>());
|
||||
|
||||
Intl::AddElement(isolate, result, index, field_type_string, substring);
|
||||
if (output_source) {
|
||||
Intl::AddElement(
|
||||
isolate, result, index, field_type_string, substring,
|
||||
isolate->factory()->source_string(),
|
||||
Intl::SourceString(isolate,
|
||||
tracker.GetSource(part.begin_pos, part.end_pos)));
|
||||
} else {
|
||||
Intl::AddElement(isolate, result, index, field_type_string, substring);
|
||||
}
|
||||
++index;
|
||||
}
|
||||
JSObject::ValidateElements(*result);
|
||||
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*, icu::FormattedValue*,
|
||||
const icu::number::LocalizedNumberFormatter*, bool)>
|
||||
MaybeHandle<T> PartitionNumberRangePattern(Isolate* isolate,
|
||||
Handle<JSNumberFormat> number_format,
|
||||
Handle<Object> x, Handle<Object> y,
|
||||
const char* func_name) {
|
||||
Factory* factory = isolate->factory();
|
||||
|
||||
// 1. If x is NaN or y is NaN, throw a RangeError exception.
|
||||
if (x->IsNaN()) {
|
||||
THROW_NEW_ERROR_RETURN_VALUE(
|
||||
isolate,
|
||||
NewRangeError(MessageTemplate::kInvalid,
|
||||
factory->NewStringFromStaticChars("start"), x),
|
||||
MaybeHandle<T>());
|
||||
}
|
||||
if (y->IsNaN()) {
|
||||
THROW_NEW_ERROR_RETURN_VALUE(
|
||||
isolate,
|
||||
NewRangeError(MessageTemplate::kInvalid,
|
||||
factory->NewStringFromStaticChars("end"), y),
|
||||
MaybeHandle<T>());
|
||||
}
|
||||
|
||||
// 2. If x is a mathematical value, then
|
||||
if (IsFiniteNonMinusZeroNumberOrBigInt(isolate, x)) {
|
||||
// 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>());
|
||||
}
|
||||
// b. Else if y is -∞, throw a RangeError exception.
|
||||
if (IsNegativeInfinity(isolate, y)) {
|
||||
THROW_NEW_ERROR_RETURN_VALUE(
|
||||
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
|
||||
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))) {
|
||||
THROW_NEW_ERROR_RETURN_VALUE(
|
||||
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
|
||||
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)) {
|
||||
THROW_NEW_ERROR_RETURN_VALUE(
|
||||
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
|
||||
MaybeHandle<T>());
|
||||
}
|
||||
// b. Else if y is -∞, throw a RangeError exception.
|
||||
if (IsNegativeInfinity(isolate, y)) {
|
||||
THROW_NEW_ERROR_RETURN_VALUE(
|
||||
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
|
||||
MaybeHandle<T>());
|
||||
}
|
||||
// c. Else if y is -0, throw a RangeError exception.
|
||||
if (IsNegativeZero(isolate, y)) {
|
||||
THROW_NEW_ERROR_RETURN_VALUE(
|
||||
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
|
||||
MaybeHandle<T>());
|
||||
}
|
||||
// 4. Else if x is -0, then
|
||||
} else if (IsNegativeZero(isolate, x)) {
|
||||
// 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))) {
|
||||
THROW_NEW_ERROR_RETURN_VALUE(
|
||||
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
|
||||
MaybeHandle<T>());
|
||||
}
|
||||
// b. Else if y is -∞, throw a RangeError exception.
|
||||
if (IsNegativeZero(isolate, y)) {
|
||||
THROW_NEW_ERROR_RETURN_VALUE(
|
||||
isolate, NewRangeError(MessageTemplate::kInvalid, x, y),
|
||||
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>());
|
||||
|
||||
icu::number::LocalizedNumberRangeFormatter* nrfmt =
|
||||
number_format->icu_number_range_formatter().raw();
|
||||
CHECK_NOT_NULL(nrfmt);
|
||||
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>());
|
||||
}
|
||||
|
||||
return F(isolate, &formatted, number_format->icu_number_formatter().raw(),
|
||||
false /* is_nan */);
|
||||
}
|
||||
|
||||
MaybeHandle<String> FormatToString(Isolate* isolate,
|
||||
icu::FormattedValue* formatted,
|
||||
const icu::number::LocalizedNumberFormatter*,
|
||||
@ -1865,19 +2078,26 @@ MaybeHandle<String> FormatToString(Isolate* isolate,
|
||||
|
||||
MaybeHandle<JSArray> FormatToJSArray(
|
||||
Isolate* isolate, icu::FormattedValue* formatted,
|
||||
const icu::number::LocalizedNumberFormatter* nfmt, bool is_nan) {
|
||||
const icu::number::LocalizedNumberFormatter* nfmt, bool is_nan,
|
||||
bool output_source) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
bool is_unit = Style::UNIT == StyleFromSkeleton(nfmt->toSkeleton(status));
|
||||
CHECK(U_SUCCESS(status));
|
||||
|
||||
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);
|
||||
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>());
|
||||
return result;
|
||||
}
|
||||
|
||||
MaybeHandle<JSArray> FormatRangeToJSArray(
|
||||
Isolate* isolate, icu::FormattedValue* formatted,
|
||||
const icu::number::LocalizedNumberFormatter* nfmt, bool is_nan) {
|
||||
return FormatToJSArray(isolate, formatted, nfmt, is_nan, true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MaybeHandle<String> JSNumberFormat::FormatNumeric(
|
||||
@ -1908,7 +2128,23 @@ MaybeHandle<JSArray> JSNumberFormat::FormatToParts(
|
||||
IcuFormatNumber(isolate, *fmt, numeric_obj, &formatted);
|
||||
MAYBE_RETURN(maybe_format, Handle<JSArray>());
|
||||
|
||||
return FormatToJSArray(isolate, &formatted, fmt, numeric_obj->IsNaN());
|
||||
return FormatToJSArray(isolate, &formatted, fmt, numeric_obj->IsNaN(), false);
|
||||
}
|
||||
|
||||
MaybeHandle<String> JSNumberFormat::FormatNumericRange(
|
||||
Isolate* isolate, Handle<JSNumberFormat> number_format,
|
||||
Handle<Object> x_obj, Handle<Object> y_obj) {
|
||||
return PartitionNumberRangePattern<String, FormatToString>(
|
||||
isolate, number_format, x_obj, y_obj,
|
||||
"Intl.NumberFormat.prototype.formatRange");
|
||||
}
|
||||
|
||||
MaybeHandle<JSArray> JSNumberFormat::FormatNumericRangeToParts(
|
||||
Isolate* isolate, Handle<JSNumberFormat> number_format,
|
||||
Handle<Object> x_obj, Handle<Object> y_obj) {
|
||||
return PartitionNumberRangePattern<JSArray, FormatRangeToJSArray>(
|
||||
isolate, number_format, x_obj, y_obj,
|
||||
"Intl.NumberFormat.prototype.formatRangeToParts");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -27,6 +27,7 @@ class UnicodeString;
|
||||
namespace number {
|
||||
class LocalizedNumberFormatter;
|
||||
class UnlocalizedNumberFormatter;
|
||||
class LocalizedNumberRangeFormatter;
|
||||
} // namespace number
|
||||
} // namespace U_ICU_NAMESPACE
|
||||
|
||||
@ -55,6 +56,16 @@ class JSNumberFormat
|
||||
Isolate* isolate, Handle<JSNumberFormat> number_format,
|
||||
Handle<Object> numeric_obj);
|
||||
|
||||
// ecma402/#sec-formatnumericrange
|
||||
V8_WARN_UNUSED_RESULT static MaybeHandle<String> FormatNumericRange(
|
||||
Isolate* isolate, Handle<JSNumberFormat> number_format, Handle<Object> x,
|
||||
Handle<Object> y);
|
||||
|
||||
// ecma402/#sec-formatnumericrangetoparts
|
||||
V8_WARN_UNUSED_RESULT static MaybeHandle<JSArray> FormatNumericRangeToParts(
|
||||
Isolate* isolate, Handle<JSNumberFormat> number_format, Handle<Object> x,
|
||||
Handle<Object> y);
|
||||
|
||||
V8_WARN_UNUSED_RESULT static MaybeHandle<String> FormatNumeric(
|
||||
Isolate* isolate,
|
||||
const icu::number::LocalizedNumberFormatter& number_format,
|
||||
@ -81,6 +92,8 @@ class JSNumberFormat
|
||||
|
||||
DECL_ACCESSORS(icu_number_formatter,
|
||||
Managed<icu::number::LocalizedNumberFormatter>)
|
||||
DECL_ACCESSORS(icu_number_range_formatter,
|
||||
Managed<icu::number::LocalizedNumberRangeFormatter>)
|
||||
|
||||
TQ_OBJECT_CONSTRUCTORS(JSNumberFormat)
|
||||
};
|
||||
|
@ -8,5 +8,7 @@ extern class JSNumberFormat extends JSObject {
|
||||
locale: String;
|
||||
icu_number_formatter:
|
||||
Foreign; // Managed<icu::number::LocalizedNumberFormatter>
|
||||
icu_number_range_formatter:
|
||||
Foreign; // Managed<icu::number::LocalizedNumberRangeFormatter>
|
||||
bound_format: JSFunction|Undefined;
|
||||
}
|
||||
|
158
test/intl/number-format/format-range-v3.js
Normal file
158
test/intl/number-format/format-range-v3.js
Normal file
@ -0,0 +1,158 @@
|
||||
// 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
|
||||
|
||||
const validRanges = [[-12345, -5678], [-12345, 56789], [12345, 56789]];
|
||||
|
||||
const nf = new Intl.NumberFormat("en", {signDisplay: "exceptZero"});
|
||||
['formatRange', 'formatRangeToParts'].forEach(function(method) {
|
||||
assertEquals("function", typeof nf[method]);
|
||||
|
||||
// 2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
|
||||
// Assert if called without nf
|
||||
let f = nf[method];
|
||||
assertThrows(() => { f(1, 23) }, TypeError);
|
||||
|
||||
// Assert normal call success
|
||||
assertDoesNotThrow(() => nf[method](1, 23));
|
||||
|
||||
// 3. If start is undefined ..., throw a TypeError exception.
|
||||
assertThrows(() => { nf[method](undefined, 23) }, TypeError);
|
||||
// 3. If ... end is undefined, throw a TypeError exception.
|
||||
assertThrows(() => { nf[method](1, undefined) }, TypeError);
|
||||
|
||||
// 4. Let x be ? ToNumeric(start).
|
||||
// Verify it won't throw error
|
||||
assertDoesNotThrow(() => nf[method](null, 23));
|
||||
assertDoesNotThrow(() => nf[method](false, 23));
|
||||
assertDoesNotThrow(() => nf[method](true, 23));
|
||||
assertDoesNotThrow(() => nf[method](12, 23));
|
||||
assertDoesNotThrow(() => nf[method](12n, 23));
|
||||
// Verify it will throw error
|
||||
assertThrows(() => { nf[method](Symbol(12), 23) }, TypeError);
|
||||
|
||||
// 5. Let y be ? ToNumeric(end).
|
||||
// Verify it won't throw error
|
||||
assertDoesNotThrow(() => nf[method](-12, null));
|
||||
assertDoesNotThrow(() => nf[method](-12, false));
|
||||
assertDoesNotThrow(() => nf[method](-12, true));
|
||||
assertDoesNotThrow(() => nf[method](12, 23));
|
||||
assertDoesNotThrow(() => nf[method](12, 23n));
|
||||
|
||||
// Verify it will throw error
|
||||
assertThrows(() => { nf[method](12, Symbol(23)) }, TypeError);
|
||||
|
||||
// 6. If x is NaN ..., throw a RangeError exception.
|
||||
assertThrows(() => { nf[method](NaN, 23) }, RangeError);
|
||||
|
||||
// 6. If ... y is NaN, throw a RangeError exception.
|
||||
assertThrows(() => { nf[method](12, NaN) }, RangeError);
|
||||
|
||||
// 8. If x is greater than y, throw a RangeError exception.
|
||||
// neither x nor y are bigint.
|
||||
assertThrows(() => { nf[method](23, 12) }, RangeError);
|
||||
assertDoesNotThrow(() => nf[method](12, 23));
|
||||
// x is not bigint but y is.
|
||||
assertThrows(() => { nf[method](23, 12n) }, RangeError);
|
||||
assertDoesNotThrow(() => nf[method](12, 23n));
|
||||
// x is bigint but y is not.
|
||||
assertThrows(() => { nf[method](23n, 12) }, RangeError);
|
||||
assertDoesNotThrow(() => nf[method](12n, 23));
|
||||
// both x and y are bigint.
|
||||
assertThrows(() => { nf[method](23n, 12n) }, RangeError);
|
||||
assertDoesNotThrow(() => nf[method](12n, 23n));
|
||||
|
||||
validRanges.forEach(
|
||||
function([x, y]) {
|
||||
const X = BigInt(x);
|
||||
const Y = BigInt(y);
|
||||
const formatted_x_y = nf[method](x, y);
|
||||
const formatted_X_y = nf[method](X, y);
|
||||
const formatted_x_Y = nf[method](x, Y);
|
||||
const formatted_X_Y = nf[method](X, Y);
|
||||
assertEquals(formatted_x_y, formatted_X_y);
|
||||
assertEquals(formatted_x_y, formatted_x_Y);
|
||||
assertEquals(formatted_x_y, formatted_X_Y);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// Check the number of part with type: "plusSign" and "minusSign" are corre
|
||||
validRanges.forEach(
|
||||
function([x, y]) {
|
||||
const expectedPlus = (x > 0) ? ((y > 0) ? 2 : 1) : ((y > 0) ? 1 : 0);
|
||||
const expectedMinus = (x < 0) ? ((y < 0) ? 2 : 1) : ((y < 0) ? 1 : 0);
|
||||
let actualPlus = 0;
|
||||
let actualMinus = 0;
|
||||
const parts = nf.formatRangeToParts(x, y);
|
||||
parts.forEach(function(part) {
|
||||
if (part.type == "plusSign") actualPlus++;
|
||||
if (part.type == "minusSign") actualMinus++;
|
||||
});
|
||||
const method = "formatRangeToParts(" + x + ", " + y + "): ";
|
||||
assertEquals(expectedPlus, actualPlus,
|
||||
method + "Number of type: 'plusSign' in parts is incorrect");
|
||||
assertEquals(expectedMinus, actualMinus,
|
||||
method + "Number of type: 'minusSign' in parts is incorrect");
|
||||
});
|
||||
|
||||
// From https://github.com/tc39/proposal-intl-numberformat-v3#formatrange-ecma-402-393
|
||||
const nf2 = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
|
||||
// README.md said it expect "€3–5"
|
||||
assertEquals("€3 – €5", nf2.formatRange(3, 5));
|
||||
|
||||
const nf3 = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
const actual3 = nf3.formatRangeToParts(3, 5);
|
||||
/*
|
||||
[
|
||||
{type: "currency", value: "€", source: "startRange"}
|
||||
{type: "integer", value: "3", source: "startRange"}
|
||||
{type: "literal", value: "–", source: "shared"}
|
||||
{type: "integer", value: "5", source: "endRange"}
|
||||
]
|
||||
*/
|
||||
assertEquals(5, actual3.length);
|
||||
assertEquals("currency", actual3[0].type);
|
||||
assertEquals("€", actual3[0].value);
|
||||
assertEquals("startRange", actual3[0].source);
|
||||
assertEquals("integer", actual3[1].type);
|
||||
assertEquals("3", actual3[1].value);
|
||||
assertEquals("startRange", actual3[1].source);
|
||||
assertEquals("literal", actual3[2].type);
|
||||
assertEquals(" – ", actual3[2].value);
|
||||
assertEquals("shared", actual3[2].source);
|
||||
assertEquals("currency", actual3[3].type);
|
||||
assertEquals("€", actual3[3].value);
|
||||
assertEquals("endRange", actual3[3].source);
|
||||
assertEquals("integer", actual3[4].type);
|
||||
assertEquals("5", actual3[4].value);
|
||||
assertEquals("endRange", actual3[4].source);
|
||||
|
||||
const nf4 = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
assertEquals("~€3", nf4.formatRange(2.9, 3.1));
|
||||
|
||||
const nf5 = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
signDisplay: "always",
|
||||
});
|
||||
assertEquals("~+€3.00", nf5.formatRange(2.999, 3.001));
|
||||
|
||||
const nf6 = new Intl.NumberFormat("en");
|
||||
assertEquals("3–∞", nf6.formatRange(3, 1/0));
|
||||
assertThrows(() => { nf6.formatRange(3, 0/0); }, RangeError);
|
@ -2451,23 +2451,10 @@
|
||||
'intl402/NumberFormat/constructor-roundingIncrement': [FAIL],
|
||||
'intl402/NumberFormat/test-option-roundingPriority': [FAIL],
|
||||
# NumberFormat.prototype.formatRange
|
||||
'intl402/NumberFormat/prototype/formatRange/builtin': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRange/en-US': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRange/invoked-as-func': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRange/length': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRange/name': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRange/nan-arguments-throws': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRange/prop-desc': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRange/pt-PT': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRange/x-greater-than-y-throws': [FAIL],
|
||||
# NumberFormat.prototype.formatRangeToParts
|
||||
'intl402/NumberFormat/prototype/formatRangeToParts/builtin': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRangeToParts/en-US': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRangeToParts/invoked-as-func': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRangeToParts/length': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRangeToParts/name': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRangeToParts/nan-arguments-throws': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRangeToParts/prop-desc': [FAIL],
|
||||
'intl402/NumberFormat/prototype/formatRangeToParts/x-greater-than-y-throws': [FAIL],
|
||||
|
||||
# PluralRules.prototype.selectRange
|
||||
|
@ -377,76 +377,76 @@ KNOWN_MAPS = {
|
||||
("read_only_space", 0x03401): (131, "BasicBlockCountersMarkerMap"),
|
||||
("read_only_space", 0x03445): (147, "ArrayBoilerplateDescriptionMap"),
|
||||
("read_only_space", 0x03545): (161, "InterceptorInfoMap"),
|
||||
("read_only_space", 0x06005): (132, "PromiseFulfillReactionJobTaskMap"),
|
||||
("read_only_space", 0x0602d): (133, "PromiseRejectReactionJobTaskMap"),
|
||||
("read_only_space", 0x06055): (134, "CallableTaskMap"),
|
||||
("read_only_space", 0x0607d): (135, "CallbackTaskMap"),
|
||||
("read_only_space", 0x060a5): (136, "PromiseResolveThenableJobTaskMap"),
|
||||
("read_only_space", 0x060cd): (139, "FunctionTemplateInfoMap"),
|
||||
("read_only_space", 0x060f5): (140, "ObjectTemplateInfoMap"),
|
||||
("read_only_space", 0x0611d): (141, "AccessCheckInfoMap"),
|
||||
("read_only_space", 0x06145): (142, "AccessorInfoMap"),
|
||||
("read_only_space", 0x0616d): (143, "AccessorPairMap"),
|
||||
("read_only_space", 0x06195): (144, "AliasedArgumentsEntryMap"),
|
||||
("read_only_space", 0x061bd): (145, "AllocationMementoMap"),
|
||||
("read_only_space", 0x061e5): (148, "AsmWasmDataMap"),
|
||||
("read_only_space", 0x0620d): (149, "AsyncGeneratorRequestMap"),
|
||||
("read_only_space", 0x06235): (150, "BreakPointMap"),
|
||||
("read_only_space", 0x0625d): (151, "BreakPointInfoMap"),
|
||||
("read_only_space", 0x06285): (152, "CachedTemplateObjectMap"),
|
||||
("read_only_space", 0x062ad): (154, "CallSiteInfoMap"),
|
||||
("read_only_space", 0x062d5): (155, "ClassPositionsMap"),
|
||||
("read_only_space", 0x062fd): (156, "DebugInfoMap"),
|
||||
("read_only_space", 0x06325): (158, "ErrorStackDataMap"),
|
||||
("read_only_space", 0x0634d): (160, "FunctionTemplateRareDataMap"),
|
||||
("read_only_space", 0x06375): (162, "InterpreterDataMap"),
|
||||
("read_only_space", 0x0639d): (163, "ModuleRequestMap"),
|
||||
("read_only_space", 0x063c5): (164, "PromiseCapabilityMap"),
|
||||
("read_only_space", 0x063ed): (165, "PromiseReactionMap"),
|
||||
("read_only_space", 0x06415): (166, "PropertyDescriptorObjectMap"),
|
||||
("read_only_space", 0x0643d): (167, "PrototypeInfoMap"),
|
||||
("read_only_space", 0x06465): (168, "RegExpBoilerplateDescriptionMap"),
|
||||
("read_only_space", 0x0648d): (169, "ScriptMap"),
|
||||
("read_only_space", 0x064b5): (170, "ScriptOrModuleMap"),
|
||||
("read_only_space", 0x064dd): (171, "SourceTextModuleInfoEntryMap"),
|
||||
("read_only_space", 0x06505): (172, "StackFrameInfoMap"),
|
||||
("read_only_space", 0x0652d): (173, "TemplateObjectDescriptionMap"),
|
||||
("read_only_space", 0x06555): (174, "Tuple2Map"),
|
||||
("read_only_space", 0x0657d): (175, "WasmContinuationObjectMap"),
|
||||
("read_only_space", 0x065a5): (176, "WasmExceptionTagMap"),
|
||||
("read_only_space", 0x065cd): (177, "WasmIndirectFunctionTableMap"),
|
||||
("read_only_space", 0x065f5): (197, "SloppyArgumentsElementsMap"),
|
||||
("read_only_space", 0x0661d): (233, "DescriptorArrayMap"),
|
||||
("read_only_space", 0x06645): (219, "UncompiledDataWithoutPreparseDataMap"),
|
||||
("read_only_space", 0x0666d): (217, "UncompiledDataWithPreparseDataMap"),
|
||||
("read_only_space", 0x06695): (220, "UncompiledDataWithoutPreparseDataWithJobMap"),
|
||||
("read_only_space", 0x066bd): (218, "UncompiledDataWithPreparseDataAndJobMap"),
|
||||
("read_only_space", 0x066e5): (251, "OnHeapBasicBlockProfilerDataMap"),
|
||||
("read_only_space", 0x0670d): (198, "TurbofanBitsetTypeMap"),
|
||||
("read_only_space", 0x06735): (202, "TurbofanUnionTypeMap"),
|
||||
("read_only_space", 0x0675d): (201, "TurbofanRangeTypeMap"),
|
||||
("read_only_space", 0x06785): (199, "TurbofanHeapConstantTypeMap"),
|
||||
("read_only_space", 0x067ad): (200, "TurbofanOtherNumberConstantTypeMap"),
|
||||
("read_only_space", 0x067d5): (247, "InternalClassMap"),
|
||||
("read_only_space", 0x067fd): (258, "SmiPairMap"),
|
||||
("read_only_space", 0x06825): (257, "SmiBoxMap"),
|
||||
("read_only_space", 0x0684d): (225, "ExportedSubClassBaseMap"),
|
||||
("read_only_space", 0x06875): (226, "ExportedSubClassMap"),
|
||||
("read_only_space", 0x0689d): (231, "AbstractInternalClassSubclass1Map"),
|
||||
("read_only_space", 0x068c5): (232, "AbstractInternalClassSubclass2Map"),
|
||||
("read_only_space", 0x068ed): (196, "InternalClassWithSmiElementsMap"),
|
||||
("read_only_space", 0x06915): (248, "InternalClassWithStructElementsMap"),
|
||||
("read_only_space", 0x0693d): (227, "ExportedSubClass2Map"),
|
||||
("read_only_space", 0x06965): (259, "SortStateMap"),
|
||||
("read_only_space", 0x0698d): (146, "AllocationSiteWithWeakNextMap"),
|
||||
("read_only_space", 0x069b5): (146, "AllocationSiteWithoutWeakNextMap"),
|
||||
("read_only_space", 0x069dd): (137, "LoadHandler1Map"),
|
||||
("read_only_space", 0x06a05): (137, "LoadHandler2Map"),
|
||||
("read_only_space", 0x06a2d): (137, "LoadHandler3Map"),
|
||||
("read_only_space", 0x06a55): (138, "StoreHandler0Map"),
|
||||
("read_only_space", 0x06a7d): (138, "StoreHandler1Map"),
|
||||
("read_only_space", 0x06aa5): (138, "StoreHandler2Map"),
|
||||
("read_only_space", 0x06acd): (138, "StoreHandler3Map"),
|
||||
("read_only_space", 0x06015): (132, "PromiseFulfillReactionJobTaskMap"),
|
||||
("read_only_space", 0x0603d): (133, "PromiseRejectReactionJobTaskMap"),
|
||||
("read_only_space", 0x06065): (134, "CallableTaskMap"),
|
||||
("read_only_space", 0x0608d): (135, "CallbackTaskMap"),
|
||||
("read_only_space", 0x060b5): (136, "PromiseResolveThenableJobTaskMap"),
|
||||
("read_only_space", 0x060dd): (139, "FunctionTemplateInfoMap"),
|
||||
("read_only_space", 0x06105): (140, "ObjectTemplateInfoMap"),
|
||||
("read_only_space", 0x0612d): (141, "AccessCheckInfoMap"),
|
||||
("read_only_space", 0x06155): (142, "AccessorInfoMap"),
|
||||
("read_only_space", 0x0617d): (143, "AccessorPairMap"),
|
||||
("read_only_space", 0x061a5): (144, "AliasedArgumentsEntryMap"),
|
||||
("read_only_space", 0x061cd): (145, "AllocationMementoMap"),
|
||||
("read_only_space", 0x061f5): (148, "AsmWasmDataMap"),
|
||||
("read_only_space", 0x0621d): (149, "AsyncGeneratorRequestMap"),
|
||||
("read_only_space", 0x06245): (150, "BreakPointMap"),
|
||||
("read_only_space", 0x0626d): (151, "BreakPointInfoMap"),
|
||||
("read_only_space", 0x06295): (152, "CachedTemplateObjectMap"),
|
||||
("read_only_space", 0x062bd): (154, "CallSiteInfoMap"),
|
||||
("read_only_space", 0x062e5): (155, "ClassPositionsMap"),
|
||||
("read_only_space", 0x0630d): (156, "DebugInfoMap"),
|
||||
("read_only_space", 0x06335): (158, "ErrorStackDataMap"),
|
||||
("read_only_space", 0x0635d): (160, "FunctionTemplateRareDataMap"),
|
||||
("read_only_space", 0x06385): (162, "InterpreterDataMap"),
|
||||
("read_only_space", 0x063ad): (163, "ModuleRequestMap"),
|
||||
("read_only_space", 0x063d5): (164, "PromiseCapabilityMap"),
|
||||
("read_only_space", 0x063fd): (165, "PromiseReactionMap"),
|
||||
("read_only_space", 0x06425): (166, "PropertyDescriptorObjectMap"),
|
||||
("read_only_space", 0x0644d): (167, "PrototypeInfoMap"),
|
||||
("read_only_space", 0x06475): (168, "RegExpBoilerplateDescriptionMap"),
|
||||
("read_only_space", 0x0649d): (169, "ScriptMap"),
|
||||
("read_only_space", 0x064c5): (170, "ScriptOrModuleMap"),
|
||||
("read_only_space", 0x064ed): (171, "SourceTextModuleInfoEntryMap"),
|
||||
("read_only_space", 0x06515): (172, "StackFrameInfoMap"),
|
||||
("read_only_space", 0x0653d): (173, "TemplateObjectDescriptionMap"),
|
||||
("read_only_space", 0x06565): (174, "Tuple2Map"),
|
||||
("read_only_space", 0x0658d): (175, "WasmContinuationObjectMap"),
|
||||
("read_only_space", 0x065b5): (176, "WasmExceptionTagMap"),
|
||||
("read_only_space", 0x065dd): (177, "WasmIndirectFunctionTableMap"),
|
||||
("read_only_space", 0x06605): (197, "SloppyArgumentsElementsMap"),
|
||||
("read_only_space", 0x0662d): (233, "DescriptorArrayMap"),
|
||||
("read_only_space", 0x06655): (219, "UncompiledDataWithoutPreparseDataMap"),
|
||||
("read_only_space", 0x0667d): (217, "UncompiledDataWithPreparseDataMap"),
|
||||
("read_only_space", 0x066a5): (220, "UncompiledDataWithoutPreparseDataWithJobMap"),
|
||||
("read_only_space", 0x066cd): (218, "UncompiledDataWithPreparseDataAndJobMap"),
|
||||
("read_only_space", 0x066f5): (251, "OnHeapBasicBlockProfilerDataMap"),
|
||||
("read_only_space", 0x0671d): (198, "TurbofanBitsetTypeMap"),
|
||||
("read_only_space", 0x06745): (202, "TurbofanUnionTypeMap"),
|
||||
("read_only_space", 0x0676d): (201, "TurbofanRangeTypeMap"),
|
||||
("read_only_space", 0x06795): (199, "TurbofanHeapConstantTypeMap"),
|
||||
("read_only_space", 0x067bd): (200, "TurbofanOtherNumberConstantTypeMap"),
|
||||
("read_only_space", 0x067e5): (247, "InternalClassMap"),
|
||||
("read_only_space", 0x0680d): (258, "SmiPairMap"),
|
||||
("read_only_space", 0x06835): (257, "SmiBoxMap"),
|
||||
("read_only_space", 0x0685d): (225, "ExportedSubClassBaseMap"),
|
||||
("read_only_space", 0x06885): (226, "ExportedSubClassMap"),
|
||||
("read_only_space", 0x068ad): (231, "AbstractInternalClassSubclass1Map"),
|
||||
("read_only_space", 0x068d5): (232, "AbstractInternalClassSubclass2Map"),
|
||||
("read_only_space", 0x068fd): (196, "InternalClassWithSmiElementsMap"),
|
||||
("read_only_space", 0x06925): (248, "InternalClassWithStructElementsMap"),
|
||||
("read_only_space", 0x0694d): (227, "ExportedSubClass2Map"),
|
||||
("read_only_space", 0x06975): (259, "SortStateMap"),
|
||||
("read_only_space", 0x0699d): (146, "AllocationSiteWithWeakNextMap"),
|
||||
("read_only_space", 0x069c5): (146, "AllocationSiteWithoutWeakNextMap"),
|
||||
("read_only_space", 0x069ed): (137, "LoadHandler1Map"),
|
||||
("read_only_space", 0x06a15): (137, "LoadHandler2Map"),
|
||||
("read_only_space", 0x06a3d): (137, "LoadHandler3Map"),
|
||||
("read_only_space", 0x06a65): (138, "StoreHandler0Map"),
|
||||
("read_only_space", 0x06a8d): (138, "StoreHandler1Map"),
|
||||
("read_only_space", 0x06ab5): (138, "StoreHandler2Map"),
|
||||
("read_only_space", 0x06add): (138, "StoreHandler3Map"),
|
||||
("map_space", 0x02149): (1057, "ExternalMap"),
|
||||
("map_space", 0x02171): (2115, "JSMessageObjectMap"),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user