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