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