Changeset 285418 in webkit
- Timestamp:
- Nov 8, 2021 11:20:42 AM (8 months ago)
- Location:
- trunk
- Files:
-
- 16 added
- 24 edited
-
JSTests/ChangeLog (modified) (1 diff)
-
JSTests/stress/intl-numberformat-format-large.js (added)
-
JSTests/stress/intl-numberformat-format-range-v3.js (added)
-
JSTests/stress/intl-numberformat-format-string-v3.js (added)
-
JSTests/stress/intl-numberformat-format-string.js (added)
-
JSTests/stress/intl-numberformat-format-to-parts.js (modified) (1 diff)
-
JSTests/stress/intl-numberformat-rounding-increment-resolved-match-v3.js (added)
-
JSTests/stress/intl-numberformat-rounding-increment-v3.js (added)
-
JSTests/stress/intl-numberformat-rounding-increment-value-hanidec.js (added)
-
JSTests/stress/intl-numberformat-rounding-increment-value-v3.js (added)
-
JSTests/stress/intl-numberformat-rounding-increment-value.js (added)
-
JSTests/stress/intl-numberformat-rounding-mode-table-v3.js (added)
-
JSTests/stress/intl-numberformat-rounding-mode-v3.js (added)
-
JSTests/stress/intl-numberformat-sign-display-v3.js (added)
-
JSTests/stress/intl-numberformat-trailing-zero-display-resolved-options-v3.js (added)
-
JSTests/stress/intl-numberformat-trailing-zero-display-v3.js (added)
-
JSTests/stress/intl-numberformat-usegrouping-v3.js (added)
-
JSTests/stress/intl-numberformat.js (modified) (3 diffs)
-
JSTests/stress/intl-pluralrules-selectrange.js (added)
-
JSTests/test262/config.yaml (modified) (1 diff)
-
Source/JavaScriptCore/ChangeLog (modified) (1 diff)
-
Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj (modified) (3 diffs)
-
Source/JavaScriptCore/Sources.txt (modified) (1 diff)
-
Source/JavaScriptCore/runtime/BigIntPrototype.cpp (modified) (2 diffs)
-
Source/JavaScriptCore/runtime/CommonIdentifiers.h (modified) (2 diffs)
-
Source/JavaScriptCore/runtime/IntlNumberFormat.cpp (modified) (10 diffs)
-
Source/JavaScriptCore/runtime/IntlNumberFormat.h (modified) (9 diffs)
-
Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h (modified) (3 diffs)
-
Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp (modified) (6 diffs)
-
Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.h (modified) (1 diff)
-
Source/JavaScriptCore/runtime/IntlObjectInlines.h (modified) (1 diff)
-
Source/JavaScriptCore/runtime/IntlPluralRules.cpp (modified) (7 diffs)
-
Source/JavaScriptCore/runtime/IntlPluralRules.h (modified) (4 diffs)
-
Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp (modified) (4 diffs)
-
Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h (modified) (1 diff)
-
Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp (modified) (1 diff)
-
Source/JavaScriptCore/runtime/JSBigInt.h (modified) (2 diffs)
-
Source/JavaScriptCore/runtime/MathCommon.h (modified) (1 diff)
-
Source/JavaScriptCore/runtime/TemporalObject.cpp (modified) (1 diff)
-
Source/JavaScriptCore/runtime/TemporalObject.h (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/JSTests/ChangeLog
r285406 r285418 1 2021-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 1 66 2021-11-08 Saam Barati <sbarati@apple.com> 2 67 -
trunk/JSTests/stress/intl-numberformat-format-to-parts.js
r259658 r285418 58 58 shouldBe(Intl.NumberFormat('en-US', { useGrouping: false }).formatToParts(Number.MAX_VALUE)[0].value.length, 309); 59 59 shouldBe(Intl.NumberFormat('en-US').formatToParts(Number.MAX_VALUE).length, 205); 60 61 shouldBe( 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 71 shouldBe( 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 82 shouldBe( 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 54 54 minimumSignificantDigits: undefined, 55 55 maximumSignificantDigits: undefined, 56 useGrouping: true,56 useGrouping: "auto", 57 57 notation: "standard", 58 signDisplay: "auto" 58 signDisplay: "auto", 59 roundingMode: "halfExpand", 60 roundingIncrement: 1, 61 trailingZeroDisplay: "auto", 62 roundingPriority: "auto", 59 63 }; 60 64 Object.assign(defaultOptions, difference); … … 185 189 186 190 // The option useGrouping is processed correctly. 187 shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: true}), [{locale: 'en', useGrouping: true}]), true);191 shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: true}), [{locale: 'en', useGrouping: "always"}]), true); 188 192 shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: false}), [{locale: 'en', useGrouping: false}]), true); 189 shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: ' false'}), [{locale: 'en', useGrouping: true}]), true);193 shouldBe(testNumberFormat(Intl.NumberFormat('en', {useGrouping: 'min2'}), [{locale: 'en', useGrouping: "min2"}]), true); 190 194 shouldThrow(() => Intl.NumberFormat('en', { get useGrouping() { throw new Error(); } }), Error); 191 195 … … 436 440 let options = defaultNFormat.resolvedOptions(); 437 441 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"}`); 439 443 } 440 444 -
trunk/JSTests/test262/config.yaml
r285178 r285418 103 103 # https://unicode-org.atlassian.net/browse/ICU-21367 104 104 - 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 105 111 106 112 # Failing because we are building WebKit with very old ICU headers -
trunk/Source/JavaScriptCore/ChangeLog
r285406 r285418 1 2021-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 1 84 2021-11-08 Saam Barati <sbarati@apple.com> 2 85 -
trunk/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
r285178 r285418 1846 1846 E307178D24C7829A00DF0644 /* IntlLocaleConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = A3AFF92C245A3CFA00C9BA3B /* IntlLocaleConstructor.h */; }; 1847 1847 E307178E24C7829D00DF0644 /* IntlLocale.h in Headers */ = {isa = PBXBuildFile; fileRef = A3AFF92B245A3CF900C9BA3B /* IntlLocale.h */; }; 1848 E30873E7272559410053B601 /* IntlPluralRules.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7D5FB18F20744BF1005DDF64 /* IntlPluralRules.cpp */; }; 1848 1849 E30E8A5426DE2E4800DA4915 /* TemporalTimeZonePrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = E30E8A4E26DE2E4700DA4915 /* TemporalTimeZonePrototype.h */; }; 1849 1850 E30E8A5626DE2E4800DA4915 /* TemporalTimeZone.h in Headers */ = {isa = PBXBuildFile; fileRef = E30E8A5026DE2E4800DA4915 /* TemporalTimeZone.h */; }; … … 1917 1918 E374166E26912BC700C80789 /* ObjectConstructorInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E374166D26912BC700C80789 /* ObjectConstructorInlines.h */; }; 1918 1919 E3750CC82502E87E006A0AAB /* IntlDateTimeFormatInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E3750CC72502E87E006A0AAB /* IntlDateTimeFormatInlines.h */; }; 1920 E378DC8D2727629400427B0B /* IntlNumberFormat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A1D792F61B43864B004516F5 /* IntlNumberFormat.cpp */; }; 1919 1921 E3794E761B77EB97005543AE /* ModuleAnalyzer.h in Headers */ = {isa = PBXBuildFile; fileRef = E3794E741B77EB97005543AE /* ModuleAnalyzer.h */; settings = {ATTRIBUTES = (Private, ); }; }; 1920 1922 E383500A2390D93B0036316D /* WasmGlobal.h in Headers */ = {isa = PBXBuildFile; fileRef = E38350092390D9370036316D /* WasmGlobal.h */; }; … … 11859 11861 E399AEC32559457F00B78485 /* IntlDateTimeFormat.cpp in Sources */, 11860 11862 E366441E254409B30001876F /* IntlListFormat.cpp in Sources */, 11863 E378DC8D2727629400427B0B /* IntlNumberFormat.cpp in Sources */, 11864 E30873E7272559410053B601 /* IntlPluralRules.cpp in Sources */, 11861 11865 A3EE8543262514B000FC9B8D /* IntlWorkaround.cpp in Sources */, 11862 11866 E38E8790254B978400F6F9E4 /* JSDateMath.cpp in Sources */, -
trunk/Source/JavaScriptCore/Sources.txt
r285178 r285418 836 836 runtime/IntlLocaleConstructor.cpp 837 837 runtime/IntlLocalePrototype.cpp 838 runtime/IntlNumberFormat.cpp 838 runtime/IntlNumberFormat.cpp @no-unify // Confine U_HIDE_DRAFT_API's effect in this file. 839 839 runtime/IntlNumberFormatConstructor.cpp 840 840 runtime/IntlNumberFormatPrototype.cpp 841 841 runtime/IntlObject.cpp 842 runtime/IntlPluralRules.cpp 842 runtime/IntlPluralRules.cpp @no-unify // Confine U_HIDE_DRAFT_API's effect to this file. 843 843 runtime/IntlPluralRulesConstructor.cpp 844 844 runtime/IntlPluralRulesPrototype.cpp -
trunk/Source/JavaScriptCore/runtime/BigIntPrototype.cpp
r267594 r285418 30 30 #include "BigIntObject.h" 31 31 #include "IntegrityInlines.h" 32 #include "IntlNumberFormat .h"32 #include "IntlNumberFormatInlines.h" 33 33 #include "JSBigInt.h" 34 34 #include "JSCInlines.h" … … 133 133 auto scope = DECLARE_THROW_SCOPE(vm); 134 134 135 JSBigInt* value = toThisBigIntValue(globalObject, callFrame->thisValue());135 JSBigInt* thisValue = toThisBigIntValue(globalObject, callFrame->thisValue()); 136 136 RETURN_IF_EXCEPTION(scope, { }); 137 137 138 138 auto* numberFormat = IntlNumberFormat::create(vm, globalObject->numberFormatStructure()); 139 139 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)))); 142 149 } 143 150 -
trunk/Source/JavaScriptCore/runtime/CommonIdentifiers.h
r284435 r285418 218 218 macro(roundingIncrement) \ 219 219 macro(roundingMode) \ 220 macro(roundingPriority) \ 220 221 macro(script) \ 221 222 macro(second) \ 222 223 macro(seconds) \ 223 224 macro(segment) \ 225 macro(selectRange) \ 224 226 macro(sensitivity) \ 225 227 macro(set) \ … … 250 252 macro(toPrecision) \ 251 253 macro(toString) \ 254 macro(trailingZeroDisplay) \ 252 255 macro(type) \ 253 256 macro(uid) \ -
trunk/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp
r281513 r285418 36 36 #include "JSCInlines.h" 37 37 #include "ObjectConstructor.h" 38 #include <wtf/Range.h> 38 39 #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 39 51 40 52 namespace JSC { … … 45 57 static constexpr bool verbose = false; 46 58 } 59 60 #if HAVE(ICU_U_NUMBER_FORMATTER) 61 void UNumberFormatterDeleter::operator()(UNumberFormatter* formatter) 62 { 63 if (formatter) 64 unumf_close(formatter); 65 } 66 #endif 67 68 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) 69 void UNumberRangeFormatterDeleter::operator()(UNumberRangeFormatter* formatter) 70 { 71 if (formatter) 72 unumrf_close(formatter); 73 } 74 #endif 47 75 48 76 struct IntlNumberFormatField { … … 215 243 } 216 244 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) 245 static ASCIILiteral partTypeString(UNumberFormatFields field, IntlNumberFormat::Style style, bool sign, IntlMathematicalValue::NumberType type) 784 246 { 785 247 switch (field) { 786 248 case UNUM_INTEGER_FIELD: 787 if (std::isnan(value)) 249 switch (type) { 250 case IntlMathematicalValue::NumberType::NaN: 788 251 return "nan"_s; 789 if (!std::isfinite(value))252 case IntlMathematicalValue::NumberType::Infinity: 790 253 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; 792 259 case UNUM_FRACTION_FIELD: 793 260 return "fraction"_s; … … 809 276 return (style == IntlNumberFormat::Style::Unit) ? "unit"_s : "percentSign"_s; 810 277 case UNUM_SIGN_FIELD: 811 return s td::signbit(value)? "minusSign"_s : "plusSign"_s;278 return sign ? "minusSign"_s : "plusSign"_s; 812 279 #if HAVE(ICU_U_NUMBER_FORMATTER) 813 280 case UNUM_MEASURE_UNIT_FIELD: … … 826 293 } 827 294 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 296 void 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 763 JSValue 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 791 JSValue 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) 822 JSValue 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 859 JSValue 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 897 ASCIILiteral 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 913 ASCIILiteral 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 929 ASCIILiteral 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 945 ASCIILiteral 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 957 ASCIILiteral 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 971 ASCIILiteral 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 983 ASCIILiteral 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 1001 ASCIILiteral 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 1027 ASCIILiteral 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 1039 ASCIILiteral 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 1054 JSValue 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 1070 JSObject* 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 1121 void IntlNumberFormat::setBoundFormat(VM& vm, JSBoundFunction* format) 1122 { 1123 m_boundFormat.set(vm, this, format); 1124 } 1125 1126 void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, Style style, bool sign, IntlMathematicalValue::NumberType numberType, const String& formatted, IntlFieldIterator& iterator, JSArray* parts, JSString* unit) 829 1127 { 830 1128 VM& vm = globalObject->vm(); … … 869 1167 while (currentIndex < stringLength && fields[currentIndex].type == fieldType) 870 1168 ++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)); 872 1170 auto partValue = jsSubstring(vm, formatted, startIndex, currentIndex - startIndex); 873 1171 JSObject* part = constructEmptyObject(globalObject); … … 895 1193 #if HAVE(ICU_U_NUMBER_FORMATTER) 896 1194 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)); 898 1196 if (U_FAILURE(status)) 899 1197 return throwTypeError(globalObject, scope, "Failed to format a number."_s); … … 916 1214 #endif 917 1215 918 auto resultString = String( result);1216 auto resultString = String(WTFMove(result)); 919 1217 920 1218 JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0); … … 922 1220 return throwOutOfMemoryError(globalObject, scope); 923 1221 924 formatToPartsInternal(globalObject, m_style, value, resultString, iterator, parts);1222 formatToPartsInternal(globalObject, m_style, std::signbit(value), IntlMathematicalValue::numberTypeFromDouble(value), resultString, iterator, parts); 925 1223 RETURN_IF_EXCEPTION(scope, { }); 926 1224 … … 928 1226 } 929 1227 1228 #if HAVE(ICU_U_NUMBER_FORMATTER) 1229 JSValue 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 930 1275 } // namespace JSC -
trunk/Source/JavaScriptCore/runtime/IntlNumberFormat.h
r278035 r285418 29 29 30 30 #include "JSObject.h" 31 #include "MathCommon.h" 32 #include "TemporalObject.h" 31 33 #include <unicode/unum.h> 32 34 #include <wtf/unicode/icu/ICUHelpers.h> … … 39 41 #endif 40 42 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 49 struct UNumberFormatter; 50 struct UNumberRangeFormatter; 44 51 45 52 namespace JSC { … … 49 56 enum class RelevantExtensionKey : uint8_t; 50 57 51 enum class IntlRoundingType : uint8_t { FractionDigits, SignificantDigits, CompactRounding }; 58 enum class IntlRoundingType : uint8_t { FractionDigits, SignificantDigits, MorePrecision, LessPrecision }; 59 enum class IntlRoundingPriority : uint8_t { Auto, MorePrecision, LessPrecision }; 52 60 enum class IntlNotation : uint8_t { Standard, Scientific, Engineering, Compact }; 53 61 template<typename IntlType> void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation); 62 template<typename IntlType> void appendNumberFormatDigitOptionsToSkeleton(IntlType*, StringBuilder&); 63 64 #if HAVE(ICU_U_NUMBER_FORMATTER) 65 struct UNumberFormatterDeleter { 66 JS_EXPORT_PRIVATE void operator()(UNumberFormatter*); 67 }; 68 #endif 69 70 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) 71 struct UNumberRangeFormatterDeleter { 72 JS_EXPORT_PRIVATE void operator()(UNumberRangeFormatter*); 73 }; 74 #endif 75 76 class IntlMathematicalValue { 77 WTF_MAKE_FAST_ALLOCATED(IntlMathematicalValue); 78 public: 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 142 private: 143 NumberType m_numberType { NumberType::Integer }; 144 bool m_sign { false }; 145 Value m_value { 0.0 }; 146 }; 54 147 55 148 class IntlNumberFormat final : public JSNonFinalObject { … … 77 170 void initializeNumberFormat(JSGlobalObject*, JSValue locales, JSValue optionsValue); 78 171 JSValue format(JSGlobalObject*, double) const; 79 JSValue format(JSGlobalObject*, JSBigInt*) const;172 JSValue format(JSGlobalObject*, IntlMathematicalValue&&) const; 80 173 JSValue formatToParts(JSGlobalObject*, double) const; 174 #if HAVE(ICU_U_NUMBER_FORMATTER) 175 JSValue formatToParts(JSGlobalObject*, IntlMathematicalValue&&) const; 176 #endif 81 177 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 82 183 83 184 JSBoundFunction* boundFormat() const { return m_boundFormat.get(); } … … 86 187 enum class Style : uint8_t { Decimal, Percent, Currency, Unit }; 87 188 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); 89 190 90 191 template<typename IntlType> 91 192 friend void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation); 193 template<typename IntlType> 194 friend void appendNumberFormatDigitOptionsToSkeleton(IntlType*, StringBuilder&); 92 195 93 196 static ASCIILiteral notationString(IntlNotation); 94 197 95 198 static IntlNumberFormat* unwrapForOldFunctions(JSGlobalObject*, JSValue); 199 200 static ASCIILiteral roundingPriorityString(IntlRoundingType); 96 201 97 202 private: … … 106 211 enum class UnitDisplay : uint8_t { Short, Narrow, Long }; 107 212 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 }; 109 216 110 217 static ASCIILiteral styleString(Style); … … 114 221 static ASCIILiteral compactDisplayString(CompactDisplay); 115 222 static ASCIILiteral signDisplayString(SignDisplay); 223 static ASCIILiteral roundingModeString(RoundingMode); 224 static ASCIILiteral trailingZeroDisplayString(TrailingZeroDisplay); 225 static JSValue useGroupingValue(VM&, UseGrouping); 116 226 117 227 WriteBarrier<JSBoundFunction> m_boundFormat; 118 228 #if HAVE(ICU_U_NUMBER_FORMATTER) 119 using UNumberFormatterDeleter = ICUDeleter<unumf_close>;120 using UFormattedNumberDeleter = ICUDeleter<unumf_closeResult>;121 229 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 122 233 #else 123 using UNumberFormatDeleter = ICUDeleter<unum_close>;124 234 std::unique_ptr<UNumberFormat, ICUDeleter<unum_close>> m_numberFormat; 125 235 #endif … … 134 244 unsigned m_minimumSignificantDigits { 0 }; 135 245 unsigned m_maximumSignificantDigits { 0 }; 246 unsigned m_roundingIncrement { 1 }; 136 247 Style m_style { Style::Decimal }; 137 248 CurrencyDisplay m_currencyDisplay; … … 141 252 IntlNotation m_notation { IntlNotation::Standard }; 142 253 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 }; 144 257 IntlRoundingType m_roundingType { IntlRoundingType::FractionDigits }; 145 258 }; -
trunk/Source/JavaScriptCore/runtime/IntlNumberFormatInlines.h
r278035 r285418 30 30 #include "IntlObjectInlines.h" 31 31 #include "JSGlobalObject.h" 32 #include "JSGlobalObjectFunctions.h" 32 33 33 34 namespace JSC { … … 62 63 intlInstance->m_minimumIntegerDigits = minimumIntegerDigits; 63 64 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 130 template<typename IntlType> 131 void 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 } 105 194 } 106 195 … … 129 218 } 130 219 220 // https://tc39.es/proposal-intl-numberformat-v3/out/numberformat/diff.html#sec-tointlmathematicalvalue 221 inline 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 131 273 } // namespace JSC -
trunk/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp
r281790 r285418 40 40 static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatFuncFormat); 41 41 42 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) 43 static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRange); 44 #endif 45 42 46 } 43 47 … … 56 60 */ 57 61 58 IntlNumberFormatPrototype* IntlNumberFormatPrototype::create(VM& vm, JSGlobalObject* , Structure* structure)62 IntlNumberFormatPrototype* IntlNumberFormatPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) 59 63 { 60 64 IntlNumberFormatPrototype* object = new (NotNull, allocateCell<IntlNumberFormatPrototype>(vm.heap)) IntlNumberFormatPrototype(vm, structure); 61 object->finishCreation(vm );65 object->finishCreation(vm, globalObject); 62 66 return object; 63 67 } … … 73 77 } 74 78 75 void IntlNumberFormatPrototype::finishCreation(VM& vm )79 void IntlNumberFormatPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) 76 80 { 77 81 Base::finishCreation(vm); 78 82 ASSERT(inherits(vm, info())); 79 83 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 80 88 } 81 89 … … 87 95 auto* numberFormat = jsCast<IntlNumberFormat*>(callFrame->thisValue()); 88 96 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)))); 109 104 } 110 105 … … 138 133 } 139 134 135 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) 136 JSC_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 140 167 JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatToParts, (JSGlobalObject* globalObject, CallFrame* callFrame)) 141 168 { … … 151 178 return JSValue::encode(throwTypeError(globalObject, scope, "Intl.NumberFormat.prototype.formatToParts called on value that's not a NumberFormat"_s)); 152 179 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 153 189 double value = callFrame->argument(0).toNumber(globalObject); 154 RETURN_IF_EXCEPTION(scope, encodedJSValue());190 RETURN_IF_EXCEPTION(scope, { }); 155 191 156 192 RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatToParts(globalObject, value))); 193 #endif 157 194 } 158 195 -
trunk/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.h
r260415 r285418 50 50 private: 51 51 IntlNumberFormatPrototype(VM&, Structure*); 52 void finishCreation(VM& );52 void finishCreation(VM&, JSGlobalObject*); 53 53 }; 54 54 -
trunk/Source/JavaScriptCore/runtime/IntlObjectInlines.h
r282257 r285418 152 152 } 153 153 154 template<typename ResultType> 155 ResultType 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 } 154 193 155 194 ALWAYS_INLINE bool canUseASCIIUCADUCETComparison(UChar character) -
trunk/Source/JavaScriptCore/runtime/IntlPluralRules.cpp
r278035 r285418 33 33 #include "ObjectConstructor.h" 34 34 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 35 47 namespace JSC { 48 49 void UPluralRulesDeleter::operator()(UPluralRules* pluralRules) 50 { 51 if (pluralRules) 52 uplrules_close(pluralRules); 53 } 36 54 37 55 const ClassInfo IntlPluralRules::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlPluralRules) }; … … 108 126 RETURN_IF_EXCEPTION(scope, void()); 109 127 128 auto locale = m_locale.utf8(); 110 129 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)); 112 156 if (U_FAILURE(status)) { 113 157 throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s); … … 126 170 unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits); 127 171 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)); 135 179 if (U_FAILURE(status)) { 136 180 throwTypeError(globalObject, scope, "failed to initialize PluralRules"_s); … … 160 204 options->putDirect(vm, vm.propertyNames->maximumSignificantDigits, jsNumber(m_maximumSignificantDigits)); 161 205 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)); 164 212 break; 165 213 } … … 184 232 } 185 233 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; 188 237 } 189 238 … … 200 249 201 250 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 202 265 Vector<UChar, 8> result(8); 203 266 auto length = uplrules_selectWithFormat(m_pluralRules.get(), value, m_numberFormat.get(), result.data(), result.size(), &status); … … 206 269 207 270 return jsString(vm, String(result.data(), length)); 208 } 271 #endif 272 } 273 274 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) 275 JSValue 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 209 303 210 304 } // namespace JSC -
trunk/Source/JavaScriptCore/runtime/IntlPluralRules.h
r278035 r285418 29 29 #include "IntlNumberFormat.h" 30 30 #include <unicode/unum.h> 31 #include <unicode/upluralrules.h>32 31 #include <wtf/unicode/icu/ICUHelpers.h> 33 32 33 struct UPluralRules; 34 34 35 namespace JSC { 36 37 struct UPluralRulesDeleter { 38 JS_EXPORT_PRIVATE void operator()(UPluralRules*); 39 }; 35 40 36 41 enum class RelevantExtensionKey : uint8_t; … … 60 65 template<typename IntlType> 61 66 friend void setNumberFormatDigitOptions(JSGlobalObject*, IntlType*, JSObject*, unsigned minimumFractionDigitsDefault, unsigned maximumFractionDigitsDefault, IntlNotation); 67 template<typename IntlType> 68 friend void appendNumberFormatDigitOptionsToSkeleton(IntlType*, StringBuilder&); 62 69 63 70 void initializePluralRules(JSGlobalObject*, JSValue locales, JSValue options); 64 71 JSValue select(JSGlobalObject*, double value) const; 65 72 JSObject* resolvedOptions(JSGlobalObject*) const; 73 74 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) 75 JSValue selectRange(JSGlobalObject*, double start, double end) const; 76 #endif 66 77 67 78 private: … … 74 85 enum class Type : bool { Cardinal, Ordinal }; 75 86 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 77 94 using UNumberFormatDeleter = ICUDeleter<unum_close>; 78 79 std::unique_ptr<UPluralRules, UPluralRulesDeleter> m_pluralRules;80 95 std::unique_ptr<UNumberFormat, UNumberFormatDeleter> m_numberFormat; 96 #endif 81 97 82 98 String m_locale; … … 86 102 unsigned m_minimumSignificantDigits { 0 }; 87 103 unsigned m_maximumSignificantDigits { 0 }; 104 unsigned m_roundingIncrement { 1 }; 88 105 IntlRoundingType m_roundingType { IntlRoundingType::FractionDigits }; 89 106 Type m_type { Type::Cardinal }; -
trunk/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.cpp
r281790 r285418 34 34 35 35 static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelect); 36 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) 37 static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncSelectRange); 38 #endif 36 39 static JSC_DECLARE_HOST_FUNCTION(intlPluralRulesPrototypeFuncResolvedOptions); 37 40 … … 51 54 */ 52 55 53 IntlPluralRulesPrototype* IntlPluralRulesPrototype::create(VM& vm, JSGlobalObject* , Structure* structure)56 IntlPluralRulesPrototype* IntlPluralRulesPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) 54 57 { 55 58 IntlPluralRulesPrototype* object = new (NotNull, allocateCell<IntlPluralRulesPrototype>(vm.heap)) IntlPluralRulesPrototype(vm, structure); 56 object->finishCreation(vm );59 object->finishCreation(vm, globalObject); 57 60 return object; 58 61 } … … 68 71 } 69 72 70 void IntlPluralRulesPrototype::finishCreation(VM& vm )73 void IntlPluralRulesPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) 71 74 { 72 75 Base::finishCreation(vm); 73 76 ASSERT(inherits(vm, info())); 74 77 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 75 82 } 76 83 … … 93 100 } 94 101 102 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) 103 JSC_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 95 123 JSC_DEFINE_HOST_FUNCTION(intlPluralRulesPrototypeFuncResolvedOptions, (JSGlobalObject* globalObject, CallFrame* callFrame)) 96 124 { -
trunk/Source/JavaScriptCore/runtime/IntlPluralRulesPrototype.h
r260415 r285418 50 50 private: 51 51 IntlPluralRulesPrototype(VM&, Structure*); 52 void finishCreation(VM& );52 void finishCreation(VM&, JSGlobalObject*); 53 53 }; 54 54 -
trunk/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp
r278253 r285418 319 319 320 320 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())); 322 322 RETURN_IF_EXCEPTION(scope, { }); 323 323 } -
trunk/Source/JavaScriptCore/runtime/JSBigInt.h
r285178 r285418 465 465 } 466 466 467 static std::optional<double> tryExtractDouble(JSValue); 468 467 469 private: 468 470 JSBigInt(VM&, Structure*, Digit*, unsigned length); … … 677 679 } 678 680 681 ALWAYS_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 679 719 } // namespace JSC -
trunk/Source/JavaScriptCore/runtime/MathCommon.h
r281910 r285418 64 64 } 65 65 66 inline bool isNegativeZero(double value) 67 { 68 return std::signbit(value) && value == 0; 69 } 70 66 71 // This in the ToInt32 operation is defined in section 9.5 of the ECMA-262 spec. 67 72 // Note that this operation is identical to ToUInt32 other than to interpretation -
trunk/Source/JavaScriptCore/runtime/TemporalObject.cpp
r285178 r285418 453 453 case RoundingMode::HalfExpand: 454 454 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; 455 463 } 456 464 -
trunk/Source/JavaScriptCore/runtime/TemporalObject.h
r285178 r285418 81 81 Ceil, 82 82 Floor, 83 Expand, 83 84 Trunc, 84 HalfExpand 85 HalfCeil, 86 HalfFloor, 87 HalfExpand, 88 HalfTrunc, 89 HalfEven 85 90 }; 86 91
Note: See TracChangeset
for help on using the changeset viewer.