js-number-format.cc 58 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif  // V8_INTL_SUPPORT

#include "src/objects/js-number-format.h"

#include <set>
#include <string>

14
#include "src/execution/isolate.h"
15 16
#include "src/objects/intl-objects.h"
#include "src/objects/js-number-format-inl.h"
17
#include "src/objects/objects-inl.h"
18
#include "unicode/currunit.h"
19 20
#include "unicode/decimfmt.h"
#include "unicode/locid.h"
21
#include "unicode/numberformatter.h"
22
#include "unicode/numfmt.h"
23
#include "unicode/numsys.h"
24
#include "unicode/ucurr.h"
25
#include "unicode/uloc.h"
26 27
#include "unicode/unumberformatter.h"
#include "unicode/uvernum.h"  // for U_ICU_VERSION_MAJOR_NUM
28 29 30 31 32 33

namespace v8 {
namespace internal {

namespace {

34 35 36 37
// [[Style]] is one of the values "decimal", "percent", "currency",
// or "unit" identifying the style of the number format.
enum class Style { DECIMAL, PERCENT, CURRENCY, UNIT };

38
// [[CurrencyDisplay]] is one of the values "code", "symbol", "name",
39
// or "narrowSymbol" identifying the display of the currency number format.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
enum class CurrencyDisplay {
  CODE,
  SYMBOL,
  NAME,
  NARROW_SYMBOL,
};

// [[CurrencySign]] is one of the String values "standard" or "accounting",
// specifying whether to render negative numbers in accounting format, often
// signified by parenthesis. It is only used when [[Style]] has the value
// "currency" and when [[SignDisplay]] is not "never".
enum class CurrencySign {
  STANDARD,
  ACCOUNTING,
};

// [[UnitDisplay]] is one of the String values "short", "narrow", or "long",
// specifying whether to display the unit as a symbol, narrow symbol, or
58 59
// localized long name if formatting with the "unit" style. It is
// only used when [[Style]] has the value "unit".
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
enum class UnitDisplay {
  SHORT,
  NARROW,
  LONG,
};

// [[Notation]] is one of the String values "standard", "scientific",
// "engineering", or "compact", specifying whether the number should be
// displayed without scaling, scaled to the units place with the power of ten
// in scientific notation, scaled to the nearest thousand with the power of
// ten in scientific notation, or scaled to the nearest locale-dependent
// compact decimal notation power of ten with the corresponding compact
// decimal notation affix.

enum class Notation {
  STANDARD,
  SCIENTIFIC,
  ENGINEERING,
  COMPACT,
};

// [[CompactDisplay]] is one of the String values "short" or "long",
// specifying whether to display compact notation affixes in short form ("5K")
// or long form ("5 thousand") if formatting with the "compact" notation. It
// is only used when [[Notation]] has the value "compact".
enum class CompactDisplay {
  SHORT,
  LONG,
};

// [[SignDisplay]] is one of the String values "auto", "always", "never", or
91
// "exceptZero", specifying whether to show the sign on negative numbers
92 93 94 95 96 97 98 99 100 101
// only, positive and negative numbers including zero, neither positive nor
// negative numbers, or positive and negative numbers but not zero.
enum class SignDisplay {
  AUTO,
  ALWAYS,
  NEVER,
  EXCEPT_ZERO,
};

UNumberUnitWidth ToUNumberUnitWidth(CurrencyDisplay currency_display) {
102
  switch (currency_display) {
103 104 105 106 107 108 109 110
    case CurrencyDisplay::SYMBOL:
      return UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
    case CurrencyDisplay::CODE:
      return UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE;
    case CurrencyDisplay::NAME:
      return UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
    case CurrencyDisplay::NARROW_SYMBOL:
      return UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
111 112 113
  }
}

114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
UNumberUnitWidth ToUNumberUnitWidth(UnitDisplay unit_display) {
  switch (unit_display) {
    case UnitDisplay::SHORT:
      return UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
    case UnitDisplay::LONG:
      return UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
    case UnitDisplay::NARROW:
      return UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
  }
}

UNumberSignDisplay ToUNumberSignDisplay(SignDisplay sign_display,
                                        CurrencySign currency_sign) {
  switch (sign_display) {
    case SignDisplay::AUTO:
      if (currency_sign == CurrencySign::ACCOUNTING) {
        return UNumberSignDisplay::UNUM_SIGN_ACCOUNTING;
      }
      DCHECK(currency_sign == CurrencySign::STANDARD);
      return UNumberSignDisplay::UNUM_SIGN_AUTO;
    case SignDisplay::NEVER:
      return UNumberSignDisplay::UNUM_SIGN_NEVER;
    case SignDisplay::ALWAYS:
      if (currency_sign == CurrencySign::ACCOUNTING) {
        return UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS;
      }
      DCHECK(currency_sign == CurrencySign::STANDARD);
      return UNumberSignDisplay::UNUM_SIGN_ALWAYS;
    case SignDisplay::EXCEPT_ZERO:
      if (currency_sign == CurrencySign::ACCOUNTING) {
        return UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO;
      }
      DCHECK(currency_sign == CurrencySign::STANDARD);
      return UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO;
  }
}

icu::number::Notation ToICUNotation(Notation notation,
                                    CompactDisplay compact_display) {
  switch (notation) {
    case Notation::STANDARD:
      return icu::number::Notation::simple();
    case Notation::SCIENTIFIC:
      return icu::number::Notation::scientific();
    case Notation::ENGINEERING:
      return icu::number::Notation::engineering();
Frank Tang's avatar
Frank Tang committed
160
    // 29. If notation is "compact", then
161
    case Notation::COMPACT:
Frank Tang's avatar
Frank Tang committed
162
      // 29. a. Set numberFormat.[[CompactDisplay]] to compactDisplay.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
      if (compact_display == CompactDisplay::SHORT) {
        return icu::number::Notation::compactShort();
      }
      DCHECK(compact_display == CompactDisplay::LONG);
      return icu::number::Notation::compactLong();
  }
}

std::map<const std::string, icu::MeasureUnit> CreateUnitMap() {
  UErrorCode status = U_ZERO_ERROR;
  int32_t total = icu::MeasureUnit::getAvailable(nullptr, 0, status);
  CHECK(U_FAILURE(status));
  status = U_ZERO_ERROR;
  // See the list in ecma402 #sec-issanctionedsimpleunitidentifier
  std::set<std::string> sanctioned(
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
      {"acre",       "bit",        "byte",
       "celsius",    "centimeter", "day",
       "degree",     "fahrenheit", "fluid-ounce",
       "foot",       "gallon",     "gigabit",
       "gigabyte",   "gram",       "hectare",
       "hour",       "inch",       "kilobit",
       "kilobyte",   "kilogram",   "kilometer",
       "liter",      "megabit",    "megabyte",
       "meter",      "mile",       "mile-scandinavian",
       "millimeter", "milliliter", "millisecond",
       "minute",     "month",      "ounce",
       "percent",    "petabyte",   "pound",
       "second",     "stone",      "terabit",
       "terabyte",   "week",       "yard",
       "year"});
193 194 195 196 197
  std::vector<icu::MeasureUnit> units(total);
  total = icu::MeasureUnit::getAvailable(units.data(), total, status);
  CHECK(U_SUCCESS(status));
  std::map<const std::string, icu::MeasureUnit> map;
  for (auto it = units.begin(); it != units.end(); ++it) {
198 199 200
    // Need to skip none/percent
    if (sanctioned.count(it->getSubtype()) > 0 &&
        strcmp("none", it->getType()) != 0) {
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
      map[it->getSubtype()] = *it;
    }
  }
  return map;
}

class UnitFactory {
 public:
  UnitFactory() : map_(CreateUnitMap()) {}
  virtual ~UnitFactory() {}

  // ecma402 #sec-issanctionedsimpleunitidentifier
  icu::MeasureUnit create(const std::string& unitIdentifier) {
    // 1. If unitIdentifier is in the following list, return true.
    auto found = map_.find(unitIdentifier);
    if (found != map_.end()) {
      return found->second;
    }
    // 2. Return false.
220
    return icu::MeasureUnit();
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
  }

