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