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