 private:
  std::map<const std::string, icu::MeasureUnit> map_;
};

// ecma402 #sec-issanctionedsimpleunitidentifier
icu::MeasureUnit IsSanctionedUnitIdentifier(const std::string& unit) {
  static base::LazyInstance<UnitFactory>::type factory =
      LAZY_INSTANCE_INITIALIZER;
  return factory.Pointer()->create(unit);
}

// ecma402 #sec-iswellformedunitidentifier
Maybe<std::pair<icu::MeasureUnit, icu::MeasureUnit>> IsWellFormedUnitIdentifier(
    Isolate* isolate, const std::string& unit) {
  icu::MeasureUnit result = IsSanctionedUnitIdentifier(unit);
238
  icu::MeasureUnit none = icu::MeasureUnit();
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
  // 1. If the result of IsSanctionedUnitIdentifier(unitIdentifier) is true,
  // then
  if (result != none) {
    // a. Return true.
    std::pair<icu::MeasureUnit, icu::MeasureUnit> pair(result, none);
    return Just(pair);
  }
  // 2. If the substring "-per-" does not occur exactly once in unitIdentifier,
  // then
  size_t first_per = unit.find("-per-");
  if (first_per == std::string::npos ||
      unit.find("-per-", first_per + 5) != std::string::npos) {
    // a. Return false.
    return Nothing<std::pair<icu::MeasureUnit, icu::MeasureUnit>>();
  }
  // 3. Let numerator be the substring of unitIdentifier from the beginning to
  // just before "-per-".
  std::string numerator = unit.substr(0, first_per);

  // 4. If the result of IsSanctionedUnitIdentifier(numerator) is false, then
  result = IsSanctionedUnitIdentifier(numerator);
  if (result == none) {
    // a. Return false.
    return Nothing<std::pair<icu::MeasureUnit, icu::MeasureUnit>>();
  }
  // 5. Let denominator be the substring of unitIdentifier from just after
  // "-per-" to the end.
  std::string denominator = unit.substr(first_per + 5);

  // 6. If the result of IsSanctionedUnitIdentifier(denominator) is false, then
  icu::MeasureUnit den_result = IsSanctionedUnitIdentifier(denominator);
  if (den_result == none) {
    // a. Return false.
    return Nothing<std::pair<icu::MeasureUnit, icu::MeasureUnit>>();
  }
  // 7. Return true.
  std::pair<icu::MeasureUnit, icu::MeasureUnit> pair(result, den_result);
  return Just(pair);
}

279 280 281 282 283
// ecma-402/#sec-currencydigits
// The currency is expected to an all upper case string value.
int CurrencyDigits(const icu::UnicodeString& currency) {
  UErrorCode status = U_ZERO_ERROR;
  uint32_t fraction_digits = ucurr_getDefaultFractionDigits(
284
      reinterpret_cast<const UChar*>(currency.getBuffer()), &status);
285 286 287 288
  // For missing currency codes, default to the most common, 2
  return U_SUCCESS(status) ? fraction_digits : 2;
}

289 290 291
bool IsAToZ(char ch) {
  return base::IsInRange(AsciiAlphaToLower(ch), 'a', 'z');
}
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310

// ecma402/#sec-iswellformedcurrencycode
bool IsWellFormedCurrencyCode(const std::string& currency) {
  // Verifies that the input is a well-formed ISO 4217 currency code.
  // ecma402/#sec-currency-codes
  // 2. If the number of elements in normalized is not 3, return false.
  if (currency.length() != 3) return false;
  // 1. Let normalized be the result of mapping currency to upper case as
  //   described in 6.1.
  //
  // 3. If normalized contains any character that is not in
  // the range "A" to "Z" (U+0041 to U+005A), return false.
  //
  // 4. Return true.
  // Don't uppercase to test. It could convert invalid code into a valid one.
  // For example \u00DFP (Eszett+P) becomes SSP.
  return (IsAToZ(currency[0]) && IsAToZ(currency[1]) && IsAToZ(currency[2]));
}

311
// Return the style as a String.
312
Handle<String> StyleAsString(Isolate* isolate, Style style) {
313
  switch (style) {
314
    case Style::PERCENT:
315
      return ReadOnlyRoots(isolate).percent_string_handle();
316
    case Style::CURRENCY:
317
      return ReadOnlyRoots(isolate).currency_string_handle();
318
    case Style::UNIT:
319
      return ReadOnlyRoots(isolate).unit_string_handle();
320
    case Style::DECIMAL:
321 322 323
      return ReadOnlyRoots(isolate).decimal_string_handle();
  }
  UNREACHABLE();
324 325 326 327 328 329 330 331 332 333 334
}

// Parse the 'currencyDisplay' from the skeleton.
Handle<String> CurrencyDisplayString(Isolate* isolate,
                                     const icu::UnicodeString& skeleton) {
  // Ex: skeleton as
  // "currency/TWD .00 rounding-mode-half-up unit-width-iso-code"
  if (skeleton.indexOf("unit-width-iso-code") >= 0) {
    return ReadOnlyRoots(isolate).code_string_handle();
  }
  // Ex: skeleton as
335
  // "currency/TWD .00 rounding-mode-half-up unit-width-full-name;"
336 337 338 339 340 341
  if (skeleton.indexOf("unit-width-full-name") >= 0) {
    return ReadOnlyRoots(isolate).name_string_handle();
  }
  // Ex: skeleton as
  // "currency/TWD .00 rounding-mode-half-up unit-width-narrow;
  if (skeleton.indexOf("unit-width-narrow") >= 0) {
342
    return ReadOnlyRoots(isolate).narrowSymbol_string_handle();
343 344 345 346 347 348 349 350 351 352 353
  }
  // Ex: skeleton as "currency/TWD .00 rounding-mode-half-up"
  return ReadOnlyRoots(isolate).symbol_string_handle();
}

// Return true if there are no "group-off" in the skeleton.
bool UseGroupingFromSkeleton(const icu::UnicodeString& skeleton) {
  return skeleton.indexOf("group-off") == -1;
}

// Parse currency code from skeleton. For example, skeleton as
354
// "currency/TWD .00 rounding-mode-half-up unit-width-full-name;"
355 356 357 358 359 360 361
const icu::UnicodeString CurrencyFromSkeleton(
    const icu::UnicodeString& skeleton) {
  const char currency[] = "currency/";
  int32_t index = skeleton.indexOf(currency);
  if (index < 0) return "";
  index += static_cast<int32_t>(std::strlen(currency));
  return skeleton.tempSubString(index, 3);
362 363
}

364 365 366
const icu::UnicodeString NumberingSystemFromSkeleton(
    const icu::UnicodeString& skeleton) {
  const char numbering_system[] = "numbering-system/";
367 368
  int32_t index = skeleton.indexOf(numbering_system);
  if (index < 0) return "latn";
369 370 371 372 373
  index += static_cast<int32_t>(std::strlen(numbering_system));
  const icu::UnicodeString res = skeleton.tempSubString(index);
  index = res.indexOf(" ");
  if (index < 0) return res;
  return res.tempSubString(0, index);
374 375
}

376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
// Return CurrencySign as string based on skeleton.
Handle<String> CurrencySignString(Isolate* isolate,
                                  const icu::UnicodeString& skeleton) {
  // Ex: skeleton as
  // "currency/TWD .00 rounding-mode-half-up sign-accounting-always" OR
  // "currency/TWD .00 rounding-mode-half-up sign-accounting-except-zero"
  if (skeleton.indexOf("sign-accounting") >= 0) {
    return ReadOnlyRoots(isolate).accounting_string_handle();
  }
  return ReadOnlyRoots(isolate).standard_string_handle();
}

// Return UnitDisplay as string based on skeleton.
Handle<String> UnitDisplayString(Isolate* isolate,
                                 const icu::UnicodeString& skeleton) {
  // Ex: skeleton as
  // "measure-unit/length-meter .### rounding-mode-half-up unit-width-full-name"
  if (skeleton.indexOf("unit-width-full-name") >= 0) {
    return ReadOnlyRoots(isolate).long_string_handle();
  }
  // Ex: skeleton as
  // "measure-unit/length-meter .### rounding-mode-half-up unit-width-narrow".
  if (skeleton.indexOf("unit-width-narrow") >= 0) {
    return ReadOnlyRoots(isolate).narrow_string_handle();
  }
  // Ex: skeleton as
  // "measure-unit/length-foot .### rounding-mode-half-up"
  return ReadOnlyRoots(isolate).short_string_handle();
}

// Parse Notation from skeleton.
Notation NotationFromSkeleton(const icu::UnicodeString& skeleton) {
  // Ex: skeleton as
  // "scientific .### rounding-mode-half-up"
  if (skeleton.indexOf("scientific") >= 0) {
    return Notation::SCIENTIFIC;
  }
  // Ex: skeleton as
  // "engineering .### rounding-mode-half-up"
  if (skeleton.indexOf("engineering") >= 0) {
    return Notation::ENGINEERING;
  }
  // Ex: skeleton as
  // "compact-short .### rounding-mode-half-up" or
  // "compact-long .### rounding-mode-half-up
  if (skeleton.indexOf("compact-") >= 0) {
    return Notation::COMPACT;
  }
  // Ex: skeleton as
  // "measure-unit/length-foot .### rounding-mode-half-up"
  return Notation::STANDARD;
}

Handle<String> NotationAsString(Isolate* isolate, Notation notation) {
  switch (notation) {
    case Notation::SCIENTIFIC:
      return ReadOnlyRoots(isolate).scientific_string_handle();
    case Notation::ENGINEERING:
      return ReadOnlyRoots(isolate).engineering_string_handle();
    case Notation::COMPACT:
      return ReadOnlyRoots(isolate).compact_string_handle();
    case Notation::STANDARD:
      return ReadOnlyRoots(isolate).standard_string_handle();
  }
  UNREACHABLE();
}

// Return CompactString as string based on skeleton.
Handle<String> CompactDisplayString(Isolate* isolate,
                                    const icu::UnicodeString& skeleton) {
  // Ex: skeleton as
  // "compact-long .### rounding-mode-half-up"
  if (skeleton.indexOf("compact-long") >= 0) {
    return ReadOnlyRoots(isolate).long_string_handle();
  }
  // Ex: skeleton as
  // "compact-short .### rounding-mode-half-up"
  DCHECK_GE(skeleton.indexOf("compact-short"), 0);
  return ReadOnlyRoots(isolate).short_string_handle();
}

// Return SignDisplay as string based on skeleton.
Handle<String> SignDisplayString(Isolate* isolate,
                                 const icu::UnicodeString& skeleton) {
  // Ex: skeleton as
  // "currency/TWD .00 rounding-mode-half-up sign-never"
  if (skeleton.indexOf("sign-never") >= 0) {
    return ReadOnlyRoots(isolate).never_string_handle();
  }
  // Ex: skeleton as
  // ".### rounding-mode-half-up sign-always" or
  // "currency/TWD .00 rounding-mode-half-up sign-accounting-always"
  if (skeleton.indexOf("sign-always") >= 0 ||
      skeleton.indexOf("sign-accounting-always") >= 0) {
    return ReadOnlyRoots(isolate).always_string_handle();
  }
  // Ex: skeleton as
  // "currency/TWD .00 rounding-mode-half-up sign-accounting-except-zero" or
  // "currency/TWD .00 rounding-mode-half-up sign-except-zero"
  if (skeleton.indexOf("sign-accounting-except-zero") >= 0 ||
      skeleton.indexOf("sign-except-zero") >= 0) {
477
    return ReadOnlyRoots(isolate).exceptZero_string_handle();
478 479 480 481
  }
  return ReadOnlyRoots(isolate).auto_string_handle();
}

482 483
}  // anonymous namespace

484 485 486 487 488 489 490
// Return the minimum integer digits by counting the number of '0' after
// "integer-width/+" in the skeleton.
// Ex: Return 15 for skeleton as
// “currency/TWD .00 rounding-mode-half-up integer-width/+000000000000000”
//                                                                 1
//                                                        123456789012345
// Return default value as 1 if there are no "integer-width/+".
491 492
int32_t JSNumberFormat::MinimumIntegerDigitsFromSkeleton(
    const icu::UnicodeString& skeleton) {
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
  // count the number of 0 after "integer-width/+"
  icu::UnicodeString search("integer-width/+");
  int32_t index = skeleton.indexOf(search);
  if (index < 0) return 1;  // return 1 if cannot find it.
  index += search.length();
  int32_t matched = 0;
  while (index < skeleton.length() && skeleton[index] == '0') {
    matched++;
    index++;
  }
  CHECK_GT(matched, 0);
  return matched;
}

// Return true if there are fraction digits, false if not.
// The minimum fraction digits is the number of '0' after '.' in the skeleton
// The maximum fraction digits is the number of '#' after the above '0's plus
// the minimum fraction digits.
// For example, as skeleton “.000#### rounding-mode-half-up”
//                            123
//                               4567
// Set The minimum as 3 and maximum as 7.
515 516
bool JSNumberFormat::FractionDigitsFromSkeleton(
    const icu::UnicodeString& skeleton, int32_t* minimum, int32_t* maximum) {
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
  icu::UnicodeString search(".");
  int32_t index = skeleton.indexOf(search);
  if (index < 0) return false;
  *minimum = 0;
  index++;  // skip the '.'
  while (index < skeleton.length() && skeleton[index] == '0') {
    (*minimum)++;
    index++;
  }
  *maximum = *minimum;
  while (index < skeleton.length() && skeleton[index] == '#') {
    (*maximum)++;
    index++;
  }
  return true;
}

// Return true if there are significant digits, false if not.
// The minimum significant digits is the number of '@' in the skeleton
// The maximum significant digits is the number of '#' after these '@'s plus
// the minimum significant digits.
// Ex: Skeleton as "@@@@@####### rounding-mode-half-up"
//                  12345
//                       6789012
// Set The minimum as 5 and maximum as 12.
542 543
bool JSNumberFormat::SignificantDigitsFromSkeleton(
    const icu::UnicodeString& skeleton, int32_t* minimum, int32_t* maximum) {
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
  icu::UnicodeString search("@");
  int32_t index = skeleton.indexOf(search);
  if (index < 0) return false;
  *minimum = 1;
  index++;  // skip the first '@'
  while (index < skeleton.length() && skeleton[index] == '@') {
    (*minimum)++;
    index++;
  }
  *maximum = *minimum;
  while (index < skeleton.length() && skeleton[index] == '#') {
    (*maximum)++;
    index++;
  }
  return true;
}

561 562
namespace {

563 564 565 566 567 568 569 570 571 572 573 574
// Ex: percent .### rounding-mode-half-up
// Special case for "percent"
// Ex: "measure-unit/length-kilometer per-measure-unit/duration-hour .###
// rounding-mode-half-up" should return "kilometer-per-unit".
// Ex: "measure-unit/duration-year .### rounding-mode-half-up" should return
// "year".
std::string UnitFromSkeleton(const icu::UnicodeString& skeleton) {
  std::string str;
  str = skeleton.toUTF8String<std::string>(str);
  std::string search("measure-unit/");
  size_t begin = str.find(search);
  if (begin == str.npos) {
575 576 577 578
    // Special case for "percent".
    if (str.find("percent") != str.npos) {
      return "percent";
    }
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
    return "";
  }
  // Skip the type (ex: "length").
  // "measure-unit/length-kilometer per-measure-unit/duration-hour"
  //                     b
  begin = str.find("-", begin + search.size());
  if (begin == str.npos) {
    return "";
  }
  begin++;  // Skip the '-'.
  // Find the end of the subtype.
  size_t end = str.find(" ", begin);
  // "measure-unit/length-kilometer per-measure-unit/duration-hour"
  //                      b        e
  if (end == str.npos) {
    end = str.size();
    return str.substr(begin, end - begin);
  }
  // "measure-unit/length-kilometer per-measure-unit/duration-hour"
  //                      b        e
  //                      [result ]
  std::string result = str.substr(begin, end - begin);
  begin = end + 1;
  // "measure-unit/length-kilometer per-measure-unit/duration-hour"
  //                      [result ]eb
  std::string search_per("per-measure-unit/");
  begin = str.find(search_per, begin);
  // "measure-unit/length-kilometer per-measure-unit/duration-hour"
  //                      [result ]e                 b
  if (begin == str.npos) {
    return result;
  }
  // Skip the type (ex: "duration").
  begin = str.find("-", begin + search_per.size());
  // "measure-unit/length-kilometer per-measure-unit/duration-hour"
  //                      [result ]e                         b
  if (begin == str.npos) {
    return result;
  }
  begin++;  // Skip the '-'.
  // "measure-unit/length-kilometer per-measure-unit/duration-hour"
  //                      [result ]e                          b
  end = str.find(" ", begin);
  if (end == str.npos) {
    end = str.size();
  }
  // "measure-unit/length-kilometer per-measure-unit/duration-hour"
  //                      [result ]                           b   e
  return result + "-per-" + str.substr(begin, end - begin);
}

630 631 632 633 634
Style StyleFromSkeleton(const icu::UnicodeString& skeleton) {
  if (skeleton.indexOf("currency/") >= 0) {
    return Style::CURRENCY;
  }
  if (skeleton.indexOf("measure-unit/") >= 0) {
635 636 637 638
    if (skeleton.indexOf("scale/100") >= 0 &&
        skeleton.indexOf("measure-unit/concentr-percent") >= 0) {
      return Style::PERCENT;
    }
639 640 641 642 643
    return Style::UNIT;
  }
  return Style::DECIMAL;
}

644 645
}  // anonymous namespace

646 647 648 649
icu::number::LocalizedNumberFormatter
JSNumberFormat::SetDigitOptionsToFormatter(
    const icu::number::LocalizedNumberFormatter& icu_number_formatter,
    const Intl::NumberFormatDigitOptions& digit_options) {
Frank Tang's avatar
Frank Tang committed
650 651 652 653 654
  icu::number::LocalizedNumberFormatter result = icu_number_formatter;
  if (digit_options.minimum_integer_digits > 1) {
    result = result.integerWidth(icu::number::IntegerWidth::zeroFillTo(
        digit_options.minimum_integer_digits));
  }
655 656 657 658 659

  // Value -1 of minimum_significant_digits represent the roundingtype is
  // "compact-rounding".
  if (digit_options.minimum_significant_digits < 0) {
    return result;
Frank Tang's avatar
Frank Tang committed
660
  }
661 662 663 664 665 666 667 668 669
  icu::number::Precision precision =
      (digit_options.minimum_significant_digits > 0)
          ? icu::number::Precision::minMaxSignificantDigits(
                digit_options.minimum_significant_digits,
                digit_options.maximum_significant_digits)
          : icu::number::Precision::minMaxFraction(
                digit_options.minimum_fraction_digits,
                digit_options.maximum_fraction_digits);

Frank Tang's avatar
Frank Tang committed
670
  return result.precision(precision);
671 672
}

673
// static
674
// ecma402 #sec-intl.numberformat.prototype.resolvedoptions
675
Handle<JSObject> JSNumberFormat::ResolvedOptions(
676
    Isolate* isolate, Handle<JSNumberFormat> number_format) {
677
  Factory* factory = isolate->factory();
678

679 680
  UErrorCode status = U_ZERO_ERROR;
  icu::number::LocalizedNumberFormatter* icu_number_formatter =
681
      number_format->icu_number_formatter().raw();
682 683 684
  icu::UnicodeString skeleton = icu_number_formatter->toSkeleton(status);
  CHECK(U_SUCCESS(status));

685
  // 4. Let options be ! ObjectCreate(%ObjectPrototype%).
686 687
  Handle<JSObject> options = factory->NewJSObject(isolate->object_function());

688
  Handle<String> locale = Handle<String>(number_format->locale(), isolate);
689 690
  const icu::UnicodeString numberingSystem_ustr =
      NumberingSystemFromSkeleton(skeleton);
691 692 693 694 695 696 697 698 699 700 701 702 703 704
  // 5. For each row of Table 4, except the header row, in table order, do
  // Table 4: Resolved Options of NumberFormat Instances
  //  Internal Slot                    Property
  //    [[Locale]]                      "locale"
  //    [[NumberingSystem]]             "numberingSystem"
  //    [[Style]]                       "style"
  //    [[Currency]]                    "currency"
  //    [[CurrencyDisplay]]             "currencyDisplay"
  //    [[MinimumIntegerDigits]]        "minimumIntegerDigits"
  //    [[MinimumFractionDigits]]       "minimumFractionDigits"
  //    [[MaximumFractionDigits]]       "maximumFractionDigits"
  //    [[MinimumSignificantDigits]]    "minimumSignificantDigits"
  //    [[MaximumSignificantDigits]]    "maximumSignificantDigits"
  //    [[UseGrouping]]                 "useGrouping"
705 706 707
  CHECK(JSReceiver::CreateDataProperty(isolate, options,
                                       factory->locale_string(), locale,
                                       Just(kDontThrow))
708
            .FromJust());
709 710 711 712 713 714
  Handle<String> numberingSystem_string;
  CHECK(Intl::ToString(isolate, numberingSystem_ustr)
            .ToHandle(&numberingSystem_string));
  CHECK(JSReceiver::CreateDataProperty(isolate, options,
                                       factory->numberingSystem_string(),
                                       numberingSystem_string, Just(kDontThrow))
715
            .FromJust());
716
  Style style = StyleFromSkeleton(skeleton);
717 718
  CHECK(JSReceiver::CreateDataProperty(
            isolate, options, factory->style_string(),
719
            StyleAsString(isolate, style), Just(kDontThrow))
720
            .FromJust());
721 722 723 724 725 726 727
  const icu::UnicodeString currency_ustr = CurrencyFromSkeleton(skeleton);
  if (!currency_ustr.isEmpty()) {
    Handle<String> currency_string;
    CHECK(Intl::ToString(isolate, currency_ustr).ToHandle(&currency_string));
    CHECK(JSReceiver::CreateDataProperty(isolate, options,
                                         factory->currency_string(),
                                         currency_string, Just(kDontThrow))
728 729
              .FromJust());

730 731
    CHECK(JSReceiver::CreateDataProperty(
              isolate, options, factory->currencyDisplay_string(),
732
              CurrencyDisplayString(isolate, skeleton), Just(kDontThrow))
733
              .FromJust());
734 735 736 737
    CHECK(JSReceiver::CreateDataProperty(
              isolate, options, factory->currencySign_string(),
              CurrencySignString(isolate, skeleton), Just(kDontThrow))
              .FromJust());
738
  }
739

740
  if (style == Style::UNIT) {
741 742
    std::string unit = UnitFromSkeleton(skeleton);
    if (!unit.empty()) {
743
      CHECK(JSReceiver::CreateDataProperty(
744 745 746
                isolate, options, factory->unit_string(),
                isolate->factory()->NewStringFromAsciiChecked(unit.c_str()),
                Just(kDontThrow))
747 748
                .FromJust());
    }
749 750 751 752 753
    CHECK(JSReceiver::CreateDataProperty(
              isolate, options, factory->unitDisplay_string(),
              UnitDisplayString(isolate, skeleton), Just(kDontThrow))
              .FromJust());
  }
754

755 756
  CHECK(
      JSReceiver::CreateDataProperty(
757 758
          isolate, options, factory->minimumIntegerDigits_string(),
          factory->NewNumberFromInt(MinimumIntegerDigitsFromSkeleton(skeleton)),
759
          Just(kDontThrow))
760
          .FromJust());
761

762
  int32_t minimum = 0, maximum = 0;
763
  if (SignificantDigitsFromSkeleton(skeleton, &minimum, &maximum)) {
764
    CHECK(JSReceiver::CreateDataProperty(
765
              isolate, options, factory->minimumSignificantDigits_string(),
766 767 768
              factory->NewNumberFromInt(minimum), Just(kDontThrow))
              .FromJust());
    CHECK(JSReceiver::CreateDataProperty(
769
              isolate, options, factory->maximumSignificantDigits_string(),
770 771
              factory->NewNumberFromInt(maximum), Just(kDontThrow))
              .FromJust());
772 773
  } else {
    FractionDigitsFromSkeleton(skeleton, &minimum, &maximum);
774
    CHECK(JSReceiver::CreateDataProperty(
775
              isolate, options, factory->minimumFractionDigits_string(),
776
              factory->NewNumberFromInt(minimum), Just(kDontThrow))
777 778
              .FromJust());
    CHECK(JSReceiver::CreateDataProperty(
779
              isolate, options, factory->maximumFractionDigits_string(),
780
              factory->NewNumberFromInt(maximum), Just(kDontThrow))
781 782
              .FromJust());
  }
783

784 785
  CHECK(JSReceiver::CreateDataProperty(
            isolate, options, factory->useGrouping_string(),
786
            factory->ToBoolean(UseGroupingFromSkeleton(skeleton)),
787
            Just(kDontThrow))
788
            .FromJust());
789 790 791 792 793 794 795
  Notation notation = NotationFromSkeleton(skeleton);
  CHECK(JSReceiver::CreateDataProperty(
            isolate, options, factory->notation_string(),
            NotationAsString(isolate, notation), Just(kDontThrow))
            .FromJust());
  // Only output compactDisplay when notation is compact.
  if (notation == Notation::COMPACT) {
796
    CHECK(JSReceiver::CreateDataProperty(
797 798
              isolate, options, factory->compactDisplay_string(),
              CompactDisplayString(isolate, skeleton), Just(kDontThrow))
799 800
              .FromJust());
  }
801 802 803 804
  CHECK(JSReceiver::CreateDataProperty(
            isolate, options, factory->signDisplay_string(),
            SignDisplayString(isolate, skeleton), Just(kDontThrow))
            .FromJust());
805 806 807 808 809 810 811 812 813
  return options;
}

// ecma402/#sec-unwrapnumberformat
MaybeHandle<JSNumberFormat> JSNumberFormat::UnwrapNumberFormat(
    Isolate* isolate, Handle<JSReceiver> format_holder) {
  // old code copy from NumberFormat::Unwrap that has no spec comment and
  // compiled but fail unit tests.
  Handle<Context> native_context =
814
      Handle<Context>(isolate->context().native_context(), isolate);
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837
  Handle<JSFunction> constructor = Handle<JSFunction>(
      JSFunction::cast(native_context->intl_number_format_function()), isolate);
  Handle<Object> object;
  ASSIGN_RETURN_ON_EXCEPTION(
      isolate, object,
      Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
                                 format_holder->IsJSNumberFormat()),
      JSNumberFormat);
  // 4. If ... or nf does not have an [[InitializedNumberFormat]] internal slot,
  // then
  if (!object->IsJSNumberFormat()) {
    // a. Throw a TypeError exception.
    THROW_NEW_ERROR(isolate,
                    NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
                                 isolate->factory()->NewStringFromAsciiChecked(
                                     "UnwrapNumberFormat")),
                    JSNumberFormat);
  }
  // 5. Return nf.
  return Handle<JSNumberFormat>::cast(object);
}

