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