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