// static
838 839 840
MaybeHandle<JSNumberFormat> JSNumberFormat::New(Isolate* isolate,
                                                Handle<Map> map,
                                                Handle<Object> locales,
841 842
                                                Handle<Object> options_obj,
                                                const char* service) {
843
  Factory* factory = isolate->factory();
844

845
  // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
846 847 848 849 850
  Maybe<std::vector<std::string>> maybe_requested_locales =
      Intl::CanonicalizeLocaleList(isolate, locales);
  MAYBE_RETURN(maybe_requested_locales, Handle<JSNumberFormat>());
  std::vector<std::string> requested_locales =
      maybe_requested_locales.FromJust();
851 852 853 854 855 856 857 858

  // 2. If options is undefined, then
  if (options_obj->IsUndefined(isolate)) {
    // 2. a. Let options be ObjectCreate(null).
    options_obj = isolate->factory()->NewJSObjectWithNullProto();
  } else {
    // 3. Else
    // 3. a. Let options be ? ToObject(options).
859 860 861
    ASSIGN_RETURN_ON_EXCEPTION(isolate, options_obj,
                               Object::ToObject(isolate, options_obj, service),
                               JSNumberFormat);
862 863 864 865 866 867 868 869 870
  }

  // At this point, options_obj can either be a JSObject or a JSProxy only.
  Handle<JSReceiver> options = Handle<JSReceiver>::cast(options_obj);

  // 4. Let opt be a new Record.
  // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «
  // "lookup", "best fit" », "best fit").
  // 6. Set opt.[[localeMatcher]] to matcher.
871
  Maybe<Intl::MatcherOption> maybe_locale_matcher =
872
      Intl::GetLocaleMatcher(isolate, options, service);
873 874
  MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSNumberFormat>());
  Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
