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