/* * Copyright (c) 2021-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecmascript/js_number_format.h" #include "ecmascript/ecma_context.h" #include "ecmascript/js_function.h" #include "ecmascript/object_factory-inl.h" namespace panda::ecmascript { const std::vector JSNumberFormat::STYLE_OPTION = { StyleOption::DECIMAL, StyleOption::PERCENT, StyleOption::CURRENCY, StyleOption::UNIT }; const std::vector JSNumberFormat::STYLE_OPTION_NAME = { "decimal", "percent", "currency", "unit" }; const std::vector JSNumberFormat::CURRENCY_DISPLAY_OPTION = { CurrencyDisplayOption::CODE, CurrencyDisplayOption::SYMBOL, CurrencyDisplayOption::NARROWSYMBOL, CurrencyDisplayOption::NAME }; const std::vector JSNumberFormat::CURRENCY_DISPLAY_OPTION_NAME = { "code", "symbol", "narrowSymbol", "name" }; const std::vector JSNumberFormat::CURRENCY_SIGN_OPTION = { CurrencySignOption::STANDARD, CurrencySignOption::ACCOUNTING }; const std::vector JSNumberFormat::CURRENCY_SIGN_OPTION_NAME = {"standard", "accounting"}; const std::vector JSNumberFormat::UNIT_DISPLAY_OPTION = { UnitDisplayOption::SHORT, UnitDisplayOption::NARROW, UnitDisplayOption::LONG }; const std::vector JSNumberFormat::UNIT_DISPLAY_OPTION_NAME = {"short", "narrow", "long"}; const std::vector JSNumberFormat::LOCALE_MATCHER_OPTION = { LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT }; const std::vector JSNumberFormat::LOCALE_MATCHER_OPTION_NAME = {"lookup", "best fit"}; const std::vector JSNumberFormat::NOTATION_OPTION = { NotationOption::STANDARD, NotationOption::SCIENTIFIC, NotationOption::ENGINEERING, NotationOption::COMPACT }; const std::vector JSNumberFormat::NOTATION_OPTION_NAME = { "standard", "scientific", "engineering", "compact" }; const std::vector JSNumberFormat::SIGN_DISPLAY_OPTION = { SignDisplayOption::AUTO, SignDisplayOption::NEVER, SignDisplayOption::ALWAYS, SignDisplayOption::EXCEPTZERO }; const std::vector JSNumberFormat::SIGN_DISPLAY_OPTION_NAME = { "auto", "never", "always", "exceptZero" }; const std::vector JSNumberFormat::COMPACT_DISPLAY_OPTION = { CompactDisplayOption::SHORT, CompactDisplayOption::LONG }; const std::vector JSNumberFormat::COMPACT_DISPLAY_OPTION_NAME = {"short", "long"}; const std::set SANCTIONED_UNIT({ "acre", "bit", "byte", "celsius", "centimeter", "day", "degree", "fahrenheit", "fluid-ounce", "foot", "gallon", "gigabit", "gigabyte", "gram", "hectare", "hour", "inch", "kilobit", "kilobyte", "kilogram", "kilometer", "liter", "megabit", "megabyte", "meter", "mile", "mile-scandinavian", "millimeter", "milliliter", "millisecond", "minute", "month", "ounce", "percent", "petabyte", "pound", "second", "stone", "terabit", "terabyte", "week", "yard", "year" }); JSHandle OptionToEcmaString(JSThread *thread, StyleOption style) { JSMutableHandle result(thread, JSTaggedValue::Undefined()); auto globalConst = thread->GlobalConstants(); switch (style) { case StyleOption::DECIMAL: result.Update(globalConst->GetHandledDecimalString().GetTaggedValue()); break; case StyleOption::CURRENCY: result.Update(globalConst->GetHandledCurrencyString().GetTaggedValue()); break; case StyleOption::PERCENT: result.Update(globalConst->GetHandledPercentString().GetTaggedValue()); break; case StyleOption::UNIT: result.Update(globalConst->GetHandledUnitString().GetTaggedValue()); break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } return result; } JSHandle OptionToEcmaString(JSThread *thread, CurrencyDisplayOption currencyDisplay) { JSMutableHandle result(thread, JSTaggedValue::Undefined()); auto globalConst = thread->GlobalConstants(); switch (currencyDisplay) { case CurrencyDisplayOption::CODE: result.Update(globalConst->GetHandledCodeString().GetTaggedValue()); break; case CurrencyDisplayOption::SYMBOL: result.Update(globalConst->GetHandledSymbolString().GetTaggedValue()); break; case CurrencyDisplayOption::NARROWSYMBOL: result.Update(globalConst->GetHandledNarrowSymbolString().GetTaggedValue()); break; case CurrencyDisplayOption::NAME: result.Update(globalConst->GetHandledNameString().GetTaggedValue()); break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } return result; } JSHandle OptionToEcmaString(JSThread *thread, CurrencySignOption currencySign) { auto globalConst = thread->GlobalConstants(); JSMutableHandle result(thread, JSTaggedValue::Undefined()); switch (currencySign) { case CurrencySignOption::STANDARD: result.Update(globalConst->GetHandledStandardString().GetTaggedValue()); break; case CurrencySignOption::ACCOUNTING: result.Update(globalConst->GetHandledAccountingString().GetTaggedValue()); break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } return result; } JSHandle OptionToEcmaString(JSThread *thread, UnitDisplayOption unitDisplay) { JSMutableHandle result(thread, JSTaggedValue::Undefined()); auto globalConst = thread->GlobalConstants(); switch (unitDisplay) { case UnitDisplayOption::SHORT: result.Update(globalConst->GetHandledShortString().GetTaggedValue()); break; case UnitDisplayOption::NARROW: result.Update(globalConst->GetHandledNarrowString().GetTaggedValue()); break; case UnitDisplayOption::LONG: result.Update(globalConst->GetHandledLongString().GetTaggedValue()); break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } return result; } JSHandle OptionToEcmaString(JSThread *thread, NotationOption notation) { JSMutableHandle result(thread, JSTaggedValue::Undefined()); auto globalConst = thread->GlobalConstants(); switch (notation) { case NotationOption::STANDARD: result.Update(globalConst->GetHandledStandardString().GetTaggedValue()); break; case NotationOption::SCIENTIFIC: result.Update(globalConst->GetHandledScientificString().GetTaggedValue()); break; case NotationOption::ENGINEERING: result.Update(globalConst->GetHandledEngineeringString().GetTaggedValue()); break; case NotationOption::COMPACT: result.Update(globalConst->GetHandledCompactString().GetTaggedValue()); break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } return result; } JSHandle OptionToEcmaString(JSThread *thread, CompactDisplayOption compactDisplay) { JSMutableHandle result(thread, JSTaggedValue::Undefined()); auto globalConst = thread->GlobalConstants(); switch (compactDisplay) { case CompactDisplayOption::SHORT: result.Update(globalConst->GetHandledShortString().GetTaggedValue()); break; case CompactDisplayOption::LONG: result.Update(globalConst->GetHandledLongString().GetTaggedValue()); break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } return result; } JSHandle OptionToEcmaString(JSThread *thread, SignDisplayOption signDisplay) { JSMutableHandle result(thread, JSTaggedValue::Undefined()); auto globalConst = thread->GlobalConstants(); switch (signDisplay) { case SignDisplayOption::AUTO: result.Update(globalConst->GetHandledAutoString().GetTaggedValue()); break; case SignDisplayOption::ALWAYS: result.Update(globalConst->GetHandledAlwaysString().GetTaggedValue()); break; case SignDisplayOption::NEVER: result.Update(globalConst->GetHandledNeverString().GetTaggedValue()); break; case SignDisplayOption::EXCEPTZERO: result.Update(globalConst->GetHandledExceptZeroString().GetTaggedValue()); break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } return result; } icu::MeasureUnit ToMeasureUnit(const std::string &sanctionedUnit) { UErrorCode status = U_ZERO_ERROR; // Get All ICU measure unit int32_t total = icu::MeasureUnit::getAvailable(nullptr, 0, status); status = U_ZERO_ERROR; std::vector units(total); icu::MeasureUnit::getAvailable(units.data(), total, status); ASSERT(U_SUCCESS(status)); // Find measure unit according to sanctioned unit // then return measure unit for (auto &unit : units) { if (std::strcmp(sanctionedUnit.c_str(), unit.getSubtype()) == 0) { return unit; } } return icu::MeasureUnit(); } // ecma402 #sec-issanctionedsimpleunitidentifier bool IsSanctionedSimpleUnitIdentifier(const std::string &unit) { // 1. If unitIdentifier is listed in sanctioned unit set, return true. auto it = SANCTIONED_UNIT.find(unit); if (it != SANCTIONED_UNIT.end()) { return true; } // 2. Else, Return false. return false; } // 6.5.1 IsWellFormedUnitIdentifier ( unitIdentifier ) bool IsWellFormedUnitIdentifier(const std::string &unit, icu::MeasureUnit &icuUnit, icu::MeasureUnit &icuPerUnit) { // 1. If the result of IsSanctionedSimpleUnitIdentifier(unitIdentifier) is true, then // a. Return true. icu::MeasureUnit result = icu::MeasureUnit(); icu::MeasureUnit emptyUnit = icu::MeasureUnit(); auto pos = unit.find("-per-"); if (IsSanctionedSimpleUnitIdentifier(unit) && pos == std::string::npos) { result = ToMeasureUnit(unit); icuUnit = result; icuPerUnit = emptyUnit; return true; } // 2. If the substring "-per-" does not occur exactly once in unitIdentifier, // a. then false size_t afterPos = pos + JSNumberFormat::PERUNIT_STRING; if (pos == std::string::npos || unit.find("-per-", afterPos) != std::string::npos) { return false; } // 3. Let numerator be the substring of unitIdentifier from the beginning to just before "-per-". std::string numerator = unit.substr(0, pos); // 4. If the result of IsSanctionedUnitIdentifier(numerator) is false, then // a. return false if (IsSanctionedSimpleUnitIdentifier(numerator)) { result = ToMeasureUnit(numerator); } else { return false; } // 5. Let denominator be the substring of unitIdentifier from just after "-per-" to the end. std::string denominator = unit.substr(pos + JSNumberFormat::PERUNIT_STRING); // 6. If the result of IsSanctionedUnitIdentifier(denominator) is false, then // a. Return false icu::MeasureUnit perResult = icu::MeasureUnit(); if (IsSanctionedSimpleUnitIdentifier(denominator)) { perResult = ToMeasureUnit(denominator); } else { return false; } // 7. Return true. icuUnit = result; icuPerUnit = perResult; return true; } // 12.1.13 SetNumberFormatUnitOptions ( intlObj, options ) FractionDigitsOption SetNumberFormatUnitOptions(JSThread *thread, const JSHandle &numberFormat, const JSHandle &optionsObject, icu::number::LocalizedNumberFormatter *icuNumberFormatter) { auto globalConst = thread->GlobalConstants(); FractionDigitsOption fractionDigitsOption; // 3. Let style be ? GetOption(options, "style", "string", « "decimal", "percent", "currency", "unit" », "decimal"). JSHandle property = globalConst->GetHandledStyleString(); auto style = JSLocale::GetOptionOfString( thread, optionsObject, property, JSNumberFormat::STYLE_OPTION, JSNumberFormat::STYLE_OPTION_NAME, StyleOption::DECIMAL); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption); // 4. Set intlObj.[[Style]] to style. numberFormat->SetStyle(style); // 5. Let currency be ? GetOption(options, "currency", "string", undefined, undefined). property = globalConst->GetHandledCurrencyString(); JSHandle undefinedValue(thread, JSTaggedValue::Undefined()); JSHandle currency = JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption); // 6. If currency is not undefined, then // a. If the result of IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception. if (!currency->IsUndefined()) { JSHandle currencyStr = JSHandle::Cast(currency); if (EcmaStringAccessor(currencyStr).IsUtf16()) { THROW_RANGE_ERROR_AND_RETURN(thread, "not a utf-8", fractionDigitsOption); } std::string currencyCStr = intl::LocaleHelper::ConvertToStdString(currencyStr); if (!JSLocale::IsWellFormedCurrencyCode(currencyCStr)) { THROW_RANGE_ERROR_AND_RETURN(thread, "not a wellformed code", fractionDigitsOption); } } else { // 7. If style is "currency" and currency is undefined, throw a TypeError exception. if (style == StyleOption::CURRENCY) { THROW_TYPE_ERROR_AND_RETURN(thread, "style is currency but currency is undefined", fractionDigitsOption); } } // 8. Let currencyDisplay be ? GetOption(options, "currencyDisplay", "string", // « "code", "symbol", "narrowSymbol", "name" », "symbol"). property = globalConst->GetHandledCurrencyDisplayString(); auto currencyDisplay = JSLocale::GetOptionOfString( thread, optionsObject, property, JSNumberFormat::CURRENCY_DISPLAY_OPTION, JSNumberFormat::CURRENCY_DISPLAY_OPTION_NAME, CurrencyDisplayOption::SYMBOL); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption); numberFormat->SetCurrencyDisplay(currencyDisplay); // 9. Let currencySign be ? GetOption(options, "currencySign", "string", « "standard", "accounting" », "standard"). property = globalConst->GetHandledCurrencySignString(); auto currencySign = JSLocale::GetOptionOfString( thread, optionsObject, property, JSNumberFormat::CURRENCY_SIGN_OPTION, JSNumberFormat::CURRENCY_SIGN_OPTION_NAME, CurrencySignOption::STANDARD); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption); numberFormat->SetCurrencySign(currencySign); // 10. Let unit be ? GetOption(options, "unit", "string", undefined, undefined). property = globalConst->GetHandledUnitString(); JSHandle unit = JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption); numberFormat->SetUnit(thread, unit); // 11. If unit is not undefined, then // If the result of IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception. icu::MeasureUnit icuUnit; icu::MeasureUnit icuPerUnit; if (!unit->IsUndefined()) { JSHandle unitStr = JSHandle::Cast(unit); if (EcmaStringAccessor(unitStr).IsUtf16()) { THROW_RANGE_ERROR_AND_RETURN(thread, "Unit input is illegal", fractionDigitsOption); } std::string str = intl::LocaleHelper::ConvertToStdString(unitStr); if (!IsWellFormedUnitIdentifier(str, icuUnit, icuPerUnit)) { THROW_RANGE_ERROR_AND_RETURN(thread, "Unit input is illegal", fractionDigitsOption); } } else { // 15.12. if style is "unit" and unit is undefined, throw a TypeError exception. if (style == StyleOption::UNIT) { THROW_TYPE_ERROR_AND_RETURN(thread, "style is unit but unit is undefined", fractionDigitsOption); } } // 13. Let unitDisplay be ? GetOption(options, "unitDisplay", "string", « "short", "narrow", "long" », "short"). property = globalConst->GetHandledUnitDisplayString(); auto unitDisplay = JSLocale::GetOptionOfString( thread, optionsObject, property, JSNumberFormat::UNIT_DISPLAY_OPTION, JSNumberFormat::UNIT_DISPLAY_OPTION_NAME, UnitDisplayOption::SHORT); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption); numberFormat->SetUnitDisplay(unitDisplay); // 14. If style is "currency", then // a. Let currency be the result of converting currency to upper case as specified in 6.1. // b. Set intlObj.[[Currency]] to currency. // c. Set intlObj.[[CurrencyDisplay]] to currencyDisplay. // d. Set intlObj.[[CurrencySign]] to currencySign. icu::UnicodeString currencyUStr; UErrorCode status = U_ZERO_ERROR; if (style == StyleOption::CURRENCY) { JSHandle currencyStr = JSHandle::Cast(currency); std::string currencyCStr = intl::LocaleHelper::ConvertToStdString(currencyStr); std::transform(currencyCStr.begin(), currencyCStr.end(), currencyCStr.begin(), toupper); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle currencyValue = JSHandle::Cast(factory->NewFromStdString(currencyCStr)); numberFormat->SetCurrency(thread, currencyValue); currencyUStr = currencyCStr.c_str(); if (!currencyUStr.isEmpty()) { // NOLINT(readability-implicit-bool-conversion) *icuNumberFormatter = icuNumberFormatter->unit(icu::CurrencyUnit(currencyUStr.getBuffer(), status)); ASSERT(U_SUCCESS(status)); UNumberUnitWidth uNumberUnitWidth; // Trans currencyDisplayOption to ICU format number display option switch (currencyDisplay) { case CurrencyDisplayOption::CODE: uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE; break; case CurrencyDisplayOption::SYMBOL: uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT; break; case CurrencyDisplayOption::NARROWSYMBOL: uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW; break; case CurrencyDisplayOption::NAME: uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME; break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } *icuNumberFormatter = icuNumberFormatter->unitWidth(uNumberUnitWidth); } } // 15. If style is "unit", then // if unit is not undefiend set unit to LocalizedNumberFormatter // then if perunit is not undefiend set perunit to LocalizedNumberFormatter if (style == StyleOption::UNIT) { icu::MeasureUnit emptyUnit = icu::MeasureUnit(); if (icuUnit != emptyUnit) { // NOLINT(readability-implicit-bool-conversion) *icuNumberFormatter = icuNumberFormatter->unit(icuUnit); } if (icuPerUnit != emptyUnit) { // NOLINT(readability-implicit-bool-conversion) *icuNumberFormatter = icuNumberFormatter->perUnit(icuPerUnit); } } // 17. If style is "currency", then // a. Let cDigits be CurrencyDigits(currency). // b. Let mnfdDefault be cDigits. // c. Let mxfdDefault be cDigits. if (style == StyleOption::CURRENCY) { int32_t cDigits = JSNumberFormat::CurrencyDigits(currencyUStr); fractionDigitsOption.mnfdDefault = cDigits; fractionDigitsOption.mxfdDefault = cDigits; } else { // 18. Else, // a. Let mnfdDefault be 0. // b. If style is "percent", then // i. Let mxfdDefault be 0. // c. else, // i. Let mxfdDefault be 3. fractionDigitsOption.mnfdDefault = 0; if (style == StyleOption::PERCENT) { fractionDigitsOption.mxfdDefault = 0; } else { fractionDigitsOption.mxfdDefault = 3; // Max decimal precision is 3 } } return fractionDigitsOption; } // 12.1.2 InitializeNumberFormat ( numberFormat, locales, options ) // NOLINTNEXTLINE(readability-function-size) void JSNumberFormat::InitializeNumberFormat(JSThread *thread, const JSHandle &numberFormat, const JSHandle &locales, const JSHandle &options, bool forIcuCache) { EcmaVM *ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). JSHandle requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales); RETURN_IF_ABRUPT_COMPLETION(thread); // 2. If options is undefined, then // a. Let options be ObjectCreate(null). // 3. Else, // a. Let options be ? ToObject(options). JSHandle optionsObject; if (options->IsUndefined()) { optionsObject = factory->CreateNullJSObject(); } else { optionsObject = JSTaggedValue::ToObject(thread, options); RETURN_IF_ABRUPT_COMPLETION(thread); } auto globalConst = thread->GlobalConstants(); // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit"). JSHandle property = globalConst->GetHandledLocaleMatcherString(); auto matcher = JSLocale::GetOptionOfString( thread, optionsObject, property, JSNumberFormat::LOCALE_MATCHER_OPTION, JSNumberFormat::LOCALE_MATCHER_OPTION_NAME, LocaleMatcherOption::BEST_FIT); RETURN_IF_ABRUPT_COMPLETION(thread); // 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined). property = globalConst->GetHandledNumberingSystemString(); JSHandle undefinedValue(thread, JSTaggedValue::Undefined()); JSHandle numberingSystemTaggedValue = JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue); RETURN_IF_ABRUPT_COMPLETION(thread); numberFormat->SetNumberingSystem(thread, numberingSystemTaggedValue); // 8. If numberingSystem is not undefined, then // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, // throw a RangeError exception. `(3*8alphanum) *("-" (3*8alphanum))` std::string numberingSystemStr; if (!numberingSystemTaggedValue->IsUndefined()) { JSHandle numberingSystemEcmaString = JSHandle::Cast(numberingSystemTaggedValue); if (EcmaStringAccessor(numberingSystemEcmaString).IsUtf16()) { THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem"); } numberingSystemStr = intl::LocaleHelper::ConvertToStdString(numberingSystemEcmaString); if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStr)) { THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem"); } } if (!numberingSystemStr.empty()) { // If numberingSystem is invalid, Let numberingSystem be undefined. if (!JSLocale::IsWellNumberingSystem(numberingSystemStr)) { numberFormat->SetNumberingSystem(thread, undefinedValue); } } // 10. Let localeData be %NumberFormat%.[[LocaleData]]. JSHandle availableLocales; if (requestedLocales->GetLength() == 0) { availableLocales = factory->EmptyArray(); } else { availableLocales = GetAvailableLocales(thread); } // 11. Let r be ResolveLocale( %NumberFormat%.[[AvailableLocales]], requestedLocales, opt, // %NumberFormat%.[[RelevantExtensionKeys]], localeData). std::set relevantExtensionKeys{"nu"}; ResolvedLocale r = JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys); // 12. Set numberFormat.[[Locale]] to r.[[locale]]. icu::Locale icuLocale = r.localeData; JSHandle localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale); RETURN_IF_ABRUPT_COMPLETION(thread); numberFormat->SetLocale(thread, localeStr.GetTaggedValue()); // Set numberingSystemStr to UnicodeKeyWord "nu" UErrorCode status = U_ZERO_ERROR; if (!numberingSystemStr.empty()) { if (JSLocale::IsWellNumberingSystem(numberingSystemStr)) { icuLocale.setUnicodeKeywordValue("nu", numberingSystemStr, status); ASSERT(U_SUCCESS(status)); } } icu::number::LocalizedNumberFormatter icuNumberFormatter = icu::number::NumberFormatter::withLocale(icuLocale).roundingMode(UNUM_ROUND_HALFUP); int32_t mnfdDefault = 0; int32_t mxfdDefault = 0; FractionDigitsOption fractionOptions = SetNumberFormatUnitOptions(thread, numberFormat, optionsObject, &icuNumberFormatter); RETURN_IF_ABRUPT_COMPLETION(thread); mnfdDefault = fractionOptions.mnfdDefault; mxfdDefault = fractionOptions.mxfdDefault; UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay(); // Trans unitDisplay option to ICU display option UNumberUnitWidth uNumberUnitWidth; switch (unitDisplay) { case UnitDisplayOption::SHORT: uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT; break; case UnitDisplayOption::NARROW: uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW; break; case UnitDisplayOption::LONG: // UNUM_UNIT_WIDTH_FULL_NAME Print the full name of the unit, without any abbreviations. uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME; break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } icuNumberFormatter = icuNumberFormatter.unitWidth(uNumberUnitWidth); // 16. Let style be numberFormat.[[Style]]. StyleOption style = numberFormat->GetStyle(); if (style == StyleOption::PERCENT) { icuNumberFormatter = icuNumberFormatter.unit(icu::MeasureUnit::getPercent()). scale(icu::number::Scale::powerOfTen(2)); // means 10^2 } // 19. Let notation be ? GetOption( // options, "notation", "string", « "standard", "scientific", "engineering", "compact" », "standard"). property = globalConst->GetHandledNotationString(); auto notation = JSLocale::GetOptionOfString( thread, optionsObject, property, JSNumberFormat::NOTATION_OPTION, JSNumberFormat::NOTATION_OPTION_NAME, NotationOption::STANDARD); RETURN_IF_ABRUPT_COMPLETION(thread); numberFormat->SetNotation(notation); // 21. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation). JSLocale::SetNumberFormatDigitOptions(thread, numberFormat, JSHandle::Cast(optionsObject), mnfdDefault, mxfdDefault, notation); RETURN_IF_ABRUPT_COMPLETION(thread); icuNumberFormatter = SetICUFormatterDigitOptions(icuNumberFormatter, numberFormat); // 22. Let compactDisplay be ? GetOptionOfString(options, "compactDisplay", "string", « "short", "long" », "short"). property = globalConst->GetHandledCompactDisplayString(); auto compactDisplay = JSLocale::GetOptionOfString( thread, optionsObject, property, JSNumberFormat::COMPACT_DISPLAY_OPTION, JSNumberFormat::COMPACT_DISPLAY_OPTION_NAME, CompactDisplayOption::SHORT); numberFormat->SetCompactDisplay(compactDisplay); // Trans NotationOption to ICU Noation and set to icuNumberFormatter if (notation == NotationOption::COMPACT) { switch (compactDisplay) { case CompactDisplayOption::SHORT: icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactShort()); break; case CompactDisplayOption::LONG: icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactLong()); break; default: break; } } switch (notation) { case NotationOption::STANDARD: icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::simple()); break; case NotationOption::SCIENTIFIC: icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::scientific()); break; case NotationOption::ENGINEERING: icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::engineering()); break; default: break; } // 24. Let useGrouping be ? GetOption(options, "useGrouping", "boolean", undefined, true). property = globalConst->GetHandledUserGroupingString(); bool useGrouping = false; [[maybe_unused]] bool find = JSLocale::GetOptionOfBool(thread, optionsObject, property, true, &useGrouping); RETURN_IF_ABRUPT_COMPLETION(thread); JSHandle useGroupingValue(thread, JSTaggedValue(useGrouping)); numberFormat->SetUseGrouping(thread, useGroupingValue); // 25. Set numberFormat.[[UseGrouping]] to useGrouping. if (!useGrouping) { icuNumberFormatter = icuNumberFormatter.grouping(UNumberGroupingStrategy::UNUM_GROUPING_OFF); } // 26. Let signDisplay be ? // GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero" », "auto"). property = globalConst->GetHandledSignDisplayString(); auto signDisplay = JSLocale::GetOptionOfString( thread, optionsObject, property, JSNumberFormat::SIGN_DISPLAY_OPTION, JSNumberFormat::SIGN_DISPLAY_OPTION_NAME, SignDisplayOption::AUTO); RETURN_IF_ABRUPT_COMPLETION(thread); numberFormat->SetSignDisplay(signDisplay); // 27. Set numberFormat.[[SignDisplay]] to signDisplay. // The default sign in ICU is UNUM_SIGN_AUTO which is mapped from // SignDisplay::AUTO and CurrencySign::STANDARD so we can skip setting // under that values for optimization. CurrencySignOption currencySign = numberFormat->GetCurrencySign(); // Trans SignDisPlayOption to ICU UNumberSignDisplay and set to icuNumberFormatter switch (signDisplay) { case SignDisplayOption::AUTO: // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers if (currencySign == CurrencySignOption::ACCOUNTING) { icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING); } else { icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_AUTO); } break; case SignDisplayOption::NEVER: icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_NEVER); break; case SignDisplayOption::ALWAYS: // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers if (currencySign == CurrencySignOption::ACCOUNTING) { icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS); } else { icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS); } break; case SignDisplayOption::EXCEPTZERO: // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers if (currencySign == CurrencySignOption::ACCOUNTING) { icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO); } else { icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO); } break; default: break; } if (forIcuCache) { std::string cacheEntry = locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString(); auto formatterPointer = new icu::number::LocalizedNumberFormatter(icuNumberFormatter); ecmaVm->GetIntlCache().SetIcuFormatterToCache(IcuFormatterType::NUMBER_FORMATTER, cacheEntry, formatterPointer, JSNumberFormat::FreeIcuNumberformat); } else { // Set numberFormat.[[IcuNumberForma]] to handleNumberFormatter factory->NewJSIntlIcuData(numberFormat, icuNumberFormatter, JSNumberFormat::FreeIcuNumberformat); } // Set numberFormat.[[BoundFormat]] to undefinedValue numberFormat->SetBoundFormat(thread, undefinedValue); } // 12.1.3 CurrencyDigits ( currency ) int32_t JSNumberFormat::CurrencyDigits(const icu::UnicodeString ¤cy) { UErrorCode status = U_ZERO_ERROR; // If the ISO 4217 currency and funds code list contains currency as an alphabetic code, // return the minor unit value corresponding to the currency from the list; otherwise, return 2. int32_t fractionDigits = ucurr_getDefaultFractionDigits(reinterpret_cast(currency.getBuffer()), &status); if (U_SUCCESS(status)) { return fractionDigits; } return JSNumberFormat::DEFAULT_FRACTION_DIGITS; } icu::number::LocalizedNumberFormatter *JSNumberFormat::GetCachedIcuNumberFormatter(JSThread *thread, const JSHandle &locales) { std::string cacheEntry = locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString(); void *cachedNumberFormatter = thread->GetEcmaVM()->GetIntlCache().GetIcuFormatterFromCache( IcuFormatterType::NUMBER_FORMATTER, cacheEntry); if (cachedNumberFormatter) { return reinterpret_cast(cachedNumberFormatter); } return nullptr; } // 12.1.8 FormatNumeric( numberFormat, x ) JSHandle JSNumberFormat::FormatNumeric(JSThread *thread, const JSHandle &numberFormat, JSTaggedValue x) { icu::number::LocalizedNumberFormatter *icuNumberFormat = numberFormat->GetIcuCallTarget(); ASSERT(icuNumberFormat != nullptr); JSHandle res = FormatNumeric(thread, icuNumberFormat, x); return res; } JSHandle JSNumberFormat::FormatNumeric(JSThread *thread, const icu::number::LocalizedNumberFormatter *icuNumberFormat, JSTaggedValue x) { UErrorCode status = U_ZERO_ERROR; icu::number::FormattedNumber formattedNumber; if (x.IsBigInt()) { JSHandle bigint(thread, x); JSHandle bigintStr = BigInt::ToString(thread, bigint); std::string stdString = EcmaStringAccessor(bigintStr).ToStdString(); formattedNumber = icuNumberFormat->formatDecimal(icu::StringPiece(stdString), status); } else { double number = x.GetNumber(); formattedNumber = icuNumberFormat->formatDouble(number, status); } if (U_FAILURE(status)) { JSHandle errorResult(thread, JSTaggedValue::Exception()); THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatter format failed", errorResult); } icu::UnicodeString result = formattedNumber.toString(status); if (U_FAILURE(status)) { JSHandle errorResult(thread, JSTaggedValue::Exception()); THROW_RANGE_ERROR_AND_RETURN(thread, "formatted number toString failed", errorResult); } JSHandle stringValue = intl::LocaleHelper::UStringToString(thread, result); return JSHandle::Cast(stringValue); } void GroupToParts(JSThread *thread, const icu::number::FormattedNumber &formatted, const JSHandle &receiver, const JSHandle &numberFormat, JSTaggedValue x) { UErrorCode status = U_ZERO_ERROR; icu::UnicodeString formattedText = formatted.toString(status); if (U_FAILURE(status)) { // NOLINT(readability-implicit-bool-conversion) THROW_TYPE_ERROR(thread, "formattedNumber toString failed"); } ASSERT(x.IsNumber() || x.IsBigInt()); StyleOption styleOption = numberFormat->GetStyle(); icu::ConstrainedFieldPosition cfpo; // Set constrainCategory to UFIELD_CATEGORY_NUMBER which is specified for UNumberFormatFields cfpo.constrainCategory(UFIELD_CATEGORY_NUMBER); auto globalConst = thread->GlobalConstants(); JSMutableHandle typeString(thread, JSTaggedValue::Undefined()); int index = 0; int previousLimit = 0; /** * From ICU header file document @unumberformatter.h * Sets a constraint on the field category. * * When this instance of ConstrainedFieldPosition is passed to FormattedValue#nextPosition, * positions are skipped unless they have the given category. * * Any previously set constraints are cleared. * * For example, to loop over only the number-related fields: * * ConstrainedFieldPosition cfpo; * cfpo.constrainCategory(UFIELDCATEGORY_NUMBER_FORMAT); * while (fmtval.nextPosition(cfpo, status)) { * // handle the number-related field position * } */ bool lastFieldGroup = false; int groupLeapLength = 0; while (formatted.nextPosition(cfpo, status)) { int32_t fieldId = cfpo.getField(); int32_t start = cfpo.getStart(); int32_t limit = cfpo.getLimit(); typeString.Update(globalConst->GetLiteralString()); // If start greater than previousLimit, means a literal type exists before number fields // so add a literal type with value of formattedText.sub(0, start) // Special case when fieldId is UNUM_GROUPING_SEPARATOR_FIELD if (static_cast(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) { JSHandle substring = intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start); typeString.Update(globalConst->GetIntegerString()); JSLocale::PutElement(thread, index, receiver, typeString, JSHandle::Cast(substring)); RETURN_IF_ABRUPT_COMPLETION(thread); index++; { typeString.Update(JSLocale::GetNumberFieldType(thread, x, fieldId).GetTaggedValue()); substring = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit); JSLocale::PutElement(thread, index, receiver, typeString, JSHandle::Cast(substring)); RETURN_IF_ABRUPT_COMPLETION(thread); index++; } lastFieldGroup = true; groupLeapLength = start - previousLimit + 1; previousLimit = limit; continue; } else if (start > previousLimit) { JSHandle substring = intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start); JSLocale::PutElement(thread, index, receiver, typeString, JSHandle::Cast(substring)); RETURN_IF_ABRUPT_COMPLETION(thread); index++; } if (lastFieldGroup) { start = start + groupLeapLength; lastFieldGroup = false; } // Special case in ICU when style is unit and unit is percent if (styleOption == StyleOption::UNIT && static_cast(fieldId) == UNUM_PERCENT_FIELD) { typeString.Update(globalConst->GetUnitString()); } else { typeString.Update(JSLocale::GetNumberFieldType(thread, x, fieldId).GetTaggedValue()); } JSHandle substring = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit); JSLocale::PutElement(thread, index, receiver, typeString, JSHandle::Cast(substring)); RETURN_IF_ABRUPT_COMPLETION(thread); index++; previousLimit = limit; } // If iterated length is smaller than formattedText.length, means a literal type exists after number fields // so add a literal type with value of formattedText.sub(previousLimit, formattedText.length) if (formattedText.length() > previousLimit) { typeString.Update(globalConst->GetLiteralString()); JSHandle substring = intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, formattedText.length()); JSLocale::PutElement(thread, index, receiver, typeString, JSHandle::Cast(substring)); RETURN_IF_ABRUPT_COMPLETION(thread); } } // 12.1.9 FormatNumericToParts( numberFormat, x ) JSHandle JSNumberFormat::FormatNumericToParts(JSThread *thread, const JSHandle &numberFormat, JSTaggedValue x) { ASSERT(x.IsNumber() || x.IsBigInt()); icu::number::LocalizedNumberFormatter *icuNumberFormatter = numberFormat->GetIcuCallTarget(); ASSERT(icuNumberFormatter != nullptr); UErrorCode status = U_ZERO_ERROR; icu::number::FormattedNumber formattedNumber; if (x.IsBigInt()) { JSHandle bigint(thread, x); JSHandle bigintStr = BigInt::ToString(thread, bigint); std::string stdString = EcmaStringAccessor(bigintStr).ToStdString(); formattedNumber = icuNumberFormatter->formatDecimal(icu::StringPiece(stdString), status); } else { double number = x.GetNumber(); formattedNumber = icuNumberFormatter->formatDouble(number, status); } if (U_FAILURE(status)) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle emptyArray = factory->NewJSArray(); THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatter format failed", emptyArray); } JSHandle arr = JSArray::ArrayCreate(thread, JSTaggedNumber(0)); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); JSHandle result = JSHandle::Cast(arr); GroupToParts(thread, formattedNumber, result, numberFormat, x); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); return result; } // 12.1.12 UnwrapNumberFormat( nf ) JSHandle JSNumberFormat::UnwrapNumberFormat(JSThread *thread, const JSHandle &nf) { // 1. Assert: Type(nf) is Object. ASSERT(nf->IsJSObject()); // 2. If nf does not have an [[InitializedNumberFormat]] internal slot and ? // InstanceofOperator(nf, %NumberFormat%) is true, then Let nf be ? Get(nf, %Intl%.[[FallbackSymbol]]). JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); bool hasInstance = JSFunction::OrdinaryHasInstance(thread, env->GetNumberFormatFunction(), nf); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSHandle(thread, JSTaggedValue::Undefined())); bool isJSNumberFormat = nf->IsJSNumberFormat(); // If nf does not have an [[InitializedNumberFormat]] internal slot and ? // InstanceofOperator(nf, %NumberFormat%) is true, then // a. Let nf be ? Get(nf, %Intl%.[[FallbackSymbol]]). if (!isJSNumberFormat && hasInstance) { JSHandle key(thread, JSHandle::Cast(env->GetIntlFunction())->GetFallbackSymbol()); OperationResult operationResult = JSTaggedValue::GetProperty(thread, nf, key); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSHandle(thread, JSTaggedValue::Undefined())); return operationResult.GetValue(); } // 3. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]). if (!isJSNumberFormat) { THROW_TYPE_ERROR_AND_RETURN(thread, "this is not object", JSHandle(thread, JSTaggedValue::Exception())); } return nf; } JSHandle JSNumberFormat::GetAvailableLocales(JSThread *thread) { JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); JSHandle numberFormatLocales = env->GetNumberFormatLocales(); if (!numberFormatLocales->IsUndefined()) { return JSHandle::Cast(numberFormatLocales); } const char *key = "NumberElements"; const char *path = nullptr; std::vector availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, key, path); JSHandle availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales); env->SetNumberFormatLocales(thread, availableLocales); return availableLocales; } void JSNumberFormat::ResolvedOptions(JSThread *thread, const JSHandle &numberFormat, const JSHandle &options) { // Table 5: Resolved Options of NumberFormat Instances // Internal Slot Property // [[Locale]] "locale" // [[NumberingSystem]] "numberingSystem" // [[Style]] "style" // [[Currency]] "currency" // [[CurrencyDisplay]] "currencyDisplay" // [[CurrencySign]] "currencySign" // [[Unit]] "unit" // [[UnitDisplay]] "unitDisplay" // [[MinimumIntegerDigits]] "minimumIntegerDigits" // [[MinimumFractionDigits]] "minimumFractionDigits" // [[MaximumFractionDigits]] "maximumFractionDigits" // [[MinimumSignificantDigits]] "minimumSignificantDigits" // [[MaximumSignificantDigits]] "maximumSignificantDigits" // [[UseGrouping]] "useGrouping" // [[Notation]] "notation" // [[CompactDisplay]] "compactDisplay" // [SignDisplay]] "signDisplay" // [[Locale]] auto globalConst = thread->GlobalConstants(); JSHandle property = globalConst->GetHandledLocaleString(); JSHandle locale(thread, numberFormat->GetLocale()); JSObject::CreateDataPropertyOrThrow(thread, options, property, locale); RETURN_IF_ABRUPT_COMPLETION(thread); // [[NumberingSystem]] JSHandle numberingSystem(thread, numberFormat->GetNumberingSystem()); if (numberingSystem->IsUndefined()) { numberingSystem = globalConst->GetHandledLatnString(); } property = globalConst->GetHandledNumberingSystemString(); JSObject::CreateDataPropertyOrThrow(thread, options, property, numberingSystem); RETURN_IF_ABRUPT_COMPLETION(thread); // [[Style]] StyleOption style = numberFormat->GetStyle(); property = globalConst->GetHandledStyleString(); JSHandle styleString = OptionToEcmaString(thread, style); JSObject::CreateDataPropertyOrThrow(thread, options, property, styleString); RETURN_IF_ABRUPT_COMPLETION(thread); // [[currency]] JSHandle currency(thread, JSTaggedValue::Undefined()); // If style is not currency the currency should be undefined if (style == StyleOption::CURRENCY) { currency = JSHandle(thread, numberFormat->GetCurrency()); } if (!currency->IsUndefined()) { // NOLINT(readability-implicit-bool-conversion) property = globalConst->GetHandledCurrencyString(); JSObject::CreateDataPropertyOrThrow(thread, options, property, currency); RETURN_IF_ABRUPT_COMPLETION(thread); // [[CurrencyDisplay]] property = globalConst->GetHandledCurrencyDisplayString(); CurrencyDisplayOption currencyDisplay = numberFormat->GetCurrencyDisplay(); JSHandle currencyDisplayString = OptionToEcmaString(thread, currencyDisplay); JSObject::CreateDataPropertyOrThrow(thread, options, property, currencyDisplayString); RETURN_IF_ABRUPT_COMPLETION(thread); // [[CurrencySign]] property = globalConst->GetHandledCurrencySignString(); CurrencySignOption currencySign = numberFormat->GetCurrencySign(); JSHandle currencySignString = OptionToEcmaString(thread, currencySign); JSObject::CreateDataPropertyOrThrow(thread, options, property, currencySignString); RETURN_IF_ABRUPT_COMPLETION(thread); } if (style == StyleOption::UNIT) { JSHandle unit(thread, numberFormat->GetUnit()); if (!unit->IsUndefined()) { // [[Unit]] property = globalConst->GetHandledUnitString(); JSObject::CreateDataPropertyOrThrow(thread, options, property, unit); RETURN_IF_ABRUPT_COMPLETION(thread); } // [[UnitDisplay]] property = globalConst->GetHandledUnitDisplayString(); UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay(); JSHandle unitDisplayString = OptionToEcmaString(thread, unitDisplay); JSObject::CreateDataPropertyOrThrow(thread, options, property, unitDisplayString); RETURN_IF_ABRUPT_COMPLETION(thread); } // [[MinimumIntegerDigits]] property = globalConst->GetHandledMinimumIntegerDigitsString(); JSHandle minimumIntegerDigits(thread, numberFormat->GetMinimumIntegerDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumIntegerDigits); RETURN_IF_ABRUPT_COMPLETION(thread); RoundingType roundingType = numberFormat->GetRoundingType(); if (roundingType == RoundingType::SIGNIFICANTDIGITS) { // [[MinimumSignificantDigits]] property = globalConst->GetHandledMinimumSignificantDigitsString(); JSHandle minimumSignificantDigits(thread, numberFormat->GetMinimumSignificantDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumSignificantDigits); RETURN_IF_ABRUPT_COMPLETION(thread); // [[MaximumSignificantDigits]] property = globalConst->GetHandledMaximumSignificantDigitsString(); JSHandle maximumSignificantDigits(thread, numberFormat->GetMaximumSignificantDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumSignificantDigits); RETURN_IF_ABRUPT_COMPLETION(thread); } else { // [[MinimumFractionDigits]] property = globalConst->GetHandledMinimumFractionDigitsString(); JSHandle minimumFractionDigits(thread, numberFormat->GetMinimumFractionDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumFractionDigits); RETURN_IF_ABRUPT_COMPLETION(thread); // [[MaximumFractionDigits]] property = globalConst->GetHandledMaximumFractionDigitsString(); JSHandle maximumFractionDigits(thread, numberFormat->GetMaximumFractionDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumFractionDigits); RETURN_IF_ABRUPT_COMPLETION(thread); // in v3, should contain BOTH significant and fraction digits if (roundingType == RoundingType::COMPACTROUNDING) { // [[MinimumSignificantDigits]] property = globalConst->GetHandledMinimumSignificantDigitsString(); JSHandle minimumSignificantDigits(thread, numberFormat->GetMinimumSignificantDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumSignificantDigits); RETURN_IF_ABRUPT_COMPLETION(thread); // [[MaximumSignificantDigits]] property = globalConst->GetHandledMaximumSignificantDigitsString(); JSHandle maximumSignificantDigits(thread, numberFormat->GetMaximumSignificantDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumSignificantDigits); RETURN_IF_ABRUPT_COMPLETION(thread); } } // [[UseGrouping]] property = globalConst->GetHandledUserGroupingString(); JSObject::CreateDataPropertyOrThrow(thread, options, property, JSHandle(thread, numberFormat->GetUseGrouping())); RETURN_IF_ABRUPT_COMPLETION(thread); // [[Notation]] property = globalConst->GetHandledNotationString(); NotationOption notation = numberFormat->GetNotation(); JSHandle notationString = OptionToEcmaString(thread, notation); JSObject::CreateDataPropertyOrThrow(thread, options, property, notationString); RETURN_IF_ABRUPT_COMPLETION(thread); // Only output compactDisplay when notation is compact. if (notation == NotationOption::COMPACT) { // [[CompactDisplay]] property = globalConst->GetHandledCompactDisplayString(); CompactDisplayOption compactDisplay = numberFormat->GetCompactDisplay(); JSHandle compactDisplayString = OptionToEcmaString(thread, compactDisplay); JSObject::CreateDataPropertyOrThrow(thread, options, property, compactDisplayString); RETURN_IF_ABRUPT_COMPLETION(thread); } // [[SignDisplay]] property = globalConst->GetHandledSignDisplayString(); SignDisplayOption signDisplay = numberFormat->GetSignDisplay(); JSHandle signDisplayString = OptionToEcmaString(thread, signDisplay); JSObject::CreateDataPropertyOrThrow(thread, options, property, signDisplayString); RETURN_IF_ABRUPT_COMPLETION(thread); } } // namespace panda::ecmascript