875

876
  std::unique_ptr<char[]> numbering_system_str = nullptr;
877 878 879 880 881 882 883 884 885
  // 7. Let _numberingSystem_ be ? GetOption(_options_, `"numberingSystem"`,
  //    `"string"`, *undefined*, *undefined*).
  Maybe<bool> maybe_numberingSystem = Intl::GetNumberingSystem(
      isolate, options, service, &numbering_system_str);
  // 8. If _numberingSystem_ is not *undefined*, then
  // a. If _numberingSystem_ does not match the
  //    `(3*8alphanum) *("-" (3*8alphanum))` sequence, throw a *RangeError*
  //     exception.
  MAYBE_RETURN(maybe_numberingSystem, MaybeHandle<JSNumberFormat>());
886

887 888 889 890
  // 7. Let localeData be %NumberFormat%.[[LocaleData]].
  // 8. Let r be ResolveLocale(%NumberFormat%.[[AvailableLocales]],
  // requestedLocales, opt,  %NumberFormat%.[[RelevantExtensionKeys]],
  // localeData).
891
  std::set<std::string> relevant_extension_keys{"nu"};
892
  Maybe<Intl::ResolvedLocale> maybe_resolve_locale =
893 894
      Intl::ResolveLocale(isolate, JSNumberFormat::GetAvailableLocales(),
                          requested_locales, matcher, relevant_extension_keys);
