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