WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
[patch]
Patch
bug-215438-20211026194619.patch (text/plain), 154.33 KB, created by
Yusuke Suzuki
on 2021-10-26 19:46:19 PDT
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Yusuke Suzuki
Created:
2021-10-26 19:46:19 PDT
Size:
154.33 KB
patch
obsolete
>Subversion Revision: 284916 >diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog >index e3df1ca7f79e05a7242fb165138a459277e07e8b..dadac1256b48da8744c38a5e7c5143f8356692b4 100644 >--- a/Source/JavaScriptCore/ChangeLog >+++ b/Source/JavaScriptCore/ChangeLog >@@ -1,3 +1,86 @@ >+2021-10-26 Yusuke Suzuki <ysuzuki@apple.com> >+ >+ [JSC] Implement IntlNumberFormat v3 >+ https://bugs.webkit.org/show_bug.cgi?id=215438 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ This patch implements part of Intl.NumberFormat v3 proposal[1]. >+ It adds (1) several new options to Intl.NumberFormat, (2) adds >+ formatRange and formatRangeToParts to Intl.NumberFormat and Intl.PluralRules, >+ and (3) adds toIntlMathematicalValue support, which allows some of Intl.NumberFormat >+ functions to take "string" decimal form. >+ >+ We cannot implement some features because it requires super new ICU. >+ >+ - trailingZeroDisplay (requires ICU 69) >+ - halfCeil / halfFloor (requires ICU 69) >+ - signDisplay: "negative" (requires ICU 69) >+ - formatRangeToParts (requires ICU 70) >+ >+ [1]: https://github.com/tc39/proposal-intl-numberformat-v3 >+ >+ * JavaScriptCore.xcodeproj/project.pbxproj: >+ * Sources.txt: >+ * runtime/BigIntPrototype.cpp: >+ (JSC::JSC_DEFINE_HOST_FUNCTION): >+ * runtime/CommonIdentifiers.h: >+ * runtime/IntlNumberFormat.cpp: >+ (JSC::UNumberFormatterDeleter::operator()): >+ (JSC::UNumberRangeFormatterDeleter::operator()): >+ (JSC::partTypeString): >+ (JSC::IntlNumberFormat::initializeNumberFormat): >+ (JSC::IntlNumberFormat::format const): >+ (JSC::IntlNumberFormat::formatRange const): >+ (JSC::IntlNumberFormat::signDisplayString): >+ (JSC::IntlNumberFormat::roundingModeString): >+ (JSC::IntlNumberFormat::trailingZeroDisplayString): >+ (JSC::IntlNumberFormat::roundingPriorityString): >+ (JSC::IntlNumberFormat::useGroupingValue): >+ (JSC::IntlNumberFormat::resolvedOptions const): >+ (JSC::IntlNumberFormat::formatToPartsInternal): >+ (JSC::IntlNumberFormat::formatToParts const): >+ * runtime/IntlNumberFormat.h: >+ (JSC::IntlMathematicalValue::IntlMathematicalValue): >+ (JSC::IntlMathematicalValue::ensureNonDouble): >+ (JSC::IntlMathematicalValue::numberType const): >+ (JSC::IntlMathematicalValue::sign const): >+ (JSC::IntlMathematicalValue::tryGetDouble const): >+ (JSC::IntlMathematicalValue::getString const): >+ (JSC::IntlMathematicalValue::numberTypeFromDouble): >+ * runtime/IntlNumberFormatInlines.h: >+ (JSC::setNumberFormatDigitOptions): >+ (JSC::appendNumberFormatDigitOptionsToSkeleton): >+ (JSC::toIntlMathematicalValue): >+ * runtime/IntlNumberFormatPrototype.cpp: >+ (JSC::IntlNumberFormatPrototype::create): >+ (JSC::IntlNumberFormatPrototype::finishCreation): >+ (JSC::JSC_DEFINE_HOST_FUNCTION): >+ * runtime/IntlNumberFormatPrototype.h: >+ * runtime/IntlObjectInlines.h: >+ (JSC::intlStringOrBooleanOption): >+ * runtime/IntlPluralRules.cpp: >+ (JSC::UPluralRulesDeleter::operator()): >+ (JSC::IntlPluralRules::initializePluralRules): >+ (JSC::IntlPluralRules::resolvedOptions const): >+ (JSC::IntlPluralRules::select const): >+ (JSC::IntlPluralRules::selectRange const): >+ * runtime/IntlPluralRules.h: >+ * runtime/IntlPluralRulesPrototype.cpp: >+ (JSC::IntlPluralRulesPrototype::create): >+ (JSC::IntlPluralRulesPrototype::finishCreation): >+ (JSC::JSC_DEFINE_HOST_FUNCTION): >+ * runtime/IntlPluralRulesPrototype.h: >+ * runtime/IntlRelativeTimeFormat.cpp: >+ (JSC::IntlRelativeTimeFormat::formatToParts const): >+ * runtime/JSBigInt.h: >+ (JSC::JSBigInt::tryExtractDouble): >+ * runtime/MathCommon.h: >+ (JSC::isNegativeZero): >+ * runtime/TemporalObject.cpp: >+ (JSC::roundNumberToIncrement): >+ * runtime/TemporalObject.h: >+ > 2021-10-26 Commit Queue <commit-queue@webkit.org> > > Unreviewed, reverting r284255. >diff --git a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj >index ec56c10eaa0e4f06a2e30e3aab646a0f0dbb68c8..68534746d0a883edd4b450d58ee8585ae7d3885d 100644 >--- a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj >+++ b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj >@@ -1845,6 +1845,7 @@ > E307178C24C7829800DF0644 /* IntlLocalePrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = A3AFF930245A3CFC00C9BA3B /* IntlLocalePrototype.h */; }; > E307178D24C7829A00DF0644 /* IntlLocaleConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = A3AFF92C245A3CFA00C9BA3B /* IntlLocaleConstructor.h */; }; > E307178E24C7829D00DF0644 /* IntlLocale.h in Headers */ = {isa = PBXBuildFile; fileRef = A3AFF92B245A3CF900C9BA3B /* IntlLocale.h */; }; >+ E30873E7272559410053B601 /* IntlPluralRules.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7D5FB18F20744BF1005DDF64 /* IntlPluralRules.cpp */; }; > E30E8A5426DE2E4800DA4915 /* TemporalTimeZonePrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = E30E8A4E26DE2E4700DA4915 /* TemporalTimeZonePrototype.h */; }; > E30E8A5626DE2E4800DA4915 /* TemporalTimeZone.h in Headers */ = {isa = PBXBuildFile; fileRef = E30E8A5026DE2E4800DA4915 /* TemporalTimeZone.h */; }; > E30E8A5726DE2E4800DA4915 /* TemporalTimeZoneConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = E30E8A5126DE2E4800DA4915 /* TemporalTimeZoneConstructor.h */; }; >@@ -1916,6 +1917,7 @@ > E3711992253FA87F00BA69A0 /* Gate.h in Headers */ = {isa = PBXBuildFile; fileRef = E3711991253FA87E00BA69A0 /* Gate.h */; settings = {ATTRIBUTES = (Private, ); }; }; > E374166E26912BC700C80789 /* ObjectConstructorInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E374166D26912BC700C80789 /* ObjectConstructorInlines.h */; }; > E3750CC82502E87E006A0AAB /* IntlDateTimeFormatInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E3750CC72502E87E006A0AAB /* IntlDateTimeFormatInlines.h */; }; >+ E378DC8D2727629400427B0B /* IntlNumberFormat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A1D792F61B43864B004516F5 /* IntlNumberFormat.cpp */; }; > E3794E761B77EB97005543AE /* ModuleAnalyzer.h in Headers */ = {isa = PBXBuildFile; fileRef = E3794E741B77EB97005543AE /* ModuleAnalyzer.h */; settings = {ATTRIBUTES = (Private, ); }; }; > E383500A2390D93B0036316D /* WasmGlobal.h in Headers */ = {isa = PBXBuildFile; fileRef = E38350092390D9370036316D /* WasmGlobal.h */; }; > E3850B15226ED641009ABF9C /* DFGMinifiedIDInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E3850B14226ED63E009ABF9C /* DFGMinifiedIDInlines.h */; }; >@@ -11832,6 +11834,8 @@ > 5C4196622270E0000047B7CD /* InspectorBackendDispatcherCompatibility.cpp in Sources */, > E399AEC32559457F00B78485 /* IntlDateTimeFormat.cpp in Sources */, > E366441E254409B30001876F /* IntlListFormat.cpp in Sources */, >+ E378DC8D2727629400427B0B /* IntlNumberFormat.cpp in Sources */, >+ E30873E7272559410053B601 /* IntlPluralRules.cpp in Sources */, > A3EE8543262514B000FC9B8D /* IntlWorkaround.cpp in Sources */, > E38E8790254B978400F6F9E4 /* JSDateMath.cpp in Sources */, > 536B319E1F735F160037FC33 /* LowLevelInterpreter.cpp in Sources */, >diff --git a/Source/JavaScriptCore/Sources.txt b/Source/JavaScriptCore/Sources.txt >index dd8cb627241be88ceef07459c85ef3c8a9f8c837..864ad3ad730fbf4471a3c32d726ca939ade0ba4d 100644 >--- a/Source/JavaScriptCore/Sources.txt >+++ b/Source/JavaScriptCore/Sources.txt >@@ -835,11 +835,11 @@ runtime/IntlListFormatPrototype.cpp > runtime/IntlLocale.cpp > runtime/IntlLocaleConstructor.cpp > runtime/IntlLocalePrototype.cpp >-runtime/IntlNumberFormat.cpp >+runtime/IntlNumberFormat.cpp @no-unify // Confine U_HIDE_DRAFT_API's effect in this file. > runtime/IntlNumberFormatConstructor.cpp > runtime/IntlNumberFormatPrototype.cpp > runtime/IntlObject.cpp >-runtime/IntlPluralRules.cpp >+runtime/IntlPluralRules.cpp @no-unify // Confine U_HIDE_DRAFT_API's effect to this file. > runtime/IntlPluralRulesConstructor.cpp > runtime/IntlPluralRulesPrototype.cpp > runtime/IntlRelativeTimeFormat.cpp >diff --git a/Source/JavaScriptCore/runtime/BigIntPrototype.cpp b/Source/JavaScriptCore/runtime/BigIntPrototype.cpp >index 9dc0fe2b6047fb2e985905f2de6313c27d2d0884..97b23e5ebe529be64971a73cc7e5c6568bf4b88c 100644 >--- a/Source/JavaScriptCore/runtime/BigIntPrototype.cpp >+++ b/Source/JavaScriptCore/runtime/BigIntPrototype.cpp >@@ -29,7 +29,7 @@ > > #include "BigIntObject.h" > #include "IntegrityInlines.h" >-#include "IntlNumberFormat.h" >+#include "IntlNumberFormatInlines.h" > #include "JSBigInt.h" > #include "JSCInlines.h" > #include "JSCast.h" >@@ -132,13 +132,20 @@ JSC_DEFINE_HOST_FUNCTION(bigIntProtoFuncToLocaleString, (JSGlobalObject* globalO > VM& vm = globalObject->vm(); > auto scope = DECLARE_THROW_SCOPE(vm); > >- JSBigInt* value = toThisBigIntValue(globalObject, callFrame->thisValue()); >+ JSBigInt* thisValue = toThisBigIntValue(globalObject, callFrame->thisValue()); > RETURN_IF_EXCEPTION(scope, { }); > > auto* numberFormat = IntlNumberFormat::create(vm, globalObject->numberFormatStructure()); > numberFormat->initializeNumberFormat(globalObject, callFrame->argument(0), callFrame->argument(1)); >- RETURN_IF_EXCEPTION(scope, encodedJSValue()); >- RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, value))); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ auto value = toIntlMathematicalValue(globalObject, thisValue); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ if (auto number = value.tryGetDouble()) >+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, number.value()))); >+ >+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, WTFMove(value)))); > } > > JSC_DEFINE_HOST_FUNCTION(bigIntProtoFuncValueOf, (JSGlobalObject* globalObject, CallFrame* callFrame)) >diff --git a/Source/JavaScriptCore/runtime/CommonIdentifiers.h b/Source/JavaScriptCore/runtime/CommonIdentifiers.h >index 7828d008a853461e48e36d2fbea61c709576ddaf..4ced52570cd95e46540bdc08567e80b9b7c911ca 100644 >--- a/Source/JavaScriptCore/runtime/CommonIdentifiers.h >+++ b/Source/JavaScriptCore/runtime/CommonIdentifiers.h >@@ -217,10 +217,12 @@ > macro(resolve) \ > macro(roundingIncrement) \ > macro(roundingMode) \ >+ macro(roundingPriority) \ > macro(script) \ > macro(second) \ > macro(seconds) \ > macro(segment) \ >+ macro(selectRange) \ > macro(sensitivity) \ > macro(set) \ > macro(size) \ >@@ -249,6 +251,7 @@ > macro(toLocaleString) \ > macro(toPrecision) \ > macro(toString) \ >+ macro(trailingZeroDisplay) \ > macro(type) \ > macro(uid) \ > macro(unicode) \ >diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp b/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp >index fc3237032022b4c6d67dd81559ec335b56bd0dff..dd38ec6b1eccf2217c3a99ecb5d1b012a7a461ae 100644 >--- a/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp >+++ b/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp >@@ -35,8 +35,20 @@ > #include "JSBoundFunction.h" > #include "JSCInlines.h" > #include "ObjectConstructor.h" >+#include <wtf/Range.h> > #include <wtf/unicode/icu/ICUHelpers.h> > >+#ifdef U_HIDE_DRAFT_API >+#undef U_HIDE_DRAFT_API >+#endif >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+#include <unicode/unumberformatter.h> >+#endif >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+#include <unicode/unumberrangeformatter.h> >+#endif >+#define U_HIDE_DRAFT_API 1 >+ > namespace JSC { > > const ClassInfo IntlNumberFormat::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlNumberFormat) }; >@@ -45,6 +57,22 @@ namespace IntlNumberFormatInternal { > static constexpr bool verbose = false; > } > >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+void UNumberFormatterDeleter::operator()(UNumberFormatter* formatter) >+{ >+ if (formatter) >+ unumf_close(formatter); >+} >+#endif >+ >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+void UNumberRangeFormatterDeleter::operator()(UNumberRangeFormatter* formatter) >+{ >+ if (formatter) >+ unumrf_close(formatter); >+} >+#endif >+ > struct IntlNumberFormatField { > int32_t type; > size_t size; >@@ -214,6 +242,56 @@ static std::optional<WellFormedUnit> wellFormedUnitIdentifier(StringView unitIde > return WellFormedUnit(numeratorUnit.value(), denominatorUnit.value()); > } > >+static ASCIILiteral partTypeString(UNumberFormatFields field, IntlNumberFormat::Style style, bool sign, IntlMathematicalValue::NumberType type) >+{ >+ switch (field) { >+ case UNUM_INTEGER_FIELD: >+ switch (type) { >+ case IntlMathematicalValue::NumberType::NaN: >+ return "nan"_s; >+ case IntlMathematicalValue::NumberType::Infinity: >+ return "infinity"_s; >+ case IntlMathematicalValue::NumberType::Integer: >+ return "integer"_s; >+ } >+ ASSERT_NOT_REACHED(); >+ return "unknown"_s; >+ case UNUM_FRACTION_FIELD: >+ return "fraction"_s; >+ case UNUM_DECIMAL_SEPARATOR_FIELD: >+ return "decimal"_s; >+ case UNUM_EXPONENT_SYMBOL_FIELD: >+ return "exponentSeparator"_s; >+ case UNUM_EXPONENT_SIGN_FIELD: >+ return "exponentMinusSign"_s; >+ case UNUM_EXPONENT_FIELD: >+ return "exponentInteger"_s; >+ case UNUM_GROUPING_SEPARATOR_FIELD: >+ return "group"_s; >+ case UNUM_CURRENCY_FIELD: >+ return "currency"_s; >+ case UNUM_PERCENT_FIELD: >+ // If the style is "unit", we should report as unit. >+ // JSTests/test262/test/intl402/NumberFormat/prototype/formatToParts/percent-en-US.js >+ return (style == IntlNumberFormat::Style::Unit) ? "unit"_s : "percentSign"_s; >+ case UNUM_SIGN_FIELD: >+ return sign ? "minusSign"_s : "plusSign"_s; >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+ case UNUM_MEASURE_UNIT_FIELD: >+ return "unit"_s; >+ case UNUM_COMPACT_FIELD: >+ return "compact"_s; >+#endif >+ // These should not show up because there is no way to specify them in NumberFormat options. >+ // If they do, they don't fit well into any of known part types, so consider it an "unknown". >+ case UNUM_PERMILL_FIELD: >+ // Any newer additions to the UNumberFormatFields enum should just be considered an "unknown" part. >+ default: >+ return "unknown"_s; >+ } >+ return "unknown"_s; >+} >+ > // https://tc39.github.io/ecma402/#sec-initializenumberformat > void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSValue locales, JSValue optionsValue) > { >@@ -309,14 +387,49 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > setNumberFormatDigitOptions(globalObject, this, options, minimumFractionDigitsDefault, maximumFractionDigitsDefault, m_notation); > RETURN_IF_EXCEPTION(scope, void()); > >+ m_roundingIncrement = intlNumberOption(globalObject, options, vm.propertyNames->roundingIncrement, 1, 5000, 1); >+ RETURN_IF_EXCEPTION(scope, void()); >+ static constexpr const unsigned roundingIncrementCandidates[] = { >+ 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 >+ }; >+ if (std::none_of(roundingIncrementCandidates, roundingIncrementCandidates + std::size(roundingIncrementCandidates), >+ [&](unsigned candidate) { >+ return candidate == m_roundingIncrement; >+ })) { >+ throwRangeError(globalObject, scope, "roundingIncrement must be one of 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000"_s); >+ return; >+ } >+ if (m_roundingIncrement != 1 && m_roundingType != IntlRoundingType::FractionDigits) { >+ throwRangeError(globalObject, scope, "rounding type is not fraction-digits while roundingIncrement is specified"_s); >+ return; >+ } >+ >+ m_trailingZeroDisplay = intlOption<TrailingZeroDisplay>(globalObject, options, vm.propertyNames->trailingZeroDisplay, { { "auto"_s, TrailingZeroDisplay::Auto }, { "stripIfInteger"_s, TrailingZeroDisplay::StripIfInteger } }, "trailingZeroDisplay must be either \"auto\" or \"stripIfInteger\""_s, TrailingZeroDisplay::Auto); >+ RETURN_IF_EXCEPTION(scope, void()); >+ > m_compactDisplay = intlOption<CompactDisplay>(globalObject, options, Identifier::fromString(vm, "compactDisplay"), { { "short"_s, CompactDisplay::Short }, { "long"_s, CompactDisplay::Long } }, "compactDisplay must be either \"short\" or \"long\""_s, CompactDisplay::Short); > RETURN_IF_EXCEPTION(scope, void()); > >- TriState useGrouping = intlBooleanOption(globalObject, options, Identifier::fromString(vm, "useGrouping")); >+ UseGrouping defaultUseGrouping = UseGrouping::Auto; >+ if (m_notation == IntlNotation::Compact) >+ defaultUseGrouping = UseGrouping::Min2; >+ >+ m_useGrouping = intlStringOrBooleanOption<UseGrouping>(globalObject, options, Identifier::fromString(vm, "useGrouping"), UseGrouping::Always, UseGrouping::False, { { "min2"_s, UseGrouping::Min2 }, { "auto"_s, UseGrouping::Auto }, { "always"_s, UseGrouping::Always } }, "useGrouping must be either true, false, \"min2\", \"auto\", or \"always\""_s, defaultUseGrouping); >+ >+ m_signDisplay = intlOption<SignDisplay>(globalObject, options, Identifier::fromString(vm, "signDisplay"), { { "auto"_s, SignDisplay::Auto }, { "never"_s, SignDisplay::Never }, { "always"_s, SignDisplay::Always }, { "exceptZero"_s, SignDisplay::ExceptZero }, { "negative"_s, SignDisplay::Negative } }, "signDisplay must be either \"auto\", \"never\", \"always\", \"exceptZero\", or \"negative\""_s, SignDisplay::Auto); > RETURN_IF_EXCEPTION(scope, void()); >- m_useGrouping = useGrouping != TriState::False; > >- m_signDisplay = intlOption<SignDisplay>(globalObject, options, Identifier::fromString(vm, "signDisplay"), { { "auto"_s, SignDisplay::Auto }, { "never"_s, SignDisplay::Never }, { "always"_s, SignDisplay::Always }, { "exceptZero"_s, SignDisplay::ExceptZero } }, "signDisplay must be either \"auto\", \"never\", \"always\", or \"exceptZero\""_s, SignDisplay::Auto); >+ m_roundingMode = intlOption<RoundingMode>(globalObject, options, vm.propertyNames->roundingMode, { >+ { "ceil"_s, RoundingMode::Ceil }, >+ { "floor"_s, RoundingMode::Floor }, >+ { "expand"_s, RoundingMode::Expand }, >+ { "trunc"_s, RoundingMode::Trunc }, >+ { "halfCeil"_s, RoundingMode::HalfCeil }, >+ { "halfFloor"_s, RoundingMode::HalfFloor }, >+ { "halfExpand"_s, RoundingMode::HalfExpand }, >+ { "halfTrunc"_s, RoundingMode::HalfTrunc }, >+ { "halfEven"_s, RoundingMode::HalfEven } >+ }, "roundingMode must be either \"ceil\", \"floor\", \"expand\", \"trunc\", \"halfCeil\", \"halfFloor\", \"halfExpand\", \"halfTrunc\", or \"halfEven\""_s, RoundingMode::HalfExpand); > RETURN_IF_EXCEPTION(scope, void()); > > CString dataLocaleWithExtensions = makeString(resolved.dataLocale, "-u-nu-", m_numberingSystem).utf8(); >@@ -329,7 +442,48 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md > > StringBuilder skeletonBuilder; >- skeletonBuilder.append("rounding-mode-half-up"); >+ >+ switch (m_roundingMode) { >+ case RoundingMode::Ceil: >+ skeletonBuilder.append("rounding-mode-ceiling"); >+ break; >+ case RoundingMode::Floor: >+ skeletonBuilder.append("rounding-mode-floor"); >+ break; >+ case RoundingMode::Expand: >+ skeletonBuilder.append("rounding-mode-up"); >+ break; >+ case RoundingMode::Trunc: >+ skeletonBuilder.append("rounding-mode-down"); >+ break; >+ case RoundingMode::HalfCeil: { >+ // Only ICU69~ supports half-ceiling. Ignore this option if linked ICU does not support it. >+ // https://github.com/unicode-org/icu/commit/e8dfea9bb6bb27596731173b352759e44ad06b21 >+ if (WTF::ICU::majorVersion() >= 69) >+ skeletonBuilder.append("rounding-mode-half-ceiling"); >+ else >+ skeletonBuilder.append("rounding-mode-half-up"); // Default option. >+ break; >+ } >+ case RoundingMode::HalfFloor: { >+ // Only ICU69~ supports half-ceil. Ignore this option if linked ICU does not support it. >+ // https://github.com/unicode-org/icu/commit/e8dfea9bb6bb27596731173b352759e44ad06b21 >+ if (WTF::ICU::majorVersion() >= 69) >+ skeletonBuilder.append("rounding-mode-half-floor"); >+ else >+ skeletonBuilder.append("rounding-mode-half-up"); // Default option. >+ break; >+ } >+ case RoundingMode::HalfExpand: >+ skeletonBuilder.append("rounding-mode-half-up"); >+ break; >+ case RoundingMode::HalfTrunc: >+ skeletonBuilder.append("rounding-mode-half-down"); >+ break; >+ case RoundingMode::HalfEven: >+ skeletonBuilder.append("rounding-mode-half-even"); >+ break; >+ } > > switch (m_style) { > case Style::Decimal: >@@ -386,32 +540,18 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > } > } > >- // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#integer-width >- skeletonBuilder.append(" integer-width/", WTF::ICU::majorVersion() >= 67 ? '*' : '+'); // Prior to ICU 67, use the symbol + instead of *. >- for (unsigned i = 0; i < m_minimumIntegerDigits; ++i) >- skeletonBuilder.append('0'); >+ appendNumberFormatDigitOptionsToSkeleton(this, skeletonBuilder); > >- switch (m_roundingType) { >- case IntlRoundingType::FractionDigits: { >- // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#fraction-precision >- skeletonBuilder.append(" ."); >- for (unsigned i = 0; i < m_minimumFractionDigits; ++i) >- skeletonBuilder.append('0'); >- for (unsigned i = 0; i < m_maximumFractionDigits - m_minimumFractionDigits; ++i) >- skeletonBuilder.append('#'); >- break; >- } >- case IntlRoundingType::SignificantDigits: { >- // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#significant-digits-precision >- skeletonBuilder.append(' '); >- for (unsigned i = 0; i < m_minimumSignificantDigits; ++i) >- skeletonBuilder.append('@'); >- for (unsigned i = 0; i < m_maximumSignificantDigits - m_minimumSignificantDigits; ++i) >- skeletonBuilder.append('#'); >+ // Configure this just after precision. >+ // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#trailing-zero-display >+ switch (m_trailingZeroDisplay) { >+ case TrailingZeroDisplay::Auto: > break; >- } >- case IntlRoundingType::CompactRounding: >- // Do not set anything. >+ case TrailingZeroDisplay::StripIfInteger: >+ // Only ICU69~ supports trailing zero display. Ignore this option if linked ICU does not support it. >+ // https://github.com/unicode-org/icu/commit/b79c299f90d4023ac237db3d0335d568bf21cd36 >+ if (WTF::ICU::majorVersion() >= 69) >+ skeletonBuilder.append("/w"); > break; > } > >@@ -462,20 +602,54 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > else > skeletonBuilder.append(" sign-except-zero"); > break; >+ case SignDisplay::Negative: >+ // Only ICU69~ supports negative sign display. Ignore this option if linked ICU does not support it. >+ // https://github.com/unicode-org/icu/commit/1aa0dad8e06ecc99bff442dd37f6daa2d39d9a5a >+ if (WTF::ICU::majorVersion() >= 69) { >+ if (useAccounting) >+ skeletonBuilder.append(" sign-accounting-negative"); >+ else >+ skeletonBuilder.append(" sign-negative"); >+ } >+ break; > } > >- if (!m_useGrouping) >+ // https://github.com/tc39/proposal-intl-numberformat-v3/issues/3 >+ // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#grouping >+ switch (m_useGrouping) { >+ case UseGrouping::False: > skeletonBuilder.append(" group-off"); >+ break; >+ case UseGrouping::Min2: >+ skeletonBuilder.append(" group-min2"); >+ break; >+ case UseGrouping::Auto: >+ skeletonBuilder.append(" group-auto"); >+ break; >+ case UseGrouping::Always: >+ skeletonBuilder.append(" group-on-aligned"); >+ break; >+ } > > String skeleton = skeletonBuilder.toString(); > dataLogLnIf(IntlNumberFormatInternal::verbose, skeleton); > StringView skeletonView(skeleton); >+ auto upconverted = skeletonView.upconvertedCharacters(); >+ > UErrorCode status = U_ZERO_ERROR; >- m_numberFormatter = std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter>(unumf_openForSkeletonAndLocale(skeletonView.upconvertedCharacters().get(), skeletonView.length(), dataLocaleWithExtensions.data(), &status)); >+ m_numberFormatter = std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter>(unumf_openForSkeletonAndLocale(upconverted.get(), skeletonView.length(), dataLocaleWithExtensions.data(), &status)); > if (U_FAILURE(status)) { > throwTypeError(globalObject, scope, "Failed to initialize NumberFormat"_s); > return; > } >+ >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ m_numberRangeFormatter = std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter>(unumrf_openForSkeletonWithCollapseAndIdentityFallback(upconverted.get(), skeletonView.length(), UNUM_RANGE_COLLAPSE_AUTO, UNUM_IDENTITY_FALLBACK_APPROXIMATELY, dataLocaleWithExtensions.data(), nullptr, &status)); >+ if (U_FAILURE(status)) { >+ throwTypeError(globalObject, scope, "failed to initialize NumberFormat"_s); >+ return; >+ } >+#endif > #else > UNumberFormatStyle style = UNUM_DEFAULT; > switch (m_style) { >@@ -529,12 +703,13 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > case SignDisplay::Never: > case SignDisplay::Always: > case SignDisplay::ExceptZero: >+ case SignDisplay::Negative: > // Ignore this case since linked ICU does not support it. > break; > } > > UErrorCode status = U_ZERO_ERROR; >- m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(style, nullptr, 0, dataLocaleWithExtensions.data(), nullptr, &status)); >+ m_numberFormat = std::unique_ptr<UNumberFormat, ICUDeleter<unum_close>>(unum_open(style, nullptr, 0, dataLocaleWithExtensions.data(), nullptr, &status)); > if (U_FAILURE(status)) { > throwTypeError(globalObject, scope, "failed to initialize NumberFormat"_s); > return; >@@ -559,11 +734,27 @@ void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSVa > unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits); > unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits); > break; >- case IntlRoundingType::CompactRounding: >+ case IntlRoundingType::MorePrecision: >+ // Ignore this case since linked ICU does not support it. >+ break; >+ case IntlRoundingType::LessPrecision: >+ // Ignore this case since linked ICU does not support it. >+ break; >+ } >+ >+ switch (m_useGrouping) { >+ case UseGrouping::False: >+ unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, false); >+ break; >+ case UseGrouping::Min2: > // Ignore this case since linked ICU does not support it. > break; >+ case UseGrouping::Auto: >+ break; >+ case UseGrouping::Always: >+ unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, true); >+ break; > } >- unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, m_useGrouping); > unum_setAttribute(m_numberFormat.get(), UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP); > #endif > } >@@ -578,7 +769,7 @@ JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, double value) con > #if HAVE(ICU_U_NUMBER_FORMATTER) > ASSERT(m_numberFormatter); > UErrorCode status = U_ZERO_ERROR; >- auto formattedNumber = std::unique_ptr<UFormattedNumber, UFormattedNumberDeleter>(unumf_openResult(&status)); >+ auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status)); > if (U_FAILURE(status)) > return throwTypeError(globalObject, scope, "Failed to format a number."_s); > unumf_formatDouble(m_numberFormatter.get(), value, formattedNumber.get(), &status); >@@ -593,29 +784,26 @@ JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, double value) con > if (U_FAILURE(status)) > return throwTypeError(globalObject, scope, "Failed to format a number."_s); > #endif >- return jsString(vm, String(buffer)); >+ return jsString(vm, String(WTFMove(buffer))); > } > > // https://tc39.es/ecma402/#sec-formatnumber >-JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, JSBigInt* value) const >+JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, IntlMathematicalValue&& value) const > { > VM& vm = globalObject->vm(); > auto scope = DECLARE_THROW_SCOPE(vm); > >- auto string = value->toString(globalObject, 10); >- RETURN_IF_EXCEPTION(scope, { }); >- >- ASSERT(string.is8Bit() && string.isAllASCII()); >- auto* rawString = reinterpret_cast<const char*>(string.characters8()); >+ value.ensureNonDouble(); >+ const auto& string = value.getString(); > > Vector<UChar, 32> buffer; > #if HAVE(ICU_U_NUMBER_FORMATTER) > ASSERT(m_numberFormatter); > UErrorCode status = U_ZERO_ERROR; >- auto formattedNumber = std::unique_ptr<UFormattedNumber, UFormattedNumberDeleter>(unumf_openResult(&status)); >+ auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status)); > if (U_FAILURE(status)) > return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s); >- unumf_formatDecimal(m_numberFormatter.get(), rawString, string.length(), formattedNumber.get(), &status); >+ unumf_formatDecimal(m_numberFormatter.get(), string.data(), string.length(), formattedNumber.get(), &status); > if (U_FAILURE(status)) > return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s); > status = callBufferProducingFunction(unumf_resultToString, formattedNumber.get(), buffer); >@@ -623,13 +811,89 @@ JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, JSBigInt* value) > return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s); > #else > ASSERT(m_numberFormat); >- auto status = callBufferProducingFunction(unum_formatDecimal, m_numberFormat.get(), rawString, string.length(), buffer, nullptr); >+ auto status = callBufferProducingFunction(unum_formatDecimal, m_numberFormat.get(), string.data(), string.length(), buffer, nullptr); > if (U_FAILURE(status)) > return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s); > #endif >- return jsString(vm, String(buffer)); >+ return jsString(vm, String(WTFMove(buffer))); >+} >+ >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+JSValue IntlNumberFormat::formatRange(JSGlobalObject* globalObject, double start, double end) const >+{ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ ASSERT(m_numberRangeFormatter); >+ >+ if (std::isnan(start) || std::isnan(end)) >+ return throwRangeError(globalObject, scope, "Passed numbers are out of range"_s); >+ >+ if (end < start) >+ return throwRangeError(globalObject, scope, "start is larger than end"_s); >+ >+ if (isNegativeZero(end) && start >= 0) >+ return throwRangeError(globalObject, scope, "start is larger than end"_s); >+ >+ UErrorCode status = U_ZERO_ERROR; >+ auto range = std::unique_ptr<UFormattedNumberRange, ICUDeleter<unumrf_closeResult>>(unumrf_openResult(&status)); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to format a range"_s); >+ >+ unumrf_formatDoubleRange(m_numberRangeFormatter.get(), start, end, range.get(), &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to format a range"_s); >+ >+ auto* formattedValue = unumrf_resultAsValue(range.get(), &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to format a range"_s); >+ >+ int32_t length = 0; >+ const UChar* string = ufmtval_getString(formattedValue, &length, &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to format a range"_s); >+ >+ return jsString(vm, String(string, length)); > } > >+JSValue IntlNumberFormat::formatRange(JSGlobalObject* globalObject, IntlMathematicalValue&& start, IntlMathematicalValue&& end) const >+{ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ ASSERT(m_numberRangeFormatter); >+ >+ if (start.numberType() == IntlMathematicalValue::NumberType::NaN || end.numberType() == IntlMathematicalValue::NumberType::NaN) >+ return throwRangeError(globalObject, scope, "Passed numbers are out of range"_s); >+ >+ start.ensureNonDouble(); >+ const auto& startString = start.getString(); >+ >+ end.ensureNonDouble(); >+ const auto& endString = end.getString(); >+ >+ UErrorCode status = U_ZERO_ERROR; >+ auto range = std::unique_ptr<UFormattedNumberRange, ICUDeleter<unumrf_closeResult>>(unumrf_openResult(&status)); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to format a range"_s); >+ >+ unumrf_formatDecimalRange(m_numberRangeFormatter.get(), startString.data(), startString.length(), endString.data(), endString.length(), range.get(), &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to format a range"_s); >+ >+ auto* formattedValue = unumrf_resultAsValue(range.get(), &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to format a range"_s); >+ >+ int32_t length = 0; >+ const UChar* string = ufmtval_getString(formattedValue, &length, &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to format a range"_s); >+ >+ return jsString(vm, String(string, length)); >+} >+#endif >+ > ASCIILiteral IntlNumberFormat::styleString(Style style) > { > switch (style) { >@@ -727,11 +991,81 @@ ASCIILiteral IntlNumberFormat::signDisplayString(SignDisplay signDisplay) > return "always"_s; > case SignDisplay::ExceptZero: > return "exceptZero"_s; >+ case SignDisplay::Negative: >+ return "negative"_s; >+ } >+ ASSERT_NOT_REACHED(); >+ return ASCIILiteral::null(); >+} >+ >+ASCIILiteral IntlNumberFormat::roundingModeString(RoundingMode roundingMode) >+{ >+ switch (roundingMode) { >+ case RoundingMode::Ceil: >+ return "ceil"_s; >+ case RoundingMode::Floor: >+ return "floor"_s; >+ case RoundingMode::Expand: >+ return "expand"_s; >+ case RoundingMode::Trunc: >+ return "trunc"_s; >+ case RoundingMode::HalfCeil: >+ return "halfCeil"_s; >+ case RoundingMode::HalfFloor: >+ return "halfFloor"_s; >+ case RoundingMode::HalfExpand: >+ return "halfExpand"_s; >+ case RoundingMode::HalfTrunc: >+ return "halfTrunc"_s; >+ case RoundingMode::HalfEven: >+ return "halfEven"_s; >+ } >+ ASSERT_NOT_REACHED(); >+ return ASCIILiteral::null(); >+} >+ >+ASCIILiteral IntlNumberFormat::trailingZeroDisplayString(TrailingZeroDisplay trailingZeroDisplay) >+{ >+ switch (trailingZeroDisplay) { >+ case TrailingZeroDisplay::Auto: >+ return "auto"_s; >+ case TrailingZeroDisplay::StripIfInteger: >+ return "stripIfInteger"_s; > } > ASSERT_NOT_REACHED(); > return ASCIILiteral::null(); > } > >+ASCIILiteral IntlNumberFormat::roundingPriorityString(IntlRoundingType roundingType) >+{ >+ switch (roundingType) { >+ case IntlRoundingType::FractionDigits: >+ case IntlRoundingType::SignificantDigits: >+ return "auto"_s; >+ case IntlRoundingType::MorePrecision: >+ return "morePrecision"_s; >+ case IntlRoundingType::LessPrecision: >+ return "lessPrecision"_s; >+ } >+ ASSERT_NOT_REACHED(); >+ return ASCIILiteral::null(); >+} >+ >+JSValue IntlNumberFormat::useGroupingValue(VM& vm, UseGrouping useGrouping) >+{ >+ switch (useGrouping) { >+ case UseGrouping::False: >+ return jsBoolean(false); >+ case UseGrouping::Min2: >+ return jsNontrivialString(vm, "min2"_s); >+ case UseGrouping::Auto: >+ return jsNontrivialString(vm, "auto"_s); >+ case UseGrouping::Always: >+ return jsNontrivialString(vm, "always"_s); >+ } >+ return jsUndefined(); >+} >+ > // https://tc39.es/ecma402/#sec-intl.numberformat.prototype.resolvedoptions > JSObject* IntlNumberFormat::resolvedOptions(JSGlobalObject* globalObject) const > { >@@ -764,14 +1098,23 @@ JSObject* IntlNumberFormat::resolvedOptions(JSGlobalObject* globalObject) const > options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits)); > options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits)); > break; >- case IntlRoundingType::CompactRounding: >+ case IntlRoundingType::MorePrecision: >+ case IntlRoundingType::LessPrecision: >+ options->putDirect(vm, vm.propertyNames->minimumFractionDigits, jsNumber(m_minimumFractionDigits)); >+ options->putDirect(vm, vm.propertyNames->maximumFractionDigits, jsNumber(m_maximumFractionDigits)); >+ options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits)); >+ options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits)); > break; > } >- options->putDirect(vm, Identifier::fromString(vm, "useGrouping"), jsBoolean(m_useGrouping)); >+ options->putDirect(vm, Identifier::fromString(vm, "useGrouping"), useGroupingValue(vm, m_useGrouping)); > options->putDirect(vm, Identifier::fromString(vm, "notation"), jsNontrivialString(vm, notationString(m_notation))); > if (m_notation == IntlNotation::Compact) > options->putDirect(vm, Identifier::fromString(vm, "compactDisplay"), jsNontrivialString(vm, compactDisplayString(m_compactDisplay))); > options->putDirect(vm, Identifier::fromString(vm, "signDisplay"), jsNontrivialString(vm, signDisplayString(m_signDisplay))); >+ options->putDirect(vm, vm.propertyNames->roundingMode, jsNontrivialString(vm, roundingModeString(m_roundingMode))); >+ options->putDirect(vm, vm.propertyNames->roundingIncrement, jsNumber(m_roundingIncrement)); >+ options->putDirect(vm, vm.propertyNames->trailingZeroDisplay, jsNontrivialString(vm, trailingZeroDisplayString(m_trailingZeroDisplay))); >+ options->putDirect(vm, vm.propertyNames->roundingPriority, jsNontrivialString(vm, roundingPriorityString(m_roundingType))); > return options; > } > >@@ -780,52 +1123,7 @@ void IntlNumberFormat::setBoundFormat(VM& vm, JSBoundFunction* format) > m_boundFormat.set(vm, this, format); > } > >-static ASCIILiteral partTypeString(UNumberFormatFields field, IntlNumberFormat::Style style, double value) >-{ >- switch (field) { >- case UNUM_INTEGER_FIELD: >- if (std::isnan(value)) >- return "nan"_s; >- if (!std::isfinite(value)) >- return "infinity"_s; >- return "integer"_s; >- case UNUM_FRACTION_FIELD: >- return "fraction"_s; >- case UNUM_DECIMAL_SEPARATOR_FIELD: >- return "decimal"_s; >- case UNUM_EXPONENT_SYMBOL_FIELD: >- return "exponentSeparator"_s; >- case UNUM_EXPONENT_SIGN_FIELD: >- return "exponentMinusSign"_s; >- case UNUM_EXPONENT_FIELD: >- return "exponentInteger"_s; >- case UNUM_GROUPING_SEPARATOR_FIELD: >- return "group"_s; >- case UNUM_CURRENCY_FIELD: >- return "currency"_s; >- case UNUM_PERCENT_FIELD: >- // If the style is "unit", we should report as unit. >- // JSTests/test262/test/intl402/NumberFormat/prototype/formatToParts/percent-en-US.js >- return (style == IntlNumberFormat::Style::Unit) ? "unit"_s : "percentSign"_s; >- case UNUM_SIGN_FIELD: >- return std::signbit(value) ? "minusSign"_s : "plusSign"_s; >-#if HAVE(ICU_U_NUMBER_FORMATTER) >- case UNUM_MEASURE_UNIT_FIELD: >- return "unit"_s; >- case UNUM_COMPACT_FIELD: >- return "compact"_s; >-#endif >- // These should not show up because there is no way to specify them in NumberFormat options. >- // If they do, they don't fit well into any of known part types, so consider it an "unknown". >- case UNUM_PERMILL_FIELD: >- // Any newer additions to the UNumberFormatFields enum should just be considered an "unknown" part. >- default: >- return "unknown"_s; >- } >- return "unknown"_s; >-} >- >-void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, Style style, double value, const String& formatted, IntlFieldIterator& iterator, JSArray* parts, JSString* unit) >+void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, Style style, bool sign, IntlMathematicalValue::NumberType numberType, const String& formatted, IntlFieldIterator& iterator, JSArray* parts, JSString* unit) > { > VM& vm = globalObject->vm(); > auto scope = DECLARE_THROW_SCOPE(vm); >@@ -868,7 +1166,7 @@ void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, Style > auto fieldType = fields[currentIndex].type; > while (currentIndex < stringLength && fields[currentIndex].type == fieldType) > ++currentIndex; >- auto partType = fieldType == literalFieldType ? literalString : jsString(vm, partTypeString(UNumberFormatFields(fieldType), style, value)); >+ auto partType = fieldType == literalFieldType ? literalString : jsString(vm, partTypeString(UNumberFormatFields(fieldType), style, sign, numberType)); > auto partValue = jsSubstring(vm, formatted, startIndex, currentIndex - startIndex); > JSObject* part = constructEmptyObject(globalObject); > part->putDirect(vm, vm.propertyNames->type, partType); >@@ -894,7 +1192,7 @@ JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double val > Vector<UChar, 32> result; > #if HAVE(ICU_U_NUMBER_FORMATTER) > ASSERT(m_numberFormatter); >- auto formattedNumber = std::unique_ptr<UFormattedNumber, UFormattedNumberDeleter>(unumf_openResult(&status)); >+ auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status)); > if (U_FAILURE(status)) > return throwTypeError(globalObject, scope, "Failed to format a number."_s); > unumf_formatDouble(m_numberFormatter.get(), value, formattedNumber.get(), &status); >@@ -915,16 +1213,63 @@ JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double val > IntlFieldIterator iterator(*fieldItr.get()); > #endif > >- auto resultString = String(result); >+ auto resultString = String(WTFMove(result)); > > JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0); > if (!parts) > return throwOutOfMemoryError(globalObject, scope); > >- formatToPartsInternal(globalObject, m_style, value, resultString, iterator, parts); >+ formatToPartsInternal(globalObject, m_style, std::signbit(value), IntlMathematicalValue::numberTypeFromDouble(value), resultString, iterator, parts); > RETURN_IF_EXCEPTION(scope, { }); > > return parts; > } > >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, IntlMathematicalValue&& value) const >+{ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ value.ensureNonDouble(); >+ const auto& string = value.getString(); >+ >+ UErrorCode status = U_ZERO_ERROR; >+ auto fieldItr = std::unique_ptr<UFieldPositionIterator, UFieldPositionIteratorDeleter>(ufieldpositer_open(&status)); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to open field position iterator"_s); >+ >+ Vector<UChar, 32> result; >+ ASSERT(m_numberFormatter); >+ auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status)); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "Failed to format a number."_s); >+ >+ unumf_formatDecimal(m_numberFormatter.get(), string.data(), string.length(), formattedNumber.get(), &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "Failed to format a number."_s); >+ >+ status = callBufferProducingFunction(unumf_resultToString, formattedNumber.get(), result); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "Failed to format a number."_s); >+ >+ unumf_resultGetAllFieldPositions(formattedNumber.get(), fieldItr.get(), &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "Failed to format a number."_s); >+ >+ IntlFieldIterator iterator(*fieldItr.get()); >+ >+ auto resultString = String(WTFMove(result)); >+ >+ JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0); >+ if (!parts) >+ return throwOutOfMemoryError(globalObject, scope); >+ >+ formatToPartsInternal(globalObject, m_style, value.sign(), value.numberType(), resultString, iterator, parts); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ return parts; >+} >+#endif >+ > } // namespace JSC >diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormat.h b/Source/JavaScriptCore/runtime/IntlNumberFormat.h >index 38a770c59d410700f89c49f769a9149dbca857de..cb61dae2fbac1d16ec02d97174a13252cc033021 100644 >--- a/Source/JavaScriptCore/runtime/IntlNumberFormat.h >+++ b/Source/JavaScriptCore/runtime/IntlNumberFormat.h >@@ -28,6 +28,8 @@ > #pragma once > > #include "JSObject.h" >+#include "MathCommon.h" >+#include "TemporalObject.h" > #include <unicode/unum.h> > #include <wtf/unicode/icu/ICUHelpers.h> > >@@ -38,19 +40,110 @@ > #endif > #endif > >-#if HAVE(ICU_U_NUMBER_FORMATTER) >-#include <unicode/unumberformatter.h> >+#if !defined(HAVE_ICU_U_NUMBER_RANGE_FORMATTER) >+#if U_ICU_VERSION_MAJOR_NUM >= 68 >+#define HAVE_ICU_U_NUMBER_RANGE_FORMATTER 1 >+#endif > #endif > >+struct UNumberFormatter; >+struct UNumberRangeFormatter; >+ > namespace JSC { > > class IntlFieldIterator; > class JSBoundFunction; > enum class RelevantExtensionKey : uint8_t; > >-enum class IntlRoundingType : uint8_t { FractionDigits, SignificantDigits, CompactRounding }; >+enum class IntlRoundingType : uint8_t { FractionDigits, SignificantDigits, MorePrecision, LessPrecision }; >+enum class IntlRoundingPriority : uint8_t { Auto, MorePrecision, LessPrecision }; > enum class IntlNotation : uint8_t { Standard, Scientific, Engineering, Compact }; > template<typename IntlType> void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation); >+template<typename IntlType> void appendNumberFormatDigitOptionsToSkeleton(IntlType*, StringBuilder&); >+ >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+struct UNumberFormatterDeleter { >+ JS_EXPORT_PRIVATE void operator()(UNumberFormatter*); >+}; >+#endif >+ >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+struct UNumberRangeFormatterDeleter { >+ JS_EXPORT_PRIVATE void operator()(UNumberRangeFormatter*); >+}; >+#endif >+ >+class IntlMathematicalValue { >+ WTF_MAKE_FAST_ALLOCATED(IntlMathematicalValue); >+public: >+ enum class NumberType { Integer, Infinity, NaN, }; >+ using Value = std::variant<double, CString>; >+ >+ IntlMathematicalValue() = default; >+ >+ explicit IntlMathematicalValue(double value) >+ : m_numberType(numberTypeFromDouble(value)) >+ , m_sign(std::signbit(value)) >+ , m_value(value) >+ { } >+ >+ explicit IntlMathematicalValue(NumberType numberType, bool sign, CString value) >+ : m_numberType(numberType) >+ , m_sign(sign) >+ , m_value(value) >+ { >+ } >+ >+ void ensureNonDouble() >+ { >+ if (std::holds_alternative<double>(m_value)) { >+ switch (m_numberType) { >+ case NumberType::Integer: { >+ double value = std::get<double>(m_value); >+ if (isNegativeZero(value)) >+ m_value = CString("-0"); >+ else >+ m_value = String::number(value).ascii(); >+ break; >+ } >+ case NumberType::NaN: >+ m_value = CString("nan"); >+ break; >+ case NumberType::Infinity: >+ m_value = CString(m_sign ? "-infinity" : "infinity"); >+ break; >+ } >+ } >+ } >+ >+ NumberType numberType() const { return m_numberType; } >+ bool sign() const { return m_sign; } >+ std::optional<double> tryGetDouble() const >+ { >+ if (std::holds_alternative<double>(m_value)) >+ return std::get<double>(m_value); >+ return std::nullopt; >+ } >+ const CString& getString() const >+ { >+ ASSERT(std::holds_alternative<CString>(m_value)); >+ return std::get<CString>(m_value); >+ } >+ >+ static NumberType numberTypeFromDouble(double value) >+ { >+ if (std::isnan(value)) >+ return NumberType::NaN; >+ if (!std::isfinite(value)) >+ return NumberType::Infinity; >+ return NumberType::Integer; >+ } >+ >+private: >+ NumberType m_numberType { NumberType::Integer }; >+ bool m_sign { false }; >+ Value m_value { 0.0 }; >+}; > > class IntlNumberFormat final : public JSNonFinalObject { > public: >@@ -76,24 +169,36 @@ class IntlNumberFormat final : public JSNonFinalObject { > > void initializeNumberFormat(JSGlobalObject*, JSValue locales, JSValue optionsValue); > JSValue format(JSGlobalObject*, double) const; >- JSValue format(JSGlobalObject*, JSBigInt*) const; >+ JSValue format(JSGlobalObject*, IntlMathematicalValue&&) const; > JSValue formatToParts(JSGlobalObject*, double) const; >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+ JSValue formatToParts(JSGlobalObject*, IntlMathematicalValue&&) const; >+#endif > JSObject* resolvedOptions(JSGlobalObject*) const; > >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ JSValue formatRange(JSGlobalObject*, double, double) const; >+ JSValue formatRange(JSGlobalObject*, IntlMathematicalValue&&, IntlMathematicalValue&&) const; >+#endif >+ > JSBoundFunction* boundFormat() const { return m_boundFormat.get(); } > void setBoundFormat(VM&, JSBoundFunction*); > > enum class Style : uint8_t { Decimal, Percent, Currency, Unit }; > >- static void formatToPartsInternal(JSGlobalObject*, Style, double, const String& formatted, IntlFieldIterator&, JSArray*, JSString* unit = nullptr); >+ static void formatToPartsInternal(JSGlobalObject*, Style, bool sign, IntlMathematicalValue::NumberType, const String& formatted, IntlFieldIterator&, JSArray*, JSString* unit = nullptr); > > template<typename IntlType> > friend void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation); >+ template<typename IntlType> >+ friend void appendNumberFormatDigitOptionsToSkeleton(IntlType*, StringBuilder&); > > static ASCIILiteral notationString(IntlNotation); > > static IntlNumberFormat* unwrapForOldFunctions(JSGlobalObject*, JSValue); > >+ static ASCIILiteral roundingPriorityString(IntlRoundingType); >+ > private: > IntlNumberFormat(VM&, Structure*); > void finishCreation(VM&); >@@ -105,7 +210,9 @@ class IntlNumberFormat final : public JSNonFinalObject { > enum class CurrencySign : uint8_t { Standard, Accounting }; > enum class UnitDisplay : uint8_t { Short, Narrow, Long }; > enum class CompactDisplay : uint8_t { Short, Long }; >- enum class SignDisplay : uint8_t { Auto, Never, Always, ExceptZero }; >+ enum class SignDisplay : uint8_t { Auto, Never, Always, ExceptZero, Negative }; >+ enum class TrailingZeroDisplay : uint8_t { Auto, StripIfInteger }; >+ enum class UseGrouping : uint8_t { False, Min2, Auto, Always }; > > static ASCIILiteral styleString(Style); > static ASCIILiteral currencyDisplayString(CurrencyDisplay); >@@ -113,14 +220,17 @@ class IntlNumberFormat final : public JSNonFinalObject { > static ASCIILiteral unitDisplayString(UnitDisplay); > static ASCIILiteral compactDisplayString(CompactDisplay); > static ASCIILiteral signDisplayString(SignDisplay); >+ static ASCIILiteral roundingModeString(RoundingMode); >+ static ASCIILiteral trailingZeroDisplayString(TrailingZeroDisplay); >+ static JSValue useGroupingValue(VM&, UseGrouping); > > WriteBarrier<JSBoundFunction> m_boundFormat; > #if HAVE(ICU_U_NUMBER_FORMATTER) >- using UNumberFormatterDeleter = ICUDeleter<unumf_close>; >- using UFormattedNumberDeleter = ICUDeleter<unumf_closeResult>; > std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter> m_numberFormatter; >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter> m_numberRangeFormatter; >+#endif > #else >- using UNumberFormatDeleter = ICUDeleter<unum_close>; > std::unique_ptr<UNumberFormat, ICUDeleter<unum_close>> m_numberFormat; > #endif > >@@ -133,6 +243,7 @@ class IntlNumberFormat final : public JSNonFinalObject { > unsigned m_maximumFractionDigits { 3 }; > unsigned m_minimumSignificantDigits { 0 }; > unsigned m_maximumSignificantDigits { 0 }; >+ unsigned m_roundingIncrement { 1 }; > Style m_style { Style::Decimal }; > CurrencyDisplay m_currencyDisplay; > CurrencySign m_currencySign; >@@ -140,7 +251,9 @@ class IntlNumberFormat final : public JSNonFinalObject { > CompactDisplay m_compactDisplay; > IntlNotation m_notation { IntlNotation::Standard }; > SignDisplay m_signDisplay; >- bool m_useGrouping { true }; >+ TrailingZeroDisplay m_trailingZeroDisplay { TrailingZeroDisplay::Auto }; >+ UseGrouping m_useGrouping { UseGrouping::Always }; >+ RoundingMode m_roundingMode { RoundingMode::HalfExpand }; > IntlRoundingType m_roundingType { IntlRoundingType::FractionDigits }; > }; > >diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h b/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h >index 23d3863d1600431ef74df40c874471766d05169a..1af41ea2123b5d6fa291cafd3f2ef62deab78394 100644 >--- a/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h >+++ b/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h >@@ -29,6 +29,7 @@ > #include "IntlNumberFormat.h" > #include "IntlObjectInlines.h" > #include "JSGlobalObject.h" >+#include "JSGlobalObjectFunctions.h" > > namespace JSC { > >@@ -61,47 +62,135 @@ void setNumberFormatDigitOptions(JSGlobalObject* globalObject, IntlType* intlIns > > intlInstance->m_minimumIntegerDigits = minimumIntegerDigits; > >- if (!minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined()) { >- intlInstance->m_roundingType = IntlRoundingType::SignificantDigits; >- unsigned minimumSignificantDigits = intlDefaultNumberOption(globalObject, minimumSignificantDigitsValue, vm.propertyNames->minimumSignificantDigits, 1, 21, 1); >- RETURN_IF_EXCEPTION(scope, void()); >- unsigned maximumSignificantDigits = intlDefaultNumberOption(globalObject, maximumSignificantDigitsValue, vm.propertyNames->maximumSignificantDigits, minimumSignificantDigits, 21, 21); >- RETURN_IF_EXCEPTION(scope, void()); >- intlInstance->m_minimumSignificantDigits = minimumSignificantDigits; >- intlInstance->m_maximumSignificantDigits = maximumSignificantDigits; >- return; >+ IntlRoundingPriority roundingPriority = intlOption<IntlRoundingPriority>(globalObject, options, vm.propertyNames->roundingPriority, { { "auto"_s, IntlRoundingPriority::Auto }, { "morePrecision"_s, IntlRoundingPriority::MorePrecision }, { "lessPrecision"_s, IntlRoundingPriority::LessPrecision } }, "roundingPriority must be either \"auto\", \"morePrecision\", or \"lessPrecision\""_s, IntlRoundingPriority::Auto); >+ RETURN_IF_EXCEPTION(scope, void()); >+ >+ bool hasSd = !minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined(); >+ bool hasFd = !minimumFractionDigitsValue.isUndefined() || !maximumFractionDigitsValue.isUndefined(); >+ bool needSd = hasSd || roundingPriority != IntlRoundingPriority::Auto; >+ bool needFd = (!hasSd && notation != IntlNotation::Compact) || roundingPriority != IntlRoundingPriority::Auto; >+ >+ if (needSd) { >+ if (hasSd) { >+ unsigned minimumSignificantDigits = intlDefaultNumberOption(globalObject, minimumSignificantDigitsValue, vm.propertyNames->minimumSignificantDigits, 1, 21, 1); >+ RETURN_IF_EXCEPTION(scope, void()); >+ unsigned maximumSignificantDigits = intlDefaultNumberOption(globalObject, maximumSignificantDigitsValue, vm.propertyNames->maximumSignificantDigits, minimumSignificantDigits, 21, 21); >+ RETURN_IF_EXCEPTION(scope, void()); >+ intlInstance->m_minimumSignificantDigits = minimumSignificantDigits; >+ intlInstance->m_maximumSignificantDigits = maximumSignificantDigits; >+ } else { >+ intlInstance->m_minimumSignificantDigits = 1; >+ intlInstance->m_maximumSignificantDigits = 21; >+ } > } > >- if (!minimumFractionDigitsValue.isUndefined() || !maximumFractionDigitsValue.isUndefined()) { >- constexpr unsigned undefinedValue = UINT32_MAX; >- intlInstance->m_roundingType = IntlRoundingType::FractionDigits; >- unsigned specifiedMinimumFractionDigits = intlDefaultNumberOption(globalObject, minimumFractionDigitsValue, vm.propertyNames->minimumFractionDigits, 0, 20, undefinedValue); >- RETURN_IF_EXCEPTION(scope, void()); >- unsigned specifiedMaximumFractionDigits = intlDefaultNumberOption(globalObject, maximumFractionDigitsValue, vm.propertyNames->maximumFractionDigits, 0, 20, undefinedValue); >- RETURN_IF_EXCEPTION(scope, void()); >- if (specifiedMaximumFractionDigits != undefinedValue) >- minimumFractionDigitsDefault = std::min(minimumFractionDigitsDefault, specifiedMaximumFractionDigits); >- unsigned minimumFractionDigits = intlDefaultNumberOption(globalObject, (specifiedMinimumFractionDigits == undefinedValue) ? jsUndefined() : jsNumber(specifiedMinimumFractionDigits), vm.propertyNames->minimumFractionDigits, 0, 20, minimumFractionDigitsDefault); >- RETURN_IF_EXCEPTION(scope, void()); >- unsigned maximumFractionDigits = intlDefaultNumberOption(globalObject, (specifiedMaximumFractionDigits == undefinedValue) ? jsUndefined() : jsNumber(specifiedMaximumFractionDigits), vm.propertyNames->maximumFractionDigits, 0, 20, std::max(maximumFractionDigitsDefault, minimumFractionDigits)); >- RETURN_IF_EXCEPTION(scope, void()); >- if (minimumFractionDigits > maximumFractionDigits) { >- throwRangeError(globalObject, scope, "Computed minimumFractionDigits is larger than maximumFractionDigits"_s); >- return; >+ if (needFd) { >+ if (hasFd) { >+ constexpr unsigned undefinedValue = UINT32_MAX; >+ unsigned minimumFractionDigits = intlDefaultNumberOption(globalObject, minimumFractionDigitsValue, vm.propertyNames->minimumFractionDigits, 0, 20, undefinedValue); >+ RETURN_IF_EXCEPTION(scope, void()); >+ unsigned maximumFractionDigits = intlDefaultNumberOption(globalObject, maximumFractionDigitsValue, vm.propertyNames->maximumFractionDigits, 0, 20, undefinedValue); >+ RETURN_IF_EXCEPTION(scope, void()); >+ >+ if (minimumFractionDigits == undefinedValue) >+ minimumFractionDigits = std::min(minimumFractionDigitsDefault, maximumFractionDigits); >+ else if (maximumFractionDigits == undefinedValue) >+ maximumFractionDigits = std::max(maximumFractionDigitsDefault, minimumFractionDigits); >+ else if (minimumFractionDigits > maximumFractionDigits) { >+ throwRangeError(globalObject, scope, "Computed minimumFractionDigits is larger than maximumFractionDigits"_s); >+ return; >+ } >+ >+ intlInstance->m_minimumFractionDigits = minimumFractionDigits; >+ intlInstance->m_maximumFractionDigits = maximumFractionDigits; >+ } else { >+ intlInstance->m_minimumFractionDigits = minimumFractionDigitsDefault; >+ intlInstance->m_maximumFractionDigits = maximumFractionDigitsDefault; > } >- intlInstance->m_minimumFractionDigits = minimumFractionDigits; >- intlInstance->m_maximumFractionDigits = maximumFractionDigits; >- return; > } > >- if (notation == IntlNotation::Compact) { >- intlInstance->m_roundingType = IntlRoundingType::CompactRounding; >- return; >+ if (needSd || needFd) { >+ if (roundingPriority == IntlRoundingPriority::MorePrecision) >+ intlInstance->m_roundingType = IntlRoundingType::MorePrecision; >+ else if (roundingPriority == IntlRoundingPriority::LessPrecision) >+ intlInstance->m_roundingType = IntlRoundingType::LessPrecision; >+ else if (hasSd) >+ intlInstance->m_roundingType = IntlRoundingType::SignificantDigits; >+ else >+ intlInstance->m_roundingType = IntlRoundingType::FractionDigits; >+ } else { >+ intlInstance->m_roundingType = IntlRoundingType::MorePrecision; >+ intlInstance->m_minimumFractionDigits = 0; >+ intlInstance->m_maximumFractionDigits = 0; >+ intlInstance->m_minimumSignificantDigits = 1; >+ intlInstance->m_maximumSignificantDigits = 2; > } >+} > >- intlInstance->m_roundingType = IntlRoundingType::FractionDigits; >- intlInstance->m_minimumFractionDigits = minimumFractionDigitsDefault; >- intlInstance->m_maximumFractionDigits = maximumFractionDigitsDefault; >+template<typename IntlType> >+void appendNumberFormatDigitOptionsToSkeleton(IntlType* intlInstance, StringBuilder& skeletonBuilder) >+{ >+ // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#integer-width >+ skeletonBuilder.append(" integer-width/", WTF::ICU::majorVersion() >= 67 ? '*' : '+'); // Prior to ICU 67, use the symbol + instead of *. >+ for (unsigned i = 0; i < intlInstance->m_minimumIntegerDigits; ++i) >+ skeletonBuilder.append('0'); >+ >+ if (intlInstance->m_roundingIncrement != 1) { >+ skeletonBuilder.append(" precision-increment/"); >+ auto string = numberToStringUnsigned<Vector<LChar, 10>>(intlInstance->m_roundingIncrement); >+ if (intlInstance->m_maximumFractionDigits >= string.size()) { >+ skeletonBuilder.append("0."); >+ for (unsigned i = 0; i < (intlInstance->m_maximumFractionDigits - string.size()); ++i) >+ skeletonBuilder.append('0'); >+ skeletonBuilder.appendCharacters(string.data(), string.size()); >+ } else { >+ unsigned nonFraction = string.size() - intlInstance->m_maximumFractionDigits; >+ skeletonBuilder.appendCharacters(string.data(), nonFraction); >+ skeletonBuilder.append('.'); >+ skeletonBuilder.appendCharacters(string.data() + nonFraction, intlInstance->m_maximumFractionDigits); >+ } >+ } else { >+ switch (intlInstance->m_roundingType) { >+ case IntlRoundingType::FractionDigits: { >+ // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#fraction-precision >+ skeletonBuilder.append(" ."); >+ for (unsigned i = 0; i < intlInstance->m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('0'); >+ for (unsigned i = 0; i < intlInstance->m_maximumFractionDigits - intlInstance->m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('#'); >+ break; >+ } >+ case IntlRoundingType::SignificantDigits: { >+ // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#significant-digits-precision >+ skeletonBuilder.append(' '); >+ for (unsigned i = 0; i < intlInstance->m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('@'); >+ for (unsigned i = 0; i < intlInstance->m_maximumSignificantDigits - intlInstance->m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('#'); >+ break; >+ } >+ case IntlRoundingType::MorePrecision: >+ case IntlRoundingType::LessPrecision: >+ // Before Intl.NumberFormat v3, it was CompactRounding mode, where we do not configure anything. >+ // So, if linked ICU is ~68, we do nothing. >+ if (WTF::ICU::majorVersion() >= 69) { >+ // https://github.com/unicode-org/icu/commit/d7db6c1f8655bb53153695b09a50029fd04a8364 >+ // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#precision >+ skeletonBuilder.append(" ."); >+ for (unsigned i = 0; i < intlInstance->m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('0'); >+ for (unsigned i = 0; i < intlInstance->m_maximumFractionDigits - intlInstance->m_minimumFractionDigits; ++i) >+ skeletonBuilder.append('#'); >+ skeletonBuilder.append('/'); >+ for (unsigned i = 0; i < intlInstance->m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('@'); >+ for (unsigned i = 0; i < intlInstance->m_maximumSignificantDigits - intlInstance->m_minimumSignificantDigits; ++i) >+ skeletonBuilder.append('#'); >+ skeletonBuilder.append(intlInstance->m_roundingType == IntlRoundingType::MorePrecision ? 'r' : 's'); >+ } >+ break; >+ } >+ } > } > > class IntlFieldIterator { >@@ -128,4 +217,57 @@ inline IntlNumberFormat* IntlNumberFormat::unwrapForOldFunctions(JSGlobalObject* > return unwrapForLegacyIntlConstructor<IntlNumberFormat>(globalObject, thisValue, globalObject->numberFormatConstructor()); > } > >+// https://tc39.es/proposal-intl-numberformat-v3/out/numberformat/diff.html#sec-tointlmathematicalvalue >+inline IntlMathematicalValue toIntlMathematicalValue(JSGlobalObject* globalObject, JSValue value) >+{ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ if (auto number = JSBigInt::tryExtractDouble(value)) >+ return IntlMathematicalValue { number.value() }; >+ >+ JSValue primitive = value.toPrimitive(globalObject, PreferredPrimitiveType::PreferNumber); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ auto bigIntToIntlMathematicalValue = [](JSGlobalObject* globalObject, JSValue value) -> IntlMathematicalValue { >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ if (auto number = JSBigInt::tryExtractDouble(value)) >+ return IntlMathematicalValue { number.value() }; >+ >+ auto* bigInt = value.asHeapBigInt(); >+ auto string = bigInt->toString(globalObject, 10); >+ RETURN_IF_EXCEPTION(scope, { }); >+ return IntlMathematicalValue { >+ IntlMathematicalValue::NumberType::Integer, >+ bigInt->sign(), >+ string.ascii(), >+ }; >+ }; >+ >+ if (primitive.isBigInt()) >+ RELEASE_AND_RETURN(scope, bigIntToIntlMathematicalValue(globalObject, primitive)); >+ >+ if (!primitive.isString()) >+ RELEASE_AND_RETURN(scope, IntlMathematicalValue { primitive.toNumber(globalObject) }); >+ >+ String string = asString(primitive)->value(globalObject); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ JSValue bigInt = JSBigInt::stringToBigInt(globalObject, string); >+ if (bigInt) { >+ // If it is -0, we cannot handle it in JSBigInt. Reparse the string as double. >+#if USE(BIGINT32) >+ if (bigInt.isBigInt32() && !value.bigInt32AsInt32()) >+ return IntlMathematicalValue { jsToNumber(string) }; >+#endif >+ if (bigInt.isHeapBigInt() && !asHeapBigInt(bigInt)->length()) >+ return IntlMathematicalValue { jsToNumber(string) }; >+ RELEASE_AND_RETURN(scope, bigIntToIntlMathematicalValue(globalObject, bigInt)); >+ } >+ >+ return IntlMathematicalValue { jsToNumber(string) }; >+} >+ > } // namespace JSC >diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp b/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp >index def029b14dd3b9eebbaa6439c4e0b4f3a46be728..cbef60909972d6ac2b6e8dfed9cd0444606f2068 100644 >--- a/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp >+++ b/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp >@@ -39,6 +39,10 @@ static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatToParts); > static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncResolvedOptions); > static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatFuncFormat); > >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRange); >+#endif >+ > } > > #include "IntlNumberFormatPrototype.lut.h" >@@ -55,10 +59,10 @@ const ClassInfo IntlNumberFormatPrototype::s_info = { "Intl.NumberFormat", &Base > @end > */ > >-IntlNumberFormatPrototype* IntlNumberFormatPrototype::create(VM& vm, JSGlobalObject*, Structure* structure) >+IntlNumberFormatPrototype* IntlNumberFormatPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) > { > IntlNumberFormatPrototype* object = new (NotNull, allocateCell<IntlNumberFormatPrototype>(vm.heap)) IntlNumberFormatPrototype(vm, structure); >- object->finishCreation(vm); >+ object->finishCreation(vm, globalObject); > return object; > } > >@@ -72,11 +76,15 @@ IntlNumberFormatPrototype::IntlNumberFormatPrototype(VM& vm, Structure* structur > { > } > >-void IntlNumberFormatPrototype::finishCreation(VM& vm) >+void IntlNumberFormatPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) > { > Base::finishCreation(vm); > ASSERT(inherits(vm, info())); > JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); >+ UNUSED_PARAM(globalObject); >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("formatRange", intlNumberFormatPrototypeFuncFormatRange, static_cast<unsigned>(PropertyAttribute::DontEnum), 1); >+#endif > } > > // https://tc39.es/ecma402/#sec-number-format-functions >@@ -86,26 +94,13 @@ JSC_DEFINE_HOST_FUNCTION(intlNumberFormatFuncFormat, (JSGlobalObject* globalObje > auto scope = DECLARE_THROW_SCOPE(vm); > auto* numberFormat = jsCast<IntlNumberFormat*>(callFrame->thisValue()); > >- JSValue bigIntOrNumber = callFrame->argument(0).toNumeric(globalObject); >- RETURN_IF_EXCEPTION(scope, encodedJSValue()); >- >- scope.release(); >- if (bigIntOrNumber.isNumber()) { >- double value = bigIntOrNumber.asNumber(); >- return JSValue::encode(numberFormat->format(globalObject, value)); >- } >+ auto value = toIntlMathematicalValue(globalObject, callFrame->argument(0)); >+ RETURN_IF_EXCEPTION(scope, { }); > >-#if USE(BIGINT32) >- if (bigIntOrNumber.isBigInt32()) { >- JSBigInt* value = JSBigInt::createFrom(globalObject, bigIntOrNumber.bigInt32AsInt32()); >- RETURN_IF_EXCEPTION(scope, { }); >- RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, value))); >- } >-#endif >+ if (auto number = value.tryGetDouble()) >+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, number.value()))); > >- ASSERT(bigIntOrNumber.isHeapBigInt()); >- JSBigInt* value = bigIntOrNumber.asHeapBigInt(); >- return JSValue::encode(numberFormat->format(globalObject, value)); >+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, WTFMove(value)))); > } > > JSC_DEFINE_CUSTOM_GETTER(intlNumberFormatPrototypeGetterFormat, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) >@@ -137,6 +132,38 @@ JSC_DEFINE_CUSTOM_GETTER(intlNumberFormatPrototypeGetterFormat, (JSGlobalObject* > return JSValue::encode(boundFormat); > } > >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRange, (JSGlobalObject* globalObject, CallFrame* callFrame)) >+{ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ // Do not use unwrapForOldFunctions. >+ auto* numberFormat = jsDynamicCast<IntlNumberFormat*>(vm, callFrame->thisValue()); >+ if (UNLIKELY(!numberFormat)) >+ return JSValue::encode(throwTypeError(globalObject, scope, "Intl.NumberFormat.prototype.formatRange called on value that's not a NumberFormat"_s)); >+ >+ JSValue startValue = callFrame->argument(0); >+ JSValue endValue = callFrame->argument(1); >+ >+ if (startValue.isUndefined() || endValue.isUndefined()) >+ return throwVMTypeError(globalObject, scope, "start or end is undefined"_s); >+ >+ auto start = toIntlMathematicalValue(globalObject, startValue); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ auto end = toIntlMathematicalValue(globalObject, endValue); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ if (auto startNumber = start.tryGetDouble()) { >+ if (auto endNumber = end.tryGetDouble()) >+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatRange(globalObject, startNumber.value(), endNumber.value()))); >+ } >+ >+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatRange(globalObject, WTFMove(start), WTFMove(end)))); >+} >+#endif >+ > JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatToParts, (JSGlobalObject* globalObject, CallFrame* callFrame)) > { > VM& vm = globalObject->vm(); >@@ -150,10 +177,20 @@ JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatToParts, (JSGlobalOb > if (UNLIKELY(!numberFormat)) > return JSValue::encode(throwTypeError(globalObject, scope, "Intl.NumberFormat.prototype.formatToParts called on value that's not a NumberFormat"_s)); > >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+ auto value = toIntlMathematicalValue(globalObject, callFrame->argument(0)); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ if (auto number = value.tryGetDouble()) >+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatToParts(globalObject, number.value()))); >+ >+ RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatToParts(globalObject, WTFMove(value)))); >+#else > double value = callFrame->argument(0).toNumber(globalObject); >- RETURN_IF_EXCEPTION(scope, encodedJSValue()); >+ RETURN_IF_EXCEPTION(scope, { }); > > RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatToParts(globalObject, value))); >+#endif > } > > JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncResolvedOptions, (JSGlobalObject* globalObject, CallFrame* callFrame)) >diff --git a/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.h b/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.h >index 6175f9dd0937929f8febdacbc4e8fcdd46bc3743..0337d60f667840bcc04a32483bce0a6a463e5a38 100644 >--- a/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.h >+++ b/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.h >@@ -49,7 +49,7 @@ class IntlNumberFormatPrototype final : public JSNonFinalObject { > > private: > IntlNumberFormatPrototype(VM&, Structure*); >- void finishCreation(VM&); >+ void finishCreation(VM&, JSGlobalObject*); > }; > > } // namespace JSC >diff --git a/Source/JavaScriptCore/runtime/IntlObjectInlines.h b/Source/JavaScriptCore/runtime/IntlObjectInlines.h >index 6a00ab7089cef86b6af5a84c0196e8f41246361a..41c2bfdf6ee515c234dcbce60cdb3303eab30afe 100644 >--- a/Source/JavaScriptCore/runtime/IntlObjectInlines.h >+++ b/Source/JavaScriptCore/runtime/IntlObjectInlines.h >@@ -151,6 +151,45 @@ ResultType intlOption(JSGlobalObject* globalObject, JSObject* options, PropertyN > return fallback; > } > >+template<typename ResultType> >+ResultType intlStringOrBooleanOption(JSGlobalObject* globalObject, JSObject* options, PropertyName property, ResultType trueValue, ResultType falsyValue, std::initializer_list<std::pair<ASCIILiteral, ResultType>> values, ASCIILiteral notFoundMessage, ResultType fallback) >+{ >+ // https://tc39.es/proposal-intl-numberformat-v3/out/negotiation/diff.html#sec-getstringorbooleanoption >+ >+ ASSERT(values.size() > 0); >+ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ if (!options) >+ return fallback; >+ >+ JSValue value = options->get(globalObject, property); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ if (!value.isUndefined()) { >+ if (value.isBoolean() && value.asBoolean()) >+ return trueValue; >+ >+ bool valueBoolean = value.toBoolean(globalObject); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ if (!valueBoolean) >+ return falsyValue; >+ >+ String stringValue = value.toWTFString(globalObject); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ for (const auto& entry : values) { >+ if (entry.first == stringValue) >+ return entry.second; >+ } >+ throwException(globalObject, scope, createRangeError(globalObject, notFoundMessage)); >+ return { }; >+ } >+ >+ return fallback; >+} > > ALWAYS_INLINE bool canUseASCIIUCADUCETComparison(UChar character) > { >diff --git a/Source/JavaScriptCore/runtime/IntlPluralRules.cpp b/Source/JavaScriptCore/runtime/IntlPluralRules.cpp >index d8bc9669fcbac2e209ae454d62bfc3d28a35e489..842d3fa9a31e3763210f1d49ca97c0cb7514dcb1 100644 >--- a/Source/JavaScriptCore/runtime/IntlPluralRules.cpp >+++ b/Source/JavaScriptCore/runtime/IntlPluralRules.cpp >@@ -32,8 +32,26 @@ > #include "JSCInlines.h" > #include "ObjectConstructor.h" > >+#ifdef U_HIDE_DRAFT_API >+#undef U_HIDE_DRAFT_API >+#endif >+#include <unicode/upluralrules.h> >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+#include <unicode/unumberformatter.h> >+#endif >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+#include <unicode/unumberrangeformatter.h> >+#endif >+#define U_HIDE_DRAFT_API 1 >+ > namespace JSC { > >+void UPluralRulesDeleter::operator()(UPluralRules* pluralRules) >+{ >+ if (pluralRules) >+ uplrules_close(pluralRules); >+} >+ > const ClassInfo IntlPluralRules::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlPluralRules) }; > > using UEnumerationDeleter = ICUDeleter<uenum_close>; >@@ -107,8 +125,34 @@ void IntlPluralRules::initializePluralRules(JSGlobalObject* globalObject, JSValu > setNumberFormatDigitOptions(globalObject, this, options, 0, 3, IntlNotation::Standard); > RETURN_IF_EXCEPTION(scope, void()); > >+ auto locale = m_locale.utf8(); > UErrorCode status = U_ZERO_ERROR; >- m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(UNUM_DECIMAL, nullptr, 0, m_locale.utf8().data(), nullptr, &status)); >+ >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+ StringBuilder skeletonBuilder; >+ skeletonBuilder.append("rounding-mode-half-up"); >+ >+ appendNumberFormatDigitOptionsToSkeleton(this, skeletonBuilder); >+ >+ String skeleton = skeletonBuilder.toString(); >+ StringView skeletonView(skeleton); >+ auto upconverted = skeletonView.upconvertedCharacters(); >+ >+ m_numberFormatter = std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter>(unumf_openForSkeletonAndLocale(upconverted.get(), skeletonView.length(), locale.data(), &status)); >+ if (U_FAILURE(status)) { >+ throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s); >+ return; >+ } >+ >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ m_numberRangeFormatter = std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter>(unumrf_openForSkeletonWithCollapseAndIdentityFallback(upconverted.get(), skeletonView.length(), UNUM_RANGE_COLLAPSE_NONE, UNUM_IDENTITY_FALLBACK_RANGE, locale.data(), nullptr, &status)); >+ if (U_FAILURE(status)) { >+ throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s); >+ return; >+ } >+#endif >+#else >+ m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(UNUM_DECIMAL, nullptr, 0, locale.data(), nullptr, &status)); > if (U_FAILURE(status)) { > throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s); > return; >@@ -125,13 +169,13 @@ void IntlPluralRules::initializePluralRules(JSGlobalObject* globalObject, JSValu > unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits); > unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits); > break; >- default: >- RELEASE_ASSERT_NOT_REACHED(); >+ case IntlRoundingType::MorePrecision: >+ case IntlRoundingType::LessPrecision: > break; > } >+#endif > >- status = U_ZERO_ERROR; >- m_pluralRules = std::unique_ptr<UPluralRules, UPluralRulesDeleter>(uplrules_openForType(m_locale.utf8().data(), m_type == Type::Ordinal ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL, &status)); >+ m_pluralRules = std::unique_ptr<UPluralRules, UPluralRulesDeleter>(uplrules_openForType(locale.data(), m_type == Type::Ordinal ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL, &status)); > if (U_FAILURE(status)) { > throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s); > return; >@@ -159,8 +203,12 @@ JSObject* IntlPluralRules::resolvedOptions(JSGlobalObject* globalObject) const > options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits)); > options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits)); > break; >- default: >- RELEASE_ASSERT_NOT_REACHED(); >+ case IntlRoundingType::MorePrecision: >+ case IntlRoundingType::LessPrecision: >+ options->putDirect(vm, vm.propertyNames->minimumFractionDigits, jsNumber(m_minimumFractionDigits)); >+ options->putDirect(vm, vm.propertyNames->maximumFractionDigits, jsNumber(m_maximumFractionDigits)); >+ options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits)); >+ options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits)); > break; > } > >@@ -183,8 +231,9 @@ JSObject* IntlPluralRules::resolvedOptions(JSGlobalObject* globalObject) const > RETURN_IF_EXCEPTION(scope, { }); > } > options->putDirect(vm, Identifier::fromString(vm, "pluralCategories"), categories); >+ options->putDirect(vm, vm.propertyNames->roundingMode, jsNontrivialString(vm, IntlNumberFormat::roundingPriorityString(m_roundingType))); > >- RELEASE_AND_RETURN(scope, options); >+ return options; > } > > // https://tc39.es/ecma402/#sec-resolveplural >@@ -199,12 +248,57 @@ JSValue IntlPluralRules::select(JSGlobalObject* globalObject, double value) cons > return jsNontrivialString(vm, "other"_s); > > UErrorCode status = U_ZERO_ERROR; >+ >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+ auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status)); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select plural value"_s); >+ unumf_formatDouble(m_numberFormatter.get(), value, formattedNumber.get(), &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select plural value"_s); >+ Vector<UChar, 32> buffer; >+ status = callBufferProducingFunction(uplrules_selectFormatted, m_pluralRules.get(), formattedNumber.get(), buffer); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select plural value"_s); >+ return jsString(vm, String(WTFMove(buffer))); >+#else > Vector<UChar, 8> result(8); > auto length = uplrules_selectWithFormat(m_pluralRules.get(), value, m_numberFormat.get(), result.data(), result.size(), &status); > if (U_FAILURE(status)) > return throwTypeError(globalObject, scope, "failed to select plural value"_s); > > return jsString(vm, String(result.data(), length)); >+#endif >+} >+ >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+JSValue IntlPluralRules::selectRange(JSGlobalObject* globalObject, double start, double end) const >+{ >+ ASSERT(m_numberRangeFormatter); >+ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ if (start > end) { >+ throwRangeError(globalObject, scope, "start is larger than end"_s); >+ return { }; >+ } >+ >+ UErrorCode status = U_ZERO_ERROR; >+ auto range = std::unique_ptr<UFormattedNumberRange, ICUDeleter<unumrf_closeResult>>(unumrf_openResult(&status)); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select range of plural value"_s); >+ >+ unumrf_formatDoubleRange(m_numberRangeFormatter.get(), start, end, range.get(), &status); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select range of plural value"_s); >+ >+ Vector<UChar, 32> buffer; >+ status = callBufferProducingFunction(uplrules_selectForRange, m_pluralRules.get(), range.get(), buffer); >+ if (U_FAILURE(status)) >+ return throwTypeError(globalObject, scope, "failed to select plural value"_s); >+ return jsString(vm, String(WTFMove(buffer))); > } >+#endif > > } // namespace JSC >diff --git a/Source/JavaScriptCore/runtime/IntlPluralRules.h b/Source/JavaScriptCore/runtime/IntlPluralRules.h >index 6d3acab84e814e5f7c07d024f3e25ff2e5f0f6e4..ebc04aa5799ce6cca8d3dbfce9f806a83661ede0 100644 >--- a/Source/JavaScriptCore/runtime/IntlPluralRules.h >+++ b/Source/JavaScriptCore/runtime/IntlPluralRules.h >@@ -28,11 +28,16 @@ > > #include "IntlNumberFormat.h" > #include <unicode/unum.h> >-#include <unicode/upluralrules.h> > #include <wtf/unicode/icu/ICUHelpers.h> > >+struct UPluralRules; >+ > namespace JSC { > >+struct UPluralRulesDeleter { >+ JS_EXPORT_PRIVATE void operator()(UPluralRules*); >+}; >+ > enum class RelevantExtensionKey : uint8_t; > > class IntlPluralRules final : public JSNonFinalObject { >@@ -59,11 +64,17 @@ class IntlPluralRules final : public JSNonFinalObject { > > template<typename IntlType> > friend void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation); >+ template<typename IntlType> >+ friend void appendNumberFormatDigitOptionsToSkeleton(IntlType*, StringBuilder&); > > void initializePluralRules(JSGlobalObject*, JSValue locales, JSValue options); > JSValue select(JSGlobalObject*, double value) const; > JSObject* resolvedOptions(JSGlobalObject*) const; > >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ JSValue selectRange(JSGlobalObject*, double start, double end) const; >+#endif >+ > private: > IntlPluralRules(VM&, Structure*); > void finishCreation(VM&); >@@ -73,11 +84,16 @@ class IntlPluralRules final : public JSNonFinalObject { > > enum class Type : bool { Cardinal, Ordinal }; > >- using UPluralRulesDeleter = ICUDeleter<uplrules_close>; >- using UNumberFormatDeleter = ICUDeleter<unum_close>; >- > std::unique_ptr<UPluralRules, UPluralRulesDeleter> m_pluralRules; >+#if HAVE(ICU_U_NUMBER_FORMATTER) >+ std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter> m_numberFormatter; >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter> m_numberRangeFormatter; >+#endif >+#else >+ using UNumberFormatDeleter = ICUDeleter<unum_close>; > std::unique_ptr<UNumberFormat, UNumberFormatDeleter> m_numberFormat; >+#endif > > String m_locale; > unsigned m_minimumIntegerDigits { 1 }; >@@ -85,6 +101,7 @@ class IntlPluralRules final : public JSNonFinalObject { > unsigned m_maximumFractionDigits { 3 }; > unsigned m_minimumSignificantDigits { 0 }; > unsigned m_maximumSignificantDigits { 0 }; >+ unsigned m_roundingIncrement { 1 }; > IntlRoundingType m_roundingType { IntlRoundingType::FractionDigits }; > Type m_type { Type::Cardinal }; > }; >diff --git a/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp b/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp >index c80bfa2223fd50e8b97818ced4b0758bbcde6c27..ec69fe29bb23980c647ba4af064b1868716a9a8b 100644 >--- a/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp >+++ b/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp >@@ -33,6 +33,9 @@ > namespace JSC { > > static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect); >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelectRange); >+#endif > static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncResolvedOptions); > > } >@@ -50,10 +53,10 @@ const ClassInfo IntlPluralRulesPrototype::s_info = { "Intl.PluralRules", &Base:: > @end > */ > >-IntlPluralRulesPrototype* IntlPluralRulesPrototype::create(VM& vm, JSGlobalObject*, Structure* structure) >+IntlPluralRulesPrototype* IntlPluralRulesPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) > { > IntlPluralRulesPrototype* object = new (NotNull, allocateCell<IntlPluralRulesPrototype>(vm.heap)) IntlPluralRulesPrototype(vm, structure); >- object->finishCreation(vm); >+ object->finishCreation(vm, globalObject); > return object; > } > >@@ -67,11 +70,15 @@ IntlPluralRulesPrototype::IntlPluralRulesPrototype(VM& vm, Structure* structure) > { > } > >-void IntlPluralRulesPrototype::finishCreation(VM& vm) >+void IntlPluralRulesPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) > { > Base::finishCreation(vm); > ASSERT(inherits(vm, info())); > JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); >+ UNUSED_PARAM(globalObject); >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+ JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->selectRange, intlPluralRulesPrototypeFuncSelectRange, static_cast<unsigned>(PropertyAttribute::DontEnum), 0); >+#endif > } > > JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect, (JSGlobalObject* globalObject, CallFrame* callFrame)) >@@ -92,6 +99,27 @@ JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect, (JSGlobalObject* gl > RELEASE_AND_RETURN(scope, JSValue::encode(pluralRules->select(globalObject, value))); > } > >+#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) >+JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelectRange, (JSGlobalObject* globalObject, CallFrame* callFrame)) >+{ >+ VM& vm = globalObject->vm(); >+ auto scope = DECLARE_THROW_SCOPE(vm); >+ >+ // https://tc39.es/proposal-intl-numberformat-v3/out/pluralrules/diff.html#sec-intl.pluralrules.prototype.selectrange >+ IntlPluralRules* pluralRules = jsDynamicCast<IntlPluralRules*>(vm, callFrame->thisValue()); >+ if (!pluralRules) >+ return JSValue::encode(throwTypeError(globalObject, scope, "Intl.PluralRules.prototype.selectRange called on value that's not a PluralRules"_s)); >+ >+ double start = callFrame->argument(0).toNumber(globalObject); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ double end = callFrame->argument(1).toNumber(globalObject); >+ RETURN_IF_EXCEPTION(scope, { }); >+ >+ RELEASE_AND_RETURN(scope, JSValue::encode(pluralRules->selectRange(globalObject, start, end))); >+} >+#endif >+ > JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncResolvedOptions, (JSGlobalObject* globalObject, CallFrame* callFrame)) > { > VM& vm = globalObject->vm(); >diff --git a/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h b/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h >index 6faa3a4f78aea598432cb13037f103593625084b..61e08b2f4d2cab063c75c6976c1b360fafe5fd0f 100644 >--- a/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h >+++ b/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h >@@ -49,7 +49,7 @@ class IntlPluralRulesPrototype final : public JSNonFinalObject { > > private: > IntlPluralRulesPrototype(VM&, Structure*); >- void finishCreation(VM&); >+ void finishCreation(VM&, JSGlobalObject*); > }; > > } // namespace JSC >diff --git a/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp b/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp >index 0b596612ff8c35f1a1de24dcf0c76ee3dab84b55..c2538500fb8e3417cbf1c608aed521b754be9b65 100644 >--- a/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp >+++ b/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp >@@ -318,7 +318,7 @@ JSValue IntlRelativeTimeFormat::formatToParts(JSGlobalObject* globalObject, doub > } > > IntlFieldIterator fieldIterator(*iterator.get()); >- IntlNumberFormat::formatToPartsInternal(globalObject, IntlNumberFormat::Style::Decimal, absValue, formattedNumber, fieldIterator, parts, jsString(vm, singularUnit(unit).toString())); >+ IntlNumberFormat::formatToPartsInternal(globalObject, IntlNumberFormat::Style::Decimal, std::signbit(absValue), IntlMathematicalValue::numberTypeFromDouble(absValue), formattedNumber, fieldIterator, parts, jsString(vm, singularUnit(unit).toString())); > RETURN_IF_EXCEPTION(scope, { }); > } > >diff --git a/Source/JavaScriptCore/runtime/JSBigInt.h b/Source/JavaScriptCore/runtime/JSBigInt.h >index 8961c1d8fb6101ab68d970f02b71edcb334dfd78..b7631671414c93c5fd84493cc49a3a3776248174 100644 >--- a/Source/JavaScriptCore/runtime/JSBigInt.h >+++ b/Source/JavaScriptCore/runtime/JSBigInt.h >@@ -462,6 +462,8 @@ class JSBigInt final : public JSCell { > return hashSlow(); > } > >+ static std::optional<double> tryExtractDouble(JSValue); >+ > private: > JSBigInt(VM&, Structure*, Digit*, unsigned length); > >@@ -676,4 +678,42 @@ ALWAYS_INLINE JSValue tryConvertToBigInt32(JSBigInt* bigInt) > return bigInt; > } > >+ALWAYS_INLINE std::optional<double> JSBigInt::tryExtractDouble(JSValue value) >+{ >+ if (value.isNumber()) >+ return value.asNumber(); >+ >+ if (!value.isBigInt()) >+ return std::nullopt; >+ >+#if USE(BIGINT32) >+ if (value.isBigInt32()) >+ return value.bigInt32AsInt32(); >+#endif >+ >+ ASSERT(value.isHeapBigInt()); >+ JSBigInt* bigInt = value.asHeapBigInt(); >+ if (!bigInt->length()) >+ return 0; >+ >+ uint64_t integer = 0; >+ if constexpr (sizeof(Digit) == 8) { >+ if (bigInt->length() != 1) >+ return std::nullopt; >+ integer = bigInt->digit(0); >+ } else { >+ ASSERT(sizeof(Digit) == 4); >+ if (bigInt->length() > 2) >+ return std::nullopt; >+ integer = bigInt->digit(0); >+ if (bigInt->length() == 2) >+ integer |= (static_cast<uint64_t>(bigInt->digit(1)) << 32); >+ } >+ >+ if (integer <= static_cast<uint64_t>(maxSafeInteger())) >+ return (bigInt->sign()) ? -static_cast<double>(integer) : static_cast<double>(integer); >+ >+ return std::nullopt; >+} >+ > } // namespace JSC >diff --git a/Source/JavaScriptCore/runtime/MathCommon.h b/Source/JavaScriptCore/runtime/MathCommon.h >index da920e1b213570609f267fa2a762e9851125d7de..4ac3dd9536f8d2242e64eda674462ae7af556391 100644 >--- a/Source/JavaScriptCore/runtime/MathCommon.h >+++ b/Source/JavaScriptCore/runtime/MathCommon.h >@@ -63,6 +63,11 @@ inline bool isSafeInteger(double value) > return std::trunc(value) == value && std::abs(value) <= maxSafeInteger(); > } > >+inline bool isNegativeZero(double value) >+{ >+ return std::signbit(value) && value == 0; >+} >+ > // This in the ToInt32 operation is defined in section 9.5 of the ECMA-262 spec. > // Note that this operation is identical to ToUInt32 other than to interpretation > // of the resulting bit-pattern (as such this method is also called to implement >diff --git a/Source/JavaScriptCore/runtime/TemporalObject.cpp b/Source/JavaScriptCore/runtime/TemporalObject.cpp >index ff0bcfdcf82d221d1ecb85a8c1b46b651b4db735..6f0887495fec9de91bedb15ab1ea600ea6400e4b 100644 >--- a/Source/JavaScriptCore/runtime/TemporalObject.cpp >+++ b/Source/JavaScriptCore/runtime/TemporalObject.cpp >@@ -430,6 +430,14 @@ double roundNumberToIncrement(double x, double increment, RoundingMode mode) > return std::trunc(quotient) * increment; > case RoundingMode::HalfExpand: > return std::round(quotient) * increment; >+ >+ // They are not supported in Temporal right now. >+ case RoundingMode::Expand: >+ case RoundingMode::HalfCeil: >+ case RoundingMode::HalfFloor: >+ case RoundingMode::HalfTrunc: >+ case RoundingMode::HalfEven: >+ return std::trunc(quotient) * increment; > } > > RELEASE_ASSERT_NOT_REACHED(); >diff --git a/Source/JavaScriptCore/runtime/TemporalObject.h b/Source/JavaScriptCore/runtime/TemporalObject.h >index 7915798cdb98ab74583b8c8ac36a55d1b45f1c14..d9fddf27ffefe669dcfe7a68a3d29727c8cad2b8 100644 >--- a/Source/JavaScriptCore/runtime/TemporalObject.h >+++ b/Source/JavaScriptCore/runtime/TemporalObject.h >@@ -79,8 +79,13 @@ class TemporalObject final : public JSNonFinalObject { > enum class RoundingMode : uint8_t { > Ceil, > Floor, >+ Expand, > Trunc, >- HalfExpand >+ HalfCeil, >+ HalfFloor, >+ HalfExpand, >+ HalfTrunc, >+ HalfEven > }; > > enum class Precision : uint8_t { >diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog >index 764b92162940567851bb4ac8c7fe29735d4054d9..bf4796b15750479ae12e19d317b3f287c87edec6 100644 >--- a/JSTests/ChangeLog >+++ b/JSTests/ChangeLog >@@ -1,3 +1,68 @@ >+2021-10-26 Yusuke Suzuki <ysuzuki@apple.com> >+ >+ [JSC] Implement IntlNumberFormat v3 >+ https://bugs.webkit.org/show_bug.cgi?id=215438 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * stress/intl-numberformat-format-large.js: Added. >+ (shouldBe): >+ * stress/intl-numberformat-format-range-v3.js: Added. >+ (shouldBe): >+ (shouldThrow): >+ (shouldNotThrow): >+ (nf.formatRange.string_appeared_here.forEach): >+ (nf.formatRangeToParts.validRanges.forEach): >+ (nf6.formatRange.shouldThrow): >+ * stress/intl-numberformat-format-string-v3.js: Added. >+ (shouldBe): >+ * stress/intl-numberformat-format-string.js: Added. >+ (shouldBe): >+ * stress/intl-numberformat-format-to-parts.js: >+ * stress/intl-numberformat-rounding-increment-resolved-match-v3.js: Added. >+ (shouldBe): >+ (validRoundingIncrements.forEach): >+ * stress/intl-numberformat-rounding-increment-v3.js: Added. >+ (shouldThrow): >+ (validRoundingIncrements.forEach): >+ (invalidRoundingIncrements.forEach): >+ * stress/intl-numberformat-rounding-increment-value-hanidec.js: Added. >+ (shouldBe): >+ * stress/intl-numberformat-rounding-increment-value-v3.js: Added. >+ (shouldBe): >+ * stress/intl-numberformat-rounding-increment-value.js: Added. >+ (shouldBe): >+ * stress/intl-numberformat-rounding-mode-table-v3.js: Added. >+ (shouldBe): >+ (Object.keys.expectations.forEach): >+ * stress/intl-numberformat-rounding-mode-v3.js: Added. >+ (shouldBe): >+ (shouldThrow): >+ (validRoundingMode.forEach): >+ (invalidRoundingMode.forEach): >+ (let.options.get signDisplay): >+ (let.options.get roundingMode): >+ * stress/intl-numberformat-sign-display-v3.js: Added. >+ (shouldBe): >+ * stress/intl-numberformat-trailing-zero-display-resolved-options-v3.js: Added. >+ (shouldBe): >+ * stress/intl-numberformat-trailing-zero-display-v3.js: Added. >+ (shouldBe): >+ * stress/intl-numberformat-usegrouping-v3.js: Added. >+ (shouldBe): >+ (shouldThrow): >+ (validUseGrouping.forEach): >+ (invalidUseGrouping.forEach): >+ (all.forEach): >+ (mgd1.forEach): >+ (mgd2.forEach): >+ * stress/intl-numberformat.js: >+ (testNumberFormat): >+ (shouldBe.testNumberFormat.Intl.NumberFormat): >+ * stress/intl-pluralrules-selectrange.js: Added. >+ (shouldBe): >+ * test262/config.yaml: >+ > 2021-10-26 Yusuke Suzuki <ysuzuki@apple.com> > > Unreivewed, fix CLoop build and test failure >diff --git a/JSTests/stress/intl-numberformat-format-large.js b/JSTests/stress/intl-numberformat-format-large.js >new file mode 100644 >index 0000000000000000000000000000000000000000..1794f579d69b00341c39effafde59fe4a49fc644 >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-format-large.js >@@ -0,0 +1,15 @@ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+const nf = new Intl.NumberFormat("en-US"); >+const nf2 = new Intl.NumberFormat("ja-JP"); >+const start = 987654321987654321n; >+const end = 987654321987654322n; >+shouldBe(nf.format(start), `987,654,321,987,654,321`); >+shouldBe(nf2.format(start), `987,654,321,987,654,321`); >+if (nf.formatRange) { >+ shouldBe(nf.formatRange(start, end), `987,654,321,987,654,321â987,654,321,987,654,322`); >+ shouldBe(nf2.formatRange(start, end), `987,654,321,987,654,321ï½987,654,321,987,654,322`); >+} >diff --git a/JSTests/stress/intl-numberformat-format-range-v3.js b/JSTests/stress/intl-numberformat-format-range-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..22237c170f16ca16cb22891eed5aa75e62382c42 >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-format-range-v3.js >@@ -0,0 +1,223 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Copyright 2021 Apple Inc. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+function shouldThrow(func, errorType) { >+ let error; >+ try { >+ func(); >+ } catch (e) { >+ error = e; >+ } >+ >+ if (!(error instanceof errorType)) >+ throw new Error(`Expected ${errorType.name} but got ${String(error)}!`); >+} >+ >+function shouldNotThrow(func) { >+ func(); >+} >+ >+const validRanges = [[-12345, -5678], [-12345, 56789], [12345, 56789]]; >+ >+const nf = new Intl.NumberFormat("en", {signDisplay: "exceptZero"}); >+if (nf.formatRange) { >+ ['formatRange'].forEach(function(method) { >+ shouldBe("function", typeof nf[method]); >+ >+ // 2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]). >+ // Assert if called without nf >+ let f = nf[method]; >+ shouldThrow(() => { f(1, 23) }, TypeError); >+ >+ // Assert normal call success >+ shouldNotThrow(() => nf[method](1, 23)); >+ >+ // 3. If start is undefined ..., throw a TypeError exception. >+ shouldThrow(() => { nf[method](undefined, 23) }, TypeError); >+ // 3. If ... end is undefined, throw a TypeError exception. >+ shouldThrow(() => { nf[method](1, undefined) }, TypeError); >+ >+ // 4. Let x be ? ToNumeric(start). >+ // Verify it won't throw error >+ shouldNotThrow(() => nf[method](null, 23)); >+ shouldNotThrow(() => nf[method](false, 23)); >+ shouldNotThrow(() => nf[method](true, 23)); >+ shouldNotThrow(() => nf[method]("12", 23)); >+ shouldNotThrow(() => nf[method]("-123456789012345678901234567890", 23)); >+ shouldThrow(() => { nf[method](Symbol(12), 23) }, TypeError); >+ shouldNotThrow(() => nf[method](12, 23)); >+ shouldNotThrow(() => nf[method](12n, 23)); >+ shouldThrow(() => { nf[method]({}, -23) }, RangeError); >+ shouldNotThrow(() => { nf[method]([], 23) }); >+ >+ // 5. Let y be ? ToNumeric(end). >+ // Verify it won't throw error >+ shouldNotThrow(() => nf[method](-12, null)); >+ shouldNotThrow(() => nf[method](-12, false)); >+ shouldNotThrow(() => nf[method](-12, true)); >+ shouldNotThrow(() => nf[method](12, "23")); >+ shouldNotThrow(() => nf[method](12, "23456789012345678901234567890")); >+ shouldThrow(() => { nf[method](12, Symbol(23)) }, TypeError); >+ shouldNotThrow(() => nf[method](12, 23)); >+ shouldNotThrow(() => nf[method](12, 23n)); >+ shouldThrow(() => { nf[method](-12, {}) }, RangeError); >+ shouldNotThrow(() => { nf[method](-12, []) }); >+ >+ // 6. If x is NaN ..., throw a RangeError exception. >+ shouldThrow(() => { nf[method](NaN, 23) }, RangeError); >+ shouldThrow(() => { nf[method]("NaN", 23) }, RangeError); >+ // 6. If ... y is NaN, throw a RangeError exception. >+ shouldThrow(() => { nf[method](12, NaN) }, RangeError); >+ shouldThrow(() => { nf[method](12, "NaN") }, RangeError); >+ shouldThrow(() => { nf[method](12, "xyz") }, RangeError); >+ >+ // 7. If x is a non-finite Number ..., throw a RangeError exception. >+ shouldNotThrow(() => { nf[method](-12/0, 12/0) }); >+ shouldThrow(() => { nf[method](12/0, -12/0) }, RangeError); >+ >+ // 8. If x is greater than y, throw a RangeError exception. >+ // neither x nor y are bigint. >+ shouldThrow(() => { nf[method](23, 12) }, RangeError); >+ shouldNotThrow(() => nf[method](12, 23)); >+ // x is not bigint but y is. >+ shouldThrow(() => { nf[method](23, 12n) }, RangeError); >+ shouldNotThrow(() => nf[method](12, 23n)); >+ // x is bigint but y is not. >+ shouldThrow(() => { nf[method](23n, 12) }, RangeError); >+ shouldNotThrow(() => nf[method](12n, 23)); >+ // both x and y are bigint. >+ shouldThrow(() => { nf[method](23n, 12n) }, RangeError); >+ shouldNotThrow(() => 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); >+ shouldBe(formatted_x_y, formatted_X_y); >+ shouldBe(formatted_x_y, formatted_x_Y); >+ shouldBe(formatted_x_y, formatted_X_Y); >+ >+ }); >+ }); >+} >+ >+// Check the number of part with type: "plusSign" and "minusSign" are corre >+if (nf.formatRangeToParts) { >+ 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 + "): "; >+ shouldBe(expectedPlus, actualPlus, >+ method + "Number of type: 'plusSign' in parts is incorrect"); >+ shouldBe(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" >+if (nf2.formatRange) >+ shouldBe("â¬3 â â¬5", nf2.formatRange(3, 5)); >+ >+const nf3 = new Intl.NumberFormat("en-US", { >+ style: "currency", >+ currency: "EUR", >+ maximumFractionDigits: 0, >+}); >+if (nf3.formatRangeToParts) { >+ 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"} >+ ] >+ */ >+ shouldBe(5, actual3.length); >+ shouldBe("currency", actual3[0].type); >+ shouldBe("â¬", actual3[0].value); >+ shouldBe("startRange", actual3[0].source); >+ shouldBe("integer", actual3[1].type); >+ shouldBe("3", actual3[1].value); >+ shouldBe("startRange", actual3[1].source); >+ shouldBe("literal", actual3[2].type); >+ shouldBe(" â ", actual3[2].value); >+ shouldBe("shared", actual3[2].source); >+ shouldBe("currency", actual3[3].type); >+ shouldBe("â¬", actual3[3].value); >+ shouldBe("endRange", actual3[3].source); >+ shouldBe("integer", actual3[4].type); >+ shouldBe("5", actual3[4].value); >+ shouldBe("endRange", actual3[4].source); >+} >+ >+const nf4 = new Intl.NumberFormat("en-US", { >+ style: "currency", >+ currency: "EUR", >+ maximumFractionDigits: 0, >+}); >+if (nf4.formatRange) >+ shouldBe("~â¬3", nf4.formatRange(2.9, 3.1)); >+ >+const nf5 = new Intl.NumberFormat("en-US", { >+ style: "currency", >+ currency: "EUR", >+ signDisplay: "always", >+}); >+if (nf5.formatRange) >+ shouldBe("~+â¬3.00", nf5.formatRange(2.999, 3.001)); >+ >+const nf6 = new Intl.NumberFormat("en"); >+if (nf6.formatRange) { >+ shouldBe("3ââ", nf6.formatRange(3, 1/0)); >+ shouldThrow(() => { nf6.formatRange(3, 0/0); }, RangeError); >+} >diff --git a/JSTests/stress/intl-numberformat-format-string-v3.js b/JSTests/stress/intl-numberformat-format-string-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..95d5bf01a11fbc776cc98fb47942cd87e6f2f6e1 >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-format-string-v3.js >@@ -0,0 +1,43 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Copyright 2021 Apple Inc. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+const nf = new Intl.NumberFormat("en-US"); >+const nf2 = new Intl.NumberFormat("ja-JP"); >+const string = "987654321987654321"; >+const string2 = "987654321987654322"; >+shouldBe(nf.format(string), `987,654,321,987,654,321`); >+shouldBe(nf2.format(string), `987,654,321,987,654,321`); >+if (nf.formatRange) { >+ shouldBe(nf.formatRange(string, string2), `987,654,321,987,654,321â987,654,321,987,654,322`); >+ shouldBe(nf2.formatRange(string, string2), `987,654,321,987,654,321ï½987,654,321,987,654,322`); >+} >diff --git a/JSTests/stress/intl-numberformat-format-string.js b/JSTests/stress/intl-numberformat-format-string.js >new file mode 100644 >index 0000000000000000000000000000000000000000..fbcbdd188811006864b45919fb2946081b670b45 >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-format-string.js >@@ -0,0 +1,18 @@ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+const nf = new Intl.NumberFormat("en-US"); >+const nf2 = new Intl.NumberFormat("ja-JP"); >+shouldBe(nf.format("54.321"), `54.321`); >+if (nf.formatRange) { >+ shouldBe(nf.formatRange("-54.321", "+54.321"), `-54.321 â 54.321`); >+ shouldBe(nf.formatRange("-54.321", "20000000000000000000000000000000000000000"), `-54.321 â 20,000,000,000,000,000,000,000,000,000,000,000,000,000`); >+ shouldBe(nf.formatRange("-0", "0"), `-0 â 0`); >+ shouldBe(nf.formatRange("-0", "1000000000000000000000000000000000000000000000"), `-0 â 1,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000`); >+ shouldBe(nf2.formatRange("-54.321", "+54.321"), `-54.321 ï½ 54.321`); >+ shouldBe(nf2.formatRange("-54.321", "20000000000000000000000000000000000000000"), `-54.321 ï½ 20,000,000,000,000,000,000,000,000,000,000,000,000,000`); >+ shouldBe(nf2.formatRange("-0", "0"), `-0 ï½ 0`); >+ shouldBe(nf2.formatRange("-0", "1000000000000000000000000000000000000000000000"), `-0 ï½ 1,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000`); >+} >diff --git a/JSTests/stress/intl-numberformat-format-to-parts.js b/JSTests/stress/intl-numberformat-format-to-parts.js >index ef71f0f1c6c541dfa9bc3441aa1b3f407cd5e803..ccf97ec24156dd2d4d2c9a05f244bc5bd8636450 100644 >--- a/JSTests/stress/intl-numberformat-format-to-parts.js >+++ b/JSTests/stress/intl-numberformat-format-to-parts.js >@@ -57,3 +57,35 @@ function shouldBe(actual, expected) { > shouldBe(Intl.NumberFormat('en-US', { useGrouping: false }).formatToParts(Number.MAX_VALUE).length, 1); > shouldBe(Intl.NumberFormat('en-US', { useGrouping: false }).formatToParts(Number.MAX_VALUE)[0].value.length, 309); > shouldBe(Intl.NumberFormat('en-US').formatToParts(Number.MAX_VALUE).length, 205); >+ >+shouldBe( >+ JSON.stringify( >+ Intl.NumberFormat('en-US').formatToParts(4000n) >+ ), >+ JSON.stringify([ >+ {"type":"integer","value":"4"}, >+ {"type":"group","value":","}, >+ {"type":"integer","value":"000"} >+ ])); >+ >+shouldBe( >+ JSON.stringify( >+ Intl.NumberFormat('en-US').formatToParts(-4000n) >+ ), >+ JSON.stringify([ >+ {"type":"minusSign","value":"-"}, >+ {"type":"integer","value":"4"}, >+ {"type":"group","value":","}, >+ {"type":"integer","value":"000"} >+ ])); >+ >+shouldBe( >+ JSON.stringify( >+ Intl.NumberFormat('en-US').formatToParts("-4000") >+ ), >+ JSON.stringify([ >+ {"type":"minusSign","value":"-"}, >+ {"type":"integer","value":"4"}, >+ {"type":"group","value":","}, >+ {"type":"integer","value":"000"} >+ ])); >diff --git a/JSTests/stress/intl-numberformat-rounding-increment-resolved-match-v3.js b/JSTests/stress/intl-numberformat-rounding-increment-resolved-match-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..721e654ef98756c8d1d761ba20953603744ac0cb >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-rounding-increment-resolved-match-v3.js >@@ -0,0 +1,41 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Copyright 2021 Apple Inc. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+let validRoundingIncrements = [ >+ 1, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 >+]; >+ >+validRoundingIncrements.forEach(function(roundingIncrement) { >+ let nf = new Intl.NumberFormat(undefined, {roundingIncrement}); >+ shouldBe(roundingIncrement, nf.resolvedOptions().roundingIncrement); >+}); >diff --git a/JSTests/stress/intl-numberformat-rounding-increment-v3.js b/JSTests/stress/intl-numberformat-rounding-increment-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..032a931ac141522a826f09ac90a8bcfd0f4e822c >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-rounding-increment-v3.js >@@ -0,0 +1,59 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Copyright 2021 Apple Inc. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldThrow(func, errorType) { >+ let error; >+ try { >+ func(); >+ } catch (e) { >+ error = e; >+ } >+ >+ if (!(error instanceof errorType)) >+ throw new Error(`Expected ${errorType.name}!`); >+} >+ >+ >+let validRoundingIncrements = [ >+ 1, 2, 5, 10, 20, 50, 100, 500, 1000, 5000 >+]; >+ >+let invalidRoundingIncrements = [ >+ -1, -5, 0, 1001, 1100, 5500, 200000, 500005, 10000000, 10000, 50000, 100000, 500000, 1000000, >+ 5000000 >+]; >+ >+validRoundingIncrements.forEach(function(roundingIncrement) { >+ new Intl.NumberFormat(undefined, {roundingIncrement}); >+}); >+ >+invalidRoundingIncrements.forEach(function(roundingIncrement) { >+ shouldThrow(() => { >+ new Intl.NumberFormat(undefined, {roundingIncrement}) >+ }, RangeError); >+}); >diff --git a/JSTests/stress/intl-numberformat-rounding-increment-value-hanidec.js b/JSTests/stress/intl-numberformat-rounding-increment-value-hanidec.js >new file mode 100644 >index 0000000000000000000000000000000000000000..f0aee5b64452b5b9839e4b4cc82b315a6fef8a5d >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-rounding-increment-value-hanidec.js >@@ -0,0 +1,46 @@ >+// Copyright 2021 Apple Inc. All rights reserved. >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+let penny = new Intl.NumberFormat("ja-JP", { numberingSystem: 'hanidec', minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 1 }); >+let nickel = new Intl.NumberFormat("ja-JP", { numberingSystem: 'hanidec', minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 5 }); >+let dime = new Intl.NumberFormat("ja-JP", { numberingSystem: 'hanidec', minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 10 }); >+ >+shouldBe("ä¸ã.ä¸äº", penny.format(10.154)); >+shouldBe("ä¸ã.ä¸å ", penny.format(10.155)); >+shouldBe("ä¸ã.ä¸ã", nickel.format(10.124)); >+shouldBe("ä¸ã.ä¸äº", nickel.format(10.125)); >+shouldBe("ä¸ã.åã", dime.format(10.444)); >+shouldBe("ä¸ã.åã", dime.format(10.445)); >+shouldBe("ä¸ã.åã", dime.format(10.445)); >+shouldBe("ä¸ã.äºã", dime.format(10.45)); >diff --git a/JSTests/stress/intl-numberformat-rounding-increment-value-v3.js b/JSTests/stress/intl-numberformat-rounding-increment-value-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..50f268d986c16e3f497af38458feed8909d26436 >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-rounding-increment-value-v3.js >@@ -0,0 +1,49 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+let penny = new Intl.NumberFormat( >+ "en", { minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 1 }); >+let nickel = new Intl.NumberFormat( >+ "en", { minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 5 }); >+let dime = new Intl.NumberFormat( >+ "en", { minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 10 }); >+ >+// https://necs.com/knowledgebase/sysprefs_prc_mod_roundmeth.htm >+shouldBe("10.15", penny.format(10.154)); >+shouldBe("10.16", penny.format(10.155)); >+shouldBe("10.10", nickel.format(10.124)); >+shouldBe("10.15", nickel.format(10.125)); >+shouldBe("10.40", dime.format(10.444)); >+// mistake in the above page, the result should be 10.40 not 10.50 >+shouldBe("10.40", dime.format(10.445)); >+shouldBe("10.40", dime.format(10.445)); >+shouldBe("10.50", dime.format(10.45)); >diff --git a/JSTests/stress/intl-numberformat-rounding-increment-value.js b/JSTests/stress/intl-numberformat-rounding-increment-value.js >new file mode 100644 >index 0000000000000000000000000000000000000000..604b1a6f4a3c6175870d2f748a2312d86f02b40f >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-rounding-increment-value.js >@@ -0,0 +1,9 @@ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+shouldBe(new Intl.NumberFormat("en", { minimumFractionDigits: 3, maximumFractionDigits: 3, roundingIncrement: 10 }).format(55555555555.555555), `55,555,555,555.560`); >+shouldBe(new Intl.NumberFormat("en", { minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 100 }).format(55555555555.555555), `55,555,555,556.00`); >+shouldBe(new Intl.NumberFormat("en", { minimumFractionDigits: 2, maximumFractionDigits: 4, roundingIncrement: 1000 }).format(55555555555.555555), `55,555,555,555.6000`); >+shouldBe(new Intl.NumberFormat("en", { minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 1000 }).format(55555555555.555555), `55,555,555,560.00`); >+shouldBe(new Intl.NumberFormat("en", { minimumFractionDigits: 1, maximumFractionDigits: 1, roundingIncrement: 1000 }).format(55555555555.555555), `55,555,555,600.0`); >diff --git a/JSTests/stress/intl-numberformat-rounding-mode-table-v3.js b/JSTests/stress/intl-numberformat-rounding-mode-table-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..4dcf9a303f4555a1c2fe9f3e06d2c2f8f1d72989 >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-rounding-mode-table-v3.js >@@ -0,0 +1,61 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected, message) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected + " " + message); >+} >+ >+// Check the rounding behavior. >+// Based on https://tc39.es/proposal-intl-numberformat-v3/out/numberformat/diff.html#table-intl-rounding-modes >+let inputs = [-1.5, 0.4, 0.5, 0.6, 1.5]; >+let expectations = { >+ "ceil": ["-1", "1", "1", "1", "2"], >+ "floor": ["-2", "0", "0", "0", "1"], >+ "expand": ["-2", "1", "1", "1", "2"], >+ "trunc": ["-1", "0", "0", "0", "1"], >+ "halfCeil": ["-1", "0", "1", "1", "2"], >+ "halfFloor": ["-2", "0", "0", "1", "1"], >+ "halfExpand": ["-2", "0", "1", "1", "2"], >+ "halfTrunc": ["-1", "0", "0", "1", "1"], >+ "halfEven": ["-2", "0", "0", "1", "2"], >+}; >+Object.keys(expectations).forEach(function(roundingMode) { >+ let exp = expectations[roundingMode]; >+ let idx = 0; >+ let nf = new Intl.NumberFormat("en", {roundingMode, maximumFractionDigits: 0}); >+ shouldBe(roundingMode, nf.resolvedOptions().roundingMode); >+ inputs.forEach(function(input) { >+ let msg = "input: " + input + " with roundingMode: " + roundingMode; >+ if ($vm.icuVersion() >= 69) >+ shouldBe(exp[idx++], nf.format(input), msg); >+ else if (roundingMode !== "halfCeil" && roundingMode !== "halfFloor") >+ shouldBe(exp[idx++], nf.format(input), msg); >+ else >+ nf.format(input) // should not throw. >+ }) >+}); >diff --git a/JSTests/stress/intl-numberformat-rounding-mode-v3.js b/JSTests/stress/intl-numberformat-rounding-mode-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..7917a6b936370236afbc7efcb91917d599030c4b >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-rounding-mode-v3.js >@@ -0,0 +1,101 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+function shouldThrow(func, errorMessage) { >+ var errorThrown = false; >+ var error = null; >+ try { >+ func(); >+ } catch (e) { >+ errorThrown = true; >+ error = e; >+ } >+ if (!errorThrown) >+ throw new Error('not thrown'); >+ if (String(error) !== errorMessage) >+ throw new Error(`bad error: ${String(error)}`); >+} >+ >+let validRoundingMode = [ >+ "ceil", >+ "floor", >+ "expand", >+ "halfCeil", >+ "halfExpand", >+ "halfFloor", >+ "halfTrunc", >+ "halfEven", >+ "trunc", >+]; >+ >+let invalidRoundingMode = [ >+ "ceiling", >+ "down", >+ "Down", >+ "flooring", >+ "halfDown", >+ "halfUp", >+ "halfup", >+ "halfeven", >+ "halfdown", >+ "half-up", >+ "half-even", >+ "half-down", >+ "up", >+ "Up", >+]; >+ >+validRoundingMode.forEach(function(roundingMode) { >+ let nf = new Intl.NumberFormat(undefined, {roundingMode}); >+ shouldBe(roundingMode, nf.resolvedOptions().roundingMode); >+}); >+ >+invalidRoundingMode.forEach(function(roundingMode) { >+ shouldThrow(() => { >+ let nf = new Intl.NumberFormat(undefined, {roundingMode}); }, `RangeError: roundingMode must be either "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", or "halfEven"`); >+}); >+ >+// Check default is "halfExpand" >+shouldBe("halfExpand", (new Intl.NumberFormat().resolvedOptions().roundingMode)); >+shouldBe("halfExpand", (new Intl.NumberFormat( >+ undefined, {roundingMode: undefined}).resolvedOptions().roundingMode)); >+ >+// Check roundingMode is read once after reading signDisplay >+ >+let read = []; >+let options = { >+ get signDisplay() { read.push('signDisplay'); return undefined; }, >+ get roundingMode() { read.push('roundingMode'); return undefined; }, >+}; >+ >+new Intl.NumberFormat(undefined, options); >+shouldBe("signDisplay,roundingMode", read.join(",")); >diff --git a/JSTests/stress/intl-numberformat-sign-display-v3.js b/JSTests/stress/intl-numberformat-sign-display-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..5e1c8513ab8a567208f0818350ee9ddc3f686358 >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-sign-display-v3.js >@@ -0,0 +1,56 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+// Test default. >+let nf = new Intl.NumberFormat(); >+shouldBe("auto", nf.resolvedOptions().signDisplay); >+ >+nf = new Intl.NumberFormat("en"); >+shouldBe("auto", nf.resolvedOptions().signDisplay); >+ >+const testData = [ >+ ["auto", "-123", "-0", "0", "123"], >+ ["always", "-123", "-0", "+0", "+123"], >+ ["never", "123", "0", "0", "123"], >+ ["exceptZero", "-123", "0", "0", "+123"], >+ ["negative", "-123", "0", "0", "123"], >+]; >+ >+for (const [signDisplay, neg, negZero, zero, pos] of testData) { >+ nf = new Intl.NumberFormat("en", {signDisplay}); >+ shouldBe(signDisplay, nf.resolvedOptions().signDisplay); >+ shouldBe(neg, nf.format(-123)); >+ if ($vm.icuVersion() >= 69) >+ shouldBe(negZero, nf.format(-0)); >+ shouldBe(zero, nf.format(0)); >+ shouldBe(pos, nf.format(123)); >+} >diff --git a/JSTests/stress/intl-numberformat-trailing-zero-display-resolved-options-v3.js b/JSTests/stress/intl-numberformat-trailing-zero-display-resolved-options-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..741355136a39245e6973b16422abe20f03cb6a6b >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-trailing-zero-display-resolved-options-v3.js >@@ -0,0 +1,45 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+let defaultFmt = new Intl.NumberFormat("en", >+ { minimumFractionDigits: 2, maximumFractionDigits: 2 }); >+let autoFmt = new Intl.NumberFormat("en", >+ { minimumFractionDigits: 2, maximumFractionDigits: 2, >+ trailingZeroDisplay: 'auto'}); >+let stripIfIntegerFmt = new Intl.NumberFormat("en", >+ { minimumFractionDigits: 2, maximumFractionDigits: 2, >+ trailingZeroDisplay: 'stripIfInteger'}); >+ >+shouldBe("auto", defaultFmt.resolvedOptions().trailingZeroDisplay); >+shouldBe("auto", autoFmt.resolvedOptions().trailingZeroDisplay); >+shouldBe("stripIfInteger", >+ stripIfIntegerFmt.resolvedOptions().trailingZeroDisplay); >diff --git a/JSTests/stress/intl-numberformat-trailing-zero-display-v3.js b/JSTests/stress/intl-numberformat-trailing-zero-display-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..85f2b1d1ca2c877ee8acbe81d590258816c96b00 >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-trailing-zero-display-v3.js >@@ -0,0 +1,56 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual + " " + expected); >+} >+ >+let defaultFmt = new Intl.NumberFormat("en", >+ { minimumFractionDigits: 2, maximumFractionDigits: 2 }); >+let autoFmt = new Intl.NumberFormat("en", >+ { minimumFractionDigits: 2, maximumFractionDigits: 2, >+ trailingZeroDisplay: 'auto'}); >+let stripIfIntegerFmt = new Intl.NumberFormat("en", >+ { minimumFractionDigits: 2, maximumFractionDigits: 2, >+ trailingZeroDisplay: 'stripIfInteger'}); >+ >+shouldBe("3.14", defaultFmt.format(3.1411)); >+shouldBe("3.14", autoFmt.format(3.1411)); >+shouldBe("3.14", stripIfIntegerFmt.format(3.1411)); >+shouldBe("3.00", defaultFmt.format(3.001411)); >+shouldBe("3.00", autoFmt.format(3.001411)); >+if ($vm.icuVersion() >= 69) >+ shouldBe("3", stripIfIntegerFmt.format(3.001411)); >+else >+ shouldBe("3.00", stripIfIntegerFmt.format(3.001411)); >+shouldBe("3.00", defaultFmt.format(2.999411)); >+shouldBe("3.00", autoFmt.format(2.999411)); >+if ($vm.icuVersion() >= 69) >+ shouldBe("3", stripIfIntegerFmt.format(2.999411)); >+else >+ shouldBe("3.00", stripIfIntegerFmt.format(2.999411)); >diff --git a/JSTests/stress/intl-numberformat-usegrouping-v3.js b/JSTests/stress/intl-numberformat-usegrouping-v3.js >new file mode 100644 >index 0000000000000000000000000000000000000000..38c7fac8960eb962e3b6d9f3348b3abf0f6c08eb >--- /dev/null >+++ b/JSTests/stress/intl-numberformat-usegrouping-v3.js >@@ -0,0 +1,155 @@ >+// Copyright 2020 the V8 project authors. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual); >+} >+ >+function shouldThrow(func, errorMessage) { >+ var errorThrown = false; >+ var error = null; >+ try { >+ func(); >+ } catch (e) { >+ errorThrown = true; >+ error = e; >+ } >+ if (!errorThrown) >+ throw new Error('not thrown'); >+ if (String(error) !== errorMessage) >+ throw new Error(`bad error: ${String(error)}`); >+} >+ >+let validUseGrouping = [ >+ "min2", >+ "auto", >+ "always", >+ false, >+]; >+ >+let invalidUseGrouping = [ >+ "min-2", >+ "true", >+]; >+ >+validUseGrouping.forEach(function(useGrouping) { >+ let nf = new Intl.NumberFormat(undefined, {useGrouping}); >+ shouldBe(useGrouping, nf.resolvedOptions().useGrouping); >+}); >+ >+invalidUseGrouping.forEach(function(useGrouping) { >+ shouldThrow(() => { >+ let nf = new Intl.NumberFormat(undefined, {useGrouping}); }, `RangeError: useGrouping must be either true, false, "min2", "auto", or "always"`); >+}); >+ >+// useGrouping: undefined get "auto" >+shouldBe("auto", >+ (new Intl.NumberFormat()).resolvedOptions().useGrouping); >+shouldBe("auto", >+ (new Intl.NumberFormat(undefined, {useGrouping: undefined})) >+ .resolvedOptions().useGrouping); >+ >+// useGrouping: true get "always" >+shouldBe("always", >+ (new Intl.NumberFormat(undefined, {useGrouping: true})) >+ .resolvedOptions().useGrouping); >+ >+// useGrouping: false get false >+// useGrouping: "" get false >+shouldBe(false, >+ (new Intl.NumberFormat(undefined, {useGrouping: false})) >+ .resolvedOptions().useGrouping); >+shouldBe(false, >+ (new Intl.NumberFormat(undefined, {useGrouping: ""})) >+ .resolvedOptions().useGrouping); >+ >+// Some locales with default minimumGroupingDigits >+let mgd1 = ["en"]; >+// Some locales with default minimumGroupingDigits{"2"} >+let mgd2 = ["es", "pl", "lv", "et", "bg"]; >+let all = mgd1.concat(mgd2); >+ >+// Check "always" >+all.forEach(function(locale) { >+ let off = new Intl.NumberFormat(locale, {useGrouping: false}); >+ let msg = "locale: " + locale + " useGrouping: false"; >+ // In useGrouping: false, no grouping. >+ shouldBe(3, off.format(123).length, msg); >+ shouldBe(4, off.format(1234).length, msg); >+ shouldBe(5, off.format(12345).length, msg); >+ shouldBe(6, off.format(123456).length, msg); >+ shouldBe(7, off.format(1234567).length, msg); >+}); >+ >+// Check false >+all.forEach(function(locale) { >+ let always = new Intl.NumberFormat(locale, {useGrouping: "always"}); >+ let msg = "locale: " + locale + " useGrouping: 'always'"; >+ shouldBe(3, always.format(123).length); >+ // In useGrouping: "always", has grouping when more than 3 digits.. >+ shouldBe(4 + 1, always.format(1234).length, msg); >+ shouldBe(5 + 1, always.format(12345).length, msg); >+ shouldBe(6 + 1, always.format(123456).length, msg); >+ shouldBe(7 + 2, always.format(1234567).length, msg); >+}); >+ >+// Check "min2" >+all.forEach(function(locale) { >+ let always = new Intl.NumberFormat(locale, {useGrouping: "min2"}); >+ let msg = "locale: " + locale + " useGrouping: 'min2'"; >+ shouldBe(3, always.format(123).length); >+ // In useGrouping: "min2", no grouping for 4 digits but has grouping >+ // when more than 4 digits.. >+ shouldBe(4, always.format(1234).length, msg); >+ shouldBe(5 + 1, always.format(12345).length, msg); >+ shouldBe(6 + 1, always.format(123456).length, msg); >+ shouldBe(7 + 2, always.format(1234567).length, msg); >+}); >+ >+// Check "auto" >+mgd1.forEach(function(locale) { >+ let auto = new Intl.NumberFormat(locale, {useGrouping: "auto"}); >+ let msg = "locale: " + locale + " useGrouping: 'auto'"; >+ shouldBe(3, auto.format(123).length, msg); >+ shouldBe(4 + 1, auto.format(1234).length, msg); >+ shouldBe(5 + 1, auto.format(12345).length, msg); >+ shouldBe(6 + 1, auto.format(123456).length, msg); >+ shouldBe(7 + 2, auto.format(1234567).length, msg); >+}); >+mgd2.forEach(function(locale) { >+ let auto = new Intl.NumberFormat(locale, {useGrouping: "auto"}); >+ let msg = "locale: " + locale + " useGrouping: 'auto'"; >+ shouldBe(3, auto.format(123).length, msg); >+ // In useGrouping: "auto", since these locales has >+ // minimumGroupingDigits{"2"}, no grouping for 4 digits but has grouping >+ // when more than 4 digits.. >+ shouldBe(4, auto.format(1234).length, msg); >+ shouldBe(5 + 1, auto.format(12345).length, msg); >+ shouldBe(6 + 1, auto.format(123456).length, msg); >+ shouldBe(7 + 2, auto.format(1234567).length, msg); >+}); >diff --git a/JSTests/stress/intl-numberformat.js b/JSTests/stress/intl-numberformat.js >index 8539c93b5acad66f0aebcbfd558a5e5310264bc5..3c301f578a5db9317411621900fd4d71a5a655d5 100644 >--- a/JSTests/stress/intl-numberformat.js >+++ b/JSTests/stress/intl-numberformat.js >@@ -53,9 +53,13 @@ function testNumberFormat(numberFormat, possibleDifferences) { > maximumFractionDigits: 3, > minimumSignificantDigits: undefined, > maximumSignificantDigits: undefined, >- useGrouping: true, >+ useGrouping: "auto", > notation: "standard", >- signDisplay: "auto" >+ signDisplay: "auto", >+ roundingMode: "halfExpand", >+ roundingIncrement: 1, >+ trailingZeroDisplay: "auto", >+ roundingPriority: "auto", > }; > Object.assign(defaultOptions, difference); > return JSON.stringify(defaultOptions); >@@ -184,9 +188,9 @@ function testNumberFormat(numberFormat, possibleDifferences) { > shouldThrow(() => Intl.NumberFormat('en', {maximumSignificantDigits: 22}), RangeError); > > // The option useGrouping is processed correctly. >-shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: true}), [{locale: 'en', useGrouping: true}]), true); >+shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: true}), [{locale: 'en', useGrouping: "always"}]), true); > shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: false}), [{locale: 'en', useGrouping: false}]), true); >-shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: 'false'}), [{locale: 'en', useGrouping: true}]), true); >+shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: 'min2'}), [{locale: 'en', useGrouping: "min2"}]), true); > shouldThrow(() => Intl.NumberFormat('en', { get useGrouping() { throw new Error(); } }), Error); > > // 11.2 Properties of the Intl.NumberFormat Constructor >@@ -435,7 +439,7 @@ function testNumberFormat(numberFormat, possibleDifferences) { > { > let options = defaultNFormat.resolvedOptions(); > delete options.locale; >- shouldBe(JSON.stringify(options), '{"numberingSystem":"latn","style":"decimal","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":3,"useGrouping":true,"notation":"standard","signDisplay":"auto"}'); >+ shouldBe(JSON.stringify(options), `{"numberingSystem":"latn","style":"decimal","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":3,"useGrouping":"auto","notation":"standard","signDisplay":"auto","roundingMode":"halfExpand","roundingIncrement":1,"trailingZeroDisplay":"auto","roundingPriority":"auto"}`); > } > > // Legacy compatibility with ECMA-402 1.0 >diff --git a/JSTests/stress/intl-pluralrules-selectrange.js b/JSTests/stress/intl-pluralrules-selectrange.js >new file mode 100644 >index 0000000000000000000000000000000000000000..3cfde78eab125327dd0d3296bd26eb7ad1dfeccb >--- /dev/null >+++ b/JSTests/stress/intl-pluralrules-selectrange.js >@@ -0,0 +1,36 @@ >+// Copyright 2021 the V8 project authors. All rights reserved. >+// Redistribution and use in source and binary forms, with or without >+// modification, are permitted provided that the following conditions are >+// met: >+// >+// * Redistributions of source code must retain the above copyright >+// notice, this list of conditions and the following disclaimer. >+// * Redistributions in binary form must reproduce the above >+// copyright notice, this list of conditions and the following >+// disclaimer in the documentation and/or other materials provided >+// with the distribution. >+// * Neither the name of Google Inc. nor the names of its >+// contributors may be used to endorse or promote products derived >+// from this software without specific prior written permission. >+// >+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS >+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT >+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR >+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT >+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, >+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT >+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, >+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY >+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ >+function shouldBe(actual, expected) { >+ if (actual !== expected) >+ throw new Error('bad value: ' + actual); >+} >+ >+if (Intl.PluralRules.prototype.selectRange) { >+ const pl = new Intl.PluralRules("sl"); >+ shouldBe("few", pl.selectRange(102, 201)); >+} >diff --git a/JSTests/test262/config.yaml b/JSTests/test262/config.yaml >index 962d52462525afbb296d9acfd6d8a0a144f20b34..28e471215c0b2e5f73ee19a52fa8dc57cfe9689f 100644 >--- a/JSTests/test262/config.yaml >+++ b/JSTests/test262/config.yaml >@@ -101,6 +101,12 @@ skip: > # https://unicode-org.atlassian.net/browse/ICU-21367 > - test/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-yes-to-true.js > >+ # Intl.NumberFormat v3 changes the behavior. >+ - test/intl402/PluralRules/prototype/resolvedOptions/order.js >+ - test/intl402/NumberFormat/prototype/resolvedOptions/basic.js >+ - test/intl402/NumberFormat/prototype/resolvedOptions/order.js >+ - test/intl402/NumberFormat/test-option-useGrouping.js >+ > # Failing because we are building WebKit with very old ICU headers > - test/intl402/ListFormat/constructor/constructor/locales-invalid.js > - test/intl402/ListFormat/constructor/constructor/locales-valid.js
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Flags:
ross.kirsling
:
review+
ews-feeder
:
commit-queue-
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 215438
:
436468
|
442293
|
442294
|
442295
|
442296
|
442297
|
442298
|
442299
|
442300
|
442302
|
442304
|
442305
|
442464
|
442472
|
442476
|
442502
|
442511
|
442540
|
442549
| 442558