895 896 897 898 899
  if (maybe_resolve_locale.IsNothing()) {
    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                    JSNumberFormat);
  }
  Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
900

901
  icu::Locale icu_locale = r.icu_locale;
902
  UErrorCode status = U_ZERO_ERROR;
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917
  if (numbering_system_str != nullptr) {
    auto nu_extension_it = r.extensions.find("nu");
    if (nu_extension_it != r.extensions.end() &&
        nu_extension_it->second != numbering_system_str.get()) {
      icu_locale.setUnicodeKeywordValue("nu", nullptr, status);
      CHECK(U_SUCCESS(status));
    }
  }

  // 9. Set numberFormat.[[Locale]] to r.[[locale]].
  Maybe<std::string> maybe_locale_str = Intl::ToLanguageTag(icu_locale);
  MAYBE_RETURN(maybe_locale_str, MaybeHandle<JSNumberFormat>());
  Handle<String> locale_str = isolate->factory()->NewStringFromAsciiChecked(
      maybe_locale_str.FromJust().c_str());

918 919
  if (numbering_system_str != nullptr &&
      Intl::IsValidNumberingSystem(numbering_system_str.get())) {
920
    icu_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(), status);
921 922 923
    CHECK(U_SUCCESS(status));
  }

924
  std::string numbering_system = Intl::GetNumberingSystem(icu_locale);
925 926

  // 11. Let dataLocale be r.[[dataLocale]].
927 928

  icu::number::LocalizedNumberFormatter icu_number_formatter =
929
      icu::number::NumberFormatter::withLocale(icu_locale)
930 931
          .roundingMode(UNUM_ROUND_HALFUP);

932 933 934 935 936
  // For 'latn' numbering system, skip the adoptSymbols which would cause
  // 10.1%-13.7% of regression of JSTests/Intl-NewIntlNumberFormat
  // See crbug/1052751 so we skip calling adoptSymbols and depending on the
  // default instead.
  if (!numbering_system.empty() && numbering_system != "latn") {
937 938 939 940 941 942
    icu_number_formatter = icu_number_formatter.adoptSymbols(
        icu::NumberingSystem::createInstanceByName(numbering_system.c_str(),
                                                   status));
    CHECK(U_SUCCESS(status));
  }

943
  // 3. Let style be ? GetOption(options, "style", "string",  « "decimal",
944 945
  // "percent", "currency", "unit" », "decimal").

946 947 948 949 950
  Maybe<Style> maybe_style = Intl::GetStringOption<Style>(
      isolate, options, "style", service,
      {"decimal", "percent", "currency", "unit"},
      {Style::DECIMAL, Style::PERCENT, Style::CURRENCY, Style::UNIT},
      Style::DECIMAL);
951
  MAYBE_RETURN(maybe_style, MaybeHandle<JSNumberFormat>());
952
  Style style = maybe_style.FromJust();
953

954
  // 4. Set intlObj.[[Style]] to style.
955

956
  // 5. Let currency be ? GetOption(options, "currency", "string", undefined,
957 958
  // undefined).
  std::unique_ptr<char[]> currency_cstr;
959
  const std::vector<const char*> empty_values = {};
960 961 962 963 964
  Maybe<bool> found_currency = Intl::GetStringOption(
      isolate, options, "currency", empty_values, service, &currency_cstr);
  MAYBE_RETURN(found_currency, MaybeHandle<JSNumberFormat>());

  std::string currency;
965
  // 6. If currency is not undefined, then
