• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "ecmascript/js_number_format.h"
17 
18 namespace panda::ecmascript {
19 constexpr uint32_t DEFAULT_FRACTION_DIGITS = 2;
20 constexpr uint32_t PERUNIT_STRING = 5;
21 
OptionToEcmaString(JSThread * thread,StyleOption style)22 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, StyleOption style)
23 {
24     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
25     auto globalConst = thread->GlobalConstants();
26     switch (style) {
27         case StyleOption::DECIMAL:
28             result.Update(globalConst->GetHandledDecimalString().GetTaggedValue());
29             break;
30         case StyleOption::CURRENCY:
31             result.Update(globalConst->GetHandledCurrencyString().GetTaggedValue());
32             break;
33         case StyleOption::PERCENT:
34             result.Update(globalConst->GetHandledPercentString().GetTaggedValue());
35             break;
36         case StyleOption::UNIT:
37             result.Update(globalConst->GetHandledUnitString().GetTaggedValue());
38             break;
39         default:
40             LOG_ECMA(FATAL) << "this branch is unreachable";
41             UNREACHABLE();
42     }
43     return result;
44 }
45 
OptionToEcmaString(JSThread * thread,CurrencyDisplayOption currencyDisplay)46 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CurrencyDisplayOption currencyDisplay)
47 {
48     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
49     auto globalConst = thread->GlobalConstants();
50     switch (currencyDisplay) {
51         case CurrencyDisplayOption::CODE:
52             result.Update(globalConst->GetHandledCodeString().GetTaggedValue());
53             break;
54         case CurrencyDisplayOption::SYMBOL:
55             result.Update(globalConst->GetHandledSymbolString().GetTaggedValue());
56             break;
57         case CurrencyDisplayOption::NARROWSYMBOL:
58             result.Update(globalConst->GetHandledNarrowSymbolString().GetTaggedValue());
59             break;
60         case CurrencyDisplayOption::NAME:
61             result.Update(globalConst->GetHandledNameString().GetTaggedValue());
62             break;
63         default:
64             LOG_ECMA(FATAL) << "this branch is unreachable";
65             UNREACHABLE();
66     }
67     return result;
68 }
69 
OptionToEcmaString(JSThread * thread,CurrencySignOption currencySign)70 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CurrencySignOption currencySign)
71 {
72     auto globalConst = thread->GlobalConstants();
73     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
74     switch (currencySign) {
75         case CurrencySignOption::STANDARD:
76             result.Update(globalConst->GetHandledStandardString().GetTaggedValue());
77             break;
78         case CurrencySignOption::ACCOUNTING:
79             result.Update(globalConst->GetHandledAccountingString().GetTaggedValue());
80             break;
81         default:
82             LOG_ECMA(FATAL) << "this branch is unreachable";
83             UNREACHABLE();
84     }
85     return result;
86 }
87 
OptionToEcmaString(JSThread * thread,UnitDisplayOption unitDisplay)88 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, UnitDisplayOption unitDisplay)
89 {
90     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
91     auto globalConst = thread->GlobalConstants();
92     switch (unitDisplay) {
93         case UnitDisplayOption::SHORT:
94             result.Update(globalConst->GetHandledShortString().GetTaggedValue());
95             break;
96         case UnitDisplayOption::NARROW:
97             result.Update(globalConst->GetHandledNarrowString().GetTaggedValue());
98             break;
99         case UnitDisplayOption::LONG:
100             result.Update(globalConst->GetHandledLongString().GetTaggedValue());
101             break;
102         default:
103             LOG_ECMA(FATAL) << "this branch is unreachable";
104             UNREACHABLE();
105     }
106     return result;
107 }
108 
OptionToEcmaString(JSThread * thread,NotationOption notation)109 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, NotationOption notation)
110 {
111     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
112     auto globalConst = thread->GlobalConstants();
113     switch (notation) {
114         case NotationOption::STANDARD:
115             result.Update(globalConst->GetHandledStandardString().GetTaggedValue());
116             break;
117         case NotationOption::SCIENTIFIC:
118             result.Update(globalConst->GetHandledScientificString().GetTaggedValue());
119             break;
120         case NotationOption::ENGINEERING:
121             result.Update(globalConst->GetHandledEngineeringString().GetTaggedValue());
122             break;
123         case NotationOption::COMPACT:
124             result.Update(globalConst->GetHandledCompactString().GetTaggedValue());
125             break;
126         default:
127             LOG_ECMA(FATAL) << "this branch is unreachable";
128             UNREACHABLE();
129     }
130     return result;
131 }
132 
OptionToEcmaString(JSThread * thread,CompactDisplayOption compactDisplay)133 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CompactDisplayOption compactDisplay)
134 {
135     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
136     auto globalConst = thread->GlobalConstants();
137     switch (compactDisplay) {
138         case CompactDisplayOption::SHORT:
139             result.Update(globalConst->GetHandledShortString().GetTaggedValue());
140             break;
141         case CompactDisplayOption::LONG:
142             result.Update(globalConst->GetHandledLongString().GetTaggedValue());
143             break;
144         default:
145             LOG_ECMA(FATAL) << "this branch is unreachable";
146             UNREACHABLE();
147     }
148     return result;
149 }
150 
OptionToEcmaString(JSThread * thread,SignDisplayOption signDisplay)151 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, SignDisplayOption signDisplay)
152 {
153     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
154     auto globalConst = thread->GlobalConstants();
155     switch (signDisplay) {
156         case SignDisplayOption::AUTO:
157             result.Update(globalConst->GetHandledAutoString().GetTaggedValue());
158             break;
159         case SignDisplayOption::ALWAYS:
160             result.Update(globalConst->GetHandledAlwaysString().GetTaggedValue());
161             break;
162         case SignDisplayOption::NEVER:
163             result.Update(globalConst->GetHandledNeverString().GetTaggedValue());
164             break;
165         case SignDisplayOption::EXCEPTZERO:
166             result.Update(globalConst->GetHandledExceptZeroString().GetTaggedValue());
167             break;
168         default:
169             LOG_ECMA(FATAL) << "this branch is unreachable";
170             UNREACHABLE();
171     }
172     return result;
173 }
174 
ToMeasureUnit(const std::string & sanctionedUnit)175 icu::MeasureUnit ToMeasureUnit(const std::string &sanctionedUnit)
176 {
177     UErrorCode status = U_ZERO_ERROR;
178     // Get All ICU measure unit
179     int32_t total = icu::MeasureUnit::getAvailable(nullptr, 0, status);
180     status = U_ZERO_ERROR;
181     std::vector<icu::MeasureUnit> units(total);
182     icu::MeasureUnit::getAvailable(units.data(), total, status);
183     ASSERT(U_SUCCESS(status));
184 
185     // Find measure unit according to sanctioned unit
186     //  then return measure unit
187     for (auto &unit : units) {
188         if (std::strcmp(sanctionedUnit.c_str(), unit.getSubtype()) == 0) {
189             return unit;
190         }
191     }
192     return icu::MeasureUnit();
193 }
194 
195 // ecma402 #sec-issanctionedsimpleunitidentifier
IsSanctionedSimpleUnitIdentifier(const std::string & unit)196 bool IsSanctionedSimpleUnitIdentifier(const std::string &unit)
197 {
198     // 1. If unitIdentifier is listed in sanctioned unit set, return true.
199     auto it = SANCTIONED_UNIT.find(unit);
200     if (it != SANCTIONED_UNIT.end()) {
201         return true;
202     }
203 
204     // 2. Else, Return false.
205     return false;
206 }
207 
208 // 6.5.1 IsWellFormedUnitIdentifier ( unitIdentifier )
IsWellFormedUnitIdentifier(const std::string & unit,icu::MeasureUnit & icuUnit,icu::MeasureUnit & icuPerUnit)209 bool IsWellFormedUnitIdentifier(const std::string &unit, icu::MeasureUnit &icuUnit, icu::MeasureUnit &icuPerUnit)
210 {
211     // 1. If the result of IsSanctionedSimpleUnitIdentifier(unitIdentifier) is true, then
212     //      a. Return true.
213     icu::MeasureUnit result = icu::MeasureUnit();
214     icu::MeasureUnit emptyUnit = icu::MeasureUnit();
215     auto pos = unit.find("-per-");
216     if (IsSanctionedSimpleUnitIdentifier(unit) && pos == std::string::npos) {
217         result = ToMeasureUnit(unit);
218         icuUnit = result;
219         icuPerUnit = emptyUnit;
220         return true;
221     }
222 
223     // 2. If the substring "-per-" does not occur exactly once in unitIdentifier,
224     //      a. then false
225     size_t afterPos = pos + PERUNIT_STRING;
226     if (pos == std::string::npos || unit.find("-per-", afterPos) != std::string::npos) {
227         return false;
228     }
229 
230     // 3. Let numerator be the substring of unitIdentifier from the beginning to just before "-per-".
231     std::string numerator = unit.substr(0, pos);
232     // 4. If the result of IsSanctionedUnitIdentifier(numerator) is false, then
233     //      a. return false
234     if (IsSanctionedSimpleUnitIdentifier(numerator)) {
235         result = ToMeasureUnit(numerator);
236     } else {
237         return false;
238     }
239 
240     // 5. Let denominator be the substring of unitIdentifier from just after "-per-" to the end.
241     std::string denominator = unit.substr(pos + PERUNIT_STRING);
242 
243     // 6. If the result of IsSanctionedUnitIdentifier(denominator) is false, then
244     //      a. Return false
245     icu::MeasureUnit perResult = icu::MeasureUnit();
246     if (IsSanctionedSimpleUnitIdentifier(denominator)) {
247         perResult = ToMeasureUnit(denominator);
248     } else {
249         return false;
250     }
251 
252     // 7. Return true.
253     icuUnit = result;
254     icuPerUnit = perResult;
255     return true;
256 }
257 
258 // 12.1.13 SetNumberFormatUnitOptions ( intlObj, options )
SetNumberFormatUnitOptions(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,const JSHandle<JSObject> & optionsObject,icu::number::LocalizedNumberFormatter * icuNumberFormatter)259 FractionDigitsOption SetNumberFormatUnitOptions(JSThread *thread,
260                                                 const JSHandle<JSNumberFormat> &numberFormat,
261                                                 const JSHandle<JSObject> &optionsObject,
262                                                 icu::number::LocalizedNumberFormatter *icuNumberFormatter)
263 {
264     auto globalConst = thread->GlobalConstants();
265     FractionDigitsOption fractionDigitsOption;
266     // 3. Let style be ? GetOption(options, "style", "string", « "decimal", "percent", "currency", "unit" », "decimal").
267     JSHandle<JSTaggedValue> property = globalConst->GetHandledStyleString();
268     auto style = JSLocale::GetOptionOfString<StyleOption>(
269         thread, optionsObject, property,
270         {StyleOption::DECIMAL, StyleOption::PERCENT, StyleOption::CURRENCY, StyleOption::UNIT},
271         {"decimal", "percent", "currency", "unit"}, StyleOption::DECIMAL);
272     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
273 
274     // 4. Set intlObj.[[Style]] to style.
275     numberFormat->SetStyle(style);
276 
277     // 5. Let currency be ? GetOption(options, "currency", "string", undefined, undefined).
278     property = globalConst->GetHandledCurrencyString();
279     JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
280     JSHandle<JSTaggedValue> currency =
281         JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
282     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
283 
284     // 6. If currency is not undefined, then
285     //    a. If the result of IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception.
286     if (!currency->IsUndefined()) {
287         JSHandle<EcmaString> currencyStr = JSHandle<EcmaString>::Cast(currency);
288         if (EcmaStringAccessor(currencyStr).IsUtf16()) {
289             THROW_RANGE_ERROR_AND_RETURN(thread, "not a utf-8", fractionDigitsOption);
290         }
291         std::string currencyCStr = intl::LocaleHelper::ConvertToStdString(currencyStr);
292         if (!JSLocale::IsWellFormedCurrencyCode(currencyCStr)) {
293             THROW_RANGE_ERROR_AND_RETURN(thread, "not a wellformed code", fractionDigitsOption);
294         }
295     } else {
296         // 7. If style is "currency" and currency is undefined, throw a TypeError exception.
297         if (style == StyleOption::CURRENCY) {
298             THROW_TYPE_ERROR_AND_RETURN(thread, "style is currency but currency is undefined", fractionDigitsOption);
299         }
300     }
301 
302     // 8. Let currencyDisplay be ? GetOption(options, "currencyDisplay", "string",
303     //  « "code", "symbol", "narrowSymbol", "name" », "symbol").
304     property = globalConst->GetHandledCurrencyDisplayString();
305     auto currencyDisplay = JSLocale::GetOptionOfString<CurrencyDisplayOption>(
306         thread, optionsObject, property,
307         {CurrencyDisplayOption::CODE, CurrencyDisplayOption::SYMBOL, CurrencyDisplayOption::NARROWSYMBOL,
308          CurrencyDisplayOption::NAME},
309         {"code", "symbol", "narrowSymbol", "name"}, CurrencyDisplayOption::SYMBOL);
310     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
311     numberFormat->SetCurrencyDisplay(currencyDisplay);
312 
313     // 9. Let currencySign be ? GetOption(options, "currencySign", "string", « "standard", "accounting" », "standard").
314     property = globalConst->GetHandledCurrencySignString();
315     auto currencySign = JSLocale::GetOptionOfString<CurrencySignOption>(
316         thread, optionsObject, property, {CurrencySignOption::STANDARD, CurrencySignOption::ACCOUNTING},
317         {"standard", "accounting"}, CurrencySignOption::STANDARD);
318     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
319     numberFormat->SetCurrencySign(currencySign);
320 
321     // 10. Let unit be ? GetOption(options, "unit", "string", undefined, undefined).
322     property = globalConst->GetHandledUnitString();
323     JSHandle<JSTaggedValue> unit =
324         JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
325     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
326     numberFormat->SetUnit(thread, unit);
327 
328     // 11. If unit is not undefined, then
329     // If the result of IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception.
330     icu::MeasureUnit icuUnit;
331     icu::MeasureUnit icuPerUnit;
332     if (!unit->IsUndefined()) {
333         JSHandle<EcmaString> unitStr = JSHandle<EcmaString>::Cast(unit);
334         if (EcmaStringAccessor(unitStr).IsUtf16()) {
335             THROW_RANGE_ERROR_AND_RETURN(thread, "Unit input is illegal", fractionDigitsOption);
336         }
337         std::string str = intl::LocaleHelper::ConvertToStdString(unitStr);
338         if (!IsWellFormedUnitIdentifier(str, icuUnit, icuPerUnit)) {
339             THROW_RANGE_ERROR_AND_RETURN(thread, "Unit input is illegal", fractionDigitsOption);
340         }
341     } else {
342         // 15.12. if style is "unit" and unit is undefined, throw a TypeError exception.
343         if (style == StyleOption::UNIT) {
344             THROW_TYPE_ERROR_AND_RETURN(thread, "style is unit but unit is undefined", fractionDigitsOption);
345         }
346     }
347 
348     // 13. Let unitDisplay be ? GetOption(options, "unitDisplay", "string", « "short", "narrow", "long" », "short").
349     property = globalConst->GetHandledUnitDisplayString();
350     auto unitDisplay = JSLocale::GetOptionOfString<UnitDisplayOption>(
351         thread, optionsObject, property, {UnitDisplayOption::SHORT, UnitDisplayOption::NARROW, UnitDisplayOption::LONG},
352         {"short", "narrow", "long"}, UnitDisplayOption::SHORT);
353     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
354     numberFormat->SetUnitDisplay(unitDisplay);
355 
356     // 14. If style is "currency", then
357     //      a. Let currency be the result of converting currency to upper case as specified in 6.1.
358     //      b. Set intlObj.[[Currency]] to currency.
359     //      c. Set intlObj.[[CurrencyDisplay]] to currencyDisplay.
360     //      d. Set intlObj.[[CurrencySign]] to currencySign.
361     icu::UnicodeString currencyUStr;
362     UErrorCode status = U_ZERO_ERROR;
363     if (style == StyleOption::CURRENCY) {
364         JSHandle<EcmaString> currencyStr = JSHandle<EcmaString>::Cast(currency);
365         std::string currencyCStr = intl::LocaleHelper::ConvertToStdString(currencyStr);
366         std::transform(currencyCStr.begin(), currencyCStr.end(), currencyCStr.begin(), toupper);
367         ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
368         JSHandle<JSTaggedValue> currencyValue = JSHandle<JSTaggedValue>::Cast(factory->NewFromStdString(currencyCStr));
369         numberFormat->SetCurrency(thread, currencyValue);
370         currencyUStr = currencyCStr.c_str();
371         if (!currencyUStr.isEmpty()) {  // NOLINT(readability-implicit-bool-conversion)
372             *icuNumberFormatter = icuNumberFormatter->unit(icu::CurrencyUnit(currencyUStr.getBuffer(), status));
373             ASSERT(U_SUCCESS(status));
374             UNumberUnitWidth uNumberUnitWidth;
375             // Trans currencyDisplayOption to ICU format number display option
376             switch (currencyDisplay) {
377                 case CurrencyDisplayOption::CODE:
378                     uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE;
379                     break;
380                 case CurrencyDisplayOption::SYMBOL:
381                     uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
382                     break;
383                 case CurrencyDisplayOption::NARROWSYMBOL:
384                     uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
385                     break;
386                 case CurrencyDisplayOption::NAME:
387                     uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
388                     break;
389                 default:
390                     LOG_ECMA(FATAL) << "this branch is unreachable";
391                     UNREACHABLE();
392             }
393             *icuNumberFormatter = icuNumberFormatter->unitWidth(uNumberUnitWidth);
394         }
395     }
396 
397     // 15. If style is "unit", then
398     // if unit is not undefiend set unit to LocalizedNumberFormatter
399     //    then if perunit is not undefiend set perunit to LocalizedNumberFormatter
400     if (style == StyleOption::UNIT) {
401         icu::MeasureUnit emptyUnit = icu::MeasureUnit();
402         if (icuUnit != emptyUnit) {  // NOLINT(readability-implicit-bool-conversion)
403             *icuNumberFormatter = icuNumberFormatter->unit(icuUnit);
404         }
405         if (icuPerUnit != emptyUnit) {  // NOLINT(readability-implicit-bool-conversion)
406             *icuNumberFormatter = icuNumberFormatter->perUnit(icuPerUnit);
407         }
408     }
409 
410     // 17. If style is "currency", then
411     //      a. Let cDigits be CurrencyDigits(currency).
412     //      b. Let mnfdDefault be cDigits.
413     //      c. Let mxfdDefault be cDigits.
414     if (style == StyleOption::CURRENCY) {
415         int32_t cDigits = JSNumberFormat::CurrencyDigits(currencyUStr);
416         fractionDigitsOption.mnfdDefault = cDigits;
417         fractionDigitsOption.mxfdDefault = cDigits;
418     } else {
419         // 18. Else,
420         //      a. Let mnfdDefault be 0.
421         //      b. If style is "percent", then
422         //          i. Let mxfdDefault be 0.
423         //      c. else,
424         //          i. Let mxfdDefault be 3.
425         fractionDigitsOption.mnfdDefault = 0;
426         if (style == StyleOption::PERCENT) {
427             fractionDigitsOption.mxfdDefault = 0;
428         } else {
429             fractionDigitsOption.mxfdDefault = 3;  // Max decimal precision is 3
430         }
431     }
432     return fractionDigitsOption;
433 }
434 
435 // 12.1.2 InitializeNumberFormat ( numberFormat, locales, options )
436 // NOLINTNEXTLINE(readability-function-size)
InitializeNumberFormat(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,const JSHandle<JSTaggedValue> & locales,const JSHandle<JSTaggedValue> & options,bool forIcuCache)437 void JSNumberFormat::InitializeNumberFormat(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
438                                             const JSHandle<JSTaggedValue> &locales,
439                                             const JSHandle<JSTaggedValue> &options,
440                                             bool forIcuCache)
441 {
442     EcmaVM *ecmaVm = thread->GetEcmaVM();
443     ObjectFactory *factory = ecmaVm->GetFactory();
444     // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
445     JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
446 
447     RETURN_IF_ABRUPT_COMPLETION(thread);
448     // 2. If options is undefined, then
449     //      a. Let options be ObjectCreate(null).
450     // 3. Else,
451     //      a. Let options be ? ToObject(options).
452     JSHandle<JSObject> optionsObject;
453     if (options->IsUndefined()) {
454         optionsObject = factory->CreateNullJSObject();
455     } else {
456         optionsObject = JSTaggedValue::ToObject(thread, options);
457         RETURN_IF_ABRUPT_COMPLETION(thread);
458     }
459 
460     auto globalConst = thread->GlobalConstants();
461     // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
462     JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
463     auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
464         thread, optionsObject, property, {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
465         {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
466     RETURN_IF_ABRUPT_COMPLETION(thread);
467 
468     // 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
469     property = globalConst->GetHandledNumberingSystemString();
470     JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
471     JSHandle<JSTaggedValue> numberingSystemTaggedValue =
472         JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
473     RETURN_IF_ABRUPT_COMPLETION(thread);
474     numberFormat->SetNumberingSystem(thread, numberingSystemTaggedValue);
475 
476     // 8. If numberingSystem is not undefined, then
477     //      a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal,
478     //      throw a RangeError exception. `(3*8alphanum) *("-" (3*8alphanum))`
479     std::string numberingSystemStr;
480     if (!numberingSystemTaggedValue->IsUndefined()) {
481         JSHandle<EcmaString> numberingSystemEcmaString = JSHandle<EcmaString>::Cast(numberingSystemTaggedValue);
482         if (EcmaStringAccessor(numberingSystemEcmaString).IsUtf16()) {
483             THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
484         }
485         numberingSystemStr = intl::LocaleHelper::ConvertToStdString(numberingSystemEcmaString);
486         if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStr)) {
487             THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
488         }
489     }
490 
491     // 10. Let localeData be %NumberFormat%.[[LocaleData]].
492     JSHandle<TaggedArray> availableLocales;
493     if (requestedLocales->GetLength() == 0) {
494         availableLocales = factory->EmptyArray();
495     } else {
496         availableLocales = GetAvailableLocales(thread);
497     }
498 
499     // 11. Let r be ResolveLocale( %NumberFormat%.[[AvailableLocales]], requestedLocales, opt,
500     // %NumberFormat%.[[RelevantExtensionKeys]], localeData).
501     std::set<std::string> relevantExtensionKeys{"nu"};
502     ResolvedLocale r =
503         JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
504 
505     // 12. Set numberFormat.[[Locale]] to r.[[locale]].
506     icu::Locale icuLocale = r.localeData;
507     JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
508     RETURN_IF_ABRUPT_COMPLETION(thread);
509     numberFormat->SetLocale(thread, localeStr.GetTaggedValue());
510 
511     // Set numberingSystemStr to UnicodeKeyWord "nu"
512     UErrorCode status = U_ZERO_ERROR;
513     if (!numberingSystemStr.empty()) {
514         if (JSLocale::IsWellNumberingSystem(numberingSystemStr)) {
515             icuLocale.setUnicodeKeywordValue("nu", numberingSystemStr, status);
516             ASSERT(U_SUCCESS(status));
517         }
518     }
519 
520     icu::number::LocalizedNumberFormatter icuNumberFormatter =
521         icu::number::NumberFormatter::withLocale(icuLocale).roundingMode(UNUM_ROUND_HALFUP);
522 
523     int32_t mnfdDefault = 0;
524     int32_t mxfdDefault = 0;
525     FractionDigitsOption fractionOptions =
526         SetNumberFormatUnitOptions(thread, numberFormat, optionsObject, &icuNumberFormatter);
527     RETURN_IF_ABRUPT_COMPLETION(thread);
528     mnfdDefault = fractionOptions.mnfdDefault;
529     mxfdDefault = fractionOptions.mxfdDefault;
530     UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay();
531 
532     // Trans unitDisplay option to ICU display option
533     UNumberUnitWidth uNumberUnitWidth;
534     switch (unitDisplay) {
535         case UnitDisplayOption::SHORT:
536             uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
537             break;
538         case UnitDisplayOption::NARROW:
539             uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
540             break;
541         case UnitDisplayOption::LONG:
542             // UNUM_UNIT_WIDTH_FULL_NAME Print the full name of the unit, without any abbreviations.
543             uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
544             break;
545         default:
546             LOG_ECMA(FATAL) << "this branch is unreachable";
547             UNREACHABLE();
548     }
549     icuNumberFormatter = icuNumberFormatter.unitWidth(uNumberUnitWidth);
550 
551     // 16. Let style be numberFormat.[[Style]].
552     StyleOption style = numberFormat->GetStyle();
553     if (style == StyleOption::PERCENT) {
554         icuNumberFormatter = icuNumberFormatter.unit(icu::MeasureUnit::getPercent()).
555             scale(icu::number::Scale::powerOfTen(2));  // means 10^2
556     }
557 
558     // 19. Let notation be ? GetOption(
559     //  options, "notation", "string", « "standard", "scientific", "engineering", "compact" », "standard").
560     property = globalConst->GetHandledNotationString();
561     auto notation = JSLocale::GetOptionOfString<NotationOption>(
562         thread, optionsObject, property,
563         {NotationOption::STANDARD, NotationOption::SCIENTIFIC, NotationOption::ENGINEERING, NotationOption::COMPACT},
564         {"standard", "scientific", "engineering", "compact"}, NotationOption::STANDARD);
565     RETURN_IF_ABRUPT_COMPLETION(thread);
566     numberFormat->SetNotation(notation);
567 
568     // 21. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
569     JSLocale::SetNumberFormatDigitOptions(thread, numberFormat, JSHandle<JSTaggedValue>::Cast(optionsObject),
570                                           mnfdDefault, mxfdDefault, notation);
571     icuNumberFormatter = SetICUFormatterDigitOptions(icuNumberFormatter, numberFormat);
572 
573     // 22. Let compactDisplay be ? GetOptionOfString(options, "compactDisplay", "string", « "short", "long" », "short").
574     property = globalConst->GetHandledCompactDisplayString();
575     auto compactDisplay = JSLocale::GetOptionOfString<CompactDisplayOption>(
576         thread, optionsObject, property, {CompactDisplayOption::SHORT, CompactDisplayOption::LONG}, {"short", "long"},
577         CompactDisplayOption::SHORT);
578     numberFormat->SetCompactDisplay(compactDisplay);
579 
580     // Trans NotationOption to ICU Noation and set to icuNumberFormatter
581     if (notation == NotationOption::COMPACT) {
582         switch (compactDisplay) {
583             case CompactDisplayOption::SHORT:
584                 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactShort());
585                 break;
586             case CompactDisplayOption::LONG:
587                 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactLong());
588                 break;
589             default:
590                 break;
591         }
592     }
593     switch (notation) {
594         case NotationOption::STANDARD:
595             icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::simple());
596             break;
597         case NotationOption::SCIENTIFIC:
598             icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::scientific());
599             break;
600         case NotationOption::ENGINEERING:
601             icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::engineering());
602             break;
603         default:
604             break;
605     }
606 
607     // 24. Let useGrouping be ? GetOption(options, "useGrouping", "boolean", undefined, true).
608     property = globalConst->GetHandledUserGroupingString();
609     bool useGrouping = false;
610     [[maybe_unused]] bool find = JSLocale::GetOptionOfBool(thread, optionsObject, property, true, &useGrouping);
611     RETURN_IF_ABRUPT_COMPLETION(thread);
612     JSHandle<JSTaggedValue> useGroupingValue(thread, JSTaggedValue(useGrouping));
613     numberFormat->SetUseGrouping(thread, useGroupingValue);
614 
615     // 25. Set numberFormat.[[UseGrouping]] to useGrouping.
616     if (!useGrouping) {
617         icuNumberFormatter = icuNumberFormatter.grouping(UNumberGroupingStrategy::UNUM_GROUPING_OFF);
618     }
619 
620     // 26. Let signDisplay be ?
621     //  GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero" », "auto").
622     property = globalConst->GetHandledSignDisplayString();
623     auto signDisplay = JSLocale::GetOptionOfString<SignDisplayOption>(
624         thread, optionsObject, property,
625         {SignDisplayOption::AUTO, SignDisplayOption::NEVER, SignDisplayOption::ALWAYS, SignDisplayOption::EXCEPTZERO},
626         {"auto", "never", "always", "exceptZero"}, SignDisplayOption::AUTO);
627     RETURN_IF_ABRUPT_COMPLETION(thread);
628     numberFormat->SetSignDisplay(signDisplay);
629 
630     // 27. Set numberFormat.[[SignDisplay]] to signDisplay.
631     // The default sign in ICU is UNUM_SIGN_AUTO which is mapped from
632     // SignDisplay::AUTO and CurrencySign::STANDARD so we can skip setting
633     // under that values for optimization.
634     CurrencySignOption currencySign = numberFormat->GetCurrencySign();
635 
636     // Trans SignDisPlayOption to ICU UNumberSignDisplay and set to icuNumberFormatter
637     switch (signDisplay) {
638         case SignDisplayOption::AUTO:
639             // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
640             if (currencySign == CurrencySignOption::ACCOUNTING) {
641                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING);
642             } else {
643                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_AUTO);
644             }
645             break;
646         case SignDisplayOption::NEVER:
647             icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_NEVER);
648             break;
649         case SignDisplayOption::ALWAYS:
650             // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
651             if (currencySign == CurrencySignOption::ACCOUNTING) {
652                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS);
653             } else {
654                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS);
655             }
656             break;
657         case SignDisplayOption::EXCEPTZERO:
658             // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
659             if (currencySign == CurrencySignOption::ACCOUNTING) {
660                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO);
661             } else {
662                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO);
663             }
664             break;
665         default:
666             break;
667     }
668 
669     if (forIcuCache) {
670         std::string cacheEntry =
671             locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString();
672         auto formatterPointer = new icu::number::LocalizedNumberFormatter(icuNumberFormatter);
673         thread->GetCurrentEcmaContext()->SetIcuFormatterToCache(IcuFormatterType::NUMBER_FORMATTER, cacheEntry,
674             formatterPointer, JSNumberFormat::FreeIcuNumberformat);
675     } else {
676         // Set numberFormat.[[IcuNumberForma]] to handleNumberFormatter
677         factory->NewJSIntlIcuData(numberFormat, icuNumberFormatter, JSNumberFormat::FreeIcuNumberformat);
678     }
679     // Set numberFormat.[[BoundFormat]] to undefinedValue
680     numberFormat->SetBoundFormat(thread, undefinedValue);
681 }
682 
683 // 12.1.3 CurrencyDigits ( currency )
CurrencyDigits(const icu::UnicodeString & currency)684 int32_t JSNumberFormat::CurrencyDigits(const icu::UnicodeString &currency)
685 {
686     UErrorCode status = U_ZERO_ERROR;
687     // If the ISO 4217 currency and funds code list contains currency as an alphabetic code,
688     // return the minor unit value corresponding to the currency from the list; otherwise, return 2.
689     int32_t fractionDigits =
690         ucurr_getDefaultFractionDigits(reinterpret_cast<const UChar *>(currency.getBuffer()), &status);
691     if (U_SUCCESS(status)) {
692         return fractionDigits;
693     }
694     return DEFAULT_FRACTION_DIGITS;
695 }
696 
GetCachedIcuNumberFormatter(JSThread * thread,const JSHandle<JSTaggedValue> & locales)697 icu::number::LocalizedNumberFormatter *JSNumberFormat::GetCachedIcuNumberFormatter(JSThread *thread,
698     const JSHandle<JSTaggedValue> &locales)
699 {
700     std::string cacheEntry = locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString();
701     void *cachedNumberFormatter = thread->GetCurrentEcmaContext()->GetIcuFormatterFromCache(
702         IcuFormatterType::NUMBER_FORMATTER, cacheEntry);
703     if (cachedNumberFormatter) {
704         return reinterpret_cast<icu::number::LocalizedNumberFormatter*>(cachedNumberFormatter);
705     }
706     return nullptr;
707 }
708 
709 // 12.1.8 FormatNumeric( numberFormat, x )
FormatNumeric(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,JSTaggedValue x)710 JSHandle<JSTaggedValue> JSNumberFormat::FormatNumeric(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
711                                                       JSTaggedValue x)
712 {
713     icu::number::LocalizedNumberFormatter *icuNumberFormat = numberFormat->GetIcuCallTarget();
714     ASSERT(icuNumberFormat != nullptr);
715     JSHandle<JSTaggedValue> res = FormatNumeric(thread, icuNumberFormat, x);
716     return res;
717 }
718 
FormatNumeric(JSThread * thread,const icu::number::LocalizedNumberFormatter * icuNumberFormat,JSTaggedValue x)719 JSHandle<JSTaggedValue> JSNumberFormat::FormatNumeric(JSThread *thread,
720                                                       const icu::number::LocalizedNumberFormatter *icuNumberFormat,
721                                                       JSTaggedValue x)
722 {
723     UErrorCode status = U_ZERO_ERROR;
724     icu::number::FormattedNumber formattedNumber;
725     if (x.IsBigInt()) {
726         JSHandle<BigInt> bigint(thread, x);
727         JSHandle<EcmaString> bigintStr = BigInt::ToString(thread, bigint);
728         std::string stdString = EcmaStringAccessor(bigintStr).ToStdString();
729         formattedNumber = icuNumberFormat->formatDecimal(icu::StringPiece(stdString), status);
730     } else {
731         double number = x.GetNumber();
732         formattedNumber = icuNumberFormat->formatDouble(number, status);
733     }
734     if (U_FAILURE(status)) {
735         JSHandle<JSTaggedValue> errorResult(thread, JSTaggedValue::Exception());
736         THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatter format failed", errorResult);
737     }
738     icu::UnicodeString result = formattedNumber.toString(status);
739     if (U_FAILURE(status)) {
740         JSHandle<JSTaggedValue> errorResult(thread, JSTaggedValue::Exception());
741         THROW_RANGE_ERROR_AND_RETURN(thread, "formatted number toString failed", errorResult);
742     }
743     JSHandle<EcmaString> stringValue = intl::LocaleHelper::UStringToString(thread, result);
744     return JSHandle<JSTaggedValue>::Cast(stringValue);
745 }
746 
GroupToParts(JSThread * thread,const icu::number::FormattedNumber & formatted,const JSHandle<JSArray> & receiver,const JSHandle<JSNumberFormat> & numberFormat,JSTaggedValue x)747 void GroupToParts(JSThread *thread, const icu::number::FormattedNumber &formatted, const JSHandle<JSArray> &receiver,
748                   const JSHandle<JSNumberFormat> &numberFormat, JSTaggedValue x)
749 {
750     UErrorCode status = U_ZERO_ERROR;
751     icu::UnicodeString formattedText = formatted.toString(status);
752     if (U_FAILURE(status)) {  // NOLINT(readability-implicit-bool-conversion)
753         THROW_TYPE_ERROR(thread, "formattedNumber toString failed");
754     }
755     ASSERT(x.IsNumber());
756 
757     StyleOption styleOption = numberFormat->GetStyle();
758 
759     icu::ConstrainedFieldPosition cfpo;
760     // Set constrainCategory to UFIELD_CATEGORY_NUMBER which is specified for UNumberFormatFields
761     cfpo.constrainCategory(UFIELD_CATEGORY_NUMBER);
762     auto globalConst = thread->GlobalConstants();
763     JSMutableHandle<JSTaggedValue> typeString(thread, JSTaggedValue::Undefined());
764     int index = 0;
765     int previousLimit = 0;
766     /**
767      * From ICU header file document @unumberformatter.h
768      * Sets a constraint on the field category.
769      *
770      * When this instance of ConstrainedFieldPosition is passed to FormattedValue#nextPosition,
771      * positions are skipped unless they have the given category.
772      *
773      * Any previously set constraints are cleared.
774      *
775      * For example, to loop over only the number-related fields:
776      *
777      *     ConstrainedFieldPosition cfpo;
778      *     cfpo.constrainCategory(UFIELDCATEGORY_NUMBER_FORMAT);
779      *     while (fmtval.nextPosition(cfpo, status)) {
780      *         // handle the number-related field position
781      *     }
782      */
783     bool lastFieldGroup = false;
784     int groupLeapLength = 0;
785     while (formatted.nextPosition(cfpo, status)) {
786         int32_t fieldId = cfpo.getField();
787         int32_t start = cfpo.getStart();
788         int32_t limit = cfpo.getLimit();
789         typeString.Update(globalConst->GetLiteralString());
790         // If start greater than previousLimit, means a literal type exists before number fields
791         // so add a literal type with value of formattedText.sub(0, start)
792         // Special case when fieldId is UNUM_GROUPING_SEPARATOR_FIELD
793         if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) {
794             JSHandle<EcmaString> substring =
795                 intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start);
796             typeString.Update(globalConst->GetIntegerString());
797             JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
798             RETURN_IF_ABRUPT_COMPLETION(thread);
799             index++;
800             {
801                 typeString.Update(JSLocale::GetNumberFieldType(thread, x, fieldId).GetTaggedValue());
802                 substring = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit);
803                 JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
804                 RETURN_IF_ABRUPT_COMPLETION(thread);
805                 index++;
806             }
807             lastFieldGroup = true;
808             groupLeapLength = start - previousLimit + 1;
809             previousLimit = limit;
810             continue;
811         } else if (start > previousLimit) {
812             JSHandle<EcmaString> substring =
813                 intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start);
814             JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
815             RETURN_IF_ABRUPT_COMPLETION(thread);
816             index++;
817         }
818         if (lastFieldGroup) {
819             start = start + groupLeapLength;
820             lastFieldGroup = false;
821         }
822         // Special case in ICU when style is unit and unit is percent
823         if (styleOption == StyleOption::UNIT && static_cast<UNumberFormatFields>(fieldId) == UNUM_PERCENT_FIELD) {
824             typeString.Update(globalConst->GetUnitString());
825         } else {
826             typeString.Update(JSLocale::GetNumberFieldType(thread, x, fieldId).GetTaggedValue());
827         }
828         JSHandle<EcmaString> substring = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit);
829         JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
830         RETURN_IF_ABRUPT_COMPLETION(thread);
831         index++;
832         previousLimit = limit;
833     }
834     // If iterated length is smaller than formattedText.length, means a literal type exists after number fields
835     // so add a literal type with value of formattedText.sub(previousLimit, formattedText.length)
836     if (formattedText.length() > previousLimit) {
837         typeString.Update(globalConst->GetLiteralString());
838         JSHandle<EcmaString> substring =
839             intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, formattedText.length());
840         JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
841     }
842 }
843 
844 // 12.1.9 FormatNumericToParts( numberFormat, x )
FormatNumericToParts(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,JSTaggedValue x)845 JSHandle<JSArray> JSNumberFormat::FormatNumericToParts(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
846                                                        JSTaggedValue x)
847 {
848     ASSERT(x.IsNumber());
849     icu::number::LocalizedNumberFormatter *icuNumberFormatter = numberFormat->GetIcuCallTarget();
850     ASSERT(icuNumberFormatter != nullptr);
851 
852     UErrorCode status = U_ZERO_ERROR;
853     double number = x.GetNumber();
854     icu::number::FormattedNumber formattedNumber = icuNumberFormatter->formatDouble(number, status);
855     if (U_FAILURE(status)) {
856         ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
857         JSHandle<JSArray> emptyArray = factory->NewJSArray();
858         THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatter format failed", emptyArray);
859     }
860 
861     JSHandle<JSTaggedValue> arr = JSArray::ArrayCreate(thread, JSTaggedNumber(0));
862     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
863     JSHandle<JSArray> result = JSHandle<JSArray>::Cast(arr);
864     GroupToParts(thread, formattedNumber, result, numberFormat, x);
865     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
866     return result;
867 }
868 
869 // 12.1.12 UnwrapNumberFormat( nf )
UnwrapNumberFormat(JSThread * thread,const JSHandle<JSTaggedValue> & nf)870 JSHandle<JSTaggedValue> JSNumberFormat::UnwrapNumberFormat(JSThread *thread, const JSHandle<JSTaggedValue> &nf)
871 {
872     // 1. Assert: Type(nf) is Object.
873     ASSERT(nf->IsJSObject());
874 
875     // 2. If nf does not have an [[InitializedNumberFormat]] internal slot and ?
876     //  InstanceofOperator(nf, %NumberFormat%) is true, then Let nf be ? Get(nf, %Intl%.[[FallbackSymbol]]).
877     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
878     bool hasInstance = JSObject::InstanceOf(thread, nf, env->GetNumberFormatFunction());
879     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSHandle<JSTaggedValue>(thread, JSTaggedValue::Undefined()));
880 
881     bool isJSNumberFormat = nf->IsJSNumberFormat();
882     // If nf does not have an [[InitializedNumberFormat]] internal slot and ?
883     // InstanceofOperator(nf, %NumberFormat%) is true, then
884     //      a. Let nf be ? Get(nf, %Intl%.[[FallbackSymbol]]).
885     if (!isJSNumberFormat && hasInstance) {
886         JSHandle<JSTaggedValue> key(thread, JSHandle<JSIntl>::Cast(env->GetIntlFunction())->GetFallbackSymbol());
887         OperationResult operationResult = JSTaggedValue::GetProperty(thread, nf, key);
888         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSHandle<JSTaggedValue>(thread, JSTaggedValue::Undefined()));
889         return operationResult.GetValue();
890     }
891     // 3. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
892     if (!isJSNumberFormat) {
893         THROW_TYPE_ERROR_AND_RETURN(thread, "this is not object",
894                                     JSHandle<JSTaggedValue>(thread, JSTaggedValue::Exception()));
895     }
896     return nf;
897 }
898 
GetAvailableLocales(JSThread * thread)899 JSHandle<TaggedArray> JSNumberFormat::GetAvailableLocales(JSThread *thread)
900 {
901     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
902     JSHandle<JSTaggedValue> numberFormatLocales = env->GetNumberFormatLocales();
903     if (!numberFormatLocales->IsUndefined()) {
904         return JSHandle<TaggedArray>::Cast(numberFormatLocales);
905     }
906     const char *key = "NumberElements";
907     const char *path = nullptr;
908     std::vector<std::string> availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, key, path);
909     JSHandle<TaggedArray> availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales);
910     env->SetNumberFormatLocales(thread, availableLocales);
911     return availableLocales;
912 }
913 
ResolvedOptions(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,const JSHandle<JSObject> & options)914 void JSNumberFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
915                                      const JSHandle<JSObject> &options)
916 {
917     // Table 5: Resolved Options of NumberFormat Instances
918     // Internal Slot                    Property
919     //    [[Locale]]                      "locale"
920     //    [[NumberingSystem]]             "numberingSystem"
921     //    [[Style]]                       "style"
922     //    [[Currency]]                    "currency"
923     //    [[CurrencyDisplay]]             "currencyDisplay"
924     //    [[CurrencySign]]                "currencySign"
925     //    [[Unit]]                        "unit"
926     //    [[UnitDisplay]]                 "unitDisplay"
927     //    [[MinimumIntegerDigits]]        "minimumIntegerDigits"
928     //    [[MinimumFractionDigits]]       "minimumFractionDigits"
929     //    [[MaximumFractionDigits]]       "maximumFractionDigits"
930     //    [[MinimumSignificantDigits]]    "minimumSignificantDigits"
931     //    [[MaximumSignificantDigits]]    "maximumSignificantDigits"
932     //    [[UseGrouping]]                 "useGrouping"
933     //    [[Notation]]                    "notation"
934     //    [[CompactDisplay]]              "compactDisplay"
935     //    [SignDisplay]]                  "signDisplay"
936     // [[Locale]]
937     auto globalConst = thread->GlobalConstants();
938     JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleString();
939     JSHandle<JSTaggedValue> locale(thread, numberFormat->GetLocale());
940     JSObject::CreateDataPropertyOrThrow(thread, options, property, locale);
941     RETURN_IF_ABRUPT_COMPLETION(thread);
942 
943     // [[NumberingSystem]]
944     JSHandle<JSTaggedValue> numberingSystem(thread, numberFormat->GetNumberingSystem());
945     if (numberingSystem->IsUndefined()) {
946         numberingSystem = globalConst->GetHandledLatnString();
947     }
948     property = globalConst->GetHandledNumberingSystemString();
949     JSObject::CreateDataPropertyOrThrow(thread, options, property, numberingSystem);
950     RETURN_IF_ABRUPT_COMPLETION(thread);
951 
952     // [[Style]]
953     StyleOption style = numberFormat->GetStyle();
954     property = globalConst->GetHandledStyleString();
955     JSHandle<JSTaggedValue> styleString = OptionToEcmaString(thread, style);
956     JSObject::CreateDataPropertyOrThrow(thread, options, property, styleString);
957     RETURN_IF_ABRUPT_COMPLETION(thread);
958 
959     // [[currency]]
960     JSHandle<JSTaggedValue> currency(thread, JSTaggedValue::Undefined());
961     // If style is not currency the currency should be undefined
962     if (style == StyleOption::CURRENCY) {
963         currency = JSHandle<JSTaggedValue>(thread, numberFormat->GetCurrency());
964     }
965     if (!currency->IsUndefined()) {  // NOLINT(readability-implicit-bool-conversion)
966         property = globalConst->GetHandledCurrencyString();
967         JSObject::CreateDataPropertyOrThrow(thread, options, property, currency);
968         RETURN_IF_ABRUPT_COMPLETION(thread);
969 
970         // [[CurrencyDisplay]]
971         property = globalConst->GetHandledCurrencyDisplayString();
972         CurrencyDisplayOption currencyDisplay = numberFormat->GetCurrencyDisplay();
973         JSHandle<JSTaggedValue> currencyDisplayString = OptionToEcmaString(thread, currencyDisplay);
974         JSObject::CreateDataPropertyOrThrow(thread, options, property, currencyDisplayString);
975         RETURN_IF_ABRUPT_COMPLETION(thread);
976 
977         // [[CurrencySign]]
978         property = globalConst->GetHandledCurrencySignString();
979         CurrencySignOption currencySign = numberFormat->GetCurrencySign();
980         JSHandle<JSTaggedValue> currencySignString = OptionToEcmaString(thread, currencySign);
981         JSObject::CreateDataPropertyOrThrow(thread, options, property, currencySignString);
982         RETURN_IF_ABRUPT_COMPLETION(thread);
983     }
984 
985     if (style == StyleOption::UNIT) {
986         JSHandle<JSTaggedValue> unit(thread, numberFormat->GetUnit());
987         if (!unit->IsUndefined()) {
988             // [[Unit]]
989             property = globalConst->GetHandledUnitString();
990             JSObject::CreateDataPropertyOrThrow(thread, options, property, unit);
991             RETURN_IF_ABRUPT_COMPLETION(thread);
992         }
993         // [[UnitDisplay]]
994         property = globalConst->GetHandledUnitDisplayString();
995         UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay();
996         JSHandle<JSTaggedValue> unitDisplayString = OptionToEcmaString(thread, unitDisplay);
997         JSObject::CreateDataPropertyOrThrow(thread, options, property, unitDisplayString);
998         RETURN_IF_ABRUPT_COMPLETION(thread);
999     }
1000     // [[MinimumIntegerDigits]]
1001     property = globalConst->GetHandledMinimumIntegerDigitsString();
1002     JSHandle<JSTaggedValue> minimumIntegerDigits(thread, numberFormat->GetMinimumIntegerDigits());
1003     JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumIntegerDigits);
1004     RETURN_IF_ABRUPT_COMPLETION(thread);
1005 
1006     RoundingType roundingType = numberFormat->GetRoundingType();
1007     if (roundingType == RoundingType::SIGNIFICANTDIGITS) {
1008         // [[MinimumSignificantDigits]]
1009         property = globalConst->GetHandledMinimumSignificantDigitsString();
1010         JSHandle<JSTaggedValue> minimumSignificantDigits(thread, numberFormat->GetMinimumSignificantDigits());
1011         JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumSignificantDigits);
1012         RETURN_IF_ABRUPT_COMPLETION(thread);
1013         // [[MaximumSignificantDigits]]
1014         property = globalConst->GetHandledMaximumSignificantDigitsString();
1015         JSHandle<JSTaggedValue> maximumSignificantDigits(thread, numberFormat->GetMaximumSignificantDigits());
1016         JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumSignificantDigits);
1017         RETURN_IF_ABRUPT_COMPLETION(thread);
1018     } else {
1019         // [[MinimumFractionDigits]]
1020         property = globalConst->GetHandledMinimumFractionDigitsString();
1021         JSHandle<JSTaggedValue> minimumFractionDigits(thread, numberFormat->GetMinimumFractionDigits());
1022         JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumFractionDigits);
1023         RETURN_IF_ABRUPT_COMPLETION(thread);
1024         // [[MaximumFractionDigits]]
1025         property = globalConst->GetHandledMaximumFractionDigitsString();
1026         JSHandle<JSTaggedValue> maximumFractionDigits(thread, numberFormat->GetMaximumFractionDigits());
1027         JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumFractionDigits);
1028         RETURN_IF_ABRUPT_COMPLETION(thread);
1029     }
1030 
1031     // [[UseGrouping]]
1032     property = globalConst->GetHandledUserGroupingString();
1033     JSObject::CreateDataPropertyOrThrow(thread, options, property,
1034                                         JSHandle<JSTaggedValue>(thread, numberFormat->GetUseGrouping()));
1035     RETURN_IF_ABRUPT_COMPLETION(thread);
1036 
1037     // [[Notation]]
1038     property = globalConst->GetHandledNotationString();
1039     NotationOption notation = numberFormat->GetNotation();
1040     JSHandle<JSTaggedValue> notationString = OptionToEcmaString(thread, notation);
1041     JSObject::CreateDataPropertyOrThrow(thread, options, property, notationString);
1042     RETURN_IF_ABRUPT_COMPLETION(thread);
1043 
1044     // Only output compactDisplay when notation is compact.
1045     if (notation == NotationOption::COMPACT) {
1046         // [[CompactDisplay]]
1047         property = globalConst->GetHandledCompactDisplayString();
1048         CompactDisplayOption compactDisplay = numberFormat->GetCompactDisplay();
1049         JSHandle<JSTaggedValue> compactDisplayString = OptionToEcmaString(thread, compactDisplay);
1050         JSObject::CreateDataPropertyOrThrow(thread, options, property, compactDisplayString);
1051         RETURN_IF_ABRUPT_COMPLETION(thread);
1052     }
1053 
1054     // [[SignDisplay]]
1055     property = globalConst->GetHandledSignDisplayString();
1056     SignDisplayOption signDisplay = numberFormat->GetSignDisplay();
1057     JSHandle<JSTaggedValue> signDisplayString = OptionToEcmaString(thread, signDisplay);
1058     JSObject::CreateDataPropertyOrThrow(thread, options, property, signDisplayString);
1059 }
1060 }  // namespace panda::ecmascript