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

Generated by: LCOV version 1.10