966 967 968
  if (found_currency.FromJust()) {
    DCHECK_NOT_NULL(currency_cstr.get());
    currency = currency_cstr.get();
969
    // 6. a. If the result of IsWellFormedCurrencyCode(currency) is false,
970 971 972 973
    // throw a RangeError exception.
    if (!IsWellFormedCurrencyCode(currency)) {
      THROW_NEW_ERROR(
          isolate,
974 975
          NewRangeError(MessageTemplate::kInvalid,
                        factory->NewStringFromStaticChars("currency code"),
976 977 978 979 980
                        factory->NewStringFromAsciiChecked(currency.c_str())),
          JSNumberFormat);
    }
  }

981
  // 7. Let currencyDisplay be ? GetOption(options, "currencyDisplay",
982
  // "string", « "code",  "symbol", "name", "narrowSymbol" », "symbol").
983
  Maybe<CurrencyDisplay> maybe_currency_display =
984 985
      Intl::GetStringOption<CurrencyDisplay>(
          isolate, options, "currencyDisplay", service,
986 987 988
          {"code", "symbol", "name", "narrowSymbol"},
          {CurrencyDisplay::CODE, CurrencyDisplay::SYMBOL,
           CurrencyDisplay::NAME, CurrencyDisplay::NARROW_SYMBOL},
989
          CurrencyDisplay::SYMBOL);
990 991 992 993
  MAYBE_RETURN(maybe_currency_display, MaybeHandle<JSNumberFormat>());
  CurrencyDisplay currency_display = maybe_currency_display.FromJust();

  CurrencySign currency_sign = CurrencySign::STANDARD;
994
  // 8. Let currencySign be ? GetOption(options, "currencySign", "string", «
995 996 997 998 999 1000 1001 1002
  // "standard",  "accounting" », "standard").
  Maybe<CurrencySign> maybe_currency_sign = Intl::GetStringOption<CurrencySign>(
      isolate, options, "currencySign", service, {"standard", "accounting"},
      {CurrencySign::STANDARD, CurrencySign::ACCOUNTING},
      CurrencySign::STANDARD);
  MAYBE_RETURN(maybe_currency_sign, MaybeHandle<JSNumberFormat>());
  currency_sign = maybe_currency_sign.FromJust();

1003 1004
  // 9. Let unit be ? GetOption(options, "unit", "string", undefined,
  // undefined).
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
  std::unique_ptr<char[]> unit_cstr;
  Maybe<bool> found_unit = Intl::GetStringOption(
      isolate, options, "unit", empty_values, service, &unit_cstr);
  MAYBE_RETURN(found_unit, MaybeHandle<JSNumberFormat>());

  std::string unit;
  if (found_unit.FromJust()) {
    DCHECK_NOT_NULL(unit_cstr.get());
    unit = unit_cstr.get();
  }
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
  // 10. If unit is not undefined, then
  // 10.a If the result of IsWellFormedUnitIdentifier(unit) is false, throw a
  // RangeError exception.
  Maybe<std::pair<icu::MeasureUnit, icu::MeasureUnit>> maybe_wellformed_unit =
      IsWellFormedUnitIdentifier(isolate, unit);
  if (found_unit.FromJust() && maybe_wellformed_unit.IsNothing()) {
    THROW_NEW_ERROR(
        isolate,
        NewRangeError(MessageTemplate::kInvalidUnit,
                      factory->NewStringFromAsciiChecked(service),
                      factory->NewStringFromAsciiChecked(unit.c_str())),
        JSNumberFormat);
  }
1028

1029
  // 11. Let unitDisplay be ? GetOption(options, "unitDisplay", "string", «
1030 1031 1032 1033 1034 1035 1036 1037
  // "short", "narrow", "long" »,  "short").
  Maybe<UnitDisplay> maybe_unit_display = Intl::GetStringOption<UnitDisplay>(
      isolate, options, "unitDisplay", service, {"short", "narrow", "long"},
      {UnitDisplay::SHORT, UnitDisplay::NARROW, UnitDisplay::LONG},
      UnitDisplay::SHORT);
  MAYBE_RETURN(maybe_unit_display, MaybeHandle<JSNumberFormat>());
  UnitDisplay unit_display = maybe_unit_display.FromJust();

1038 1039
  // 12. If style is "currency", then
  icu::UnicodeString currency_ustr;
1040
  if (style == Style::CURRENCY) {
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
    // 12.a. If currency is undefined, throw a TypeError exception.
    if (!found_currency.FromJust()) {
      THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kCurrencyCode),
                      JSNumberFormat);
    }
    // 12.b. Let currency be the result of converting currency to upper case as
    //    specified in 6.1
    std::transform(currency.begin(), currency.end(), currency.begin(), toupper);
    currency_ustr = currency.c_str();

    // 12.c. Set numberFormat.[[Currency]] to currency.
    if (!currency_ustr.isEmpty()) {
      Handle<String> currency_string;
      ASSIGN_RETURN_ON_EXCEPTION(isolate, currency_string,
                                 Intl::ToString(isolate, currency_ustr),
                                 JSNumberFormat);

      icu_number_formatter = icu_number_formatter.unit(
          icu::CurrencyUnit(currency_ustr.getBuffer(), status));
      CHECK(U_SUCCESS(status));
      // 12.d Set intlObj.[[CurrencyDisplay]] to currencyDisplay.
      // The default unitWidth is SHORT in ICU and that mapped from
      // Symbol so we can skip the setting for optimization.
      if (currency_display != CurrencyDisplay::SYMBOL) {
        icu_number_formatter = icu_number_formatter.unitWidth(
            ToUNumberUnitWidth(currency_display));
      }
      CHECK(U_SUCCESS(status));
    }
  }

  // 13. If style is "unit", then
1073
  if (style == Style::UNIT) {
1074 1075 1076
    // Track newer style "unit".
    isolate->CountUsage(v8::Isolate::UseCounterFeature::kNumberFormatStyleUnit);

1077
    // 13.a If unit is undefined, throw a TypeError exception.
1078 1079 1080 1081 1082 1083
    if (unit == "") {
      THROW_NEW_ERROR(isolate,
                      NewTypeError(MessageTemplate::kInvalidUnit,
                                   factory->NewStringFromAsciiChecked(service),
                                   factory->empty_string()),
                      JSNumberFormat);
1084 1085
    }

1086
    std::pair<icu::MeasureUnit, icu::MeasureUnit> unit_pair =
1087
        maybe_wellformed_unit.FromJust();
1088

1089
    icu::MeasureUnit none = icu::MeasureUnit();
1090
    // 13.b Set intlObj.[[Unit]] to unit.
1091
    if (unit_pair.first != none) {
1092 1093
      icu_number_formatter = icu_number_formatter.unit(unit_pair.first);
    }
1094
    if (unit_pair.second != none) {
1095 1096
      icu_number_formatter = icu_number_formatter.perUnit(unit_pair.second);
    }
1097

1098 1099 1100 1101 1102
    // The default unitWidth is SHORT in ICU and that mapped from
    // Symbol so we can skip the setting for optimization.
    if (unit_display != UnitDisplay::SHORT) {
      icu_number_formatter =
          icu_number_formatter.unitWidth(ToUNumberUnitWidth(unit_display));
1103 1104
    }
  }
1105

1106
  if (style == Style::PERCENT) {
1107 1108 1109
    icu_number_formatter =
        icu_number_formatter.unit(icu::MeasureUnit::getPercent())
            .scale(icu::number::Scale::powerOfTen(2));
1110
  }
1111

Frank Tang's avatar
Frank Tang committed
1112
  // 23. If style is "currency", then
1113
  int mnfd_default, mxfd_default;
1114
  if (style == Style::CURRENCY) {
1115 1116 1117 1118
    // b. Let cDigits be CurrencyDigits(currency).
    int c_digits = CurrencyDigits(currency_ustr);
    // c. Let mnfdDefault be cDigits.
    // d. Let mxfdDefault be cDigits.
1119 1120
    mnfd_default = c_digits;
    mxfd_default = c_digits;
Frank Tang's avatar
Frank Tang committed
1121
    // 24. Else,
1122 1123 1124 1125
  } else {
    // a. Let mnfdDefault be 0.
    mnfd_default = 0;
    // b. If style is "percent", then
1126
    if (style == Style::PERCENT) {
1127 1128 1129 1130 1131 1132 1133 1134
      // i. Let mxfdDefault be 0.
      mxfd_default = 0;
    } else {
      // c. Else,
      // i. Let mxfdDefault be 3.
      mxfd_default = 3;
    }
  }
1135

