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 // 2. If options is undefined, then
448 // a. Let options be ObjectCreate(null).
449 // 3. Else,
450 // a. Let options be ? ToObject(options).
451 JSHandle<JSObject> optionsObject;
452 if (options->IsUndefined()) {
453 optionsObject = factory->CreateNullJSObject();
454 } else {
455 optionsObject = JSTaggedValue::ToObject(thread, options);
456 RETURN_IF_ABRUPT_COMPLETION(thread);
457 }
458
459 auto globalConst = thread->GlobalConstants();
460 // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
461 JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
462 auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
463 thread, optionsObject, property, {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
464 {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
465 RETURN_IF_ABRUPT_COMPLETION(thread);
466
467 // 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
468 property = globalConst->GetHandledNumberingSystemString();
469 JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
470 JSHandle<JSTaggedValue> numberingSystemTaggedValue =
471 JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
472 RETURN_IF_ABRUPT_COMPLETION(thread);
473 numberFormat->SetNumberingSystem(thread, numberingSystemTaggedValue);
474
475 // 8. If numberingSystem is not undefined, then
476 // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal,
477 // throw a RangeError exception. `(3*8alphanum) *("-" (3*8alphanum))`
478 std::string numberingSystemStr;
479 if (!numberingSystemTaggedValue->IsUndefined()) {
480 JSHandle<EcmaString> numberingSystemEcmaString = JSHandle<EcmaString>::Cast(numberingSystemTaggedValue);
481 if (EcmaStringAccessor(numberingSystemEcmaString).IsUtf16()) {
482 THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
483 }
484 numberingSystemStr = intl::LocaleHelper::ConvertToStdString(numberingSystemEcmaString);
485 if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStr)) {
486 THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
487 }
488 }
489
490 // 10. Let localeData be %NumberFormat%.[[LocaleData]].
491 JSHandle<TaggedArray> availableLocales;
492 if (requestedLocales->GetLength() == 0) {
493 availableLocales = factory->EmptyArray();
494 } else {
495 availableLocales = GetAvailableLocales(thread);
496 }
497
498 // 11. Let r be ResolveLocale( %NumberFormat%.[[AvailableLocales]], requestedLocales, opt,
499 // %NumberFormat%.[[RelevantExtensionKeys]], localeData).
500 std::set<std::string> relevantExtensionKeys{"nu"};
501 ResolvedLocale r =
502 JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
503
504 // 12. Set numberFormat.[[Locale]] to r.[[locale]].
505 icu::Locale icuLocale = r.localeData;
506 JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
507 RETURN_IF_ABRUPT_COMPLETION(thread);
508 numberFormat->SetLocale(thread, localeStr.GetTaggedValue());
509
510 // Set numberingSystemStr to UnicodeKeyWord "nu"
511 UErrorCode status = U_ZERO_ERROR;
512 if (!numberingSystemStr.empty()) {
513 if (JSLocale::IsWellNumberingSystem(numberingSystemStr)) {
514 icuLocale.setUnicodeKeywordValue("nu", numberingSystemStr, status);
515 ASSERT(U_SUCCESS(status));
516 }
517 }
518
519 icu::number::LocalizedNumberFormatter icuNumberFormatter =
520 icu::number::NumberFormatter::withLocale(icuLocale).roundingMode(UNUM_ROUND_HALFUP);
521
522 int32_t mnfdDefault = 0;
523 int32_t mxfdDefault = 0;
524 FractionDigitsOption fractionOptions =
525 SetNumberFormatUnitOptions(thread, numberFormat, optionsObject, &icuNumberFormatter);
526 RETURN_IF_ABRUPT_COMPLETION(thread);
527 mnfdDefault = fractionOptions.mnfdDefault;
528 mxfdDefault = fractionOptions.mxfdDefault;
529 UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay();
530
531 // Trans unitDisplay option to ICU display option
532 UNumberUnitWidth uNumberUnitWidth;
533 switch (unitDisplay) {
534 case UnitDisplayOption::SHORT:
535 uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
536 break;
537 case UnitDisplayOption::NARROW:
538 uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
539 break;
540 case UnitDisplayOption::LONG:
541 // UNUM_UNIT_WIDTH_FULL_NAME Print the full name of the unit, without any abbreviations.
542 uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
543 break;
544 default:
545 LOG_ECMA(FATAL) << "this branch is unreachable";
546 UNREACHABLE();
547 }
548 icuNumberFormatter = icuNumberFormatter.unitWidth(uNumberUnitWidth);
549
550 // 16. Let style be numberFormat.[[Style]].
551 StyleOption style = numberFormat->GetStyle();
552 if (style == StyleOption::PERCENT) {
553 icuNumberFormatter = icuNumberFormatter.unit(icu::MeasureUnit::getPercent()).
554 scale(icu::number::Scale::powerOfTen(2)); // means 10^2
555 }
556
557 // 19. Let notation be ? GetOption(
558 // options, "notation", "string", « "standard", "scientific", "engineering", "compact" », "standard").
559 property = globalConst->GetHandledNotationString();
560 auto notation = JSLocale::GetOptionOfString<NotationOption>(
561 thread, optionsObject, property,
562 {NotationOption::STANDARD, NotationOption::SCIENTIFIC, NotationOption::ENGINEERING, NotationOption::COMPACT},
563 {"standard", "scientific", "engineering", "compact"}, NotationOption::STANDARD);
564 RETURN_IF_ABRUPT_COMPLETION(thread);
565 numberFormat->SetNotation(notation);
566
567 // 21. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
568 JSLocale::SetNumberFormatDigitOptions(thread, numberFormat, JSHandle<JSTaggedValue>::Cast(optionsObject),
569 mnfdDefault, mxfdDefault, notation);
570 icuNumberFormatter = SetICUFormatterDigitOptions(icuNumberFormatter, numberFormat);
571
572 // 22. Let compactDisplay be ? GetOptionOfString(options, "compactDisplay", "string", « "short", "long" », "short").
573 property = globalConst->GetHandledCompactDisplayString();
574 auto compactDisplay = JSLocale::GetOptionOfString<CompactDisplayOption>(
575 thread, optionsObject, property, {CompactDisplayOption::SHORT, CompactDisplayOption::LONG}, {"short", "long"},
576 CompactDisplayOption::SHORT);
577 numberFormat->SetCompactDisplay(compactDisplay);
578
579 // Trans NotationOption to ICU Noation and set to icuNumberFormatter
580 if (notation == NotationOption::COMPACT) {
581 switch (compactDisplay) {
582 case CompactDisplayOption::SHORT:
583 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactShort());
584 break;
585 case CompactDisplayOption::LONG:
586 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactLong());
587 break;
588 default:
589 break;
590 }
591 }
592 switch (notation) {
593 case NotationOption::STANDARD:
594 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::simple());
595 break;
596 case NotationOption::SCIENTIFIC:
597 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::scientific());
598 break;
599 case NotationOption::ENGINEERING:
600 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::engineering());
601 break;
602 default:
603 break;
604 }
605
606 // 24. Let useGrouping be ? GetOption(options, "useGrouping", "boolean", undefined, true).
607 property = globalConst->GetHandledUserGroupingString();
608 bool useGrouping = false;
609 [[maybe_unused]] bool find = JSLocale::GetOptionOfBool(thread, optionsObject, property, true, &useGrouping);
610 RETURN_IF_ABRUPT_COMPLETION(thread);
611 JSHandle<JSTaggedValue> useGroupingValue(thread, JSTaggedValue(useGrouping));
612 numberFormat->SetUseGrouping(thread, useGroupingValue);
613
614 // 25. Set numberFormat.[[UseGrouping]] to useGrouping.
615 if (!useGrouping) {
616 icuNumberFormatter = icuNumberFormatter.grouping(UNumberGroupingStrategy::UNUM_GROUPING_OFF);
617 }
618
619 // 26. Let signDisplay be ?
620 // GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero" », "auto").
621 property = globalConst->GetHandledSignDisplayString();
622 auto signDisplay = JSLocale::GetOptionOfString<SignDisplayOption>(
623 thread, optionsObject, property,
624 {SignDisplayOption::AUTO, SignDisplayOption::NEVER, SignDisplayOption::ALWAYS, SignDisplayOption::EXCEPTZERO},
625 {"auto", "never", "always", "exceptZero"}, SignDisplayOption::AUTO);
626 RETURN_IF_ABRUPT_COMPLETION(thread);
627 numberFormat->SetSignDisplay(signDisplay);
628
629 // 27. Set numberFormat.[[SignDisplay]] to signDisplay.
630 // The default sign in ICU is UNUM_SIGN_AUTO which is mapped from
631 // SignDisplay::AUTO and CurrencySign::STANDARD so we can skip setting
632 // under that values for optimization.
633 CurrencySignOption currencySign = numberFormat->GetCurrencySign();
634
635 // Trans SignDisPlayOption to ICU UNumberSignDisplay and set to icuNumberFormatter
636 switch (signDisplay) {
637 case SignDisplayOption::AUTO:
638 // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
639 if (currencySign == CurrencySignOption::ACCOUNTING) {
640 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING);
641 } else {
642 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_AUTO);
643 }
644 break;
645 case SignDisplayOption::NEVER:
646 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_NEVER);
647 break;
648 case SignDisplayOption::ALWAYS:
649 // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
650 if (currencySign == CurrencySignOption::ACCOUNTING) {
651 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS);
652 } else {
653 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS);
654 }
655 break;
656 case SignDisplayOption::EXCEPTZERO:
657 // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
658 if (currencySign == CurrencySignOption::ACCOUNTING) {
659 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO);
660 } else {
661 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO);
662 }
663 break;
664 default:
665 break;
666 }
667
668 if (forIcuCache) {
669 std::string cacheEntry =
670 locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString();
671 auto formatterPointer = new icu::number::LocalizedNumberFormatter(icuNumberFormatter);
672 thread->GetCurrentEcmaContext()->SetIcuFormatterToCache(IcuFormatterType::NUMBER_FORMATTER, cacheEntry,
673 formatterPointer, JSNumberFormat::FreeIcuNumberformat);
674 } else {
675 // Set numberFormat.[[IcuNumberForma]] to handleNumberFormatter
676 factory->NewJSIntlIcuData(numberFormat, icuNumberFormatter, JSNumberFormat::FreeIcuNumberformat);
677 }
678 // Set numberFormat.[[BoundFormat]] to undefinedValue
679 numberFormat->SetBoundFormat(thread, undefinedValue);
680 }
681
682 // 12.1.3 CurrencyDigits ( currency )
CurrencyDigits(const icu::UnicodeString & currency)683 int32_t JSNumberFormat::CurrencyDigits(const icu::UnicodeString ¤cy)
684 {
685 UErrorCode status = U_ZERO_ERROR;
686 // If the ISO 4217 currency and funds code list contains currency as an alphabetic code,
687 // return the minor unit value corresponding to the currency from the list; otherwise, return 2.
688 int32_t fractionDigits =
689 ucurr_getDefaultFractionDigits(reinterpret_cast<const UChar *>(currency.getBuffer()), &status);
690 if (U_SUCCESS(status)) {
691 return fractionDigits;
692 }
693 return DEFAULT_FRACTION_DIGITS;
694 }
695
GetCachedIcuNumberFormatter(JSThread * thread,const JSHandle<JSTaggedValue> & locales)696 icu::number::LocalizedNumberFormatter *JSNumberFormat::GetCachedIcuNumberFormatter(JSThread *thread,
697 const JSHandle<JSTaggedValue> &locales)
698 {
699 std::string cacheEntry = locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString();
700 void *cachedNumberFormatter = thread->GetCurrentEcmaContext()->GetIcuFormatterFromCache(
701 IcuFormatterType::NUMBER_FORMATTER, cacheEntry);
702 if (cachedNumberFormatter) {
703 return reinterpret_cast<icu::number::LocalizedNumberFormatter*>(cachedNumberFormatter);
704 }
705 return nullptr;
706 }
707
708 // 12.1.8 FormatNumeric( numberFormat, x )
FormatNumeric(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,JSTaggedValue x)709 JSHandle<JSTaggedValue> JSNumberFormat::FormatNumeric(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
710 JSTaggedValue x)
711 {
712 icu::number::LocalizedNumberFormatter *icuNumberFormat = numberFormat->GetIcuCallTarget();
713 ASSERT(icuNumberFormat != nullptr);
714 JSHandle<JSTaggedValue> res = FormatNumeric(thread, icuNumberFormat, x);
715 return res;
716 }
717
FormatNumeric(JSThread * thread,const icu::number::LocalizedNumberFormatter * icuNumberFormat,JSTaggedValue x)718 JSHandle<JSTaggedValue> JSNumberFormat::FormatNumeric(JSThread *thread,
719 const icu::number::LocalizedNumberFormatter *icuNumberFormat,
720 JSTaggedValue x)
721 {
722 UErrorCode status = U_ZERO_ERROR;
723 icu::number::FormattedNumber formattedNumber;
724 if (x.IsBigInt()) {
725 JSHandle<BigInt> bigint(thread, x);
726 JSHandle<EcmaString> bigintStr = BigInt::ToString(thread, bigint);
727 std::string stdString = EcmaStringAccessor(bigintStr).ToStdString();
728 formattedNumber = icuNumberFormat->formatDecimal(icu::StringPiece(stdString), status);
729 } else {
730 double number = x.GetNumber();
731 formattedNumber = icuNumberFormat->formatDouble(number, status);
732 }
733 if (U_FAILURE(status)) {
734 JSHandle<JSTaggedValue> errorResult(thread, JSTaggedValue::Exception());
735 THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatter format failed", errorResult);
736 }
737 icu::UnicodeString result = formattedNumber.toString(status);
738 if (U_FAILURE(status)) {
739 JSHandle<JSTaggedValue> errorResult(thread, JSTaggedValue::Exception());
740 THROW_RANGE_ERROR_AND_RETURN(thread, "formatted number toString failed", errorResult);
741 }
742 JSHandle<EcmaString> stringValue = intl::LocaleHelper::UStringToString(thread, result);
743 return JSHandle<JSTaggedValue>::Cast(stringValue);
744 }
745
GroupToParts(JSThread * thread,const icu::number::FormattedNumber & formatted,const JSHandle<JSArray> & receiver,const JSHandle<JSNumberFormat> & numberFormat,JSTaggedValue x)746 void GroupToParts(JSThread *thread, const icu::number::FormattedNumber &formatted, const JSHandle<JSArray> &receiver,
747 const JSHandle<JSNumberFormat> &numberFormat, JSTaggedValue x)
748 {
749 UErrorCode status = U_ZERO_ERROR;
750 icu::UnicodeString formattedText = formatted.toString(status);
751 if (U_FAILURE(status)) { // NOLINT(readability-implicit-bool-conversion)
752 THROW_TYPE_ERROR(thread, "formattedNumber toString failed");
753 }
754 ASSERT(x.IsNumber());
755
756 StyleOption styleOption = numberFormat->GetStyle();
757
758 icu::ConstrainedFieldPosition cfpo;
759 // Set constrainCategory to UFIELD_CATEGORY_NUMBER which is specified for UNumberFormatFields
760 cfpo.constrainCategory(UFIELD_CATEGORY_NUMBER);
761 auto globalConst = thread->GlobalConstants();
762 JSMutableHandle<JSTaggedValue> typeString(thread, JSTaggedValue::Undefined());
763 int index = 0;
764 int previousLimit = 0;
765 /**
766 * From ICU header file document @unumberformatter.h
767 * Sets a constraint on the field category.
768 *
769 * When this instance of ConstrainedFieldPosition is passed to FormattedValue#nextPosition,
770 * positions are skipped unless they have the given category.
771 *
772 * Any previously set constraints are cleared.
773 *
774 * For example, to loop over only the number-related fields:
775 *
776 * ConstrainedFieldPosition cfpo;
777 * cfpo.constrainCategory(UFIELDCATEGORY_NUMBER_FORMAT);
778 * while (fmtval.nextPosition(cfpo, status)) {
779 * // handle the number-related field position
780 * }
781 */
782 bool lastFieldGroup = false;
783 int groupLeapLength = 0;
784 while (formatted.nextPosition(cfpo, status)) {
785 int32_t fieldId = cfpo.getField();
786 int32_t start = cfpo.getStart();
787 int32_t limit = cfpo.getLimit();
788 typeString.Update(globalConst->GetLiteralString());
789 // If start greater than previousLimit, means a literal type exists before number fields
790 // so add a literal type with value of formattedText.sub(0, start)
791 // Special case when fieldId is UNUM_GROUPING_SEPARATOR_FIELD
792 if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) {
793 JSHandle<EcmaString> substring =
794 intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start);
795 typeString.Update(globalConst->GetIntegerString());
796 JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
797 RETURN_IF_ABRUPT_COMPLETION(thread);
798 index++;
799 {
800 typeString.Update(JSLocale::GetNumberFieldType(thread, x, fieldId).GetTaggedValue());
801 substring = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit);
802 JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
803 RETURN_IF_ABRUPT_COMPLETION(thread);
804 index++;
805 }
806 lastFieldGroup = true;
807 groupLeapLength = start - previousLimit + 1;
808 previousLimit = limit;
809 continue;
810 } else if (start > previousLimit) {
811 JSHandle<EcmaString> substring =
812 intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start);
813 JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
814 RETURN_IF_ABRUPT_COMPLETION(thread);
815 index++;
816 }
817 if (lastFieldGroup) {
818 start = start + groupLeapLength;
819 lastFieldGroup = false;
820 }
821 // Special case in ICU when style is unit and unit is percent
822 if (styleOption == StyleOption::UNIT && static_cast<UNumberFormatFields>(fieldId) == UNUM_PERCENT_FIELD) {
823 typeString.Update(globalConst->GetUnitString());
824 } else {
825 typeString.Update(JSLocale::GetNumberFieldType(thread, x, fieldId).GetTaggedValue());
826 }
827 JSHandle<EcmaString> substring = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit);
828 JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
829 RETURN_IF_ABRUPT_COMPLETION(thread);
830 index++;
831 previousLimit = limit;
832 }
833 // If iterated length is smaller than formattedText.length, means a literal type exists after number fields
834 // so add a literal type with value of formattedText.sub(previousLimit, formattedText.length)
835 if (formattedText.length() > previousLimit) {
836 typeString.Update(globalConst->GetLiteralString());
837 JSHandle<EcmaString> substring =
838 intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, formattedText.length());
839 JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
840 }
841 }
842
843 // 12.1.9 FormatNumericToParts( numberFormat, x )
FormatNumericToParts(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,JSTaggedValue x)844 JSHandle<JSArray> JSNumberFormat::FormatNumericToParts(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
845 JSTaggedValue x)
846 {
847 ASSERT(x.IsNumber());
848 icu::number::LocalizedNumberFormatter *icuNumberFormatter = numberFormat->GetIcuCallTarget();
849 ASSERT(icuNumberFormatter != nullptr);
850
851 UErrorCode status = U_ZERO_ERROR;
852 double number = x.GetNumber();
853 icu::number::FormattedNumber formattedNumber = icuNumberFormatter->formatDouble(number, status);
854 if (U_FAILURE(status)) {
855 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
856 JSHandle<JSArray> emptyArray = factory->NewJSArray();
857 THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatter format failed", emptyArray);
858 }
859
860 JSHandle<JSTaggedValue> arr = JSArray::ArrayCreate(thread, JSTaggedNumber(0));
861 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
862 JSHandle<JSArray> result = JSHandle<JSArray>::Cast(arr);
863 GroupToParts(thread, formattedNumber, result, numberFormat, x);
864 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
865 return result;
866 }
867
868 // 12.1.12 UnwrapNumberFormat( nf )
UnwrapNumberFormat(JSThread * thread,const JSHandle<JSTaggedValue> & nf)869 JSHandle<JSTaggedValue> JSNumberFormat::UnwrapNumberFormat(JSThread *thread, const JSHandle<JSTaggedValue> &nf)
870 {
871 // 1. Assert: Type(nf) is Object.
872 ASSERT(nf->IsJSObject());
873
874 // 2. If nf does not have an [[InitializedNumberFormat]] internal slot and ?
875 // InstanceofOperator(nf, %NumberFormat%) is true, then Let nf be ? Get(nf, %Intl%.[[FallbackSymbol]]).
876 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
877 bool hasInstance = JSObject::InstanceOf(thread, nf, env->GetNumberFormatFunction());
878 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSHandle<JSTaggedValue>(thread, JSTaggedValue::Undefined()));
879
880 bool isJSNumberFormat = nf->IsJSNumberFormat();
881 // If nf does not have an [[InitializedNumberFormat]] internal slot and ?
882 // InstanceofOperator(nf, %NumberFormat%) is true, then
883 // a. Let nf be ? Get(nf, %Intl%.[[FallbackSymbol]]).
884 if (!isJSNumberFormat && hasInstance) {
885 JSHandle<JSTaggedValue> key(thread, JSHandle<JSIntl>::Cast(env->GetIntlFunction())->GetFallbackSymbol());
886 OperationResult operationResult = JSTaggedValue::GetProperty(thread, nf, key);
887 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSHandle<JSTaggedValue>(thread, JSTaggedValue::Undefined()));
888 return operationResult.GetValue();
889 }
890 // 3. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
891 if (!isJSNumberFormat) {
892 THROW_TYPE_ERROR_AND_RETURN(thread, "this is not object",
893 JSHandle<JSTaggedValue>(thread, JSTaggedValue::Exception()));
894 }
895 return nf;
896 }
897
GetAvailableLocales(JSThread * thread)898 JSHandle<TaggedArray> JSNumberFormat::GetAvailableLocales(JSThread *thread)
899 {
900 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
901 JSHandle<JSTaggedValue> numberFormatLocales = env->GetNumberFormatLocales();
902 if (!numberFormatLocales->IsUndefined()) {
903 return JSHandle<TaggedArray>::Cast(numberFormatLocales);
904 }
905 const char *key = "NumberElements";
906 const char *path = nullptr;
907 std::vector<std::string> availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, key, path);
908 JSHandle<TaggedArray> availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales);
909 env->SetNumberFormatLocales(thread, availableLocales);
910 return availableLocales;
911 }
912
ResolvedOptions(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,const JSHandle<JSObject> & options)913 void JSNumberFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
914 const JSHandle<JSObject> &options)
915 {
916 // Table 5: Resolved Options of NumberFormat Instances
917 // Internal Slot Property
918 // [[Locale]] "locale"
919 // [[NumberingSystem]] "numberingSystem"
920 // [[Style]] "style"
921 // [[Currency]] "currency"
922 // [[CurrencyDisplay]] "currencyDisplay"
923 // [[CurrencySign]] "currencySign"
924 // [[Unit]] "unit"
925 // [[UnitDisplay]] "unitDisplay"
926 // [[MinimumIntegerDigits]] "minimumIntegerDigits"
927 // [[MinimumFractionDigits]] "minimumFractionDigits"
928 // [[MaximumFractionDigits]] "maximumFractionDigits"
929 // [[MinimumSignificantDigits]] "minimumSignificantDigits"
930 // [[MaximumSignificantDigits]] "maximumSignificantDigits"
931 // [[UseGrouping]] "useGrouping"
932 // [[Notation]] "notation"
933 // [[CompactDisplay]] "compactDisplay"
934 // [SignDisplay]] "signDisplay"
935 // [[Locale]]
936 auto globalConst = thread->GlobalConstants();
937 JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleString();
938 JSHandle<JSTaggedValue> locale(thread, numberFormat->GetLocale());
939 JSObject::CreateDataPropertyOrThrow(thread, options, property, locale);
940 RETURN_IF_ABRUPT_COMPLETION(thread);
941
942 // [[NumberingSystem]]
943 JSHandle<JSTaggedValue> numberingSystem(thread, numberFormat->GetNumberingSystem());
944 if (numberingSystem->IsUndefined()) {
945 numberingSystem = globalConst->GetHandledLatnString();
946 }
947 property = globalConst->GetHandledNumberingSystemString();
948 JSObject::CreateDataPropertyOrThrow(thread, options, property, numberingSystem);
949 RETURN_IF_ABRUPT_COMPLETION(thread);
950
951 // [[Style]]
952 StyleOption style = numberFormat->GetStyle();
953 property = globalConst->GetHandledStyleString();
954 JSHandle<JSTaggedValue> styleString = OptionToEcmaString(thread, style);
955 JSObject::CreateDataPropertyOrThrow(thread, options, property, styleString);
956 RETURN_IF_ABRUPT_COMPLETION(thread);
957
958 // [[currency]]
959 JSHandle<JSTaggedValue> currency(thread, JSTaggedValue::Undefined());
960 // If style is not currency the currency should be undefined
961 if (style == StyleOption::CURRENCY) {
962 currency = JSHandle<JSTaggedValue>(thread, numberFormat->GetCurrency());
963 }
964 if (!currency->IsUndefined()) { // NOLINT(readability-implicit-bool-conversion)
965 property = globalConst->GetHandledCurrencyString();
966 JSObject::CreateDataPropertyOrThrow(thread, options, property, currency);
967 RETURN_IF_ABRUPT_COMPLETION(thread);
968
969 // [[CurrencyDisplay]]
970 property = globalConst->GetHandledCurrencyDisplayString();
971 CurrencyDisplayOption currencyDisplay = numberFormat->GetCurrencyDisplay();
972 JSHandle<JSTaggedValue> currencyDisplayString = OptionToEcmaString(thread, currencyDisplay);
973 JSObject::CreateDataPropertyOrThrow(thread, options, property, currencyDisplayString);
974 RETURN_IF_ABRUPT_COMPLETION(thread);
975
976 // [[CurrencySign]]
977 property = globalConst->GetHandledCurrencySignString();
978 CurrencySignOption currencySign = numberFormat->GetCurrencySign();
979 JSHandle<JSTaggedValue> currencySignString = OptionToEcmaString(thread, currencySign);
980 JSObject::CreateDataPropertyOrThrow(thread, options, property, currencySignString);
981 RETURN_IF_ABRUPT_COMPLETION(thread);
982 }
983
984 if (style == StyleOption::UNIT) {
985 JSHandle<JSTaggedValue> unit(thread, numberFormat->GetUnit());
986 if (!unit->IsUndefined()) {
987 // [[Unit]]
988 property = globalConst->GetHandledUnitString();
989 JSObject::CreateDataPropertyOrThrow(thread, options, property, unit);
990 RETURN_IF_ABRUPT_COMPLETION(thread);
991 }
992 // [[UnitDisplay]]
993 property = globalConst->GetHandledUnitDisplayString();
994 UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay();
995 JSHandle<JSTaggedValue> unitDisplayString = OptionToEcmaString(thread, unitDisplay);
996 JSObject::CreateDataPropertyOrThrow(thread, options, property, unitDisplayString);
997 RETURN_IF_ABRUPT_COMPLETION(thread);
998 }
999 // [[MinimumIntegerDigits]]
1000 property = globalConst->GetHandledMinimumIntegerDigitsString();
1001 JSHandle<JSTaggedValue> minimumIntegerDigits(thread, numberFormat->GetMinimumIntegerDigits());
1002 JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumIntegerDigits);
1003 RETURN_IF_ABRUPT_COMPLETION(thread);
1004
1005 RoundingType roundingType = numberFormat->GetRoundingType();
1006 if (roundingType == RoundingType::SIGNIFICANTDIGITS) {
1007 // [[MinimumSignificantDigits]]
1008 property = globalConst->GetHandledMinimumSignificantDigitsString();
1009 JSHandle<JSTaggedValue> minimumSignificantDigits(thread, numberFormat->GetMinimumSignificantDigits());
1010 JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumSignificantDigits);
1011 RETURN_IF_ABRUPT_COMPLETION(thread);
1012 // [[MaximumSignificantDigits]]
1013 property = globalConst->GetHandledMaximumSignificantDigitsString();
1014 JSHandle<JSTaggedValue> maximumSignificantDigits(thread, numberFormat->GetMaximumSignificantDigits());
1015 JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumSignificantDigits);
1016 RETURN_IF_ABRUPT_COMPLETION(thread);
1017 } else {
1018 // [[MinimumFractionDigits]]
1019 property = globalConst->GetHandledMinimumFractionDigitsString();
1020 JSHandle<JSTaggedValue> minimumFractionDigits(thread, numberFormat->GetMinimumFractionDigits());
1021 JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumFractionDigits);
1022 RETURN_IF_ABRUPT_COMPLETION(thread);
1023 // [[MaximumFractionDigits]]
1024 property = globalConst->GetHandledMaximumFractionDigitsString();
1025 JSHandle<JSTaggedValue> maximumFractionDigits(thread, numberFormat->GetMaximumFractionDigits());
1026 JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumFractionDigits);
1027 RETURN_IF_ABRUPT_COMPLETION(thread);
1028 }
1029
1030 // [[UseGrouping]]
1031 property = globalConst->GetHandledUserGroupingString();
1032 JSObject::CreateDataPropertyOrThrow(thread, options, property,
1033 JSHandle<JSTaggedValue>(thread, numberFormat->GetUseGrouping()));
1034 RETURN_IF_ABRUPT_COMPLETION(thread);
1035
1036 // [[Notation]]
1037 property = globalConst->GetHandledNotationString();
1038 NotationOption notation = numberFormat->GetNotation();
1039 JSHandle<JSTaggedValue> notationString = OptionToEcmaString(thread, notation);
1040 JSObject::CreateDataPropertyOrThrow(thread, options, property, notationString);
1041 RETURN_IF_ABRUPT_COMPLETION(thread);
1042
1043 // Only output compactDisplay when notation is compact.
1044 if (notation == NotationOption::COMPACT) {
1045 // [[CompactDisplay]]
1046 property = globalConst->GetHandledCompactDisplayString();
1047 CompactDisplayOption compactDisplay = numberFormat->GetCompactDisplay();
1048 JSHandle<JSTaggedValue> compactDisplayString = OptionToEcmaString(thread, compactDisplay);
1049 JSObject::CreateDataPropertyOrThrow(thread, options, property, compactDisplayString);
1050 RETURN_IF_ABRUPT_COMPLETION(thread);
1051 }
1052
1053 // [[SignDisplay]]
1054 property = globalConst->GetHandledSignDisplayString();
1055 SignDisplayOption signDisplay = numberFormat->GetSignDisplay();
1056 JSHandle<JSTaggedValue> signDisplayString = OptionToEcmaString(thread, signDisplay);
1057 JSObject::CreateDataPropertyOrThrow(thread, options, property, signDisplayString);
1058 }
1059 } // namespace panda::ecmascript