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