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/js_function.h"
18 #include "ecmascript/object_factory-inl.h"
19
20 namespace panda::ecmascript {
21
22 const std::vector<StyleOption> JSNumberFormat::STYLE_OPTION = {
23 StyleOption::DECIMAL, StyleOption::PERCENT, StyleOption::CURRENCY, StyleOption::UNIT
24 };
25 const std::vector<std::string> JSNumberFormat::STYLE_OPTION_NAME = {
26 "decimal", "percent", "currency", "unit"
27 };
28
29 const std::vector<CurrencyDisplayOption> JSNumberFormat::CURRENCY_DISPLAY_OPTION = {
30 CurrencyDisplayOption::CODE, CurrencyDisplayOption::SYMBOL,
31 CurrencyDisplayOption::NARROWSYMBOL, CurrencyDisplayOption::NAME
32 };
33 const std::vector<std::string> JSNumberFormat::CURRENCY_DISPLAY_OPTION_NAME = {
34 "code", "symbol", "narrowSymbol", "name"
35 };
36
37 const std::vector<CurrencySignOption> JSNumberFormat::CURRENCY_SIGN_OPTION = {
38 CurrencySignOption::STANDARD, CurrencySignOption::ACCOUNTING
39 };
40 const std::vector<std::string> JSNumberFormat::CURRENCY_SIGN_OPTION_NAME = {"standard", "accounting"};
41
42 const std::vector<UnitDisplayOption> JSNumberFormat::UNIT_DISPLAY_OPTION = {
43 UnitDisplayOption::SHORT, UnitDisplayOption::NARROW, UnitDisplayOption::LONG
44 };
45 const std::vector<std::string> JSNumberFormat::UNIT_DISPLAY_OPTION_NAME = {"short", "narrow", "long"};
46
47 const std::vector<LocaleMatcherOption> JSNumberFormat::LOCALE_MATCHER_OPTION = {
48 LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT
49 };
50 const std::vector<std::string> JSNumberFormat::LOCALE_MATCHER_OPTION_NAME = {"lookup", "best fit"};
51
52 const std::vector<NotationOption> JSNumberFormat::NOTATION_OPTION = {
53 NotationOption::STANDARD, NotationOption::SCIENTIFIC,
54 NotationOption::ENGINEERING, NotationOption::COMPACT
55 };
56 const std::vector<std::string> JSNumberFormat::NOTATION_OPTION_NAME = {
57 "standard", "scientific", "engineering", "compact"
58 };
59
60 const std::vector<SignDisplayOption> JSNumberFormat::SIGN_DISPLAY_OPTION = {
61 SignDisplayOption::AUTO, SignDisplayOption::NEVER,
62 SignDisplayOption::ALWAYS, SignDisplayOption::EXCEPTZERO
63 };
64 const std::vector<std::string> JSNumberFormat::SIGN_DISPLAY_OPTION_NAME = {
65 "auto", "never", "always", "exceptZero"
66 };
67
68 const std::vector<CompactDisplayOption> JSNumberFormat::COMPACT_DISPLAY_OPTION = {
69 CompactDisplayOption::SHORT, CompactDisplayOption::LONG
70 };
71 const std::vector<std::string> JSNumberFormat::COMPACT_DISPLAY_OPTION_NAME = {"short", "long"};
72
73 const std::set<std::string> SANCTIONED_UNIT({ "acre", "bit", "byte", "celsius", "centimeter", "day", "degree",
74 "fahrenheit", "fluid-ounce", "foot", "gallon", "gigabit", "gigabyte",
75 "gram", "hectare", "hour", "inch", "kilobit", "kilobyte", "kilogram",
76 "kilometer", "liter", "megabit", "megabyte", "meter", "mile",
77 "mile-scandinavian", "millimeter", "milliliter", "millisecond",
78 "minute", "month", "ounce", "percent", "petabyte", "pound", "second",
79 "stone", "terabit", "terabyte", "week", "yard", "year" });
80
OptionToEcmaString(JSThread * thread,StyleOption style)81 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, StyleOption style)
82 {
83 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
84 auto globalConst = thread->GlobalConstants();
85 switch (style) {
86 case StyleOption::DECIMAL:
87 result.Update(globalConst->GetHandledDecimalString().GetTaggedValue());
88 break;
89 case StyleOption::CURRENCY:
90 result.Update(globalConst->GetHandledCurrencyString().GetTaggedValue());
91 break;
92 case StyleOption::PERCENT:
93 result.Update(globalConst->GetHandledPercentString().GetTaggedValue());
94 break;
95 case StyleOption::UNIT:
96 result.Update(globalConst->GetHandledUnitString().GetTaggedValue());
97 break;
98 default:
99 LOG_ECMA(FATAL) << "this branch is unreachable";
100 UNREACHABLE();
101 }
102 return result;
103 }
104
OptionToEcmaString(JSThread * thread,CurrencyDisplayOption currencyDisplay)105 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CurrencyDisplayOption currencyDisplay)
106 {
107 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
108 auto globalConst = thread->GlobalConstants();
109 switch (currencyDisplay) {
110 case CurrencyDisplayOption::CODE:
111 result.Update(globalConst->GetHandledCodeString().GetTaggedValue());
112 break;
113 case CurrencyDisplayOption::SYMBOL:
114 result.Update(globalConst->GetHandledSymbolString().GetTaggedValue());
115 break;
116 case CurrencyDisplayOption::NARROWSYMBOL:
117 result.Update(globalConst->GetHandledNarrowSymbolString().GetTaggedValue());
118 break;
119 case CurrencyDisplayOption::NAME:
120 result.Update(globalConst->GetHandledNameString().GetTaggedValue());
121 break;
122 default:
123 LOG_ECMA(FATAL) << "this branch is unreachable";
124 UNREACHABLE();
125 }
126 return result;
127 }
128
OptionToEcmaString(JSThread * thread,CurrencySignOption currencySign)129 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CurrencySignOption currencySign)
130 {
131 auto globalConst = thread->GlobalConstants();
132 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
133 switch (currencySign) {
134 case CurrencySignOption::STANDARD:
135 result.Update(globalConst->GetHandledStandardString().GetTaggedValue());
136 break;
137 case CurrencySignOption::ACCOUNTING:
138 result.Update(globalConst->GetHandledAccountingString().GetTaggedValue());
139 break;
140 default:
141 LOG_ECMA(FATAL) << "this branch is unreachable";
142 UNREACHABLE();
143 }
144 return result;
145 }
146
OptionToEcmaString(JSThread * thread,UnitDisplayOption unitDisplay)147 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, UnitDisplayOption unitDisplay)
148 {
149 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
150 auto globalConst = thread->GlobalConstants();
151 switch (unitDisplay) {
152 case UnitDisplayOption::SHORT:
153 result.Update(globalConst->GetHandledShortString().GetTaggedValue());
154 break;
155 case UnitDisplayOption::NARROW:
156 result.Update(globalConst->GetHandledNarrowString().GetTaggedValue());
157 break;
158 case UnitDisplayOption::LONG:
159 result.Update(globalConst->GetHandledLongString().GetTaggedValue());
160 break;
161 default:
162 LOG_ECMA(FATAL) << "this branch is unreachable";
163 UNREACHABLE();
164 }
165 return result;
166 }
167
OptionToEcmaString(JSThread * thread,NotationOption notation)168 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, NotationOption notation)
169 {
170 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
171 auto globalConst = thread->GlobalConstants();
172 switch (notation) {
173 case NotationOption::STANDARD:
174 result.Update(globalConst->GetHandledStandardString().GetTaggedValue());
175 break;
176 case NotationOption::SCIENTIFIC:
177 result.Update(globalConst->GetHandledScientificString().GetTaggedValue());
178 break;
179 case NotationOption::ENGINEERING:
180 result.Update(globalConst->GetHandledEngineeringString().GetTaggedValue());
181 break;
182 case NotationOption::COMPACT:
183 result.Update(globalConst->GetHandledCompactString().GetTaggedValue());
184 break;
185 default:
186 LOG_ECMA(FATAL) << "this branch is unreachable";
187 UNREACHABLE();
188 }
189 return result;
190 }
191
OptionToEcmaString(JSThread * thread,CompactDisplayOption compactDisplay)192 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, CompactDisplayOption compactDisplay)
193 {
194 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
195 auto globalConst = thread->GlobalConstants();
196 switch (compactDisplay) {
197 case CompactDisplayOption::SHORT:
198 result.Update(globalConst->GetHandledShortString().GetTaggedValue());
199 break;
200 case CompactDisplayOption::LONG:
201 result.Update(globalConst->GetHandledLongString().GetTaggedValue());
202 break;
203 default:
204 LOG_ECMA(FATAL) << "this branch is unreachable";
205 UNREACHABLE();
206 }
207 return result;
208 }
209
OptionToEcmaString(JSThread * thread,SignDisplayOption signDisplay)210 JSHandle<JSTaggedValue> OptionToEcmaString(JSThread *thread, SignDisplayOption signDisplay)
211 {
212 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
213 auto globalConst = thread->GlobalConstants();
214 switch (signDisplay) {
215 case SignDisplayOption::AUTO:
216 result.Update(globalConst->GetHandledAutoString().GetTaggedValue());
217 break;
218 case SignDisplayOption::ALWAYS:
219 result.Update(globalConst->GetHandledAlwaysString().GetTaggedValue());
220 break;
221 case SignDisplayOption::NEVER:
222 result.Update(globalConst->GetHandledNeverString().GetTaggedValue());
223 break;
224 case SignDisplayOption::EXCEPTZERO:
225 result.Update(globalConst->GetHandledExceptZeroString().GetTaggedValue());
226 break;
227 default:
228 LOG_ECMA(FATAL) << "this branch is unreachable";
229 UNREACHABLE();
230 }
231 return result;
232 }
233
ToMeasureUnit(const std::string & sanctionedUnit)234 icu::MeasureUnit ToMeasureUnit(const std::string &sanctionedUnit)
235 {
236 UErrorCode status = U_ZERO_ERROR;
237 // Get All ICU measure unit
238 int32_t total = icu::MeasureUnit::getAvailable(nullptr, 0, status);
239 status = U_ZERO_ERROR;
240 std::vector<icu::MeasureUnit> units(total);
241 icu::MeasureUnit::getAvailable(units.data(), total, status);
242 ASSERT(U_SUCCESS(status));
243
244 // Find measure unit according to sanctioned unit
245 // then return measure unit
246 for (auto &unit : units) {
247 if (std::strcmp(sanctionedUnit.c_str(), unit.getSubtype()) == 0) {
248 return unit;
249 }
250 }
251 return icu::MeasureUnit();
252 }
253
254 // ecma402 #sec-issanctionedsimpleunitidentifier
IsSanctionedSimpleUnitIdentifier(const std::string & unit)255 bool IsSanctionedSimpleUnitIdentifier(const std::string &unit)
256 {
257 // 1. If unitIdentifier is listed in sanctioned unit set, return true.
258 auto it = SANCTIONED_UNIT.find(unit);
259 if (it != SANCTIONED_UNIT.end()) {
260 return true;
261 }
262
263 // 2. Else, Return false.
264 return false;
265 }
266
267 // 6.5.1 IsWellFormedUnitIdentifier ( unitIdentifier )
IsWellFormedUnitIdentifier(const std::string & unit,icu::MeasureUnit & icuUnit,icu::MeasureUnit & icuPerUnit)268 bool IsWellFormedUnitIdentifier(const std::string &unit, icu::MeasureUnit &icuUnit, icu::MeasureUnit &icuPerUnit)
269 {
270 // 1. If the result of IsSanctionedSimpleUnitIdentifier(unitIdentifier) is true, then
271 // a. Return true.
272 icu::MeasureUnit result = icu::MeasureUnit();
273 icu::MeasureUnit emptyUnit = icu::MeasureUnit();
274 auto pos = unit.find("-per-");
275 if (IsSanctionedSimpleUnitIdentifier(unit) && pos == std::string::npos) {
276 result = ToMeasureUnit(unit);
277 icuUnit = result;
278 icuPerUnit = emptyUnit;
279 return true;
280 }
281
282 // 2. If the substring "-per-" does not occur exactly once in unitIdentifier,
283 // a. then false
284 size_t afterPos = pos + JSNumberFormat::PERUNIT_STRING;
285 if (pos == std::string::npos || unit.find("-per-", afterPos) != std::string::npos) {
286 return false;
287 }
288
289 // 3. Let numerator be the substring of unitIdentifier from the beginning to just before "-per-".
290 std::string numerator = unit.substr(0, pos);
291 // 4. If the result of IsSanctionedUnitIdentifier(numerator) is false, then
292 // a. return false
293 if (IsSanctionedSimpleUnitIdentifier(numerator)) {
294 result = ToMeasureUnit(numerator);
295 } else {
296 return false;
297 }
298
299 // 5. Let denominator be the substring of unitIdentifier from just after "-per-" to the end.
300 std::string denominator = unit.substr(pos + JSNumberFormat::PERUNIT_STRING);
301
302 // 6. If the result of IsSanctionedUnitIdentifier(denominator) is false, then
303 // a. Return false
304 icu::MeasureUnit perResult = icu::MeasureUnit();
305 if (IsSanctionedSimpleUnitIdentifier(denominator)) {
306 perResult = ToMeasureUnit(denominator);
307 } else {
308 return false;
309 }
310
311 // 7. Return true.
312 icuUnit = result;
313 icuPerUnit = perResult;
314 return true;
315 }
316
317 // 12.1.13 SetNumberFormatUnitOptions ( intlObj, options )
SetNumberFormatUnitOptions(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,const JSHandle<JSObject> & optionsObject,icu::number::LocalizedNumberFormatter * icuNumberFormatter)318 FractionDigitsOption SetNumberFormatUnitOptions(JSThread *thread,
319 const JSHandle<JSNumberFormat> &numberFormat,
320 const JSHandle<JSObject> &optionsObject,
321 icu::number::LocalizedNumberFormatter *icuNumberFormatter)
322 {
323 auto globalConst = thread->GlobalConstants();
324 FractionDigitsOption fractionDigitsOption;
325 // 3. Let style be ? GetOption(options, "style", "string", « "decimal", "percent", "currency", "unit" », "decimal").
326 JSHandle<JSTaggedValue> property = globalConst->GetHandledStyleString();
327 auto style = JSLocale::GetOptionOfString<StyleOption>(
328 thread, optionsObject, property,
329 JSNumberFormat::STYLE_OPTION, JSNumberFormat::STYLE_OPTION_NAME,
330 StyleOption::DECIMAL);
331 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
332
333 // 4. Set intlObj.[[Style]] to style.
334 numberFormat->SetStyle(style);
335
336 // 5. Let currency be ? GetOption(options, "currency", "string", undefined, undefined).
337 property = globalConst->GetHandledCurrencyString();
338 JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
339 JSHandle<JSTaggedValue> currency =
340 JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
341 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
342
343 // 6. If currency is not undefined, then
344 // a. If the result of IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception.
345 if (!currency->IsUndefined()) {
346 JSHandle<EcmaString> currencyStr = JSHandle<EcmaString>::Cast(currency);
347 if (EcmaStringAccessor(currencyStr).IsUtf16()) {
348 THROW_RANGE_ERROR_AND_RETURN(thread, "not a utf-8", fractionDigitsOption);
349 }
350 std::string currencyCStr = intl::LocaleHelper::ConvertToStdString(thread, currencyStr);
351 if (!JSLocale::IsWellFormedCurrencyCode(currencyCStr)) {
352 THROW_RANGE_ERROR_AND_RETURN(thread, "not a wellformed code", fractionDigitsOption);
353 }
354 } else {
355 // 7. If style is "currency" and currency is undefined, throw a TypeError exception.
356 if (style == StyleOption::CURRENCY) {
357 THROW_TYPE_ERROR_AND_RETURN(thread, "style is currency but currency is undefined", fractionDigitsOption);
358 }
359 }
360
361 // 8. Let currencyDisplay be ? GetOption(options, "currencyDisplay", "string",
362 // « "code", "symbol", "narrowSymbol", "name" », "symbol").
363 property = globalConst->GetHandledCurrencyDisplayString();
364 auto currencyDisplay = JSLocale::GetOptionOfString<CurrencyDisplayOption>(
365 thread, optionsObject, property,
366 JSNumberFormat::CURRENCY_DISPLAY_OPTION, JSNumberFormat::CURRENCY_DISPLAY_OPTION_NAME,
367 CurrencyDisplayOption::SYMBOL);
368 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
369 numberFormat->SetCurrencyDisplay(currencyDisplay);
370
371 // 9. Let currencySign be ? GetOption(options, "currencySign", "string", « "standard", "accounting" », "standard").
372 property = globalConst->GetHandledCurrencySignString();
373 auto currencySign = JSLocale::GetOptionOfString<CurrencySignOption>(
374 thread, optionsObject, property,
375 JSNumberFormat::CURRENCY_SIGN_OPTION, JSNumberFormat::CURRENCY_SIGN_OPTION_NAME,
376 CurrencySignOption::STANDARD);
377 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
378 numberFormat->SetCurrencySign(currencySign);
379
380 // 10. Let unit be ? GetOption(options, "unit", "string", undefined, undefined).
381 property = globalConst->GetHandledUnitString();
382 JSHandle<JSTaggedValue> unit =
383 JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
384 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
385 numberFormat->SetUnit(thread, unit);
386
387 // 11. If unit is not undefined, then
388 // If the result of IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception.
389 icu::MeasureUnit icuUnit;
390 icu::MeasureUnit icuPerUnit;
391 if (!unit->IsUndefined()) {
392 JSHandle<EcmaString> unitStr = JSHandle<EcmaString>::Cast(unit);
393 if (EcmaStringAccessor(unitStr).IsUtf16()) {
394 THROW_RANGE_ERROR_AND_RETURN(thread, "Unit input is illegal", fractionDigitsOption);
395 }
396 std::string str = intl::LocaleHelper::ConvertToStdString(thread, unitStr);
397 if (!IsWellFormedUnitIdentifier(str, icuUnit, icuPerUnit)) {
398 THROW_RANGE_ERROR_AND_RETURN(thread, "Unit input is illegal", fractionDigitsOption);
399 }
400 } else {
401 // 15.12. if style is "unit" and unit is undefined, throw a TypeError exception.
402 if (style == StyleOption::UNIT) {
403 THROW_TYPE_ERROR_AND_RETURN(thread, "style is unit but unit is undefined", fractionDigitsOption);
404 }
405 }
406
407 // 13. Let unitDisplay be ? GetOption(options, "unitDisplay", "string", « "short", "narrow", "long" », "short").
408 property = globalConst->GetHandledUnitDisplayString();
409 auto unitDisplay = JSLocale::GetOptionOfString<UnitDisplayOption>(
410 thread, optionsObject, property,
411 JSNumberFormat::UNIT_DISPLAY_OPTION, JSNumberFormat::UNIT_DISPLAY_OPTION_NAME,
412 UnitDisplayOption::SHORT);
413 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fractionDigitsOption);
414 numberFormat->SetUnitDisplay(unitDisplay);
415
416 // 14. If style is "currency", then
417 // a. Let currency be the result of converting currency to upper case as specified in 6.1.
418 // b. Set intlObj.[[Currency]] to currency.
419 // c. Set intlObj.[[CurrencyDisplay]] to currencyDisplay.
420 // d. Set intlObj.[[CurrencySign]] to currencySign.
421 icu::UnicodeString currencyUStr;
422 UErrorCode status = U_ZERO_ERROR;
423 if (style == StyleOption::CURRENCY) {
424 JSHandle<EcmaString> currencyStr = JSHandle<EcmaString>::Cast(currency);
425 std::string currencyCStr = intl::LocaleHelper::ConvertToStdString(thread, currencyStr);
426 std::transform(currencyCStr.begin(), currencyCStr.end(), currencyCStr.begin(), toupper);
427 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
428 JSHandle<JSTaggedValue> currencyValue = JSHandle<JSTaggedValue>::Cast(factory->NewFromStdString(currencyCStr));
429 numberFormat->SetCurrency(thread, currencyValue);
430 currencyUStr = currencyCStr.c_str();
431 if (!currencyUStr.isEmpty()) { // NOLINT(readability-implicit-bool-conversion)
432 *icuNumberFormatter = icuNumberFormatter->unit(icu::CurrencyUnit(currencyUStr.getBuffer(), status));
433 ASSERT(U_SUCCESS(status));
434 UNumberUnitWidth uNumberUnitWidth;
435 // Trans currencyDisplayOption to ICU format number display option
436 switch (currencyDisplay) {
437 case CurrencyDisplayOption::CODE:
438 uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE;
439 break;
440 case CurrencyDisplayOption::SYMBOL:
441 uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
442 break;
443 case CurrencyDisplayOption::NARROWSYMBOL:
444 uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
445 break;
446 case CurrencyDisplayOption::NAME:
447 uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
448 break;
449 default:
450 LOG_ECMA(FATAL) << "this branch is unreachable";
451 UNREACHABLE();
452 }
453 *icuNumberFormatter = icuNumberFormatter->unitWidth(uNumberUnitWidth);
454 }
455 }
456
457 // 15. If style is "unit", then
458 // if unit is not undefiend set unit to LocalizedNumberFormatter
459 // then if perunit is not undefiend set perunit to LocalizedNumberFormatter
460 if (style == StyleOption::UNIT) {
461 icu::MeasureUnit emptyUnit = icu::MeasureUnit();
462 if (icuUnit != emptyUnit) { // NOLINT(readability-implicit-bool-conversion)
463 *icuNumberFormatter = icuNumberFormatter->unit(icuUnit);
464 }
465 if (icuPerUnit != emptyUnit) { // NOLINT(readability-implicit-bool-conversion)
466 *icuNumberFormatter = icuNumberFormatter->perUnit(icuPerUnit);
467 }
468 }
469
470 // 17. If style is "currency", then
471 // a. Let cDigits be CurrencyDigits(currency).
472 // b. Let mnfdDefault be cDigits.
473 // c. Let mxfdDefault be cDigits.
474 if (style == StyleOption::CURRENCY) {
475 int32_t cDigits = JSNumberFormat::CurrencyDigits(currencyUStr);
476 fractionDigitsOption.mnfdDefault = cDigits;
477 fractionDigitsOption.mxfdDefault = cDigits;
478 } else {
479 // 18. Else,
480 // a. Let mnfdDefault be 0.
481 // b. If style is "percent", then
482 // i. Let mxfdDefault be 0.
483 // c. else,
484 // i. Let mxfdDefault be 3.
485 fractionDigitsOption.mnfdDefault = 0;
486 if (style == StyleOption::PERCENT) {
487 fractionDigitsOption.mxfdDefault = 0;
488 } else {
489 fractionDigitsOption.mxfdDefault = 3; // Max decimal precision is 3
490 }
491 }
492 return fractionDigitsOption;
493 }
494
495 // 12.1.2 InitializeNumberFormat ( numberFormat, locales, options )
496 // NOLINTNEXTLINE(readability-function-size)
InitializeNumberFormat(JSThread * thread,const JSHandle<JSNumberFormat> & numberFormat,const JSHandle<JSTaggedValue> & locales,const JSHandle<JSTaggedValue> & options,bool forIcuCache)497 void JSNumberFormat::InitializeNumberFormat(JSThread *thread, const JSHandle<JSNumberFormat> &numberFormat,
498 const JSHandle<JSTaggedValue> &locales,
499 const JSHandle<JSTaggedValue> &options,
500 bool forIcuCache)
501 {
502 EcmaVM *ecmaVm = thread->GetEcmaVM();
503 ObjectFactory *factory = ecmaVm->GetFactory();
504 // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
505 JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
506 RETURN_IF_ABRUPT_COMPLETION(thread);
507 // 2. If options is undefined, then
508 // a. Let options be ObjectCreate(null).
509 // 3. Else,
510 // a. Let options be ? ToObject(options).
511 JSHandle<JSObject> optionsObject;
512 if (options->IsUndefined()) {
513 optionsObject = factory->CreateNullJSObject();
514 } else {
515 optionsObject = JSTaggedValue::ToObject(thread, options);
516 RETURN_IF_ABRUPT_COMPLETION(thread);
517 }
518
519 auto globalConst = thread->GlobalConstants();
520 // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
521 JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
522 auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
523 thread, optionsObject, property,
524 JSNumberFormat::LOCALE_MATCHER_OPTION, JSNumberFormat::LOCALE_MATCHER_OPTION_NAME,
525 LocaleMatcherOption::BEST_FIT);
526 RETURN_IF_ABRUPT_COMPLETION(thread);
527
528 // 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
529 property = globalConst->GetHandledNumberingSystemString();
530 JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
531 JSHandle<JSTaggedValue> numberingSystemTaggedValue =
532 JSLocale::GetOption(thread, optionsObject, property, OptionType::STRING, undefinedValue, undefinedValue);
533 RETURN_IF_ABRUPT_COMPLETION(thread);
534 numberFormat->SetNumberingSystem(thread, numberingSystemTaggedValue);
535
536 // 8. If numberingSystem is not undefined, then
537 // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal,
538 // throw a RangeError exception. `(3*8alphanum) *("-" (3*8alphanum))`
539 std::string numberingSystemStr;
540 if (!numberingSystemTaggedValue->IsUndefined()) {
541 JSHandle<EcmaString> numberingSystemEcmaString = JSHandle<EcmaString>::Cast(numberingSystemTaggedValue);
542 if (EcmaStringAccessor(numberingSystemEcmaString).IsUtf16()) {
543 THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
544 }
545 numberingSystemStr = intl::LocaleHelper::ConvertToStdString(thread, numberingSystemEcmaString);
546 if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStr)) {
547 THROW_ERROR(thread, ErrorType::RANGE_ERROR, "invalid numberingSystem");
548 }
549 }
550
551 if (!numberingSystemStr.empty()) {
552 // If numberingSystem is invalid, Let numberingSystem be undefined.
553 if (!JSLocale::IsWellNumberingSystem(numberingSystemStr)) {
554 numberFormat->SetNumberingSystem(thread, undefinedValue);
555 }
556 }
557
558 // 10. Let localeData be %NumberFormat%.[[LocaleData]].
559 JSHandle<TaggedArray> availableLocales;
560 if (requestedLocales->GetLength() == 0) {
561 availableLocales = factory->EmptyArray();
562 } else {
563 availableLocales = GetAvailableLocales(thread);
564 }
565
566 // 11. Let r be ResolveLocale( %NumberFormat%.[[AvailableLocales]], requestedLocales, opt,
567 // %NumberFormat%.[[RelevantExtensionKeys]], localeData).
568 std::set<std::string> relevantExtensionKeys{"nu"};
569 ResolvedLocale r =
570 JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
571
572 // 12. Set numberFormat.[[Locale]] to r.[[locale]].
573 icu::Locale icuLocale = r.localeData;
574 JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
575 RETURN_IF_ABRUPT_COMPLETION(thread);
576 numberFormat->SetLocale(thread, localeStr.GetTaggedValue());
577
578 // Set numberingSystemStr to UnicodeKeyWord "nu"
579 UErrorCode status = U_ZERO_ERROR;
580 if (!numberingSystemStr.empty()) {
581 if (JSLocale::IsWellNumberingSystem(numberingSystemStr)) {
582 icuLocale.setUnicodeKeywordValue("nu", numberingSystemStr, status);
583 ASSERT(U_SUCCESS(status));
584 }
585 }
586
587 icu::number::LocalizedNumberFormatter icuNumberFormatter =
588 icu::number::NumberFormatter::withLocale(icuLocale).roundingMode(UNUM_ROUND_HALFUP);
589
590 int32_t mnfdDefault = 0;
591 int32_t mxfdDefault = 0;
592 FractionDigitsOption fractionOptions =
593 SetNumberFormatUnitOptions(thread, numberFormat, optionsObject, &icuNumberFormatter);
594 RETURN_IF_ABRUPT_COMPLETION(thread);
595 mnfdDefault = fractionOptions.mnfdDefault;
596 mxfdDefault = fractionOptions.mxfdDefault;
597 UnitDisplayOption unitDisplay = numberFormat->GetUnitDisplay();
598
599 // Trans unitDisplay option to ICU display option
600 UNumberUnitWidth uNumberUnitWidth;
601 switch (unitDisplay) {
602 case UnitDisplayOption::SHORT:
603 uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
604 break;
605 case UnitDisplayOption::NARROW:
606 uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
607 break;
608 case UnitDisplayOption::LONG:
609 // UNUM_UNIT_WIDTH_FULL_NAME Print the full name of the unit, without any abbreviations.
610 uNumberUnitWidth = UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
611 break;
612 default:
613 LOG_ECMA(FATAL) << "this branch is unreachable";
614 UNREACHABLE();
615 }
616 icuNumberFormatter = icuNumberFormatter.unitWidth(uNumberUnitWidth);
617
618 // 16. Let style be numberFormat.[[Style]].
619 StyleOption style = numberFormat->GetStyle();
620 if (style == StyleOption::PERCENT) {
621 icuNumberFormatter = icuNumberFormatter.unit(icu::MeasureUnit::getPercent()).
622 scale(icu::number::Scale::powerOfTen(2)); // means 10^2
623 }
624
625 // 19. Let notation be ? GetOption(
626 // options, "notation", "string", « "standard", "scientific", "engineering", "compact" », "standard").
627 property = globalConst->GetHandledNotationString();
628 auto notation = JSLocale::GetOptionOfString<NotationOption>(
629 thread, optionsObject, property,
630 JSNumberFormat::NOTATION_OPTION, JSNumberFormat::NOTATION_OPTION_NAME,
631 NotationOption::STANDARD);
632 RETURN_IF_ABRUPT_COMPLETION(thread);
633 numberFormat->SetNotation(notation);
634
635 // 21. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
636 JSLocale::SetNumberFormatDigitOptions(thread, numberFormat, JSHandle<JSTaggedValue>::Cast(optionsObject),
637 mnfdDefault, mxfdDefault, notation);
638 RETURN_IF_ABRUPT_COMPLETION(thread);
639 icuNumberFormatter = SetICUFormatterDigitOptions(thread, icuNumberFormatter, numberFormat);
640
641 // 22. Let compactDisplay be ? GetOptionOfString(options, "compactDisplay", "string", « "short", "long" », "short").
642 property = globalConst->GetHandledCompactDisplayString();
643 auto compactDisplay = JSLocale::GetOptionOfString<CompactDisplayOption>(
644 thread, optionsObject, property,
645 JSNumberFormat::COMPACT_DISPLAY_OPTION, JSNumberFormat::COMPACT_DISPLAY_OPTION_NAME,
646 CompactDisplayOption::SHORT);
647 numberFormat->SetCompactDisplay(compactDisplay);
648
649 // Trans NotationOption to ICU Noation and set to icuNumberFormatter
650 if (notation == NotationOption::COMPACT) {
651 switch (compactDisplay) {
652 case CompactDisplayOption::SHORT:
653 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactShort());
654 break;
655 case CompactDisplayOption::LONG:
656 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::compactLong());
657 break;
658 default:
659 break;
660 }
661 }
662 switch (notation) {
663 case NotationOption::STANDARD:
664 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::simple());
665 break;
666 case NotationOption::SCIENTIFIC:
667 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::scientific());
668 break;
669 case NotationOption::ENGINEERING:
670 icuNumberFormatter = icuNumberFormatter.notation(icu::number::Notation::engineering());
671 break;
672 default:
673 break;
674 }
675
676 // 24. Let useGrouping be ? GetOption(options, "useGrouping", "boolean", undefined, true).
677 property = globalConst->GetHandledUserGroupingString();
678 bool useGrouping = false;
679 [[maybe_unused]] bool find = JSLocale::GetOptionOfBool(thread, optionsObject, property, true, &useGrouping);
680 RETURN_IF_ABRUPT_COMPLETION(thread);
681 JSHandle<JSTaggedValue> useGroupingValue(thread, JSTaggedValue(useGrouping));
682 numberFormat->SetUseGrouping(thread, useGroupingValue);
683
684 // 25. Set numberFormat.[[UseGrouping]] to useGrouping.
685 if (!useGrouping) {
686 icuNumberFormatter = icuNumberFormatter.grouping(UNumberGroupingStrategy::UNUM_GROUPING_OFF);
687 }
688
689 // 26. Let signDisplay be ?
690 // GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero" », "auto").
691 property = globalConst->GetHandledSignDisplayString();
692 auto signDisplay = JSLocale::GetOptionOfString<SignDisplayOption>(
693 thread, optionsObject, property,
694 JSNumberFormat::SIGN_DISPLAY_OPTION, JSNumberFormat::SIGN_DISPLAY_OPTION_NAME,
695 SignDisplayOption::AUTO);
696 RETURN_IF_ABRUPT_COMPLETION(thread);
697 numberFormat->SetSignDisplay(signDisplay);
698
699 // 27. Set numberFormat.[[SignDisplay]] to signDisplay.
700 // The default sign in ICU is UNUM_SIGN_AUTO which is mapped from
701 // SignDisplay::AUTO and CurrencySign::STANDARD so we can skip setting
702 // under that values for optimization.
703 CurrencySignOption currencySign = numberFormat->GetCurrencySign();
704
705 // Trans SignDisPlayOption to ICU UNumberSignDisplay and set to icuNumberFormatter
706 switch (signDisplay) {
707 case SignDisplayOption::AUTO:
708 // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
709 if (currencySign == CurrencySignOption::ACCOUNTING) {
710 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING);
711 } else {
712 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_AUTO);
713 }
714 break;
715 case SignDisplayOption::NEVER:
716 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_NEVER);
717 break;
718 case SignDisplayOption::ALWAYS:
719 // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
720 if (currencySign == CurrencySignOption::ACCOUNTING) {
721 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS);
722 } else {
723 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS);
724 }
725 break;
726 case SignDisplayOption::EXCEPTZERO:
727 // if CurrencySign is ACCOUNTING, Use the locale-dependent accounting format on negative numbers
728 if (currencySign == CurrencySignOption::ACCOUNTING) {
729 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO);
730 } else {
731 icuNumberFormatter = icuNumberFormatter.sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO);
732 }
733 break;
734 default:
735 break;
736 }
737
738 if (forIcuCache) {
739 std::string cacheEntry =
740 locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString(thread);
741 auto formatterPointer = new icu::number::LocalizedNumberFormatter(icuNumberFormatter);
742 ecmaVm->GetIntlCache().SetIcuFormatterToCache(IcuFormatterType::NUMBER_FORMATTER, cacheEntry,
743 formatterPointer, JSNumberFormat::FreeIcuNumberformat);
744 } else {
745 // Set numberFormat.[[IcuNumberForma]] to handleNumberFormatter
746 factory->NewJSIntlIcuData(numberFormat, icuNumberFormatter, JSNumberFormat::FreeIcuNumberformat);
747 }
748 // Set numberFormat.[[BoundFormat]] to undefinedValue
749 numberFormat->SetBoundFormat(thread, undefinedValue);
750 }
751
752 // 12.1.3 CurrencyDigits ( currency )
CurrencyDigits(const icu::UnicodeString & currency)753 int32_t JSNumberFormat::CurrencyDigits(const icu::UnicodeString ¤cy)
754 {
755 UErrorCode status = U_ZERO_ERROR;
756 // If the ISO 4217 currency and funds code list contains currency as an alphabetic code,
757 // return the minor unit value corresponding to the currency from the list; otherwise, return 2.
758 int32_t fractionDigits =
759 ucurr_getDefaultFractionDigits(reinterpret_cast<const UChar *>(currency.getBuffer()), &status);
760 if (U_SUCCESS(status)) {
761 return fractionDigits;
762 }
763 return JSNumberFormat::DEFAULT_FRACTION_DIGITS;
764 }
765
GetCachedIcuNumberFormatter(JSThread * thread,const JSHandle<JSTaggedValue> & locales)766 icu::number::LocalizedNumberFormatter *JSNumberFormat::GetCachedIcuNumberFormatter(JSThread *thread,
767 const JSHandle<JSTaggedValue> &locales)
768 {
769 std::string cacheEntry =
770 locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString(thread);
771 void *cachedNumberFormatter = thread->GetEcmaVM()->GetIntlCache().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(thread);
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(thread);
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(thread);
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(thread);
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(thread));
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(thread));
1019 JSObject::CreateDataPropertyOrThrow(thread, options, property, locale);
1020 RETURN_IF_ABRUPT_COMPLETION(thread);
1021
1022 // [[NumberingSystem]]
1023 JSHandle<JSTaggedValue> numberingSystem(thread, numberFormat->GetNumberingSystem(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread));
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(thread)));
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
1155