• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "ecmascript/js_number_format.h"
17 #include "ecmascript/js_function.h"
18 #include "ecmascript/object_factory-inl.h"
19 
20 namespace panda::ecmascript {
21 
22 const std::vector<StyleOption> JSNumberFormat::STYLE_OPTION = {
23     StyleOption::DECIMAL, StyleOption::PERCENT, StyleOption::CURRENCY, StyleOption::UNIT
24 };
25 const std::vector<std::string> JSNumberFormat::STYLE_OPTION_NAME = {
26     "decimal", "percent", "currency", "unit"
27 };
28 
29 const std::vector<CurrencyDisplayOption> JSNumberFormat::CURRENCY_DISPLAY_OPTION = {
30     CurrencyDisplayOption::CODE, CurrencyDisplayOption::SYMBOL,
31     CurrencyDisplayOption::NARROWSYMBOL, CurrencyDisplayOption::NAME
32 };
33 const std::vector<std::string> JSNumberFormat::CURRENCY_DISPLAY_OPTION_NAME = {
34     "code", "symbol", "narrowSymbol", "name"
35 };
36 
37 const std::vector<CurrencySignOption> JSNumberFormat::CURRENCY_SIGN_OPTION = {
38     CurrencySignOption::STANDARD, CurrencySignOption::ACCOUNTING
39 };
40 const std::vector<std::string> JSNumberFormat::CURRENCY_SIGN_OPTION_NAME = {"standard", "accounting"};
41 
42 const std::vector<UnitDisplayOption> JSNumberFormat::UNIT_DISPLAY_OPTION = {
43     UnitDisplayOption::SHORT, UnitDisplayOption::NARROW, UnitDisplayOption::LONG
44 };
45 const std::vector<std::string> JSNumberFormat::UNIT_DISPLAY_OPTION_NAME = {"short", "narrow", "long"};
46 
47 const std::vector<LocaleMatcherOption> JSNumberFormat::LOCALE_MATCHER_OPTION = {
48     LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT
49 };
50 const std::vector<std::string> JSNumberFormat::LOCALE_MATCHER_OPTION_NAME = {"lookup", "best fit"};
51 
52 const std::vector<NotationOption> JSNumberFormat::NOTATION_OPTION = {
53     NotationOption::STANDARD, NotationOption::SCIENTIFIC,
54     NotationOption::ENGINEERING, NotationOption::COMPACT
55 };
56 const std::vector<std::string> JSNumberFormat::NOTATION_OPTION_NAME = {
57     "standard", "scientific", "engineering", "compact"
58 };
59 
60 const std::vector<SignDisplayOption> JSNumberFormat::SIGN_DISPLAY_OPTION = {
61     SignDisplayOption::AUTO, SignDisplayOption::NEVER,
62     SignDisplayOption::ALWAYS, SignDisplayOption::EXCEPTZERO
63 };
64 const std::vector<std::string> JSNumberFormat::SIGN_DISPLAY_OPTION_NAME = {
65     "auto", "never", "always", "exceptZero"
66 };
67 
68 const std::vector<CompactDisplayOption> JSNumberFormat::COMPACT_DISPLAY_OPTION = {
69     CompactDisplayOption::SHORT, CompactDisplayOption::LONG
70 };
71 const std::vector<std::string> JSNumberFormat::COMPACT_DISPLAY_OPTION_NAME = {"short", "long"};
72 
73 const std::set<std::string> SANCTIONED_UNIT({ "acre", "bit", "byte", "celsius", "centimeter", "day", "degree",
74                                             "fahrenheit", "fluid-ounce", "foot", "gallon", "gigabit", "gigabyte",
75                                             "gram", "hectare", "hour", "inch", "kilobit", "kilobyte", "kilogram",
76                                             "kilometer", "liter", "megabit", "megabyte", "meter", "mile",
77                                             "mile-scandinavian", "millimeter", "milliliter", "millisecond",
78                                             "minute", "month", "ounce", "percent", "petabyte", "pound", "second",
79                                             "stone", "terabit", "terabyte", "week", "yard", "year" });
80 
OptionToEcmaString(JSThread * thread,StyleOption style)81 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, StyleOption style)
82 {
83     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
84     auto globalConst = thread->GlobalConstants();
85     switch (style) {
86         case StyleOption::DECIMAL:
87             result.Update(globalConst->GetHandledDecimalString().GetTaggedValue());
88             break;
89         case StyleOption::CURRENCY:
90             result.Update(globalConst->GetHandledCurrencyString().GetTaggedValue());
91             break;
92         case StyleOption::PERCENT:
93             result.Update(globalConst->GetHandledPercentString().GetTaggedValue());
94             break;
95         case StyleOption::UNIT:
96             result.Update(globalConst->GetHandledUnitString().GetTaggedValue());
97             break;
98         default:
99             LOG_ECMA(FATAL) << "this branch is unreachable";
100             UNREACHABLE();
101     }
102     return result;
103 }
104 
OptionToEcmaString(JSThread * thread,CurrencyDisplayOption currencyDisplay)105 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CurrencyDisplayOption currencyDisplay)
106 {
107     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
108     auto globalConst = thread->GlobalConstants();
109     switch (currencyDisplay) {
110         case CurrencyDisplayOption::CODE:
111             result.Update(globalConst->GetHandledCodeString().GetTaggedValue());
112             break;
113         case CurrencyDisplayOption::SYMBOL:
114             result.Update(globalConst->GetHandledSymbolString().GetTaggedValue());
115             break;
116         case CurrencyDisplayOption::NARROWSYMBOL:
117             result.Update(globalConst->GetHandledNarrowSymbolString().GetTaggedValue());
118             break;
119         case CurrencyDisplayOption::NAME:
120             result.Update(globalConst->GetHandledNameString().GetTaggedValue());
121             break;
122         default:
123             LOG_ECMA(FATAL) << "this branch is unreachable";
124             UNREACHABLE();
125     }
126     return result;
127 }
128 
OptionToEcmaString(JSThread * thread,CurrencySignOption currencySign)129 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CurrencySignOption currencySign)
130 {
131     auto globalConst = thread->GlobalConstants();
132     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
133     switch (currencySign) {
134         case CurrencySignOption::STANDARD:
135             result.Update(globalConst->GetHandledStandardString().GetTaggedValue());
136             break;
137         case CurrencySignOption::ACCOUNTING:
138             result.Update(globalConst->GetHandledAccountingString().GetTaggedValue());
139             break;
140         default:
141             LOG_ECMA(FATAL) << "this branch is unreachable";
142             UNREACHABLE();
143     }
144     return result;
145 }
146 
OptionToEcmaString(JSThread * thread,UnitDisplayOption unitDisplay)147 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, UnitDisplayOption unitDisplay)
148 {
149     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
150     auto globalConst = thread->GlobalConstants();
151     switch (unitDisplay) {
152         case UnitDisplayOption::SHORT:
153             result.Update(globalConst->GetHandledShortString().GetTaggedValue());
154             break;
155         case UnitDisplayOption::NARROW:
156             result.Update(globalConst->GetHandledNarrowString().GetTaggedValue());
157             break;
158         case UnitDisplayOption::LONG:
159             result.Update(globalConst->GetHandledLongString().GetTaggedValue());
160             break;
161         default:
162             LOG_ECMA(FATAL) << "this branch is unreachable";
163             UNREACHABLE();
164     }
165     return result;
166 }
167 
OptionToEcmaString(JSThread * thread,NotationOption notation)168 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, NotationOption notation)
169 {
170     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
171     auto globalConst = thread->GlobalConstants();
172     switch (notation) {
173         case NotationOption::STANDARD:
174             result.Update(globalConst->GetHandledStandardString().GetTaggedValue());
175             break;
176         case NotationOption::SCIENTIFIC:
177             result.Update(globalConst->GetHandledScientificString().GetTaggedValue());
178             break;
179         case NotationOption::ENGINEERING:
180             result.Update(globalConst->GetHandledEngineeringString().GetTaggedValue());
181             break;
182         case NotationOption::COMPACT:
183             result.Update(globalConst->GetHandledCompactString().GetTaggedValue());
184             break;
185         default:
186             LOG_ECMA(FATAL) << "this branch is unreachable";
187             UNREACHABLE();
188     }
189     return result;
190 }
191 
OptionToEcmaString(JSThread * thread,CompactDisplayOption compactDisplay)192 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CompactDisplayOption compactDisplay)
193 {
194     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
195     auto globalConst = thread->GlobalConstants();
196     switch (compactDisplay) {
197         case CompactDisplayOption::SHORT:
198             result.Update(globalConst->GetHandledShortString().GetTaggedValue());
199             break;
200         case CompactDisplayOption::LONG:
201             result.Update(globalConst->GetHandledLongString().GetTaggedValue());
202             break;
203         default:
204             LOG_ECMA(FATAL) << "this branch is unreachable";
205             UNREACHABLE();
206     }
207     return result;
208 }
209 
OptionToEcmaString(JSThread * thread,SignDisplayOption signDisplay)210 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, SignDisplayOption signDisplay)
211 {
212     JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
213     auto globalConst = thread->GlobalConstants();
214     switch (signDisplay) {
215         case SignDisplayOption::AUTO:
216             result.Update(globalConst->GetHandledAutoString().GetTaggedValue());
217             break;
218         case SignDisplayOption::ALWAYS:
219             result.Update(globalConst->GetHandledAlwaysString().GetTaggedValue());
220             break;
221         case SignDisplayOption::NEVER:
222             result.Update(globalConst->GetHandledNeverString().GetTaggedValue());
223             break;
224         case SignDisplayOption::EXCEPTZERO:
225             result.Update(globalConst->GetHandledExceptZeroString().GetTaggedValue());
226             break;
227         default:
228             LOG_ECMA(FATAL) << "this branch is unreachable";
229             UNREACHABLE();
230     }
231     return result;
232 }
233 
ToMeasureUnit(const std::string & sanctionedUnit)234 icu::MeasureUnit ToMeasureUnit(const std::string &sanctionedUnit)
235 {
236     UErrorCode status = U_ZERO_ERROR;
237     // Get All ICU measure unit
238     int32_t total = icu::MeasureUnit::getAvailable(nullptr, 0, status);
239     status = U_ZERO_ERROR;
240     std::vector<icu::MeasureUnit> units(total);
241     icu::MeasureUnit::getAvailable(units.data(), total, status);
242     ASSERT(U_SUCCESS(status));
243 
244     // Find measure unit according to sanctioned unit
245     //  then return measure unit
246     for (auto &unit : units) {
247         if (std::strcmp(sanctionedUnit.c_str(), unit.getSubtype()) == 0) {
248             return unit;
249         }
250     }
251     return icu::MeasureUnit();
252 }
253 
254 // ecma402 #sec-issanctionedsimpleunitidentifier
IsSanctionedSimpleUnitIdentifier(const std::string & unit)255 bool IsSanctionedSimpleUnitIdentifier(const std::string &unit)
256 {
257     // 1. If unitIdentifier is listed in sanctioned unit set, return true.
258     auto it = SANCTIONED_UNIT.find(unit);
259     if (it != SANCTIONED_UNIT.end()) {
260         return true;
261     }
262 
263     // 2. Else, Return false.
264     return false;
265 }
266 
267 // 6.5.1 IsWellFormedUnitIdentifier ( unitIdentifier )
IsWellFormedUnitIdentifier(const std::string & unit,icu::MeasureUnit & icuUnit,icu::MeasureUnit & icuPerUnit)268 bool IsWellFormedUnitIdentifier(const std::string &unit, icu::MeasureUnit &icuUnit, icu::MeasureUnit &icuPerUnit)
269 {
270     // 1. If the result of IsSanctionedSimpleUnitIdentifier(unitIdentifier) is true, then
271     //      a. Return true.
272     icu::MeasureUnit result = icu::MeasureUnit();
273     icu::MeasureUnit emptyUnit = icu::MeasureUnit();
274     auto pos = unit.find("-per-");
275     if (IsSanctionedSimpleUnitIdentifier(unit) && pos == std::string::npos) {
276         result = ToMeasureUnit(unit);
277         icuUnit = result;
278         icuPerUnit = emptyUnit;
279         return true;
280     }
281 
282     // 2. If the substring "-per-" does not occur exactly once in unitIdentifier,
283     //      a. then false
284     size_t afterPos = pos + JSNumberFormat::PERUNIT_STRING;
285     if (pos == std::string::npos || unit.find("-per-", afterPos) != std::string::npos) {
286         return false;
287     }
288 
289     // 3. Let numerator be the substring of unitIdentifier from the beginning to just before "-per-".
290     std::string numerator = unit.substr(0, pos);
291     // 4. If the result of IsSanctionedUnitIdentifier(numerator) is false, then
292     //      a. return false
293     if (IsSanctionedSimpleUnitIdentifier(numerator)) {
294         result = ToMeasureUnit(numerator);
295     } else {
296         return false;
297     }
298 
299     // 5. Let denominator be the substring of unitIdentifier from just after "-per-" to the end.
300     std::string denominator = unit.substr(pos + JSNumberFormat::PERUNIT_STRING);
301 
302     // 6. If the result of IsSanctionedUnitIdentifier(denominator) is false, then
303     //      a. Return false
304     icu::MeasureUnit perResult = icu::MeasureUnit();
305     if (IsSanctionedSimpleUnitIdentifier(denominator)) {
306         perResult = ToMeasureUnit(denominator);
307     } else {
308         return false;
309     }
310 
311     // 7. Return true.
312     icuUnit = result;
313     icuPerUnit = perResult;
314     return true;
315 }
316 
317 // 12.1.13 SetNumberFormatUnitOptions ( intlObj, options )
SetNumberFormatUnitOptions(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,const JSHandle<JSObject> & optionsObject,icu::number::LocalizedNumberFormatter * icuNumberFormatter)318 FractionDigitsOption SetNumberFormatUnitOptions(JSThread *thread,
319                                                 const JSHandle<JSNumberFormat> &numberFormat,
320                                                 const JSHandle<JSObject> &optionsObject,
321                                                 icu::number::LocalizedNumberFormatter *icuNumberFormatter)
322 {
323     auto globalConst = thread->GlobalConstants();
324     FractionDigitsOption fractionDigitsOption;
325     // 3. Let style be ? GetOption(options, "style", "string", « "decimal", "percent", "currency", "unit" », "decimal").
326     JSHandle<JSTaggedValue> property = globalConst->GetHandledStyleString();
327     auto style = JSLocale::GetOptionOfString<StyleOption>(
328         thread, optionsObject, property,
329         JSNumberFormat::STYLE_OPTION, JSNumberFormat::STYLE_OPTION_NAME,
330         StyleOption::DECIMAL);
331     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
332 
333     // 4. Set intlObj.[[Style]] to style.
334     numberFormat->SetStyle(style);
335 
336     // 5. Let currency be ? GetOption(options, "currency", "string", undefined, undefined).
337     property = globalConst->GetHandledCurrencyString();
338     JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
339     JSHandle<JSTaggedValue> currency =
340         JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
341     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
342 
343     // 6. If currency is not undefined, then
344     //    a. If the result of IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception.
345     if (!currency->IsUndefined()) {
346         JSHandle<EcmaString> currencyStr = JSHandle<EcmaString>::Cast(currency);
347         if (EcmaStringAccessor(currencyStr).IsUtf16()) {
348             THROW_RANGE_ERROR_AND_RETURN(thread, "not a utf-8", fractionDigitsOption);
349         }
350         std::string currencyCStr = intl::LocaleHelper::ConvertToStdString(thread, currencyStr);
351         if (!JSLocale::IsWellFormedCurrencyCode(currencyCStr)) {
352             THROW_RANGE_ERROR_AND_RETURN(thread, "not a wellformed code", fractionDigitsOption);
353         }
354     } else {
355         // 7. If style is "currency" and currency is undefined, throw a TypeError exception.
356         if (style == StyleOption::CURRENCY) {
357             THROW_TYPE_ERROR_AND_RETURN(thread, "style is currency but currency is undefined", fractionDigitsOption);
358         }
359     }
360 
361     // 8. Let currencyDisplay be ? GetOption(options, "currencyDisplay", "string",
362     //  « "code", "symbol", "narrowSymbol", "name" », "symbol").
363     property = globalConst->GetHandledCurrencyDisplayString();
364     auto currencyDisplay = JSLocale::GetOptionOfString<CurrencyDisplayOption>(
365         thread, optionsObject, property,
366         JSNumberFormat::CURRENCY_DISPLAY_OPTION, JSNumberFormat::CURRENCY_DISPLAY_OPTION_NAME,
367         CurrencyDisplayOption::SYMBOL);
368     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
369     numberFormat->SetCurrencyDisplay(currencyDisplay);
370 
371     // 9. Let currencySign be ? GetOption(options, "currencySign", "string", « "standard", "accounting" », "standard").
372     property = globalConst->GetHandledCurrencySignString();
373     auto currencySign = JSLocale::GetOptionOfString<CurrencySignOption>(
374         thread, optionsObject, property,
375         JSNumberFormat::CURRENCY_SIGN_OPTION, JSNumberFormat::CURRENCY_SIGN_OPTION_NAME,
376         CurrencySignOption::STANDARD);
377     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
378     numberFormat->SetCurrencySign(currencySign);
379 
380     // 10. Let unit be ? GetOption(options, "unit", "string", undefined, undefined).
381     property = globalConst->GetHandledUnitString();
382     JSHandle<JSTaggedValue> unit =
383         JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
384     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
385     numberFormat->SetUnit(thread, unit);
386 
387     // 11. If unit is not undefined, then
388     // If the result of IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception.
389     icu::MeasureUnit icuUnit;
390     icu::MeasureUnit icuPerUnit;
391     if (!unit->IsUndefined()) {
392         JSHandle<EcmaString> unitStr = JSHandle<EcmaString>::Cast(unit);
393         if (EcmaStringAccessor(unitStr).IsUtf16()) {
394             THROW_RANGE_ERROR_AND_RETURN(thread, "Unit input is illegal", fractionDigitsOption);
395         }
396         std::string str = intl::LocaleHelper::ConvertToStdString(thread, unitStr);
397         if (!IsWellFormedUnitIdentifier(str, icuUnit, icuPerUnit)) {
398             THROW_RANGE_ERROR_AND_RETURN(thread, "Unit input is illegal", fractionDigitsOption);
399         }
400     } else {
401         // 15.12. if style is "unit" and unit is undefined, throw a TypeError exception.
402         if (style == StyleOption::UNIT) {
403             THROW_TYPE_ERROR_AND_RETURN(thread, "style is unit but unit is undefined", fractionDigitsOption);
404         }
405     }
406 
407     // 13. Let unitDisplay be ? GetOption(options, "unitDisplay", "string", « "short", "narrow", "long" », "short").
408     property = globalConst->GetHandledUnitDisplayString();
409     auto unitDisplay = JSLocale::GetOptionOfString<UnitDisplayOption>(
410         thread, optionsObject, property,
411         JSNumberFormat::UNIT_DISPLAY_OPTION, JSNumberFormat::UNIT_DISPLAY_OPTION_NAME,
412         UnitDisplayOption::SHORT);
413     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
414     numberFormat->SetUnitDisplay(unitDisplay);
415 
416     // 14. If style is "currency", then
417     //      a. Let currency be the result of converting currency to upper case as specified in 6.1.
418     //      b. Set intlObj.[[Currency]] to currency.
419     //      c. Set intlObj.[[CurrencyDisplay]] to currencyDisplay.
420     //      d. Set intlObj.[[CurrencySign]] to currencySign.
421     icu::UnicodeString currencyUStr;
422     UErrorCode status = U_ZERO_ERROR;
423     if (style == StyleOption::CURRENCY) {
424         JSHandle<EcmaString> currencyStr = JSHandle<EcmaString>::Cast(currency);
425         std::string currencyCStr = intl::LocaleHelper::ConvertToStdString(thread, currencyStr);
426         std::transform(currencyCStr.begin(), currencyCStr.end(), currencyCStr.begin(), toupper);
427         ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
428         JSHandle<JSTaggedValue> currencyValue = JSHandle<JSTaggedValue>::Cast(factory->NewFromStdString(currencyCStr));
429         numberFormat->SetCurrency(thread, currencyValue);
430         currencyUStr = currencyCStr.c_str();
431         if (!currencyUStr.isEmpty()) {  // NOLINT(readability-implicit-bool-conversion)
432             *icuNumberFormatter = icuNumberFormatter->unit(icu::CurrencyUnit(currencyUStr.getBuffer(), status));
433             ASSERT(U_SUCCESS(status));
434             UNumberUnitWidth uNumberUnitWidth;
435             // Trans currencyDisplayOption to ICU format number display option
436             switch (currencyDisplay) {
437                 case CurrencyDisplayOption::CODE:
438                     uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE;
439                     break;
440                 case CurrencyDisplayOption::SYMBOL:
441                     uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
442                     break;
443                 case CurrencyDisplayOption::NARROWSYMBOL:
444                     uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
445                     break;
446                 case CurrencyDisplayOption::NAME:
447                     uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
448                     break;
449                 default:
450                     LOG_ECMA(FATAL) << "this branch is unreachable";
451                     UNREACHABLE();
452             }
453             *icuNumberFormatter = icuNumberFormatter->unitWidth(uNumberUnitWidth);
454         }
455     }
456 
457     // 15. If style is "unit", then
458     // if unit is not undefiend set unit to LocalizedNumberFormatter
459     //    then if perunit is not undefiend set perunit to LocalizedNumberFormatter
460     if (style == StyleOption::UNIT) {
461         icu::MeasureUnit emptyUnit = icu::MeasureUnit();
462         if (icuUnit != emptyUnit) {  // NOLINT(readability-implicit-bool-conversion)
463             *icuNumberFormatter = icuNumberFormatter->unit(icuUnit);
464         }
465         if (icuPerUnit != emptyUnit) {  // NOLINT(readability-implicit-bool-conversion)
466             *icuNumberFormatter = icuNumberFormatter->perUnit(icuPerUnit);
467         }
468     }
469 
470     // 17. If style is "currency", then
471     //      a. Let cDigits be CurrencyDigits(currency).
472     //      b. Let mnfdDefault be cDigits.
473     //      c. Let mxfdDefault be cDigits.
474     if (style == StyleOption::CURRENCY) {
475         int32_t cDigits = JSNumberFormat::CurrencyDigits(currencyUStr);
476         fractionDigitsOption.mnfdDefault = cDigits;
477         fractionDigitsOption.mxfdDefault = cDigits;
478     } else {
479         // 18. Else,
480         //      a. Let mnfdDefault be 0.
481         //      b. If style is "percent", then
482         //          i. Let mxfdDefault be 0.
483         //      c. else,
484         //          i. Let mxfdDefault be 3.
485         fractionDigitsOption.mnfdDefault = 0;
486         if (style == StyleOption::PERCENT) {
487             fractionDigitsOption.mxfdDefault = 0;
488         } else {
489             fractionDigitsOption.mxfdDefault = 3;  // Max decimal precision is 3
490         }
491     }
492     return fractionDigitsOption;
493 }
494 
495 // 12.1.2 InitializeNumberFormat ( numberFormat, locales, options )
496 // NOLINTNEXTLINE(readability-function-size)
InitializeNumberFormat(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,const JSHandle<JSTaggedValue> & locales,const JSHandle<JSTaggedValue> & options,bool forIcuCache)497 void JSNumberFormat::InitializeNumberFormat(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
498                                             const JSHandle<JSTaggedValue> &locales,
499                                             const JSHandle<JSTaggedValue> &options,
500                                             bool forIcuCache)
501 {
502     EcmaVM *ecmaVm = thread->GetEcmaVM();
503     ObjectFactory *factory = ecmaVm->GetFactory();
504     // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
505     JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
506     RETURN_IF_ABRUPT_COMPLETION(thread);
507     // 2. If options is undefined, then
508     //      a. Let options be ObjectCreate(null).
509     // 3. Else,
510     //      a. Let options be ? ToObject(options).
511     JSHandle<JSObject> optionsObject;
512     if (options->IsUndefined()) {
513         optionsObject = factory->CreateNullJSObject();
514     } else {
515         optionsObject = JSTaggedValue::ToObject(thread, options);
516         RETURN_IF_ABRUPT_COMPLETION(thread);
517     }
518 
519     auto globalConst = thread->GlobalConstants();
520     // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
521     JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
522     auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
523         thread, optionsObject, property,
524         JSNumberFormat::LOCALE_MATCHER_OPTION, JSNumberFormat::LOCALE_MATCHER_OPTION_NAME,
525         LocaleMatcherOption::BEST_FIT);
526     RETURN_IF_ABRUPT_COMPLETION(thread);
527 
528     // 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
529     property = globalConst->GetHandledNumberingSystemString();
530     JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
531     JSHandle<JSTaggedValue> numberingSystemTaggedValue =
532         JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
533     RETURN_IF_ABRUPT_COMPLETION(thread);
534     numberFormat->SetNumberingSystem(thread, numberingSystemTaggedValue);
535 
536     // 8. If numberingSystem is not undefined, then
537     //      a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal,
538     //      throw a RangeError exception. `(3*8alphanum) *("-" (3*8alphanum))`
539     std::string numberingSystemStr;
540     if (!numberingSystemTaggedValue->IsUndefined()) {
541         JSHandle<EcmaString> numberingSystemEcmaString = JSHandle<EcmaString>::Cast(numberingSystemTaggedValue);
542         if (EcmaStringAccessor(numberingSystemEcmaString).IsUtf16()) {
543             THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
544         }
545         numberingSystemStr = intl::LocaleHelper::ConvertToStdString(thread, numberingSystemEcmaString);
546         if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStr)) {
547             THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
548         }
549     }
550 
551     if (!numberingSystemStr.empty()) {
552         // If numberingSystem is invalid, Let numberingSystem be undefined.
553         if (!JSLocale::IsWellNumberingSystem(numberingSystemStr)) {
554             numberFormat->SetNumberingSystem(thread, undefinedValue);
555         }
556     }
557 
558     // 10. Let localeData be %NumberFormat%.[[LocaleData]].
559     JSHandle<TaggedArray> availableLocales;
560     if (requestedLocales->GetLength() == 0) {
561         availableLocales = factory->EmptyArray();
562     } else {
563         availableLocales = GetAvailableLocales(thread);
564     }
565 
566     // 11. Let r be ResolveLocale( %NumberFormat%.[[AvailableLocales]], requestedLocales, opt,
567     // %NumberFormat%.[[RelevantExtensionKeys]], localeData).
568     std::set<std::string> relevantExtensionKeys{"nu"};
569     ResolvedLocale r =
570         JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
571 
572     // 12. Set numberFormat.[[Locale]] to r.[[locale]].
573     icu::Locale icuLocale = r.localeData;
574     JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
575     RETURN_IF_ABRUPT_COMPLETION(thread);
576     numberFormat->SetLocale(thread, localeStr.GetTaggedValue());
577 
578     // Set numberingSystemStr to UnicodeKeyWord "nu"
579     UErrorCode status = U_ZERO_ERROR;
580     if (!numberingSystemStr.empty()) {
581         if (JSLocale::IsWellNumberingSystem(numberingSystemStr)) {
582             icuLocale.setUnicodeKeywordValue("nu", numberingSystemStr, status);
583             ASSERT(U_SUCCESS(status));
584         }
585     }
586 
587     icu::number::LocalizedNumberFormatter icuNumberFormatter =
588         icu::number::NumberFormatter::withLocale(icuLocale).roundingMode(UNUM_ROUND_HALFUP);
589 
590     int32_t mnfdDefault = 0;
591     int32_t mxfdDefault = 0;
592     FractionDigitsOption fractionOptions =
593         SetNumberFormatUnitOptions(thread, numberFormat, optionsObject, &icuNumberFormatter);
594     RETURN_IF_ABRUPT_COMPLETION(thread);
595     mnfdDefault = fractionOptions.mnfdDefault;
596     mxfdDefault = fractionOptions.mxfdDefault;
597     UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay();
598 
599     // Trans unitDisplay option to ICU display option
600     UNumberUnitWidth uNumberUnitWidth;
601     switch (unitDisplay) {
602         case UnitDisplayOption::SHORT:
603             uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
604             break;
605         case UnitDisplayOption::NARROW:
606             uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
607             break;
608         case UnitDisplayOption::LONG:
609             // UNUM_UNIT_WIDTH_FULL_NAME Print the full name of the unit, without any abbreviations.
610             uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
611             break;
612         default:
613             LOG_ECMA(FATAL) << "this branch is unreachable";
614             UNREACHABLE();
615     }
616     icuNumberFormatter = icuNumberFormatter.unitWidth(uNumberUnitWidth);
617 
618     // 16. Let style be numberFormat.[[Style]].
619     StyleOption style = numberFormat->GetStyle();
620     if (style == StyleOption::PERCENT) {
621         icuNumberFormatter = icuNumberFormatter.unit(icu::MeasureUnit::getPercent()).
622             scale(icu::number::Scale::powerOfTen(2));  // means 10^2
623     }
624 
625     // 19. Let notation be ? GetOption(
626     //  options, "notation", "string", « "standard", "scientific", "engineering", "compact" », "standard").
627     property = globalConst->GetHandledNotationString();
628     auto notation = JSLocale::GetOptionOfString<NotationOption>(
629         thread, optionsObject, property,
630         JSNumberFormat::NOTATION_OPTION, JSNumberFormat::NOTATION_OPTION_NAME,
631         NotationOption::STANDARD);
632     RETURN_IF_ABRUPT_COMPLETION(thread);
633     numberFormat->SetNotation(notation);
634 
635     // 21. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
636     JSLocale::SetNumberFormatDigitOptions(thread, numberFormat, JSHandle<JSTaggedValue>::Cast(optionsObject),
637                                           mnfdDefault, mxfdDefault, notation);
638     RETURN_IF_ABRUPT_COMPLETION(thread);
639     icuNumberFormatter = SetICUFormatterDigitOptions(thread, icuNumberFormatter, numberFormat);
640 
641     // 22. Let compactDisplay be ? GetOptionOfString(options, "compactDisplay", "string", « "short", "long" », "short").
642     property = globalConst->GetHandledCompactDisplayString();
643     auto compactDisplay = JSLocale::GetOptionOfString<CompactDisplayOption>(
644         thread, optionsObject, property,
645         JSNumberFormat::COMPACT_DISPLAY_OPTION, JSNumberFormat::COMPACT_DISPLAY_OPTION_NAME,
646         CompactDisplayOption::SHORT);
647     numberFormat->SetCompactDisplay(compactDisplay);
648 
649     // Trans NotationOption to ICU Noation and set to icuNumberFormatter
650     if (notation == NotationOption::COMPACT) {
651         switch (compactDisplay) {
652             case CompactDisplayOption::SHORT:
653                 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactShort());
654                 break;
655             case CompactDisplayOption::LONG:
656                 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactLong());
657                 break;
658             default:
659                 break;
660         }
661     }
662     switch (notation) {
663         case NotationOption::STANDARD:
664             icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::simple());
665             break;
666         case NotationOption::SCIENTIFIC:
667             icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::scientific());
668             break;
669         case NotationOption::ENGINEERING:
670             icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::engineering());
671             break;
672         default:
673             break;
674     }
675 
676     // 24. Let useGrouping be ? GetOption(options, "useGrouping", "boolean", undefined, true).
677     property = globalConst->GetHandledUserGroupingString();
678     bool useGrouping = false;
679     [[maybe_unused]] bool find = JSLocale::GetOptionOfBool(thread, optionsObject, property, true, &useGrouping);
680     RETURN_IF_ABRUPT_COMPLETION(thread);
681     JSHandle<JSTaggedValue> useGroupingValue(thread, JSTaggedValue(useGrouping));
682     numberFormat->SetUseGrouping(thread, useGroupingValue);
683 
684     // 25. Set numberFormat.[[UseGrouping]] to useGrouping.
685     if (!useGrouping) {
686         icuNumberFormatter = icuNumberFormatter.grouping(UNumberGroupingStrategy::UNUM_GROUPING_OFF);
687     }
688 
689     // 26. Let signDisplay be ?
690     //  GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero" », "auto").
691     property = globalConst->GetHandledSignDisplayString();
692     auto signDisplay = JSLocale::GetOptionOfString<SignDisplayOption>(
693         thread, optionsObject, property,
694         JSNumberFormat::SIGN_DISPLAY_OPTION, JSNumberFormat::SIGN_DISPLAY_OPTION_NAME,
695         SignDisplayOption::AUTO);
696     RETURN_IF_ABRUPT_COMPLETION(thread);
697     numberFormat->SetSignDisplay(signDisplay);
698 
699     // 27. Set numberFormat.[[SignDisplay]] to signDisplay.
700     // The default sign in ICU is UNUM_SIGN_AUTO which is mapped from
701     // SignDisplay::AUTO and CurrencySign::STANDARD so we can skip setting
702     // under that values for optimization.
703     CurrencySignOption currencySign = numberFormat->GetCurrencySign();
704 
705     // Trans SignDisPlayOption to ICU UNumberSignDisplay and set to icuNumberFormatter
706     switch (signDisplay) {
707         case SignDisplayOption::AUTO:
708             // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
709             if (currencySign == CurrencySignOption::ACCOUNTING) {
710                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING);
711             } else {
712                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_AUTO);
713             }
714             break;
715         case SignDisplayOption::NEVER:
716             icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_NEVER);
717             break;
718         case SignDisplayOption::ALWAYS:
719             // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
720             if (currencySign == CurrencySignOption::ACCOUNTING) {
721                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS);
722             } else {
723                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS);
724             }
725             break;
726         case SignDisplayOption::EXCEPTZERO:
727             // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
728             if (currencySign == CurrencySignOption::ACCOUNTING) {
729                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO);
730             } else {
731                 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO);
732             }
733             break;
734         default:
735             break;
736     }
737 
738     if (forIcuCache) {
739         std::string cacheEntry =
740             locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString(thread);
741         auto formatterPointer = new icu::number::LocalizedNumberFormatter(icuNumberFormatter);
742         ecmaVm->GetIntlCache().SetIcuFormatterToCache(IcuFormatterType::NUMBER_FORMATTER, cacheEntry,
743             formatterPointer, JSNumberFormat::FreeIcuNumberformat);
744     } else {
745         // Set numberFormat.[[IcuNumberForma]] to handleNumberFormatter
746         factory->NewJSIntlIcuData(numberFormat, icuNumberFormatter, JSNumberFormat::FreeIcuNumberformat);
747     }
748     // Set numberFormat.[[BoundFormat]] to undefinedValue
749     numberFormat->SetBoundFormat(thread, undefinedValue);
750 }
751 
752 // 12.1.3 CurrencyDigits ( currency )
CurrencyDigits(const icu::UnicodeString & currency)753 int32_t JSNumberFormat::CurrencyDigits(const icu::UnicodeString &currency)
754 {
755     UErrorCode status = U_ZERO_ERROR;
756     // If the ISO 4217 currency and funds code list contains currency as an alphabetic code,
757     // return the minor unit value corresponding to the currency from the list; otherwise, return 2.
758     int32_t fractionDigits =
759         ucurr_getDefaultFractionDigits(reinterpret_cast<const UChar *>(currency.getBuffer()), &status);
760     if (U_SUCCESS(status)) {
761         return fractionDigits;
762     }
763     return JSNumberFormat::DEFAULT_FRACTION_DIGITS;
764 }
765 
GetCachedIcuNumberFormatter(JSThread * thread,const JSHandle<JSTaggedValue> & locales)766 icu::number::LocalizedNumberFormatter *JSNumberFormat::GetCachedIcuNumberFormatter(JSThread *thread,
767     const JSHandle<JSTaggedValue> &locales)
768 {
769     std::string cacheEntry =
770         locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString(thread);
771     void *cachedNumberFormatter = thread->GetEcmaVM()->GetIntlCache().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(thread);
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(thread);
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(thread);
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(thread);
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(thread));
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(thread));
1019     JSObject::CreateDataPropertyOrThrow(thread, options, property, locale);
1020     RETURN_IF_ABRUPT_COMPLETION(thread);
1021 
1022     // [[NumberingSystem]]
1023     JSHandle<JSTaggedValue> numberingSystem(thread, numberFormat->GetNumberingSystem(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread)));
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
1155