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