Changeset 285418 in webkit


Ignore:
Timestamp:
Nov 8, 2021 11:20:42 AM (8 months ago)
Author:
ysuzuki@apple.com
Message:

[JSC] Implement IntlNumberFormat v3
https://bugs.webkit.org/show_bug.cgi?id=215438

Reviewed by Ross Kirsling.

JSTests:

  • 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:

Source/JavaScriptCore:

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:
Location:
trunk
Files:
16 added
24 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r285406 r285418  
     12021-11-08  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] Implement IntlNumberFormat v3
     4        https://bugs.webkit.org/show_bug.cgi?id=215438
     5
     6        Reviewed by Ross Kirsling.
     7
     8        * stress/intl-numberformat-format-large.js: Added.
     9        (shouldBe):
     10        * stress/intl-numberformat-format-range-v3.js: Added.
     11        (shouldBe):
     12        (shouldThrow):
     13        (shouldNotThrow):
     14        (nf.formatRange.string_appeared_here.forEach):
     15        (nf.formatRangeToParts.validRanges.forEach):
     16        (nf6.formatRange.shouldThrow):
     17        * stress/intl-numberformat-format-string-v3.js: Added.
     18        (shouldBe):
     19        * stress/intl-numberformat-format-string.js: Added.
     20        (shouldBe):
     21        * stress/intl-numberformat-format-to-parts.js:
     22        * stress/intl-numberformat-rounding-increment-resolved-match-v3.js: Added.
     23        (shouldBe):
     24        (validRoundingIncrements.forEach):
     25        * stress/intl-numberformat-rounding-increment-v3.js: Added.
     26        (shouldThrow):
     27        (validRoundingIncrements.forEach):
     28        (invalidRoundingIncrements.forEach):
     29        * stress/intl-numberformat-rounding-increment-value-hanidec.js: Added.
     30        (shouldBe):
     31        * stress/intl-numberformat-rounding-increment-value-v3.js: Added.
     32        (shouldBe):
     33        * stress/intl-numberformat-rounding-increment-value.js: Added.
     34        (shouldBe):
     35        * stress/intl-numberformat-rounding-mode-table-v3.js: Added.
     36        (shouldBe):
     37        (Object.keys.expectations.forEach):
     38        * stress/intl-numberformat-rounding-mode-v3.js: Added.
     39        (shouldBe):
     40        (shouldThrow):
     41        (validRoundingMode.forEach):
     42        (invalidRoundingMode.forEach):
     43        (let.options.get signDisplay):
     44        (let.options.get roundingMode):
     45        * stress/intl-numberformat-sign-display-v3.js: Added.
     46        (shouldBe):
     47        * stress/intl-numberformat-trailing-zero-display-resolved-options-v3.js: Added.
     48        (shouldBe):
     49        * stress/intl-numberformat-trailing-zero-display-v3.js: Added.
     50        (shouldBe):
     51        * stress/intl-numberformat-usegrouping-v3.js: Added.
     52        (shouldBe):
     53        (shouldThrow):
     54        (validUseGrouping.forEach):
     55        (invalidUseGrouping.forEach):
     56        (all.forEach):
     57        (mgd1.forEach):
     58        (mgd2.forEach):
     59        * stress/intl-numberformat.js:
     60        (testNumberFormat):
     61        (shouldBe.testNumberFormat.Intl.NumberFormat):
     62        * stress/intl-pluralrules-selectrange.js: Added.
     63        (shouldBe):
     64        * test262/config.yaml:
     65
    1662021-11-08  Saam Barati  <sbarati@apple.com>
    267
  • trunk/JSTests/stress/intl-numberformat-format-to-parts.js

    r259658 r285418  
    5858shouldBe(Intl.NumberFormat('en-US', { useGrouping: false }).formatToParts(Number.MAX_VALUE)[0].value.length, 309);
    5959shouldBe(Intl.NumberFormat('en-US').formatToParts(Number.MAX_VALUE).length, 205);
     60
     61shouldBe(
     62    JSON.stringify(
     63        Intl.NumberFormat('en-US').formatToParts(4000n)
     64    ),
     65    JSON.stringify([
     66        {"type":"integer","value":"4"},
     67        {"type":"group","value":","},
     68        {"type":"integer","value":"000"}
     69    ]));
     70
     71shouldBe(
     72    JSON.stringify(
     73        Intl.NumberFormat('en-US').formatToParts(-4000n)
     74    ),
     75    JSON.stringify([
     76        {"type":"minusSign","value":"-"},
     77        {"type":"integer","value":"4"},
     78        {"type":"group","value":","},
     79        {"type":"integer","value":"000"}
     80    ]));
     81
     82shouldBe(
     83    JSON.stringify(
     84        Intl.NumberFormat('en-US').formatToParts("-4000")
     85    ),
     86    JSON.stringify([
     87        {"type":"minusSign","value":"-"},
     88        {"type":"integer","value":"4"},
     89        {"type":"group","value":","},
     90        {"type":"integer","value":"000"}
     91    ]));
  • trunk/JSTests/stress/intl-numberformat.js

    r267500 r285418  
    5454            minimumSignificantDigits: undefined,
    5555            maximumSignificantDigits: undefined,
    56             useGrouping: true,
     56            useGrouping: "auto",
    5757            notation: "standard",
    58             signDisplay: "auto"
     58            signDisplay: "auto",
     59            roundingMode: "halfExpand",
     60            roundingIncrement: 1,
     61            trailingZeroDisplay: "auto",
     62            roundingPriority: "auto",
    5963        };
    6064        Object.assign(defaultOptions, difference);
     
    185189
    186190// The option useGrouping is processed correctly.
    187 shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: true}), [{locale: 'en', useGrouping: true}]), true);
     191shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: true}), [{locale: 'en', useGrouping: "always"}]), true);
    188192shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: false}), [{locale: 'en', useGrouping: false}]), true);
    189 shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: 'false'}), [{locale: 'en', useGrouping: true}]), true);
     193shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: 'min2'}), [{locale: 'en', useGrouping: "min2"}]), true);
    190194shouldThrow(() => Intl.NumberFormat('en', { get useGrouping() { throw new Error(); } }), Error);
    191195
     
    436440    let options = defaultNFormat.resolvedOptions();
    437441    delete options.locale;
    438     shouldBe(JSON.stringify(options), '{"numberingSystem":"latn","style":"decimal","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":3,"useGrouping":true,"notation":"standard","signDisplay":"auto"}');
     442    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"}`);
    439443}
    440444
  • trunk/JSTests/test262/config.yaml

    r285178 r285418  
    103103    # https://unicode-org.atlassian.net/browse/ICU-21367
    104104    - test/intl402/Intl/getCanonicalLocales/unicode-ext-canonicalize-yes-to-true.js
     105
     106    # Intl.NumberFormat v3 changes the behavior.
     107    - test/intl402/PluralRules/prototype/resolvedOptions/order.js
     108    - test/intl402/NumberFormat/prototype/resolvedOptions/basic.js
     109    - test/intl402/NumberFormat/prototype/resolvedOptions/order.js
     110    - test/intl402/NumberFormat/test-option-useGrouping.js
    105111
    106112    # Failing because we are building WebKit with very old ICU headers
  • trunk/Source/JavaScriptCore/ChangeLog

    r285406 r285418  
     12021-11-08  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] Implement IntlNumberFormat v3
     4        https://bugs.webkit.org/show_bug.cgi?id=215438
     5
     6        Reviewed by Ross Kirsling.
     7
     8        This patch implements part of Intl.NumberFormat v3 proposal[1].
     9        It adds (1) several new options to Intl.NumberFormat, (2) adds
     10        formatRange and formatRangeToParts to Intl.NumberFormat and Intl.PluralRules,
     11        and (3) adds toIntlMathematicalValue support, which allows some of Intl.NumberFormat
     12        functions to take "string" decimal form.
     13
     14        We cannot implement some features because it requires super new ICU.
     15
     16            - trailingZeroDisplay (requires ICU 69)
     17            - halfCeil / halfFloor (requires ICU 69)
     18            - signDisplay: "negative" (requires ICU 69)
     19            - formatRangeToParts (requires ICU 70)
     20
     21        [1]: https://github.com/tc39/proposal-intl-numberformat-v3
     22
     23        * JavaScriptCore.xcodeproj/project.pbxproj:
     24        * Sources.txt:
     25        * runtime/BigIntPrototype.cpp:
     26        (JSC::JSC_DEFINE_HOST_FUNCTION):
     27        * runtime/CommonIdentifiers.h:
     28        * runtime/IntlNumberFormat.cpp:
     29        (JSC::UNumberFormatterDeleter::operator()):
     30        (JSC::UNumberRangeFormatterDeleter::operator()):
     31        (JSC::partTypeString):
     32        (JSC::IntlNumberFormat::initializeNumberFormat):
     33        (JSC::IntlNumberFormat::format const):
     34        (JSC::IntlNumberFormat::formatRange const):
     35        (JSC::IntlNumberFormat::signDisplayString):
     36        (JSC::IntlNumberFormat::roundingModeString):
     37        (JSC::IntlNumberFormat::trailingZeroDisplayString):
     38        (JSC::IntlNumberFormat::roundingPriorityString):
     39        (JSC::IntlNumberFormat::useGroupingValue):
     40        (JSC::IntlNumberFormat::resolvedOptions const):
     41        (JSC::IntlNumberFormat::formatToPartsInternal):
     42        (JSC::IntlNumberFormat::formatToParts const):
     43        * runtime/IntlNumberFormat.h:
     44        (JSC::IntlMathematicalValue::IntlMathematicalValue):
     45        (JSC::IntlMathematicalValue::ensureNonDouble):
     46        (JSC::IntlMathematicalValue::numberType const):
     47        (JSC::IntlMathematicalValue::sign const):
     48        (JSC::IntlMathematicalValue::tryGetDouble const):
     49        (JSC::IntlMathematicalValue::getString const):
     50        (JSC::IntlMathematicalValue::numberTypeFromDouble):
     51        * runtime/IntlNumberFormatInlines.h:
     52        (JSC::setNumberFormatDigitOptions):
     53        (JSC::appendNumberFormatDigitOptionsToSkeleton):
     54        (JSC::toIntlMathematicalValue):
     55        * runtime/IntlNumberFormatPrototype.cpp:
     56        (JSC::IntlNumberFormatPrototype::create):
     57        (JSC::IntlNumberFormatPrototype::finishCreation):
     58        (JSC::JSC_DEFINE_HOST_FUNCTION):
     59        * runtime/IntlNumberFormatPrototype.h:
     60        * runtime/IntlObjectInlines.h:
     61        (JSC::intlStringOrBooleanOption):
     62        * runtime/IntlPluralRules.cpp:
     63        (JSC::UPluralRulesDeleter::operator()):
     64        (JSC::IntlPluralRules::initializePluralRules):
     65        (JSC::IntlPluralRules::resolvedOptions const):
     66        (JSC::IntlPluralRules::select const):
     67        (JSC::IntlPluralRules::selectRange const):
     68        * runtime/IntlPluralRules.h:
     69        * runtime/IntlPluralRulesPrototype.cpp:
     70        (JSC::IntlPluralRulesPrototype::create):
     71        (JSC::IntlPluralRulesPrototype::finishCreation):
     72        (JSC::JSC_DEFINE_HOST_FUNCTION):
     73        * runtime/IntlPluralRulesPrototype.h:
     74        * runtime/IntlRelativeTimeFormat.cpp:
     75        (JSC::IntlRelativeTimeFormat::formatToParts const):
     76        * runtime/JSBigInt.h:
     77        (JSC::JSBigInt::tryExtractDouble):
     78        * runtime/MathCommon.h:
     79        (JSC::isNegativeZero):
     80        * runtime/TemporalObject.cpp:
     81        (JSC::roundNumberToIncrement):
     82        * runtime/TemporalObject.h:
     83
    1842021-11-08  Saam Barati  <sbarati@apple.com>
    285
  • trunk/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj

    r285178 r285418  
    18461846                E307178D24C7829A00DF0644 /* IntlLocaleConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = A3AFF92C245A3CFA00C9BA3B /* IntlLocaleConstructor.h */; };
    18471847                E307178E24C7829D00DF0644 /* IntlLocale.h in Headers */ = {isa = PBXBuildFile; fileRef = A3AFF92B245A3CF900C9BA3B /* IntlLocale.h */; };
     1848                E30873E7272559410053B601 /* IntlPluralRules.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7D5FB18F20744BF1005DDF64 /* IntlPluralRules.cpp */; };
    18481849                E30E8A5426DE2E4800DA4915 /* TemporalTimeZonePrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = E30E8A4E26DE2E4700DA4915 /* TemporalTimeZonePrototype.h */; };
    18491850                E30E8A5626DE2E4800DA4915 /* TemporalTimeZone.h in Headers */ = {isa = PBXBuildFile; fileRef = E30E8A5026DE2E4800DA4915 /* TemporalTimeZone.h */; };
     
    19171918                E374166E26912BC700C80789 /* ObjectConstructorInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E374166D26912BC700C80789 /* ObjectConstructorInlines.h */; };
    19181919                E3750CC82502E87E006A0AAB /* IntlDateTimeFormatInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E3750CC72502E87E006A0AAB /* IntlDateTimeFormatInlines.h */; };
     1920                E378DC8D2727629400427B0B /* IntlNumberFormat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A1D792F61B43864B004516F5 /* IntlNumberFormat.cpp */; };
    19191921                E3794E761B77EB97005543AE /* ModuleAnalyzer.h in Headers */ = {isa = PBXBuildFile; fileRef = E3794E741B77EB97005543AE /* ModuleAnalyzer.h */; settings = {ATTRIBUTES = (Private, ); }; };
    19201922                E383500A2390D93B0036316D /* WasmGlobal.h in Headers */ = {isa = PBXBuildFile; fileRef = E38350092390D9370036316D /* WasmGlobal.h */; };
     
    1185911861                                E399AEC32559457F00B78485 /* IntlDateTimeFormat.cpp in Sources */,
    1186011862                                E366441E254409B30001876F /* IntlListFormat.cpp in Sources */,
     11863                                E378DC8D2727629400427B0B /* IntlNumberFormat.cpp in Sources */,
     11864                                E30873E7272559410053B601 /* IntlPluralRules.cpp in Sources */,
    1186111865                                A3EE8543262514B000FC9B8D /* IntlWorkaround.cpp in Sources */,
    1186211866                                E38E8790254B978400F6F9E4 /* JSDateMath.cpp in Sources */,
  • trunk/Source/JavaScriptCore/Sources.txt

    r285178 r285418  
    836836runtime/IntlLocaleConstructor.cpp
    837837runtime/IntlLocalePrototype.cpp
    838 runtime/IntlNumberFormat.cpp
     838runtime/IntlNumberFormat.cpp @no-unify // Confine U_HIDE_DRAFT_API's effect in this file.
    839839runtime/IntlNumberFormatConstructor.cpp
    840840runtime/IntlNumberFormatPrototype.cpp
    841841runtime/IntlObject.cpp
    842 runtime/IntlPluralRules.cpp
     842runtime/IntlPluralRules.cpp @no-unify // Confine U_HIDE_DRAFT_API's effect to this file.
    843843runtime/IntlPluralRulesConstructor.cpp
    844844runtime/IntlPluralRulesPrototype.cpp
  • trunk/Source/JavaScriptCore/runtime/BigIntPrototype.cpp

    r267594 r285418  
    3030#include "BigIntObject.h"
    3131#include "IntegrityInlines.h"
    32 #include "IntlNumberFormat.h"
     32#include "IntlNumberFormatInlines.h"
    3333#include "JSBigInt.h"
    3434#include "JSCInlines.h"
     
    133133    auto scope = DECLARE_THROW_SCOPE(vm);
    134134
    135     JSBigInt* value = toThisBigIntValue(globalObject, callFrame->thisValue());
     135    JSBigInt* thisValue = toThisBigIntValue(globalObject, callFrame->thisValue());
    136136    RETURN_IF_EXCEPTION(scope, { });
    137137
    138138    auto* numberFormat = IntlNumberFormat::create(vm, globalObject->numberFormatStructure());
    139139    numberFormat->initializeNumberFormat(globalObject, callFrame->argument(0), callFrame->argument(1));
    140     RETURN_IF_EXCEPTION(scope, encodedJSValue());
    141     RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, value)));
     140    RETURN_IF_EXCEPTION(scope, { });
     141
     142    auto value = toIntlMathematicalValue(globalObject, thisValue);
     143    RETURN_IF_EXCEPTION(scope, { });
     144
     145    if (auto number = value.tryGetDouble())
     146        RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, number.value())));
     147
     148    RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, WTFMove(value))));
    142149}
    143150
  • trunk/Source/JavaScriptCore/runtime/CommonIdentifiers.h

    r284435 r285418  
    218218    macro(roundingIncrement) \
    219219    macro(roundingMode) \
     220    macro(roundingPriority) \
    220221    macro(script) \
    221222    macro(second) \
    222223    macro(seconds) \
    223224    macro(segment) \
     225    macro(selectRange) \
    224226    macro(sensitivity) \
    225227    macro(set) \
     
    250252    macro(toPrecision) \
    251253    macro(toString) \
     254    macro(trailingZeroDisplay) \
    252255    macro(type) \
    253256    macro(uid) \
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp

    r281513 r285418  
    3636#include "JSCInlines.h"
    3737#include "ObjectConstructor.h"
     38#include <wtf/Range.h>
    3839#include <wtf/unicode/icu/ICUHelpers.h>
     40
     41#ifdef U_HIDE_DRAFT_API
     42#undef U_HIDE_DRAFT_API
     43#endif
     44#if HAVE(ICU_U_NUMBER_FORMATTER)
     45#include <unicode/unumberformatter.h>
     46#endif
     47#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     48#include <unicode/unumberrangeformatter.h>
     49#endif
     50#define U_HIDE_DRAFT_API 1
    3951
    4052namespace JSC {
     
    4557static constexpr bool verbose = false;
    4658}
     59
     60#if HAVE(ICU_U_NUMBER_FORMATTER)
     61void UNumberFormatterDeleter::operator()(UNumberFormatter* formatter)
     62{
     63    if (formatter)
     64        unumf_close(formatter);
     65}
     66#endif
     67
     68#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     69void UNumberRangeFormatterDeleter::operator()(UNumberRangeFormatter* formatter)
     70{
     71    if (formatter)
     72        unumrf_close(formatter);
     73}
     74#endif
    4775
    4876struct IntlNumberFormatField {
     
    215243}
    216244
    217 // https://tc39.github.io/ecma402/#sec-initializenumberformat
    218 void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSValue locales, JSValue optionsValue)
    219 {
    220     VM& vm = globalObject->vm();
    221     auto scope = DECLARE_THROW_SCOPE(vm);
    222 
    223     auto requestedLocales = canonicalizeLocaleList(globalObject, locales);
    224     RETURN_IF_EXCEPTION(scope, void());
    225 
    226     JSObject* options = intlCoerceOptionsToObject(globalObject, optionsValue);
    227     RETURN_IF_EXCEPTION(scope, void());
    228 
    229     ResolveLocaleOptions localeOptions;
    230 
    231     LocaleMatcher localeMatcher = intlOption<LocaleMatcher>(globalObject, options, vm.propertyNames->localeMatcher, { { "lookup"_s, LocaleMatcher::Lookup }, { "best fit"_s, LocaleMatcher::BestFit } }, "localeMatcher must be either \"lookup\" or \"best fit\""_s, LocaleMatcher::BestFit);
    232     RETURN_IF_EXCEPTION(scope, void());
    233 
    234     String numberingSystem = intlStringOption(globalObject, options, vm.propertyNames->numberingSystem, { }, nullptr, nullptr);
    235     RETURN_IF_EXCEPTION(scope, void());
    236     if (!numberingSystem.isNull()) {
    237         if (!isUnicodeLocaleIdentifierType(numberingSystem)) {
    238             throwRangeError(globalObject, scope, "numberingSystem is not a well-formed numbering system value"_s);
    239             return;
    240         }
    241         localeOptions[static_cast<unsigned>(RelevantExtensionKey::Nu)] = numberingSystem;
    242     }
    243 
    244     const auto& availableLocales = intlNumberFormatAvailableLocales();
    245     auto resolved = resolveLocale(globalObject, availableLocales, requestedLocales, localeMatcher, localeOptions, { RelevantExtensionKey::Nu }, localeData);
    246 
    247     m_locale = resolved.locale;
    248     if (m_locale.isEmpty()) {
    249         throwTypeError(globalObject, scope, "failed to initialize NumberFormat due to invalid locale"_s);
    250         return;
    251     }
    252 
    253     m_numberingSystem = resolved.extensions[static_cast<unsigned>(RelevantExtensionKey::Nu)];
    254 
    255     m_style = intlOption<Style>(globalObject, options, vm.propertyNames->style, { { "decimal"_s, Style::Decimal }, { "percent"_s, Style::Percent }, { "currency"_s, Style::Currency }, { "unit"_s, Style::Unit } }, "style must be either \"decimal\", \"percent\", \"currency\", or \"unit\""_s, Style::Decimal);
    256     RETURN_IF_EXCEPTION(scope, void());
    257 
    258     String currency = intlStringOption(globalObject, options, Identifier::fromString(vm, "currency"), { }, nullptr, nullptr);
    259     RETURN_IF_EXCEPTION(scope, void());
    260     if (!currency.isNull()) {
    261         if (!isWellFormedCurrencyCode(currency)) {
    262             throwException(globalObject, scope, createRangeError(globalObject, "currency is not a well-formed currency code"_s));
    263             return;
    264         }
    265     }
    266 
    267     unsigned currencyDigits = 0;
    268     if (m_style == Style::Currency) {
    269         if (currency.isNull()) {
    270             throwTypeError(globalObject, scope, "currency must be a string"_s);
    271             return;
    272         }
    273 
    274         currency = currency.convertToASCIIUppercase();
    275         m_currency = currency;
    276         currencyDigits = computeCurrencyDigits(currency);
    277     }
    278 
    279     m_currencyDisplay = intlOption<CurrencyDisplay>(globalObject, options, Identifier::fromString(vm, "currencyDisplay"), { { "code"_s, CurrencyDisplay::Code }, { "symbol"_s, CurrencyDisplay::Symbol }, { "narrowSymbol"_s, CurrencyDisplay::NarrowSymbol }, { "name"_s, CurrencyDisplay::Name } }, "currencyDisplay must be either \"code\", \"symbol\", or \"name\""_s, CurrencyDisplay::Symbol);
    280     RETURN_IF_EXCEPTION(scope, void());
    281 
    282     m_currencySign = intlOption<CurrencySign>(globalObject, options, Identifier::fromString(vm, "currencySign"), { { "standard"_s, CurrencySign::Standard }, { "accounting"_s, CurrencySign::Accounting } }, "currencySign must be either \"standard\" or \"accounting\""_s, CurrencySign::Standard);
    283     RETURN_IF_EXCEPTION(scope, void());
    284 
    285     String unit = intlStringOption(globalObject, options, Identifier::fromString(vm, "unit"), { }, nullptr, nullptr);
    286     RETURN_IF_EXCEPTION(scope, void());
    287     std::optional<WellFormedUnit> wellFormedUnit;
    288     if (!unit.isNull()) {
    289         wellFormedUnit = wellFormedUnitIdentifier(unit);
    290         if (!wellFormedUnit) {
    291             throwRangeError(globalObject, scope, "unit is not a well-formed unit identifier"_s);
    292             return;
    293         }
    294         m_unit = unit;
    295     } else if (m_style == Style::Unit) {
    296         throwTypeError(globalObject, scope, "unit must be a string"_s);
    297         return;
    298     }
    299 
    300     m_unitDisplay = intlOption<UnitDisplay>(globalObject, options, Identifier::fromString(vm, "unitDisplay"), { { "short"_s, UnitDisplay::Short }, { "narrow"_s, UnitDisplay::Narrow }, { "long"_s, UnitDisplay::Long } }, "unitDisplay must be either \"short\", \"narrow\", or \"long\""_s, UnitDisplay::Short);
    301     RETURN_IF_EXCEPTION(scope, void());
    302 
    303     unsigned minimumFractionDigitsDefault = (m_style == Style::Currency) ? currencyDigits : 0;
    304     unsigned maximumFractionDigitsDefault = (m_style == Style::Currency) ? currencyDigits : (m_style == Style::Percent) ? 0 : 3;
    305 
    306     m_notation = intlOption<IntlNotation>(globalObject, options, Identifier::fromString(vm, "notation"), { { "standard"_s, IntlNotation::Standard }, { "scientific"_s, IntlNotation::Scientific }, { "engineering"_s, IntlNotation::Engineering }, { "compact"_s, IntlNotation::Compact } }, "notation must be either \"standard\", \"scientific\", \"engineering\", or \"compact\""_s, IntlNotation::Standard);
    307     RETURN_IF_EXCEPTION(scope, void());
    308 
    309     setNumberFormatDigitOptions(globalObject, this, options, minimumFractionDigitsDefault, maximumFractionDigitsDefault, m_notation);
    310     RETURN_IF_EXCEPTION(scope, void());
    311 
    312     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);
    313     RETURN_IF_EXCEPTION(scope, void());
    314 
    315     TriState useGrouping = intlBooleanOption(globalObject, options, Identifier::fromString(vm, "useGrouping"));
    316     RETURN_IF_EXCEPTION(scope, void());
    317     m_useGrouping = useGrouping != TriState::False;
    318 
    319     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);
    320     RETURN_IF_EXCEPTION(scope, void());
    321 
    322     CString dataLocaleWithExtensions = makeString(resolved.dataLocale, "-u-nu-", m_numberingSystem).utf8();
    323     dataLogLnIf(IntlNumberFormatInternal::verbose, "dataLocaleWithExtensions:(", dataLocaleWithExtensions , ")");
    324 
    325     // Options are obtained. Configure formatter here.
    326 
    327 #if HAVE(ICU_U_NUMBER_FORMATTER)
    328     // Constructing ICU Number Skeletons to configure UNumberFormatter.
    329     // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md
    330 
    331     StringBuilder skeletonBuilder;
    332     skeletonBuilder.append("rounding-mode-half-up");
    333 
    334     switch (m_style) {
    335     case Style::Decimal:
    336         // No skeleton is needed.
    337         break;
    338     case Style::Percent:
    339         skeletonBuilder.append(" percent scale/100");
    340         break;
    341     case Style::Currency: {
    342         skeletonBuilder.append(" currency/", currency);
    343 
    344         // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#unit-width
    345         switch (m_currencyDisplay) {
    346         case CurrencyDisplay::Code:
    347             skeletonBuilder.append(" unit-width-iso-code");
    348             break;
    349         case CurrencyDisplay::Symbol:
    350             // Default option. Do not specify unit-width.
    351             break;
    352         case CurrencyDisplay::NarrowSymbol:
    353             skeletonBuilder.append(" unit-width-narrow");
    354             break;
    355         case CurrencyDisplay::Name:
    356             skeletonBuilder.append(" unit-width-full-name");
    357             break;
    358         }
    359         break;
    360     }
    361     case Style::Unit: {
    362         // The measure-unit stem takes one required option: the unit identifier of the unit to be formatted.
    363         // The full unit identifier is required: both the type and the subtype (for example, length-meter).
    364         // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#unit
    365         skeletonBuilder.append(" measure-unit/");
    366         auto numeratorUnit = wellFormedUnit->numerator;
    367         skeletonBuilder.append(numeratorUnit.type, '-', numeratorUnit.subType);
    368         if (auto denominatorUnitValue = wellFormedUnit->denominator) {
    369             auto denominatorUnit = denominatorUnitValue.value();
    370             skeletonBuilder.append(" per-measure-unit/", denominatorUnit.type, '-', denominatorUnit.subType);
    371         }
    372 
    373         // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#unit-width
    374         switch (m_unitDisplay) {
    375         case UnitDisplay::Short:
    376             skeletonBuilder.append(" unit-width-short");
    377             break;
    378         case UnitDisplay::Narrow:
    379             skeletonBuilder.append(" unit-width-narrow");
    380             break;
    381         case UnitDisplay::Long:
    382             skeletonBuilder.append(" unit-width-full-name");
    383             break;
    384         }
    385         break;
    386     }
    387     }
    388 
    389     // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#integer-width
    390     skeletonBuilder.append(" integer-width/", WTF::ICU::majorVersion() >= 67 ? '*' : '+'); // Prior to ICU 67, use the symbol + instead of *.
    391     for (unsigned i = 0; i < m_minimumIntegerDigits; ++i)
    392         skeletonBuilder.append('0');
    393 
    394     switch (m_roundingType) {
    395     case IntlRoundingType::FractionDigits: {
    396         // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#fraction-precision
    397         skeletonBuilder.append(" .");
    398         for (unsigned i = 0; i < m_minimumFractionDigits; ++i)
    399             skeletonBuilder.append('0');
    400         for (unsigned i = 0; i < m_maximumFractionDigits - m_minimumFractionDigits; ++i)
    401             skeletonBuilder.append('#');
    402         break;
    403     }
    404     case IntlRoundingType::SignificantDigits: {
    405         // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#significant-digits-precision
    406         skeletonBuilder.append(' ');
    407         for (unsigned i = 0; i < m_minimumSignificantDigits; ++i)
    408             skeletonBuilder.append('@');
    409         for (unsigned i = 0; i < m_maximumSignificantDigits - m_minimumSignificantDigits; ++i)
    410             skeletonBuilder.append('#');
    411         break;
    412     }
    413     case IntlRoundingType::CompactRounding:
    414         // Do not set anything.
    415         break;
    416     }
    417 
    418     // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#notation
    419     switch (m_notation) {
    420     case IntlNotation::Standard:
    421         break;
    422     case IntlNotation::Scientific:
    423         skeletonBuilder.append(" scientific");
    424         break;
    425     case IntlNotation::Engineering:
    426         skeletonBuilder.append(" engineering");
    427         break;
    428     case IntlNotation::Compact:
    429         switch (m_compactDisplay) {
    430         case CompactDisplay::Short:
    431             skeletonBuilder.append(" compact-short");
    432             break;
    433         case CompactDisplay::Long:
    434             skeletonBuilder.append(" compact-long");
    435             break;
    436         }
    437         break;
    438     }
    439 
    440     // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#sign-display
    441     // CurrencySign's accounting is a part of SignDisplay in ICU.
    442     bool useAccounting = (m_style == Style::Currency && m_currencySign == CurrencySign::Accounting);
    443     switch (m_signDisplay) {
    444     case SignDisplay::Auto:
    445         if (useAccounting)
    446             skeletonBuilder.append(" sign-accounting");
    447         else
    448             skeletonBuilder.append(" sign-auto");
    449         break;
    450     case SignDisplay::Never:
    451         skeletonBuilder.append(" sign-never");
    452         break;
    453     case SignDisplay::Always:
    454         if (useAccounting)
    455             skeletonBuilder.append(" sign-accounting-always");
    456         else
    457             skeletonBuilder.append(" sign-always");
    458         break;
    459     case SignDisplay::ExceptZero:
    460         if (useAccounting)
    461             skeletonBuilder.append(" sign-accounting-except-zero");
    462         else
    463             skeletonBuilder.append(" sign-except-zero");
    464         break;
    465     }
    466 
    467     if (!m_useGrouping)
    468         skeletonBuilder.append(" group-off");
    469 
    470     String skeleton = skeletonBuilder.toString();
    471     dataLogLnIf(IntlNumberFormatInternal::verbose, skeleton);
    472     StringView skeletonView(skeleton);
    473     UErrorCode status = U_ZERO_ERROR;
    474     m_numberFormatter = std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter>(unumf_openForSkeletonAndLocale(skeletonView.upconvertedCharacters().get(), skeletonView.length(), dataLocaleWithExtensions.data(), &status));
    475     if (U_FAILURE(status)) {
    476         throwTypeError(globalObject, scope, "Failed to initialize NumberFormat"_s);
    477         return;
    478     }
    479 #else
    480     UNumberFormatStyle style = UNUM_DEFAULT;
    481     switch (m_style) {
    482     case Style::Decimal:
    483         style = UNUM_DECIMAL;
    484         break;
    485     case Style::Percent:
    486         style = UNUM_PERCENT;
    487         break;
    488     case Style::Currency:
    489         switch (m_currencyDisplay) {
    490         case CurrencyDisplay::Code:
    491             style = UNUM_CURRENCY_ISO;
    492             break;
    493         case CurrencyDisplay::Symbol:
    494             style = UNUM_CURRENCY;
    495             break;
    496         case CurrencyDisplay::NarrowSymbol:
    497             style = UNUM_CURRENCY; // Use the same option to "symbol" since linked-ICU does not support it.
    498             break;
    499         case CurrencyDisplay::Name:
    500             style = UNUM_CURRENCY_PLURAL;
    501             break;
    502         }
    503         switch (m_currencySign) {
    504         case CurrencySign::Standard:
    505             break;
    506         case CurrencySign::Accounting:
    507             // Ignore this case since linked ICU does not support it.
    508             break;
    509         }
    510         break;
    511     case Style::Unit:
    512         // Ignore this case since linked ICU does not support it.
    513         break;
    514     }
    515 
    516     switch (m_notation) {
    517     case IntlNotation::Standard:
    518         break;
    519     case IntlNotation::Scientific:
    520     case IntlNotation::Engineering:
    521     case IntlNotation::Compact:
    522         // Ignore this case since linked ICU does not support it.
    523         break;
    524     }
    525 
    526     switch (m_signDisplay) {
    527     case SignDisplay::Auto:
    528         break;
    529     case SignDisplay::Never:
    530     case SignDisplay::Always:
    531     case SignDisplay::ExceptZero:
    532         // Ignore this case since linked ICU does not support it.
    533         break;
    534     }
    535 
    536     UErrorCode status = U_ZERO_ERROR;
    537     m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(style, nullptr, 0, dataLocaleWithExtensions.data(), nullptr, &status));
    538     if (U_FAILURE(status)) {
    539         throwTypeError(globalObject, scope, "failed to initialize NumberFormat"_s);
    540         return;
    541     }
    542 
    543     if (m_style == Style::Currency) {
    544         unum_setTextAttribute(m_numberFormat.get(), UNUM_CURRENCY_CODE, StringView(m_currency).upconvertedCharacters(), m_currency.length(), &status);
    545         if (U_FAILURE(status)) {
    546             throwTypeError(globalObject, scope, "failed to initialize NumberFormat"_s);
    547             return;
    548         }
    549     }
    550 
    551     switch (m_roundingType) {
    552     case IntlRoundingType::FractionDigits:
    553         unum_setAttribute(m_numberFormat.get(), UNUM_MIN_INTEGER_DIGITS, m_minimumIntegerDigits);
    554         unum_setAttribute(m_numberFormat.get(), UNUM_MIN_FRACTION_DIGITS, m_minimumFractionDigits);
    555         unum_setAttribute(m_numberFormat.get(), UNUM_MAX_FRACTION_DIGITS, m_maximumFractionDigits);
    556         break;
    557     case IntlRoundingType::SignificantDigits:
    558         unum_setAttribute(m_numberFormat.get(), UNUM_SIGNIFICANT_DIGITS_USED, true);
    559         unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits);
    560         unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits);
    561         break;
    562     case IntlRoundingType::CompactRounding:
    563         // Ignore this case since linked ICU does not support it.
    564         break;
    565     }
    566     unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, m_useGrouping);
    567     unum_setAttribute(m_numberFormat.get(), UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);
    568 #endif
    569 }
    570 
    571 // https://tc39.es/ecma402/#sec-formatnumber
    572 JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, double value) const
    573 {
    574     VM& vm = globalObject->vm();
    575     auto scope = DECLARE_THROW_SCOPE(vm);
    576 
    577     Vector<UChar, 32> buffer;
    578 #if HAVE(ICU_U_NUMBER_FORMATTER)
    579     ASSERT(m_numberFormatter);
    580     UErrorCode status = U_ZERO_ERROR;
    581     auto formattedNumber = std::unique_ptr<UFormattedNumber, UFormattedNumberDeleter>(unumf_openResult(&status));
    582     if (U_FAILURE(status))
    583         return throwTypeError(globalObject, scope, "Failed to format a number."_s);
    584     unumf_formatDouble(m_numberFormatter.get(), value, formattedNumber.get(), &status);
    585     if (U_FAILURE(status))
    586         return throwTypeError(globalObject, scope, "Failed to format a number."_s);
    587     status = callBufferProducingFunction(unumf_resultToString, formattedNumber.get(), buffer);
    588     if (U_FAILURE(status))
    589         return throwTypeError(globalObject, scope, "Failed to format a number."_s);
    590 #else
    591     ASSERT(m_numberFormat);
    592     auto status = callBufferProducingFunction(unum_formatDouble, m_numberFormat.get(), value, buffer, nullptr);
    593     if (U_FAILURE(status))
    594         return throwTypeError(globalObject, scope, "Failed to format a number."_s);
    595 #endif
    596     return jsString(vm, String(buffer));
    597 }
    598 
    599 // https://tc39.es/ecma402/#sec-formatnumber
    600 JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, JSBigInt* value) const
    601 {
    602     VM& vm = globalObject->vm();
    603     auto scope = DECLARE_THROW_SCOPE(vm);
    604 
    605     auto string = value->toString(globalObject, 10);
    606     RETURN_IF_EXCEPTION(scope, { });
    607 
    608     ASSERT(string.is8Bit() && string.isAllASCII());
    609     auto* rawString = reinterpret_cast<const char*>(string.characters8());
    610 
    611     Vector<UChar, 32> buffer;
    612 #if HAVE(ICU_U_NUMBER_FORMATTER)
    613     ASSERT(m_numberFormatter);
    614     UErrorCode status = U_ZERO_ERROR;
    615     auto formattedNumber = std::unique_ptr<UFormattedNumber, UFormattedNumberDeleter>(unumf_openResult(&status));
    616     if (U_FAILURE(status))
    617         return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s);
    618     unumf_formatDecimal(m_numberFormatter.get(), rawString, string.length(), formattedNumber.get(), &status);
    619     if (U_FAILURE(status))
    620         return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s);
    621     status = callBufferProducingFunction(unumf_resultToString, formattedNumber.get(), buffer);
    622     if (U_FAILURE(status))
    623         return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s);
    624 #else
    625     ASSERT(m_numberFormat);
    626     auto status = callBufferProducingFunction(unum_formatDecimal, m_numberFormat.get(), rawString, string.length(), buffer, nullptr);
    627     if (U_FAILURE(status))
    628         return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s);
    629 #endif
    630     return jsString(vm, String(buffer));
    631 }
    632 
    633 ASCIILiteral IntlNumberFormat::styleString(Style style)
    634 {
    635     switch (style) {
    636     case Style::Decimal:
    637         return "decimal"_s;
    638     case Style::Percent:
    639         return "percent"_s;
    640     case Style::Currency:
    641         return "currency"_s;
    642     case Style::Unit:
    643         return "unit"_s;
    644     }
    645     ASSERT_NOT_REACHED();
    646     return ASCIILiteral::null();
    647 }
    648 
    649 ASCIILiteral IntlNumberFormat::currencyDisplayString(CurrencyDisplay currencyDisplay)
    650 {
    651     switch (currencyDisplay) {
    652     case CurrencyDisplay::Code:
    653         return "code"_s;
    654     case CurrencyDisplay::Symbol:
    655         return "symbol"_s;
    656     case CurrencyDisplay::NarrowSymbol:
    657         return "narrowSymbol"_s;
    658     case CurrencyDisplay::Name:
    659         return "name"_s;
    660     }
    661     ASSERT_NOT_REACHED();
    662     return ASCIILiteral::null();
    663 }
    664 
    665 ASCIILiteral IntlNumberFormat::notationString(IntlNotation notation)
    666 {
    667     switch (notation) {
    668     case IntlNotation::Standard:
    669         return "standard"_s;
    670     case IntlNotation::Scientific:
    671         return "scientific"_s;
    672     case IntlNotation::Engineering:
    673         return "engineering"_s;
    674     case IntlNotation::Compact:
    675         return "compact"_s;
    676     }
    677     ASSERT_NOT_REACHED();
    678     return ASCIILiteral::null();
    679 }
    680 
    681 ASCIILiteral IntlNumberFormat::currencySignString(CurrencySign currencySign)
    682 {
    683     switch (currencySign) {
    684     case CurrencySign::Standard:
    685         return "standard"_s;
    686     case CurrencySign::Accounting:
    687         return "accounting"_s;
    688     }
    689     ASSERT_NOT_REACHED();
    690     return ASCIILiteral::null();
    691 }
    692 
    693 ASCIILiteral IntlNumberFormat::unitDisplayString(UnitDisplay unitDisplay)
    694 {
    695     switch (unitDisplay) {
    696     case UnitDisplay::Short:
    697         return "short"_s;
    698     case UnitDisplay::Narrow:
    699         return "narrow"_s;
    700     case UnitDisplay::Long:
    701         return "long"_s;
    702     }
    703     ASSERT_NOT_REACHED();
    704     return ASCIILiteral::null();
    705 }
    706 
    707 ASCIILiteral IntlNumberFormat::compactDisplayString(CompactDisplay compactDisplay)
    708 {
    709     switch (compactDisplay) {
    710     case CompactDisplay::Short:
    711         return "short"_s;
    712     case CompactDisplay::Long:
    713         return "long"_s;
    714     }
    715     ASSERT_NOT_REACHED();
    716     return ASCIILiteral::null();
    717 }
    718 
    719 ASCIILiteral IntlNumberFormat::signDisplayString(SignDisplay signDisplay)
    720 {
    721     switch (signDisplay) {
    722     case SignDisplay::Auto:
    723         return "auto"_s;
    724     case SignDisplay::Never:
    725         return "never"_s;
    726     case SignDisplay::Always:
    727         return "always"_s;
    728     case SignDisplay::ExceptZero:
    729         return "exceptZero"_s;
    730     }
    731     ASSERT_NOT_REACHED();
    732     return ASCIILiteral::null();
    733 }
    734 
    735 // https://tc39.es/ecma402/#sec-intl.numberformat.prototype.resolvedoptions
    736 JSObject* IntlNumberFormat::resolvedOptions(JSGlobalObject* globalObject) const
    737 {
    738     VM& vm = globalObject->vm();
    739     JSObject* options = constructEmptyObject(globalObject);
    740     options->putDirect(vm, vm.propertyNames->locale, jsString(vm, m_locale));
    741     options->putDirect(vm, vm.propertyNames->numberingSystem, jsString(vm, m_numberingSystem));
    742     options->putDirect(vm, vm.propertyNames->style, jsNontrivialString(vm, styleString(m_style)));
    743     switch (m_style) {
    744     case Style::Decimal:
    745     case Style::Percent:
    746         break;
    747     case Style::Currency:
    748         options->putDirect(vm, Identifier::fromString(vm, "currency"), jsNontrivialString(vm, m_currency));
    749         options->putDirect(vm, Identifier::fromString(vm, "currencyDisplay"), jsNontrivialString(vm, currencyDisplayString(m_currencyDisplay)));
    750         options->putDirect(vm, Identifier::fromString(vm, "currencySign"), jsNontrivialString(vm, currencySignString(m_currencySign)));
    751         break;
    752     case Style::Unit:
    753         options->putDirect(vm, Identifier::fromString(vm, "unit"), jsNontrivialString(vm, m_unit));
    754         options->putDirect(vm, Identifier::fromString(vm, "unitDisplay"), jsNontrivialString(vm, unitDisplayString(m_unitDisplay)));
    755         break;
    756     }
    757     options->putDirect(vm, vm.propertyNames->minimumIntegerDigits, jsNumber(m_minimumIntegerDigits));
    758     switch (m_roundingType) {
    759     case IntlRoundingType::FractionDigits:
    760         options->putDirect(vm, vm.propertyNames->minimumFractionDigits, jsNumber(m_minimumFractionDigits));
    761         options->putDirect(vm, vm.propertyNames->maximumFractionDigits, jsNumber(m_maximumFractionDigits));
    762         break;
    763     case IntlRoundingType::SignificantDigits:
    764         options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits));
    765         options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits));
    766         break;
    767     case IntlRoundingType::CompactRounding:
    768         break;
    769     }
    770     options->putDirect(vm, Identifier::fromString(vm, "useGrouping"), jsBoolean(m_useGrouping));
    771     options->putDirect(vm, Identifier::fromString(vm, "notation"), jsNontrivialString(vm, notationString(m_notation)));
    772     if (m_notation == IntlNotation::Compact)
    773         options->putDirect(vm, Identifier::fromString(vm, "compactDisplay"), jsNontrivialString(vm, compactDisplayString(m_compactDisplay)));
    774     options->putDirect(vm, Identifier::fromString(vm, "signDisplay"), jsNontrivialString(vm, signDisplayString(m_signDisplay)));
    775     return options;
    776 }
    777 
    778 void IntlNumberFormat::setBoundFormat(VM& vm, JSBoundFunction* format)
    779 {
    780     m_boundFormat.set(vm, this, format);
    781 }
    782 
    783 static ASCIILiteral partTypeString(UNumberFormatFields field, IntlNumberFormat::Style style, double value)
     245static ASCIILiteral partTypeString(UNumberFormatFields field, IntlNumberFormat::Style style, bool sign, IntlMathematicalValue::NumberType type)
    784246{
    785247    switch (field) {
    786248    case UNUM_INTEGER_FIELD:
    787         if (std::isnan(value))
     249        switch (type) {
     250        case IntlMathematicalValue::NumberType::NaN:
    788251            return "nan"_s;
    789         if (!std::isfinite(value))
     252        case IntlMathematicalValue::NumberType::Infinity:
    790253            return "infinity"_s;
    791         return "integer"_s;
     254        case IntlMathematicalValue::NumberType::Integer:
     255            return "integer"_s;
     256        }
     257        ASSERT_NOT_REACHED();
     258        return "unknown"_s;
    792259    case UNUM_FRACTION_FIELD:
    793260        return "fraction"_s;
     
    809276        return (style == IntlNumberFormat::Style::Unit) ? "unit"_s : "percentSign"_s;
    810277    case UNUM_SIGN_FIELD:
    811         return std::signbit(value) ? "minusSign"_s : "plusSign"_s;
     278        return sign ? "minusSign"_s : "plusSign"_s;
    812279#if HAVE(ICU_U_NUMBER_FORMATTER)
    813280    case UNUM_MEASURE_UNIT_FIELD:
     
    826293}
    827294
    828 void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, Style style, double value, const String& formatted, IntlFieldIterator& iterator, JSArray* parts, JSString* unit)
     295// https://tc39.github.io/ecma402/#sec-initializenumberformat
     296void IntlNumberFormat::initializeNumberFormat(JSGlobalObject* globalObject, JSValue locales, JSValue optionsValue)
     297{
     298    VM& vm = globalObject->vm();
     299    auto scope = DECLARE_THROW_SCOPE(vm);
     300
     301    auto requestedLocales = canonicalizeLocaleList(globalObject, locales);
     302    RETURN_IF_EXCEPTION(scope, void());
     303
     304    JSObject* options = intlCoerceOptionsToObject(globalObject, optionsValue);
     305    RETURN_IF_EXCEPTION(scope, void());
     306
     307    ResolveLocaleOptions localeOptions;
     308
     309    LocaleMatcher localeMatcher = intlOption<LocaleMatcher>(globalObject, options, vm.propertyNames->localeMatcher, { { "lookup"_s, LocaleMatcher::Lookup }, { "best fit"_s, LocaleMatcher::BestFit } }, "localeMatcher must be either \"lookup\" or \"best fit\""_s, LocaleMatcher::BestFit);
     310    RETURN_IF_EXCEPTION(scope, void());
     311
     312    String numberingSystem = intlStringOption(globalObject, options, vm.propertyNames->numberingSystem, { }, nullptr, nullptr);
     313    RETURN_IF_EXCEPTION(scope, void());
     314    if (!numberingSystem.isNull()) {
     315        if (!isUnicodeLocaleIdentifierType(numberingSystem)) {
     316            throwRangeError(globalObject, scope, "numberingSystem is not a well-formed numbering system value"_s);
     317            return;
     318        }
     319        localeOptions[static_cast<unsigned>(RelevantExtensionKey::Nu)] = numberingSystem;
     320    }
     321
     322    const auto& availableLocales = intlNumberFormatAvailableLocales();
     323    auto resolved = resolveLocale(globalObject, availableLocales, requestedLocales, localeMatcher, localeOptions, { RelevantExtensionKey::Nu }, localeData);
     324
     325    m_locale = resolved.locale;
     326    if (m_locale.isEmpty()) {
     327        throwTypeError(globalObject, scope, "failed to initialize NumberFormat due to invalid locale"_s);
     328        return;
     329    }
     330
     331    m_numberingSystem = resolved.extensions[static_cast<unsigned>(RelevantExtensionKey::Nu)];
     332
     333    m_style = intlOption<Style>(globalObject, options, vm.propertyNames->style, { { "decimal"_s, Style::Decimal }, { "percent"_s, Style::Percent }, { "currency"_s, Style::Currency }, { "unit"_s, Style::Unit } }, "style must be either \"decimal\", \"percent\", \"currency\", or \"unit\""_s, Style::Decimal);
     334    RETURN_IF_EXCEPTION(scope, void());
     335
     336    String currency = intlStringOption(globalObject, options, Identifier::fromString(vm, "currency"), { }, nullptr, nullptr);
     337    RETURN_IF_EXCEPTION(scope, void());
     338    if (!currency.isNull()) {
     339        if (!isWellFormedCurrencyCode(currency)) {
     340            throwException(globalObject, scope, createRangeError(globalObject, "currency is not a well-formed currency code"_s));
     341            return;
     342        }
     343    }
     344
     345    unsigned currencyDigits = 0;
     346    if (m_style == Style::Currency) {
     347        if (currency.isNull()) {
     348            throwTypeError(globalObject, scope, "currency must be a string"_s);
     349            return;
     350        }
     351
     352        currency = currency.convertToASCIIUppercase();
     353        m_currency = currency;
     354        currencyDigits = computeCurrencyDigits(currency);
     355    }
     356
     357    m_currencyDisplay = intlOption<CurrencyDisplay>(globalObject, options, Identifier::fromString(vm, "currencyDisplay"), { { "code"_s, CurrencyDisplay::Code }, { "symbol"_s, CurrencyDisplay::Symbol }, { "narrowSymbol"_s, CurrencyDisplay::NarrowSymbol }, { "name"_s, CurrencyDisplay::Name } }, "currencyDisplay must be either \"code\", \"symbol\", or \"name\""_s, CurrencyDisplay::Symbol);
     358    RETURN_IF_EXCEPTION(scope, void());
     359
     360    m_currencySign = intlOption<CurrencySign>(globalObject, options, Identifier::fromString(vm, "currencySign"), { { "standard"_s, CurrencySign::Standard }, { "accounting"_s, CurrencySign::Accounting } }, "currencySign must be either \"standard\" or \"accounting\""_s, CurrencySign::Standard);
     361    RETURN_IF_EXCEPTION(scope, void());
     362
     363    String unit = intlStringOption(globalObject, options, Identifier::fromString(vm, "unit"), { }, nullptr, nullptr);
     364    RETURN_IF_EXCEPTION(scope, void());
     365    std::optional<WellFormedUnit> wellFormedUnit;
     366    if (!unit.isNull()) {
     367        wellFormedUnit = wellFormedUnitIdentifier(unit);
     368        if (!wellFormedUnit) {
     369            throwRangeError(globalObject, scope, "unit is not a well-formed unit identifier"_s);
     370            return;
     371        }
     372        m_unit = unit;
     373    } else if (m_style == Style::Unit) {
     374        throwTypeError(globalObject, scope, "unit must be a string"_s);
     375        return;
     376    }
     377
     378    m_unitDisplay = intlOption<UnitDisplay>(globalObject, options, Identifier::fromString(vm, "unitDisplay"), { { "short"_s, UnitDisplay::Short }, { "narrow"_s, UnitDisplay::Narrow }, { "long"_s, UnitDisplay::Long } }, "unitDisplay must be either \"short\", \"narrow\", or \"long\""_s, UnitDisplay::Short);
     379    RETURN_IF_EXCEPTION(scope, void());
     380
     381    unsigned minimumFractionDigitsDefault = (m_style == Style::Currency) ? currencyDigits : 0;
     382    unsigned maximumFractionDigitsDefault = (m_style == Style::Currency) ? currencyDigits : (m_style == Style::Percent) ? 0 : 3;
     383
     384    m_notation = intlOption<IntlNotation>(globalObject, options, Identifier::fromString(vm, "notation"), { { "standard"_s, IntlNotation::Standard }, { "scientific"_s, IntlNotation::Scientific }, { "engineering"_s, IntlNotation::Engineering }, { "compact"_s, IntlNotation::Compact } }, "notation must be either \"standard\", \"scientific\", \"engineering\", or \"compact\""_s, IntlNotation::Standard);
     385    RETURN_IF_EXCEPTION(scope, void());
     386
     387    setNumberFormatDigitOptions(globalObject, this, options, minimumFractionDigitsDefault, maximumFractionDigitsDefault, m_notation);
     388    RETURN_IF_EXCEPTION(scope, void());
     389
     390    m_roundingIncrement = intlNumberOption(globalObject, options, vm.propertyNames->roundingIncrement, 1, 5000, 1);
     391    RETURN_IF_EXCEPTION(scope, void());
     392    static constexpr const unsigned roundingIncrementCandidates[] = {
     393        1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000
     394    };
     395    if (std::none_of(roundingIncrementCandidates, roundingIncrementCandidates + std::size(roundingIncrementCandidates),
     396        [&](unsigned candidate) {
     397            return candidate == m_roundingIncrement;
     398        })) {
     399        throwRangeError(globalObject, scope, "roundingIncrement must be one of 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000"_s);
     400        return;
     401    }
     402    if (m_roundingIncrement != 1 && m_roundingType != IntlRoundingType::FractionDigits) {
     403        throwRangeError(globalObject, scope, "rounding type is not fraction-digits while roundingIncrement is specified"_s);
     404        return;
     405    }
     406
     407    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);
     408    RETURN_IF_EXCEPTION(scope, void());
     409
     410    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);
     411    RETURN_IF_EXCEPTION(scope, void());
     412
     413    UseGrouping defaultUseGrouping = UseGrouping::Auto;
     414    if (m_notation == IntlNotation::Compact)
     415        defaultUseGrouping = UseGrouping::Min2;
     416
     417    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);
     418
     419    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);
     420    RETURN_IF_EXCEPTION(scope, void());
     421
     422    m_roundingMode = intlOption<RoundingMode>(globalObject, options, vm.propertyNames->roundingMode, {
     423            { "ceil"_s, RoundingMode::Ceil },
     424            { "floor"_s, RoundingMode::Floor },
     425            { "expand"_s, RoundingMode::Expand },
     426            { "trunc"_s, RoundingMode::Trunc },
     427            { "halfCeil"_s, RoundingMode::HalfCeil },
     428            { "halfFloor"_s, RoundingMode::HalfFloor },
     429            { "halfExpand"_s, RoundingMode::HalfExpand },
     430            { "halfTrunc"_s, RoundingMode::HalfTrunc },
     431            { "halfEven"_s, RoundingMode::HalfEven }
     432        }, "roundingMode must be either \"ceil\", \"floor\", \"expand\", \"trunc\", \"halfCeil\", \"halfFloor\", \"halfExpand\", \"halfTrunc\", or \"halfEven\""_s, RoundingMode::HalfExpand);
     433    RETURN_IF_EXCEPTION(scope, void());
     434
     435    CString dataLocaleWithExtensions = makeString(resolved.dataLocale, "-u-nu-", m_numberingSystem).utf8();
     436    dataLogLnIf(IntlNumberFormatInternal::verbose, "dataLocaleWithExtensions:(", dataLocaleWithExtensions , ")");
     437
     438    // Options are obtained. Configure formatter here.
     439
     440#if HAVE(ICU_U_NUMBER_FORMATTER)
     441    // Constructing ICU Number Skeletons to configure UNumberFormatter.
     442    // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md
     443
     444    StringBuilder skeletonBuilder;
     445
     446    switch (m_roundingMode) {
     447    case RoundingMode::Ceil:
     448        skeletonBuilder.append("rounding-mode-ceiling");
     449        break;
     450    case RoundingMode::Floor:
     451        skeletonBuilder.append("rounding-mode-floor");
     452        break;
     453    case RoundingMode::Expand:
     454        skeletonBuilder.append("rounding-mode-up");
     455        break;
     456    case RoundingMode::Trunc:
     457        skeletonBuilder.append("rounding-mode-down");
     458        break;
     459    case RoundingMode::HalfCeil: {
     460        // Only ICU69~ supports half-ceiling. Ignore this option if linked ICU does not support it.
     461        // https://github.com/unicode-org/icu/commit/e8dfea9bb6bb27596731173b352759e44ad06b21
     462        if (WTF::ICU::majorVersion() >= 69)
     463            skeletonBuilder.append("rounding-mode-half-ceiling");
     464        else
     465            skeletonBuilder.append("rounding-mode-half-up"); // Default option.
     466        break;
     467    }
     468    case RoundingMode::HalfFloor: {
     469        // Only ICU69~ supports half-ceil. Ignore this option if linked ICU does not support it.
     470        // https://github.com/unicode-org/icu/commit/e8dfea9bb6bb27596731173b352759e44ad06b21
     471        if (WTF::ICU::majorVersion() >= 69)
     472            skeletonBuilder.append("rounding-mode-half-floor");
     473        else
     474            skeletonBuilder.append("rounding-mode-half-up"); // Default option.
     475        break;
     476    }
     477    case RoundingMode::HalfExpand:
     478        skeletonBuilder.append("rounding-mode-half-up");
     479        break;
     480    case RoundingMode::HalfTrunc:
     481        skeletonBuilder.append("rounding-mode-half-down");
     482        break;
     483    case RoundingMode::HalfEven:
     484        skeletonBuilder.append("rounding-mode-half-even");
     485        break;
     486    }
     487
     488    switch (m_style) {
     489    case Style::Decimal:
     490        // No skeleton is needed.
     491        break;
     492    case Style::Percent:
     493        skeletonBuilder.append(" percent scale/100");
     494        break;
     495    case Style::Currency: {
     496        skeletonBuilder.append(" currency/", currency);
     497
     498        // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#unit-width
     499        switch (m_currencyDisplay) {
     500        case CurrencyDisplay::Code:
     501            skeletonBuilder.append(" unit-width-iso-code");
     502            break;
     503        case CurrencyDisplay::Symbol:
     504            // Default option. Do not specify unit-width.
     505            break;
     506        case CurrencyDisplay::NarrowSymbol:
     507            skeletonBuilder.append(" unit-width-narrow");
     508            break;
     509        case CurrencyDisplay::Name:
     510            skeletonBuilder.append(" unit-width-full-name");
     511            break;
     512        }
     513        break;
     514    }
     515    case Style::Unit: {
     516        // The measure-unit stem takes one required option: the unit identifier of the unit to be formatted.
     517        // The full unit identifier is required: both the type and the subtype (for example, length-meter).
     518        // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#unit
     519        skeletonBuilder.append(" measure-unit/");
     520        auto numeratorUnit = wellFormedUnit->numerator;
     521        skeletonBuilder.append(numeratorUnit.type, '-', numeratorUnit.subType);
     522        if (auto denominatorUnitValue = wellFormedUnit->denominator) {
     523            auto denominatorUnit = denominatorUnitValue.value();
     524            skeletonBuilder.append(" per-measure-unit/", denominatorUnit.type, '-', denominatorUnit.subType);
     525        }
     526
     527        // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#unit-width
     528        switch (m_unitDisplay) {
     529        case UnitDisplay::Short:
     530            skeletonBuilder.append(" unit-width-short");
     531            break;
     532        case UnitDisplay::Narrow:
     533            skeletonBuilder.append(" unit-width-narrow");
     534            break;
     535        case UnitDisplay::Long:
     536            skeletonBuilder.append(" unit-width-full-name");
     537            break;
     538        }
     539        break;
     540    }
     541    }
     542
     543    appendNumberFormatDigitOptionsToSkeleton(this, skeletonBuilder);
     544
     545    // Configure this just after precision.
     546    // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#trailing-zero-display
     547    switch (m_trailingZeroDisplay) {
     548    case TrailingZeroDisplay::Auto:
     549        break;
     550    case TrailingZeroDisplay::StripIfInteger:
     551        // Only ICU69~ supports trailing zero display. Ignore this option if linked ICU does not support it.
     552        // https://github.com/unicode-org/icu/commit/b79c299f90d4023ac237db3d0335d568bf21cd36
     553        if (WTF::ICU::majorVersion() >= 69)
     554            skeletonBuilder.append("/w");
     555        break;
     556    }
     557
     558    // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#notation
     559    switch (m_notation) {
     560    case IntlNotation::Standard:
     561        break;
     562    case IntlNotation::Scientific:
     563        skeletonBuilder.append(" scientific");
     564        break;
     565    case IntlNotation::Engineering:
     566        skeletonBuilder.append(" engineering");
     567        break;
     568    case IntlNotation::Compact:
     569        switch (m_compactDisplay) {
     570        case CompactDisplay::Short:
     571            skeletonBuilder.append(" compact-short");
     572            break;
     573        case CompactDisplay::Long:
     574            skeletonBuilder.append(" compact-long");
     575            break;
     576        }
     577        break;
     578    }
     579
     580    // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#sign-display
     581    // CurrencySign's accounting is a part of SignDisplay in ICU.
     582    bool useAccounting = (m_style == Style::Currency && m_currencySign == CurrencySign::Accounting);
     583    switch (m_signDisplay) {
     584    case SignDisplay::Auto:
     585        if (useAccounting)
     586            skeletonBuilder.append(" sign-accounting");
     587        else
     588            skeletonBuilder.append(" sign-auto");
     589        break;
     590    case SignDisplay::Never:
     591        skeletonBuilder.append(" sign-never");
     592        break;
     593    case SignDisplay::Always:
     594        if (useAccounting)
     595            skeletonBuilder.append(" sign-accounting-always");
     596        else
     597            skeletonBuilder.append(" sign-always");
     598        break;
     599    case SignDisplay::ExceptZero:
     600        if (useAccounting)
     601            skeletonBuilder.append(" sign-accounting-except-zero");
     602        else
     603            skeletonBuilder.append(" sign-except-zero");
     604        break;
     605    case SignDisplay::Negative:
     606        // Only ICU69~ supports negative sign display. Ignore this option if linked ICU does not support it.
     607        // https://github.com/unicode-org/icu/commit/1aa0dad8e06ecc99bff442dd37f6daa2d39d9a5a
     608        if (WTF::ICU::majorVersion() >= 69) {
     609            if (useAccounting)
     610                skeletonBuilder.append(" sign-accounting-negative");
     611            else
     612                skeletonBuilder.append(" sign-negative");
     613        }
     614        break;
     615    }
     616
     617    // https://github.com/tc39/proposal-intl-numberformat-v3/issues/3
     618    // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#grouping
     619    switch (m_useGrouping) {
     620    case UseGrouping::False:
     621        skeletonBuilder.append(" group-off");
     622        break;
     623    case UseGrouping::Min2:
     624        skeletonBuilder.append(" group-min2");
     625        break;
     626    case UseGrouping::Auto:
     627        skeletonBuilder.append(" group-auto");
     628        break;
     629    case UseGrouping::Always:
     630        skeletonBuilder.append(" group-on-aligned");
     631        break;
     632    }
     633
     634    String skeleton = skeletonBuilder.toString();
     635    dataLogLnIf(IntlNumberFormatInternal::verbose, skeleton);
     636    StringView skeletonView(skeleton);
     637    auto upconverted = skeletonView.upconvertedCharacters();
     638
     639    UErrorCode status = U_ZERO_ERROR;
     640    m_numberFormatter = std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter>(unumf_openForSkeletonAndLocale(upconverted.get(), skeletonView.length(), dataLocaleWithExtensions.data(), &status));
     641    if (U_FAILURE(status)) {
     642        throwTypeError(globalObject, scope, "Failed to initialize NumberFormat"_s);
     643        return;
     644    }
     645
     646#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     647    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));
     648    if (U_FAILURE(status)) {
     649        throwTypeError(globalObject, scope, "failed to initialize NumberFormat"_s);
     650        return;
     651    }
     652#endif
     653#else
     654    UNumberFormatStyle style = UNUM_DEFAULT;
     655    switch (m_style) {
     656    case Style::Decimal:
     657        style = UNUM_DECIMAL;
     658        break;
     659    case Style::Percent:
     660        style = UNUM_PERCENT;
     661        break;
     662    case Style::Currency:
     663        switch (m_currencyDisplay) {
     664        case CurrencyDisplay::Code:
     665            style = UNUM_CURRENCY_ISO;
     666            break;
     667        case CurrencyDisplay::Symbol:
     668            style = UNUM_CURRENCY;
     669            break;
     670        case CurrencyDisplay::NarrowSymbol:
     671            style = UNUM_CURRENCY; // Use the same option to "symbol" since linked-ICU does not support it.
     672            break;
     673        case CurrencyDisplay::Name:
     674            style = UNUM_CURRENCY_PLURAL;
     675            break;
     676        }
     677        switch (m_currencySign) {
     678        case CurrencySign::Standard:
     679            break;
     680        case CurrencySign::Accounting:
     681            // Ignore this case since linked ICU does not support it.
     682            break;
     683        }
     684        break;
     685    case Style::Unit:
     686        // Ignore this case since linked ICU does not support it.
     687        break;
     688    }
     689
     690    switch (m_notation) {
     691    case IntlNotation::Standard:
     692        break;
     693    case IntlNotation::Scientific:
     694    case IntlNotation::Engineering:
     695    case IntlNotation::Compact:
     696        // Ignore this case since linked ICU does not support it.
     697        break;
     698    }
     699
     700    switch (m_signDisplay) {
     701    case SignDisplay::Auto:
     702        break;
     703    case SignDisplay::Never:
     704    case SignDisplay::Always:
     705    case SignDisplay::ExceptZero:
     706    case SignDisplay::Negative:
     707        // Ignore this case since linked ICU does not support it.
     708        break;
     709    }
     710
     711    UErrorCode status = U_ZERO_ERROR;
     712    m_numberFormat = std::unique_ptr<UNumberFormat, ICUDeleter<unum_close>>(unum_open(style, nullptr, 0, dataLocaleWithExtensions.data(), nullptr, &status));
     713    if (U_FAILURE(status)) {
     714        throwTypeError(globalObject, scope, "failed to initialize NumberFormat"_s);
     715        return;
     716    }
     717
     718    if (m_style == Style::Currency) {
     719        unum_setTextAttribute(m_numberFormat.get(), UNUM_CURRENCY_CODE, StringView(m_currency).upconvertedCharacters(), m_currency.length(), &status);
     720        if (U_FAILURE(status)) {
     721            throwTypeError(globalObject, scope, "failed to initialize NumberFormat"_s);
     722            return;
     723        }
     724    }
     725
     726    switch (m_roundingType) {
     727    case IntlRoundingType::FractionDigits:
     728        unum_setAttribute(m_numberFormat.get(), UNUM_MIN_INTEGER_DIGITS, m_minimumIntegerDigits);
     729        unum_setAttribute(m_numberFormat.get(), UNUM_MIN_FRACTION_DIGITS, m_minimumFractionDigits);
     730        unum_setAttribute(m_numberFormat.get(), UNUM_MAX_FRACTION_DIGITS, m_maximumFractionDigits);
     731        break;
     732    case IntlRoundingType::SignificantDigits:
     733        unum_setAttribute(m_numberFormat.get(), UNUM_SIGNIFICANT_DIGITS_USED, true);
     734        unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits);
     735        unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits);
     736        break;
     737    case IntlRoundingType::MorePrecision:
     738        // Ignore this case since linked ICU does not support it.
     739        break;
     740    case IntlRoundingType::LessPrecision:
     741        // Ignore this case since linked ICU does not support it.
     742        break;
     743    }
     744
     745    switch (m_useGrouping) {
     746    case UseGrouping::False:
     747        unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, false);
     748        break;
     749    case UseGrouping::Min2:
     750        // Ignore this case since linked ICU does not support it.
     751        break;
     752    case UseGrouping::Auto:
     753        break;
     754    case UseGrouping::Always:
     755        unum_setAttribute(m_numberFormat.get(), UNUM_GROUPING_USED, true);
     756        break;
     757    }
     758    unum_setAttribute(m_numberFormat.get(), UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);
     759#endif
     760}
     761
     762// https://tc39.es/ecma402/#sec-formatnumber
     763JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, double value) const
     764{
     765    VM& vm = globalObject->vm();
     766    auto scope = DECLARE_THROW_SCOPE(vm);
     767
     768    Vector<UChar, 32> buffer;
     769#if HAVE(ICU_U_NUMBER_FORMATTER)
     770    ASSERT(m_numberFormatter);
     771    UErrorCode status = U_ZERO_ERROR;
     772    auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status));
     773    if (U_FAILURE(status))
     774        return throwTypeError(globalObject, scope, "Failed to format a number."_s);
     775    unumf_formatDouble(m_numberFormatter.get(), value, formattedNumber.get(), &status);
     776    if (U_FAILURE(status))
     777        return throwTypeError(globalObject, scope, "Failed to format a number."_s);
     778    status = callBufferProducingFunction(unumf_resultToString, formattedNumber.get(), buffer);
     779    if (U_FAILURE(status))
     780        return throwTypeError(globalObject, scope, "Failed to format a number."_s);
     781#else
     782    ASSERT(m_numberFormat);
     783    auto status = callBufferProducingFunction(unum_formatDouble, m_numberFormat.get(), value, buffer, nullptr);
     784    if (U_FAILURE(status))
     785        return throwTypeError(globalObject, scope, "Failed to format a number."_s);
     786#endif
     787    return jsString(vm, String(WTFMove(buffer)));
     788}
     789
     790// https://tc39.es/ecma402/#sec-formatnumber
     791JSValue IntlNumberFormat::format(JSGlobalObject* globalObject, IntlMathematicalValue&& value) const
     792{
     793    VM& vm = globalObject->vm();
     794    auto scope = DECLARE_THROW_SCOPE(vm);
     795
     796    value.ensureNonDouble();
     797    const auto& string = value.getString();
     798
     799    Vector<UChar, 32> buffer;
     800#if HAVE(ICU_U_NUMBER_FORMATTER)
     801    ASSERT(m_numberFormatter);
     802    UErrorCode status = U_ZERO_ERROR;
     803    auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status));
     804    if (U_FAILURE(status))
     805        return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s);
     806    unumf_formatDecimal(m_numberFormatter.get(), string.data(), string.length(), formattedNumber.get(), &status);
     807    if (U_FAILURE(status))
     808        return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s);
     809    status = callBufferProducingFunction(unumf_resultToString, formattedNumber.get(), buffer);
     810    if (U_FAILURE(status))
     811        return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s);
     812#else
     813    ASSERT(m_numberFormat);
     814    auto status = callBufferProducingFunction(unum_formatDecimal, m_numberFormat.get(), string.data(), string.length(), buffer, nullptr);
     815    if (U_FAILURE(status))
     816        return throwTypeError(globalObject, scope, "Failed to format a BigInt."_s);
     817#endif
     818    return jsString(vm, String(WTFMove(buffer)));
     819}
     820
     821#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     822JSValue IntlNumberFormat::formatRange(JSGlobalObject* globalObject, double start, double end) const
     823{
     824    VM& vm = globalObject->vm();
     825    auto scope = DECLARE_THROW_SCOPE(vm);
     826
     827    ASSERT(m_numberRangeFormatter);
     828
     829    if (std::isnan(start) || std::isnan(end))
     830        return throwRangeError(globalObject, scope, "Passed numbers are out of range"_s);
     831
     832    if (end < start)
     833        return throwRangeError(globalObject, scope, "start is larger than end"_s);
     834
     835    if (isNegativeZero(end) && start >= 0)
     836        return throwRangeError(globalObject, scope, "start is larger than end"_s);
     837
     838    UErrorCode status = U_ZERO_ERROR;
     839    auto range = std::unique_ptr<UFormattedNumberRange, ICUDeleter<unumrf_closeResult>>(unumrf_openResult(&status));
     840    if (U_FAILURE(status))
     841        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     842
     843    unumrf_formatDoubleRange(m_numberRangeFormatter.get(), start, end, range.get(), &status);
     844    if (U_FAILURE(status))
     845        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     846
     847    auto* formattedValue = unumrf_resultAsValue(range.get(), &status);
     848    if (U_FAILURE(status))
     849        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     850
     851    int32_t length = 0;
     852    const UChar* string = ufmtval_getString(formattedValue, &length, &status);
     853    if (U_FAILURE(status))
     854        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     855
     856    return jsString(vm, String(string, length));
     857}
     858
     859JSValue IntlNumberFormat::formatRange(JSGlobalObject* globalObject, IntlMathematicalValue&& start, IntlMathematicalValue&& end) const
     860{
     861    VM& vm = globalObject->vm();
     862    auto scope = DECLARE_THROW_SCOPE(vm);
     863
     864    ASSERT(m_numberRangeFormatter);
     865
     866    if (start.numberType() == IntlMathematicalValue::NumberType::NaN || end.numberType() == IntlMathematicalValue::NumberType::NaN)
     867        return throwRangeError(globalObject, scope, "Passed numbers are out of range"_s);
     868
     869    start.ensureNonDouble();
     870    const auto& startString = start.getString();
     871
     872    end.ensureNonDouble();
     873    const auto& endString = end.getString();
     874
     875    UErrorCode status = U_ZERO_ERROR;
     876    auto range = std::unique_ptr<UFormattedNumberRange, ICUDeleter<unumrf_closeResult>>(unumrf_openResult(&status));
     877    if (U_FAILURE(status))
     878        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     879
     880    unumrf_formatDecimalRange(m_numberRangeFormatter.get(), startString.data(), startString.length(), endString.data(), endString.length(), range.get(), &status);
     881    if (U_FAILURE(status))
     882        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     883
     884    auto* formattedValue = unumrf_resultAsValue(range.get(), &status);
     885    if (U_FAILURE(status))
     886        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     887
     888    int32_t length = 0;
     889    const UChar* string = ufmtval_getString(formattedValue, &length, &status);
     890    if (U_FAILURE(status))
     891        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     892
     893    return jsString(vm, String(string, length));
     894}
     895#endif
     896
     897ASCIILiteral IntlNumberFormat::styleString(Style style)
     898{
     899    switch (style) {
     900    case Style::Decimal:
     901        return "decimal"_s;
     902    case Style::Percent:
     903        return "percent"_s;
     904    case Style::Currency:
     905        return "currency"_s;
     906    case Style::Unit:
     907        return "unit"_s;
     908    }
     909    ASSERT_NOT_REACHED();
     910    return ASCIILiteral::null();
     911}
     912
     913ASCIILiteral IntlNumberFormat::currencyDisplayString(CurrencyDisplay currencyDisplay)
     914{
     915    switch (currencyDisplay) {
     916    case CurrencyDisplay::Code:
     917        return "code"_s;
     918    case CurrencyDisplay::Symbol:
     919        return "symbol"_s;
     920    case CurrencyDisplay::NarrowSymbol:
     921        return "narrowSymbol"_s;
     922    case CurrencyDisplay::Name:
     923        return "name"_s;
     924    }
     925    ASSERT_NOT_REACHED();
     926    return ASCIILiteral::null();
     927}
     928
     929ASCIILiteral IntlNumberFormat::notationString(IntlNotation notation)
     930{
     931    switch (notation) {
     932    case IntlNotation::Standard:
     933        return "standard"_s;
     934    case IntlNotation::Scientific:
     935        return "scientific"_s;
     936    case IntlNotation::Engineering:
     937        return "engineering"_s;
     938    case IntlNotation::Compact:
     939        return "compact"_s;
     940    }
     941    ASSERT_NOT_REACHED();
     942    return ASCIILiteral::null();
     943}
     944
     945ASCIILiteral IntlNumberFormat::currencySignString(CurrencySign currencySign)
     946{
     947    switch (currencySign) {
     948    case CurrencySign::Standard:
     949        return "standard"_s;
     950    case CurrencySign::Accounting:
     951        return "accounting"_s;
     952    }
     953    ASSERT_NOT_REACHED();
     954    return ASCIILiteral::null();
     955}
     956
     957ASCIILiteral IntlNumberFormat::unitDisplayString(UnitDisplay unitDisplay)
     958{
     959    switch (unitDisplay) {
     960    case UnitDisplay::Short:
     961        return "short"_s;
     962    case UnitDisplay::Narrow:
     963        return "narrow"_s;
     964    case UnitDisplay::Long:
     965        return "long"_s;
     966    }
     967    ASSERT_NOT_REACHED();
     968    return ASCIILiteral::null();
     969}
     970
     971ASCIILiteral IntlNumberFormat::compactDisplayString(CompactDisplay compactDisplay)
     972{
     973    switch (compactDisplay) {
     974    case CompactDisplay::Short:
     975        return "short"_s;
     976    case CompactDisplay::Long:
     977        return "long"_s;
     978    }
     979    ASSERT_NOT_REACHED();
     980    return ASCIILiteral::null();
     981}
     982
     983ASCIILiteral IntlNumberFormat::signDisplayString(SignDisplay signDisplay)
     984{
     985    switch (signDisplay) {
     986    case SignDisplay::Auto:
     987        return "auto"_s;
     988    case SignDisplay::Never:
     989        return "never"_s;
     990    case SignDisplay::Always:
     991        return "always"_s;
     992    case SignDisplay::ExceptZero:
     993        return "exceptZero"_s;
     994    case SignDisplay::Negative:
     995        return "negative"_s;
     996    }
     997    ASSERT_NOT_REACHED();
     998    return ASCIILiteral::null();
     999}
     1000
     1001ASCIILiteral IntlNumberFormat::roundingModeString(RoundingMode roundingMode)
     1002{
     1003    switch (roundingMode) {
     1004    case RoundingMode::Ceil:
     1005        return "ceil"_s;
     1006    case RoundingMode::Floor:
     1007        return "floor"_s;
     1008    case RoundingMode::Expand:
     1009        return "expand"_s;
     1010    case RoundingMode::Trunc:
     1011        return "trunc"_s;
     1012    case RoundingMode::HalfCeil:
     1013        return "halfCeil"_s;
     1014    case RoundingMode::HalfFloor:
     1015        return "halfFloor"_s;
     1016    case RoundingMode::HalfExpand:
     1017        return "halfExpand"_s;
     1018    case RoundingMode::HalfTrunc:
     1019        return "halfTrunc"_s;
     1020    case RoundingMode::HalfEven:
     1021        return "halfEven"_s;
     1022    }
     1023    ASSERT_NOT_REACHED();
     1024    return ASCIILiteral::null();
     1025}
     1026
     1027ASCIILiteral IntlNumberFormat::trailingZeroDisplayString(TrailingZeroDisplay trailingZeroDisplay)
     1028{
     1029    switch (trailingZeroDisplay) {
     1030    case TrailingZeroDisplay::Auto:
     1031        return "auto"_s;
     1032    case TrailingZeroDisplay::StripIfInteger:
     1033        return "stripIfInteger"_s;
     1034    }
     1035    ASSERT_NOT_REACHED();
     1036    return ASCIILiteral::null();
     1037}
     1038
     1039ASCIILiteral IntlNumberFormat::roundingPriorityString(IntlRoundingType roundingType)
     1040{
     1041    switch (roundingType) {
     1042    case IntlRoundingType::FractionDigits:
     1043    case IntlRoundingType::SignificantDigits:
     1044        return "auto"_s;
     1045    case IntlRoundingType::MorePrecision:
     1046        return "morePrecision"_s;
     1047    case IntlRoundingType::LessPrecision:
     1048        return "lessPrecision"_s;
     1049    }
     1050    ASSERT_NOT_REACHED();
     1051    return ASCIILiteral::null();
     1052}
     1053
     1054JSValue IntlNumberFormat::useGroupingValue(VM& vm, UseGrouping useGrouping)
     1055{
     1056    switch (useGrouping) {
     1057    case UseGrouping::False:
     1058        return jsBoolean(false);
     1059    case UseGrouping::Min2:
     1060        return jsNontrivialString(vm, "min2"_s);
     1061    case UseGrouping::Auto:
     1062        return jsNontrivialString(vm, "auto"_s);
     1063    case UseGrouping::Always:
     1064        return jsNontrivialString(vm, "always"_s);
     1065    }
     1066    return jsUndefined();
     1067}
     1068
     1069// https://tc39.es/ecma402/#sec-intl.numberformat.prototype.resolvedoptions
     1070JSObject* IntlNumberFormat::resolvedOptions(JSGlobalObject* globalObject) const
     1071{
     1072    VM& vm = globalObject->vm();
     1073    JSObject* options = constructEmptyObject(globalObject);
     1074    options->putDirect(vm, vm.propertyNames->locale, jsString(vm, m_locale));
     1075    options->putDirect(vm, vm.propertyNames->numberingSystem, jsString(vm, m_numberingSystem));
     1076    options->putDirect(vm, vm.propertyNames->style, jsNontrivialString(vm, styleString(m_style)));
     1077    switch (m_style) {
     1078    case Style::Decimal:
     1079    case Style::Percent:
     1080        break;
     1081    case Style::Currency:
     1082        options->putDirect(vm, Identifier::fromString(vm, "currency"), jsNontrivialString(vm, m_currency));
     1083        options->putDirect(vm, Identifier::fromString(vm, "currencyDisplay"), jsNontrivialString(vm, currencyDisplayString(m_currencyDisplay)));
     1084        options->putDirect(vm, Identifier::fromString(vm, "currencySign"), jsNontrivialString(vm, currencySignString(m_currencySign)));
     1085        break;
     1086    case Style::Unit:
     1087        options->putDirect(vm, Identifier::fromString(vm, "unit"), jsNontrivialString(vm, m_unit));
     1088        options->putDirect(vm, Identifier::fromString(vm, "unitDisplay"), jsNontrivialString(vm, unitDisplayString(m_unitDisplay)));
     1089        break;
     1090    }
     1091    options->putDirect(vm, vm.propertyNames->minimumIntegerDigits, jsNumber(m_minimumIntegerDigits));
     1092    switch (m_roundingType) {
     1093    case IntlRoundingType::FractionDigits:
     1094        options->putDirect(vm, vm.propertyNames->minimumFractionDigits, jsNumber(m_minimumFractionDigits));
     1095        options->putDirect(vm, vm.propertyNames->maximumFractionDigits, jsNumber(m_maximumFractionDigits));
     1096        break;
     1097    case IntlRoundingType::SignificantDigits:
     1098        options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits));
     1099        options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits));
     1100        break;
     1101    case IntlRoundingType::MorePrecision:
     1102    case IntlRoundingType::LessPrecision:
     1103        options->putDirect(vm, vm.propertyNames->minimumFractionDigits, jsNumber(m_minimumFractionDigits));
     1104        options->putDirect(vm, vm.propertyNames->maximumFractionDigits, jsNumber(m_maximumFractionDigits));
     1105        options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits));
     1106        options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits));
     1107        break;
     1108    }
     1109    options->putDirect(vm, Identifier::fromString(vm, "useGrouping"), useGroupingValue(vm, m_useGrouping));
     1110    options->putDirect(vm, Identifier::fromString(vm, "notation"), jsNontrivialString(vm, notationString(m_notation)));
     1111    if (m_notation == IntlNotation::Compact)
     1112        options->putDirect(vm, Identifier::fromString(vm, "compactDisplay"), jsNontrivialString(vm, compactDisplayString(m_compactDisplay)));
     1113    options->putDirect(vm, Identifier::fromString(vm, "signDisplay"), jsNontrivialString(vm, signDisplayString(m_signDisplay)));
     1114    options->putDirect(vm, vm.propertyNames->roundingMode, jsNontrivialString(vm, roundingModeString(m_roundingMode)));
     1115    options->putDirect(vm, vm.propertyNames->roundingIncrement, jsNumber(m_roundingIncrement));
     1116    options->putDirect(vm, vm.propertyNames->trailingZeroDisplay, jsNontrivialString(vm, trailingZeroDisplayString(m_trailingZeroDisplay)));
     1117    options->putDirect(vm, vm.propertyNames->roundingPriority, jsNontrivialString(vm, roundingPriorityString(m_roundingType)));
     1118    return options;
     1119}
     1120
     1121void IntlNumberFormat::setBoundFormat(VM& vm, JSBoundFunction* format)
     1122{
     1123    m_boundFormat.set(vm, this, format);
     1124}
     1125
     1126void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, Style style, bool sign, IntlMathematicalValue::NumberType numberType, const String& formatted, IntlFieldIterator& iterator, JSArray* parts, JSString* unit)
    8291127{
    8301128    VM& vm = globalObject->vm();
     
    8691167        while (currentIndex < stringLength && fields[currentIndex].type == fieldType)
    8701168            ++currentIndex;
    871         auto partType = fieldType == literalFieldType ? literalString : jsString(vm, partTypeString(UNumberFormatFields(fieldType), style, value));
     1169        auto partType = fieldType == literalFieldType ? literalString : jsString(vm, partTypeString(UNumberFormatFields(fieldType), style, sign, numberType));
    8721170        auto partValue = jsSubstring(vm, formatted, startIndex, currentIndex - startIndex);
    8731171        JSObject* part = constructEmptyObject(globalObject);
     
    8951193#if HAVE(ICU_U_NUMBER_FORMATTER)
    8961194    ASSERT(m_numberFormatter);
    897     auto formattedNumber = std::unique_ptr<UFormattedNumber, UFormattedNumberDeleter>(unumf_openResult(&status));
     1195    auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status));
    8981196    if (U_FAILURE(status))
    8991197        return throwTypeError(globalObject, scope, "Failed to format a number."_s);
     
    9161214#endif
    9171215
    918     auto resultString = String(result);
     1216    auto resultString = String(WTFMove(result));
    9191217
    9201218    JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
     
    9221220        return throwOutOfMemoryError(globalObject, scope);
    9231221
    924     formatToPartsInternal(globalObject, m_style, value, resultString, iterator, parts);
     1222    formatToPartsInternal(globalObject, m_style, std::signbit(value), IntlMathematicalValue::numberTypeFromDouble(value), resultString, iterator, parts);
    9251223    RETURN_IF_EXCEPTION(scope, { });
    9261224
     
    9281226}
    9291227
     1228#if HAVE(ICU_U_NUMBER_FORMATTER)
     1229JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, IntlMathematicalValue&& value) const
     1230{
     1231    VM& vm = globalObject->vm();
     1232    auto scope = DECLARE_THROW_SCOPE(vm);
     1233
     1234    value.ensureNonDouble();
     1235    const auto& string = value.getString();
     1236
     1237    UErrorCode status = U_ZERO_ERROR;
     1238    auto fieldItr = std::unique_ptr<UFieldPositionIterator, UFieldPositionIteratorDeleter>(ufieldpositer_open(&status));
     1239    if (U_FAILURE(status))
     1240        return throwTypeError(globalObject, scope, "failed to open field position iterator"_s);
     1241
     1242    Vector<UChar, 32> result;
     1243    ASSERT(m_numberFormatter);
     1244    auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status));
     1245    if (U_FAILURE(status))
     1246        return throwTypeError(globalObject, scope, "Failed to format a number."_s);
     1247
     1248    unumf_formatDecimal(m_numberFormatter.get(), string.data(), string.length(), formattedNumber.get(), &status);
     1249    if (U_FAILURE(status))
     1250        return throwTypeError(globalObject, scope, "Failed to format a number."_s);
     1251
     1252    status = callBufferProducingFunction(unumf_resultToString, formattedNumber.get(), result);
     1253    if (U_FAILURE(status))
     1254        return throwTypeError(globalObject, scope, "Failed to format a number."_s);
     1255
     1256    unumf_resultGetAllFieldPositions(formattedNumber.get(), fieldItr.get(), &status);
     1257    if (U_FAILURE(status))
     1258        return throwTypeError(globalObject, scope, "Failed to format a number."_s);
     1259
     1260    IntlFieldIterator iterator(*fieldItr.get());
     1261
     1262    auto resultString = String(WTFMove(result));
     1263
     1264    JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
     1265    if (!parts)
     1266        return throwOutOfMemoryError(globalObject, scope);
     1267
     1268    formatToPartsInternal(globalObject, m_style, value.sign(), value.numberType(), resultString, iterator, parts);
     1269    RETURN_IF_EXCEPTION(scope, { });
     1270
     1271    return parts;
     1272}
     1273#endif
     1274
    9301275} // namespace JSC
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormat.h

    r278035 r285418  
    2929
    3030#include "JSObject.h"
     31#include "MathCommon.h"
     32#include "TemporalObject.h"
    3133#include <unicode/unum.h>
    3234#include <wtf/unicode/icu/ICUHelpers.h>
     
    3941#endif
    4042
    41 #if HAVE(ICU_U_NUMBER_FORMATTER)
    42 #include <unicode/unumberformatter.h>
    43 #endif
     43#if !defined(HAVE_ICU_U_NUMBER_RANGE_FORMATTER)
     44#if U_ICU_VERSION_MAJOR_NUM >= 68
     45#define HAVE_ICU_U_NUMBER_RANGE_FORMATTER 1
     46#endif
     47#endif
     48
     49struct UNumberFormatter;
     50struct UNumberRangeFormatter;
    4451
    4552namespace JSC {
     
    4956enum class RelevantExtensionKey : uint8_t;
    5057
    51 enum class IntlRoundingType : uint8_t { FractionDigits, SignificantDigits, CompactRounding };
     58enum class IntlRoundingType : uint8_t { FractionDigits, SignificantDigits, MorePrecision, LessPrecision };
     59enum class IntlRoundingPriority : uint8_t { Auto, MorePrecision, LessPrecision };
    5260enum class IntlNotation : uint8_t { Standard, Scientific, Engineering, Compact };
    5361template<typename IntlType> void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation);
     62template<typename IntlType> void appendNumberFormatDigitOptionsToSkeleton(IntlType*, StringBuilder&);
     63
     64#if HAVE(ICU_U_NUMBER_FORMATTER)
     65struct UNumberFormatterDeleter {
     66    JS_EXPORT_PRIVATE void operator()(UNumberFormatter*);
     67};
     68#endif
     69
     70#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     71struct UNumberRangeFormatterDeleter {
     72    JS_EXPORT_PRIVATE void operator()(UNumberRangeFormatter*);
     73};
     74#endif
     75
     76class IntlMathematicalValue {
     77    WTF_MAKE_FAST_ALLOCATED(IntlMathematicalValue);
     78public:
     79    enum class NumberType { Integer, Infinity, NaN, };
     80    using Value = std::variant<double, CString>;
     81
     82    IntlMathematicalValue() = default;
     83
     84    explicit IntlMathematicalValue(double value)
     85        : m_numberType(numberTypeFromDouble(value))
     86        , m_sign(std::signbit(value))
     87        , m_value(value)
     88    { }
     89
     90    explicit IntlMathematicalValue(NumberType numberType, bool sign, CString value)
     91        : m_numberType(numberType)
     92        , m_sign(sign)
     93        , m_value(value)
     94    {
     95    }
     96
     97    void ensureNonDouble()
     98    {
     99        if (std::holds_alternative<double>(m_value)) {
     100            switch (m_numberType) {
     101            case NumberType::Integer: {
     102                double value = std::get<double>(m_value);
     103                if (isNegativeZero(value))
     104                    m_value = CString("-0");
     105                else
     106                    m_value = String::number(value).ascii();
     107                break;
     108            }
     109            case NumberType::NaN:
     110                m_value = CString("nan");
     111                break;
     112            case NumberType::Infinity:
     113                m_value = CString(m_sign ? "-infinity" : "infinity");
     114                break;
     115            }
     116        }
     117    }
     118
     119    NumberType numberType() const { return m_numberType; }
     120    bool sign() const { return m_sign; }
     121    std::optional<double> tryGetDouble() const
     122    {
     123        if (std::holds_alternative<double>(m_value))
     124            return std::get<double>(m_value);
     125        return std::nullopt;
     126    }
     127    const CString& getString() const
     128    {
     129        ASSERT(std::holds_alternative<CString>(m_value));
     130        return std::get<CString>(m_value);
     131    }
     132
     133    static NumberType numberTypeFromDouble(double value)
     134    {
     135        if (std::isnan(value))
     136            return NumberType::NaN;
     137        if (!std::isfinite(value))
     138            return NumberType::Infinity;
     139        return NumberType::Integer;
     140    }
     141
     142private:
     143    NumberType m_numberType { NumberType::Integer };
     144    bool m_sign { false };
     145    Value m_value { 0.0 };
     146};
    54147
    55148class IntlNumberFormat final : public JSNonFinalObject {
     
    77170    void initializeNumberFormat(JSGlobalObject*, JSValue locales, JSValue optionsValue);
    78171    JSValue format(JSGlobalObject*, double) const;
    79     JSValue format(JSGlobalObject*, JSBigInt*) const;
     172    JSValue format(JSGlobalObject*, IntlMathematicalValue&&) const;
    80173    JSValue formatToParts(JSGlobalObject*, double) const;
     174#if HAVE(ICU_U_NUMBER_FORMATTER)
     175    JSValue formatToParts(JSGlobalObject*, IntlMathematicalValue&&) const;
     176#endif
    81177    JSObject* resolvedOptions(JSGlobalObject*) const;
     178
     179#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     180    JSValue formatRange(JSGlobalObject*, double, double) const;
     181    JSValue formatRange(JSGlobalObject*, IntlMathematicalValue&&, IntlMathematicalValue&&) const;
     182#endif
    82183
    83184    JSBoundFunction* boundFormat() const { return m_boundFormat.get(); }
     
    86187    enum class Style : uint8_t { Decimal, Percent, Currency, Unit };
    87188
    88     static void formatToPartsInternal(JSGlobalObject*, Style, double, const String& formatted, IntlFieldIterator&, JSArray*, JSString* unit = nullptr);
     189    static void formatToPartsInternal(JSGlobalObject*, Style, bool sign, IntlMathematicalValue::NumberType, const String& formatted, IntlFieldIterator&, JSArray*, JSString* unit = nullptr);
    89190
    90191    template<typename IntlType>
    91192    friend void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation);
     193    template<typename IntlType>
     194    friend void appendNumberFormatDigitOptionsToSkeleton(IntlType*, StringBuilder&);
    92195
    93196    static ASCIILiteral notationString(IntlNotation);
    94197
    95198    static IntlNumberFormat* unwrapForOldFunctions(JSGlobalObject*, JSValue);
     199
     200    static ASCIILiteral roundingPriorityString(IntlRoundingType);
    96201
    97202private:
     
    106211    enum class UnitDisplay : uint8_t { Short, Narrow, Long };
    107212    enum class CompactDisplay : uint8_t { Short, Long };
    108     enum class SignDisplay : uint8_t { Auto, Never, Always, ExceptZero };
     213    enum class SignDisplay : uint8_t { Auto, Never, Always, ExceptZero, Negative };
     214    enum class TrailingZeroDisplay : uint8_t { Auto, StripIfInteger };
     215    enum class UseGrouping : uint8_t { False, Min2, Auto, Always };
    109216
    110217    static ASCIILiteral styleString(Style);
     
    114221    static ASCIILiteral compactDisplayString(CompactDisplay);
    115222    static ASCIILiteral signDisplayString(SignDisplay);
     223    static ASCIILiteral roundingModeString(RoundingMode);
     224    static ASCIILiteral trailingZeroDisplayString(TrailingZeroDisplay);
     225    static JSValue useGroupingValue(VM&, UseGrouping);
    116226
    117227    WriteBarrier<JSBoundFunction> m_boundFormat;
    118228#if HAVE(ICU_U_NUMBER_FORMATTER)
    119     using UNumberFormatterDeleter = ICUDeleter<unumf_close>;
    120     using UFormattedNumberDeleter = ICUDeleter<unumf_closeResult>;
    121229    std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter> m_numberFormatter;
     230#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     231    std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter> m_numberRangeFormatter;
     232#endif
    122233#else
    123     using UNumberFormatDeleter = ICUDeleter<unum_close>;
    124234    std::unique_ptr<UNumberFormat, ICUDeleter<unum_close>> m_numberFormat;
    125235#endif
     
    134244    unsigned m_minimumSignificantDigits { 0 };
    135245    unsigned m_maximumSignificantDigits { 0 };
     246    unsigned m_roundingIncrement { 1 };
    136247    Style m_style { Style::Decimal };
    137248    CurrencyDisplay m_currencyDisplay;
     
    141252    IntlNotation m_notation { IntlNotation::Standard };
    142253    SignDisplay m_signDisplay;
    143     bool m_useGrouping { true };
     254    TrailingZeroDisplay m_trailingZeroDisplay { TrailingZeroDisplay::Auto };
     255    UseGrouping m_useGrouping { UseGrouping::Always };
     256    RoundingMode m_roundingMode { RoundingMode::HalfExpand };
    144257    IntlRoundingType m_roundingType { IntlRoundingType::FractionDigits };
    145258};
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h

    r278035 r285418  
    3030#include "IntlObjectInlines.h"
    3131#include "JSGlobalObject.h"
     32#include "JSGlobalObjectFunctions.h"
    3233
    3334namespace JSC  {
     
    6263    intlInstance->m_minimumIntegerDigits = minimumIntegerDigits;
    6364
    64     if (!minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined()) {
    65         intlInstance->m_roundingType = IntlRoundingType::SignificantDigits;
    66         unsigned minimumSignificantDigits = intlDefaultNumberOption(globalObject, minimumSignificantDigitsValue, vm.propertyNames->minimumSignificantDigits, 1, 21, 1);
    67         RETURN_IF_EXCEPTION(scope, void());
    68         unsigned maximumSignificantDigits = intlDefaultNumberOption(globalObject, maximumSignificantDigitsValue, vm.propertyNames->maximumSignificantDigits, minimumSignificantDigits, 21, 21);
    69         RETURN_IF_EXCEPTION(scope, void());
    70         intlInstance->m_minimumSignificantDigits = minimumSignificantDigits;
    71         intlInstance->m_maximumSignificantDigits = maximumSignificantDigits;
    72         return;
    73     }
    74 
    75     if (!minimumFractionDigitsValue.isUndefined() || !maximumFractionDigitsValue.isUndefined()) {
    76         constexpr unsigned undefinedValue = UINT32_MAX;
    77         intlInstance->m_roundingType = IntlRoundingType::FractionDigits;
    78         unsigned specifiedMinimumFractionDigits = intlDefaultNumberOption(globalObject, minimumFractionDigitsValue, vm.propertyNames->minimumFractionDigits, 0, 20, undefinedValue);
    79         RETURN_IF_EXCEPTION(scope, void());
    80         unsigned specifiedMaximumFractionDigits = intlDefaultNumberOption(globalObject, maximumFractionDigitsValue, vm.propertyNames->maximumFractionDigits, 0, 20, undefinedValue);
    81         RETURN_IF_EXCEPTION(scope, void());
    82         if (specifiedMaximumFractionDigits != undefinedValue)
    83             minimumFractionDigitsDefault = std::min(minimumFractionDigitsDefault, specifiedMaximumFractionDigits);
    84         unsigned minimumFractionDigits = intlDefaultNumberOption(globalObject, (specifiedMinimumFractionDigits == undefinedValue) ? jsUndefined() : jsNumber(specifiedMinimumFractionDigits), vm.propertyNames->minimumFractionDigits, 0, 20, minimumFractionDigitsDefault);
    85         RETURN_IF_EXCEPTION(scope, void());
    86         unsigned maximumFractionDigits = intlDefaultNumberOption(globalObject, (specifiedMaximumFractionDigits == undefinedValue) ? jsUndefined() : jsNumber(specifiedMaximumFractionDigits), vm.propertyNames->maximumFractionDigits, 0, 20, std::max(maximumFractionDigitsDefault, minimumFractionDigits));
    87         RETURN_IF_EXCEPTION(scope, void());
    88         if (minimumFractionDigits > maximumFractionDigits) {
    89             throwRangeError(globalObject, scope, "Computed minimumFractionDigits is larger than maximumFractionDigits"_s);
    90             return;
    91         }
    92         intlInstance->m_minimumFractionDigits = minimumFractionDigits;
    93         intlInstance->m_maximumFractionDigits = maximumFractionDigits;
    94         return;
    95     }
    96 
    97     if (notation == IntlNotation::Compact) {
    98         intlInstance->m_roundingType = IntlRoundingType::CompactRounding;
    99         return;
    100     }
    101 
    102     intlInstance->m_roundingType = IntlRoundingType::FractionDigits;
    103     intlInstance->m_minimumFractionDigits = minimumFractionDigitsDefault;
    104     intlInstance->m_maximumFractionDigits = maximumFractionDigitsDefault;
     65    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);
     66    RETURN_IF_EXCEPTION(scope, void());
     67
     68    bool hasSd = !minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined();
     69    bool hasFd = !minimumFractionDigitsValue.isUndefined() || !maximumFractionDigitsValue.isUndefined();
     70    bool needSd = hasSd || roundingPriority != IntlRoundingPriority::Auto;
     71    bool needFd = (!hasSd && notation != IntlNotation::Compact) || roundingPriority != IntlRoundingPriority::Auto;
     72
     73    if (needSd) {
     74        if (hasSd) {
     75            unsigned minimumSignificantDigits = intlDefaultNumberOption(globalObject, minimumSignificantDigitsValue, vm.propertyNames->minimumSignificantDigits, 1, 21, 1);
     76            RETURN_IF_EXCEPTION(scope, void());
     77            unsigned maximumSignificantDigits = intlDefaultNumberOption(globalObject, maximumSignificantDigitsValue, vm.propertyNames->maximumSignificantDigits, minimumSignificantDigits, 21, 21);
     78            RETURN_IF_EXCEPTION(scope, void());
     79            intlInstance->m_minimumSignificantDigits = minimumSignificantDigits;
     80            intlInstance->m_maximumSignificantDigits = maximumSignificantDigits;
     81        } else {
     82            intlInstance->m_minimumSignificantDigits = 1;
     83            intlInstance->m_maximumSignificantDigits = 21;
     84        }
     85    }
     86
     87    if (needFd) {
     88        if (hasFd) {
     89            constexpr unsigned undefinedValue = UINT32_MAX;
     90            unsigned minimumFractionDigits = intlDefaultNumberOption(globalObject, minimumFractionDigitsValue, vm.propertyNames->minimumFractionDigits, 0, 20, undefinedValue);
     91            RETURN_IF_EXCEPTION(scope, void());
     92            unsigned maximumFractionDigits = intlDefaultNumberOption(globalObject, maximumFractionDigitsValue, vm.propertyNames->maximumFractionDigits, 0, 20, undefinedValue);
     93            RETURN_IF_EXCEPTION(scope, void());
     94
     95            if (minimumFractionDigits == undefinedValue)
     96                minimumFractionDigits = std::min(minimumFractionDigitsDefault, maximumFractionDigits);
     97            else if (maximumFractionDigits == undefinedValue)
     98                maximumFractionDigits = std::max(maximumFractionDigitsDefault, minimumFractionDigits);
     99            else if (minimumFractionDigits > maximumFractionDigits) {
     100                throwRangeError(globalObject, scope, "Computed minimumFractionDigits is larger than maximumFractionDigits"_s);
     101                return;
     102            }
     103
     104            intlInstance->m_minimumFractionDigits = minimumFractionDigits;
     105            intlInstance->m_maximumFractionDigits = maximumFractionDigits;
     106        } else {
     107            intlInstance->m_minimumFractionDigits = minimumFractionDigitsDefault;
     108            intlInstance->m_maximumFractionDigits = maximumFractionDigitsDefault;
     109        }
     110    }
     111
     112    if (needSd || needFd) {
     113        if (roundingPriority == IntlRoundingPriority::MorePrecision)
     114            intlInstance->m_roundingType = IntlRoundingType::MorePrecision;
     115        else if (roundingPriority == IntlRoundingPriority::LessPrecision)
     116            intlInstance->m_roundingType = IntlRoundingType::LessPrecision;
     117        else if (hasSd)
     118            intlInstance->m_roundingType = IntlRoundingType::SignificantDigits;
     119        else
     120            intlInstance->m_roundingType = IntlRoundingType::FractionDigits;
     121    } else {
     122        intlInstance->m_roundingType = IntlRoundingType::MorePrecision;
     123        intlInstance->m_minimumFractionDigits = 0;
     124        intlInstance->m_maximumFractionDigits = 0;
     125        intlInstance->m_minimumSignificantDigits = 1;
     126        intlInstance->m_maximumSignificantDigits = 2;
     127    }
     128}
     129
     130template<typename IntlType>
     131void appendNumberFormatDigitOptionsToSkeleton(IntlType* intlInstance, StringBuilder& skeletonBuilder)
     132{
     133    // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#integer-width
     134    skeletonBuilder.append(" integer-width/", WTF::ICU::majorVersion() >= 67 ? '*' : '+'); // Prior to ICU 67, use the symbol + instead of *.
     135    for (unsigned i = 0; i < intlInstance->m_minimumIntegerDigits; ++i)
     136        skeletonBuilder.append('0');
     137
     138    if (intlInstance->m_roundingIncrement != 1) {
     139        skeletonBuilder.append(" precision-increment/");
     140        auto string = numberToStringUnsigned<Vector<LChar, 10>>(intlInstance->m_roundingIncrement);
     141        if (intlInstance->m_maximumFractionDigits >= string.size()) {
     142            skeletonBuilder.append("0.");
     143            for (unsigned i = 0; i < (intlInstance->m_maximumFractionDigits - string.size()); ++i)
     144                skeletonBuilder.append('0');
     145            skeletonBuilder.appendCharacters(string.data(), string.size());
     146        } else {
     147            unsigned nonFraction = string.size() - intlInstance->m_maximumFractionDigits;
     148            skeletonBuilder.appendCharacters(string.data(), nonFraction);
     149            skeletonBuilder.append('.');
     150            skeletonBuilder.appendCharacters(string.data() + nonFraction, intlInstance->m_maximumFractionDigits);
     151        }
     152    } else {
     153        switch (intlInstance->m_roundingType) {
     154        case IntlRoundingType::FractionDigits: {
     155            // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#fraction-precision
     156            skeletonBuilder.append(" .");
     157            for (unsigned i = 0; i < intlInstance->m_minimumFractionDigits; ++i)
     158                skeletonBuilder.append('0');
     159            for (unsigned i = 0; i < intlInstance->m_maximumFractionDigits - intlInstance->m_minimumFractionDigits; ++i)
     160                skeletonBuilder.append('#');
     161            break;
     162        }
     163        case IntlRoundingType::SignificantDigits: {
     164            // https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#significant-digits-precision
     165            skeletonBuilder.append(' ');
     166            for (unsigned i = 0; i < intlInstance->m_minimumSignificantDigits; ++i)
     167                skeletonBuilder.append('@');
     168            for (unsigned i = 0; i < intlInstance->m_maximumSignificantDigits - intlInstance->m_minimumSignificantDigits; ++i)
     169                skeletonBuilder.append('#');
     170            break;
     171        }
     172        case IntlRoundingType::MorePrecision:
     173        case IntlRoundingType::LessPrecision:
     174            // Before Intl.NumberFormat v3, it was CompactRounding mode, where we do not configure anything.
     175            // So, if linked ICU is ~68, we do nothing.
     176            if (WTF::ICU::majorVersion() >= 69) {
     177                // https://github.com/unicode-org/icu/commit/d7db6c1f8655bb53153695b09a50029fd04a8364
     178                // https://github.com/unicode-org/icu/blob/main/docs/userguide/format_parse/numbers/skeletons.md#precision
     179                skeletonBuilder.append(" .");
     180                for (unsigned i = 0; i < intlInstance->m_minimumFractionDigits; ++i)
     181                    skeletonBuilder.append('0');
     182                for (unsigned i = 0; i < intlInstance->m_maximumFractionDigits - intlInstance->m_minimumFractionDigits; ++i)
     183                    skeletonBuilder.append('#');
     184                skeletonBuilder.append('/');
     185                for (unsigned i = 0; i < intlInstance->m_minimumSignificantDigits; ++i)
     186                    skeletonBuilder.append('@');
     187                for (unsigned i = 0; i < intlInstance->m_maximumSignificantDigits - intlInstance->m_minimumSignificantDigits; ++i)
     188                    skeletonBuilder.append('#');
     189                skeletonBuilder.append(intlInstance->m_roundingType == IntlRoundingType::MorePrecision ? 'r' : 's');
     190            }
     191            break;
     192        }
     193    }
    105194}
    106195
     
    129218}
    130219
     220// https://tc39.es/proposal-intl-numberformat-v3/out/numberformat/diff.html#sec-tointlmathematicalvalue
     221inline IntlMathematicalValue toIntlMathematicalValue(JSGlobalObject* globalObject, JSValue value)
     222{
     223    VM& vm = globalObject->vm();
     224    auto scope = DECLARE_THROW_SCOPE(vm);
     225
     226    if (auto number = JSBigInt::tryExtractDouble(value))
     227        return IntlMathematicalValue { number.value() };
     228
     229    JSValue primitive = value.toPrimitive(globalObject, PreferredPrimitiveType::PreferNumber);
     230    RETURN_IF_EXCEPTION(scope, { });
     231
     232    auto bigIntToIntlMathematicalValue = [](JSGlobalObject* globalObject, JSValue value) -> IntlMathematicalValue {
     233        VM& vm = globalObject->vm();
     234        auto scope = DECLARE_THROW_SCOPE(vm);
     235
     236        if (auto number = JSBigInt::tryExtractDouble(value))
     237            return IntlMathematicalValue { number.value() };
     238
     239        auto* bigInt = value.asHeapBigInt();
     240        auto string = bigInt->toString(globalObject, 10);
     241        RETURN_IF_EXCEPTION(scope, { });
     242        return IntlMathematicalValue {
     243            IntlMathematicalValue::NumberType::Integer,
     244            bigInt->sign(),
     245            string.ascii(),
     246        };
     247    };
     248
     249    if (primitive.isBigInt())
     250        RELEASE_AND_RETURN(scope, bigIntToIntlMathematicalValue(globalObject, primitive));
     251
     252    if (!primitive.isString())
     253        RELEASE_AND_RETURN(scope, IntlMathematicalValue { primitive.toNumber(globalObject) });
     254
     255    String string = asString(primitive)->value(globalObject);
     256    RETURN_IF_EXCEPTION(scope, { });
     257
     258    JSValue bigInt = JSBigInt::stringToBigInt(globalObject, string);
     259    if (bigInt) {
     260        // If it is -0, we cannot handle it in JSBigInt. Reparse the string as double.
     261#if USE(BIGINT32)
     262        if (bigInt.isBigInt32() && !value.bigInt32AsInt32())
     263            return IntlMathematicalValue { jsToNumber(string) };
     264#endif
     265        if (bigInt.isHeapBigInt() && !asHeapBigInt(bigInt)->length())
     266            return IntlMathematicalValue { jsToNumber(string) };
     267        RELEASE_AND_RETURN(scope, bigIntToIntlMathematicalValue(globalObject, bigInt));
     268    }
     269
     270    return IntlMathematicalValue { jsToNumber(string) };
     271}
     272
    131273} // namespace JSC
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp

    r281790 r285418  
    4040static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatFuncFormat);
    4141
     42#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     43static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRange);
     44#endif
     45
    4246}
    4347
     
    5660*/
    5761
    58 IntlNumberFormatPrototype* IntlNumberFormatPrototype::create(VM& vm, JSGlobalObject*, Structure* structure)
     62IntlNumberFormatPrototype* IntlNumberFormatPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
    5963{
    6064    IntlNumberFormatPrototype* object = new (NotNull, allocateCell<IntlNumberFormatPrototype>(vm.heap)) IntlNumberFormatPrototype(vm, structure);
    61     object->finishCreation(vm);
     65    object->finishCreation(vm, globalObject);
    6266    return object;
    6367}
     
    7377}
    7478
    75 void IntlNumberFormatPrototype::finishCreation(VM& vm)
     79void IntlNumberFormatPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
    7680{
    7781    Base::finishCreation(vm);
    7882    ASSERT(inherits(vm, info()));
    7983    JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
     84    UNUSED_PARAM(globalObject);
     85#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     86    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("formatRange", intlNumberFormatPrototypeFuncFormatRange, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
     87#endif
    8088}
    8189
     
    8795    auto* numberFormat = jsCast<IntlNumberFormat*>(callFrame->thisValue());
    8896
    89     JSValue bigIntOrNumber = callFrame->argument(0).toNumeric(globalObject);
    90     RETURN_IF_EXCEPTION(scope, encodedJSValue());
    91 
    92     scope.release();
    93     if (bigIntOrNumber.isNumber()) {
    94         double value = bigIntOrNumber.asNumber();
    95         return JSValue::encode(numberFormat->format(globalObject, value));
    96     }
    97 
    98 #if USE(BIGINT32)
    99     if (bigIntOrNumber.isBigInt32()) {
    100         JSBigInt* value = JSBigInt::createFrom(globalObject, bigIntOrNumber.bigInt32AsInt32());
    101         RETURN_IF_EXCEPTION(scope, { });
    102         RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, value)));
    103     }
    104 #endif
    105 
    106     ASSERT(bigIntOrNumber.isHeapBigInt());
    107     JSBigInt* value = bigIntOrNumber.asHeapBigInt();
    108     return JSValue::encode(numberFormat->format(globalObject, value));
     97    auto value = toIntlMathematicalValue(globalObject, callFrame->argument(0));
     98    RETURN_IF_EXCEPTION(scope, { });
     99
     100    if (auto number = value.tryGetDouble())
     101        RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, number.value())));
     102
     103    RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->format(globalObject, WTFMove(value))));
    109104}
    110105
     
    138133}
    139134
     135#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     136JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRange, (JSGlobalObject* globalObject, CallFrame* callFrame))
     137{
     138    VM& vm = globalObject->vm();
     139    auto scope = DECLARE_THROW_SCOPE(vm);
     140
     141    // Do not use unwrapForOldFunctions.
     142    auto* numberFormat = jsDynamicCast<IntlNumberFormat*>(vm, callFrame->thisValue());
     143    if (UNLIKELY(!numberFormat))
     144        return JSValue::encode(throwTypeError(globalObject, scope, "Intl.NumberFormat.prototype.formatRange called on value that's not a NumberFormat"_s));
     145
     146    JSValue startValue = callFrame->argument(0);
     147    JSValue endValue = callFrame->argument(1);
     148
     149    if (startValue.isUndefined() || endValue.isUndefined())
     150        return throwVMTypeError(globalObject, scope, "start or end is undefined"_s);
     151
     152    auto start = toIntlMathematicalValue(globalObject, startValue);
     153    RETURN_IF_EXCEPTION(scope, { });
     154
     155    auto end = toIntlMathematicalValue(globalObject, endValue);
     156    RETURN_IF_EXCEPTION(scope, { });
     157
     158    if (auto startNumber = start.tryGetDouble()) {
     159        if (auto endNumber = end.tryGetDouble())
     160            RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatRange(globalObject, startNumber.value(), endNumber.value())));
     161    }
     162
     163    RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatRange(globalObject, WTFMove(start), WTFMove(end))));
     164}
     165#endif
     166
    140167JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatToParts, (JSGlobalObject* globalObject, CallFrame* callFrame))
    141168{
     
    151178        return JSValue::encode(throwTypeError(globalObject, scope, "Intl.NumberFormat.prototype.formatToParts called on value that's not a NumberFormat"_s));
    152179
     180#if HAVE(ICU_U_NUMBER_FORMATTER)
     181    auto value = toIntlMathematicalValue(globalObject, callFrame->argument(0));
     182    RETURN_IF_EXCEPTION(scope, { });
     183
     184    if (auto number = value.tryGetDouble())
     185        RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatToParts(globalObject, number.value())));
     186
     187    RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatToParts(globalObject, WTFMove(value))));
     188#else
    153189    double value = callFrame->argument(0).toNumber(globalObject);
    154     RETURN_IF_EXCEPTION(scope, encodedJSValue());
     190    RETURN_IF_EXCEPTION(scope, { });
    155191
    156192    RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatToParts(globalObject, value)));
     193#endif
    157194}
    158195
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.h

    r260415 r285418  
    5050private:
    5151    IntlNumberFormatPrototype(VM&, Structure*);
    52     void finishCreation(VM&);
     52    void finishCreation(VM&, JSGlobalObject*);
    5353};
    5454
  • trunk/Source/JavaScriptCore/runtime/IntlObjectInlines.h

    r282257 r285418  
    152152}
    153153
     154template<typename ResultType>
     155ResultType intlStringOrBooleanOption(JSGlobalObject* globalObject, JSObject* options, PropertyName property, ResultType trueValue, ResultType falsyValue, std::initializer_list<std::pair<ASCIILiteral, ResultType>> values, ASCIILiteral notFoundMessage, ResultType fallback)
     156{
     157    // https://tc39.es/proposal-intl-numberformat-v3/out/negotiation/diff.html#sec-getstringorbooleanoption
     158
     159    ASSERT(values.size() > 0);
     160
     161    VM& vm = globalObject->vm();
     162    auto scope = DECLARE_THROW_SCOPE(vm);
     163
     164    if (!options)
     165        return fallback;
     166
     167    JSValue value = options->get(globalObject, property);
     168    RETURN_IF_EXCEPTION(scope, { });
     169
     170    if (!value.isUndefined()) {
     171        if (value.isBoolean() && value.asBoolean())
     172            return trueValue;
     173
     174        bool valueBoolean = value.toBoolean(globalObject);
     175        RETURN_IF_EXCEPTION(scope, { });
     176
     177        if (!valueBoolean)
     178            return falsyValue;
     179
     180        String stringValue = value.toWTFString(globalObject);
     181        RETURN_IF_EXCEPTION(scope, { });
     182
     183        for (const auto& entry : values) {
     184            if (entry.first == stringValue)
     185                return entry.second;
     186        }
     187        throwException(globalObject, scope, createRangeError(globalObject, notFoundMessage));
     188        return { };
     189    }
     190
     191    return fallback;
     192}
    154193
    155194ALWAYS_INLINE bool canUseASCIIUCADUCETComparison(UChar character)
  • trunk/Source/JavaScriptCore/runtime/IntlPluralRules.cpp

    r278035 r285418  
    3333#include "ObjectConstructor.h"
    3434
     35#ifdef U_HIDE_DRAFT_API
     36#undef U_HIDE_DRAFT_API
     37#endif
     38#include <unicode/upluralrules.h>
     39#if HAVE(ICU_U_NUMBER_FORMATTER)
     40#include <unicode/unumberformatter.h>
     41#endif
     42#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     43#include <unicode/unumberrangeformatter.h>
     44#endif
     45#define U_HIDE_DRAFT_API 1
     46
    3547namespace JSC {
     48
     49void UPluralRulesDeleter::operator()(UPluralRules* pluralRules)
     50{
     51    if (pluralRules)
     52        uplrules_close(pluralRules);
     53}
    3654
    3755const ClassInfo IntlPluralRules::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlPluralRules) };
     
    108126    RETURN_IF_EXCEPTION(scope, void());
    109127
     128    auto locale = m_locale.utf8();
    110129    UErrorCode status = U_ZERO_ERROR;
    111     m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(UNUM_DECIMAL, nullptr, 0, m_locale.utf8().data(), nullptr, &status));
     130
     131#if HAVE(ICU_U_NUMBER_FORMATTER)
     132    StringBuilder skeletonBuilder;
     133    skeletonBuilder.append("rounding-mode-half-up");
     134
     135    appendNumberFormatDigitOptionsToSkeleton(this, skeletonBuilder);
     136
     137    String skeleton = skeletonBuilder.toString();
     138    StringView skeletonView(skeleton);
     139    auto upconverted = skeletonView.upconvertedCharacters();
     140
     141    m_numberFormatter = std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter>(unumf_openForSkeletonAndLocale(upconverted.get(), skeletonView.length(), locale.data(), &status));
     142    if (U_FAILURE(status)) {
     143        throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s);
     144        return;
     145    }
     146
     147#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     148    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));
     149    if (U_FAILURE(status)) {
     150        throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s);
     151        return;
     152    }
     153#endif
     154#else
     155    m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(UNUM_DECIMAL, nullptr, 0, locale.data(), nullptr, &status));
    112156    if (U_FAILURE(status)) {
    113157        throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s);
     
    126170        unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits);
    127171        break;
    128     default:
    129         RELEASE_ASSERT_NOT_REACHED();
    130         break;
    131     }
    132 
    133     status = U_ZERO_ERROR;
    134     m_pluralRules = std::unique_ptr<UPluralRules, UPluralRulesDeleter>(uplrules_openForType(m_locale.utf8().data(), m_type == Type::Ordinal ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL, &status));
     172    case IntlRoundingType::MorePrecision:
     173    case IntlRoundingType::LessPrecision:
     174        break;
     175    }
     176#endif
     177
     178    m_pluralRules = std::unique_ptr<UPluralRules, UPluralRulesDeleter>(uplrules_openForType(locale.data(), m_type == Type::Ordinal ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL, &status));
    135179    if (U_FAILURE(status)) {
    136180        throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s);
     
    160204        options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits));
    161205        break;
    162     default:
    163         RELEASE_ASSERT_NOT_REACHED();
     206    case IntlRoundingType::MorePrecision:
     207    case IntlRoundingType::LessPrecision:
     208        options->putDirect(vm, vm.propertyNames->minimumFractionDigits, jsNumber(m_minimumFractionDigits));
     209        options->putDirect(vm, vm.propertyNames->maximumFractionDigits, jsNumber(m_maximumFractionDigits));
     210        options->putDirect(vm, vm.propertyNames->minimumSignificantDigits, jsNumber(m_minimumSignificantDigits));
     211        options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits));
    164212        break;
    165213    }
     
    184232    }
    185233    options->putDirect(vm, Identifier::fromString(vm, "pluralCategories"), categories);
    186 
    187     RELEASE_AND_RETURN(scope, options);
     234    options->putDirect(vm, vm.propertyNames->roundingMode, jsNontrivialString(vm, IntlNumberFormat::roundingPriorityString(m_roundingType)));
     235
     236    return options;
    188237}
    189238
     
    200249
    201250    UErrorCode status = U_ZERO_ERROR;
     251
     252#if HAVE(ICU_U_NUMBER_FORMATTER)
     253    auto formattedNumber = std::unique_ptr<UFormattedNumber, ICUDeleter<unumf_closeResult>>(unumf_openResult(&status));
     254    if (U_FAILURE(status))
     255        return throwTypeError(globalObject, scope, "failed to select plural value"_s);
     256    unumf_formatDouble(m_numberFormatter.get(), value, formattedNumber.get(), &status);
     257    if (U_FAILURE(status))
     258        return throwTypeError(globalObject, scope, "failed to select plural value"_s);
     259    Vector<UChar, 32> buffer;
     260    status = callBufferProducingFunction(uplrules_selectFormatted, m_pluralRules.get(), formattedNumber.get(), buffer);
     261    if (U_FAILURE(status))
     262        return throwTypeError(globalObject, scope, "failed to select plural value"_s);
     263    return jsString(vm, String(WTFMove(buffer)));
     264#else
    202265    Vector<UChar, 8> result(8);
    203266    auto length = uplrules_selectWithFormat(m_pluralRules.get(), value, m_numberFormat.get(), result.data(), result.size(), &status);
     
    206269
    207270    return jsString(vm, String(result.data(), length));
    208 }
     271#endif
     272}
     273
     274#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     275JSValue IntlPluralRules::selectRange(JSGlobalObject* globalObject, double start, double end) const
     276{
     277    ASSERT(m_numberRangeFormatter);
     278
     279    VM& vm = globalObject->vm();
     280    auto scope = DECLARE_THROW_SCOPE(vm);
     281
     282    if (start > end) {
     283        throwRangeError(globalObject, scope, "start is larger than end"_s);
     284        return { };
     285    }
     286
     287    UErrorCode status = U_ZERO_ERROR;
     288    auto range = std::unique_ptr<UFormattedNumberRange, ICUDeleter<unumrf_closeResult>>(unumrf_openResult(&status));
     289    if (U_FAILURE(status))
     290        return throwTypeError(globalObject, scope, "failed to select range of plural value"_s);
     291
     292    unumrf_formatDoubleRange(m_numberRangeFormatter.get(), start, end, range.get(), &status);
     293    if (U_FAILURE(status))
     294        return throwTypeError(globalObject, scope, "failed to select range of plural value"_s);
     295
     296    Vector<UChar, 32> buffer;
     297    status = callBufferProducingFunction(uplrules_selectForRange, m_pluralRules.get(), range.get(), buffer);
     298    if (U_FAILURE(status))
     299        return throwTypeError(globalObject, scope, "failed to select plural value"_s);
     300    return jsString(vm, String(WTFMove(buffer)));
     301}
     302#endif
    209303
    210304} // namespace JSC
  • trunk/Source/JavaScriptCore/runtime/IntlPluralRules.h

    r278035 r285418  
    2929#include "IntlNumberFormat.h"
    3030#include <unicode/unum.h>
    31 #include <unicode/upluralrules.h>
    3231#include <wtf/unicode/icu/ICUHelpers.h>
    3332
     33struct UPluralRules;
     34
    3435namespace JSC {
     36
     37struct UPluralRulesDeleter {
     38    JS_EXPORT_PRIVATE void operator()(UPluralRules*);
     39};
    3540
    3641enum class RelevantExtensionKey : uint8_t;
     
    6065    template<typename IntlType>
    6166    friend void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation);
     67    template<typename IntlType>
     68    friend void appendNumberFormatDigitOptionsToSkeleton(IntlType*, StringBuilder&);
    6269
    6370    void initializePluralRules(JSGlobalObject*, JSValue locales, JSValue options);
    6471    JSValue select(JSGlobalObject*, double value) const;
    6572    JSObject* resolvedOptions(JSGlobalObject*) const;
     73
     74#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     75    JSValue selectRange(JSGlobalObject*, double start, double end) const;
     76#endif
    6677
    6778private:
     
    7485    enum class Type : bool { Cardinal, Ordinal };
    7586
    76     using UPluralRulesDeleter = ICUDeleter<uplrules_close>;
     87    std::unique_ptr<UPluralRules, UPluralRulesDeleter> m_pluralRules;
     88#if HAVE(ICU_U_NUMBER_FORMATTER)
     89    std::unique_ptr<UNumberFormatter, UNumberFormatterDeleter> m_numberFormatter;
     90#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     91    std::unique_ptr<UNumberRangeFormatter, UNumberRangeFormatterDeleter> m_numberRangeFormatter;
     92#endif
     93#else
    7794    using UNumberFormatDeleter = ICUDeleter<unum_close>;
    78 
    79     std::unique_ptr<UPluralRules, UPluralRulesDeleter> m_pluralRules;
    8095    std::unique_ptr<UNumberFormat, UNumberFormatDeleter> m_numberFormat;
     96#endif
    8197
    8298    String m_locale;
     
    86102    unsigned m_minimumSignificantDigits { 0 };
    87103    unsigned m_maximumSignificantDigits { 0 };
     104    unsigned m_roundingIncrement { 1 };
    88105    IntlRoundingType m_roundingType { IntlRoundingType::FractionDigits };
    89106    Type m_type { Type::Cardinal };
  • trunk/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp

    r281790 r285418  
    3434
    3535static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect);
     36#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     37static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelectRange);
     38#endif
    3639static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncResolvedOptions);
    3740
     
    5154*/
    5255
    53 IntlPluralRulesPrototype* IntlPluralRulesPrototype::create(VM& vm, JSGlobalObject*, Structure* structure)
     56IntlPluralRulesPrototype* IntlPluralRulesPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
    5457{
    5558    IntlPluralRulesPrototype* object = new (NotNull, allocateCell<IntlPluralRulesPrototype>(vm.heap)) IntlPluralRulesPrototype(vm, structure);
    56     object->finishCreation(vm);
     59    object->finishCreation(vm, globalObject);
    5760    return object;
    5861}
     
    6871}
    6972
    70 void IntlPluralRulesPrototype::finishCreation(VM& vm)
     73void IntlPluralRulesPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
    7174{
    7275    Base::finishCreation(vm);
    7376    ASSERT(inherits(vm, info()));
    7477    JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
     78    UNUSED_PARAM(globalObject);
     79#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     80    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->selectRange, intlPluralRulesPrototypeFuncSelectRange, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
     81#endif
    7582}
    7683
     
    93100}
    94101
     102#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
     103JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelectRange, (JSGlobalObject* globalObject, CallFrame* callFrame))
     104{
     105    VM& vm = globalObject->vm();
     106    auto scope = DECLARE_THROW_SCOPE(vm);
     107
     108    // https://tc39.es/proposal-intl-numberformat-v3/out/pluralrules/diff.html#sec-intl.pluralrules.prototype.selectrange
     109    IntlPluralRules* pluralRules = jsDynamicCast<IntlPluralRules*>(vm, callFrame->thisValue());
     110    if (!pluralRules)
     111        return JSValue::encode(throwTypeError(globalObject, scope, "Intl.PluralRules.prototype.selectRange called on value that's not a PluralRules"_s));
     112
     113    double start = callFrame->argument(0).toNumber(globalObject);
     114    RETURN_IF_EXCEPTION(scope, { });
     115
     116    double end = callFrame->argument(1).toNumber(globalObject);
     117    RETURN_IF_EXCEPTION(scope, { });
     118
     119    RELEASE_AND_RETURN(scope, JSValue::encode(pluralRules->selectRange(globalObject, start, end)));
     120}
     121#endif
     122
    95123JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncResolvedOptions, (JSGlobalObject* globalObject, CallFrame* callFrame))
    96124{
  • trunk/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h

    r260415 r285418  
    5050private:
    5151    IntlPluralRulesPrototype(VM&, Structure*);
    52     void finishCreation(VM&);
     52    void finishCreation(VM&, JSGlobalObject*);
    5353};
    5454
  • trunk/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp

    r278253 r285418  
    319319
    320320        IntlFieldIterator fieldIterator(*iterator.get());
    321         IntlNumberFormat::formatToPartsInternal(globalObject, IntlNumberFormat::Style::Decimal, absValue, formattedNumber, fieldIterator, parts, jsString(vm, singularUnit(unit).toString()));
     321        IntlNumberFormat::formatToPartsInternal(globalObject, IntlNumberFormat::Style::Decimal, std::signbit(absValue), IntlMathematicalValue::numberTypeFromDouble(absValue), formattedNumber, fieldIterator, parts, jsString(vm, singularUnit(unit).toString()));
    322322        RETURN_IF_EXCEPTION(scope, { });
    323323    }
  • trunk/Source/JavaScriptCore/runtime/JSBigInt.h

    r285178 r285418  
    465465    }
    466466
     467    static std::optional<double> tryExtractDouble(JSValue);
     468
    467469private:
    468470    JSBigInt(VM&, Structure*, Digit*, unsigned length);
     
    677679}
    678680
     681ALWAYS_INLINE std::optional<double> JSBigInt::tryExtractDouble(JSValue value)
     682{
     683    if (value.isNumber())
     684        return value.asNumber();
     685
     686    if (!value.isBigInt())
     687        return std::nullopt;
     688
     689#if USE(BIGINT32)
     690    if (value.isBigInt32())
     691        return value.bigInt32AsInt32();
     692#endif
     693
     694    ASSERT(value.isHeapBigInt());
     695    JSBigInt* bigInt = value.asHeapBigInt();
     696    if (!bigInt->length())
     697        return 0;
     698
     699    uint64_t integer = 0;
     700    if constexpr (sizeof(Digit) == 8) {
     701        if (bigInt->length() != 1)
     702            return std::nullopt;
     703        integer = bigInt->digit(0);
     704    } else {
     705        ASSERT(sizeof(Digit) == 4);
     706        if (bigInt->length() > 2)
     707            return std::nullopt;
     708        integer = bigInt->digit(0);
     709        if (bigInt->length() == 2)
     710            integer |= (static_cast<uint64_t>(bigInt->digit(1)) << 32);
     711    }
     712
     713    if (integer <= static_cast<uint64_t>(maxSafeInteger()))
     714        return (bigInt->sign()) ? -static_cast<double>(integer) : static_cast<double>(integer);
     715
     716    return std::nullopt;
     717}
     718
    679719} // namespace JSC
  • trunk/Source/JavaScriptCore/runtime/MathCommon.h

    r281910 r285418  
    6464}
    6565
     66inline bool isNegativeZero(double value)
     67{
     68    return std::signbit(value) && value == 0;
     69}
     70
    6671// This in the ToInt32 operation is defined in section 9.5 of the ECMA-262 spec.
    6772// Note that this operation is identical to ToUInt32 other than to interpretation
  • trunk/Source/JavaScriptCore/runtime/TemporalObject.cpp

    r285178 r285418  
    453453    case RoundingMode::HalfExpand:
    454454        return std::round(quotient) * increment;
     455
     456    // They are not supported in Temporal right now.
     457    case RoundingMode::Expand:
     458    case RoundingMode::HalfCeil:
     459    case RoundingMode::HalfFloor:
     460    case RoundingMode::HalfTrunc:
     461    case RoundingMode::HalfEven:
     462        return std::trunc(quotient) * increment;
    455463    }
    456464
  • trunk/Source/JavaScriptCore/runtime/TemporalObject.h

    r285178 r285418  
    8181    Ceil,
    8282    Floor,
     83    Expand,
    8384    Trunc,
    84     HalfExpand
     85    HalfCeil,
     86    HalfFloor,
     87    HalfExpand,
     88    HalfTrunc,
     89    HalfEven
    8590};
    8691
Note: See TracChangeset for help on using the changeset viewer.