Commit 41bc1cfd authored by Frank Tang's avatar Frank Tang Committed by Commit Bot

[Intl] Speed up Intl.NumberFormat constructor x4

1. Use the newer LocalizedNumberFormatter API which improve
   the performance score x3.3.
   Here are how I got the performance score:
  $ python -u tools/run_perf.py --binary-override-path \
    out/x64.release/d8 --filter "JSTests/Intl"  \
    test/js-perf-test/JSTests5.json

  Look for NewIntlNumberFormat-Intl(Score) for 3 runs.

  BEFORE: 539   507  507
   AFTER: 2009 2069 1994

2. Also add symbol and enum to prepare implementing of the unified
   number proposal.


Bug: v8:8515
Change-Id: Ie1ca1dba1e806449632cc96b81d44f0dc61b6093
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1392233
Commit-Queue: Frank Tang <ftang@chromium.org>
Reviewed-by: 's avatarSathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: 's avatarFrank Tang <ftang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61421}
parent 55a7c6a5
......@@ -482,13 +482,14 @@ BUILTIN(NumberFormatInternalFormatNumber) {
Object::ToNumber(isolate, value));
}
icu::NumberFormat* icu_number_format =
number_format->icu_number_format()->raw();
CHECK_NOT_NULL(icu_number_format);
icu::number::LocalizedNumberFormatter* icu_localized_number_formatter =
number_format->icu_number_formatter()->raw();
CHECK_NOT_NULL(icu_localized_number_formatter);
// Return FormatNumber(nf, x).
RETURN_RESULT_OR_FAILURE(
isolate,
JSNumberFormat::FormatNumeric(isolate, *icu_number_format, numeric_obj));
isolate, JSNumberFormat::FormatNumeric(
isolate, *icu_localized_number_formatter, numeric_obj));
}
BUILTIN(DateTimeFormatConstructor) {
......
......@@ -9,20 +9,30 @@
#define INTERNALIZED_STRING_LIST_GENERATOR_INTL(V, _) \
V(_, adoptText_string, "adoptText") \
V(_, baseName_string, "baseName") \
V(_, accounting_string, "accounting") \
V(_, breakType_string, "breakType") \
V(_, calendar_string, "calendar") \
V(_, cardinal_string, "cardinal") \
V(_, caseFirst_string, "caseFirst") \
V(_, compare_string, "compare") \
V(_, current_string, "current") \
V(_, collation_string, "collation") \
V(_, compact_string, "compact") \
V(_, currency_string, "currency") \
V(_, currencyDisplay_string, "currencyDisplay") \
V(_, dateStyle_string, "dateStyle") \
V(_, day_string, "day") \
V(_, dayPeriod_string, "dayPeriod") \
V(_, decimal_string, "decimal") \
V(_, endRange_string, "endRange") \
V(_, engineering_string, "engineering") \
V(_, era_string, "era") \
V(_, first_string, "first") \
V(_, format_string, "format") \
V(_, except_zero_string, "except-zero") \
V(_, exponentInteger_string, "exponentInteger") \
V(_, exponentMinusSign_string, "exponentMinusSign") \
V(_, exponentSeparator_string, "exponentSeparator") \
V(_, fraction_string, "fraction") \
V(_, full_string, "full") \
V(_, granularity_string, "granularity") \
......@@ -35,9 +45,6 @@
V(_, hour_string, "hour") \
V(_, hour12_string, "hour12") \
V(_, hourCycle_string, "hourCycle") \
V(_, collation_string, "collation") \
V(_, currency_string, "currency") \
V(_, currencyDisplay_string, "currencyDisplay") \
V(_, ideo_string, "ideo") \
V(_, ignorePunctuation_string, "ignorePunctuation") \
V(_, Invalid_Date_string, "Invalid Date") \
......@@ -59,6 +66,8 @@
V(_, minute_string, "minute") \
V(_, month_string, "month") \
V(_, nan_string, "nan") \
V(_, narrow_symbol_string, "narrow-symbol") \
V(_, never_string, "never") \
V(_, none_string, "none") \
V(_, normal_string, "normal") \
V(_, numberingSystem_string, "numberingSystem") \
......@@ -68,12 +77,14 @@
V(_, plusSign_string, "plusSign") \
V(_, quarter_string, "quarter") \
V(_, region_string, "region") \
V(_, scientific_string, "scientific") \
V(_, second_string, "second") \
V(_, segment_string, "segment") \
V(_, SegmentIterator_string, "Segment Iterator") \
V(_, sensitivity_string, "sensitivity") \
V(_, sep_string, "sep") \
V(_, shared_string, "shared") \
V(_, standard_string, "standard") \
V(_, startRange_string, "startRange") \
V(_, strict_string, "strict") \
V(_, style_string, "style") \
......
......@@ -4626,12 +4626,12 @@ void Isolate::SetIdle(bool is_idle) {
}
#ifdef V8_INTL_SUPPORT
icu::UObject* Isolate::get_cached_icu_object(ICUObjectCacheType cache_type) {
icu::UMemory* Isolate::get_cached_icu_object(ICUObjectCacheType cache_type) {
return icu_object_cache_[cache_type].get();
}
void Isolate::set_icu_object_in_cache(ICUObjectCacheType cache_type,
std::shared_ptr<icu::UObject> obj) {
std::shared_ptr<icu::UMemory> obj) {
icu_object_cache_[cache_type] = obj;
}
......
......@@ -38,7 +38,7 @@
#ifdef V8_INTL_SUPPORT
#include "unicode/uversion.h" // Define U_ICU_NAMESPACE.
namespace U_ICU_NAMESPACE {
class UObject;
class UMemory;
} // namespace U_ICU_NAMESPACE
#endif // V8_INTL_SUPPORT
......@@ -1143,9 +1143,9 @@ class Isolate final : private HiddenFactory {
kDefaultCollator, kDefaultNumberFormat, kDefaultSimpleDateFormat,
kDefaultSimpleDateFormatForTime, kDefaultSimpleDateFormatForDate};
icu::UObject* get_cached_icu_object(ICUObjectCacheType cache_type);
icu::UMemory* get_cached_icu_object(ICUObjectCacheType cache_type);
void set_icu_object_in_cache(ICUObjectCacheType cache_type,
std::shared_ptr<icu::UObject> obj);
std::shared_ptr<icu::UMemory> obj);
void clear_cached_icu_object(ICUObjectCacheType cache_type);
#endif // V8_INTL_SUPPORT
......@@ -1709,7 +1709,7 @@ class Isolate final : private HiddenFactory {
return static_cast<std::size_t>(a);
}
};
std::unordered_map<ICUObjectCacheType, std::shared_ptr<icu::UObject>,
std::unordered_map<ICUObjectCacheType, std::shared_ptr<icu::UMemory>,
ICUObjectCacheTypeHash>
icu_object_cache_;
......
......@@ -2268,7 +2268,7 @@ void JSNumberFormat::JSNumberFormatVerify(Isolate* isolate) {
CHECK(IsJSNumberFormat());
JSObjectVerify(isolate);
VerifyObjectField(isolate, kLocaleOffset);
VerifyObjectField(isolate, kIcuNumberFormatOffset);
VerifyObjectField(isolate, kIcuNumberFormatterOffset);
VerifyObjectField(isolate, kBoundFormatOffset);
CHECK(bound_format()->IsUndefined(isolate) || bound_format()->IsJSFunction());
VerifySmiField(kFlagsOffset);
......
......@@ -2164,10 +2164,8 @@ void JSLocale::JSLocalePrint(std::ostream& os) { // NOLINT
void JSNumberFormat::JSNumberFormatPrint(std::ostream& os) { // NOLINT
JSObjectPrintHeader(os, *this, "JSNumberFormat");
os << "\n - locale: " << Brief(locale());
os << "\n - icu_number_format: " << Brief(icu_number_format());
os << "\n - icu_number_formatter: " << Brief(icu_number_formatter());
os << "\n - bound_format: " << Brief(bound_format());
os << "\n - style: " << StyleAsString();
os << "\n - currency_display: " << CurrencyDisplayAsString();
JSObjectPrintBody(os, *this);
}
......
......@@ -977,7 +977,7 @@ MaybeHandle<Object> Intl::StringLocaleCompare(Isolate* isolate,
if (can_cache) {
isolate->set_icu_object_in_cache(
Isolate::ICUObjectCacheType::kDefaultCollator,
std::static_pointer_cast<icu::UObject>(
std::static_pointer_cast<icu::UMemory>(
collator->icu_collator()->get()));
}
icu::Collator* icu_collator = collator->icu_collator()->raw();
......@@ -1039,9 +1039,10 @@ MaybeHandle<String> Intl::NumberToLocaleString(Isolate* isolate,
bool can_cache =
locales->IsUndefined(isolate) && options->IsUndefined(isolate);
if (can_cache) {
icu::NumberFormat* cached_number_format =
static_cast<icu::NumberFormat*>(isolate->get_cached_icu_object(
Isolate::ICUObjectCacheType::kDefaultNumberFormat));
icu::number::LocalizedNumberFormatter* cached_number_format =
static_cast<icu::number::LocalizedNumberFormatter*>(
isolate->get_cached_icu_object(
Isolate::ICUObjectCacheType::kDefaultNumberFormat));
// We may use the cached icu::NumberFormat for a fast path.
if (cached_number_format != nullptr) {
return JSNumberFormat::FormatNumeric(isolate, *cached_number_format,
......@@ -1062,13 +1063,13 @@ MaybeHandle<String> Intl::NumberToLocaleString(Isolate* isolate,
if (can_cache) {
isolate->set_icu_object_in_cache(
Isolate::ICUObjectCacheType::kDefaultNumberFormat,
std::static_pointer_cast<icu::UObject>(
number_format->icu_number_format()->get()));
std::static_pointer_cast<icu::UMemory>(
number_format->icu_number_formatter()->get()));
}
// Return FormatNumber(numberFormat, x).
icu::NumberFormat* icu_number_format =
number_format->icu_number_format()->raw();
icu::number::LocalizedNumberFormatter* icu_number_format =
number_format->icu_number_formatter()->raw();
return JSNumberFormat::FormatNumeric(isolate, *icu_number_format,
numeric_obj);
}
......@@ -1130,19 +1131,17 @@ Maybe<int> GetNumberOption(Isolate* isolate, Handle<JSReceiver> options,
} // namespace
Maybe<bool> Intl::SetNumberFormatDigitOptions(Isolate* isolate,
icu::DecimalFormat* number_format,
Handle<JSReceiver> options,
int mnfd_default,
int mxfd_default) {
CHECK_NOT_NULL(number_format);
Maybe<Intl::NumberFormatDigitOptions> Intl::SetNumberFormatDigitOptions(
Isolate* isolate, Handle<JSReceiver> options, int mnfd_default,
int mxfd_default) {
Intl::NumberFormatDigitOptions digit_options;
// 5. Let mnid be ? GetNumberOption(options, "minimumIntegerDigits,", 1, 21,
// 1).
int mnid;
if (!GetNumberOption(isolate, options, "minimumIntegerDigits", 1, 21, 1)
.To(&mnid)) {
return Nothing<bool>();
return Nothing<NumberFormatDigitOptions>();
}
// 6. Let mnfd be ? GetNumberOption(options, "minimumFractionDigits", 0, 20,
......@@ -1151,7 +1150,7 @@ Maybe<bool> Intl::SetNumberFormatDigitOptions(Isolate* isolate,
if (!GetNumberOption(isolate, options, "minimumFractionDigits", 0, 20,
mnfd_default)
.To(&mnfd)) {
return Nothing<bool>();
return Nothing<NumberFormatDigitOptions>();
}
// 7. Let mxfdActualDefault be max( mnfd, mxfdDefault ).
......@@ -1163,7 +1162,7 @@ Maybe<bool> Intl::SetNumberFormatDigitOptions(Isolate* isolate,
if (!GetNumberOption(isolate, options, "maximumFractionDigits", mnfd, 20,
mxfd_actual_default)
.To(&mxfd)) {
return Nothing<bool>();
return Nothing<NumberFormatDigitOptions>();
}
// 9. Let mnsd be ? Get(options, "minimumSignificantDigits").
......@@ -1172,7 +1171,7 @@ Maybe<bool> Intl::SetNumberFormatDigitOptions(Isolate* isolate,
isolate->factory()->minimumSignificantDigits_string();
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, mnsd_obj, JSReceiver::GetProperty(isolate, options, mnsd_str),
Nothing<bool>());
Nothing<NumberFormatDigitOptions>());
// 10. Let mxsd be ? Get(options, "maximumSignificantDigits").
Handle<Object> mxsd_obj;
......@@ -1180,45 +1179,43 @@ Maybe<bool> Intl::SetNumberFormatDigitOptions(Isolate* isolate,
isolate->factory()->maximumSignificantDigits_string();
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, mxsd_obj, JSReceiver::GetProperty(isolate, options, mxsd_str),
Nothing<bool>());
Nothing<NumberFormatDigitOptions>());
// 11. Set intlObj.[[MinimumIntegerDigits]] to mnid.
number_format->setMinimumIntegerDigits(mnid);
digit_options.minimum_integer_digits = mnid;
// 12. Set intlObj.[[MinimumFractionDigits]] to mnfd.
number_format->setMinimumFractionDigits(mnfd);
digit_options.minimum_fraction_digits = mnfd;
// 13. Set intlObj.[[MaximumFractionDigits]] to mxfd.
number_format->setMaximumFractionDigits(mxfd);
digit_options.maximum_fraction_digits = mxfd;
bool significant_digits_used = false;
// 14. If mnsd is not undefined or mxsd is not undefined, then
if (!mnsd_obj->IsUndefined(isolate) || !mxsd_obj->IsUndefined(isolate)) {
// 14. a. Let mnsd be ? DefaultNumberOption(mnsd, 1, 21, 1).
int mnsd;
if (!DefaultNumberOption(isolate, mnsd_obj, 1, 21, 1, mnsd_str).To(&mnsd)) {
return Nothing<bool>();
return Nothing<NumberFormatDigitOptions>();
}
// 14. b. Let mxsd be ? DefaultNumberOption(mxsd, mnsd, 21, 21).
int mxsd;
if (!DefaultNumberOption(isolate, mxsd_obj, mnsd, 21, 21, mxsd_str)
.To(&mxsd)) {
return Nothing<bool>();
return Nothing<NumberFormatDigitOptions>();
}
significant_digits_used = true;
// 14. c. Set intlObj.[[MinimumSignificantDigits]] to mnsd.
number_format->setMinimumSignificantDigits(mnsd);
digit_options.minimum_significant_digits = mnsd;
// 14. d. Set intlObj.[[MaximumSignificantDigits]] to mxsd.
number_format->setMaximumSignificantDigits(mxsd);
digit_options.maximum_significant_digits = mxsd;
} else {
digit_options.minimum_significant_digits = 0;
digit_options.maximum_significant_digits = 0;
}
number_format->setSignificantDigitsUsed(significant_digits_used);
number_format->setRoundingMode(icu::DecimalFormat::kRoundHalfUp);
return Just(true);
return Just(digit_options);
}
namespace {
......@@ -1972,6 +1969,11 @@ Handle<String> Intl::NumberFieldToType(Isolate* isolate,
UNREACHABLE();
return Handle<String>();
case UNUM_COMPACT_FIELD:
return isolate->factory()->compact_string();
case UNUM_MEASURE_UNIT_FIELD:
return isolate->factory()->unit_string();
default:
UNREACHABLE();
return Handle<String>();
......
......@@ -25,11 +25,10 @@
namespace U_ICU_NAMESPACE {
class BreakIterator;
class Collator;
class DecimalFormat;
class FormattedValue;
class SimpleDateFormat;
class UnicodeString;
}
} // namespace U_ICU_NAMESPACE
namespace v8 {
namespace internal {
......@@ -172,9 +171,16 @@ class Intl {
Handle<Object> options);
// ecma402/#sec-setnfdigitoptions
V8_WARN_UNUSED_RESULT static Maybe<bool> SetNumberFormatDigitOptions(
Isolate* isolate, icu::DecimalFormat* number_format,
Handle<JSReceiver> options, int mnfd_default, int mxfd_default);
struct NumberFormatDigitOptions {
int minimum_integer_digits;
int minimum_fraction_digits;
int maximum_fraction_digits;
int minimum_significant_digits;
int maximum_significant_digits;
};
V8_WARN_UNUSED_RESULT static Maybe<NumberFormatDigitOptions>
SetNumberFormatDigitOptions(Isolate* isolate, Handle<JSReceiver> options,
int mnfd_default, int mxfd_default);
static icu::Locale CreateICULocale(const std::string& bcp47_locale);
......
......@@ -28,7 +28,8 @@ extern class JSListFormat extends JSObject {
extern class JSNumberFormat extends JSObject {
locale: String;
icu_number_format: Foreign; // Managed<icu::NumberFormat>
icu_number_formatter:
Foreign; // Managed<icu::number::LocalizedNumberFormatter>
bound_format: JSFunction | Undefined;
flags: Smi;
}
......
......@@ -658,7 +658,7 @@ MaybeHandle<String> JSDateTimeFormat::ToLocaleDateTime(
if (can_cache) {
isolate->set_icu_object_in_cache(
cache_type, std::static_pointer_cast<icu::UObject>(
cache_type, std::static_pointer_cast<icu::UMemory>(
date_time_format->icu_simple_date_format()->get()));
}
// 5. Return FormatDateTime(dateFormat, x).
......
......@@ -21,35 +21,51 @@ namespace internal {
OBJECT_CONSTRUCTORS_IMPL(JSNumberFormat, JSObject)
ACCESSORS(JSNumberFormat, locale, String, kLocaleOffset)
ACCESSORS(JSNumberFormat, icu_number_format, Managed<icu::NumberFormat>,
kIcuNumberFormatOffset)
ACCESSORS(JSNumberFormat, icu_number_formatter,
Managed<icu::number::LocalizedNumberFormatter>,
kIcuNumberFormatterOffset)
ACCESSORS(JSNumberFormat, bound_format, Object, kBoundFormatOffset)
// Currenct ECMA 402 spec mandate to record (Min|Max)imumFractionDigits
// uncondictionally while the unified number proposal eventually will only
// record either (Min|Max)imumFractionDigits or (Min|Max)imumSignaficantDigits
// Since LocalizedNumberFormatter can only remember one set, and during
// 2019-1-17 ECMA402 meeting that the committee decide not to take a PR to
// address that prior to the unified number proposal, we have to add these two
// 5 bits int into flags to remember the (Min|Max)imumFractionDigits while
// (Min|Max)imumSignaficantDigits is present.
// TODO(ftang) remove the following once we ship int-number-format-unified
// * SMI_ACCESSORS of flags
// * Four inline functions: (set_)?(min|max)imum_fraction_digits
SMI_ACCESSORS(JSNumberFormat, flags, kFlagsOffset)
inline void JSNumberFormat::set_style(Style style) {
DCHECK_LT(style, Style::COUNT);
inline int JSNumberFormat::minimum_fraction_digits() const {
return MinimumFractionDigitsBits::decode(flags());
}
inline void JSNumberFormat::set_minimum_fraction_digits(int digits) {
DCHECK_GE(MinimumFractionDigitsBits::kMax, digits);
DCHECK_LE(0, digits);
DCHECK_GE(20, digits);
int hints = flags();
hints = StyleBits::update(hints, style);
hints = MinimumFractionDigitsBits::update(hints, digits);
set_flags(hints);
}
inline JSNumberFormat::Style JSNumberFormat::style() const {
return StyleBits::decode(flags());
inline int JSNumberFormat::maximum_fraction_digits() const {
return MaximumFractionDigitsBits::decode(flags());
}
inline void JSNumberFormat::set_currency_display(
CurrencyDisplay currency_display) {
DCHECK_LT(currency_display, CurrencyDisplay::COUNT);
inline void JSNumberFormat::set_maximum_fraction_digits(int digits) {
DCHECK_GE(MaximumFractionDigitsBits::kMax, digits);
DCHECK_LE(0, digits);
DCHECK_GE(20, digits);
int hints = flags();
hints = CurrencyDisplayBits::update(hints, currency_display);
hints = MaximumFractionDigitsBits::update(hints, digits);
set_flags(hints);
}
inline JSNumberFormat::CurrencyDisplay JSNumberFormat::currency_display()
const {
return CurrencyDisplayBits::decode(flags());
}
CAST_ACCESSOR(JSNumberFormat)
} // namespace internal
......
......@@ -15,27 +15,106 @@
#include "src/objects-inl.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-number-format-inl.h"
#include "unicode/currunit.h"
#include "unicode/decimfmt.h"
#include "unicode/locid.h"
#include "unicode/nounit.h"
#include "unicode/numberformatter.h"
#include "unicode/numfmt.h"
#include "unicode/ucurr.h"
#include "unicode/uloc.h"
#include "unicode/unumberformatter.h"
#include "unicode/uvernum.h" // for U_ICU_VERSION_MAJOR_NUM
namespace v8 {
namespace internal {
namespace {
UNumberFormatStyle ToNumberFormatStyle(
JSNumberFormat::CurrencyDisplay currency_display) {
// [[Style]] is one of the values "decimal", "percent", "currency",
// or "unit" identifying the style of the number format.
// Note: "unit" is added in proposal-unified-intl-numberformat
enum class Style {
DECIMAL,
PERCENT,
CURRENCY,
UNIT,
};
// [[CurrencyDisplay]] is one of the values "code", "symbol", "name",
// or "narrow-symbol" identifying the display of the currency number format.
// Note: "narrow-symbol" is added in proposal-unified-intl-numberformat
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
// localized long name if formatting with the "unit" or "percent" style. It is
// only used when [[Style]] has the value "unit" or "percent".
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
// "except-zero", specifying whether to show the sign on negative numbers
// 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) {
switch (currency_display) {
case JSNumberFormat::CurrencyDisplay::SYMBOL:
return UNUM_CURRENCY;
case JSNumberFormat::CurrencyDisplay::CODE:
return UNUM_CURRENCY_ISO;
case JSNumberFormat::CurrencyDisplay::NAME:
return UNUM_CURRENCY_PLURAL;
case JSNumberFormat::CurrencyDisplay::COUNT:
UNREACHABLE();
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;
}
}
......@@ -69,23 +148,162 @@ bool IsWellFormedCurrencyCode(const std::string& currency) {
return (IsAToZ(currency[0]) && IsAToZ(currency[1]) && IsAToZ(currency[2]));
}
// Parse the 'style' from the skeleton.
Handle<String> StyleString(Isolate* isolate,
const icu::UnicodeString& skeleton) {
// Ex: skeleton as
// "percent precision-integer rounding-mode-half-up scale/100"
if (skeleton.indexOf("percent") >= 0) {
return ReadOnlyRoots(isolate).percent_string_handle();
}
// Ex: skeleton as "currency/TWD .00 rounding-mode-half-up"
if (skeleton.indexOf("currency") >= 0) {
return ReadOnlyRoots(isolate).currency_string_handle();
}
// Ex: skeleton as
// "measure-unit/length-meter .### rounding-mode-half-up unit-width-narrow"
if (skeleton.indexOf("measure-unit") >= 0) {
return ReadOnlyRoots(isolate).unit_string_handle();
}
// Ex: skeleton as ".### rounding-mode-half-up"
return ReadOnlyRoots(isolate).decimal_string_handle();
}
// 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
// "currency/TWD .00 rounding-mode-half-up unit-width-full-name;
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) {
return ReadOnlyRoots(isolate).narrow_symbol_string_handle();
}
// 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
// "currency/TWD .00 rounding-mode-half-up unit-width-full-name;
std::string CurrencyFromSkeleton(const icu::UnicodeString& skeleton) {
std::string str;
str = skeleton.toUTF8String<std::string>(str);
std::string search("currency/");
size_t index = str.find(search);
if (index == str.npos) return "";
return str.substr(index + search.size(), 3);
}
// 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/+".
int32_t MinimumIntegerDigitsFromSkeleton(const icu::UnicodeString& skeleton) {
// 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.
bool FractionDigitsFromSkeleton(const icu::UnicodeString& skeleton,
int32_t* minimum, int32_t* maximum) {
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.
bool SignificantDigitsFromSkeleton(const icu::UnicodeString& skeleton,
int32_t* minimum, int32_t* maximum) {
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;
}
} // anonymous namespace
// static
// ecma402 #sec-intl.numberformat.prototype.resolvedoptions
Handle<JSObject> JSNumberFormat::ResolvedOptions(
Isolate* isolate, Handle<JSNumberFormat> number_format_holder) {
Isolate* isolate, Handle<JSNumberFormat> number_format) {
Factory* factory = isolate->factory();
UErrorCode status = U_ZERO_ERROR;
icu::number::LocalizedNumberFormatter* icu_number_formatter =
number_format->icu_number_formatter()->raw();
icu::UnicodeString skeleton = icu_number_formatter->toSkeleton(status);
CHECK(U_SUCCESS(status));
std::string s_str;
s_str = skeleton.toUTF8String<std::string>(s_str);
// 4. Let options be ! ObjectCreate(%ObjectPrototype%).
Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
icu::NumberFormat* number_format =
number_format_holder->icu_number_format()->raw();
CHECK_NOT_NULL(number_format);
Handle<String> locale =
Handle<String>(number_format_holder->locale(), isolate);
Handle<String> locale = Handle<String>(number_format->locale(), isolate);
std::unique_ptr<char[]> locale_str = locale->ToCString();
icu::Locale icu_locale = Intl::CreateICULocale(locale_str.get());
......@@ -119,65 +337,67 @@ Handle<JSObject> JSNumberFormat::ResolvedOptions(
}
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->style_string(),
number_format_holder->StyleAsString(), Just(kDontThrow))
StyleString(isolate, skeleton), Just(kDontThrow))
.FromJust());
if (number_format_holder->style() == Style::CURRENCY) {
icu::UnicodeString currency(number_format->getCurrency());
DCHECK(!currency.isEmpty());
std::string currency = CurrencyFromSkeleton(skeleton);
if (!currency.empty()) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->currency_string(),
factory
->NewStringFromTwoByte(Vector<const uint16_t>(
reinterpret_cast<const uint16_t*>(currency.getBuffer()),
currency.length()))
.ToHandleChecked(),
factory->NewStringFromAsciiChecked(currency.c_str()),
Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->currencyDisplay_string(),
number_format_holder->CurrencyDisplayAsString(), Just(kDontThrow))
CurrencyDisplayString(isolate, skeleton), Just(kDontThrow))
.FromJust());
}
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->minimumIntegerDigits_string(),
factory->NewNumberFromInt(number_format->getMinimumIntegerDigits()),
Just(kDontThrow))
.FromJust());
CHECK(
JSReceiver::CreateDataProperty(
isolate, options, factory->minimumFractionDigits_string(),
factory->NewNumberFromInt(number_format->getMinimumFractionDigits()),
Just(kDontThrow))
.FromJust());
CHECK(
JSReceiver::CreateDataProperty(
isolate, options, factory->maximumFractionDigits_string(),
factory->NewNumberFromInt(number_format->getMaximumFractionDigits()),
isolate, options, factory->minimumIntegerDigits_string(),
factory->NewNumberFromInt(MinimumIntegerDigitsFromSkeleton(skeleton)),
Just(kDontThrow))
.FromJust());
CHECK(number_format->getDynamicClassID() ==
icu::DecimalFormat::getStaticClassID());
icu::DecimalFormat* decimal_format =
static_cast<icu::DecimalFormat*>(number_format);
CHECK_NOT_NULL(decimal_format);
if (decimal_format->areSignificantDigitsUsed()) {
int32_t minimum = 0, maximum = 0;
if (!FractionDigitsFromSkeleton(skeleton, &minimum, &maximum)) {
// Currenct ECMA 402 spec mandate to record (Min|Max)imumFractionDigits
// uncondictionally while the unified number proposal eventually will only
// record either (Min|Max)imumFractionDigits or
// (Min|Max)imumSignaficantDigits Since LocalizedNumberFormatter can only
// remember one set, and during 2019-1-17 ECMA402 meeting that the committee
// decide not to take a PR to address that prior to the unified number
// proposal, we have to add these two 5 bits int into flags to remember the
// (Min|Max)imumFractionDigits while (Min|Max)imumSignaficantDigits is
// present.
// TODO(ftang) remove the following two lines once we ship
// int-number-format-unified
minimum = number_format->minimum_fraction_digits();
maximum = number_format->maximum_fraction_digits();
}
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->minimumFractionDigits_string(),
factory->NewNumberFromInt(minimum), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->maximumFractionDigits_string(),
factory->NewNumberFromInt(maximum), Just(kDontThrow))
.FromJust());
minimum = 0;
maximum = 0;
if (SignificantDigitsFromSkeleton(skeleton, &minimum, &maximum)) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->minimumSignificantDigits_string(),
factory->NewNumberFromInt(
decimal_format->getMinimumSignificantDigits()),
Just(kDontThrow))
factory->NewNumberFromInt(minimum), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->maximumSignificantDigits_string(),
factory->NewNumberFromInt(
decimal_format->getMaximumSignificantDigits()),
Just(kDontThrow))
factory->NewNumberFromInt(maximum), Just(kDontThrow))
.FromJust());
}
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->useGrouping_string(),
factory->ToBoolean((number_format->isGroupingUsed() == TRUE)),
factory->ToBoolean(UseGroupingFromSkeleton(skeleton)),
Just(kDontThrow))
.FromJust());
return options;
......@@ -216,8 +436,8 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::UnwrapNumberFormat(
MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> locales, Handle<Object> options_obj) {
// set the flags to 0 ASAP.
number_format->set_flags(0);
// set the flags to 0 ASAP.
Factory* factory = isolate->factory();
// 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
......@@ -299,7 +519,6 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
Style style = maybe_style.FromJust();
// 13. Set numberFormat.[[Style]] to style.
number_format->set_style(style);
// 14. Let currency be ? GetOption(options, "currency", "string", undefined,
// undefined).
......@@ -355,75 +574,35 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
CurrencyDisplay::SYMBOL);
MAYBE_RETURN(maybe_currencyDisplay, MaybeHandle<JSNumberFormat>());
CurrencyDisplay currency_display = maybe_currencyDisplay.FromJust();
UNumberFormatStyle format_style = ToNumberFormatStyle(currency_display);
std::unique_ptr<icu::NumberFormat> icu_number_format;
icu::Locale no_extension_locale(r.icu_locale.getBaseName());
if (style == Style::DECIMAL) {
icu_number_format.reset(
icu::NumberFormat::createInstance(r.icu_locale, status));
// If the subclass is not DecimalFormat, fallback to no extension
// because other subclass has not support the format() with
// FieldPositionIterator yet.
if (U_FAILURE(status) || icu_number_format.get() == nullptr ||
icu_number_format->getDynamicClassID() !=
icu::DecimalFormat::getStaticClassID()) {
status = U_ZERO_ERROR;
icu_number_format.reset(
icu::NumberFormat::createInstance(no_extension_locale, status));
}
} else if (style == Style::PERCENT) {
icu_number_format.reset(
icu::NumberFormat::createPercentInstance(r.icu_locale, status));
// If the subclass is not DecimalFormat, fallback to no extension
// because other subclass has not support the format() with
// FieldPositionIterator yet.
if (U_FAILURE(status) || icu_number_format.get() == nullptr ||
icu_number_format->getDynamicClassID() !=
icu::DecimalFormat::getStaticClassID()) {
status = U_ZERO_ERROR;
icu_number_format.reset(icu::NumberFormat::createPercentInstance(
no_extension_locale, status));
}
} else {
DCHECK_EQ(style, Style::CURRENCY);
icu_number_format.reset(
icu::NumberFormat::createInstance(r.icu_locale, format_style, status));
// If the subclass is not DecimalFormat, fallback to no extension
// because other subclass has not support the format() with
// FieldPositionIterator yet.
if (U_FAILURE(status) || icu_number_format.get() == nullptr ||
icu_number_format->getDynamicClassID() !=
icu::DecimalFormat::getStaticClassID()) {
status = U_ZERO_ERROR;
icu_number_format.reset(icu::NumberFormat::createInstance(
no_extension_locale, format_style, status));
}
}
if (U_FAILURE(status) || icu_number_format.get() == nullptr) {
status = U_ZERO_ERROR;
// Remove extensions and try again.
icu_number_format.reset(
icu::NumberFormat::createInstance(no_extension_locale, status));
if (U_FAILURE(status) || icu_number_format.get() == nullptr) {
FATAL("Failed to create ICU number_format, are ICU data files missing?");
}
icu::number::LocalizedNumberFormatter icu_number_formatter =
icu::number::NumberFormatter::withLocale(r.icu_locale)
.roundingMode(UNUM_ROUND_HALFUP);
if (style == Style::PERCENT) {
icu_number_formatter = icu_number_formatter.unit(icu::NoUnit::percent())
.scale(icu::number::Scale::powerOfTen(2));
}
DCHECK(U_SUCCESS(status));
CHECK_NOT_NULL(icu_number_format.get());
CHECK(icu_number_format->getDynamicClassID() ==
icu::DecimalFormat::getStaticClassID());
if (style == Style::CURRENCY) {
// 19. If style is "currency", set numberFormat.[[CurrencyDisplay]] to
// currencyDisplay.
number_format->set_currency_display(currency_display);
// 17.b. Set numberFormat.[[Currency]] to currency.
if (!currency_ustr.isEmpty()) {
status = U_ZERO_ERROR;
icu_number_format->setCurrency(currency_ustr.getBuffer(), status);
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));
// 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));
}
}
......@@ -451,14 +630,45 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
}
// 22. Perform ? SetNumberFormatDigitOptions(numberFormat, options,
// mnfdDefault, mxfdDefault).
CHECK(icu_number_format->getDynamicClassID() ==
icu::DecimalFormat::getStaticClassID());
icu::DecimalFormat* icu_decimal_format =
static_cast<icu::DecimalFormat*>(icu_number_format.get());
Maybe<bool> maybe_set_number_for_digit_options =
Intl::SetNumberFormatDigitOptions(isolate, icu_decimal_format, options,
mnfd_default, mxfd_default);
MAYBE_RETURN(maybe_set_number_for_digit_options, Handle<JSNumberFormat>());
Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
Intl::SetNumberFormatDigitOptions(isolate, options, mnfd_default,
mxfd_default);
MAYBE_RETURN(maybe_digit_options, Handle<JSNumberFormat>());
Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
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);
if (digit_options.minimum_significant_digits > 0) {
// Currenct ECMA 402 spec mandate to record (Min|Max)imumFractionDigits
// uncondictionally while the unified number proposal eventually will only
// record either (Min|Max)imumFractionDigits or
// (Min|Max)imumSignaficantDigits Since LocalizedNumberFormatter can only
// remember one set, and during 2019-1-17 ECMA402 meeting that the committee
// decide not to take a PR to address that prior to the unified number
// proposal, we have to add these two 5 bits int into flags to remember the
// (Min|Max)imumFractionDigits while (Min|Max)imumSignaficantDigits is
// present.
// TODO(ftang) remove the following two lines once we ship
// int-number-format-unified
number_format->set_minimum_fraction_digits(
digit_options.minimum_fraction_digits);
number_format->set_maximum_fraction_digits(
digit_options.maximum_fraction_digits);
}
icu_number_formatter = icu_number_formatter.precision(precision);
if (digit_options.minimum_integer_digits > 1) {
icu_number_formatter =
icu_number_formatter.integerWidth(icu::number::IntegerWidth::zeroFillTo(
digit_options.minimum_integer_digits));
}
// 23. Let useGrouping be ? GetOption(options, "useGrouping", "boolean",
// undefined, true).
......@@ -467,7 +677,10 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
isolate, options, "useGrouping", service, &use_grouping);
MAYBE_RETURN(found_use_grouping, MaybeHandle<JSNumberFormat>());
// 24. Set numberFormat.[[UseGrouping]] to useGrouping.
icu_number_format->setGroupingUsed(use_grouping ? TRUE : FALSE);
if (!use_grouping) {
icu_number_formatter = icu_number_formatter.grouping(
UNumberGroupingStrategy::UNUM_GROUPING_OFF);
}
// 25. Let dataLocaleData be localeData.[[<dataLocale>]].
//
......@@ -482,63 +695,41 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
//
// 30. Set numberFormat.[[NegativePattern]] to
// stylePatterns.[[negativePattern]].
Handle<Managed<icu::NumberFormat>> managed_number_format =
Managed<icu::NumberFormat>::FromUniquePtr(isolate, 0,
std::move(icu_number_format));
number_format->set_icu_number_format(*managed_number_format);
//
Handle<Managed<icu::number::LocalizedNumberFormatter>>
managed_number_formatter =
Managed<icu::number::LocalizedNumberFormatter>::FromRawPtr(
isolate, 0,
new icu::number::LocalizedNumberFormatter(icu_number_formatter));
number_format->set_icu_number_formatter(*managed_number_formatter);
number_format->set_bound_format(*factory->undefined_value());
// 31. Return numberFormat.
return number_format;
}
Handle<String> JSNumberFormat::StyleAsString() const {
switch (style()) {
case Style::DECIMAL:
return GetReadOnlyRoots().decimal_string_handle();
case Style::PERCENT:
return GetReadOnlyRoots().percent_string_handle();
case Style::CURRENCY:
return GetReadOnlyRoots().currency_string_handle();
case Style::COUNT:
UNREACHABLE();
}
}
Handle<String> JSNumberFormat::CurrencyDisplayAsString() const {
switch (currency_display()) {
case CurrencyDisplay::CODE:
return GetReadOnlyRoots().code_string_handle();
case CurrencyDisplay::SYMBOL:
return GetReadOnlyRoots().symbol_string_handle();
case CurrencyDisplay::NAME:
return GetReadOnlyRoots().name_string_handle();
case CurrencyDisplay::COUNT:
UNREACHABLE();
}
}
namespace {
Maybe<icu::UnicodeString> IcuFormatNumber(
Isolate* isolate, const icu::NumberFormat& number_format,
Isolate* isolate,
const icu::number::LocalizedNumberFormatter& number_format,
Handle<Object> numeric_obj, icu::FieldPositionIterator* fp_iter) {
icu::UnicodeString result;
// If it is BigInt, handle it differently.
UErrorCode status = U_ZERO_ERROR;
icu::number::FormattedNumber formatted;
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),
Nothing<icu::UnicodeString>());
number_format.format(
{big_int_string->ToCString().get(), big_int_string->length()}, result,
fp_iter, status);
formatted = number_format.formatDecimal(
{big_int_string->ToCString().get(), big_int_string->length()}, status);
} else {
double number = numeric_obj->Number();
number_format.format(number, result, fp_iter, status);
formatted = number_format.formatDouble(number, status);
}
formatted.getAllFieldPositions(*fp_iter, status);
icu::UnicodeString result = formatted.toString(status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR_RETURN_VALUE(isolate,
NewTypeError(MessageTemplate::kIcuError),
......@@ -550,17 +741,15 @@ Maybe<icu::UnicodeString> IcuFormatNumber(
} // namespace
MaybeHandle<String> JSNumberFormat::FormatNumeric(
Isolate* isolate, const icu::NumberFormat& number_format,
Isolate* isolate,
const icu::number::LocalizedNumberFormatter& number_format,
Handle<Object> numeric_obj) {
DCHECK(numeric_obj->IsNumeric());
Maybe<icu::UnicodeString> maybe_format =
IcuFormatNumber(isolate, number_format, numeric_obj, nullptr);
MAYBE_RETURN(maybe_format, Handle<String>());
icu::UnicodeString result = maybe_format.FromJust();
return isolate->factory()->NewStringFromTwoByte(Vector<const uint16_t>(
reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length()));
return Intl::ToString(isolate, maybe_format.FromJust());
}
namespace {
......@@ -672,19 +861,12 @@ std::vector<NumberFormatSpan> FlattenRegionsToParts(
return out_parts;
}
Maybe<int> JSNumberFormat::FormatToParts(Isolate* isolate,
Handle<JSArray> result,
int start_index,
const icu::NumberFormat& number_format,
Handle<Object> numeric_obj,
Handle<String> unit) {
namespace {
Maybe<int> ConstructParts(Isolate* isolate, const icu::UnicodeString& formatted,
icu::FieldPositionIterator* fp_iter,
Handle<JSArray> result, int start_index,
Handle<Object> numeric_obj, Handle<String> unit) {
DCHECK(numeric_obj->IsNumeric());
icu::FieldPositionIterator fp_iter;
Maybe<icu::UnicodeString> maybe_format =
IcuFormatNumber(isolate, number_format, numeric_obj, &fp_iter);
MAYBE_RETURN(maybe_format, Nothing<int>());
icu::UnicodeString formatted = maybe_format.FromJust();
int32_t length = formatted.length();
int index = start_index;
if (length == 0) return Just(index);
......@@ -698,7 +880,7 @@ Maybe<int> JSNumberFormat::FormatToParts(Isolate* isolate,
{
icu::FieldPosition fp;
while (fp_iter.next(fp)) {
while (fp_iter->next(fp)) {
regions.push_back(NumberFormatSpan(fp.getField(), fp.getBeginIndex(),
fp.getEndIndex()));
}
......@@ -729,18 +911,26 @@ Maybe<int> JSNumberFormat::FormatToParts(Isolate* isolate,
return Just(index);
}
} // namespace
MaybeHandle<JSArray> JSNumberFormat::FormatToParts(
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> numeric_obj) {
CHECK(numeric_obj->IsNumeric());
Factory* factory = isolate->factory();
icu::NumberFormat* fmt = number_format->icu_number_format()->raw();
icu::number::LocalizedNumberFormatter* fmt =
number_format->icu_number_formatter()->raw();
CHECK_NOT_NULL(fmt);
Handle<JSArray> result = factory->NewJSArray(0);
icu::FieldPositionIterator fp_iter;
Maybe<icu::UnicodeString> maybe_format =
IcuFormatNumber(isolate, *fmt, numeric_obj, &fp_iter);
MAYBE_RETURN(maybe_format, Handle<JSArray>());
Maybe<int> maybe_format_to_parts = JSNumberFormat::FormatToParts(
isolate, result, 0, *fmt, numeric_obj, Handle<String>());
Handle<JSArray> result = factory->NewJSArray(0);
Maybe<int> maybe_format_to_parts =
ConstructParts(isolate, maybe_format.FromJust(), &fp_iter, result, 0,
numeric_obj, Handle<String>());
MAYBE_RETURN(maybe_format_to_parts, Handle<JSArray>());
return result;
......
......@@ -17,12 +17,14 @@
#include "src/objects.h"
#include "src/objects/intl-objects.h"
#include "src/objects/managed.h"
#include "unicode/numberformatter.h"
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
namespace U_ICU_NAMESPACE {
class NumberFormat;
class UnicodeString;
} // namespace U_ICU_NAMESPACE
namespace v8 {
......@@ -47,78 +49,55 @@ class JSNumberFormat : public JSObject {
Isolate* isolate, Handle<JSNumberFormat> number_format,
Handle<Object> numeric_obj);
// A utility function used by the above JSNumberFormat::FormatToParts()
// and JSRelativeTimeFormat::FormatToParts().
// Format the number by using the icu::NumberFormat to get the field
// information. It add an object into the result array, starting from the
// start_index and return the total number of elements in the result array.
// For each object added as element, it set the substring of the field as
// "value", the field type as "type". If the unit is not null, it also set
// unit as "unit" to each added object.
V8_WARN_UNUSED_RESULT static Maybe<int> FormatToParts(
Isolate* isolate, Handle<JSArray> result, int start_index,
const icu::NumberFormat& fmt, Handle<Object> numeric_obj,
Handle<String> unit);
V8_WARN_UNUSED_RESULT static MaybeHandle<String> FormatNumeric(
Isolate* isolate, const icu::NumberFormat& number_format,
Isolate* isolate,
const icu::number::LocalizedNumberFormatter& number_format,
Handle<Object> numeric_obj);
V8_EXPORT_PRIVATE static const std::set<std::string>& GetAvailableLocales();
Handle<String> StyleAsString() const;
Handle<String> CurrencyDisplayAsString() const;
DECL_CAST(JSNumberFormat)
DECL_PRINTER(JSNumberFormat)
DECL_VERIFIER(JSNumberFormat)
// [[Style]] is one of the values "decimal", "percent" or "currency",
// identifying the style of the number format.
enum class Style {
DECIMAL,
PERCENT,
CURRENCY,
COUNT
};
inline void set_style(Style style);
inline Style style() const;
// [[CurrencyDisplay]] is one of the values "code", "symbol" or "name",
// identifying the display of the currency number format.
enum class CurrencyDisplay {
CODE,
SYMBOL,
NAME,
COUNT
};
inline void set_currency_display(CurrencyDisplay currency_display);
inline CurrencyDisplay currency_display() const;
// Layout description.
// Current ECMA 402 spec mandates to record (Min|Max)imumFractionDigits
// unconditionally while the unified number proposal eventually will only
// record either (Min|Max)imumFractionDigits or (Min|Max)imumSignaficantDigits
// Since LocalizedNumberFormatter can only remember one set, and during
// 2019-1-17 ECMA402 meeting that the committee decide not to take a PR to
// address that prior to the unified number proposal, we have to add these two
// 5 bits int into flags to remember the (Min|Max)imumFractionDigits while
// (Min|Max)imumSignaficantDigits is present.
// TODO(ftang) remove the following once we ship int-number-format-unified
// * Four inline functions: (set_)?(min|max)imum_fraction_digits
// * kFlagsOffset
// * #define FLAGS_BIT_FIELDS
// * DECL_INT_ACCESSORS(flags)
inline int minimum_fraction_digits() const;
inline void set_minimum_fraction_digits(int digits);
inline int maximum_fraction_digits() const;
inline void set_maximum_fraction_digits(int digits);
// Layout description.
DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize,
TORQUE_GENERATED_JSNUMBER_FORMAT_FIELDS)
// Bit positions in |flags|.
#define FLAGS_BIT_FIELDS(V, _) \
V(StyleBits, Style, 2, _) \
V(CurrencyDisplayBits, CurrencyDisplay, 2, _)
#define FLAGS_BIT_FIELDS(V, _) \
V(MinimumFractionDigitsBits, int, 5, _) \
V(MaximumFractionDigitsBits, int, 5, _)
DEFINE_BIT_FIELDS(FLAGS_BIT_FIELDS)
#undef FLAGS_BIT_FIELDS
STATIC_ASSERT(Style::DECIMAL <= StyleBits::kMax);
STATIC_ASSERT(Style::PERCENT <= StyleBits::kMax);
STATIC_ASSERT(Style::CURRENCY <= StyleBits::kMax);
STATIC_ASSERT(CurrencyDisplay::CODE <= CurrencyDisplayBits::kMax);
STATIC_ASSERT(CurrencyDisplay::SYMBOL <= CurrencyDisplayBits::kMax);
STATIC_ASSERT(CurrencyDisplay::NAME <= CurrencyDisplayBits::kMax);
STATIC_ASSERT(20 <= MinimumFractionDigitsBits::kMax);
STATIC_ASSERT(20 <= MaximumFractionDigitsBits::kMax);
DECL_ACCESSORS(locale, String)
DECL_ACCESSORS(icu_number_format, Managed<icu::NumberFormat>)
DECL_ACCESSORS(icu_number_formatter,
Managed<icu::number::LocalizedNumberFormatter>)
DECL_ACCESSORS(bound_format, Object)
DECL_INT_ACCESSORS(flags)
......
......@@ -164,9 +164,24 @@ MaybeHandle<JSPluralRules> JSPluralRules::Initialize(
CHECK_NOT_NULL(icu_decimal_format.get());
// 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3).
Maybe<bool> done = Intl::SetNumberFormatDigitOptions(
isolate, icu_decimal_format.get(), options, 0, 3);
MAYBE_RETURN(done, MaybeHandle<JSPluralRules>());
Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
Intl::SetNumberFormatDigitOptions(isolate, options, 0, 3);
MAYBE_RETURN(maybe_digit_options, MaybeHandle<JSPluralRules>());
Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
icu_decimal_format->setRoundingMode(icu::DecimalFormat::kRoundHalfUp);
icu_decimal_format->setMinimumIntegerDigits(
digit_options.minimum_integer_digits);
icu_decimal_format->setMinimumFractionDigits(
digit_options.minimum_fraction_digits);
icu_decimal_format->setMaximumFractionDigits(
digit_options.maximum_fraction_digits);
if (digit_options.minimum_significant_digits > 0) {
icu_decimal_format->setMinimumSignificantDigits(
digit_options.minimum_significant_digits);
icu_decimal_format->setMaximumSignificantDigits(
digit_options.maximum_significant_digits);
}
Handle<Managed<icu::PluralRules>> managed_plural_rules =
Managed<icu::PluralRules>::FromUniquePtr(isolate, 0,
......
......@@ -22,6 +22,7 @@
#include "src/objects/object-macros.h"
namespace U_ICU_NAMESPACE {
class DecimalFormat;
class PluralRules;
} // namespace U_ICU_NAMESPACE
......
......@@ -306,49 +306,49 @@ KNOWN_MAPS = {
("read_only_space", 0x014b9): (98, "EnumCacheMap"),
("read_only_space", 0x01509): (115, "ArrayBoilerplateDescriptionMap"),
("read_only_space", 0x016e1): (101, "InterceptorInfoMap"),
("read_only_space", 0x03445): (89, "AccessCheckInfoMap"),
("read_only_space", 0x0346d): (90, "AccessorInfoMap"),
("read_only_space", 0x03495): (91, "AccessorPairMap"),
("read_only_space", 0x034bd): (92, "AliasedArgumentsEntryMap"),
("read_only_space", 0x034e5): (93, "AllocationMementoMap"),
("read_only_space", 0x0350d): (94, "AsmWasmDataMap"),
("read_only_space", 0x03535): (95, "AsyncGeneratorRequestMap"),
("read_only_space", 0x0355d): (96, "ClassPositionsMap"),
("read_only_space", 0x03585): (97, "DebugInfoMap"),
("read_only_space", 0x035ad): (99, "FunctionTemplateInfoMap"),
("read_only_space", 0x035d5): (100, "FunctionTemplateRareDataMap"),
("read_only_space", 0x035fd): (102, "InterpreterDataMap"),
("read_only_space", 0x03625): (103, "ModuleInfoEntryMap"),
("read_only_space", 0x0364d): (104, "ModuleMap"),
("read_only_space", 0x03675): (105, "ObjectTemplateInfoMap"),
("read_only_space", 0x0369d): (106, "PromiseCapabilityMap"),
("read_only_space", 0x036c5): (107, "PromiseReactionMap"),
("read_only_space", 0x036ed): (108, "PrototypeInfoMap"),
("read_only_space", 0x03715): (109, "ScriptMap"),
("read_only_space", 0x0373d): (110, "SourcePositionTableWithFrameCacheMap"),
("read_only_space", 0x03765): (111, "StackFrameInfoMap"),
("read_only_space", 0x0378d): (112, "StackTraceFrameMap"),
("read_only_space", 0x037b5): (113, "Tuple2Map"),
("read_only_space", 0x037dd): (114, "Tuple3Map"),
("read_only_space", 0x03805): (116, "WasmCapiFunctionDataMap"),
("read_only_space", 0x0382d): (117, "WasmDebugInfoMap"),
("read_only_space", 0x03855): (118, "WasmExceptionTagMap"),
("read_only_space", 0x0387d): (119, "WasmExportedFunctionDataMap"),
("read_only_space", 0x038a5): (120, "CallableTaskMap"),
("read_only_space", 0x038cd): (121, "CallbackTaskMap"),
("read_only_space", 0x038f5): (122, "PromiseFulfillReactionJobTaskMap"),
("read_only_space", 0x0391d): (123, "PromiseRejectReactionJobTaskMap"),
("read_only_space", 0x03945): (124, "PromiseResolveThenableJobTaskMap"),
("read_only_space", 0x0396d): (125, "FinalizationGroupCleanupJobTaskMap"),
("read_only_space", 0x03995): (126, "AllocationSiteWithWeakNextMap"),
("read_only_space", 0x039bd): (126, "AllocationSiteWithoutWeakNextMap"),
("read_only_space", 0x039e5): (161, "LoadHandler1Map"),
("read_only_space", 0x03a0d): (161, "LoadHandler2Map"),
("read_only_space", 0x03a35): (161, "LoadHandler3Map"),
("read_only_space", 0x03a5d): (169, "StoreHandler0Map"),
("read_only_space", 0x03a85): (169, "StoreHandler1Map"),
("read_only_space", 0x03aad): (169, "StoreHandler2Map"),
("read_only_space", 0x03ad5): (169, "StoreHandler3Map"),
("read_only_space", 0x03559): (89, "AccessCheckInfoMap"),
("read_only_space", 0x03581): (90, "AccessorInfoMap"),
("read_only_space", 0x035a9): (91, "AccessorPairMap"),
("read_only_space", 0x035d1): (92, "AliasedArgumentsEntryMap"),
("read_only_space", 0x035f9): (93, "AllocationMementoMap"),
("read_only_space", 0x03621): (94, "AsmWasmDataMap"),
("read_only_space", 0x03649): (95, "AsyncGeneratorRequestMap"),
("read_only_space", 0x03671): (96, "ClassPositionsMap"),
("read_only_space", 0x03699): (97, "DebugInfoMap"),
("read_only_space", 0x036c1): (99, "FunctionTemplateInfoMap"),
("read_only_space", 0x036e9): (100, "FunctionTemplateRareDataMap"),
("read_only_space", 0x03711): (102, "InterpreterDataMap"),
("read_only_space", 0x03739): (103, "ModuleInfoEntryMap"),
("read_only_space", 0x03761): (104, "ModuleMap"),
("read_only_space", 0x03789): (105, "ObjectTemplateInfoMap"),
("read_only_space", 0x037b1): (106, "PromiseCapabilityMap"),
("read_only_space", 0x037d9): (107, "PromiseReactionMap"),
("read_only_space", 0x03801): (108, "PrototypeInfoMap"),
("read_only_space", 0x03829): (109, "ScriptMap"),
("read_only_space", 0x03851): (110, "SourcePositionTableWithFrameCacheMap"),
("read_only_space", 0x03879): (111, "StackFrameInfoMap"),
("read_only_space", 0x038a1): (112, "StackTraceFrameMap"),
("read_only_space", 0x038c9): (113, "Tuple2Map"),
("read_only_space", 0x038f1): (114, "Tuple3Map"),
("read_only_space", 0x03919): (116, "WasmCapiFunctionDataMap"),
("read_only_space", 0x03941): (117, "WasmDebugInfoMap"),
("read_only_space", 0x03969): (118, "WasmExceptionTagMap"),
("read_only_space", 0x03991): (119, "WasmExportedFunctionDataMap"),
("read_only_space", 0x039b9): (120, "CallableTaskMap"),
("read_only_space", 0x039e1): (121, "CallbackTaskMap"),
("read_only_space", 0x03a09): (122, "PromiseFulfillReactionJobTaskMap"),
("read_only_space", 0x03a31): (123, "PromiseRejectReactionJobTaskMap"),
("read_only_space", 0x03a59): (124, "PromiseResolveThenableJobTaskMap"),
("read_only_space", 0x03a81): (125, "FinalizationGroupCleanupJobTaskMap"),
("read_only_space", 0x03aa9): (126, "AllocationSiteWithWeakNextMap"),
("read_only_space", 0x03ad1): (126, "AllocationSiteWithoutWeakNextMap"),
("read_only_space", 0x03af9): (161, "LoadHandler1Map"),
("read_only_space", 0x03b21): (161, "LoadHandler2Map"),
("read_only_space", 0x03b49): (161, "LoadHandler3Map"),
("read_only_space", 0x03b71): (169, "StoreHandler0Map"),
("read_only_space", 0x03b99): (169, "StoreHandler1Map"),
("read_only_space", 0x03bc1): (169, "StoreHandler2Map"),
("read_only_space", 0x03be9): (169, "StoreHandler3Map"),
("map_space", 0x00141): (1057, "ExternalMap"),
("map_space", 0x00169): (1073, "JSMessageObjectMap"),
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment