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

Generated by: LCOV version 1.10