Frank Tang's avatar
Frank Tang committed
1136
  Notation notation = Notation::STANDARD;
1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
  // 25. Let notation be ? GetOption(options, "notation", "string", «
  // "standard", "scientific",  "engineering", "compact" », "standard").
  Maybe<Notation> maybe_notation = Intl::GetStringOption<Notation>(
      isolate, options, "notation", service,
      {"standard", "scientific", "engineering", "compact"},
      {Notation::STANDARD, Notation::SCIENTIFIC, Notation::ENGINEERING,
       Notation::COMPACT},
      Notation::STANDARD);
  MAYBE_RETURN(maybe_notation, MaybeHandle<JSNumberFormat>());
  notation = maybe_notation.FromJust();
Frank Tang's avatar
Frank Tang committed
1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157

  // 27. Perform ? SetNumberFormatDigitOptions(numberFormat, options,
  // mnfdDefault, mxfdDefault).
  Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
      Intl::SetNumberFormatDigitOptions(isolate, options, mnfd_default,
                                        mxfd_default,
                                        notation == Notation::COMPACT);
  MAYBE_RETURN(maybe_digit_options, Handle<JSNumberFormat>());
  Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
  icu_number_formatter = JSNumberFormat::SetDigitOptionsToFormatter(
      icu_number_formatter, digit_options);
1158

1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173
  // 28. Let compactDisplay be ? GetOption(options, "compactDisplay",
  // "string", « "short", "long" »,  "short").
  Maybe<CompactDisplay> maybe_compact_display =
      Intl::GetStringOption<CompactDisplay>(
          isolate, options, "compactDisplay", service, {"short", "long"},
          {CompactDisplay::SHORT, CompactDisplay::LONG}, CompactDisplay::SHORT);
  MAYBE_RETURN(maybe_compact_display, MaybeHandle<JSNumberFormat>());
  CompactDisplay compact_display = maybe_compact_display.FromJust();

  // 26. Set numberFormat.[[Notation]] to notation.
  // The default notation in ICU is Simple, which mapped from STANDARD
  // so we can skip setting it.
  if (notation != Notation::STANDARD) {
    icu_number_formatter =
        icu_number_formatter.notation(ToICUNotation(notation, compact_display));
1174
  }
Frank Tang's avatar
Frank Tang committed
1175
  // 30. Let useGrouping be ? GetOption(options, "useGrouping", "boolean",
1176 1177 1178 1179 1180
  // undefined, true).
  bool use_grouping = true;
  Maybe<bool> found_use_grouping = Intl::GetBoolOption(
      isolate, options, "useGrouping", service, &use_grouping);
  MAYBE_RETURN(found_use_grouping, MaybeHandle<JSNumberFormat>());
Frank Tang's avatar
Frank Tang committed
1181
  // 31. Set numberFormat.[[UseGrouping]] to useGrouping.
1182 1183 1184 1185
  if (!use_grouping) {
    icu_number_formatter = icu_number_formatter.grouping(
        UNumberGroupingStrategy::UNUM_GROUPING_OFF);
  }
1186

1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205
  // 32. Let signDisplay be ? GetOption(options, "signDisplay", "string", «
  // "auto", "never", "always",  "exceptZero" », "auto").
  Maybe<SignDisplay> maybe_sign_display = Intl::GetStringOption<SignDisplay>(
      isolate, options, "signDisplay", service,
      {"auto", "never", "always", "exceptZero"},
      {SignDisplay::AUTO, SignDisplay::NEVER, SignDisplay::ALWAYS,
       SignDisplay::EXCEPT_ZERO},
      SignDisplay::AUTO);
  MAYBE_RETURN(maybe_sign_display, MaybeHandle<JSNumberFormat>());
  SignDisplay sign_display = maybe_sign_display.FromJust();

  // 33. Set numberFormat.[[SignDisplay]] to signDisplay.
  // The default sign in ICU is UNUM_SIGN_AUTO which is mapped from
  // SignDisplay::AUTO and CurrencySign::STANDARD so we can skip setting
  // under that values for optimization.
  if (sign_display != SignDisplay::AUTO ||
      currency_sign != CurrencySign::STANDARD) {
    icu_number_formatter = icu_number_formatter.sign(
        ToUNumberSignDisplay(sign_display, currency_sign));
1206 1207
  }

1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220
  // 25. Let dataLocaleData be localeData.[[<dataLocale>]].
  //
  // 26. Let patterns be dataLocaleData.[[patterns]].
  //
  // 27. Assert: patterns is a record (see 11.3.3).
  //
  // 28. Let stylePatterns be patterns.[[<style>]].
  //
  // 29. Set numberFormat.[[PositivePattern]] to
  // stylePatterns.[[positivePattern]].
  //
  // 30. Set numberFormat.[[NegativePattern]] to
  // stylePatterns.[[negativePattern]].
1221 1222 1223 1224 1225 1226
  //
  Handle<Managed<icu::number::LocalizedNumberFormatter>>
      managed_number_formatter =
          Managed<icu::number::LocalizedNumberFormatter>::FromRawPtr(
              isolate, 0,
              new icu::number::LocalizedNumberFormatter(icu_number_formatter));
1227 1228 1229 1230 1231 1232 1233

  // Now all properties are ready, so we can allocate the result object.
  Handle<JSNumberFormat> number_format = Handle<JSNumberFormat>::cast(
      isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
  DisallowHeapAllocation no_gc;
  number_format->set_locale(*locale_str);

1234
  number_format->set_icu_number_formatter(*managed_number_formatter);
1235 1236 1237 1238 1239 1240
  number_format->set_bound_format(*factory->undefined_value());

  // 31. Return numberFormat.
  return number_format;
}

1241
namespace {
1242
Maybe<bool> IcuFormatNumber(
1243 1244
    Isolate* isolate,
    const icu::number::LocalizedNumberFormatter& number_format,
1245
    Handle<Object> numeric_obj, icu::number::FormattedNumber* formatted) {
1246 1247 1248 1249 1250 1251 1252
  // If it is BigInt, handle it differently.
  UErrorCode status = U_ZERO_ERROR;
  if (numeric_obj->IsBigInt()) {
    Handle<BigInt> big_int = Handle<BigInt>::cast(numeric_obj);
    Handle<String> big_int_string;
    ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, big_int_string,
                                     BigInt::ToString(isolate, big_int),
1253 1254
                                     Nothing<bool>());
    *formatted = number_format.formatDecimal(
1255
        {big_int_string->ToCString().get(), big_int_string->length()}, status);
1256
  } else {
1257 1258 1259
    double number = numeric_obj->IsNaN()
                        ? std::numeric_limits<double>::quiet_NaN()
                        : numeric_obj->Number();
1260
    *formatted = number_format.formatDouble(number, status);
1261
  }
1262 1263 1264
  if (U_FAILURE(status)) {
    // This happen because of icu data trimming trim out "unit".
    // See https://bugs.chromium.org/p/v8/issues/detail?id=8641
1265 1266
    THROW_NEW_ERROR_RETURN_VALUE(
        isolate, NewTypeError(MessageTemplate::kIcuError), Nothing<bool>());
1267
  }
1268
  return Just(true);
1269 1270 1271 1272 1273
}

}  // namespace

MaybeHandle<String> JSNumberFormat::FormatNumeric(
1274 1275
    Isolate* isolate,
    const icu::number::LocalizedNumberFormatter& number_format,
1276 1277 1278
    Handle<Object> numeric_obj) {
  DCHECK(numeric_obj->IsNumeric());

1279 1280 1281
  icu::number::FormattedNumber formatted;
  Maybe<bool> maybe_format =
      IcuFormatNumber(isolate, number_format, numeric_obj, &formatted);
1282
  MAYBE_RETURN(maybe_format, Handle<String>());
1283 1284 1285 1286 1287 1288
  UErrorCode status = U_ZERO_ERROR;
  icu::UnicodeString result = formatted.toString(status);
  if (U_FAILURE(status)) {
    THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), String);
  }
  return Intl::ToString(isolate, result);
1289 1290
}

1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399
namespace {

bool cmp_NumberFormatSpan(const NumberFormatSpan& a,
                          const NumberFormatSpan& b) {
  // Regions that start earlier should be encountered earlier.
  if (a.begin_pos < b.begin_pos) return true;
  if (a.begin_pos > b.begin_pos) return false;
  // For regions that start in the same place, regions that last longer should
  // be encountered earlier.
  if (a.end_pos < b.end_pos) return false;
  if (a.end_pos > b.end_pos) return true;
  // For regions that are exactly the same, one of them must be the "literal"
  // backdrop we added, which has a field_id of -1, so consider higher field_ids
  // to be later.
  return a.field_id < b.field_id;
}

}  // namespace

