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