Line data Source code
1 : // Copyright 2018 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #ifndef V8_INTL_SUPPORT
6 : #error Internationalization is expected to be enabled.
7 : #endif // V8_INTL_SUPPORT
8 :
9 : #include "src/objects/js-number-format.h"
10 :
11 : #include <set>
12 : #include <string>
13 :
14 : #include "src/isolate.h"
15 : #include "src/objects-inl.h"
16 : #include "src/objects/intl-objects.h"
17 : #include "src/objects/js-number-format-inl.h"
18 : #include "unicode/decimfmt.h"
19 : #include "unicode/locid.h"
20 : #include "unicode/numfmt.h"
21 : #include "unicode/uloc.h"
22 :
23 : namespace v8 {
24 : namespace internal {
25 :
26 : namespace {
27 :
28 2291 : UNumberFormatStyle ToNumberFormatStyle(
29 : JSNumberFormat::CurrencyDisplay currency_display) {
30 2291 : switch (currency_display) {
31 : case JSNumberFormat::CurrencyDisplay::SYMBOL:
32 : return UNUM_CURRENCY;
33 : case JSNumberFormat::CurrencyDisplay::CODE:
34 27 : return UNUM_CURRENCY_ISO;
35 : case JSNumberFormat::CurrencyDisplay::NAME:
36 54 : return UNUM_CURRENCY_PLURAL;
37 : case JSNumberFormat::CurrencyDisplay::COUNT:
38 0 : UNREACHABLE();
39 : }
40 0 : }
41 :
42 : // ecma-402/#sec-currencydigits
43 : // The currency is expected to an all upper case string value.
44 122 : int CurrencyDigits(const icu::UnicodeString& currency) {
45 122 : UErrorCode status = U_ZERO_ERROR;
46 122 : uint32_t fraction_digits = ucurr_getDefaultFractionDigits(
47 : reinterpret_cast<const UChar*>(currency.getBuffer()), &status);
48 : // For missing currency codes, default to the most common, 2
49 122 : return U_SUCCESS(status) ? fraction_digits : 2;
50 : }
51 :
52 : bool IsAToZ(char ch) { return IsInRange(AsciiAlphaToLower(ch), 'a', 'z'); }
53 :
54 : // ecma402/#sec-iswellformedcurrencycode
55 131 : bool IsWellFormedCurrencyCode(const std::string& currency) {
56 : // Verifies that the input is a well-formed ISO 4217 currency code.
57 : // ecma402/#sec-currency-codes
58 : // 2. If the number of elements in normalized is not 3, return false.
59 131 : if (currency.length() != 3) return false;
60 : // 1. Let normalized be the result of mapping currency to upper case as
61 : // described in 6.1.
62 : //
63 : // 3. If normalized contains any character that is not in
64 : // the range "A" to "Z" (U+0041 to U+005A), return false.
65 : //
66 : // 4. Return true.
67 : // Don't uppercase to test. It could convert invalid code into a valid one.
68 : // For example \u00DFP (Eszett+P) becomes SSP.
69 524 : return (IsAToZ(currency[0]) && IsAToZ(currency[1]) && IsAToZ(currency[2]));
70 : }
71 :
72 : } // anonymous namespace
73 :
74 : // static
75 : // ecma402 #sec-intl.numberformat.prototype.resolvedoptions
76 747 : Handle<JSObject> JSNumberFormat::ResolvedOptions(
77 : Isolate* isolate, Handle<JSNumberFormat> number_format_holder) {
78 : Factory* factory = isolate->factory();
79 :
80 : // 4. Let options be ! ObjectCreate(%ObjectPrototype%).
81 747 : Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
82 :
83 : icu::NumberFormat* number_format =
84 : number_format_holder->icu_number_format()->raw();
85 747 : CHECK_NOT_NULL(number_format);
86 :
87 : Handle<String> locale =
88 : Handle<String>(number_format_holder->locale(), isolate);
89 :
90 747 : std::unique_ptr<char[]> locale_str = locale->ToCString();
91 2241 : icu::Locale icu_locale = Intl::CreateICULocale(locale_str.get());
92 :
93 747 : std::string numbering_system = Intl::GetNumberingSystem(icu_locale);
94 :
95 : // 5. For each row of Table 4, except the header row, in table order, do
96 : // Table 4: Resolved Options of NumberFormat Instances
97 : // Internal Slot Property
98 : // [[Locale]] "locale"
99 : // [[NumberingSystem]] "numberingSystem"
100 : // [[Style]] "style"
101 : // [[Currency]] "currency"
102 : // [[CurrencyDisplay]] "currencyDisplay"
103 : // [[MinimumIntegerDigits]] "minimumIntegerDigits"
104 : // [[MinimumFractionDigits]] "minimumFractionDigits"
105 : // [[MaximumFractionDigits]] "maximumFractionDigits"
106 : // [[MinimumSignificantDigits]] "minimumSignificantDigits"
107 : // [[MaximumSignificantDigits]] "maximumSignificantDigits"
108 : // [[UseGrouping]] "useGrouping"
109 1494 : CHECK(JSReceiver::CreateDataProperty(isolate, options,
110 : factory->locale_string(), locale,
111 : Just(kDontThrow))
112 : .FromJust());
113 747 : if (!numbering_system.empty()) {
114 2241 : CHECK(JSReceiver::CreateDataProperty(
115 : isolate, options, factory->numberingSystem_string(),
116 : factory->NewStringFromAsciiChecked(numbering_system.c_str()),
117 : Just(kDontThrow))
118 : .FromJust());
119 : }
120 2241 : CHECK(JSReceiver::CreateDataProperty(
121 : isolate, options, factory->style_string(),
122 : number_format_holder->StyleAsString(), Just(kDontThrow))
123 : .FromJust());
124 747 : if (number_format_holder->style() == Style::CURRENCY) {
125 18 : icu::UnicodeString currency(number_format->getCurrency());
126 : DCHECK(!currency.isEmpty());
127 36 : CHECK(JSReceiver::CreateDataProperty(
128 : isolate, options, factory->currency_string(),
129 : factory
130 : ->NewStringFromTwoByte(Vector<const uint16_t>(
131 : reinterpret_cast<const uint16_t*>(currency.getBuffer()),
132 : currency.length()))
133 : .ToHandleChecked(),
134 : Just(kDontThrow))
135 : .FromJust());
136 :
137 27 : CHECK(JSReceiver::CreateDataProperty(
138 : isolate, options, factory->currencyDisplay_string(),
139 : number_format_holder->CurrencyDisplayAsString(), Just(kDontThrow))
140 : .FromJust());
141 : }
142 2241 : CHECK(JSReceiver::CreateDataProperty(
143 : isolate, options, factory->minimumIntegerDigits_string(),
144 : factory->NewNumberFromInt(number_format->getMinimumIntegerDigits()),
145 : Just(kDontThrow))
146 : .FromJust());
147 2241 : CHECK(
148 : JSReceiver::CreateDataProperty(
149 : isolate, options, factory->minimumFractionDigits_string(),
150 : factory->NewNumberFromInt(number_format->getMinimumFractionDigits()),
151 : Just(kDontThrow))
152 : .FromJust());
153 2241 : CHECK(
154 : JSReceiver::CreateDataProperty(
155 : isolate, options, factory->maximumFractionDigits_string(),
156 : factory->NewNumberFromInt(number_format->getMaximumFractionDigits()),
157 : Just(kDontThrow))
158 : .FromJust());
159 747 : CHECK(number_format->getDynamicClassID() ==
160 : icu::DecimalFormat::getStaticClassID());
161 : icu::DecimalFormat* decimal_format =
162 : static_cast<icu::DecimalFormat*>(number_format);
163 747 : CHECK_NOT_NULL(decimal_format);
164 747 : if (decimal_format->areSignificantDigitsUsed()) {
165 27 : CHECK(JSReceiver::CreateDataProperty(
166 : isolate, options, factory->minimumSignificantDigits_string(),
167 : factory->NewNumberFromInt(
168 : decimal_format->getMinimumSignificantDigits()),
169 : Just(kDontThrow))
170 : .FromJust());
171 27 : CHECK(JSReceiver::CreateDataProperty(
172 : isolate, options, factory->maximumSignificantDigits_string(),
173 : factory->NewNumberFromInt(
174 : decimal_format->getMaximumSignificantDigits()),
175 : Just(kDontThrow))
176 : .FromJust());
177 : }
178 2241 : CHECK(JSReceiver::CreateDataProperty(
179 : isolate, options, factory->useGrouping_string(),
180 : factory->ToBoolean((number_format->isGroupingUsed() == TRUE)),
181 : Just(kDontThrow))
182 : .FromJust());
183 1494 : return options;
184 : }
185 :
186 : // ecma402/#sec-unwrapnumberformat
187 3006 : MaybeHandle<JSNumberFormat> JSNumberFormat::UnwrapNumberFormat(
188 : Isolate* isolate, Handle<JSReceiver> format_holder) {
189 : // old code copy from NumberFormat::Unwrap that has no spec comment and
190 : // compiled but fail unit tests.
191 : Handle<Context> native_context =
192 : Handle<Context>(isolate->context()->native_context(), isolate);
193 : Handle<JSFunction> constructor = Handle<JSFunction>(
194 6012 : JSFunction::cast(native_context->intl_number_format_function()), isolate);
195 : Handle<Object> object;
196 6012 : ASSIGN_RETURN_ON_EXCEPTION(
197 : isolate, object,
198 : Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
199 : format_holder->IsJSNumberFormat()),
200 : JSNumberFormat);
201 : // 4. If ... or nf does not have an [[InitializedNumberFormat]] internal slot,
202 : // then
203 3006 : if (!object->IsJSNumberFormat()) {
204 : // a. Throw a TypeError exception.
205 135 : THROW_NEW_ERROR(isolate,
206 : NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
207 : isolate->factory()->NewStringFromAsciiChecked(
208 : "UnwrapNumberFormat")),
209 : JSNumberFormat);
210 : }
211 : // 5. Return nf.
212 2961 : return Handle<JSNumberFormat>::cast(object);
213 : }
214 :
215 : // static
216 2363 : MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
217 : Isolate* isolate, Handle<JSNumberFormat> number_format,
218 : Handle<Object> locales, Handle<Object> options_obj) {
219 : // set the flags to 0 ASAP.
220 : number_format->set_flags(0);
221 : Factory* factory = isolate->factory();
222 :
223 : // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
224 : Maybe<std::vector<std::string>> maybe_requested_locales =
225 2363 : Intl::CanonicalizeLocaleList(isolate, locales);
226 2363 : MAYBE_RETURN(maybe_requested_locales, Handle<JSNumberFormat>());
227 : std::vector<std::string> requested_locales =
228 2300 : maybe_requested_locales.FromJust();
229 :
230 : // 2. If options is undefined, then
231 2300 : if (options_obj->IsUndefined(isolate)) {
232 : // 2. a. Let options be ObjectCreate(null).
233 1458 : options_obj = isolate->factory()->NewJSObjectWithNullProto();
234 : } else {
235 : // 3. Else
236 : // 3. a. Let options be ? ToObject(options).
237 1684 : ASSIGN_RETURN_ON_EXCEPTION(
238 : isolate, options_obj,
239 : Object::ToObject(isolate, options_obj, "Intl.NumberFormat"),
240 : JSNumberFormat);
241 : }
242 :
243 : // At this point, options_obj can either be a JSObject or a JSProxy only.
244 2291 : Handle<JSReceiver> options = Handle<JSReceiver>::cast(options_obj);
245 :
246 : // 4. Let opt be a new Record.
247 : // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «
248 : // "lookup", "best fit" », "best fit").
249 : // 6. Set opt.[[localeMatcher]] to matcher.
250 : Maybe<Intl::MatcherOption> maybe_locale_matcher =
251 2291 : Intl::GetLocaleMatcher(isolate, options, "Intl.NumberFormat");
252 2291 : MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSNumberFormat>());
253 : Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
254 :
255 : // 7. Let localeData be %NumberFormat%.[[LocaleData]].
256 : // 8. Let r be ResolveLocale(%NumberFormat%.[[AvailableLocales]],
257 : // requestedLocales, opt, %NumberFormat%.[[RelevantExtensionKeys]],
258 : // localeData).
259 4582 : std::set<std::string> relevant_extension_keys{"nu"};
260 : Intl::ResolvedLocale r =
261 : Intl::ResolveLocale(isolate, JSNumberFormat::GetAvailableLocales(),
262 4582 : requested_locales, matcher, relevant_extension_keys);
263 :
264 : // 9. Set numberFormat.[[Locale]] to r.[[locale]].
265 : Handle<String> locale_str =
266 2291 : isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
267 2291 : number_format->set_locale(*locale_str);
268 :
269 : // 11. Let dataLocale be r.[[dataLocale]].
270 : //
271 : // 12. Let style be ? GetOption(options, "style", "string", « "decimal",
272 : // "percent", "currency" », "decimal").
273 : const char* service = "Intl.NumberFormat";
274 : Maybe<Style> maybe_style = Intl::GetStringOption<Style>(
275 : isolate, options, "style", service, {"decimal", "percent", "currency"},
276 6873 : {Style::DECIMAL, Style::PERCENT, Style::CURRENCY}, Style::DECIMAL);
277 2291 : MAYBE_RETURN(maybe_style, MaybeHandle<JSNumberFormat>());
278 : Style style = maybe_style.FromJust();
279 :
280 : // 13. Set numberFormat.[[Style]] to style.
281 2291 : number_format->set_style(style);
282 :
283 : // 14. Let currency be ? GetOption(options, "currency", "string", undefined,
284 : // undefined).
285 2291 : std::unique_ptr<char[]> currency_cstr;
286 : const std::vector<const char*> empty_values = {};
287 : Maybe<bool> found_currency = Intl::GetStringOption(
288 4582 : isolate, options, "currency", empty_values, service, ¤cy_cstr);
289 2291 : MAYBE_RETURN(found_currency, MaybeHandle<JSNumberFormat>());
290 :
291 : std::string currency;
292 : // 15. If currency is not undefined, then
293 2291 : if (found_currency.FromJust()) {
294 : DCHECK_NOT_NULL(currency_cstr.get());
295 : currency = currency_cstr.get();
296 : // 15. a. If the result of IsWellFormedCurrencyCode(currency) is false,
297 : // throw a RangeError exception.
298 131 : if (!IsWellFormedCurrencyCode(currency)) {
299 0 : THROW_NEW_ERROR(
300 : isolate,
301 : NewRangeError(MessageTemplate::kInvalidCurrencyCode,
302 : factory->NewStringFromAsciiChecked(currency.c_str())),
303 : JSNumberFormat);
304 : }
305 : }
306 :
307 : // 16. If style is "currency" and currency is undefined, throw a TypeError
308 : // exception.
309 2413 : if (style == Style::CURRENCY && !found_currency.FromJust()) {
310 0 : THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kCurrencyCode),
311 : JSNumberFormat);
312 : }
313 : // 17. If style is "currency", then
314 : int c_digits = 0;
315 2291 : icu::UnicodeString currency_ustr;
316 2291 : if (style == Style::CURRENCY) {
317 : // a. Let currency be the result of converting currency to upper case as
318 : // specified in 6.1
319 : std::transform(currency.begin(), currency.end(), currency.begin(), toupper);
320 : // c. Let cDigits be CurrencyDigits(currency).
321 122 : currency_ustr = currency.c_str();
322 122 : c_digits = CurrencyDigits(currency_ustr);
323 : }
324 :
325 : // 18. Let currencyDisplay be ? GetOption(options, "currencyDisplay",
326 : // "string", « "code", "symbol", "name" », "symbol").
327 : Maybe<CurrencyDisplay> maybe_currencyDisplay =
328 : Intl::GetStringOption<CurrencyDisplay>(
329 : isolate, options, "currencyDisplay", service,
330 : {"code", "symbol", "name"},
331 : {CurrencyDisplay::CODE, CurrencyDisplay::SYMBOL,
332 : CurrencyDisplay::NAME},
333 6873 : CurrencyDisplay::SYMBOL);
334 2291 : MAYBE_RETURN(maybe_currencyDisplay, MaybeHandle<JSNumberFormat>());
335 : CurrencyDisplay currency_display = maybe_currencyDisplay.FromJust();
336 2291 : UNumberFormatStyle format_style = ToNumberFormatStyle(currency_display);
337 :
338 2291 : UErrorCode status = U_ZERO_ERROR;
339 : std::unique_ptr<icu::NumberFormat> icu_number_format;
340 4582 : icu::Locale no_extension_locale(r.icu_locale.getBaseName());
341 2291 : if (style == Style::DECIMAL) {
342 1863 : icu_number_format.reset(
343 : icu::NumberFormat::createInstance(r.icu_locale, status));
344 : // If the subclass is not DecimalFormat, fallback to no extension
345 : // because other subclass has not support the format() with
346 : // FieldPositionIterator yet.
347 3582 : if (U_FAILURE(status) || icu_number_format.get() == nullptr ||
348 1719 : icu_number_format->getDynamicClassID() !=
349 1719 : icu::DecimalFormat::getStaticClassID()) {
350 144 : status = U_ZERO_ERROR;
351 144 : icu_number_format.reset(
352 : icu::NumberFormat::createInstance(no_extension_locale, status));
353 : }
354 428 : } else if (style == Style::PERCENT) {
355 306 : icu_number_format.reset(
356 : icu::NumberFormat::createPercentInstance(r.icu_locale, status));
357 : // If the subclass is not DecimalFormat, fallback to no extension
358 : // because other subclass has not support the format() with
359 : // FieldPositionIterator yet.
360 612 : if (U_FAILURE(status) || icu_number_format.get() == nullptr ||
361 306 : icu_number_format->getDynamicClassID() !=
362 306 : icu::DecimalFormat::getStaticClassID()) {
363 0 : status = U_ZERO_ERROR;
364 0 : icu_number_format.reset(icu::NumberFormat::createPercentInstance(
365 : no_extension_locale, status));
366 : }
367 : } else {
368 : DCHECK_EQ(style, Style::CURRENCY);
369 122 : icu_number_format.reset(
370 : icu::NumberFormat::createInstance(r.icu_locale, format_style, status));
371 : // If the subclass is not DecimalFormat, fallback to no extension
372 : // because other subclass has not support the format() with
373 : // FieldPositionIterator yet.
374 244 : if (U_FAILURE(status) || icu_number_format.get() == nullptr ||
375 122 : icu_number_format->getDynamicClassID() !=
376 122 : icu::DecimalFormat::getStaticClassID()) {
377 0 : status = U_ZERO_ERROR;
378 0 : icu_number_format.reset(icu::NumberFormat::createInstance(
379 : no_extension_locale, format_style, status));
380 : }
381 : }
382 :
383 2291 : if (U_FAILURE(status) || icu_number_format.get() == nullptr) {
384 0 : status = U_ZERO_ERROR;
385 : // Remove extensions and try again.
386 0 : icu_number_format.reset(
387 : icu::NumberFormat::createInstance(no_extension_locale, status));
388 :
389 0 : if (U_FAILURE(status) || icu_number_format.get() == nullptr) {
390 0 : FATAL("Failed to create ICU number_format, are ICU data files missing?");
391 : }
392 : }
393 : DCHECK(U_SUCCESS(status));
394 2291 : CHECK_NOT_NULL(icu_number_format.get());
395 2291 : CHECK(icu_number_format->getDynamicClassID() ==
396 : icu::DecimalFormat::getStaticClassID());
397 2291 : if (style == Style::CURRENCY) {
398 : // 19. If style is "currency", set numberFormat.[[CurrencyDisplay]] to
399 : // currencyDisplay.
400 122 : number_format->set_currency_display(currency_display);
401 :
402 : // 17.b. Set numberFormat.[[Currency]] to currency.
403 122 : if (!currency_ustr.isEmpty()) {
404 122 : status = U_ZERO_ERROR;
405 244 : icu_number_format->setCurrency(currency_ustr.getBuffer(), status);
406 122 : CHECK(U_SUCCESS(status));
407 : }
408 : }
409 :
410 : // 20. If style is "currency", then
411 : int mnfd_default, mxfd_default;
412 2291 : if (style == Style::CURRENCY) {
413 : // a. Let mnfdDefault be cDigits.
414 : // b. Let mxfdDefault be cDigits.
415 : mnfd_default = c_digits;
416 : mxfd_default = c_digits;
417 : } else {
418 : // 21. Else,
419 : // a. Let mnfdDefault be 0.
420 : mnfd_default = 0;
421 : // b. If style is "percent", then
422 2169 : if (style == Style::PERCENT) {
423 : // i. Let mxfdDefault be 0.
424 : mxfd_default = 0;
425 : } else {
426 : // c. Else,
427 : // i. Let mxfdDefault be 3.
428 : mxfd_default = 3;
429 : }
430 : }
431 : // 22. Perform ? SetNumberFormatDigitOptions(numberFormat, options,
432 : // mnfdDefault, mxfdDefault).
433 2291 : CHECK(icu_number_format->getDynamicClassID() ==
434 : icu::DecimalFormat::getStaticClassID());
435 : icu::DecimalFormat* icu_decimal_format =
436 : static_cast<icu::DecimalFormat*>(icu_number_format.get());
437 : Maybe<bool> maybe_set_number_for_digit_options =
438 : Intl::SetNumberFormatDigitOptions(isolate, icu_decimal_format, options,
439 2291 : mnfd_default, mxfd_default);
440 2291 : MAYBE_RETURN(maybe_set_number_for_digit_options, Handle<JSNumberFormat>());
441 :
442 : // 23. Let useGrouping be ? GetOption(options, "useGrouping", "boolean",
443 : // undefined, true).
444 2183 : bool use_grouping = true;
445 : Maybe<bool> found_use_grouping = Intl::GetBoolOption(
446 2183 : isolate, options, "useGrouping", service, &use_grouping);
447 2183 : MAYBE_RETURN(found_use_grouping, MaybeHandle<JSNumberFormat>());
448 : // 24. Set numberFormat.[[UseGrouping]] to useGrouping.
449 2183 : icu_number_format->setGroupingUsed(use_grouping ? TRUE : FALSE);
450 :
451 : // 25. Let dataLocaleData be localeData.[[<dataLocale>]].
452 : //
453 : // 26. Let patterns be dataLocaleData.[[patterns]].
454 : //
455 : // 27. Assert: patterns is a record (see 11.3.3).
456 : //
457 : // 28. Let stylePatterns be patterns.[[<style>]].
458 : //
459 : // 29. Set numberFormat.[[PositivePattern]] to
460 : // stylePatterns.[[positivePattern]].
461 : //
462 : // 30. Set numberFormat.[[NegativePattern]] to
463 : // stylePatterns.[[negativePattern]].
464 :
465 : Handle<Managed<icu::NumberFormat>> managed_number_format =
466 : Managed<icu::NumberFormat>::FromUniquePtr(isolate, 0,
467 4366 : std::move(icu_number_format));
468 2183 : number_format->set_icu_number_format(*managed_number_format);
469 4366 : number_format->set_bound_format(*factory->undefined_value());
470 :
471 : // 31. Return numberFormat.
472 2183 : return number_format;
473 : }
474 :
475 747 : Handle<String> JSNumberFormat::StyleAsString() const {
476 747 : switch (style()) {
477 : case Style::DECIMAL:
478 : return GetReadOnlyRoots().decimal_string_handle();
479 : case Style::PERCENT:
480 : return GetReadOnlyRoots().percent_string_handle();
481 : case Style::CURRENCY:
482 : return GetReadOnlyRoots().currency_string_handle();
483 : case Style::COUNT:
484 0 : UNREACHABLE();
485 : }
486 0 : }
487 :
488 9 : Handle<String> JSNumberFormat::CurrencyDisplayAsString() const {
489 9 : switch (currency_display()) {
490 : case CurrencyDisplay::CODE:
491 : return GetReadOnlyRoots().code_string_handle();
492 : case CurrencyDisplay::SYMBOL:
493 : return GetReadOnlyRoots().symbol_string_handle();
494 : case CurrencyDisplay::NAME:
495 : return GetReadOnlyRoots().name_string_handle();
496 : case CurrencyDisplay::COUNT:
497 0 : UNREACHABLE();
498 : }
499 0 : }
500 :
501 : namespace {
502 5378 : Maybe<icu::UnicodeString> IcuFormatNumber(
503 : Isolate* isolate, const icu::NumberFormat& number_format,
504 : Handle<Object> numeric_obj, icu::FieldPositionIterator* fp_iter) {
505 5378 : icu::UnicodeString result;
506 : // If it is BigInt, handle it differently.
507 5378 : UErrorCode status = U_ZERO_ERROR;
508 5378 : if (numeric_obj->IsBigInt()) {
509 1512 : Handle<BigInt> big_int = Handle<BigInt>::cast(numeric_obj);
510 : Handle<String> big_int_string;
511 3024 : ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, big_int_string,
512 : BigInt::ToString(isolate, big_int),
513 : Nothing<icu::UnicodeString>());
514 : number_format.format(
515 3024 : {big_int_string->ToCString().get(), big_int_string->length()}, result,
516 3024 : fp_iter, status);
517 : } else {
518 : double number = numeric_obj->Number();
519 3866 : number_format.format(number, result, fp_iter, status);
520 : }
521 5378 : if (U_FAILURE(status)) {
522 0 : THROW_NEW_ERROR_RETURN_VALUE(isolate,
523 : NewTypeError(MessageTemplate::kIcuError),
524 : Nothing<icu::UnicodeString>());
525 : }
526 : return Just(result);
527 : }
528 :
529 : } // namespace
530 :
531 4244 : MaybeHandle<String> JSNumberFormat::FormatNumeric(
532 : Isolate* isolate, const icu::NumberFormat& number_format,
533 : Handle<Object> numeric_obj) {
534 : DCHECK(numeric_obj->IsNumeric());
535 :
536 : Maybe<icu::UnicodeString> maybe_format =
537 4244 : IcuFormatNumber(isolate, number_format, numeric_obj, nullptr);
538 4244 : MAYBE_RETURN(maybe_format, Handle<String>());
539 4244 : icu::UnicodeString result = maybe_format.FromJust();
540 :
541 : return isolate->factory()->NewStringFromTwoByte(Vector<const uint16_t>(
542 8488 : reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length()));
543 : }
544 :
545 : namespace {
546 :
547 15800 : bool cmp_NumberFormatSpan(const NumberFormatSpan& a,
548 : const NumberFormatSpan& b) {
549 : // Regions that start earlier should be encountered earlier.
550 15800 : if (a.begin_pos < b.begin_pos) return true;
551 12119 : if (a.begin_pos > b.begin_pos) return false;
552 : // For regions that start in the same place, regions that last longer should
553 : // be encountered earlier.
554 2318 : if (a.end_pos < b.end_pos) return false;
555 821 : if (a.end_pos > b.end_pos) return true;
556 : // For regions that are exactly the same, one of them must be the "literal"
557 : // backdrop we added, which has a field_id of -1, so consider higher field_ids
558 : // to be later.
559 771 : return a.field_id < b.field_id;
560 : }
561 :
562 : } // namespace
563 :
564 : // Flattens a list of possibly-overlapping "regions" to a list of
565 : // non-overlapping "parts". At least one of the input regions must span the
566 : // entire space of possible indexes. The regions parameter will sorted in-place
567 : // according to some criteria; this is done for performance to avoid copying the
568 : // input.
569 1164 : std::vector<NumberFormatSpan> FlattenRegionsToParts(
570 : std::vector<NumberFormatSpan>* regions) {
571 : // The intention of this algorithm is that it's used to translate ICU "fields"
572 : // to JavaScript "parts" of a formatted string. Each ICU field and JavaScript
573 : // part has an integer field_id, which corresponds to something like "grouping
574 : // separator", "fraction", or "percent sign", and has a begin and end
575 : // position. Here's a diagram of:
576 :
577 : // var nf = new Intl.NumberFormat(['de'], {style:'currency',currency:'EUR'});
578 : // nf.formatToParts(123456.78);
579 :
580 : // : 6
581 : // input regions: 0000000211 7
582 : // ('-' means -1): ------------
583 : // formatted string: "123.456,78 €"
584 : // output parts: 0006000211-7
585 :
586 : // To illustrate the requirements of this algorithm, here's a contrived and
587 : // convoluted example of inputs and expected outputs:
588 :
589 : // : 4
590 : // : 22 33 3
591 : // : 11111 22
592 : // input regions: 0000000 111
593 : // : ------------
594 : // formatted string: "abcdefghijkl"
595 : // output parts: 0221340--231
596 : // (The characters in the formatted string are irrelevant to this function.)
597 :
598 : // We arrange the overlapping input regions like a mountain range where
599 : // smaller regions are "on top" of larger regions, and we output a birds-eye
600 : // view of the mountains, so that smaller regions take priority over larger
601 : // regions.
602 : std::sort(regions->begin(), regions->end(), cmp_NumberFormatSpan);
603 : std::vector<size_t> overlapping_region_index_stack;
604 : // At least one item in regions must be a region spanning the entire string.
605 : // Due to the sorting above, the first item in the vector will be one of them.
606 2328 : overlapping_region_index_stack.push_back(0);
607 1164 : NumberFormatSpan top_region = regions->at(0);
608 : size_t region_iterator = 1;
609 : int32_t entire_size = top_region.end_pos;
610 :
611 : std::vector<NumberFormatSpan> out_parts;
612 :
613 : // The "climber" is a cursor that advances from left to right climbing "up"
614 : // and "down" the mountains. Whenever the climber moves to the right, that
615 : // represents an item of output.
616 : int32_t climber = 0;
617 8365 : while (climber < entire_size) {
618 : int32_t next_region_begin_pos;
619 7201 : if (region_iterator < regions->size()) {
620 6037 : next_region_begin_pos = regions->at(region_iterator).begin_pos;
621 : } else {
622 : // finish off the rest of the input by proceeding to the end.
623 : next_region_begin_pos = entire_size;
624 : }
625 :
626 7201 : if (climber < next_region_begin_pos) {
627 14081 : while (top_region.end_pos < next_region_begin_pos) {
628 4012 : if (climber < top_region.end_pos) {
629 : // step down
630 3893 : out_parts.push_back(NumberFormatSpan(top_region.field_id, climber,
631 : top_region.end_pos));
632 : climber = top_region.end_pos;
633 : } else {
634 : // drop down
635 : }
636 : overlapping_region_index_stack.pop_back();
637 8024 : top_region = regions->at(overlapping_region_index_stack.back());
638 : }
639 6057 : if (climber < next_region_begin_pos) {
640 : // cross a plateau/mesa/valley
641 6057 : out_parts.push_back(NumberFormatSpan(top_region.field_id, climber,
642 : next_region_begin_pos));
643 : climber = next_region_begin_pos;
644 : }
645 : }
646 7201 : if (region_iterator < regions->size()) {
647 12074 : overlapping_region_index_stack.push_back(region_iterator++);
648 12074 : top_region = regions->at(overlapping_region_index_stack.back());
649 : }
650 : }
651 1164 : return out_parts;
652 : }
653 :
654 1134 : Maybe<int> JSNumberFormat::FormatToParts(Isolate* isolate,
655 : Handle<JSArray> result,
656 : int start_index,
657 : const icu::NumberFormat& number_format,
658 : Handle<Object> numeric_obj,
659 : Handle<String> unit) {
660 : DCHECK(numeric_obj->IsNumeric());
661 2268 : icu::FieldPositionIterator fp_iter;
662 : Maybe<icu::UnicodeString> maybe_format =
663 1134 : IcuFormatNumber(isolate, number_format, numeric_obj, &fp_iter);
664 1134 : MAYBE_RETURN(maybe_format, Nothing<int>());
665 1134 : icu::UnicodeString formatted = maybe_format.FromJust();
666 :
667 : int32_t length = formatted.length();
668 : int index = start_index;
669 1134 : if (length == 0) return Just(index);
670 :
671 : std::vector<NumberFormatSpan> regions;
672 : // Add a "literal" backdrop for the entire string. This will be used if no
673 : // other region covers some part of the formatted string. It's possible
674 : // there's another field with exactly the same begin and end as this backdrop,
675 : // in which case the backdrop's field_id of -1 will give it lower priority.
676 1134 : regions.push_back(NumberFormatSpan(-1, 0, formatted.length()));
677 :
678 : {
679 1134 : icu::FieldPosition fp;
680 12978 : while (fp_iter.next(fp)) {
681 5922 : regions.push_back(NumberFormatSpan(fp.getField(), fp.getBeginIndex(),
682 : fp.getEndIndex()));
683 : }
684 : }
685 :
686 1134 : std::vector<NumberFormatSpan> parts = FlattenRegionsToParts(®ions);
687 :
688 10944 : for (auto it = parts.begin(); it < parts.end(); it++) {
689 9810 : NumberFormatSpan part = *it;
690 : Handle<String> field_type_string =
691 : part.field_id == -1
692 : ? isolate->factory()->literal_string()
693 10062 : : Intl::NumberFieldToType(isolate, numeric_obj, part.field_id);
694 : Handle<String> substring;
695 19620 : ASSIGN_RETURN_ON_EXCEPTION_VALUE(
696 : isolate, substring,
697 : Intl::ToString(isolate, formatted, part.begin_pos, part.end_pos),
698 : Nothing<int>());
699 9810 : if (unit.is_null()) {
700 9810 : Intl::AddElement(isolate, result, index, field_type_string, substring);
701 : } else {
702 : Intl::AddElement(isolate, result, index, field_type_string, substring,
703 0 : isolate->factory()->unit_string(), unit);
704 : }
705 9810 : ++index;
706 : }
707 1134 : JSObject::ValidateElements(*result);
708 : return Just(index);
709 : }
710 :
711 1134 : MaybeHandle<JSArray> JSNumberFormat::FormatToParts(
712 : Isolate* isolate, Handle<JSNumberFormat> number_format,
713 : Handle<Object> numeric_obj) {
714 1134 : CHECK(numeric_obj->IsNumeric());
715 : Factory* factory = isolate->factory();
716 : icu::NumberFormat* fmt = number_format->icu_number_format()->raw();
717 1134 : CHECK_NOT_NULL(fmt);
718 :
719 : Handle<JSArray> result = factory->NewJSArray(0);
720 :
721 : Maybe<int> maybe_format_to_parts = JSNumberFormat::FormatToParts(
722 1134 : isolate, result, 0, *fmt, numeric_obj, Handle<String>());
723 1134 : MAYBE_RETURN(maybe_format_to_parts, Handle<JSArray>());
724 :
725 1134 : return result;
726 : }
727 :
728 131 : const std::set<std::string>& JSNumberFormat::GetAvailableLocales() {
729 : static base::LazyInstance<Intl::AvailableLocales<icu::NumberFormat>>::type
730 : available_locales = LAZY_INSTANCE_INITIALIZER;
731 131 : return available_locales.Pointer()->Get();
732 : }
733 :
734 : } // namespace internal
735 122036 : } // namespace v8
|