LCOV - code coverage report
Current view: top level - src/objects - js-number-format.cc (source / functions) Hit Total Coverage
Test: app.info Lines: 174 195 89.2 %
Date: 2019-01-20 Functions: 17 17 100.0 %

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

Generated by: LCOV version 1.10