// Flattens a list of possibly-overlapping "regions" to a list of
// non-overlapping "parts". At least one of the input regions must span the
// entire space of possible indexes. The regions parameter will sorted in-place
// according to some criteria; this is done for performance to avoid copying the
// input.
std::vector<NumberFormatSpan> FlattenRegionsToParts(
    std::vector<NumberFormatSpan>* regions) {
  // The intention of this algorithm is that it's used to translate ICU "fields"
  // to JavaScript "parts" of a formatted string. Each ICU field and JavaScript
  // part has an integer field_id, which corresponds to something like "grouping
  // separator", "fraction", or "percent sign", and has a begin and end
  // position. Here's a diagram of:

  // var nf = new Intl.NumberFormat(['de'], {style:'currency',currency:'EUR'});
  // nf.formatToParts(123456.78);

  //               :       6
  //  input regions:    0000000211 7
  // ('-' means -1):    ------------
  // formatted string: "123.456,78 €"
  // output parts:      0006000211-7

  // To illustrate the requirements of this algorithm, here's a contrived and
  // convoluted example of inputs and expected outputs:

  //              :          4
  //              :      22 33    3
  //              :      11111   22
  // input regions:     0000000  111
  //              :     ------------
  // formatted string: "abcdefghijkl"
  // output parts:      0221340--231
  // (The characters in the formatted string are irrelevant to this function.)

  // We arrange the overlapping input regions like a mountain range where
  // smaller regions are "on top" of larger regions, and we output a birds-eye
  // view of the mountains, so that smaller regions take priority over larger
  // regions.
  std::sort(regions->begin(), regions->end(), cmp_NumberFormatSpan);
  std::vector<size_t> overlapping_region_index_stack;
  // At least one item in regions must be a region spanning the entire string.
  // Due to the sorting above, the first item in the vector will be one of them.
  overlapping_region_index_stack.push_back(0);
  NumberFormatSpan top_region = regions->at(0);
  size_t region_iterator = 1;
  int32_t entire_size = top_region.end_pos;

  std::vector<NumberFormatSpan> out_parts;

  // The "climber" is a cursor that advances from left to right climbing "up"
  // and "down" the mountains. Whenever the climber moves to the right, that
  // represents an item of output.
  int32_t climber = 0;
  while (climber < entire_size) {
    int32_t next_region_begin_pos;
    if (region_iterator < regions->size()) {
      next_region_begin_pos = regions->at(region_iterator).begin_pos;
    } else {
      // finish off the rest of the input by proceeding to the end.
      next_region_begin_pos = entire_size;
    }

    if (climber < next_region_begin_pos) {
      while (top_region.end_pos < next_region_begin_pos) {
        if (climber < top_region.end_pos) {
          // step down
          out_parts.push_back(NumberFormatSpan(top_region.field_id, climber,
                                               top_region.end_pos));
          climber = top_region.end_pos;
        } else {
          // drop down
        }
        overlapping_region_index_stack.pop_back();
        top_region = regions->at(overlapping_region_index_stack.back());
      }
      if (climber < next_region_begin_pos) {
        // cross a plateau/mesa/valley
        out_parts.push_back(NumberFormatSpan(top_region.field_id, climber,
                                             next_region_begin_pos));
        climber = next_region_begin_pos;
      }
    }
    if (region_iterator < regions->size()) {
      overlapping_region_index_stack.push_back(region_iterator++);
      top_region = regions->at(overlapping_region_index_stack.back());
    }
  }
  return out_parts;
}

1400
namespace {
1401 1402
Maybe<int> ConstructParts(Isolate* isolate,
                          icu::number::FormattedNumber* formatted,
1403
                          Handle<JSArray> result, int start_index,
1404
                          Handle<Object> numeric_obj, bool style_is_unit) {
1405 1406 1407 1408 1409 1410
  UErrorCode status = U_ZERO_ERROR;
  icu::UnicodeString formatted_text = formatted->toString(status);
  if (U_FAILURE(status)) {
    THROW_NEW_ERROR_RETURN_VALUE(
        isolate, NewTypeError(MessageTemplate::kIcuError), Nothing<int>());
  }
1411
  DCHECK(numeric_obj->IsNumeric());
1412
  int32_t length = formatted_text.length();
1413 1414
  int index = start_index;
  if (length == 0) return Just(index);
1415 1416 1417 1418 1419 1420

  std::vector<NumberFormatSpan> regions;
  // Add a "literal" backdrop for the entire string. This will be used if no
  // other region covers some part of the formatted string. It's possible
  // there's another field with exactly the same begin and end as this backdrop,
  // in which case the backdrop's field_id of -1 will give it lower priority.
1421
  regions.push_back(NumberFormatSpan(-1, 0, formatted_text.length()));
1422 1423

  {
1424 1425 1426 1427 1428
    icu::ConstrainedFieldPosition cfp;
    cfp.constrainCategory(UFIELD_CATEGORY_NUMBER);
    while (formatted->nextPosition(cfp, status)) {
      regions.push_back(
          NumberFormatSpan(cfp.getField(), cfp.getStart(), cfp.getLimit()));
1429 1430 1431 1432 1433 1434 1435
    }
  }

  std::vector<NumberFormatSpan> parts = FlattenRegionsToParts(&regions);

  for (auto it = parts.begin(); it < parts.end(); it++) {
    NumberFormatSpan part = *it;
1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446
    Handle<String> field_type_string = isolate->factory()->literal_string();
    if (part.field_id != -1) {
      if (style_is_unit && static_cast<UNumberFormatFields>(part.field_id) ==
                               UNUM_PERCENT_FIELD) {
        // Special case when style is unit.
        field_type_string = isolate->factory()->unit_string();
      } else {
        field_type_string =
            Intl::NumberFieldToType(isolate, numeric_obj, part.field_id);
      }
    }
1447
    Handle<String> substring;
1448
    ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1449
        isolate, substring,
1450
        Intl::ToString(isolate, formatted_text, part.begin_pos, part.end_pos),
1451
        Nothing<int>());
1452
    Intl::AddElement(isolate, result, index, field_type_string, substring);
1453 1454 1455
    ++index;
  }
  JSObject::ValidateElements(*result);
1456 1457 1458
  return Just(index);
}

1459 1460
}  // namespace

1461
MaybeHandle<JSArray> JSNumberFormat::FormatToParts(
1462 1463 1464
    Isolate* isolate, Handle<JSNumberFormat> number_format,
    Handle<Object> numeric_obj) {
  CHECK(numeric_obj->IsNumeric());
1465
  Factory* factory = isolate->factory();
1466
  icu::number::LocalizedNumberFormatter* fmt =
1467
      number_format->icu_number_formatter().raw();
1468 1469
  CHECK_NOT_NULL(fmt);

1470 1471 1472
  icu::number::FormattedNumber formatted;
  Maybe<bool> maybe_format =
      IcuFormatNumber(isolate, *fmt, numeric_obj, &formatted);
1473
  MAYBE_RETURN(maybe_format, Handle<JSArray>());
1474
  UErrorCode status = U_ZERO_ERROR;
1475

1476 1477 1478 1479
  bool style_is_unit =
      Style::UNIT == StyleFromSkeleton(fmt->toSkeleton(status));
  CHECK(U_SUCCESS(status));

1480
  Handle<JSArray> result = factory->NewJSArray(0);
1481 1482
  Maybe<int> maybe_format_to_parts = ConstructParts(
      isolate, &formatted, result, 0, numeric_obj, style_is_unit);
1483
  MAYBE_RETURN(maybe_format_to_parts, Handle<JSArray>());
1484 1485 1486 1487

  return result;
}

1488 1489 1490 1491 1492 1493 1494 1495 1496
namespace {

struct CheckNumberElements {
  static const char* key() { return "NumberElements"; }
  static const char* path() { return nullptr; }
};

}  // namespace

1497
const std::set<std::string>& JSNumberFormat::GetAvailableLocales() {
1498
  static base::LazyInstance<Intl::AvailableLocales<CheckNumberElements>>::type
1499 1500
      available_locales = LAZY_INSTANCE_INITIALIZER;
  return available_locales.Pointer()->Get();
1501 1502
}

1503 1504
}  // namespace internal
}  // namespace v8