• 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     // 2. If options is undefined, then
448     //      a. Let options be ObjectCreate(null).
449     // 3. Else,
450     //      a. Let options be ? ToObject(options).
451     JSHandle<JSObject> optionsObject;
452     if (options->IsUndefined()) {
453         optionsObject = factory->CreateNullJSObject();
454     } else {
455         optionsObject = JSTaggedValue::ToObject(thread, options);
456         RETURN_IF_ABRUPT_COMPLETION(thread);
457     }
458 
459     auto globalConst = thread->GlobalConstants();
460     // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
461     JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
462     auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
463         thread, optionsObject, property, {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
464         {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
465     RETURN_IF_ABRUPT_COMPLETION(thread);
466 
467     // 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
468     property = globalConst->GetHandledNumberingSystemString();
469     JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
470     JSHandle<JSTaggedValue> numberingSystemTaggedValue =
471         JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
472     RETURN_IF_ABRUPT_COMPLETION(thread);
473     numberFormat->SetNumberingSystem(thread, numberingSystemTaggedValue);
474 
475     // 8. If numberingSystem is not undefined, then
476     //      a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal,
477     //      throw a RangeError exception. `(3*8alphanum) *("-" (3*8alphanum))`
478     std::string numberingSystemStr;
479     if (!numberingSystemTaggedValue->IsUndefined()) {
480         JSHandle<EcmaString> numberingSystemEcmaString = JSHandle<EcmaString>::Cast(numberingSystemTaggedValue);
481         if (EcmaStringAccessor(numberingSystemEcmaString).IsUtf16()) {
482             THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
483         }
484         numberingSystemStr = intl::LocaleHelper::ConvertToStdString(numberingSystemEcmaString);
485         if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStr)) {
486             THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
487         }
488     }
489 
490     // 10. Let localeData be %NumberFormat%.[[LocaleData]].
491     JSHandle<TaggedArray> availableLocales;
492     if (requestedLocales->GetLength() == 0) {
493         availableLocales = factory->EmptyArray();
494     } else {
495         availableLocales = GetAvailableLocales(thread);
496     }
497 
498     // 11. Let r be ResolveLocale( %NumberFormat%.[[AvailableLocales]], requestedLocales, opt,
499     // %NumberFormat%.[[RelevantExtensionKeys]], localeData).
500     std::set<std::string> relevantExtensionKeys{"nu"};
501     ResolvedLocale r =
502         JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
503 
504     // 12. Set numberFormat.[[Locale]] to r.[[locale]].
505     icu::Locale icuLocale = r.localeData;
506     JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
507     RETURN_IF_ABRUPT_COMPLETION(thread);
508     numberFormat->SetLocale(thread, localeStr.GetTaggedValue());
509 
510     // Set numberingSystemStr to UnicodeKeyWord "nu"
511     UErrorCode status = U_ZERO_ERROR;
512     if (!numberingSystemStr.empty()) {
513         if (JSLocale::IsWellNumberingSystem(numberingSystemStr)) {
514             icuLocale.setUnicodeKeywordValue("nu", numberingSystemStr, status);
515             ASSERT(U_SUCCESS(status));
516         }
517     }
518 
519     icu::number::LocalizedNumberFormatter icuNumberFormatter =
520         icu::number::NumberFormatter::withLocale(icuLocale).roundingMode(UNUM_ROUND_HALFUP);
521 
522     int32_t mnfdDefault = 0;
523     int32_t mxfdDefault = 0;
524     FractionDigitsOption fractionOptions =
525         SetNumberFormatUnitOptions(thread, numberFormat, optionsObject, &icuNumberFormatter);
526     RETURN_IF_ABRUPT_COMPLETION(thread);
527     mnfdDefault = fractionOptions.mnfdDefault;
528     mxfdDefault = fractionOptions.mxfdDefault;
529     UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay();
530 
531     // Trans unitDisplay option to ICU display option
532     UNumberUnitWidth uNumberUnitWidth;
533     switch (unitDisplay) {
534         case UnitDisplayOption::SHORT:
535             uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
536             break;
537         case UnitDisplayOption::NARROW:
538             uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
539             break;
540         case UnitDisplayOption::LONG:
541             // UNUM_UNIT_WIDTH_FULL_NAME Print the full name of the unit, without any abbreviations.
542             uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
543             break;
544         default:
545             LOG_ECMA(FATAL) << "this branch is unreachable";
546             UNREACHABLE();
547     }
548     icuNumberFormatter = icuNumberFormatter.unitWidth(uNumberUnitWidth);
549 
550     // 16. Let style be numberFormat.[[Style]].
551     StyleOption style = numberFormat->GetStyle();
552     if (style == StyleOption::PERCENT) {
553         icuNumberFormatter = icuNumberFormatter.unit(icu::MeasureUnit::getPercent()).
554             scale(icu::number::Scale::powerOfTen(2));  // means 10^2
555     }
556 
557     // 19. Let notation be ? GetOption(
558     //  options, "notation", "string", « "standard", "scientific", "engineering", "compact" », "standard").
559     property = globalConst->GetHandledNotationString();
560     auto notation = JSLocale::GetOptionOfString<NotationOption>(
561         thread, optionsObject, property,
562         {NotationOption::STANDARD, NotationOption::SCIENTIFIC, NotationOption::ENGINEERING, NotationOption::COMPACT},
563         {"standard", "scientific", "engineering", "compact"}, NotationOption::STANDARD);
564     RETURN_IF_ABRUPT_COMPLETION(thread);
565     numberFormat->SetNotation(notation);
566 
567     // 21. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
568     JSLocale::SetNumberFormatDigitOptions(thread, numberFormat, JSHandle<JSTaggedValue>::Cast(optionsObject),
569                                           mnfdDefault, mxfdDefault, notation);
570     icuNumberFormatter = SetICUFormatterDigitOptions(icuNumberFormatter, numberFormat);
571 
572     // 22. Let compactDisplay be ? GetOptionOfString(options, "compactDisplay", "string", « "short", "long" », "short").
573     property = globalConst->GetHandledCompactDisplayString();
574     auto compactDisplay = JSLocale::GetOptionOfString<CompactDisplayOption>(
575         thread, optionsObject, property, {CompactDisplayOption::SHORT, CompactDisplayOption::LONG}, {"short", "long"},
576         CompactDisplayOption::SHORT);
577     numberFormat->SetCompactDisplay(compactDisplay);
578 
579     // Trans NotationOption to ICU Noation and set to icuNumberFormatter
580     if (notation == NotationOption::COMPACT) {
581         switch (compactDisplay) {
582             case CompactDisplayOption::SHORT:
583                 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactShort());
584                 break;
585             case CompactDisplayOption::LONG:
586                 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactLong());
587                 break;
588             default:
589                 break;
590         }
591     }
592     switch (notation) {
593         case NotationOption::STANDARD:
594             icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::simple());
595             break;
596         case NotationOption::SCIENTIFIC:
597             icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::scientific());
598             break;
599         case NotationOption::ENGINEERING:
600             icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::engineering());
601             break;
602         default:
603             break;
604     }
605 
606     // 24. Let useGrouping be ? GetOption(options, "useGrouping", "boolean", undefined, true).
607     property = globalConst->GetHandledUserGroupingString();
608     bool useGrouping = false;
609     [[maybe_unused]] bool find = JSLocale::GetOptionOfBool(thread, optionsObject, property, true, &useGrouping);
610     RETURN_IF_ABRUPT_COMPLETION(thread);
611     JSHandle<JSTaggedValue> useGroupingValue(thread, JSTaggedValue(useGrouping));
612     numberFormat->SetUseGrouping(thread, useGroupingValue);
613 
614     // 25. Set numberFormat.[[UseGrouping]] to useGrouping.
615     if (!useGrouping) {
616         icuNumberFormatter = icuNumberFormatter.grouping(UNumberGroupingStrategy::UNUM_GROUPING_OFF);
617     }
618 
619     // 26. Let signDisplay be ?
620     //  GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero" », "auto").
621     property = globalConst->GetHandledSignDisplayString();
622     auto signDisplay = JSLocale::GetOptionOfString<SignDisplayOption>(
623         thread, optionsObject, property,
624         {SignDisplayOption::AUTO, SignDisplayOption::NEVER, SignDisplayOption::ALWAYS, SignDisplayOption::EXCEPTZERO},
625         {"auto", "never", "always", "exceptZero"}, SignDisplayOption::AUTO);
626     RETURN_IF_ABRUPT_COMPLETION(thread);
627     numberFormat->SetSignDisplay(signDisplay);
628 
629     // 27. Set numberFormat.[[SignDisplay]] to signDisplay.
630     // The default sign in ICU is UNUM_SIGN_AUTO which is mapped from
631     // SignDisplay::AUTO and CurrencySign::STANDARD so we can skip setting
632     // under that values for optimization.
633     CurrencySignOption currencySign = numberFormat->GetCurrencySign();
634 
635     // Trans SignDisPlayOption to ICU UNumberSignDisplay and set to icuNumberFormatter
636     switch (signDisplay) {
637         case SignDisplayOption::AUTO:
638             // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
639             if (currencySign == CurrencySignOption::ACCOUNTING) {
640                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING);
641             } else {
642                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_AUTO);
643             }
644             break;
645         case SignDisplayOption::NEVER:
646             icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_NEVER);
647             break;
648         case SignDisplayOption::ALWAYS:
649             // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
650             if (currencySign == CurrencySignOption::ACCOUNTING) {
651                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS);
652             } else {
653                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS);
654             }
655             break;
656         case SignDisplayOption::EXCEPTZERO:
657             // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
658             if (currencySign == CurrencySignOption::ACCOUNTING) {
659                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO);
660             } else {
661                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO);
662             }
663             break;
664         default:
665             break;
666     }
667 
668     if (forIcuCache) {
669         std::string cacheEntry =
670             locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString();
671         auto formatterPointer = new icu::number::LocalizedNumberFormatter(icuNumberFormatter);
672         thread->GetCurrentEcmaContext()->SetIcuFormatterToCache(IcuFormatterType::NUMBER_FORMATTER, cacheEntry,
673             formatterPointer, JSNumberFormat::FreeIcuNumberformat);
674     } else {
675         // Set numberFormat.[[IcuNumberForma]] to handleNumberFormatter
676         factory->NewJSIntlIcuData(numberFormat, icuNumberFormatter, JSNumberFormat::FreeIcuNumberformat);
677     }
678     // Set numberFormat.[[BoundFormat]] to undefinedValue
679     numberFormat->SetBoundFormat(thread, undefinedValue);
680 }
681 
682 // 12.1.3 CurrencyDigits ( currency )
CurrencyDigits(const icu::UnicodeString & currency)683 int32_t JSNumberFormat::CurrencyDigits(const icu::UnicodeString &currency)
684 {
685     UErrorCode status = U_ZERO_ERROR;
686     // If the ISO 4217 currency and funds code list contains currency as an alphabetic code,
687     // return the minor unit value corresponding to the currency from the list; otherwise, return 2.
688     int32_t fractionDigits =
689         ucurr_getDefaultFractionDigits(reinterpret_cast<const UChar *>(currency.getBuffer()), &status);
690     if (U_SUCCESS(status)) {
691         return fractionDigits;
692     }
693     return DEFAULT_FRACTION_DIGITS;
694 }
695 
GetCachedIcuNumberFormatter(JSThread * thread,const JSHandle<JSTaggedValue> & locales)696 icu::number::LocalizedNumberFormatter *JSNumberFormat::GetCachedIcuNumberFormatter(JSThread *thread,
697     const JSHandle<JSTaggedValue> &locales)
698 {
699     std::string cacheEntry = locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString();
700     void *cachedNumberFormatter = thread->GetCurrentEcmaContext()->GetIcuFormatterFromCache(
701         IcuFormatterType::NUMBER_FORMATTER, cacheEntry);
702     if (cachedNumberFormatter) {
703         return reinterpret_cast<icu::number::LocalizedNumberFormatter*>(cachedNumberFormatter);
704     }
705     return nullptr;
706 }
707 
708 // 12.1.8 FormatNumeric( numberFormat, x )
FormatNumeric(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,JSTaggedValue x)709 JSHandle<JSTaggedValue> JSNumberFormat::FormatNumeric(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
710                                                       JSTaggedValue x)
711 {
712     icu::number::LocalizedNumberFormatter *icuNumberFormat = numberFormat->GetIcuCallTarget();
713     ASSERT(icuNumberFormat != nullptr);
714     JSHandle<JSTaggedValue> res = FormatNumeric(thread, icuNumberFormat, x);
715     return res;
716 }
717 
FormatNumeric(JSThread * thread,const icu::number::LocalizedNumberFormatter * icuNumberFormat,JSTaggedValue x)718 JSHandle<JSTaggedValue> JSNumberFormat::FormatNumeric(JSThread *thread,
719                                                       const icu::number::LocalizedNumberFormatter *icuNumberFormat,
720                                                       JSTaggedValue x)
721 {
722     UErrorCode status = U_ZERO_ERROR;
723     icu::number::FormattedNumber formattedNumber;
724     if (x.IsBigInt()) {
725         JSHandle<BigInt> bigint(thread, x);
726         JSHandle<EcmaString> bigintStr = BigInt::ToString(thread, bigint);
727         std::string stdString = EcmaStringAccessor(bigintStr).ToStdString();
728         formattedNumber = icuNumberFormat->formatDecimal(icu::StringPiece(stdString), status);
729     } else {
730         double number = x.GetNumber();
731         formattedNumber = icuNumberFormat->formatDouble(number, status);
732     }
733     if (U_FAILURE(status)) {
734         JSHandle<JSTaggedValue> errorResult(thread, JSTaggedValue::Exception());
735         THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatter format failed", errorResult);
736     }
737     icu::UnicodeString result = formattedNumber.toString(status);
738     if (U_FAILURE(status)) {
739         JSHandle<JSTaggedValue> errorResult(thread, JSTaggedValue::Exception());
740         THROW_RANGE_ERROR_AND_RETURN(thread, "formatted number toString failed", errorResult);
741     }
742     JSHandle<EcmaString> stringValue = intl::LocaleHelper::UStringToString(thread, result);
743     return JSHandle<JSTaggedValue>::Cast(stringValue);
744 }
745 
GroupToParts(JSThread * thread,const icu::number::FormattedNumber & formatted,const JSHandle<JSArray> & receiver,const JSHandle<JSNumberFormat> & numberFormat,JSTaggedValue x)746 void GroupToParts(JSThread *thread, const icu::number::FormattedNumber &formatted, const JSHandle<JSArray> &receiver,
747                   const JSHandle<JSNumberFormat> &numberFormat, JSTaggedValue x)
748 {
749     UErrorCode status = U_ZERO_ERROR;
750     icu::UnicodeString formattedText = formatted.toString(status);
751     if (U_FAILURE(status)) {  // NOLINT(readability-implicit-bool-conversion)
752         THROW_TYPE_ERROR(thread, "formattedNumber toString failed");
753     }
754     ASSERT(x.IsNumber());
755 
756     StyleOption styleOption = numberFormat->GetStyle();
757 
758     icu::ConstrainedFieldPosition cfpo;
759     // Set constrainCategory to UFIELD_CATEGORY_NUMBER which is specified for UNumberFormatFields
760     cfpo.constrainCategory(UFIELD_CATEGORY_NUMBER);
761     auto globalConst = thread->GlobalConstants();
762     JSMutableHandle<JSTaggedValue> typeString(thread, JSTaggedValue::Undefined());
763     int index = 0;
764     int previousLimit = 0;
765     /**
766      * From ICU header file document @unumberformatter.h
767      * Sets a constraint on the field category.
768      *
769      * When this instance of ConstrainedFieldPosition is passed to FormattedValue#nextPosition,
770      * positions are skipped unless they have the given category.
771      *
772      * Any previously set constraints are cleared.
773      *
774      * For example, to loop over only the number-related fields:
775      *
776      *     ConstrainedFieldPosition cfpo;
777      *     cfpo.constrainCategory(UFIELDCATEGORY_NUMBER_FORMAT);
778      *     while (fmtval.nextPosition(cfpo, status)) {
779      *         // handle the number-related field position
780      *     }
781      */
782     bool lastFieldGroup = false;
783     int groupLeapLength = 0;
784     while (formatted.nextPosition(cfpo, status)) {
785         int32_t fieldId = cfpo.getField();
786         int32_t start = cfpo.getStart();
787         int32_t limit = cfpo.getLimit();
788         typeString.Update(globalConst->GetLiteralString());
789         // If start greater than previousLimit, means a literal type exists before number fields
790         // so add a literal type with value of formattedText.sub(0, start)
791         // Special case when fieldId is UNUM_GROUPING_SEPARATOR_FIELD
792         if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) {
793             JSHandle<EcmaString> substring =
794                 intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start);
795             typeString.Update(globalConst->GetIntegerString());
796             JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
797             RETURN_IF_ABRUPT_COMPLETION(thread);
798             index++;
799             {
800                 typeString.Update(JSLocale::GetNumberFieldType(thread, x, fieldId).GetTaggedValue());
801                 substring = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit);
802                 JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
803                 RETURN_IF_ABRUPT_COMPLETION(thread);
804                 index++;
805             }
806             lastFieldGroup = true;
807             groupLeapLength = start - previousLimit + 1;
808             previousLimit = limit;
809             continue;
810         } else if (start > previousLimit) {
811             JSHandle<EcmaString> substring =
812                 intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start);
813             JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
814             RETURN_IF_ABRUPT_COMPLETION(thread);
815             index++;
816         }
817         if (lastFieldGroup) {
818             start = start + groupLeapLength;
819             lastFieldGroup = false;
820         }
821         // Special case in ICU when style is unit and unit is percent
822         if (styleOption == StyleOption::UNIT && static_cast<UNumberFormatFields>(fieldId) == UNUM_PERCENT_FIELD) {
823             typeString.Update(globalConst->GetUnitString());
824         } else {
825             typeString.Update(JSLocale::GetNumberFieldType(thread, x, fieldId).GetTaggedValue());
826         }
827         JSHandle<EcmaString> substring = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit);
828         JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
829         RETURN_IF_ABRUPT_COMPLETION(thread);
830         index++;
831         previousLimit = limit;
832     }
833     // If iterated length is smaller than formattedText.length, means a literal type exists after number fields
834     // so add a literal type with value of formattedText.sub(previousLimit, formattedText.length)
835     if (formattedText.length() > previousLimit) {
836         typeString.Update(globalConst->GetLiteralString());
837         JSHandle<EcmaString> substring =
838             intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, formattedText.length());
839         JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
840     }
841 }
842 
843 // 12.1.9 FormatNumericToParts( numberFormat, x )
FormatNumericToParts(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,JSTaggedValue x)844 JSHandle<JSArray> JSNumberFormat::FormatNumericToParts(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
845                                                        JSTaggedValue x)
846 {
847     ASSERT(x.IsNumber());
848     icu::number::LocalizedNumberFormatter *icuNumberFormatter = numberFormat->GetIcuCallTarget();
849     ASSERT(icuNumberFormatter != nullptr);
850 
851     UErrorCode status = U_ZERO_ERROR;
852     double number = x.GetNumber();
853     icu::number::FormattedNumber formattedNumber = icuNumberFormatter->formatDouble(number, status);
854     if (U_FAILURE(status)) {
855         ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
856         JSHandle<JSArray> emptyArray = factory->NewJSArray();
857         THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatter format failed", emptyArray);
858     }
859 
860     JSHandle<JSTaggedValue> arr = JSArray::ArrayCreate(thread, JSTaggedNumber(0));
861     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
862     JSHandle<JSArray> result = JSHandle<JSArray>::Cast(arr);
863     GroupToParts(thread, formattedNumber, result, numberFormat, x);
864     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
865     return result;
866 }
867 
868 // 12.1.12 UnwrapNumberFormat( nf )
UnwrapNumberFormat(JSThread * thread,const JSHandle<JSTaggedValue> & nf)869 JSHandle<JSTaggedValue> JSNumberFormat::UnwrapNumberFormat(JSThread *thread, const JSHandle<JSTaggedValue> &nf)
870 {
871     // 1. Assert: Type(nf) is Object.
872     ASSERT(nf->IsJSObject());
873 
874     // 2. If nf does not have an [[InitializedNumberFormat]] internal slot and ?
875     //  InstanceofOperator(nf, %NumberFormat%) is true, then Let nf be ? Get(nf, %Intl%.[[FallbackSymbol]]).
876     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
877     bool hasInstance = JSObject::InstanceOf(thread, nf, env->GetNumberFormatFunction());
878     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSHandle<JSTaggedValue>(thread, JSTaggedValue::Undefined()));
879 
880     bool isJSNumberFormat = nf->IsJSNumberFormat();
881     // If nf does not have an [[InitializedNumberFormat]] internal slot and ?
882     // InstanceofOperator(nf, %NumberFormat%) is true, then
883     //      a. Let nf be ? Get(nf, %Intl%.[[FallbackSymbol]]).
884     if (!isJSNumberFormat && hasInstance) {
885         JSHandle<JSTaggedValue> key(thread, JSHandle<JSIntl>::Cast(env->GetIntlFunction())->GetFallbackSymbol());
886         OperationResult operationResult = JSTaggedValue::GetProperty(thread, nf, key);
887         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSHandle<JSTaggedValue>(thread, JSTaggedValue::Undefined()));
888         return operationResult.GetValue();
889     }
890     // 3. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
891     if (!isJSNumberFormat) {
892         THROW_TYPE_ERROR_AND_RETURN(thread, "this is not object",
893                                     JSHandle<JSTaggedValue>(thread, JSTaggedValue::Exception()));
894     }
895     return nf;
896 }
897 
GetAvailableLocales(JSThread * thread)898 JSHandle<TaggedArray> JSNumberFormat::GetAvailableLocales(JSThread *thread)
899 {
900     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
901     JSHandle<JSTaggedValue> numberFormatLocales = env->GetNumberFormatLocales();
902     if (!numberFormatLocales->IsUndefined()) {
903         return JSHandle<TaggedArray>::Cast(numberFormatLocales);
904     }
905     const char *key = "NumberElements";
906     const char *path = nullptr;
907     std::vector<std::string> availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, key, path);
908     JSHandle<TaggedArray> availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales);
909     env->SetNumberFormatLocales(thread, availableLocales);
910     return availableLocales;
911 }
912 
ResolvedOptions(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,const JSHandle<JSObject> & options)913 void JSNumberFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
914                                      const JSHandle<JSObject> &options)
915 {
916     // Table 5: Resolved Options of NumberFormat Instances
917     // Internal Slot                    Property
918     //    [[Locale]]                      "locale"
919     //    [[NumberingSystem]]             "numberingSystem"
920     //    [[Style]]                       "style"
921     //    [[Currency]]                    "currency"
922     //    [[CurrencyDisplay]]             "currencyDisplay"
923     //    [[CurrencySign]]                "currencySign"
924     //    [[Unit]]                        "unit"
925     //    [[UnitDisplay]]                 "unitDisplay"
926     //    [[MinimumIntegerDigits]]        "minimumIntegerDigits"
927     //    [[MinimumFractionDigits]]       "minimumFractionDigits"
928     //    [[MaximumFractionDigits]]       "maximumFractionDigits"
929     //    [[MinimumSignificantDigits]]    "minimumSignificantDigits"
930     //    [[MaximumSignificantDigits]]    "maximumSignificantDigits"
931     //    [[UseGrouping]]                 "useGrouping"
932     //    [[Notation]]                    "notation"
933     //    [[CompactDisplay]]              "compactDisplay"
934     //    [SignDisplay]]                  "signDisplay"
935     // [[Locale]]
936     auto globalConst = thread->GlobalConstants();
937     JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleString();
938     JSHandle<JSTaggedValue> locale(thread, numberFormat->GetLocale());
939     JSObject::CreateDataPropertyOrThrow(thread, options, property, locale);
940     RETURN_IF_ABRUPT_COMPLETION(thread);
941 
942     // [[NumberingSystem]]
943     JSHandle<JSTaggedValue> numberingSystem(thread, numberFormat->GetNumberingSystem());
944     if (numberingSystem->IsUndefined()) {
945         numberingSystem = globalConst->GetHandledLatnString();
946     }
947     property = globalConst->GetHandledNumberingSystemString();
948     JSObject::CreateDataPropertyOrThrow(thread, options, property, numberingSystem);
949     RETURN_IF_ABRUPT_COMPLETION(thread);
950 
951     // [[Style]]
952     StyleOption style = numberFormat->GetStyle();
953     property = globalConst->GetHandledStyleString();
954     JSHandle<JSTaggedValue> styleString = OptionToEcmaString(thread, style);
955     JSObject::CreateDataPropertyOrThrow(thread, options, property, styleString);
956     RETURN_IF_ABRUPT_COMPLETION(thread);
957 
958     // [[currency]]
959     JSHandle<JSTaggedValue> currency(thread, JSTaggedValue::Undefined());
960     // If style is not currency the currency should be undefined
961     if (style == StyleOption::CURRENCY) {
962         currency = JSHandle<JSTaggedValue>(thread, numberFormat->GetCurrency());
963     }
964     if (!currency->IsUndefined()) {  // NOLINT(readability-implicit-bool-conversion)
965         property = globalConst->GetHandledCurrencyString();
966         JSObject::CreateDataPropertyOrThrow(thread, options, property, currency);
967         RETURN_IF_ABRUPT_COMPLETION(thread);
968 
969         // [[CurrencyDisplay]]
970         property = globalConst->GetHandledCurrencyDisplayString();
971         CurrencyDisplayOption currencyDisplay = numberFormat->GetCurrencyDisplay();
972         JSHandle<JSTaggedValue> currencyDisplayString = OptionToEcmaString(thread, currencyDisplay);
973         JSObject::CreateDataPropertyOrThrow(thread, options, property, currencyDisplayString);
974         RETURN_IF_ABRUPT_COMPLETION(thread);
975 
976         // [[CurrencySign]]
977         property = globalConst->GetHandledCurrencySignString();
978         CurrencySignOption currencySign = numberFormat->GetCurrencySign();
979         JSHandle<JSTaggedValue> currencySignString = OptionToEcmaString(thread, currencySign);
980         JSObject::CreateDataPropertyOrThrow(thread, options, property, currencySignString);
981         RETURN_IF_ABRUPT_COMPLETION(thread);
982     }
983 
984     if (style == StyleOption::UNIT) {
985         JSHandle<JSTaggedValue> unit(thread, numberFormat->GetUnit());
986         if (!unit->IsUndefined()) {
987             // [[Unit]]
988             property = globalConst->GetHandledUnitString();
989             JSObject::CreateDataPropertyOrThrow(thread, options, property, unit);
990             RETURN_IF_ABRUPT_COMPLETION(thread);
991         }
992         // [[UnitDisplay]]
993         property = globalConst->GetHandledUnitDisplayString();
994         UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay();
995         JSHandle<JSTaggedValue> unitDisplayString = OptionToEcmaString(thread, unitDisplay);
996         JSObject::CreateDataPropertyOrThrow(thread, options, property, unitDisplayString);
997         RETURN_IF_ABRUPT_COMPLETION(thread);
998     }
999     // [[MinimumIntegerDigits]]
1000     property = globalConst->GetHandledMinimumIntegerDigitsString();
1001     JSHandle<JSTaggedValue> minimumIntegerDigits(thread, numberFormat->GetMinimumIntegerDigits());
1002     JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumIntegerDigits);
1003     RETURN_IF_ABRUPT_COMPLETION(thread);
1004 
1005     RoundingType roundingType = numberFormat->GetRoundingType();
1006     if (roundingType == RoundingType::SIGNIFICANTDIGITS) {
1007         // [[MinimumSignificantDigits]]
1008         property = globalConst->GetHandledMinimumSignificantDigitsString();
1009         JSHandle<JSTaggedValue> minimumSignificantDigits(thread, numberFormat->GetMinimumSignificantDigits());
1010         JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumSignificantDigits);
1011         RETURN_IF_ABRUPT_COMPLETION(thread);
1012         // [[MaximumSignificantDigits]]
1013         property = globalConst->GetHandledMaximumSignificantDigitsString();
1014         JSHandle<JSTaggedValue> maximumSignificantDigits(thread, numberFormat->GetMaximumSignificantDigits());
1015         JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumSignificantDigits);
1016         RETURN_IF_ABRUPT_COMPLETION(thread);
1017     } else {
1018         // [[MinimumFractionDigits]]
1019         property = globalConst->GetHandledMinimumFractionDigitsString();
1020         JSHandle<JSTaggedValue> minimumFractionDigits(thread, numberFormat->GetMinimumFractionDigits());
1021         JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumFractionDigits);
1022         RETURN_IF_ABRUPT_COMPLETION(thread);
1023         // [[MaximumFractionDigits]]
1024         property = globalConst->GetHandledMaximumFractionDigitsString();
1025         JSHandle<JSTaggedValue> maximumFractionDigits(thread, numberFormat->GetMaximumFractionDigits());
1026         JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumFractionDigits);
1027         RETURN_IF_ABRUPT_COMPLETION(thread);
1028     }
1029 
1030     // [[UseGrouping]]
1031     property = globalConst->GetHandledUserGroupingString();
1032     JSObject::CreateDataPropertyOrThrow(thread, options, property,
1033                                         JSHandle<JSTaggedValue>(thread, numberFormat->GetUseGrouping()));
1034     RETURN_IF_ABRUPT_COMPLETION(thread);
1035 
1036     // [[Notation]]
1037     property = globalConst->GetHandledNotationString();
1038     NotationOption notation = numberFormat->GetNotation();
1039     JSHandle<JSTaggedValue> notationString = OptionToEcmaString(thread, notation);
1040     JSObject::CreateDataPropertyOrThrow(thread, options, property, notationString);
1041     RETURN_IF_ABRUPT_COMPLETION(thread);
1042 
1043     // Only output compactDisplay when notation is compact.
1044     if (notation == NotationOption::COMPACT) {
1045         // [[CompactDisplay]]
1046         property = globalConst->GetHandledCompactDisplayString();
1047         CompactDisplayOption compactDisplay = numberFormat->GetCompactDisplay();
1048         JSHandle<JSTaggedValue> compactDisplayString = OptionToEcmaString(thread, compactDisplay);
1049         JSObject::CreateDataPropertyOrThrow(thread, options, property, compactDisplayString);
1050         RETURN_IF_ABRUPT_COMPLETION(thread);
1051     }
1052 
1053     // [[SignDisplay]]
1054     property = globalConst->GetHandledSignDisplayString();
1055     SignDisplayOption signDisplay = numberFormat->GetSignDisplay();
1056     JSHandle<JSTaggedValue> signDisplayString = OptionToEcmaString(thread, signDisplay);
1057     JSObject::CreateDataPropertyOrThrow(thread, options, property, signDisplayString);
1058 }
1059 }  // namespace panda::ecmascript